From ec9ca07f537d0e957734b044f0456deb56c4a005 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Sun, 30 Aug 2026 14:35:14 +0000 Subject: [PATCH 1/7] =?UTF-8?q?Prose=20bankruptcy:=20module-scope=20commen?= =?UTF-8?q?tary=20rows=20become=20=C2=A74c=20annotations=20corpus-wide;=20?= =?UTF-8?q?docs=20tightened?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LICENSING.md | 29 +- dag/examples/weather/README.md | 8 +- dag/extdeps/access/posix.dag | 12 +- .../posix_effective_principal_read_op.dag | 5 +- dag/extdeps/accessibility/wcag_contrast.dag | 18 +- dag/extdeps/advertising/google_ads.dag | 35 +- dag/extdeps/advertising/meta_ads.dag | 17 +- dag/extdeps/advertising/types.dag | 11 +- .../ampere/mt_collins_product_brief/bmc.dag | 15 +- .../memory_population.dag | 7 +- .../mt_collins_product_brief/platform.dag | 8 +- .../mt_collins_product_brief/subject.dag | 5 +- dag/extdeps/ampere/scp_diagnostic.dag | 18 +- dag/extdeps/archive/format.dag | 5 +- .../astronomy/stellar_classification.dag | 8 +- dag/extdeps/auth/oidc.dag | 35 +- dag/extdeps/bmc/aspeed_ast2500.dag | 11 +- dag/extdeps/bmc/http.dag | 51 +- dag/extdeps/bmc/megarac.dag | 20 +- dag/extdeps/bmc/openbmc_fan_control.dag | 20 +- dag/extdeps/bmc/openbmc_operation.dag | 6 +- .../bmc/openbmc_password_ssh_transport.dag | 6 +- dag/extdeps/bmc/pid_control_program.dag | 6 +- dag/extdeps/bmc/types.dag | 7 +- dag/extdeps/bmc/virtual_media.dag | 8 +- dag/extdeps/boards/asrock_rack.dag | 65 +- dag/extdeps/boards/gigabyte.dag | 81 +- dag/extdeps/browser/browser.dag | 13 +- dag/extdeps/browser/google_chrome.dag | 8 +- dag/extdeps/cache/materialization.dag | 26 +- dag/extdeps/cache/pin.dag | 16 +- dag/extdeps/cache/sccache.dag | 92 +- dag/extdeps/clock/ti_cdce913.dag | 11 +- dag/extdeps/cloud/ubicloud.dag | 21 +- dag/extdeps/cloud_init/cloud_init.dag | 36 +- dag/extdeps/color/okabe_ito.dag | 6 +- dag/extdeps/color/solarized.dag | 6 +- dag/extdeps/color/srgb.dag | 30 +- .../connector/samtec_ftsh_105_01_l_dv.dag | 11 +- dag/extdeps/container/engine.dag | 36 +- dag/extdeps/container/oci/digest.dag | 13 +- dag/extdeps/container/oci/reference.dag | 39 +- dag/extdeps/cooling/types.dag | 12 +- dag/extdeps/cpu/amd.dag | 41 +- .../cpu/ampere_altra_memory_controller.dag | 21 +- dag/extdeps/cpu/ampere_altra_package.dag | 26 +- .../cpu/ampere_altra_package_table4_raw.dag | 7 +- .../platform.dag | 63 +- .../subject.dag | 11 +- dag/extdeps/cpu_attachment/ilm4926.dag | 17 +- dag/extdeps/crm/hubspot_lifecycle.dag | 9 +- dag/extdeps/crm/salesforce_opportunity.dag | 7 +- dag/extdeps/crypto/hash.dag | 9 +- dag/extdeps/darwin/rusage.dag | 13 +- dag/extdeps/darwin/sysctl.dag | 22 +- dag/extdeps/dhcp/v4.dag | 8 +- dag/extdeps/exec/command.dag | 33 +- dag/extdeps/exec/exec_arg_limit.dag | 15 +- dag/extdeps/exec/xargs.dag | 22 +- dag/extdeps/external_authority.dag | 34 +- dag/extdeps/filesystem/filesystem_io.dag | 23 +- dag/extdeps/filesystem/linux.dag | 27 +- dag/extdeps/firmware/megarac/subject.dag | 10 +- dag/extdeps/firmware/openbmc/subject.dag | 18 +- .../firmware/tianocore_edk2/subject.dag | 8 +- .../firmware/trusted_firmware_a/subject.dag | 10 +- dag/extdeps/firmware/types.dag | 9 +- dag/extdeps/firmware/uefi_shell.dag | 13 +- dag/extdeps/git/git.dag | 94 +- dag/extdeps/git/inspect.dag | 7 +- dag/extdeps/git/object_store.dag | 10 +- dag/extdeps/git/plumbing.dag | 11 +- dag/extdeps/github/actions.dag | 51 +- dag/extdeps/github/actions_environment.dag | 22 +- dag/extdeps/github/actions_token.dag | 35 +- dag/extdeps/github/auth.dag | 14 +- dag/extdeps/github/ci_runner.dag | 5 +- dag/extdeps/github/expressions.dag | 39 +- dag/extdeps/github/github.dag | 12 +- dag/extdeps/github/hosted_runners.dag | 13 +- dag/extdeps/github/issues.dag | 85 +- dag/extdeps/github/pulls.dag | 15 +- dag/extdeps/github/push_event.dag | 5 +- dag/extdeps/github/rulesets.dag | 54 +- dag/extdeps/github/workflow_run_event.dag | 80 +- dag/extdeps/github/workflows.dag | 16 +- dag/extdeps/http/server.dag | 13 +- dag/extdeps/instrument/pip_boy_3000.dag | 30 +- dag/extdeps/instrument/pip_boy_3000_mk_v.dag | 23 +- dag/extdeps/ipc/dpmx.dag | 14 +- dag/extdeps/iproute2/ss.dag | 4 +- dag/extdeps/jobserver.dag | 24 +- dag/extdeps/languages/bash/subject.dag | 31 +- dag/extdeps/languages/c/subject.dag | 8 +- dag/extdeps/languages/cpp/subject.dag | 10 +- dag/extdeps/languages/css/properties.dag | 12 +- dag/extdeps/languages/css/selectors.dag | 14 +- dag/extdeps/languages/css/subject.dag | 10 +- dag/extdeps/languages/css/values.dag | 16 +- dag/extdeps/languages/ecmascript/subject.dag | 11 +- dag/extdeps/languages/go/subject.dag | 8 +- dag/extdeps/languages/html.dag | 9 +- dag/extdeps/languages/html/subject.dag | 8 +- dag/extdeps/languages/java/subject.dag | 9 +- dag/extdeps/languages/json/emit.dag | 8 +- dag/extdeps/languages/json/grammar.dag | 48 +- dag/extdeps/languages/json/parse.dag | 52 +- dag/extdeps/languages/json/subject.dag | 8 +- dag/extdeps/languages/jsx/subject.dag | 10 +- dag/extdeps/languages/kotlin/subject.dag | 9 +- dag/extdeps/languages/lean/overflow.dag | 6 +- dag/extdeps/languages/lean/subject.dag | 10 +- dag/extdeps/languages/llvm_ir/subject.dag | 10 +- dag/extdeps/languages/markdown/subject.dag | 11 +- dag/extdeps/languages/ptx/subject.dag | 9 +- dag/extdeps/languages/python/subject.dag | 19 +- dag/extdeps/languages/react_jsx.dag | 9 +- dag/extdeps/languages/riscv/subject.dag | 13 +- dag/extdeps/languages/rust/capabilities.dag | 18 +- .../languages/rust/derive_contracts.dag | 34 +- dag/extdeps/languages/rust/emit.dag | 23 +- dag/extdeps/languages/rust/representation.dag | 38 +- dag/extdeps/languages/rust/subject.dag | 20 +- dag/extdeps/languages/rust/types.dag | 18 +- dag/extdeps/languages/spice/subject.dag | 33 +- dag/extdeps/languages/swift/subject.dag | 9 +- dag/extdeps/languages/typescript/program.dag | 72 +- dag/extdeps/languages/typescript/subject.dag | 11 +- dag/extdeps/languages/verilog/subject.dag | 13 +- dag/extdeps/languages/wasm/subject.dag | 8 +- dag/extdeps/languages/yaml/gha_workflow.dag | 45 +- dag/extdeps/languages/yaml/subject.dag | 8 +- dag/extdeps/linux/cgroup_v2_memory.dag | 31 +- dag/extdeps/linux/proc_self_cgroup.dag | 7 +- dag/extdeps/linux/rusage.dag | 11 +- dag/extdeps/llm/claude_agent_sdk_stream.dag | 10 +- dag/extdeps/llm/claude_setup_token_cli.dag | 11 +- dag/extdeps/llm/cli.dag | 86 +- dag/extdeps/llm/cli_lifecycle.dag | 11 +- dag/extdeps/llm/codex_app_server.dag | 101 +- .../llm/codex_app_server_stdio_session.dag | 16 +- dag/extdeps/llm/codex_auth.dag | 108 +- dag/extdeps/llm/cursor_cli.dag | 56 +- dag/extdeps/llm/cursor_sdk.dag | 13 +- dag/extdeps/llm/cursor_sdk_errors.dag | 4 +- dag/extdeps/llm/cursor_sdk_facts.dag | 28 +- dag/extdeps/llm/cursor_stream.dag | 3 +- dag/extdeps/memory/macronix_mx25l25673g.dag | 9 +- dag/extdeps/memory/microchip_at24cm02.dag | 10 +- dag/extdeps/memory/te_ddr4_dimm_socket.dag | 18 +- dag/extdeps/memory/training.dag | 21 +- dag/extdeps/netplan/netplan.dag | 32 +- dag/extdeps/network/address.dag | 7 +- dag/extdeps/network/endpoint.dag | 7 +- dag/extdeps/network/ipv4.dag | 12 +- dag/extdeps/network/ipv6.dag | 12 +- dag/extdeps/network/reach.dag | 16 +- dag/extdeps/network/ti_dp83867.dag | 12 +- dag/extdeps/npm.dag | 58 +- dag/extdeps/numeric/base16.dag | 9 +- dag/extdeps/ocp/mt_jade/subject.dag | 12 +- dag/extdeps/ocp/mt_mitchell/platform.dag | 35 +- dag/extdeps/ocp/mt_mitchell/subject.dag | 8 +- dag/extdeps/ocp/publication.dag | 6 +- dag/extdeps/ollama/binary_release.dag | 11 +- dag/extdeps/ollama/gpu_support.dag | 4 +- dag/extdeps/os/ubuntu_autoinstall.dag | 7 +- dag/extdeps/package.dag | 48 +- .../physics/bach_et_al_2013_double_slit.dag | 18 +- ...al_1995_atom_interferometer_which_path.dag | 12 +- dag/extdeps/pin.dag | 202 +- dag/extdeps/posix/rusage.dag | 21 +- dag/extdeps/posix/shell_command_language.dag | 96 +- dag/extdeps/power/ti_tps3808.dag | 12 +- dag/extdeps/power/ti_tps53688.dag | 14 +- dag/extdeps/pricing/aws_ec2.dag | 4 +- dag/extdeps/pricing/billing_units.dag | 27 +- dag/extdeps/pricing/github_actions.dag | 3 +- dag/extdeps/pricing/gitlab_subscription.dag | 23 +- dag/extdeps/pricing/hetzner_dedicated.dag | 4 +- dag/extdeps/pricing/object_storage.dag | 105 +- dag/extdeps/pricing/regional_compute.dag | 36 +- dag/extdeps/pricing/ubicloud.dag | 14 +- .../probes/provider_interface_acquisition.dag | 4 +- dag/extdeps/procps/pgrep.dag | 3 +- .../ice40/configuration.dag | 61 +- .../programmable_logic/ice40/subject.dag | 25 +- .../programmable_logic/ice40/supply.dag | 16 +- dag/extdeps/project_schedule/cpm_pert.dag | 15 +- .../ubuntu_install_media_fetch.dag | 26 +- .../ubuntu_seeded_install_media_remaster.dag | 17 +- dag/extdeps/publication.dag | 7 +- dag/extdeps/realization/artifact_store_fs.dag | 61 +- .../realization/emit_on_demand_host.dag | 42 +- dag/extdeps/realization/eval_memo.dag | 35 +- .../realization/frame_boundary_simulated.dag | 14 +- .../realization/native_artifact_cache.dag | 13 +- dag/extdeps/render/ansi.dag | 15 +- dag/extdeps/render/glyphs.dag | 8 +- dag/extdeps/render/terminal.dag | 8 +- dag/extdeps/rust/cargo_build.dag | 7 +- dag/extdeps/rust/cargo_message.dag | 34 +- dag/extdeps/rust/rustc.dag | 24 +- dag/extdeps/rust/target_triple.dag | 27 +- dag/extdeps/sec/facts.dag | 9 +- dag/extdeps/sec/issuer/atlassian_facts.dag | 19 +- dag/extdeps/sec/issuer/chemed_facts.dag | 16 +- dag/extdeps/sec/issuer/gitlab_facts.dag | 53 +- dag/extdeps/sec/issuer/microsoft_facts.dag | 24 +- dag/extdeps/sec/issuer/rollins_facts.dag | 11 +- dag/extdeps/sec/operating_metrics.dag | 7 +- dag/extdeps/sec/types.dag | 10 +- dag/extdeps/shell.dag | 59 +- dag/extdeps/shell/exec.dag | 12 +- .../simulators/icarus_verilog/subject.dag | 9 +- dag/extdeps/simulators/ngspice/subject.dag | 13 +- dag/extdeps/ssh/client_options.dag | 205 +- dag/extdeps/ssh/keys.dag | 13 +- dag/extdeps/ssh/password_session.dag | 8 +- dag/extdeps/ssh/session.dag | 60 +- dag/extdeps/standards/ieee_754_2019.dag | 16 +- dag/extdeps/standards/web_annotation.dag | 7 +- dag/extdeps/storage/amphenol_mdt420m01001.dag | 13 +- dag/extdeps/storage/te_m2_ngff_connector.dag | 14 +- dag/extdeps/sudo/nopasswd_execute_probe.dag | 15 +- .../sudo/nopasswd_execute_probe_check_op.dag | 6 +- dag/extdeps/systemd/loginctl.dag | 9 +- dag/extdeps/tailscale/serve.dag | 27 +- dag/extdeps/tmux/tmux.dag | 64 +- dag/extdeps/toolchain/icestorm/subject.dag | 24 +- dag/extdeps/tools.dag | 17 +- dag/extdeps/tools/diffutils.dag | 5 +- dag/extdeps/tools/jq.dag | 7 +- dag/extdeps/tools/node.dag | 5 +- dag/extdeps/tools/npm.dag | 12 +- dag/extdeps/tools/pin.dag | 73 +- dag/extdeps/tools/sha256sum.dag | 13 +- dag/extdeps/tools/sha512sum.dag | 15 +- dag/extdeps/transports/rest.dag | 135 +- dag/extdeps/ucamco/gerber.dag | 25 +- dag/extdeps/unicode/blocks.dag | 3 +- dag/extdeps/units/dimensionless.dag | 7 +- dag/extdeps/units/iec_80000_13.dag | 3 +- dag/extdeps/units/iso8601.dag | 3 +- dag/extdeps/units/iso_80000_3.dag | 32 +- dag/extdeps/uri.dag | 8 +- dag/extdeps/uri_path.dag | 21 +- dag/extdeps/uuid/uuid.dag | 15 +- dag/extdeps/vendor/amphenol.dag | 13 +- dag/extdeps/vendor/aspeed.dag | 5 +- dag/extdeps/vendor/lattice_semiconductor.dag | 5 +- dag/extdeps/vendor/lotes.dag | 4 +- dag/extdeps/vendor/macronix.dag | 5 +- dag/extdeps/vendor/microchip_technology.dag | 6 +- dag/extdeps/vendor/samtec.dag | 10 +- dag/extdeps/vendor/texas_instruments.dag | 7 +- dag/extdeps/vendor/the_wand_company.dag | 6 +- dag/extdeps/vendor/tyco_electronics.dag | 10 +- .../vendor/tyco_electronics_amp_kk.dag | 9 +- dag/extdeps/w3c/accelerometer.dag | 22 +- .../w3c/device_orientation_and_motion.dag | 67 +- dag/extdeps/w3c/generic_sensor.dag | 60 +- dag/gunbc/apply.dag | 5 +- .../assimilate/bmc_bootstrap_provision.dag | 6 +- dag/gunbc/assimilate/bmc_token_federation.dag | 13 +- dag/gunbc/auth/access_token_source.dag | 11 +- dag/gunbc/auth/github_gcp_federation.dag | 7 +- dag/gunbc/auth/materialized_secret.dag | 102 +- dag/gunbc/bach_double_slit_observation.dag | 18 +- ...slit_path_additivity_synthetic_fixture.dag | 4 +- .../bare_reference_scanner_admission.dag | 75 +- dag/gunbc/bmc/bmc_converge.dag | 33 +- dag/gunbc/bmc/bmc_fan_converge.dag | 12 +- dag/gunbc/bmc/bmc_fan_projection.dag | 35 +- dag/gunbc/bmc/bmc_intent.dag | 39 +- dag/gunbc/bmc/bmc_netboot_serve.dag | 7 +- .../build_cache_endpoint_observe.dag | 176 +- .../build_cache/build_cache_endpoint_path.dag | 76 +- dag/gunbc/build_cache/build_cache_ensure.dag | 134 +- .../build_cache/build_cache_instance.dag | 148 +- .../build_cache_instance_readiness.dag | 22 +- .../build_cache/build_cache_latency_probe.dag | 92 +- .../build_cache_provision_verdict.dag | 136 +- dag/gunbc/build_cache/build_cache_unit.dag | 133 +- dag/gunbc/capability_binding.dag | 102 +- dag/gunbc/capability_origin.dag | 43 +- dag/gunbc/change_realization.dag | 53 +- dag/gunbc/ci/ci_budget_tree.dag | 14 +- .../ci_build_job_v1_compiler_unit_receipt.dag | 33 +- dag/gunbc/ci/ci_deploy_access.dag | 12 +- dag/gunbc/ci/ci_deploy_sudoers.dag | 6 +- dag/gunbc/ci/ci_failure_class.dag | 26 +- dag/gunbc/ci/ci_floor_measurement.dag | 104 +- dag/gunbc/ci/ci_gate.dag | 45 +- dag/gunbc/ci/ci_layer_roots.dag | 120 +- dag/gunbc/ci/ci_materialization.dag | 150 +- dag/gunbc/ci/ci_runner_placement.dag | 96 +- dag/gunbc/ci/ci_runner_target.dag | 19 +- dag/gunbc/ci/ci_spec.dag | 937 +++-- dag/gunbc/ci/ci_workflow_expressions.dag | 82 +- dag/gunbc/citation/pdf_safe_profile.dag | 4 +- dag/gunbc/claim_unwind_seed_growth.dag | 57 +- dag/gunbc/class_b_import_closure_overlay.dag | 13 +- dag/gunbc/claude_setup_token_enrollment.dag | 75 +- dag/gunbc/cli_run_hand_rust_area_ledger.dag | 88 +- dag/gunbc/cli_run_repo_grant.dag | 11 +- dag/gunbc/codex_app_server_press.dag | 137 +- dag/gunbc/codex_provider_runtime_receipt.dag | 12 +- dag/gunbc/codex_runtime_bundle.dag | 12 +- dag/gunbc/codex_runtime_paths.dag | 6 +- dag/gunbc/codex_supervised_turn.dag | 55 +- dag/gunbc/command_runner.dag | 7 +- dag/gunbc/commit_workflow.dag | 304 +- dag/gunbc/compile_clean_diagnostic_policy.dag | 18 +- dag/gunbc/compile_diagnostic_census.dag | 65 +- dag/gunbc/compile_pool_ensure.dag | 14 +- dag/gunbc/credential_argv_exposure.dag | 55 +- dag/gunbc/cursor_sdk_auth_probe.dag | 7 +- dag/gunbc/cursor_sdk_local_binding.dag | 4 +- dag/gunbc/cursor_sdk_provider_standing.dag | 5 +- dag/gunbc/cursor_sdk_secret_admission.dag | 3 +- dag/gunbc/dag_grammar_fork_census.dag | 11 +- dag/gunbc/declaration_index_seed_growth.dag | 464 ++- dag/gunbc/declared_import_closure_binding.dag | 11 +- dag/gunbc/deploy_transition_bootstrap.dag | 233 +- dag/gunbc/deployed_tree_remote.dag | 58 +- dag/gunbc/design/archetype.dag | 25 +- dag/gunbc/design/catalog.dag | 5 +- dag/gunbc/design/component.dag | 14 +- dag/gunbc/design/instrument_audition.dag | 82 +- dag/gunbc/design/instrument_bob.dag | 111 +- dag/gunbc/design/instrument_camera.dag | 62 +- dag/gunbc/design/instrument_physical.dag | 91 +- dag/gunbc/design/instrument_projection.dag | 30 +- dag/gunbc/design/interaction.dag | 26 +- dag/gunbc/design/interaction_cause.dag | 17 +- dag/gunbc/design/material.dag | 37 +- dag/gunbc/design/palette.dag | 17 +- dag/gunbc/design/principles.dag | 15 +- dag/gunbc/design/reference_instrument.dag | 103 +- dag/gunbc/design/salience.dag | 96 +- dag/gunbc/design/scale.dag | 53 +- dag/gunbc/design/sound.dag | 6 +- dag/gunbc/design/state_response.dag | 30 +- dag/gunbc/design/theme.dag | 14 +- dag/gunbc/design/theme_transition.dag | 17 +- dag/gunbc/devboot/program.dag | 17 +- dag/gunbc/devboot/transport.dag | 8 +- dag/gunbc/diff_baseline.dag | 56 +- dag/gunbc/dispatch_pipe_pane_emit.dag | 23 +- dag/gunbc/dispatch_preflight.dag | 88 +- dag/gunbc/dispatch_selection.dag | 56 +- dag/gunbc/doc_graph_roots.dag | 95 +- dag/gunbc/documentary_refs.dag | 40 +- dag/gunbc/econ/acquisition.dag | 24 +- dag/gunbc/econ/free_tier_serving.dag | 22 +- dag/gunbc/econ/knowable_wedge.dag | 8 +- dag/gunbc/econ/llm_attempt_receipt.dag | 108 +- dag/gunbc/econ/regional_compute_premium.dag | 24 +- dag/gunbc/econ/scm_serving_model.dag | 49 +- .../emit_stage_blocking_population_census.dag | 51 +- .../emitted_closure_compile_seed_growth.dag | 93 +- dag/gunbc/emitted_record_deserialize_gap.dag | 27 +- .../endpoint_authority_replacement_cut.dag | 36 +- dag/gunbc/ensure.dag | 10 +- dag/gunbc/executor_schedule_retention.dag | 25 +- dag/gunbc/explicit_witness_admission.dag | 146 +- dag/gunbc/extdeps_scope_frontier.dag | 105 +- .../fabric/fabric_capacity_class_gap.dag | 124 +- dag/gunbc/fabric/fabric_cell_converge.dag | 9 +- .../fabric/fabric_control_plane_charge.dag | 56 +- dag/gunbc/fabric/fabric_witness_run.dag | 403 +-- .../fleet/fleet_bmc_firmware_evidence.dag | 10 +- dag/gunbc/fleet/fleet_bmc_observation.dag | 54 +- dag/gunbc/fleet/fleet_bmc_state.dag | 9 +- dag/gunbc/fleet/fleet_capacity_control.dag | 4 +- dag/gunbc/fleet/fleet_capacity_panel.dag | 7 +- dag/gunbc/fleet/fleet_converge_cli.dag | 4 +- dag/gunbc/fleet/fleet_converge_plan.dag | 28 +- dag/gunbc/fleet/fleet_converge_workflow.dag | 15 +- dag/gunbc/fleet/fleet_desired_admission.dag | 3 +- .../fleet_desired_admission_workflow.dag | 2 +- dag/gunbc/fleet/fleet_desired_observe.dag | 6 +- .../fleet/fleet_hardware_standing_panel.dag | 4 +- dag/gunbc/fleet/fleet_host_budget.dag | 54 +- dag/gunbc/fleet/fleet_intent.dag | 34 +- dag/gunbc/fleet/fleet_intent_network.dag | 46 +- dag/gunbc/fleet/fleet_posix_accounts.dag | 63 +- .../fleet/fleet_probe_identity_observe.dag | 23 +- dag/gunbc/fleet/fleet_reach.dag | 41 +- dag/gunbc/fleet/fleet_runner_connectivity.dag | 141 +- .../fleet_shell_transport_observation.dag | 26 +- dag/gunbc/fleet/fleet_workflow_steps.dag | 59 +- .../float_add_associativity_observation.dag | 13 +- dag/gunbc/floor/floor_component_receipt.dag | 180 +- .../floor_component_receipt_document.dag | 372 +- dag/gunbc/floor/floor_discovery_scaffold.dag | 12 +- dag/gunbc/floor/floor_materialization.dag | 91 +- .../floor_non_verdict_classification.dag | 101 +- dag/gunbc/generated_artifact.dag | 25 +- dag/gunbc/generated_artifact_assessment.dag | 89 +- ...enerated_artifact_boundary_seed_growth.dag | 90 +- dag/gunbc/generated_artifact_emit.dag | 10 +- dag/gunbc/generated_artifact_merge_driver.dag | 60 +- dag/gunbc/generated_artifact_observation.dag | 28 +- dag/gunbc/generated_projection_paths.dag | 20 +- ...eneric_binder_field_projection_deficit.dag | 96 +- dag/gunbc/git_diff_change_window.dag | 6 +- dag/gunbc/gitattributes_emit.dag | 34 +- .../githooks/githooks_pre_commit_emit.dag | 19 +- dag/gunbc/githooks/githooks_pre_push_plan.dag | 8 +- .../githooks_repo_local_git_config_emit.dag | 16 +- dag/gunbc/guarantee_measurement.dag | 87 +- dag/gunbc/guarantee_probe_corpus.dag | 75 +- .../hand_lens_host_bridge_scaffold_index.dag | 12 +- dag/gunbc/host/host_assimilation_program.dag | 113 +- .../host/host_authorized_keys_reconcile.dag | 50 +- dag/gunbc/host/host_axis_caps.dag | 9 +- dag/gunbc/host/host_budget_source.dag | 138 +- dag/gunbc/host/host_build_cache_provision.dag | 27 +- dag/gunbc/host/host_cli_dependency.dag | 26 +- .../host/host_codex_runtime_provision.dag | 25 +- .../host_codex_runtime_provision_verdict.dag | 17 +- dag/gunbc/host/host_compile_pool.dag | 77 +- .../host/host_compile_pool_provision.dag | 22 +- dag/gunbc/host/host_converge.dag | 55 +- dag/gunbc/host/host_converge_slice1.dag | 66 +- dag/gunbc/host/host_diagnostic_channel.dag | 25 +- dag/gunbc/host/host_disk_reclaim.dag | 178 +- dag/gunbc/host/host_disk_reclaim_units.dag | 3 +- dag/gunbc/host/host_effect_realize.dag | 208 +- dag/gunbc/host/host_identity_access.dag | 30 +- dag/gunbc/host/host_layout.dag | 199 +- dag/gunbc/host/host_memory_qualification.dag | 45 +- dag/gunbc/host/host_network_diagnosis.dag | 57 +- dag/gunbc/host/host_phase_status.dag | 138 +- dag/gunbc/host/host_realization.dag | 37 +- dag/gunbc/host/host_resource.dag | 43 +- .../host/host_runner_memory_cap_plan_emit.dag | 5 +- .../host/host_runner_memory_cap_verify.dag | 7 +- .../host/host_runner_memory_provision.dag | 14 +- dag/gunbc/host/host_standup.dag | 31 +- dag/gunbc/host/host_swap_backing.dag | 83 +- dag/gunbc/host/host_toolchain_components.dag | 70 +- dag/gunbc/host/host_toolchain_ensure.dag | 35 +- dag/gunbc/hostname_read.dag | 6 +- .../body_lowering_population_probe.dag | 287 +- .../instruments/cas_store_live_probe.dag | 29 +- dag/gunbc/instruments/cheap_gate_pool.dag | 36 +- dag/gunbc/instruments/ci_heal_dispatch.dag | 8 +- .../instruments/dag_compile_clean_gate.dag | 10 +- .../dag_compile_clean_partition.dag | 13 +- .../instruments/dag_compile_clean_scope.dag | 11 +- .../dag_compile_clean_seam_transport.dag | 6 +- .../dag_compile_clean_shard_roster.dag | 8 +- .../dag_compile_clean_transport.dag | 28 +- .../direct_rust_door_transport.dag | 5 +- .../e0599_emitter_decision_census.dag | 78 +- dag/gunbc/instruments/emit_host_gate.dag | 13 +- dag/gunbc/instruments/emit_host_transport.dag | 60 +- .../extdeps_external_authority_gate.dag | 10 +- .../extdeps_external_authority_transport.dag | 15 +- .../extdeps_scope_placement_gate.dag | 51 +- .../fabric_control_plane_live_probe.dag | 73 +- .../instruments/floor_effect_gate_witness.dag | 25 +- .../instruments/frontier_ingestion_probe.dag | 198 +- .../instruments/generated_artifact_gate.dag | 38 +- dag/gunbc/instruments/host_prelude.dag | 53 +- ...spatch_bijection_real_roster_transport.dag | 29 +- .../instruments/merge_admission_capture.dag | 23 +- .../instruments/merge_admission_walk.dag | 63 +- .../module_impact_query_front_door.dag | 55 +- .../multi_module_compile_fixture.dag | 46 +- ...ce_import_closure_behavioral_transport.dag | 44 +- .../prose_row_introduction_gate.dag | 66 +- .../instruments/publication_publisher.dag | 6 +- .../instruments/publication_push_shadow.dag | 5 +- .../rust_item_census_instrument.dag | 5 +- ...host_03_normalize_declared_source_refs.dag | 31 +- .../self_host_curated_probe_cargo.dag | 6 +- .../self_host_curated_seed_linked_harness.dag | 68 +- .../self_host_logic_behavioral_transport.dag | 25 +- ...ost_module_behavioral_transport_roster.dag | 17 +- dag/gunbc/interpreter_replacement_cut.dag | 162 +- dag/gunbc/lambda_argument_typing_gate.dag | 5 +- dag/gunbc/language_module_home.dag | 95 +- dag/gunbc/language_source_scaffold_index.dag | 22 +- dag/gunbc/language_target_registry.dag | 336 +- .../legacy_test_behavior_disposition.dag | 37 +- dag/gunbc/legacy_test_inflow.dag | 103 +- dag/gunbc/live_deploy/apply.dag | 12 +- dag/gunbc/live_deploy/candidate.dag | 68 +- dag/gunbc/live_deploy/deployed_tree_scope.dag | 84 +- dag/gunbc/live_deploy/emit.dag | 205 +- dag/gunbc/live_deploy/intent.dag | 5 +- dag/gunbc/live_deploy/readiness.dag | 81 +- dag/gunbc/live_deploy/revision.dag | 78 +- dag/gunbc/live_deploy/service_ready.dag | 44 +- dag/gunbc/live_deploy/spec.dag | 315 +- dag/gunbc/live_deploy/target_decision.dag | 96 +- dag/gunbc/live_rust_observation.dag | 48 +- dag/gunbc/local_tidy_spec.dag | 36 +- dag/gunbc/managed_directory.dag | 59 +- ...lization_kernel_closing_frontier_audit.dag | 27 +- .../materialization_provider_targets.dag | 26 +- dag/gunbc/measurement_provenance.dag | 119 +- dag/gunbc/membership_reconcile.dag | 70 +- dag/gunbc/memory_provisioning.dag | 358 +- dag/gunbc/merge_admission.dag | 79 +- dag/gunbc/merge_admission_produce.dag | 25 +- dag/gunbc/merge_admission_subject.dag | 39 +- dag/gunbc/merge_lifecycle.dag | 72 +- .../namespace/namespace_census_receipt.dag | 7 +- .../namespace_clause_e_projection_law.dag | 12 +- .../namespace_cut_subject_observation.dag | 71 +- .../namespace_cut_subject_roster.dag | 39 +- .../namespace/namespace_pool_independence.dag | 46 +- ...ace_reference_derived_closure_contract.dag | 27 +- ...uctural_observation_bridge_seed_growth.dag | 5 +- ...space_structural_observations_contract.dag | 4 +- ...ace_structural_observations_production.dag | 4 +- .../namespace/namespace_wave_admission.dag | 140 +- dag/gunbc/native_scm_interaction_contract.dag | 19 +- .../native_witness_transition_receipt.dag | 63 +- dag/gunbc/node_http_server_emit.dag | 13 +- dag/gunbc/non_fold_residue.dag | 12 +- dag/gunbc/observation_ci_render.dag | 112 +- dag/gunbc/observation_emit_census.dag | 163 +- dag/gunbc/observation_seed_render.dag | 52 +- dag/gunbc/observation_tty_render.dag | 22 +- ...occurrence_identity_acceptance_closure.dag | 40 +- dag/gunbc/offline_local_recipe.dag | 18 +- dag/gunbc/ollama_runtime_bundle.dag | 10 +- dag/gunbc/oomd_install.dag | 19 +- dag/gunbc/os_install_mechanism.dag | 23 +- dag/gunbc/output_policy.dag | 80 +- dag/gunbc/ownership.dag | 14 +- dag/gunbc/package_delivery.dag | 117 +- .../bach_double_slit_path_additivity.dag | 12 +- dag/gunbc/plan.dag | 32 +- .../plans/extdeps_modeling_preflight.dag | 6 +- dag/gunbc/plans/import_namespace_program.dag | 19 +- dag/gunbc/pr_base_freshness.dag | 156 +- dag/gunbc/proactive_verification_ledger.dag | 26 +- dag/gunbc/product/compute_board/admission.dag | 20 +- dag/gunbc/product/compute_board/analog.dag | 14 +- dag/gunbc/product/compute_board/article.dag | 11 +- .../product/compute_board/composition.dag | 11 +- .../compute_board/contact_population.dag | 11 +- .../product/compute_board/device_binding.dag | 15 +- .../product/compute_board/digital_control.dag | 15 +- .../product/compute_board/emission_entry.dag | 14 +- dag/gunbc/product/compute_board/intent.dag | 33 +- .../compute_board/power_distribution.dag | 12 +- .../compute_board/simulation_receipt.dag | 13 +- .../compute_board/spice_projection.dag | 98 +- .../compute_board/verilog_projection.dag | 24 +- dag/gunbc/product/cooling_qualification.dag | 10 +- dag/gunbc/product/fabric/arbitration.dag | 117 +- dag/gunbc/product/fabric/envelope.dag | 4 +- dag/gunbc/product/fabric/isolation.dag | 4 +- dag/gunbc/product/fabric/sanitation.dag | 8 +- dag/gunbc/product/installed_bom_reconcile.dag | 62 +- dag/gunbc/product/inventory.dag | 155 +- dag/gunbc/product/node_power_envelope.dag | 190 +- dag/gunbc/product/pcb/plan_view.dag | 19 +- dag/gunbc/product/spatial_world.dag | 100 +- dag/gunbc/product/training/attempt_graph.dag | 60 +- dag/gunbc/prose_row_frontier.dag | 328 +- dag/gunbc/provider_account.dag | 100 +- dag/gunbc/provider_interface_binding.dag | 41 +- dag/gunbc/provider_standing.dag | 110 +- dag/gunbc/provider_standing_probe_bridge.dag | 128 +- dag/gunbc/provider_turn_adjudication.dag | 27 +- dag/gunbc/provider_wire_evidence.dag | 32 +- dag/gunbc/public_projection.dag | 6 +- dag/gunbc/publication_decision.dag | 5 +- .../qualified_pipe_callee_seed_growth.dag | 25 +- dag/gunbc/quarantine_probe_disposition.dag | 9 +- dag/gunbc/readback_independence.dag | 41 +- .../realization_vocab_confinement_census.dag | 89 +- dag/gunbc/regen_round_cost.dag | 8 +- dag/gunbc/remote_shell_command.dag | 94 +- dag/gunbc/replacement_cut.dag | 53 +- dag/gunbc/replacement_cut_registry.dag | 10 +- dag/gunbc/repo/repo_atlas_projection.dag | 47 +- dag/gunbc/repo/repo_identity.dag | 33 +- dag/gunbc/repo/repo_local_git_config.dag | 43 +- .../repo_local_git_config_clone_bootstrap.dag | 10 +- dag/gunbc/repo/repo_ruleset.dag | 4 +- dag/gunbc/repo/repo_self_build.dag | 25 +- dag/gunbc/repo_workspace.dag | 14 +- dag/gunbc/repository_census_coverage.dag | 235 +- dag/gunbc/repository_census_observation.dag | 178 +- dag/gunbc/required_ci_phase_roster.dag | 49 +- dag/gunbc/retained_shell_script.dag | 38 +- ...roadmap_acceptance_history_observation.dag | 29 +- .../roadmap_acceptance_history_projection.dag | 14 +- dag/gunbc/roadmap/roadmap_altitude.dag | 23 +- dag/gunbc/roadmap/roadmap_authority.dag | 896 ++++- dag/gunbc/roadmap/roadmap_belt.dag | 75 +- dag/gunbc/roadmap/roadmap_belt_actuate.dag | 806 ++++- .../roadmap_closing_contract_authoring.dag | 26 +- dag/gunbc/roadmap/roadmap_component.dag | 327 +- .../roadmap/roadmap_dashboard_instance.dag | 319 +- .../roadmap_dashboard_instance_apply.dag | 280 +- .../roadmap/roadmap_dispatch_actuator.dag | 213 +- .../roadmap/roadmap_dispatch_environment.dag | 35 +- dag/gunbc/roadmap/roadmap_emit.dag | 7 +- .../roadmap/roadmap_execution_contract.dag | 46 +- dag/gunbc/roadmap/roadmap_focus.dag | 44 +- dag/gunbc/roadmap/roadmap_forecast.dag | 51 +- dag/gunbc/roadmap/roadmap_identity.dag | 53 +- .../roadmap/roadmap_instrument_motion.dag | 30 +- .../roadmap/roadmap_instrument_sandbox.dag | 110 +- .../roadmap/roadmap_instrument_tuner.dag | 9 +- dag/gunbc/roadmap/roadmap_interaction.dag | 53 +- .../roadmap/roadmap_launch_admission.dag | 191 +- dag/gunbc/roadmap/roadmap_model.dag | 20 +- dag/gunbc/roadmap/roadmap_page.dag | 181 +- dag/gunbc/roadmap/roadmap_presentation.dag | 263 +- dag/gunbc/roadmap/roadmap_program_view.dag | 146 +- dag/gunbc/roadmap/roadmap_provider_events.dag | 64 +- dag/gunbc/roadmap/roadmap_publish.dag | 199 +- dag/gunbc/roadmap/roadmap_publish_observe.dag | 128 +- .../roadmap/roadmap_repo_atlas_sandbox.dag | 35 +- dag/gunbc/roadmap/roadmap_sandbox.dag | 64 +- dag/gunbc/roadmap/roadmap_serve.dag | 196 +- .../roadmap/roadmap_site_surface_expect.dag | 51 +- .../roadmap/roadmap_site_surface_observe.dag | 5 +- .../roadmap/roadmap_site_surface_render.dag | 20 +- .../roadmap/roadmap_site_surface_types.dag | 10 +- dag/gunbc/roadmap/roadmap_spawner.dag | 26 +- dag/gunbc/roadmap/roadmap_static_site.dag | 26 +- dag/gunbc/roadmap/roadmap_status.dag | 38 +- dag/gunbc/roadmap/roadmap_style.dag | 141 +- .../roadmap/roadmap_validation_oracle.dag | 129 +- .../roadmap/roadmap_verification_receipt.dag | 74 +- dag/gunbc/roadmap/roadmap_verify.dag | 126 +- .../roadmap/roadmap_workflow_command.dag | 21 +- .../roadmap/roadmap_workflow_progress.dag | 72 +- dag/gunbc/roadmap/roadmap_workflow_stage.dag | 10 +- dag/gunbc/roster_registry.dag | 42 +- dag/gunbc/runner/runner_activation.dag | 86 +- .../runner_broker_progress_watchdog.dag | 18 +- .../runner_capacity_operator_access.dag | 54 +- dag/gunbc/runner/runner_capacity_plan.dag | 23 +- dag/gunbc/runner/runner_capacity_realize.dag | 15 +- .../runner/runner_connectivity_recovery.dag | 18 +- .../runner_connectivity_repair_plan.dag | 11 +- dag/gunbc/runner/runner_host_deploy.dag | 152 +- dag/gunbc/runner/runner_incarnation.dag | 73 +- dag/gunbc/runner/runner_lifecycle.dag | 135 +- dag/gunbc/runner/runner_slot_allocation.dag | 290 +- dag/gunbc/runner/runner_slot_provision.dag | 92 +- dag/gunbc/running_release_identity.dag | 98 +- dag/gunbc/rust_item_host_observation.dag | 170 +- dag/gunbc/rust_item_scan.dag | 35 +- dag/gunbc/scm/repository_save.dag | 83 +- dag/gunbc/seed_growth.dag | 25 +- dag/gunbc/seed_growth_admission.dag | 34 +- .../self_host_artifact_materialization.dag | 62 +- dag/gunbc/session_reservation_policy.dag | 3 +- dag/gunbc/session_residency.dag | 95 +- dag/gunbc/shell_command_text.dag | 15 +- dag/gunbc/shell_target_conformance.dag | 13 +- dag/gunbc/site/accent_study.dag | 7 +- dag/gunbc/site/constellation.dag | 8 +- dag/gunbc/site/mark.dag | 5 +- dag/gunbc/site/moodboard.dag | 8 +- dag/gunbc/site/starlight.dag | 35 +- dag/gunbc/spark/bootstrap_provision.dag | 37 +- dag/gunbc/spark/cell_role.dag | 5 +- dag/gunbc/spark/dgx_procurement.dag | 13 +- dag/gunbc/spark/secret_access_ensure.dag | 110 +- dag/gunbc/spark/serving_converge_apply.dag | 23 +- dag/gunbc/spark/serving_converge_plan.dag | 8 +- dag/gunbc/spark/serving_converge_realize.dag | 12 +- .../spark/serving_converge_slice_wet.dag | 6 +- dag/gunbc/spark/serving_desired.dag | 8 +- .../spark/serving_execution_schedule.dag | 11 +- dag/gunbc/spark/serving_install_paths.dag | 7 +- dag/gunbc/spark/serving_loaded_release.dag | 40 +- .../spark/serving_member_realization.dag | 7 +- dag/gunbc/spark/serving_membership.dag | 6 +- dag/gunbc/spark/serving_observe.dag | 12 +- dag/gunbc/spark/serving_readback_parse.dag | 4 +- dag/gunbc/spark/serving_realization.dag | 7 +- dag/gunbc/spark/serving_runtime_admission.dag | 5 +- dag/gunbc/spark/serving_runtime_receipt.dag | 41 +- dag/gunbc/spark/serving_unit_identity.dag | 5 +- dag/gunbc/spark/training_ready.dag | 5 +- ...specification_citation_read_provenance.dag | 9 +- dag/gunbc/srv3/srv3_os_install_actuate.dag | 20 +- .../stage0/stage0_rust_host_observation.dag | 13 +- .../stage0/stage0_rust_lifecycle_totality.dag | 36 +- .../stage0_rust_source_lifecycle_scaffold.dag | 53 +- dag/gunbc/systemctl_show_read.dag | 6 +- .../systemd_property_directive_overlap.dag | 47 +- dag/gunbc/tailscale_acl_phase2_credential.dag | 48 +- dag/gunbc/target_invocation_seed_growth.dag | 67 +- dag/gunbc/test_module_hygiene.dag | 15 +- dag/gunbc/tool_readiness.dag | 27 +- dag/gunbc/toolchain_coherence.dag | 76 +- dag/gunbc/tools/review.dag | 9 +- dag/gunbc/type_reference_binding_context.dag | 16 +- dag/gunbc/typed_argv_exec.dag | 19 +- dag/gunbc/typed_module_cache_capacity.dag | 15 +- dag/gunbc/typed_remote_file_write.dag | 20 +- dag/gunbc/uefi_boot_install_decision.dag | 7 +- dag/gunbc/upstream_document_carriage.dag | 20 +- .../v1/v1_complexity_capability_census.dag | 162 +- .../v1/v1_complexity_decl_classification.dag | 21 +- dag/gunbc/v1/v1_consumer_census.dag | 100 +- dag/gunbc/v1/v1_deletion_plan.dag | 17 +- dag/gunbc/v1/v1_interpreter_dispatch_emit.dag | 29 +- .../v1/v1_interpreter_primitive_surface.dag | 149 +- dag/gunbc/v1/v1_maintenance_standing.dag | 377 +- dag/gunbc/v1/v1_witness_census.dag | 63 +- dag/gunbc/web_motion_binding.dag | 82 +- dag/gunbc/whole_corpus_compile_admission.dag | 76 +- dag/gunbc/witness/witness_deferral_freeze.dag | 186 +- dag/gunbc/witness/witness_floor_workflow.dag | 1697 +++++---- dag/gunbc/witness/witness_row_cost.dag | 396 +- dag/gunbc/worker_lifecycle.dag | 10 +- dag/std/algebra.dag | 9 +- dag/std/algebra_law.dag | 8 +- dag/std/artifact_store.dag | 33 +- dag/std/artifact_store_scratch_grant.dag | 10 +- dag/std/authorization_profile.dag | 6 +- dag/std/bytes.dag | 23 +- dag/std/checked_arithmetic.dag | 26 +- dag/std/citation.dag | 50 +- dag/std/coercion.dag | 99 +- dag/std/computation_identity.dag | 11 +- dag/std/content_hash.dag | 22 +- dag/std/coordinate_frame.dag | 70 +- dag/std/decimal.dag | 72 +- dag/std/decl_ref.dag | 24 +- dag/std/effect_axes.dag | 21 +- dag/std/effect_grant.dag | 58 +- dag/std/effects.dag | 31 +- dag/std/emit_on_demand.dag | 59 +- dag/std/evaluation_budget.dag | 54 +- dag/std/execution_mode.dag | 15 +- dag/std/filesystem_path_grant.dag | 7 +- dag/std/frame_boundary.dag | 24 +- dag/std/human_intervention.dag | 38 +- dag/std/integer.dag | 24 +- dag/std/interface_summary.dag | 14 +- dag/std/interval.dag | 34 +- dag/std/key_relation.dag | 25 +- dag/std/keyed_roster.dag | 29 +- dag/std/keyed_row.dag | 5 +- dag/std/language_target_identity.dag | 213 +- dag/std/materialization_ladder.dag | 109 +- dag/std/materialization_provider.dag | 224 +- dag/std/measure.dag | 64 +- dag/std/minted_identity.dag | 13 +- dag/std/observation.dag | 227 +- dag/std/occurrence_binding.dag | 7 +- dag/std/occurrence_binding_candidates.dag | 210 +- dag/std/occurrence_binding_resolve.dag | 24 +- dag/std/occurrence_identity.dag | 55 +- dag/std/os/types.dag | 19 +- dag/std/path_intent.dag | 7 +- dag/std/primitive_identity.dag | 142 +- dag/std/primitive_projection.dag | 43 +- dag/std/primitives.dag | 6 +- dag/std/realization_measurement.dag | 28 +- dag/std/realization_schedule.dag | 120 +- dag/std/realize_pack.dag | 26 +- dag/std/reference_binding_observation.dag | 6 +- dag/std/runtime/bin_shims/README.md | 7 +- dag/std/selected_witness_bundle.dag | 10 +- dag/std/shell_stream_capture.dag | 7 +- dag/std/source_annotation.dag | 57 +- dag/std/spatial_frame.dag | 11 +- dag/std/state_durability.dag | 25 +- dag/std/trait_derive_shape.dag | 64 +- dag/std/types.dag | 81 +- dag/std/unicode/types.dag | 5 +- dag/std/upsert_decision.dag | 6 +- dag/std/witness_admission.dag | 16 +- dag/std/witness_purpose.dag | 21 +- .../algebra_carrier_roster_witness_test.dag | 23 +- .../altra_attachment_stack_witness_test.dag | 115 +- ...notation_erasure_emission_witness_test.dag | 13 +- .../claim/anomaly_evidence_witness_test.dag | 61 +- ...mous_record_head_use_line_witness_test.dag | 21 +- .../argv_command_render_witness_test.dag | 19 +- .../claim/artifact_store_fs_witness_test.dag | 28 +- .../claim/artifact_store_witness_test.dag | 18 +- .../claim/asrock_live_probe_witness_test.dag | 12 +- ...havioral_receipt_standing_witness_test.dag | 43 +- .../bmc/bmc_firmware_thermal_witness_test.dag | 9 +- .../bmc/bmc_typed_operations_witness_test.dag | 9 +- ...artifact_corruption_probe_witness_test.dag | 12 +- ...configuration_observation_witness_test.dag | 9 +- .../build_cache_endpoint_observe_test.dag | 180 +- .../claim/build_cache_endpoint_path_test.dag | 19 +- dag/test/claim/build_cache_ensure_test.dag | 119 +- ...build_cache_latency_probe_witness_test.dag | 26 +- ...build_cache_placement_observation_test.dag | 44 +- .../cache_retention_axes_witness_test.dag | 38 +- .../claim/capability_binding_witness_test.dag | 75 +- dag/test/claim/caret_syntax_witness_test.dag | 17 +- .../cargo_artifact_selection_witness_test.dag | 38 +- ...rrier_derivation_baseline_witness_test.dag | 25 +- .../claim/change_realization_witness_test.dag | 49 +- .../claim/char_at_unicode_witness_test.dag | 14 +- .../check_coverage_admission_witness_test.dag | 9 +- .../claim/ci/ci_budget_tree_witness_test.dag | 21 +- ...cost_arc_closeout_receipt_witness_test.dag | 14 +- .../claim/ci/ci_deploy_observed_wet_test.dag | 11 +- .../ci/ci_deploy_target_host_witness_test.dag | 19 +- ...ization_receipt_hand_rust_witness_test.dag | 6 +- ...iling_evidence_cause_swap_witness_test.dag | 30 +- ...t_closure_binding_refusal_witness_test.dag | 7 +- .../class_b_strip_receipt_witness_test.dag | 4 +- .../claude_sdk_parser_drop_witness_test.dag | 9 +- ...run_hand_rust_area_ledger_witness_test.dag | 17 +- ...ant_hand_rust_equivalence_witness_test.dag | 6 +- .../claim/cli_run_repo_grant_witness_test.dag | 6 +- .../climbing_hold_witness_roster_test.dag | 47 +- .../codex_app_server_press_witness_test.dag | 9 +- .../codex_device_prompt_witness_test.dag | 35 +- ...codex_supervised_turn_wet_witness_test.dag | 32 +- ...mmit_witness_claim_roster_witness_test.dag | 7 +- .../commit_writer_admission_witness_test.dag | 10 +- ...it_writer_index_selection_witness_test.dag | 20 +- ...epted_unevaluable_program_control_test.dag | 16 +- ...ntry_paths_fast_hand_rust_witness_test.dag | 28 +- ...compile_diagnostic_census_witness_test.dag | 48 +- ...component_dispatch_button_witness_test.dag | 21 +- ...tation_demand_duplication_witness_test.dag | 13 +- ...pute_board_emission_entry_witness_test.dag | 40 +- ...tent_hash_family_grounded_witness_test.dag | 19 +- .../cooling_qualification_witness_test.dag | 8 +- ...oss_file_binding_assembly_witness_test.dag | 8 +- dag/test/claim/css_grain_witness_test.dag | 14 +- .../cursor_cli_invocation_witness_test.dag | 12 +- .../claim/cursor_sdk_stream_witness_test.dag | 11 +- ...ag_compile_clean_perturb_receipts_test.dag | 7 +- ...pile_clean_shard_totality_witness_test.dag | 24 +- ...g_line_comment_annotation_channel_test.dag | 15 +- ...ts_initializer_projection_witness_test.dag | 5 +- .../decl_facts_reflection_witness_test.dag | 10 +- ...e_inhabitance_direct_call_witness_test.dag | 5 +- ..._cadence_reference_census_witness_test.dag | 16 +- .../claim/design_palette_witness_test.dag | 5 +- ...sign_register_lift_parity_witness_test.dag | 355 +- .../devboot_subject_identity_witness_test.dag | 100 +- ...t_rust_door_write_compile_witness_test.dag | 5 +- .../claim/dispatch_attempts_witness_test.dag | 7 +- .../claim/dispatch_preflight_witness_test.dag | 8 +- .../dispatch_presentation_witness_test.dag | 6 +- dag/test/claim/dispatch_stop_witness_test.dag | 18 +- ...graph_reference_partition_witness_test.dag | 52 +- .../claim/doc_reachability_witness_test.dag | 31 +- .../claim/documentary_refs_witness_test.dag | 56 +- ...uplicate_declaration_wall_witness_test.dag | 28 +- ...9_emitter_decision_census_witness_test.dag | 17 +- .../claim/e0599_probe_census_witness_test.dag | 6 +- dag/test/claim/effect_grant_witness_test.dag | 6 +- ...aterialize_real_execution_witness_test.dag | 6 +- dag/test/claim/effects_witness_test.dag | 7 +- .../claim/emit_host_gate_verdicts_test.dag | 8 +- dag/test/claim/emit_host_typed_smoke_test.dag | 8 +- ...variant_expected_adoption_witness_test.dag | 70 +- ...er_callable_field_capture_witness_test.dag | 33 +- ...er_nested_refinement_cast_witness_test.dag | 23 +- ...ter_optional_payload_cast_witness_test.dag | 27 +- ...al_present_branded_scalar_witness_test.dag | 8 +- ...ter_sole_constructor_seal_witness_test.dag | 43 +- ...ter_variant_tag_reference_witness_test.dag | 33 +- ...authority_replacement_cut_witness_test.dag | 16 +- .../env_execution_authority_witness_test.dag | 3 +- .../claim/evaluation_budget_witness_test.dag | 11 +- .../exact_witness_admission_witness_test.dag | 96 +- .../claim/exec_arg_limit_witness_test.dag | 7 +- .../expectation_frontier_witness_test.dag | 37 +- ...al_model_scope_live_cover_witness_test.dag | 12 +- .../external_model_scope_witness_test.dag | 44 +- ...ric_cell_evidence_algebra_witness_test.dag | 84 +- .../fabric/fabric_envelope_witness_test.dag | 21 +- .../fabric/fabric_isolation_witness_test.dag | 17 +- .../fabric/fabric_sanitation_witness_test.dag | 21 +- ...tem_absence_establishment_witness_test.dag | 14 +- ...licability_and_loop_cause_witness_test.dag | 52 +- .../fleet_intent_network_witness_test.dag | 18 +- ...eet_observation_producers_witness_test.dag | 31 +- ...fleet_revision_acceptance_witness_test.dag | 70 +- ...fleet_runner_connectivity_witness_test.dag | 9 +- ...fleet_show_effective_read_witness_test.dag | 35 +- .../floor_component_receipt_witness_test.dag | 74 +- ...ery_hand_rust_equivalence_witness_test.dag | 8 +- ..._preparation_shared_build_witness_test.dag | 47 +- ...floor_resolve_realization_witness_test.dag | 6 +- .../claim/fn_equality_bound_witness_test.dag | 43 +- .../claim/frame_boundary_witness_test.dag | 9 +- ...ntend_literal_and_compile_witness_test.dag | 11 +- ...function_derive_alias_hop_witness_test.dag | 9 +- dag/test/claim/gate_receipt_witness_test.dag | 21 +- ...rge_driver_real_execution_witness_test.dag | 8 +- .../generic_item_clone_bound_witness_test.dag | 106 +- .../generic_item_ord_bound_witness_test.dag | 19 +- .../claim/gha_job_projection_witness_test.dag | 27 +- ...gabyte_mp72_board_profile_witness_test.dag | 33 +- dag/test/claim/git_ls_remote_witness_test.dag | 7 +- ...git_plumbing_ref_mutation_witness_test.dag | 28 +- .../git_ref_set_decoder_witness_test.dag | 9 +- .../claim/git_upstream_model_witness_test.dag | 4 +- .../git_worktree_set_decoder_witness_test.dag | 10 +- .../claim/githooks_pre_push_emit_test.dag | 14 +- ...epo_local_git_config_emit_witness_test.dag | 8 +- ...arantee_floor_class_probe_witness_test.dag | 36 +- .../guarantee_measurement_witness_test.dag | 10 +- .../guarantee_probe_corpus_witness_test.dag | 134 +- dag/test/claim/gunbc_invoke_witness_test.dag | 34 +- .../claim/heal_revalidation_witness_test.dag | 10 +- ...orphism_trait_derive_emit_witness_test.dag | 7 +- .../host_allocation_conservation_test.dag | 124 +- .../host/host_budget_source_witness_test.dag | 97 +- ...ld_cache_provision_design_witness_test.dag | 14 +- ..._provision_real_execution_witness_test.dag | 12 +- .../claim/host/host_compile_pool_test.dag | 23 +- .../host_disk_reclaim_units_witness_test.dag | 14 +- .../host/host_effect_plan_witness_test.dag | 6 +- .../claim/host/host_hygiene_liveness_test.dag | 11 +- .../host_network_diagnosis_witness_test.dag | 18 +- .../host/host_phase_status_witness_test.dag | 100 +- ...st_run_boundary_admission_witness_test.dag | 11 +- .../host/host_swap_backing_witness_test.dag | 6 +- ...host_toolchain_components_witness_test.dag | 34 +- .../claim/http_serve_route_witness_test.dag | 9 +- ...dowed_by_local_definition_witness_test.dag | 4 +- .../claim/instrument_bob_witness_test.dag | 72 +- .../claim/instrument_camera_witness_test.dag | 9 +- .../instrument_motion_page_witness_test.dag | 29 +- .../instrument_physical_witness_test.dag | 9 +- .../claim/instrument_sandbox_witness_test.dag | 54 +- .../claim/instrument_tuner_witness_test.dag | 6 +- ...tch_bijection_real_roster_witness_test.dag | 10 +- ...terpreter_replacement_cut_witness_test.dag | 21 +- .../claim/inventory_ledger_witness_test.dag | 49 +- dag/test/claim/json_emit_witness_test.dag | 8 +- dag/test/claim/json_parse_witness_test.dag | 28 +- .../claim/jwt_oidc_claims_witness_test.dag | 63 +- dag/test/claim/keyed_roster_witness_test.dag | 9 +- ...da_capture_clone_required_witness_test.dag | 36 +- .../lambda_receiver_type_witness_test.dag | 41 +- dag/test/claim/language_module_home_test.dag | 12 +- .../language_source_scaffold_index_test.dag | 19 +- .../language_target_identity_witness_test.dag | 56 +- ...language_target_registry_totality_test.dag | 64 +- ...guage_target_subject_registration_test.dag | 33 +- .../legacy_baseline_capture_witness_test.dag | 10 +- ...gacy_binding_tap_coverage_witness_test.dag | 21 +- ...t_behavior_disposition_acceptance_test.dag | 8 +- .../candidate_checkout_witness_test.dag | 23 +- .../live_deploy/deploy_release_fixture.dag | 25 +- dag/test/claim/live_deploy/emit_test.dag | 219 +- .../running_release_identity_witness_test.dag | 109 +- .../claim/local_tidy_fmt_outcome_test.dag | 8 +- ...rrier_reference_integrity_witness_test.dag | 70 +- ...mmit_witness_claim_roster_witness_test.dag | 11 +- .../long/dag_arrow_lambda_witness_test.dag | 12 +- ...e_clean_scope_disposition_witness_test.dag | 12 +- .../long/decl_ref_resolution_witness_test.dag | 30 +- .../long/dissolution_census_witness_test.dag | 23 +- ...e_placement_gate_loudness_witness_test.dag | 76 +- .../instrument_sandbox_live_witness_test.dag | 56 +- ...ce_graft_body_dissolution_witness_test.dag | 20 +- ...ference_emit_cross_module_witness_test.dag | 11 +- .../claim/long/realization_sweep_test.dag | 11 +- .../reference_closure_equivalence_test.dag | 11 +- .../claim/long/roadmap_page_witness_test.dag | 123 +- ...t_fixtures_import_closure_witness_test.dag | 79 +- ...xity_capability_census_resolution_test.dag | 43 +- ...xity_decl_classification_totality_test.dag | 16 +- ...re_refinement_enforcement_witness_test.dag | 133 +- .../machine_shape_construction_wall_test.dag | 4 +- .../claim/managed_directory_witness_test.dag | 11 +- .../map_lookup_dual_dispatch_witness_test.dag | 7 +- ...p_key_generic_realization_witness_test.dag | 5 +- ...pattern_identity_emission_witness_test.dag | 24 +- ..._exhaustiveness_coproduct_witness_test.dag | 6 +- ...el_closing_frontier_audit_witness_test.dag | 30 +- .../materialization_ladder_witness_test.dag | 15 +- .../materialization_provider_witness_test.dag | 55 +- .../materialized_secret_witness_test.dag | 37 +- .../measurement_provenance_witness_test.dag | 8 +- .../membership_reconcile_witness_test.dag | 36 +- .../merge_admission_attempt_witness_test.dag | 156 +- ...ge_lifecycle_interleaving_witness_test.dag | 28 +- ...ntract_cross_module_alias_witness_test.dag | 45 +- ...hod_arg_declared_contract_witness_test.dag | 47 +- ...module_filename_collision_witness_test.dag | 14 +- .../module_graph_edge_source_witness_test.dag | 5 +- .../module_impact_query_witness_test.dag | 57 +- ...ti_module_compile_fixture_witness_test.dag | 25 +- ...e_clause_e_projection_law_witness_test.dag | 6 +- ...e_cut_subject_observation_witness_test.dag | 25 +- .../namespace_import_closure_witness_test.dag | 8 +- .../namespace_occurrence_transport_test.dag | 13 +- ...mespace_pool_independence_witness_test.dag | 27 +- ...erence_derived_closure_acceptance_test.dag | 10 +- ...eference_derived_closure_contract_test.dag | 8 +- ...tructural_observations_acceptance_test.dag | 6 +- ...itness_transition_receipt_witness_test.dag | 44 +- .../claim/network_grounding_witness_test.dag | 34 +- dag/test/claim/network_mac_witness_test.dag | 19 +- .../claim/no_fake_anomalies_witness_test.dag | 25 +- .../claim/non_fold_residue_frontier_test.dag | 20 +- ...ormalize_retention_contract_probe_test.dag | 78 +- .../observation_emit_census_witness_test.dag | 134 +- ...n_raw_print_retirement_acceptance_test.dag | 58 +- .../claim/observation_rollup_witness_test.dag | 9 +- ...rrence_binding_candidates_witness_test.dag | 88 +- ...entity_acceptance_closure_witness_test.dag | 14 +- .../offline_local_recipe_witness_test.dag | 9 +- ...eration_argv_binding_wall_witness_test.dag | 10 +- .../operation_argv_corpus_witness_test.dag | 11 +- .../claim/optional_carrier_signature_test.dag | 17 +- .../optional_consumer_fail_closed_test.dag | 15 +- dag/test/claim/orchestration_if_emit_test.dag | 4 +- .../orthogonal_topology_witness_test.dag | 24 +- dag/test/claim/output_policy_witness_test.dag | 5 +- ...cb_capability_established_witness_test.dag | 8 +- dag/test/claim/pcb_copper_witness_test.dag | 52 +- .../peak_resident_measured_witness_test.dag | 10 +- ...osix_principal_allocation_witness_test.dag | 7 +- .../preemption_reachability_witness_test.dag | 45 +- .../primitive_identity_join_witness_test.dag | 8 +- ...tive_projection_authority_witness_test.dag | 14 +- ...itive_signature_grounding_witness_test.dag | 7 +- .../claim/probed_at_word_witness_test.dag | 32 +- .../claim/proc_self_cgroup_witness_test.dag | 14 +- .../prose_row_introduction_witness_test.dag | 45 +- ...rovider_account_admission_witness_test.dag | 34 +- ...der_standing_probe_bridge_witness_test.dag | 26 +- .../qualified_arm_start_witness_test.dag | 21 +- ...eclaration_reference_emit_witness_test.dag | 14 +- ...f_registry_collision_emit_witness_test.dag | 8 +- .../qualified_pattern_head_witness_test.dag | 33 +- ...alified_spelling_identity_witness_test.dag | 37 +- ...rantine_probe_disposition_witness_test.dag | 9 +- dag/test/claim/rack_power_witness_test.dag | 30 +- .../readback_independence_witness_test.dag | 42 +- .../realization_attempt_keystone_test.dag | 9 +- ...lization_vocab_confinement_census_test.dag | 29 +- dag/test/claim/realize_pack_witness_test.dag | 9 +- ...ecord_construction_census_witness_test.dag | 27 +- ...rd_decl_spelling_identity_witness_test.dag | 43 +- ..._literal_call_arg_handoff_witness_test.dag | 13 +- ...ived_authored_source_gate_witness_test.dag | 22 +- .../reference_instrument_witness_test.dag | 16 +- .../reference_realization_witness_test.dag | 41 +- ...n_receipt_prior_reference_witness_test.dag | 38 +- .../remote_shell_command_witness_test.dag | 11 +- .../claim/render_glyphs_ansi_witness_test.dag | 8 +- .../repo_atlas_projection_witness_test.dag | 66 +- dag/test/claim/repo_ruleset_witness_test.dag | 34 +- ...epository_census_coverage_witness_test.dag | 78 +- ...epository_convergence_wet_witness_test.dag | 43 +- ...red_record_field_omission_witness_test.dag | 9 +- .../required_regen_admission_witness_test.dag | 87 +- ...en_obligation_disposition_witness_test.dag | 39 +- ...ed_call_emission_identity_witness_test.dag | 19 +- ...ved_graph_cache_hand_rust_witness_test.dag | 7 +- .../roadmap/roadmap_altitude_witness_test.dag | 15 +- .../claim/roadmap/roadmap_authority_test.dag | 110 +- .../roadmap_belt_actuate_witness_test.dag | 214 +- ...losing_contract_authoring_witness_test.dag | 9 +- ..._dashboard_instance_apply_witness_test.dag | 41 +- ...oadmap_dashboard_instance_witness_test.dag | 42 +- ...roadmap_dispatch_actuator_witness_test.dag | 7 +- ...dmap_dispatch_environment_witness_test.dag | 7 +- .../roadmap/roadmap_focus_witness_test.dag | 13 +- .../roadmap/roadmap_identity_witness_test.dag | 20 +- .../roadmap_launch_admission_fixture.dag | 18 +- .../roadmap_launch_admission_witness_test.dag | 38 +- .../roadmap_launch_canary_witness_test.dag | 14 +- .../roadmap_presentation_witness_test.dag | 103 +- ..._program_view_live_corpus_receipt_test.dag | 11 +- .../roadmap_program_view_witness_test.dag | 11 +- .../roadmap_provider_events_witness_test.dag | 29 +- .../roadmap_publish_observe_witness_test.dag | 23 +- .../roadmap/roadmap_publish_witness_test.dag | 155 +- ...p_receipt_continuity_live_witness_test.dag | 13 +- .../roadmap/roadmap_register_witness_test.dag | 44 +- ...oadmap_repo_atlas_sandbox_witness_test.dag | 63 +- .../roadmap/roadmap_row_witness_test.dag | 25 +- .../roadmap/roadmap_sandbox_witness_test.dag | 24 +- .../roadmap/roadmap_serve_witness_test.dag | 50 +- .../roadmap/roadmap_tactile_witness_test.dag | 14 +- ...roadmap_validation_oracle_witness_test.dag | 123 +- ...dmap_verification_receipt_witness_test.dag | 7 +- .../roadmap/roadmap_verify_witness_test.dag | 23 +- ...roadmap_workflow_progress_witness_test.dag | 45 +- ..._measure_missing_generics_witness_test.dag | 38 +- .../claim/run_verdict_exit_status_fixture.dag | 24 +- .../run_verdict_exit_status_witness_test.dag | 12 +- .../runner/runner_activation_wall_test.dag | 57 +- .../runner_host_deploy_witness_test.dag | 18 +- .../runner/runner_placement_witness_test.dag | 46 +- .../runner_slot_allocation_witness_test.dag | 48 +- .../runner_slot_provision_witness_test.dag | 7 +- ..._rust_seed_bootstrap_sync_witness_test.dag | 12 +- dag/test/claim/salience_witness_test.dag | 17 +- ...isposition_census_fixture_witness_test.dag | 16 +- ...he_local_content_verified_on_read_test.dag | 12 +- dag/test/claim/sccache_pin_witness_test.dag | 6 +- .../scm/scm_agentic_stress_witness_test.dag | 10 +- dag/test/claim/scm/scm_log_witness_test.dag | 6 +- .../scm/scm_provider_matrix_witness_test.dag | 6 +- .../scm_repository_save_witness_test.dag | 36 +- .../sec_edgar_rollins_chemed_witness_test.dag | 6 +- .../seed_growth_admission_witness_test.dag | 27 +- ...eed_honesty_discharge_unavailable_test.dag | 50 +- ...seed_mirror_constant_lens_witness_test.dag | 102 +- ...ost_00_compile_behavioral_witness_test.dag | 6 +- ...st_01_tokenize_behavioral_witness_test.dag | 6 +- ..._host_02_parse_behavioral_witness_test.dag | 11 +- ...host_03_ingest_behavioral_witness_test.dag | 11 +- ...t_03_normalize_behavioral_witness_test.dag | 16 +- ...ost_03_resolve_behavioral_witness_test.dag | 6 +- ..._host_04_infer_behavioral_witness_test.dag | 6 +- ...ialization_real_execution_witness_test.dag | 47 +- ..._artifact_materialization_witness_test.dag | 18 +- ..._body_producer_behavioral_witness_test.dag | 7 +- ...ry_enumeration_behavioral_witness_test.dag | 7 +- ...d_call_target_realization_witness_test.dag | 22 +- ..._host_generation_identity_witness_test.dag | 31 +- ...elf_host_logic_behavioral_witness_test.dag | 8 +- ...ation_carriers_behavioral_witness_test.dag | 10 +- ...e_engine_hooks_behavioral_witness_test.dag | 7 +- ...ogram_assembly_behavioral_witness_test.dag | 6 +- ...gram_partition_behavioral_witness_test.dag | 6 +- ...urce_authority_behavioral_witness_test.dag | 6 +- ...arget_carriers_behavioral_witness_test.dag | 7 +- ...e_site_verdict_behavioral_witness_test.dag | 8 +- ...ession_reservation_policy_witness_test.dag | 47 +- .../claim/session_residency_witness_test.dag | 19 +- .../claim/sessions_panel_witness_test.dag | 21 +- .../shared_pool_encoding_control_test.dag | 23 +- ...l_dag_census_5a_typed_ops_witness_test.dag | 6 +- ...shell_exec_run_argv_embed_witness_test.dag | 10 +- .../shell_stream_capture_witness_test.dag | 3 +- .../shell_target_conformance_witness_test.dag | 11 +- .../claim/site_accent_study_witness_test.dag | 8 +- ...ap_keys_interpreter_order_witness_test.dag | 43 +- ...rce_annotation_attachment_witness_test.dag | 4 +- ...tegration_proof_kernel_acceptance_test.dag | 21 +- ...ration_proof_kernel_model_witness_test.dag | 11 +- ...spark_bootstrap_provision_witness_test.dag | 6 +- ...park_cell_role_retirement_witness_test.dag | 5 +- .../spark_serving_membership_witness_test.dag | 4 +- .../spark_serving_observe_witness_test.dag | 15 +- ...k_serving_runtime_receipt_witness_test.dag | 5 +- ...ving_write_unit_operation_witness_test.dag | 58 +- .../spark_training_ready_witness_test.dag | 9 +- .../spatial_frame_scale_witness_test.dag | 30 +- .../claim/spatial_placement_witness_test.dag | 8 +- .../srv3/srv3_subsumption_witness_test.dag | 44 +- .../srv3/srv3_token_subject_witness_test.dag | 3 +- dag/test/claim/ssh_transport_witness_test.dag | 6 +- ...onvergence_real_execution_witness_test.dag | 21 +- ...ust_host_observation_live_witness_test.dag | 55 +- ...source_lifecycle_scaffold_witness_test.dag | 7 +- .../state_response_totality_witness_test.dag | 25 +- .../string_brace_escape_witness_test.dag | 17 +- ...application_mismatch_wall_witness_test.dag | 9 +- ...ystemd_property_directive_overlap_test.dag | 11 +- ...tailscale_serve_route_key_witness_test.dag | 26 +- .../terminal_wire_projection_witness_test.dag | 9 +- dag/test/claim/tool_pin_witness_test.dag | 142 +- .../claim/tool_readiness_witness_test.dag | 20 +- .../toolchain_coherence_witness_test.dag | 16 +- .../claim/training_readback_witness_test.dag | 51 +- dag/test/claim/trait_bound_witness_test.dag | 78 +- ...port_emission_not_modeled_witness_test.dag | 9 +- ...transport_script_wall_compile_red_test.dag | 29 +- ...reference_binding_context_witness_test.dag | 8 +- ...ve_changeover_equivalence_witness_test.dag | 11 +- ...ped_argv_exec_realization_witness_test.dag | 8 +- ...ped_module_cache_capacity_witness_test.dag | 8 +- .../typed_remote_file_write_witness_test.dag | 6 +- .../claim/typed_witness_invocation_test.dag | 8 +- .../typescript_program_serializer_test.dag | 26 +- ...plexity_capability_census_witness_test.dag | 93 +- ...exity_decl_classification_witness_test.dag | 12 +- ...rpreter_primitive_surface_witness_test.dag | 13 +- .../claim/v1_source_audit_witness_test.dag | 18 +- .../claim/web_motion_binding_witness_test.dag | 60 +- ...et_receipt_route_standing_witness_test.dag | 14 +- ..._corpus_compile_admission_witness_test.dag | 22 +- .../witness_deferral_freeze_witness_test.dag | 25 +- ...tness_execution_class_live_census_test.dag | 29 +- ...or_workflow_consolidation_witness_test.dag | 9 +- .../witness_row_cost_drift_witness_test.dag | 14 +- ...cost_migration_disclosure_witness_test.dag | 6 +- ...tness_row_cost_projection_witness_test.dag | 9 +- .../workflow_dispatch_input_witness_test.dag | 16 +- .../workload_simulation_witness_test.dag | 61 +- dag/test/fixture/codex_device_login/README.md | 22 +- .../fixture/codex_provider_events/README.md | 30 +- .../git_upstream_model_execution_test.dag | 11 +- ...ercurial_upstream_model_execution_test.dag | 12 +- .../pijul_upstream_model_execution_test.dag | 13 +- .../process_argv_expansion_receipt_test.dag | 33 +- docs/extdeps/lotes_azifa072/README.md | 93 +- .../plans/ci-floor-child-spawn-attribution.md | 59 +- docs/plans/ci-minutes-product-design.md | 54 +- docs/plans/cli-invocation-emission-design.md | 981 +++-- docs/plans/cli-run-reconcile-defork.md | 32 +- docs/plans/content-hash-family-grounding.md | 2 +- docs/plans/dag-native-scm-design.md | 313 +- docs/plans/dag-scm-design.md | 764 ++-- docs/plans/dispatch-maintain-cc.md | 24 +- .../dissolution-census-a-ci-layer-roots.md | 16 +- docs/plans/effect-namespace-grants.md | 36 +- ...n-admission-stage-aware-pipeline-design.md | 341 +- .../plans/emit-type-surface-traversal-cost.md | 14 +- docs/plans/fabric-concept-reconciliation.md | 32 +- docs/plans/fabric-recut-program.md | 505 ++- docs/plans/five-minute-ci-gate-design.md | 42 +- docs/plans/fleet-revision-relation-fixture.md | 25 +- docs/plans/floor-cut-replacement-plan.md | 22 +- ...expected-red-shrink-monotonicity-design.md | 68 +- docs/plans/floor-semantic-artifact-design.md | 30 +- docs/plans/floor-shared-fill-ledger.md | 191 +- ...ime-namespace-walk-regression-diagnosis.md | 88 +- docs/plans/generated-file-conflict-policy.md | 59 +- .../git-plumbing-extdeps-authority-design.md | 106 +- docs/plans/gunbc-served-dashboard-design.md | 26 +- ...host-network-attachment-converge-design.md | 125 +- ...rip-witness-discovery-cascade-diagnosis.md | 873 +++-- docs/plans/keying-relation-design.md | 193 +- ...yering-imports-reference-repoint-design.md | 8 +- .../machine-shape-orthogonal-scheduling.md | 38 +- .../membership-diff-reconcile-spine-design.md | 70 +- docs/plans/namespace-cut-replacement-plan.md | 111 +- ...pace-flip-last-28-root-a-two-std-defork.md | 48 +- docs/plans/namespace-resolution-design.md | 691 ++-- ...espace-unique-on-chain-operational-plan.md | 22 +- ...-grammar-choice-overlap-residue-finding.md | 38 +- .../parsed-body-projection-increment-spec.md | 144 +- docs/plans/post-engine-pr-roadmap.md | 22 +- .../rc-ownership-wrap-decision-design.md | 94 +- docs/plans/reconcile-cost-root-cause.md | 53 +- docs/plans/repo-stability-2026-08.md | 42 +- docs/plans/resource-aware-scheduler.md | 48 +- ...ore-src-v1-required-floor-stall-finding.md | 26 +- docs/plans/review-surface-subject-map.md | 41 +- docs/plans/roadmap-launch-mvp-plan.md | 22 +- .../plans/roadmap-presentation-seam-design.md | 21 +- docs/plans/roadmap-spawner.md | 18 +- docs/plans/roadmap-workspace-remodel-plan.md | 18 +- docs/plans/roadmap-workspace-ux-plan.md | 48 +- .../runner-service-capacity-convergence.md | 64 +- docs/plans/s2-v2-self-emit-brief.md | 12 +- docs/plans/s2-v2-self-emit-direction.md | 54 +- docs/plans/seed-honesty-discharge-design.md | 298 +- .../self-host-cargo-refusal-root-partition.md | 543 ++- docs/plans/shell-dag-census-0a-brief.md | 48 +- .../shell-dag-census-0a-projection-blocker.md | 198 +- .../shell-intent-emit-realization-design.md | 82 +- ...-dag-residual-census-and-arc-completion.md | 206 +- docs/plans/space-lens-minimal-project.md | 36 +- ...bearing-serde-debug-decision-2026-08-21.md | 177 +- .../plans/type-env-single-authority-design.md | 24 +- docs/plans/unconsumed-module-census.md | 1030 +++--- .../unconsumed-module-residue-disposition.md | 757 ++-- docs/plans/v1-run-stability-throughline.md | 54 +- .../witness-cost-first-touch-attribution.md | 702 ++-- docs/plans/witness-execution-closure.md | 246 +- ...tinuation_silently_truncates_2026-08-23.md | 61 +- docs/runbooks/bmc-assimilator-wif-setup.md | 54 +- .../srv3-nbd-proxy-ws-upgrade-dry-run.md | 4 +- docs/runbooks/srv3-os-install-actuate.md | 14 +- fixtures/atomic_materialization/subject.dag | 11 +- .../class_b_trim/coincidence_specimen.dag | 5 +- fixtures/class_b_trim/monoid_specimen.dag | 5 +- .../narrow_pool/dag/std/types.dag | 6 +- fixtures/class_b_trim/specimen.dag | 6 +- .../class_b_trim/wire_projection_specimen.dag | 6 +- src/v1/00_core.dag | 170 +- src/v1/01_tokenize.dag | 63 +- src/v1/02_parse.dag | 168 +- src/v1/03_resolve.dag | 9 +- src/v1/04_emit_info.dag | 15 +- src/v1/04_env.dag | 85 +- src/v1/04_infer.dag | 458 ++- src/v1/04_lookup.dag | 7 +- src/v1/04_method.dag | 77 +- src/v1/04_occurrence_binding.dag | 25 +- src/v1/04_resolve.dag | 6 +- src/v1/04_service.dag | 10 +- src/v1/04_sigs.dag | 17 +- src/v1/04_types.dag | 13 +- src/v1/05_emit_core_support.dag | 17 +- src/v1/05_emit_rust.dag | 315 +- src/v1/annotation_bind.dag | 44 +- src/v1/closure_stub_v2_std_integer_rust.dag | 5 +- src/v1/closure_stub_v2_std_text_rust.dag | 5 +- src/v1/compile.dag | 18 +- ...erived_closure_production_observations.dag | 32 +- .../gunbc/occurrence_binding_parser_walk.dag | 11 +- src/v1/stage0/src/behavioral_receipt_host.rs | 1291 +++---- src/v1/stage0/src/cli_run.rs | 1402 ++++---- src/v1/stage0/src/coproduct_reflection.rs | 162 +- .../src/cssl_seed_linked_closure_assembly.rs | 121 +- src/v1/stage0/src/declaration_index.rs | 1157 +++--- .../src/emitted_closure_compile_host.rs | 749 ++-- .../src/extdeps_container_oci_digest.rs | 9 - .../stage0/src/extdeps_external_authority.rs | 45 - .../extdeps_languages_rust_capabilities.rs | 9 - ...extdeps_languages_rust_derive_contracts.rs | 18 - .../stage0/src/extdeps_languages_rust_emit.rs | 18 - .../src/extdeps_languages_rust_types.rs | 9 - .../stage0/src/extdeps_units_dimensionless.rs | 18 - .../stage0/src/extdeps_units_iec_80000_13.rs | 9 - src/v1/stage0/src/extdeps_units_iso8601.rs | 9 - .../stage0/src/extdeps_units_iso_80000_3.rs | 18 - src/v1/stage0/src/extdeps_uri.rs | 9 - src/v1/stage0/src/extdeps_uri_path.rs | 18 - .../src/generated_artifact_boundary_host.rs | 197 +- src/v1/stage0/src/main.rs | 504 ++- src/v1/stage0/src/memory_governor.rs | 250 +- src/v1/stage0/src/namespace_wave_admission.rs | 701 ++-- .../src/partition_crate_boundary_host.rs | 123 +- src/v1/stage0/src/phase_profile.rs | 26 +- src/v1/stage0/src/pre_push.rs | 27 +- src/v1/stage0/src/required_regen_host.rs | 813 ++--- src/v1/stage0/src/resolved_graph_cache.rs | 72 +- src/v1/stage0/src/shared_typecheck_store.rs | 26 +- src/v1/stage0/src/std_algebra.rs | 9 - src/v1/stage0/src/std_checked_arithmetic.rs | 18 - src/v1/stage0/src/std_coercion.rs | 9 - src/v1/stage0/src/std_content_hash.rs | 18 - src/v1/stage0/src/std_decl_ref.rs | 9 - src/v1/stage0/src/std_effects.rs | 27 - src/v1/stage0/src/std_execution_mode.rs | 18 - src/v1/stage0/src/std_integer.rs | 19 - src/v1/stage0/src/std_interface_summary.rs | 9 - src/v1/stage0/src/std_keyed_roster.rs | 9 - src/v1/stage0/src/std_keyed_row.rs | 9 - src/v1/stage0/src/std_measure.rs | 99 - src/v1/stage0/src/std_occurrence_binding.rs | 9 - .../src/std_occurrence_binding_candidates.rs | 207 -- .../src/std_occurrence_binding_resolve.rs | 36 - src/v1/stage0/src/std_occurrence_identity.rs | 81 - src/v1/stage0/src/std_primitive_projection.rs | 36 - src/v1/stage0/src/std_realization_schedule.rs | 90 - .../src/std_reference_binding_observation.rs | 9 - src/v1/stage0/src/std_source_annotation.rs | 27 - src/v1/stage0/src/std_trait_derive_shape.rs | 36 - src/v1/stage0/src/std_types.rs | 18 - src/v1/stage0/src/std_unicode_types.rs | 9 - src/v1/stage0/src/std_witness_admission.rs | 9 - src/v1/stage0/src/target_invocation_host.rs | 141 +- .../stage0/src/v1_compiler_annotation_bind.rs | 18 - ...mpiler_closure_stub_v2_std_integer_rust.rs | 9 - ..._compiler_closure_stub_v2_std_text_rust.rs | 9 - src/v1/stage0/src/v1_compiler_compile.rs | 27 - .../src/v1_compiler_emit_core_support.rs | 9 - src/v1/stage0/src/v1_compiler_emit_rust.rs | 171 - src/v1/stage0/src/v1_compiler_infer.rs | 216 -- .../stage0/src/v1_compiler_infer_emit_info.rs | 18 - src/v1/stage0/src/v1_compiler_infer_env.rs | 81 - src/v1/stage0/src/v1_compiler_infer_lookup.rs | 9 - src/v1/stage0/src/v1_compiler_infer_method.rs | 45 - .../v1_compiler_infer_occurrence_binding.rs | 18 - .../stage0/src/v1_compiler_infer_resolve.rs | 9 - src/v1/stage0/src/v1_compiler_infer_sigs.rs | 9 - src/v1/stage0/src/v1_compiler_infer_types.rs | 9 - src/v1/stage0/src/v1_compiler_parse.rs | 36 - .../stage0/src/v1_compiler_stage0_crates.rs | 18 - src/v1/stage0/src/v1_compiler_tokenize.rs | 63 - .../src/v1_compiler_trait_bound_witness.rs | 36 - .../src/v1_compiler_trait_derive_emit.rs | 54 - ...derived_closure_production_observations.rs | 27 - ...v1_gunbc_occurrence_binding_parser_walk.rs | 20 - src/v1/stage0/src/v1_interpreter.rs | 3193 ++++++++--------- src/v1/stage0/src/v1_std_core.rs | 28 - ...checkpoint_identity_keying_witness_test.rs | 9 - ...derived_disposition_census_witness_test.rs | 38 +- ...m_transport_roster_closure_witness_test.rs | 9 - .../tests/dag_parse_sweep_refusal_location.rs | 50 +- .../tests/declaration_index_integrity.rs | 286 +- .../floor_non_verdict_admission_seed_path.rs | 55 +- ...erpreter_dispatch_bijection_compile_red.rs | 50 +- ...eter_dispatch_bijection_real_roster_red.rs | 71 +- .../stage0/tests/namespace_wave_admission.rs | 296 +- .../tests/qualified_callee_pipe_position.rs | 47 +- .../stage0/tests/tokenize_escape_receipt.rs | 59 +- src/v1/stage0_crates.dag | 19 +- ...heckpoint_identity_keying_witness_test.dag | 116 +- ...erived_disposition_census_witness_test.dag | 89 +- .../transport_roster_closure_witness_test.dag | 54 +- src/v1/trait_bound_witness.dag | 127 +- src/v1/trait_derive_emit.dag | 148 +- src/v2/compiler/00_compile.dag | 13 +- src/v2/compiler/02_parse.dag | 30 +- src/v2/compiler/03_name_resolve.dag | 4 +- src/v2/compiler/04_infer.dag | 81 +- src/v2/compiler/05_emit_orchestration.dag | 42 +- src/v2/compiler/05_eval.dag | 10 +- src/v2/compiler/06_translate.dag | 29 +- src/v2/compiler/body_lowering_fold.dag | 57 +- src/v2/compiler/build_transport_admission.dag | 22 +- src/v2/compiler/build_workspace_grant.dag | 27 +- src/v2/compiler/emit_host.dag | 6 +- src/v2/compiler/emit_module.dag | 6 +- src/v2/compiler/fold_lowering.dag | 52 +- .../compiler/host_run_boundary_admission.dag | 21 +- src/v2/compiler/ingested_fixture_arrows.dag | 14 +- src/v2/compiler/materialization_carriers.dag | 5 +- src/v2/compiler/namespace_graft.dag | 16 +- .../native_selected_bundle_process.dag | 18 +- src/v2/compiler/normalized_tree.dag | 37 +- src/v2/compiler/program_assembly.dag | 11 +- src/v2/compiler/resolution_provenance.dag | 15 +- .../self_host/candidate_generation.dag | 36 +- .../self_host/direct_rust_door_fixture.dag | 3 +- .../direct_rust_door_observation.dag | 14 +- .../self_host/emit_coverage_frontier.dag | 42 +- .../self_host/emitter_producer_provenance.dag | 49 +- src/v2/compiler/self_host/frontier_probe.dag | 19 +- ...frontier_probe_semantic_family_receipt.dag | 72 +- .../self_host/frontier_probe_types.dag | 281 +- .../self_host/native_routing_frontier.dag | 47 +- .../self_host/parity_receipts_local.dag | 13 +- src/v2/compiler/self_host/parity_window.dag | 49 +- ...itter_behavioral_wet_known_red_entries.dag | 10 +- ...emitter_behavioral_wet_module_bindings.dag | 7 +- .../self_host/seed_retention_frontier.dag | 79 +- .../self_host/stage0_crate_layout.dag | 16 +- .../v2_emitter_direct_rust_door_contract.dag | 20 +- .../self_host/wet_receipt_enrollment.dag | 66 +- .../self_host/wet_receipt_route_standing.dag | 53 +- .../self_host/witness_bulk_routing.dag | 36 +- .../witness_entry_eligibility_census.dag | 15 +- src/v2/compiler/source_authority.dag | 116 +- src/v2/compiler/source_authority_read.dag | 39 +- src/v2/compiler/staged_front_end.dag | 32 +- src/v2/compiler/symbol_index_fill.dag | 9 +- src/v2/compiler/trait_derive_completeness.dag | 29 +- src/v2/extdeps/formats/icestorm_pcf.dag | 11 +- src/v2/extdeps/formats/json.dag | 4 +- src/v2/extdeps/languages/bash.dag | 16 +- .../extdeps/languages/bash_command_fold.dag | 8 +- src/v2/extdeps/languages/bash_orch_if.dag | 7 +- .../languages/bash_proc_self_cgroup.dag | 7 +- src/v2/extdeps/languages/c.dag | 45 +- src/v2/extdeps/languages/dag.dag | 30 +- src/v2/extdeps/languages/lean.dag | 49 +- src/v2/extdeps/languages/rust.dag | 39 +- .../extdeps/languages/rust_test_fixtures.dag | 65 +- src/v2/extdeps/languages/typescript.dag | 12 +- src/v2/extdeps/languages/verilog.dag | 118 +- .../extdeps/runtimes/effect_io_host_grant.dag | 18 +- src/v2/extdeps/runtimes/v2_effect_io_host.dag | 10 +- src/v2/extdeps/runtimes/v2_evaluator.dag | 8 +- src/v2/lens/affected_set/entry_selection.dag | 21 +- src/v2/lens/common/graph_invariant.dag | 18 +- src/v2/lens/complexity.dag | 7 +- src/v2/lens/complexity_accumulator_copy.dag | 24 +- .../complexity_accumulator_copy/analyze.dag | 15 +- .../roster_gate.dag | 11 +- src/v2/lens/cost.dag | 20 +- src/v2/lens/cost/copied_port_citations.dag | 7 +- src/v2/lens/cost/copied_port_derivation.dag | 9 +- src/v2/lens/cost/summary.dag | 7 +- src/v2/lens/cost/valuation.dag | 158 +- src/v2/lens/determinism.dag | 49 +- .../emitted_binary_effect_confinement.dag | 19 +- .../complexity_contract_subject.dag | 29 +- src/v2/lens/enforcement/contract.dag | 10 +- src/v2/lens/enforcement/inventory.dag | 21 +- src/v2/lens/enforcement/lens_module_gate.dag | 8 +- src/v2/lens/fact_density.dag | 6 +- src/v2/lens/fallback_arm_census.dag | 6 +- src/v2/lens/identity_captured_navigation.dag | 15 +- .../identity_captured_navigation/analyze.dag | 9 +- .../roster_gate.dag | 21 +- src/v2/lens/live_read_classification.dag | 32 +- src/v2/lens/machine_shape.dag | 18 +- src/v2/lens/mandatory_tag.dag | 63 +- src/v2/lens/mandatory_tag/corpus_scan.dag | 15 +- .../corpus_scan_witness_test.dag | 11 +- src/v2/lens/meta_exec_confinement.dag | 20 +- src/v2/lens/module_graph.dag | 75 +- src/v2/lens/module_impact_query.dag | 226 +- .../production_qualification_origin_probe.dag | 61 +- src/v2/lens/roster_registry.dag | 24 +- src/v2/lens/schedule_lens.dag | 10 +- src/v2/lens/testdata/README.md | 26 +- src/v2/lens/unit_modeling.dag | 5 +- src/v2/lens/vacuity.dag | 119 +- .../lens/vacuity_self_application_fixture.dag | 44 +- src/v2/lens/witness_cost_locality.dag | 24 +- src/v2/std/cardinality.dag | 7 +- src/v2/std/coercion.dag | 10 +- src/v2/std/compilers/compilation_unit.dag | 54 +- src/v2/std/compilers/lexing.dag | 15 +- src/v2/std/compilers/target_model.dag | 47 +- src/v2/std/data_initializer_identity.dag | 4 +- src/v2/std/decl_ref_resolution.dag | 14 +- src/v2/std/effect_plan.dag | 5 +- src/v2/std/effect_reach.dag | 17 +- src/v2/std/effects.dag | 27 +- src/v2/std/emit_on_demand.dag | 4 +- src/v2/std/execution_envelope.dag | 14 +- src/v2/std/execution_surface.dag | 6 +- src/v2/std/fn_index.dag | 7 +- src/v2/std/grammar_choice_ambiguity.dag | 4 +- src/v2/std/layer.dag | 10 +- src/v2/std/layer_import_scan.dag | 16 +- src/v2/std/lens_verdict.dag | 4 +- src/v2/std/live_read.dag | 9 +- src/v2/std/materialize.dag | 22 +- src/v2/std/namespace_alias.dag | 8 +- src/v2/std/native_agreement.dag | 9 +- src/v2/std/node.dag | 14 +- src/v2/std/occurrence_id.dag | 3 +- src/v2/std/operation_argv.dag | 53 +- src/v2/std/orchestration.dag | 67 +- src/v2/std/passing_candidate_fold.dag | 6 +- src/v2/std/spine.dag | 16 +- src/v2/std/value_carrier.dag | 17 +- src/v2/std/witness_evaluation.dag | 57 +- src/v2/std/witness_execution_routing.dag | 17 +- src/v2/test/claim/bash_command_fold_test.dag | 5 +- .../test/claim/bash_program_fold_support.dag | 5 +- src/v2/test/claim/bash_program_fold_test.dag | 25 +- .../declaration_structure_preserved_test.dag | 30 +- .../fn_body_seam_witness_helpers.dag | 5 +- .../frontier_three_state_test.dag | 12 +- ...transform_binary_infix_witness_helpers.dag | 6 +- ...er_transform_binary_infix_witness_test.dag | 5 +- .../body_lowering/statement_let_bind_test.dag | 19 +- .../wave1_gate1_c1_copied_port_helpers.dag | 4 +- ...wave1_gate1_d_ingested_fixture_helpers.dag | 5 +- .../wave1_gate1_d_table_fixture_helpers.dag | 3 +- .../body_lowering_wrapper_retained_test.dag | 10 +- ...build_transport_admission_witness_test.dag | 13 +- .../claim/c_compilation_unit_witness_test.dag | 38 +- .../claim/compilation_unit_witness_test.dag | 12 +- .../compile_door_ledger_ownership_test.dag | 15 +- .../compile_eval_thesis_proof_test.dag | 14 +- .../compiler_materialization_witness_test.dag | 7 +- .../accumulator_copy_roster_gate_std_test.dag | 18 +- .../accumulator_copy_roster_gate_test.dag | 7 +- .../table_fixture_add_subject_producer.dag | 3 +- .../table_fixture_bind_subject_producer.dag | 3 +- .../table_fixture_branch_subject_producer.dag | 3 +- .../table_fixture_loop_subject_producer.dag | 3 +- .../table_fixture_match_subject_producer.dag | 3 +- .../concept_index_enumeration_test.dag | 10 +- .../claim/dag_acceptance_rustc_wet_test.dag | 12 +- src/v2/test/claim/dag_acceptance_test.dag | 77 +- .../placement_grain_witness_test.dag | 17 +- .../effect_io_host_grant_witness_test.dag | 6 +- .../effect_plan_bash_materialize_test.dag | 11 +- .../emit/conditional_delimiting_test.dag | 26 +- .../produced_decl_support_preserved_test.dag | 12 +- .../emit/produced_decl_two_target_test.dag | 9 +- .../rust_production_closure_let_emit_test.dag | 30 +- .../semantic_decl_target_refusal_test.dag | 3 +- .../trait_derive_seed_emit_binding_test.dag | 21 +- ...pplemental_generic_bound_contract_test.dag | 12 +- ...ry_effect_confinement_red_control_test.dag | 12 +- .../enforcement_inventory_witness_test.dag | 29 +- ..._host_filesystem_read_equals_eval_test.dag | 15 +- ...mit_host_fold_closure_equals_eval_test.dag | 6 +- ...t_host_shell_exec_run_equals_eval_test.dag | 9 +- ...sical_not_ingested_family_witness_test.dag | 10 +- ..._demand_complement_family_witness_test.dag | 10 +- ...it_on_demand_family_crate_witness_test.dag | 36 +- ...emand_field_access_family_witness_test.dag | 10 +- .../emit_on_demand_kernel_witness_test.dag | 12 +- ...nd_match_loop_fold_family_witness_test.dag | 46 +- ...n_demand_meet_join_family_witness_test.dag | 10 +- ..._variant_construct_family_witness_test.dag | 9 +- ...lassical_not_ingested_equals_eval_test.dag | 24 +- .../emit_host_native_only_verdict_test.dag | 26 +- ...ive_selected_witness_bundle_production.dag | 5 +- .../native_selected_witness_bundle_test.dag | 7 +- ...fication_ledger_overlap_execution_test.dag | 7 +- .../fallback_arm_census_planted_test.dag | 4 +- src/v2/test/claim/fold_lowering_test.dag | 154 +- .../claim/host_build_environment_row_test.dag | 31 +- .../corpus/wall_residue_live_test.dag | 19 +- .../claim/infer_self_grounding_wall_test.dag | 7 +- src/v2/test/claim/layer_transition_test.dag | 36 +- ...n_bit_width_admissibility_witness_test.dag | 6 +- .../lean_overflow_semantics_witness_test.dag | 13 +- .../live_tree/live_tree_collision_test.dag | 6 +- .../accumulator_copy_compile_gate_test.dag | 12 +- .../long/alias_decl_parse_witness_test.dag | 6 +- .../bash_program_real_probes_witness_test.dag | 4 +- .../claim/long/compile_door_ledger_test.dag | 28 +- ...direct_rust_door_production_group_test.dag | 31 +- .../long/door_real_module_probe_test.dag | 13 +- .../long/fallback_arm_census_witness_test.dag | 7 +- ..._probe_closure_matrix_integration_test.dag | 8 +- .../gauntlet_lane_enrollment_witness_test.dag | 53 +- ...er_transform_binary_infix_witness_test.dag | 7 +- ...bitant_neutralization_e2e_witness_test.dag | 7 +- .../long/mandatory_tag_gate_witness_test.dag | 18 +- .../meta_exec_confinement_clean_tree_test.dag | 28 +- .../module_binding_host_consistency_test.dag | 10 +- ...module_storage_binding_derivation_test.dag | 7 +- ...namespace_graft_normalize_witness_test.dag | 6 +- ...native_routing_membership_receipt_test.dag | 14 +- .../parse_binding_fidelity_witness_test.dag | 5 +- .../parse_table_memo_amortization_test.dag | 7 +- ...age_add_pick_relationship_witness_test.dag | 8 +- ...peline_stage_match_corpus_witness_test.dag | 6 +- ...ualification_origin_probe_witness_test.dag | 28 +- .../long/realization_sweep_probe_entry.dag | 11 +- ...on_vocabulary_containment_witness_test.dag | 14 +- ...e_bridged_pipeline_bridge_witness_test.dag | 3 +- .../symbol_index_scaling_receipt_test.dag | 31 +- ...cript_descriptor_node_run_witness_test.dag | 4 +- .../vacuity_consumer_witness_long_test.dag | 195 +- ...idate_ingest_staging_stage_bisect_test.dag | 8 +- .../claim/long/variant_visibility_test.dag | 7 +- ...ve1_gate1_a1_fn_body_seam_witness_test.dag | 8 +- ..._gate1_a1_projection_call_witness_test.dag | 7 +- ...ymbol_index_body_producer_witness_test.dag | 10 +- ...1_bare_call_body_producer_witness_test.dag | 9 +- ...c1_copied_port_derivation_witness_test.dag | 5 +- ...d_ingested_bind_loop_eval_witness_test.dag | 3 +- ...1_gate1_d_mvp_subsumption_witness_test.dag | 4 +- ...te1_general_body_producer_witness_test.dag | 9 +- .../test/claim/loop_infer_iteration_test.dag | 8 +- .../manual/branch_infer_if_then_else_test.dag | 5 +- .../e0599_phase_a_body_evidence_probe.dag | 16 +- .../claim/manual/emit_source_store_test.dag | 9 +- .../manual/floor_drain_retention_test.dag | 14 +- .../manual/int_literal_parse_smoke_test.dag | 4 +- .../manual/loop_demand_driven_eval_test.dag | 5 +- .../match_infer_fail_open_audit_test.dag | 15 +- .../qualified_pattern_reconcile_test.dag | 9 +- .../manual/value_null_split_witness_test.dag | 7 +- .../claim/map_carrier_shape_gate_test.dag | 4 +- .../roster_soundness_test.dag | 30 +- .../claim/module_storage_binding_test.dag | 22 +- .../admission_fail_closed_test.dag | 9 +- .../namespace_graft/collapsed_reader_test.dag | 8 +- .../namespace_graft/marker_strip_test.dag | 5 +- .../normalize_graft_witness_helpers.dag | 4 +- .../namespace_graft/zero_metadata_test.dag | 5 +- .../claim/normalized_tree_admission_test.dag | 7 +- .../parse/parse_binding_fidelity_support.dag | 5 +- .../parse/parse_binding_fidelity_test.dag | 4 +- ...ctive_verification_ledger_witness_test.dag | 12 +- .../program_assembly/real_ingest_test.dag | 11 +- .../provenance/cross_file_provenance_test.dag | 16 +- .../lens_unit/discriminators_test.dag | 4 +- .../no_new_debt_gate_test.dag | 8 +- .../roster_soundness_test.dag | 8 +- ...ied_module_projection_stripped_fixture.dag | 5 +- .../round_trip/dag_comment_wall_test.dag | 17 +- ...compiler_closure_emit_from_ingest_test.dag | 9 +- ...direct_rust_door_emission_witness_test.dag | 8 +- ..._door_fixture_containment_witness_test.dag | 4 +- .../direct_rust_door_group_algebra_test.dag | 41 +- .../self_host/emit_coverage_frontier_test.dag | 8 +- ...er_probe_cause_location_roundtrip_test.dag | 6 +- .../frontier_probe_cause_location_test.dag | 15 +- .../frontier_probe_empty_ingest_test.dag | 11 +- ...ier_probe_semantic_family_receipt_test.dag | 12 +- ...cation_origin_probe_structural_fixture.dag | 6 +- ...ualification_origin_probe_witness_test.dag | 12 +- .../self_host/ref_transport_coverage_test.dag | 45 +- .../seed_retention_frontier_test.dag | 6 +- ...bject_evidence_cause_swap_witness_test.dag | 27 +- ...emitter_direct_rust_door_contract_test.dag | 10 +- ...ve1_gate1_a1_fn_body_seam_witness_test.dag | 5 +- ...ymbol_index_body_producer_witness_test.dag | 6 +- ...c1_copied_port_derivation_witness_test.dag | 5 +- ...1_gate1_d_mvp_subsumption_witness_test.dag | 4 +- src/v2/test/claim/staged_front_end_test.dag | 16 +- ...ait_derive_completeness_predicate_test.dag | 4 +- .../translate_underived_refusal_test.dag | 13 +- .../claim/vacuity_consumer_witness_test.dag | 20 +- .../claim/verilog_interlock_emission_test.dag | 7 +- src/v2/test/claim/witness_admission_test.dag | 16 +- .../witness_exclusion_reconciliation_test.dag | 42 +- .../body_lowering_retention_census_test.dag | 24 +- .../workflow/host_module_binding_manifest.dag | 10 +- .../claim/wrap_decision_predicate_test.dag | 17 +- .../compiler/pipeline/infer_stage_test.dag | 4 +- .../compiler/pipeline/lower_stage_test.dag | 3 +- .../compiler/pipeline/tokenize_stage_test.dag | 4 +- .../identity_captured_navigation_test.dag | 11 +- .../arrow_body_cost_domain_excluded.dag | 4 +- .../lens_cost/copied_port_derivation_test.dag | 6 +- src/v2/test/lens_cost/valuation_test.dag | 42 +- .../disposition_redundancy_test.dag | 12 +- .../non_fold_residue_test.dag | 9 +- src/v2/test/lens_vacuity/vacuity_test.dag | 10 +- src/v2/test/manual/ownership_movable_test.dag | 3 +- .../parse_forensics_scaling_witness.dag | 8 +- src/v2/workflow/bash_emit.dag | 4 +- src/v2/workflow/bootstrap.dag | 3 +- src/v2/workflow/ci_floor_peak_emit.dag | 51 +- src/v2/workflow/ci_floor_peak_emit_test.dag | 6 +- .../ci_heal_revalidation_preflight_emit.dag | 11 +- .../workflow/ci_materialization_emit_test.dag | 4 +- src/v2/workflow/ci_placement.dag | 17 +- ...ci_v1_compiler_tests_compile_gate_emit.dag | 13 +- src/v2/workflow/ci_workflow_run_emit.dag | 13 +- .../workflow/class_b_import_closure_probe.dag | 25 +- .../class_b_import_closure_transport.dag | 44 +- src/v2/workflow/compile_door_ledger.dag | 75 +- .../compiler_closure_ingest_transport.dag | 7 +- src/v2/workflow/dag_acceptance.dag | 164 +- src/v2/workflow/dag_acceptance_rustc.dag | 20 +- .../workflow/effect_plan_bash_materialize.dag | 16 +- .../floor_compile_clean_predicates.dag | 6 +- src/v2/workflow/floor_diff_observe.dag | 11 +- src/v2/workflow/floor_discovery.dag | 23 +- src/v2/workflow/floor_discovery_producer.dag | 12 +- src/v2/workflow/floor_discovery_transport.dag | 8 +- src/v2/workflow/floor_expected_red.dag | 1263 ++++--- .../workflow/floor_expected_red_coherence.dag | 132 +- src/v2/workflow/floor_non_verdict.dag | 185 +- .../workflow/floor_non_verdict_admission.dag | 39 +- src/v2/workflow/floor_preparation.dag | 31 +- src/v2/workflow/legacy_baseline_capture.dag | 159 +- .../module_binding_supply_transport.dag | 10 +- src/v2/workflow/module_resolution_plan.dag | 15 +- src/v2/workflow/native_cache_fusion.dag | 10 +- src/v2/workflow/operand_flow.dag | 11 +- .../orchestration_bash_emit_support.dag | 5 +- .../orchestration_retry_emit_test.dag | 11 +- .../orchestration_tier2_emit_test.dag | 5 +- .../orchestration_while_emit_test.dag | 69 +- src/v2/workflow/product_receipt_stage.dag | 53 +- src/v2/workflow/product_receipt_transport.dag | 8 +- src/v2/workflow/realization_attempt.dag | 69 +- src/v2/workflow/realization_runner.dag | 50 +- src/v2/workflow/realization_sweep.dag | 43 +- .../workflow/realize_advisory_soundness.dag | 9 +- src/v2/workflow/rust_crate_partition.dag | 22 +- src/v2/workflow/source_root_ingest_gate.dag | 12 +- .../workflow/source_root_ingest_transport.dag | 11 +- src/v2/workflow/witness_admission.dag | 62 +- 1754 files changed, 62855 insertions(+), 26420 deletions(-) diff --git a/LICENSING.md b/LICENSING.md index f150c337210..25336e0b9c0 100644 --- a/LICENSING.md +++ b/LICENSING.md @@ -1,8 +1,8 @@ # Licensing — plain language gunbc is released under the [Business Source License 1.1](LICENSE) with an -Additional Use Grant. This page explains what that means; the [LICENSE](LICENSE) -file is the actual agreement and controls wherever the two differ. +Additional Use Grant. This page is a plain-language summary; the [LICENSE](LICENSE) +file is the agreement and controls wherever the two differ. ## The short version @@ -26,19 +26,17 @@ clause, no royalty on output, and no obligation to contribute anything back. ## Is this open source? -Not yet, and we would rather say so than blur it. While a release is inside its -two-year window it is **source-available**: you can read it, modify it, -redistribute it, and use it under the terms above, but the Open Source -Initiative would not call it open source. Two years after a release ships, that -release is plain MIT — genuinely open source, forever. +Not yet. Inside its two-year window a release is **source-available**: you can +read, modify, redistribute and use it under the terms above, but the Open Source +Initiative would not call it open source. Two years after it ships, that release +is plain MIT — open source, forever. ## Why this shape The paid boundary is the distance between the current frontier and the free -trailing edge, and that distance exists only while the project is actively -developed. If development stops, the trailing edge catches up and the -commercial boundary disappears on its own — no adjudication, no argument about -whether the maintainers still deserve it. The clock decides. +trailing edge, which exists only while the project is actively developed. If +development stops, the trailing edge catches up and the commercial boundary +disappears on its own — no adjudication. The clock decides. ## Do I owe anything? @@ -53,15 +51,14 @@ Work through it in order: 4. Otherwise → production use needs a commercial license. Get in touch. "Corporate group" means the entities consolidated in your group's own financial -statements — we use the accounting definition your auditor already applies -rather than inventing one. If a contractor runs gunbc on your behalf, that is -your use. If you merely consume someone else's product or output that happened -to be built with gunbc, that is not your use at all. +statements — the accounting definition your auditor already applies. A +contractor running gunbc on your behalf is your use. Consuming someone else's +product or output built with gunbc is not your use at all. ## History Releases made before this transition remain under the MIT license they shipped -under; that grant is irrevocable and nothing here withdraws it. +under; that grant is irrevocable. ## Contact diff --git a/dag/examples/weather/README.md b/dag/examples/weather/README.md index 7d1bc66c59e..b7e3eb195c1 100644 --- a/dag/examples/weather/README.md +++ b/dag/examples/weather/README.md @@ -1,8 +1,7 @@ # Weather forecast (hero demo) -A self-contained daglang program: domain types, pattern matching on a sum -type, pure functions, and a list pipeline. It compiles to Rust with no -hand-written glue. +A self-contained daglang program — domain types, sum-type pattern matching, pure functions, +a list pipeline — compiled to Rust with no hand-written glue. ## What it shows @@ -44,5 +43,4 @@ All logic lives in [`weather.dag`](weather.dag). The compiler pulls in ## Tests in this repo `src/v1/tests` includes structural checks over the emitted Rust (L4 -bootstrap). That path is heavier than the gate above; the gate is the -fast public smoke test. +bootstrap) — heavier than the gate above, which is the fast public smoke test. diff --git a/dag/extdeps/access/posix.dag b/dag/extdeps/access/posix.dag index 6b5f2e57a1d..d97a82e8004 100644 --- a/dag/extdeps/access/posix.dag +++ b/dag/extdeps/access/posix.dag @@ -1,6 +1,5 @@ module extdeps.access.posix - import extdeps.external_authority { ExternalAuthority } import extdeps.uri { Uri, Https } import std.types { Bool, Int, NonEmptyStr, String } @@ -92,14 +91,21 @@ type PosixUser { shell: PosixLoginShell } - data posix_root_uid: Int = 0 fn posix_user_is_root(user: PosixUser) -> Bool { user.uid == posix_root_uid } -data file_mode_octal_note: String = "The octal spelling is a SERIALIZATION of FileMode, not a second representation of it — the same relation hex has to an sRGB triple. chmod's numeric mode is four octal digits, most significant first: setuid/setgid/sticky, then the owner, group, and other classes, each digit the sum read=4 + write=2 + execute=1 (Open Group sys/stat.h, the authority this module is anchored to). All four digits are rendered rather than the three this repo's callers happen to need today, because a serializer that silently dropped the special-bits digit would emit a mode that means something else the moment setgid is set — the leading digit is 0 here by computation, never by assumption. This exists because FilePermissions had no way to reach a host argv, which is a large part of why it sat modeled and unconsumed." +// The octal spelling is a SERIALIZATION of FileMode, not a second representation of it — the same +// relation hex has to an sRGB triple. chmod's numeric mode is four octal digits, most significant +// first: setuid/setgid/sticky, then the owner, group, and other classes, each digit the sum read=4 +// + write=2 + execute=1 (Open Group sys/stat.h, the authority this module is anchored to). All four +// digits are rendered rather than the three this repo's callers happen to need today, because a +// serializer that silently dropped the special-bits digit would emit a mode that means something +// else the moment setgid is set — the leading digit is 0 here by computation, never by assumption. +// This exists because FilePermissions had no way to reach a host argv, which is a large part of why +// it sat modeled and unconsumed. fn permission_bits_octal_digit(bits: PermissionBits) -> Int { let r = if bits.read { 4 } else { 0 } diff --git a/dag/extdeps/access/posix_effective_principal_read_op.dag b/dag/extdeps/access/posix_effective_principal_read_op.dag index 7be32429311..fb836b0a8ac 100644 --- a/dag/extdeps/access/posix_effective_principal_read_op.dag +++ b/dag/extdeps/access/posix_effective_principal_read_op.dag @@ -14,7 +14,10 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data posix_effective_principal_read_op_note: String = "Typed argv transport for access.PosixEffectivePrincipal.Read — program+args dispatch at the realization edge without shell.Exec.Run (Wave C5 / bash-emit #5828 slice). Hermetic realization refuses this external identity observation; its mock response is a failed read and cannot become principal evidence." +// Typed argv transport for access.PosixEffectivePrincipal.Read — program+args dispatch at the +// realization edge without shell.Exec.Run (Wave C5 / bash-emit #5828 slice). Hermetic realization +// refuses this external identity observation; its mock response is a failed read and cannot become +// principal evidence. fn posix_effective_principal_read_argv() -> List { ["whoami"] diff --git a/dag/extdeps/accessibility/wcag_contrast.dag b/dag/extdeps/accessibility/wcag_contrast.dag index 81fbe8395a8..28a76951e80 100644 --- a/dag/extdeps/accessibility/wcag_contrast.dag +++ b/dag/extdeps/accessibility/wcag_contrast.dag @@ -12,7 +12,23 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data wcag_contrast_note: String = "Cited upstream: W3C WCAG 2.2, definitions of relative luminance and contrast ratio (retrieved 2026-07-21). Relative luminance L = 0.2126 R + 0.7152 G + 0.0722 B where each channel c (sRGB, 0..1) linearizes as: c <= 0.03928 -> c/12.92, else ((c+0.055)/1.055)^2.4 (the 0.03928 knee is WCAG's published constant). Contrast ratio = (L_lighter + 0.05) / (L_darker + 0.05); thresholds 4.5:1 (AA normal text), 3:1 (AA large text and non-text graphical objects, per SC 1.4.11), 7:1 (AAA). Arithmetic here is integer at scale 10000 (one unit = 0.0001). The single approximated kernel is the exponent 2.4, computed as x^2.4 = x^2 * (x^2)^(1/5) with the fifth root by integer Newton iteration at scale 1000 — deterministic, no floats, no lookup tables; wcag_pow24_pin_witnesses in the test claim pins it against hand-computable values of the cited formula. Threshold checks avoid the ratio division entirely: (L_hi + 0.05) >= t * (L_lo + 0.05) compared by integer cross-multiplication, so the pass/fail verdicts are exact up to channel-luminance precision (~2e-4). This module owns perceptual LAW only; the color-space representations it reads (Srgb8, RgbScaled, the HSL conversion) live in extdeps.color.srgb — accessibility consumes the color space, it does not own it (re-homed 2026-07-21, DESIGN sect 3). Entry points exist per authored representation (HSL for the site register's materials, Srgb8 for catalogued swatches) over ONE shared luminance kernel — one law, N representations, never a per-representation fork." +// Cited upstream: W3C WCAG 2.2, definitions of relative luminance and contrast ratio (retrieved +// 2026-07-21). Relative luminance L = 0.2126 R + 0.7152 G + 0.0722 B where each channel c (sRGB, +// 0..1) linearizes as: c <= 0.03928 -> c/12.92, else ((c+0.055)/1.055)^2.4 (the 0.03928 knee is +// WCAG's published constant). Contrast ratio = (L_lighter + 0.05) / (L_darker + 0.05); thresholds +// 4.5:1 (AA normal text), 3:1 (AA large text and non-text graphical objects, per SC 1.4.11), 7:1 +// (AAA). Arithmetic here is integer at scale 10000 (one unit = 0.0001). The single approximated +// kernel is the exponent 2.4, computed as x^2.4 = x^2 * (x^2)^(1/5) with the fifth root by integer +// Newton iteration at scale 1000 — deterministic, no floats, no lookup tables; +// wcag_pow24_pin_witnesses in the test claim pins it against hand-computable values of the cited +// formula. Threshold checks avoid the ratio division entirely: (L_hi + 0.05) >= t * (L_lo + 0.05) +// compared by integer cross-multiplication, so the pass/fail verdicts are exact up to +// channel-luminance precision (~2e-4). This module owns perceptual LAW only; the color-space +// representations it reads (Srgb8, RgbScaled, the HSL conversion) live in extdeps.color.srgb — +// accessibility consumes the color space, it does not own it (re-homed 2026-07-21, DESIGN sect 3). +// Entry points exist per authored representation (HSL for the site register's materials, Srgb8 for +// catalogued swatches) over ONE shared luminance kernel — one law, N representations, never a +// per-representation fork. fn imax(a: Int, b: Int) -> Int { if a > b { a } else { b } diff --git a/dag/extdeps/advertising/google_ads.dag b/dag/extdeps/advertising/google_ads.dag index 056c2dc1587..af73d391413 100644 --- a/dag/extdeps/advertising/google_ads.dag +++ b/dag/extdeps/advertising/google_ads.dag @@ -19,9 +19,18 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { data google_ads_subject: Vendor = alphabet -data google_ads_subject_note: String = "Subject and source are orthogonal axes (operator ruling 2026-07-25, extdeps.pricing.gitlab_subscription precedent): the anchor above is the SOURCE (Google's own product help pages under support.google.com/google-ads), and the subject edge names the ENTITY -- the SEC issuer Alphabet Inc., the filing parent whose income statement reports Google advertising revenue. The consumer-facing product brand is Google Ads; no separate legal-entity row is minted for the operating subsidiary because no fact in this module is about corporate structure." - -data google_ads_provenance_note: String = "Interface facts live-fetched 2026-08-01 from two Google Ads Help pages: 'Determine a bid strategy based on your goals - Google Ads Help' (answer/2472725, the anchor) and 'Auction - Google Ads Help' (answer/142918). Neither page publishes any numeric price, minimum bid, or rate floor -- the pricing interface Google documents is a per-auction mechanism plus an advertiser-set budget cap, which is why the rate row below is AuctionDetermined rather than a money amount." +// Subject and source are orthogonal axes (operator ruling 2026-07-25, +// extdeps.pricing.gitlab_subscription precedent): the anchor above is the SOURCE (Google's own +// product help pages under support.google.com/google-ads), and the subject edge names the ENTITY -- +// the SEC issuer Alphabet Inc., the filing parent whose income statement reports Google advertising +// revenue. The consumer-facing product brand is Google Ads; no separate legal-entity row is minted +// for the operating subsidiary because no fact in this module is about corporate structure. + +// Interface facts live-fetched 2026-08-01 from two Google Ads Help pages: 'Determine a bid strategy +// based on your goals - Google Ads Help' (answer/2472725, the anchor) and 'Auction - Google Ads +// Help' (answer/142918). Neither page publishes any numeric price, minimum bid, or rate floor -- +// the pricing interface Google documents is a per-auction mechanism plus an advertiser-set budget +// cap, which is why the rate row below is AuctionDetermined rather than a money amount. type GoogleAdsBillableUnit = GoogleCostPerClick @@ -69,7 +78,11 @@ fn google_ads_billable_unit_catalog() -> List { ] } -data google_ads_bid_strategy_roster_note: String = "Bid-strategy names transcribed verbatim from the anchor page 2026-08-01. Kept as a wire-label roster rather than a closed enum: this roster is product-versioned help prose, not a machine-facing API enum (the machine-facing contrast is extdeps.advertising.meta_ads, where billing_event and bid_strategy ARE published API enums). Strategies select what the auction optimizes; the billable unit stays one of the four rows above." +// Bid-strategy names transcribed verbatim from the anchor page 2026-08-01. Kept as a wire-label +// roster rather than a closed enum: this roster is product-versioned help prose, not a +// machine-facing API enum (the machine-facing contrast is extdeps.advertising.meta_ads, where +// billing_event and bid_strategy ARE published API enums). Strategies select what the auction +// optimizes; the billable unit stays one of the four rows above. data google_ads_bid_strategy_wire_labels: List = [ "Target cost per action (CPA)", @@ -90,7 +103,10 @@ data google_ads_bid_strategy_wire_labels: List = [ type GoogleAdsBudgetControl = | GoogleAverageDailyBudget -data google_ads_budget_note: String = "The advertiser-side spend cap the page documents, quoted: 'All you have to do is set an average daily budget, and the Google Ads system automatically manages your bids to bring you the most clicks possible within your budget.' The budget is the advertiser's declared cap; the per-event price under it remains auction-determined." +// The advertiser-side spend cap the page documents, quoted: 'All you have to do is set an average +// daily budget, and the Google Ads system automatically manages your bids to bring you the most +// clicks possible within your budget.' The budget is the advertiser's declared cap; the per-event +// price under it remains auction-determined. type GoogleAdsAuctionTimeSignal = GoogleSignalDevice @@ -99,9 +115,14 @@ type GoogleAdsAuctionTimeSignal | GoogleSignalLanguage | GoogleSignalOperatingSystem +// The cited mechanism page ('Auction - Google Ads Help') describes selection and pricing by Ad +// Rank, quoted: eligibility considers 'your bid, ad quality, the Ad Rank thresholds, the context of +// the person's search, and the expected impact of extensions and other ad formats', and 'even if +// your competition bids higher than you, you can still win a higher position -- at a lower price -- +// with highly relevant keywords and ads.' The page contains no numeric amounts, which is the fact +// the AuctionDetermined arm carries. + // Coarse-grain targeting dimensions exactly as the anchor page enumerates them, quoted: Smart Bidding 'factors in a wide range of auction-time signals such as device, location, time of day, language, and operating system to capture the unique context of every search.' The page says 'such as' -- this enum models the five signals the vendor names, and is deliberately NOT claimed closed over everything the auction reads. data google_ads_media_rate: AdPriceDetermination = AuctionDetermined { mechanism_citation: "support.google.com/google-ads/answer/142918", } - -data google_ads_auction_mechanism_note: String = "The cited mechanism page ('Auction - Google Ads Help') describes selection and pricing by Ad Rank, quoted: eligibility considers 'your bid, ad quality, the Ad Rank thresholds, the context of the person's search, and the expected impact of extensions and other ad formats', and 'even if your competition bids higher than you, you can still win a higher position -- at a lower price -- with highly relevant keywords and ads.' The page contains no numeric amounts, which is the fact the AuctionDetermined arm carries." diff --git a/dag/extdeps/advertising/meta_ads.dag b/dag/extdeps/advertising/meta_ads.dag index c226579b6a2..3d4ae7c0904 100644 --- a/dag/extdeps/advertising/meta_ads.dag +++ b/dag/extdeps/advertising/meta_ads.dag @@ -19,7 +19,16 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { data meta_ads_subject: Vendor = meta_platforms -data meta_ads_provenance_note: String = "Interface facts live-fetched 2026-08-01 from the Meta Marketing API developer docs: the anchor page (which, per the API's legacy object naming, is the AD SET reference -- billing_event, bid_strategy, and optimization_goal are ad-set-level fields) and the bidding overview at developers.facebook.com/docs/marketing-api/bidding, which states the billing-event concept verbatim: the system 'defines events you want to pay for, including impressions, clicks, or various actions.' Two Business Help Center pages were attempted and are client-rendered (this fetch reads only their titles): 'About Ad Auctions | Meta Business Help Center' (facebook.com/business/help/430291176997542) and 'Best Practices for Minimum Budgets | Meta Business Help Center' (facebook.com/business/help/203183363050448) -- the latter is why the minimum-budget row below is RateUnavailable with that cause, never a remembered number." +// Interface facts live-fetched 2026-08-01 from the Meta Marketing API developer docs: the anchor +// page (which, per the API's legacy object naming, is the AD SET reference -- billing_event, +// bid_strategy, and optimization_goal are ad-set-level fields) and the bidding overview at +// developers.facebook.com/docs/marketing-api/bidding, which states the billing-event concept +// verbatim: the system 'defines events you want to pay for, including impressions, clicks, or +// various actions.' Two Business Help Center pages were attempted and are client-rendered (this +// fetch reads only their titles): 'About Ad Auctions | Meta Business Help Center' +// (facebook.com/business/help/430291176997542) and 'Best Practices for Minimum Budgets | Meta +// Business Help Center' (facebook.com/business/help/203183363050448) -- the latter is why the +// minimum-budget row below is RateUnavailable with that cause, never a remembered number. type MetaBillingEvent = MetaBillAppInstalls @@ -168,7 +177,11 @@ fn meta_bid_strategy_catalog() -> List { ] } -data meta_optimization_goal_note: String = "optimization_goal wire values transcribed verbatim from the anchor reference 2026-08-01. Kept as a wire-value roster rather than a closed enum: this is the coarse-grain record that the axis EXISTS and is distinct from billing_event; per-goal modeling waits for a consumer that needs it (second-consumer discipline), and a 33-variant enum nobody eliminates over would be roster theater." +// optimization_goal wire values transcribed verbatim from the anchor reference 2026-08-01. Kept as +// a wire-value roster rather than a closed enum: this is the coarse-grain record that the axis +// EXISTS and is distinct from billing_event; per-goal modeling waits for a consumer that needs it +// (second-consumer discipline), and a 33-variant enum nobody eliminates over would be roster +// theater. data meta_optimization_goal_wire_values: List = [ "NONE", diff --git a/dag/extdeps/advertising/types.dag b/dag/extdeps/advertising/types.dag index e2f116ac0cc..daa2b53c972 100644 --- a/dag/extdeps/advertising/types.dag +++ b/dag/extdeps/advertising/types.dag @@ -12,7 +12,16 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data ad_price_determination_note: String = "Auction ad platforms publish the pricing MECHANISM, not a rate card: the amount charged per billable event is determined per-auction and no citable numeric rate exists upstream. AuctionDetermined is therefore the true upstream fact (an answer, with the mechanism page as its citation), while RateUnavailable marks a figure the vendor DOES publish but this fetch could not read (ignorance) -- the DESIGN section 5 distinction between top-as-answer and top-as-ignorance, kept as two typed arms so neither can masquerade as the other. A listed-rate arm is deliberately absent: neither modeled vendor publishes a citable numeric rate today, and that arm is minted when the first citable listed figure appears, never before -- a fabricated plausible rate from training data is exactly the wrong answer, and a vendor rate is precisely the kind of fact that goes stale." +// Auction ad platforms publish the pricing MECHANISM, not a rate card: the amount charged per +// billable event is determined per-auction and no citable numeric rate exists upstream. +// AuctionDetermined is therefore the true upstream fact (an answer, with the mechanism page as its +// citation), while RateUnavailable marks a figure the vendor DOES publish but this fetch could not +// read (ignorance) -- the DESIGN section 5 distinction between top-as-answer and top-as-ignorance, +// kept as two typed arms so neither can masquerade as the other. A listed-rate arm is deliberately +// absent: neither modeled vendor publishes a citable numeric rate today, and that arm is minted +// when the first citable listed figure appears, never before -- a fabricated plausible rate from +// training data is exactly the wrong answer, and a vendor rate is precisely the kind of fact that +// goes stale. type AdPriceDetermination = AuctionDetermined { mechanism_citation: NonEmptyStr } diff --git a/dag/extdeps/ampere/mt_collins_product_brief/bmc.dag b/dag/extdeps/ampere/mt_collins_product_brief/bmc.dag index e7cde99e24d..1eb7b986fa8 100644 --- a/dag/extdeps/ampere/mt_collins_product_brief/bmc.dag +++ b/dag/extdeps/ampere/mt_collins_product_brief/bmc.dag @@ -15,6 +15,11 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { data bmc_subject: MtCollinsPublicProductBrief = mt_collins_public_product_brief +// BUILD-SCOPED: GET /redfish/v1 returns 404 on this shipping build (firmware revision 0.32, probed +// 2026-08-29); the service roster is web/kvm/cd-media/hd-media/ssh with no Redfish service present. +// This is the build-grain carrier the family module's protocol floor points at - family-level +// Redfish support is an AMI marketing claim, decided per build here. + // The management stack shipping on the Foxconn Mt. Collins build, established by // execution against a held unit 2026-08-29 (FRU-confirmed board): AMI MegaRAC, reached // over IPMI lanplus and the proprietary REST surface modeled in extdeps.bmc.megarac, @@ -23,6 +28,10 @@ data bmc_subject: MtCollinsPublicProductBrief = mt_collins_public_product_brief // a workflow holding a Mt. Collins knows which access authority applies. data mt_collins_bmc_firmware: BmcFirmwareFamily = AmiMegaRac -data mt_collins_bmc_no_redfish_observation: String = "BUILD-SCOPED: GET /redfish/v1 returns 404 on this shipping build (firmware revision 0.32, probed 2026-08-29); the service roster is web/kvm/cd-media/hd-media/ssh with no Redfish service present. This is the build-grain carrier the family module's protocol floor points at - family-level Redfish support is an AMI marketing claim, decided per build here." - -data mt_collins_bmc_provenance_note: String = "Grounded by execution, not collateral: the public Ampere/Foxconn documents for Mt. Collins do not state the BMC firmware product. Scope is the observed shipping build; a re-flashed unit would change the family row for that unit - though as of 2026-08-29 no OpenBMC machine layer exists for Mt. Collins: upstream and Ampere-fork meta-ampere carry meta-jade, meta-jefferson and meta-mitchell only, so an OpenBMC Collins is a porting project (the fork ships a porting_guide.md), not a flash, which is why fleet workflows read the family from the unit's intake receipt when present and fall back to this platform default only for an unprobed unit." +// Grounded by execution, not collateral: the public Ampere/Foxconn documents for Mt. Collins do not +// state the BMC firmware product. Scope is the observed shipping build; a re-flashed unit would +// change the family row for that unit - though as of 2026-08-29 no OpenBMC machine layer exists for +// Mt. Collins: upstream and Ampere-fork meta-ampere carry meta-jade, meta-jefferson and +// meta-mitchell only, so an OpenBMC Collins is a porting project (the fork ships a +// porting_guide.md), not a flash, which is why fleet workflows read the family from the unit's +// intake receipt when present and fall back to this platform default only for an unprobed unit. diff --git a/dag/extdeps/ampere/mt_collins_product_brief/memory_population.dag b/dag/extdeps/ampere/mt_collins_product_brief/memory_population.dag index ff56f3a552c..8237d7e62cc 100644 --- a/dag/extdeps/ampere/mt_collins_product_brief/memory_population.dag +++ b/dag/extdeps/ampere/mt_collins_product_brief/memory_population.dag @@ -102,7 +102,12 @@ data mt_collins_population_32_dimms_completion: MtCollinsPopulationCorrection = basis: "Tables 12-13 of the same document populate all sixteen connectors per socket in the full configuration - #17..#32 for socket 1, J32 included - so the printed omission of J32 in Table 11 reads as a printing defect, not a population rule. This completion is gunbc's inference from in-document evidence, not an upstream statement.", } -data population_constraint_note: String = "Table 10 constraint carried with the rows: supported channel counts per socket are 1, 2, 4, 6, and 8 - the population sequences above are the only supported shapes, not a fill-in-any-order suggestion. DIMMs on all populated channels of one socket must be identically sized; socket 0 and socket 1 may differ from each other. Max data rate at these populations is the platform row's DDR4-3200; the guide states the sequences without per-population data-rate derating, so any 2DPC downclock is unstated by this source rather than absent." +// Table 10 constraint carried with the rows: supported channel counts per socket are 1, 2, 4, 6, +// and 8 - the population sequences above are the only supported shapes, not a fill-in-any-order +// suggestion. DIMMs on all populated channels of one socket must be identically sized; socket 0 and +// socket 1 may differ from each other. Max data rate at these populations is the platform row's +// DDR4-3200; the guide states the sequences without per-population data-rate derating, so any 2DPC +// downclock is unstated by this source rather than absent. data mt_collins_memory_population_model_scope: ExternalModelScope = ExternalModelScope { subject: ExternalSubjectRef { diff --git a/dag/extdeps/ampere/mt_collins_product_brief/platform.dag b/dag/extdeps/ampere/mt_collins_product_brief/platform.dag index fce3cba859a..51d82ac99bd 100644 --- a/dag/extdeps/ampere/mt_collins_product_brief/platform.dag +++ b/dag/extdeps/ampere/mt_collins_product_brief/platform.dag @@ -69,7 +69,6 @@ data extdeps_model_scope: ExternalModelScope = mt_collins_platform_model_scope // figure for the 1U chassis if one ever surfaces. // --------------------------------------------------------------------------------------------- - type MtCollinsDimensionProvenance = CitedCollateral | OperatorMeasuredApproximate @@ -88,11 +87,14 @@ data mt_collins_chassis_2u: MtCollinsChassisVariant = MtCollinsChassisVariant { depth_provenance: CitedCollateral, } +// The 2U depth is the collateral's 33.36in converted (847mm). The 1U depth is ~33.75in (857mm) by +// operator tape measure 2026-08-29 on the held unit - approximate, not a datasheet figure, and +// within 10mm of the 2U depth: the 1U variant is the same very deep chassis, not a shortened one. +// Mounting-ear positions and rail-member part numbers remain unmeasured. + data mt_collins_chassis_1u: MtCollinsChassisVariant = MtCollinsChassisVariant { chassis_part_number: "1A624RA00-600-G", rack_units: rack_unit(count: 1), overall_depth: millimeter(count: 857), depth_provenance: OperatorMeasuredApproximate, } - -data chassis_depth_note: String = "The 2U depth is the collateral's 33.36in converted (847mm). The 1U depth is ~33.75in (857mm) by operator tape measure 2026-08-29 on the held unit - approximate, not a datasheet figure, and within 10mm of the 2U depth: the 1U variant is the same very deep chassis, not a shortened one. Mounting-ear positions and rail-member part numbers remain unmeasured." diff --git a/dag/extdeps/ampere/mt_collins_product_brief/subject.dag b/dag/extdeps/ampere/mt_collins_product_brief/subject.dag index 4c0484a6fc7..5f8e89881ff 100644 --- a/dag/extdeps/ampere/mt_collins_product_brief/subject.dag +++ b/dag/extdeps/ampere/mt_collins_product_brief/subject.dag @@ -15,7 +15,10 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data mt_collins_product_brief_subject_note: String = "PUBLIC MARKETING COLLATERAL ONLY. Mt. Collins is dual-socket 2U reference hardware whose engineering documents are NDA-gated through Customer Connect; this brief is recorded so the lane does not repeat the search. HOW THIS REPOSITORY READ THE LOCATOR IS RECORDED AS A GUNBC RECEIPT WITH A DeclarationRef TO mt_collins_public_product_brief, NOT AS A FIELD ON THIS RECORD." +// PUBLIC MARKETING COLLATERAL ONLY. Mt. Collins is dual-socket 2U reference hardware whose +// engineering documents are NDA-gated through Customer Connect; this brief is recorded so the lane +// does not repeat the search. HOW THIS REPOSITORY READ THE LOCATOR IS RECORDED AS A GUNBC RECEIPT +// WITH A DeclarationRef TO mt_collins_public_product_brief, NOT AS A FIELD ON THIS RECORD. type MtCollinsBrief2022V2 {} diff --git a/dag/extdeps/ampere/scp_diagnostic.dag b/dag/extdeps/ampere/scp_diagnostic.dag index 38912f160fd..b452a783f82 100644 --- a/dag/extdeps/ampere/scp_diagnostic.dag +++ b/dag/extdeps/ampere/scp_diagnostic.dag @@ -25,7 +25,12 @@ data extdeps_model_scope: ExternalModelScope = ExternalModelScope { further_citations: [] } -data scp_diagnostic_scope_subject_note: String = "THE SUBJECT IS THE AMPERE SCP/SMpro/PMpro DIAGNOSTIC TOKEN STREAM AND ITS STRUCTURED BOOT-PROGRESS INTERFACE, as an upstream surface only. Deliberately NO token-to-meaning function exists: no codebook has been obtained, so a token is an identity this repository may compare and count and may not interpret. The read-effect axis is part of the subject because the interface is an acknowledged queue rather than a sampled register, which decides whether a reader may poll it. NOT gunbc receipts about a host, NOT any verdict about fatality." +// THE SUBJECT IS THE AMPERE SCP/SMpro/PMpro DIAGNOSTIC TOKEN STREAM AND ITS STRUCTURED +// BOOT-PROGRESS INTERFACE, as an upstream surface only. Deliberately NO token-to-meaning function +// exists: no codebook has been obtained, so a token is an identity this repository may compare and +// count and may not interpret. The read-effect axis is part of the subject because the interface is +// an acknowledged queue rather than a sampled register, which decides whether a reader may poll it. +// NOT gunbc receipts about a host, NOT any verdict about fatality. // AMPERE owns the SCP / SMpro / PMpro diagnostic interfaces and the token // namespace. This module models that upstream shape; it does NOT record what @@ -153,10 +158,19 @@ data ampere_smpro_error_queue_interfaces: List = [ AmpereDiagnosticInterface { sysfs_attribute: "overflow_mem_ue", read_effect: ReadSnapshot }, ] +// The E prefix classifies a token's NAMESPACE, not its consequence. Public boot output from an +// ALTRAD8UD-1L2T / Altra Max M128-30 machine that boots successfully contains E559 and E571, so an +// E token is routinely non-fatal. Conversely P012 appears in that same successful progression +// immediately after 'PMpro output complete', so it is ordinary progress context rather than a stall +// marker. Any claim that a specific token is fatal must come from an observed transition on an +// observed host — token, then reset — and belongs in a fleet receipt, never in this upstream +// module. scp_token_is_error_class answers which NAMESPACE a token belongs to and nothing else; +// there is intentionally no token-to-meaning function here, because with no codebook obtained every +// implementation of one would be invention. + fn scp_token_is_error_class(token: AmpereScpConsoleToken) -> Bool { token.class == ScpECode } // Deliberately absent: any function from a token to a verdict, a severity, or // a description. There is no codebook, so no such function can be honest. -data ampere_scp_token_class_is_not_a_verdict_note: String = "The E prefix classifies a token's NAMESPACE, not its consequence. Public boot output from an ALTRAD8UD-1L2T / Altra Max M128-30 machine that boots successfully contains E559 and E571, so an E token is routinely non-fatal. Conversely P012 appears in that same successful progression immediately after 'PMpro output complete', so it is ordinary progress context rather than a stall marker. Any claim that a specific token is fatal must come from an observed transition on an observed host — token, then reset — and belongs in a fleet receipt, never in this upstream module. scp_token_is_error_class answers which NAMESPACE a token belongs to and nothing else; there is intentionally no token-to-meaning function here, because with no codebook obtained every implementation of one would be invention." diff --git a/dag/extdeps/archive/format.dag b/dag/extdeps/archive/format.dag index ab29ab448c0..d38e0058c0e 100644 --- a/dag/extdeps/archive/format.dag +++ b/dag/extdeps/archive/format.dag @@ -38,7 +38,10 @@ data extdeps_model_scope: ExternalModelScope = ExternalModelScope { further_citations: [gzip_rfc_1952_authority, zstd_rfc_8878_authority] } -data archive_format_decomposition_note: String = "ArchiveFormat is container x compression codec (DESIGN section 2 deep decomposition). A .tar.zst distribution is PosixPaxUstarContainer with Zstd codec — not one opaque TarZst enum member or a literal .tar.zst string. .tar.gz and .tgz are the same axes with different distribution suffix conventions." +// ArchiveFormat is container x compression codec (DESIGN section 2 deep decomposition). A .tar.zst +// distribution is PosixPaxUstarContainer with Zstd codec — not one opaque TarZst enum member or a +// literal .tar.zst string. .tar.gz and .tgz are the same axes with different distribution suffix +// conventions. type PosixPaxUstarContainer {} diff --git a/dag/extdeps/astronomy/stellar_classification.dag b/dag/extdeps/astronomy/stellar_classification.dag index a95ed1bf44e..a0baa8b8d4e 100644 --- a/dag/extdeps/astronomy/stellar_classification.dag +++ b/dag/extdeps/astronomy/stellar_classification.dag @@ -11,7 +11,13 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data stellar_classification_note: String = "Cited upstream: Wikipedia, Stellar classification (the Morgan-Keenan / Harvard system), retrieved 2026-07-21. This module models what the source's main-sequence table actually says — class letters O B A F G K M hottest to coolest, effective temperature ranges in kelvin, BOTH chromaticity columns (Vega-relative and D65-calibrated, verbatim strings), hydrogen-line strength, and the main-sequence share column (kept as source-verbatim text; a typed magnitude lands when a consumer needs arithmetic on it). Perceptual rendering of these rows into any palette is a downstream projection (gunbc.site.starlight), never a fact of this module." +// Cited upstream: Wikipedia, Stellar classification (the Morgan-Keenan / Harvard system), retrieved +// 2026-07-21. This module models what the source's main-sequence table actually says — class +// letters O B A F G K M hottest to coolest, effective temperature ranges in kelvin, BOTH +// chromaticity columns (Vega-relative and D65-calibrated, verbatim strings), hydrogen-line +// strength, and the main-sequence share column (kept as source-verbatim text; a typed magnitude +// lands when a consumer needs arithmetic on it). Perceptual rendering of these rows into any +// palette is a downstream projection (gunbc.site.starlight), never a fact of this module. data stellar_color_perception_quote: String = "Excluding colour-contrast effects in dim light, in typical viewing conditions there are no green, cyan, indigo, or violet stars. 'Yellow' dwarfs such as the Sun are white, 'red' dwarfs are a deep shade of yellow/orange. (Source, section: Conventional and apparent colours.)" diff --git a/dag/extdeps/auth/oidc.dag b/dag/extdeps/auth/oidc.dag index c98a7e7d6f7..135cbff9bc3 100644 --- a/dag/extdeps/auth/oidc.dag +++ b/dag/extdeps/auth/oidc.dag @@ -30,7 +30,20 @@ data oidc_amr_external_authority: ExternalAuthority = ExternalAuthority { } } -data oidc_standard_claims_note: String = "OpenID Connect Core 1.0 section 5.1 STANDARD CLAIMS, which are the OIDF's and not any provider's. email, email_verified and name mean the same thing in every id_token that carries them, so a vendor module redefining them would fork a concept with a specification.\\n\\nALL THREE ARE OPTIONAL AND THE FIRST CUT REQUIRED THEM. Section 5.1 defines standard claims that MAY be returned, and section 5.4 makes their presence contingent on the requested scope — email and email_verified arrive with the email scope, name with profile. An ID Token issued for the openid scope alone carries none of them and is perfectly conforming. Requiring them encoded one observed token's scope grant as though it were the specification.\\n\\nemail_verified is still the one worth naming explicitly: section 5.1 says it is a BOOLEAN, and that when true the provider asserts it took affirmative steps to verify the address at some point — a claim about the provider's past diligence, not a guarantee the address is currently reachable or still controlled by the subject. Treating it as proof of present control is the standard misreading, and the spec says so in the claim's own definition. Its absence is a third state and now representable: 'the provider did not say' is not 'the provider said false'." +// OpenID Connect Core 1.0 section 5.1 STANDARD CLAIMS, which are the OIDF's and not any provider's. +// email, email_verified and name mean the same thing in every id_token that carries them, so a +// vendor module redefining them would fork a concept with a specification.\n\nALL THREE ARE +// OPTIONAL AND THE FIRST CUT REQUIRED THEM. Section 5.1 defines standard claims that MAY be +// returned, and section 5.4 makes their presence contingent on the requested scope — email and +// email_verified arrive with the email scope, name with profile. An ID Token issued for the openid +// scope alone carries none of them and is perfectly conforming. Requiring them encoded one observed +// token's scope grant as though it were the specification.\n\nemail_verified is still the one worth +// naming explicitly: section 5.1 says it is a BOOLEAN, and that when true the provider asserts it +// took affirmative steps to verify the address at some point — a claim about the provider's past +// diligence, not a guarantee the address is currently reachable or still controlled by the subject. +// Treating it as proof of present control is the standard misreading, and the spec says so in the +// claim's own definition. Its absence is a third state and now representable: 'the provider did not +// say' is not 'the provider said false'. type OidcStandardClaims { email: Email? @@ -81,7 +94,25 @@ fn oidc_amr_of_wire(raw: NonEmptyStr) -> OidcAmrDecode { } } -data oidc_id_token_claims_note: String = "THE ID TOKEN IS WHERE REQUIREMENT LIVES, which is why this record has a required core while extdeps.auth.jwt's registered claims are all optional. Section 2 lists iss, sub, aud, exp and iat as REQUIRED in an ID Token — a real obligation the JWT layer does not impose on JWTs generally — so those five are non-optional fields here and nowhere else. That split is the single-authority reading: the RFC owns the claim vocabulary, the profile owns the obligation, and neither restates the other.\\n\\nEVERYTHING ELSE ON THIS RECORD IS OPTIONAL, and the first cut required six claims that no specification universally demands. auth_time is REQUIRED only when the request carried max_age or asked for auth_time as an Essential Claim, and section 2 says so in the claim's own definition; acr and amr are explicitly OPTIONAL there; nonce is required only if it was sent in the request; azp is OPTIONAL and applies to a specific multi-audience case. sid is not in section 2 at all — it comes from Session Management and the logout specs, and is OPTIONAL there. An ID Token carrying none of them is conforming, and the earlier shape could not represent one.\\n\\nauth_time and iat remain DIFFERENT INSTANTS and the distinction still matters for any freshness decision. iat is when this token was issued — which a silent refresh updates — while auth_time is when the human last actually authenticated, which a refresh does not. A max-age policy written against iat would be satisfied forever by automatic refreshes without the user ever proving presence again. The claim being optional makes that worse rather than better: a policy needing auth_time must handle its ABSENCE, and absence is now a state it must match instead of a value it may assume." +// THE ID TOKEN IS WHERE REQUIREMENT LIVES, which is why this record has a required core while +// extdeps.auth.jwt's registered claims are all optional. Section 2 lists iss, sub, aud, exp and iat +// as REQUIRED in an ID Token — a real obligation the JWT layer does not impose on JWTs generally — +// so those five are non-optional fields here and nowhere else. That split is the single-authority +// reading: the RFC owns the claim vocabulary, the profile owns the obligation, and neither restates +// the other.\n\nEVERYTHING ELSE ON THIS RECORD IS OPTIONAL, and the first cut required six claims +// that no specification universally demands. auth_time is REQUIRED only when the request carried +// max_age or asked for auth_time as an Essential Claim, and section 2 says so in the claim's own +// definition; acr and amr are explicitly OPTIONAL there; nonce is required only if it was sent in +// the request; azp is OPTIONAL and applies to a specific multi-audience case. sid is not in section +// 2 at all — it comes from Session Management and the logout specs, and is OPTIONAL there. An ID +// Token carrying none of them is conforming, and the earlier shape could not represent +// one.\n\nauth_time and iat remain DIFFERENT INSTANTS and the distinction still matters for any +// freshness decision. iat is when this token was issued — which a silent refresh updates — while +// auth_time is when the human last actually authenticated, which a refresh does not. A max-age +// policy written against iat would be satisfied forever by automatic refreshes without the user +// ever proving presence again. The claim being optional makes that worse rather than better: a +// policy needing auth_time must handle its ABSENCE, and absence is now a state it must match +// instead of a value it may assume. type OidcIdTokenClaims { issuer: JwtStringOrUri diff --git a/dag/extdeps/bmc/aspeed_ast2500.dag b/dag/extdeps/bmc/aspeed_ast2500.dag index 2d934d0c314..b321f956ab9 100644 --- a/dag/extdeps/bmc/aspeed_ast2500.dag +++ b/dag/extdeps/bmc/aspeed_ast2500.dag @@ -16,7 +16,16 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data aspeed_ast2500_datasheet_note: String = "AST2500/AST2520 A2 Datasheet V1.6 (ASPEED Technology Inc., 2017-05-12). The six management acquisition paths below are the closed set of on-chip mechanisms through which platform management firmware acquires host and board state from this SoC. They are split into hardwired trip lines — digital inputs whose controllers expose per-line interrupt generation with programmable level and edge sensitivity (sections 1.3.26 and 1.3.27) — and pollable sensor readings — paths whose useful value is obtained only by initiating a read transaction or counter sample (sections 1.3.25, 1.3.39, 1.3.41, and 1.3.42). Conflating the two modes is the state-space error this module refuses: a GPIO fault line is not an I2C temperature read, and treating them as one undifferentiated sensor surface hides which failures can be interrupt-driven versus which require a poll loop." +// AST2500/AST2520 A2 Datasheet V1.6 (ASPEED Technology Inc., 2017-05-12). The six management +// acquisition paths below are the closed set of on-chip mechanisms through which platform +// management firmware acquires host and board state from this SoC. They are split into hardwired +// trip lines — digital inputs whose controllers expose per-line interrupt generation with +// programmable level and edge sensitivity (sections 1.3.26 and 1.3.27) — and pollable sensor +// readings — paths whose useful value is obtained only by initiating a read transaction or counter +// sample (sections 1.3.25, 1.3.39, 1.3.41, and 1.3.42). Conflating the two modes is the state-space +// error this module refuses: a GPIO fault line is not an I2C temperature read, and treating them as +// one undifferentiated sensor surface hides which failures can be interrupt-driven versus which +// require a poll loop. // HOW THE BMC ACQUIRES PLATFORM STATE THROUGH THE AST2500 SILICON. Six paths, // closed — not an open-ended sensor enum — because the datasheet names exactly diff --git a/dag/extdeps/bmc/http.dag b/dag/extdeps/bmc/http.dag index 60385c67b6a..c81dd8e35f1 100644 --- a/dag/extdeps/bmc/http.dag +++ b/dag/extdeps/bmc/http.dag @@ -1,6 +1,5 @@ module extdeps.bmc.http - import extdeps.external_authority { ExternalAuthority } import extdeps.uri { Uri, Https } import std.decl_ref { DeclarationRef, WholeDeclaration } @@ -24,36 +23,42 @@ data redfish_http_hardwired_transport_dissolution_trigger: Disposition = Scaffol data redfish_http_hardwired_transport_note: String = "§3(b) declared debt: every operation below re-spells the shell/curl transport handler per row (the hardwired-single-transport trap; the DMTF-owned interface facts are only the method x path x auth-mode axes). Dissolves when the operations become declarative transport rows realized by the shared HTTP-client machinery - i.e. when `transport rest` (or a derived-curl realization of the extdeps.http.client shape) supports the three axes these argv rows carry by hand: netrc/basic auth, insecure-TLS (self-signed BMC certs), and request-body-from-file. Until then the argv rows stay - live BMC consumers (bmc_onboard*, bmc_read_telemetry, bmc_first_contact, srv3_boot_once_cd) execute them as-is." -data redfish_http_success_semantics_note: String = "LOAD-BEARING: success:Bool derives from exit_success, so curl MUST fail-close on HTTP status. Every argv carries --fail-with-body (curl >=7.76). WITHOUT it curl exits 0 on 401/403/404/5xx and `success` reads a rejected auth or a missing resource as TRUE — a §5 fail-open: a factory-401 misreads as FactoryCredentialActive (so an already-rotated BMC gets re-rotated), and a rotation PATCH that 401s reports rotated while changing nothing (the 'secret populated, server never rotated' state). --fail-with-body (not bare --fail) keeps the OpenBMC error body in `body` for diagnosis (e.g. PropertyValueFormatError on a rejected password). Do not drop the flag; a new redfish op without it re-opens the arm. Found by execution against srv4 2026-07-23." +// LOAD-BEARING: success:Bool derives from exit_success, so curl MUST fail-close on HTTP status. +// Every argv carries --fail-with-body (curl >=7.76). WITHOUT it curl exits 0 on 401/403/404/5xx and +// `success` reads a rejected auth or a missing resource as TRUE — a §5 fail-open: a factory-401 +// misreads as FactoryCredentialActive (so an already-rotated BMC gets re-rotated), and a rotation +// PATCH that 401s reports rotated while changing nothing (the 'secret populated, server never +// rotated' state). --fail-with-body (not bare --fail) keeps the OpenBMC error body in `body` for +// diagnosis (e.g. PropertyValueFormatError on a rejected password). Do not drop the flag; a new +// redfish op without it re-opens the arm. Found by execution against srv4 2026-07-23. // EVERY REDFISH OPERATION AUTHENTICATES BY FILE, AND THE ABSENCE OF `Secret` IN THIS MODULE'S // IMPORTS IS THE RECEIPT. GetSystem, GetPower and GetThermal each declared `password: Secret` and -// then interpolated it into an argv token -- `-u {username}:{password}` -- so the credential became -// a word in curl's command line. On the fleet hosts that is not a theoretical exposure: /proc is -// mounted with no hidepid, /proc/PID/cmdline is mode 0444, and the CI runner slots are ordinary host -// systemd services sharing the host pid namespace, so any job payload reads it with an unprivileged -// ps. bmc_onboard called GetSystem with the FRESHLY ROTATED password, which put the new credential -// in argv at the exact moment the old one was being retired. +// interpolated it into an argv token -- `-u {username}:{password}` -- so the credential became a +// word in curl's command line. On the fleet hosts that is a real exposure: /proc is mounted with no +// hidepid, /proc/PID/cmdline is mode 0444, and the CI runner slots are ordinary host systemd +// services sharing the host pid namespace, so any job payload reads it with an unprivileged ps. +// bmc_onboard called GetSystem with the FRESHLY ROTATED password, putting the new credential in +// argv at the moment the old one was retired. // -// THIS WAS A HALF-FINISHED REPLACEMENT MIGRATION, NOT A NEW DISCOVERY, and that is the part worth -// recording. Every MUTATING operation here already used --netrc-file, and a GetSystemWithNetrc sat -// directly beside the leaking GetSystem answering the identical question. So the correct shape was -// already present and already understood; what kept the leak alive was that the old form was left -// standing next to the new one instead of being cut at the root. While X stands, callers keep +// THIS WAS A HALF-FINISHED REPLACEMENT MIGRATION, NOT A NEW DISCOVERY. Every MUTATING operation +// already used --netrc-file, and a GetSystemWithNetrc sat beside the leaking GetSystem answering +// the identical question. The correct shape was present and understood; the leak stayed alive +// because the old form was left standing instead of cut at the root. While X stands, callers keep // reaching for it -- five call sites did. // -// The cut is therefore at the root rather than at the leaves: the three argv-borne arms are DELETED, -// not deprecated, and GetSystemWithNetrc is deleted too because once GetSystem no longer takes a -// password the `WithNetrc` suffix distinguishes nothing and is a second name for one concept. One -// operation per resource, authenticating one way. A future operation cannot reintroduce the class by -// copying a neighbour, because no neighbour spells it any more. +// The cut is at the root: the three argv-borne arms are DELETED, not deprecated, and +// GetSystemWithNetrc is deleted too because once GetSystem takes no password the `WithNetrc` suffix +// distinguishes nothing and is a second name for one concept. One operation per resource, +// authenticating one way. A future operation cannot reintroduce the class by copying a neighbour, +// because no neighbour spells it. // // WHAT THIS DOES NOT CLAIM: the module is clean by CONSTRUCTION nowhere. Nothing refuses a new -// `password: Secret` interpolated into an argv token -- v2.std.operation_argv's binding wall refuses -// undeclared names and a non-literal executable position, but ShellTransportOperationRow carries -// declared_inputs as a List, names without types, so the binder cannot see that an input is -// Secret and could not refuse it. That missing declared-input TYPE on the row is this class's -// next-rung trigger; until it lands the class sits at mitigatable and this note is diligence. +// `password: Secret` interpolated into an argv token -- v2.std.operation_argv's binding wall +// refuses undeclared names and a non-literal executable position, but ShellTransportOperationRow +// carries declared_inputs as a List, names without types, so the binder cannot see that an +// input is Secret. That missing declared-input TYPE on the row is this class's next-rung trigger; +// until it lands the class sits at mitigatable and this note is diligence. service redfish.Http { operation GetServiceRoot { diff --git a/dag/extdeps/bmc/megarac.dag b/dag/extdeps/bmc/megarac.dag index ad3733c7033..ed24cce85ec 100644 --- a/dag/extdeps/bmc/megarac.dag +++ b/dag/extdeps/bmc/megarac.dag @@ -13,7 +13,15 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data megarac_scope_note: String = "AMI MEGARAC, THE PROPRIETARY BMC FIRMWARE STACK - subject authority: extdeps.firmware.megarac.subject. SCOPE DISCIPLINE, PER GRAIN: rows in this module answer at the FAMILY grain and each states its basis - a published vendor default, or the guaranteed-across-builds floor. Anything observed on one shipping build only (Redfish absence, firmware revision, service roster) is a BUILD-scoped fact and lives with the build binding (extdeps.ampere.mt_collins_product_brief.bmc for the Foxconn Mt. Collins build), never promoted here. The REST operation rows below are the SP-X interface shape as executed on that build - the one place a single-build execution grounds a family-grain row - and carry that provenance explicitly." +// AMI MEGARAC, THE PROPRIETARY BMC FIRMWARE STACK - subject authority: +// extdeps.firmware.megarac.subject. SCOPE DISCIPLINE, PER GRAIN: rows in this module answer at the +// FAMILY grain and each states its basis - a published vendor default, or the +// guaranteed-across-builds floor. Anything observed on one shipping build only (Redfish absence, +// firmware revision, service roster) is a BUILD-scoped fact and lives with the build binding +// (extdeps.ampere.mt_collins_product_brief.bmc for the Foxconn Mt. Collins build), never promoted +// here. The REST operation rows below are the SP-X interface shape as executed on that build - the +// one place a single-build execution grounds a family-grain row - and carry that provenance +// explicitly. // The family answer is the PUBLISHED default - which is what FactoryLogin's // published_password field means - admin/admin being AMI's widely published MegaRAC @@ -81,6 +89,14 @@ data megarac_op_media_status: MegaRacApiOperation = MegaRacApiOperation { contract_note: "one row per media instance: media_type, image_name, session_index, redirection_status", } +// THE EXECUTED HEADLESS BOOT-FROM-ISO SEQUENCE (Foxconn Mt. Collins build, 2026-08-29): export the +// ISO directory over NFS to the BMC address; PUT media config; wait for the rmedia retry to mount +// (images listing goes from refusal to listing); POST start-media WITH image_redirection:1; +// ipmitool chassis bootdev cdrom then power reset. Execution receipts available without any screen: +// the NFS server's read counter (bulk reads then quiet = image loaded), the configurations row +// (redirection_status/session_index), and the boot CPU's power rails rising while the second socket +// idles. + data megarac_api_operations: List = [ megarac_op_session_login, megarac_op_media_config, @@ -89,5 +105,3 @@ data megarac_api_operations: List = [ megarac_op_media_stop, megarac_op_media_status, ] - -data megarac_virtual_cd_boot_recipe_note: String = "THE EXECUTED HEADLESS BOOT-FROM-ISO SEQUENCE (Foxconn Mt. Collins build, 2026-08-29): export the ISO directory over NFS to the BMC address; PUT media config; wait for the rmedia retry to mount (images listing goes from refusal to listing); POST start-media WITH image_redirection:1; ipmitool chassis bootdev cdrom then power reset. Execution receipts available without any screen: the NFS server's read counter (bulk reads then quiet = image loaded), the configurations row (redirection_status/session_index), and the boot CPU's power rails rising while the second socket idles." diff --git a/dag/extdeps/bmc/openbmc_fan_control.dag b/dag/extdeps/bmc/openbmc_fan_control.dag index d2e35a0d079..334dca3f2bb 100644 --- a/dag/extdeps/bmc/openbmc_fan_control.dag +++ b/dag/extdeps/bmc/openbmc_fan_control.dag @@ -113,9 +113,16 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { }, } -data openbmc_fan_control_authority_anchor_scaffold_note: String = "SCAFFOLD (mandatory-tag corpus cadence) - the host scanner reads the correct openbmc_busctl_authority alias as absent, so the literal anchor duplicates its projection; openbmc_busctl_authority remains the semantic authority. This copy dissolves with the host seam under the corpus_scan_scaffold_note in v2.lens.mandatory_tag.corpus_scan when native witness realization runs the parse-grain corpus witness." +// SCAFFOLD (mandatory-tag corpus cadence) - the host scanner reads the correct +// openbmc_busctl_authority alias as absent, so the literal anchor duplicates its projection; +// openbmc_busctl_authority remains the semantic authority. This copy dissolves with the host seam +// under the corpus_scan_scaffold_note in v2.lens.mandatory_tag.corpus_scan when native witness +// realization runs the parse-grain corpus witness. -data openbmc_systemctl_is_active_authority_note: String = "The systemctl(1) is-active contract names the unit-state tokens and assigns a nonzero exit status when no named unit is active. This decoder admits only the exact inactive token as the typed false observation; other non-active states remain typed refusals rather than being collapsed into inactive." +// The systemctl(1) is-active contract names the unit-state tokens and assigns a nonzero exit status +// when no named unit is active. This decoder admits only the exact inactive token as the typed +// false observation; other non-active states remain typed refusals rather than being collapsed into +// inactive. data openbmc_systemctl_is_active_authority: ExternalAuthority = systemctl_authority @@ -1821,6 +1828,13 @@ fn openbmc_wait_seconds( openbmc_execute(session: session, operation: OpenBmcWaitSeconds { duration: duration }) } +// OpenBMC fan observation and mutation are a composition of typed argv and typed file-transfer +// operations. ObjectMapper and sensor values are requested from busctl as JSON and projected +// through typed local jq operations; no consumer parses busctl's presentation text. The jq programs +// are confined to this extdeps realizer as fixed semantic projections; gunbc intent never +// constructs shell syntax. Password material remains on sshpass fd 0. The caller separately models +// which file-publication operations establish a durability commit barrier. + fn openbmc_upload_file( session: PasswordSshSession, local_path: NonEmptyStr, @@ -1832,5 +1846,3 @@ fn openbmc_upload_file( destination_path: remote_path, ) } - -data openbmc_fan_typed_operation_note: String = "OpenBMC fan observation and mutation are a composition of typed argv and typed file-transfer operations. ObjectMapper and sensor values are requested from busctl as JSON and projected through typed local jq operations; no consumer parses busctl's presentation text. The jq programs are confined to this extdeps realizer as fixed semantic projections; gunbc intent never constructs shell syntax. Password material remains on sshpass fd 0. The caller separately models which file-publication operations establish a durability commit barrier." diff --git a/dag/extdeps/bmc/openbmc_operation.dag b/dag/extdeps/bmc/openbmc_operation.dag index 47d9864f78f..4138ac8254b 100644 --- a/dag/extdeps/bmc/openbmc_operation.dag +++ b/dag/extdeps/bmc/openbmc_operation.dag @@ -19,7 +19,11 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { data openbmc_busctl_authority: ExternalAuthority = extdeps_external_authority_anchor -data openbmc_operation_authority_anchor_scaffold_note: String = "SCAFFOLD (mandatory-tag corpus cadence) - the host scanner reads an aliased anchor as absent, so the literal anchor grounds its projection while openbmc_busctl_authority remains the semantic authority name. This copy dissolves with the host seam under the corpus_scan_scaffold_note in v2.lens.mandatory_tag.corpus_scan when native witness realization runs the parse-grain corpus witness." +// SCAFFOLD (mandatory-tag corpus cadence) - the host scanner reads an aliased anchor as absent, so +// the literal anchor grounds its projection while openbmc_busctl_authority remains the semantic +// authority name. This copy dissolves with the host seam under the corpus_scan_scaffold_note in +// v2.lens.mandatory_tag.corpus_scan when native witness realization runs the parse-grain corpus +// witness. type OpenBmcOperation = OpenBmcReadFile { path: NonEmptyStr } diff --git a/dag/extdeps/bmc/openbmc_password_ssh_transport.dag b/dag/extdeps/bmc/openbmc_password_ssh_transport.dag index 570af8f380d..204825e6d36 100644 --- a/dag/extdeps/bmc/openbmc_password_ssh_transport.dag +++ b/dag/extdeps/bmc/openbmc_password_ssh_transport.dag @@ -12,7 +12,11 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { }, } -data openbmc_password_ssh_authority_anchor_scaffold_note: String = "SCAFFOLD (mandatory-tag corpus cadence) - the host scanner reads the correct sshpass_authority alias as absent, so the literal anchor duplicates its projection; sshpass_authority remains the semantic authority. This copy dissolves with the host seam under the corpus_scan_scaffold_note in v2.lens.mandatory_tag.corpus_scan when native witness realization runs the parse-grain corpus witness." +// SCAFFOLD (mandatory-tag corpus cadence) - the host scanner reads the sshpass_authority alias as +// absent, so the literal anchor duplicates its projection; sshpass_authority stays the semantic +// authority. Dissolves with the host seam under corpus_scan_scaffold_note in +// v2.lens.mandatory_tag.corpus_scan when native witness realization runs the parse-grain corpus +// witness. data openbmc_password_ssh_transport_note: String = "Every transport operation has fixed argv selected by the closed OpenBmcOperation interpreter. There is no command-string or caller-authored argv input. SSH verifies the target-specific known-hosts file and password material stays on inherited fd 0." diff --git a/dag/extdeps/bmc/pid_control_program.dag b/dag/extdeps/bmc/pid_control_program.dag index a4553a32022..73f91f75e84 100644 --- a/dag/extdeps/bmc/pid_control_program.dag +++ b/dag/extdeps/bmc/pid_control_program.dag @@ -303,7 +303,11 @@ type BmcFanControlProgram { // the root once, rather than main hosting a dual-authority interval with a half-migrated writer. // Until then that type is FROZEN: no new fields, no new coefficients, no new callers. A change // that would widen it is a change that belongs here instead. -data partial_prior_authority_note: String = "extdeps.bmc.openbmc_fan_control OpenBmcStepwiseController and OpenBmcFanController are a lossy subset of this model, retained because extdeps.bmc.openbmc_operation actuates through them and actuation is a separately-decided lane. Frozen: no new rows on their growth surfaces. Dissolve-on: the actuation cutover, which replaces the desired-config argument bundle with a projection of BmcFanControlProgram and deletes both types with openbmc_fan_config_desired_json." +// extdeps.bmc.openbmc_fan_control OpenBmcStepwiseController and OpenBmcFanController are a lossy +// subset of this model, retained because extdeps.bmc.openbmc_operation actuates through them and +// actuation is a separately-decided lane. Frozen: no new rows on their growth surfaces. +// Dissolve-on: the actuation cutover, which replaces the desired-config argument bundle with a +// projection of BmcFanControlProgram and deletes both types with openbmc_fan_config_desired_json. fn program_sensor_named(program: BmcFanControlProgram, name: NonEmptyStr) -> List { filter(program.sensors, sensor => sensor.name == name) diff --git a/dag/extdeps/bmc/types.dag b/dag/extdeps/bmc/types.dag index 9816c4f71e3..2001ea71296 100644 --- a/dag/extdeps/bmc/types.dag +++ b/dag/extdeps/bmc/types.dag @@ -249,7 +249,12 @@ type ValidatedBmcFanCurve { curve: BmcFanCurve } -data validated_bmc_fan_curve_construction_scaffold_note: String = "🟡 dissolve-on: docs/plans/hollow-alias-construction-wall.md makes refinement/bodyless declarations construction-enforced after its lens-first corpus proof. validate_bmc_fan_curve is the sole producer in this module and returns typed, located invalid causes, but .dag record literals remain public, so callers can still forge this proof wrapper. This is the same corpus-wide language-layer gap as ValidatedGrammarRoot, not authority for a second validation protocol." +// 🟡 dissolve-on: docs/plans/hollow-alias-construction-wall.md makes refinement/bodyless +// declarations construction-enforced after its lens-first corpus proof. validate_bmc_fan_curve is +// the sole producer in this module and returns typed, located invalid causes, but .dag record +// literals remain public, so callers can still forge this proof wrapper. This is the same +// corpus-wide language-layer gap as ValidatedGrammarRoot, not authority for a second validation +// protocol. type BmcFanCurveInvalidCause = BmcFanCurveEmpty diff --git a/dag/extdeps/bmc/virtual_media.dag b/dag/extdeps/bmc/virtual_media.dag index 5448ca922db..9cd8b6076c9 100644 --- a/dag/extdeps/bmc/virtual_media.dag +++ b/dag/extdeps/bmc/virtual_media.dag @@ -12,7 +12,13 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data virtual_media_fork_reconciliation: String = "§3 single-authority: 'present a block source (ISO) to a host as USB storage' is ONE concept — VirtualMediaIntent — with N transport realizations named centrally in VirtualMediaTransport (the dispatch axis). ConfigfsGadgetTransport (nbd-server → nbd-client → configfs mass_storage gadget; realized in gunbc.bmc_virtual_media) and NbdProxyWebsocatTransport (nbdkit → websocat → bmcweb WebUI nbd wss; realized in extdeps.bmc.webui.nbd_proxy_serve) were two independent virtual-media concepts; they now consume this shared intent. Per §3 the dispatch that selects a realization is itself realization, so each transport's concrete plan binds in its peripheral module, not here." +// §3 single-authority: 'present a block source (ISO) to a host as USB storage' is ONE concept — +// VirtualMediaIntent — with N transport realizations named centrally in VirtualMediaTransport (the +// dispatch axis). ConfigfsGadgetTransport (nbd-server → nbd-client → configfs mass_storage gadget; +// realized in gunbc.bmc_virtual_media) and NbdProxyWebsocatTransport (nbdkit → websocat → bmcweb +// WebUI nbd wss; realized in extdeps.bmc.webui.nbd_proxy_serve) were two independent virtual-media +// concepts; they now consume this shared intent. Per §3 the dispatch that selects a realization is +// itself realization, so each transport's concrete plan binds in its peripheral module, not here. type VirtualMediaSource { iso_path: NonEmptyStr diff --git a/dag/extdeps/boards/asrock_rack.dag b/dag/extdeps/boards/asrock_rack.dag index ae76e7adb55..8762f65df7e 100644 --- a/dag/extdeps/boards/asrock_rack.dag +++ b/dag/extdeps/boards/asrock_rack.dag @@ -157,7 +157,17 @@ data asrock_altrad8ud_1l2t_bmc_redfish_surface_catalog: List = [ BmcFanCurveStep { at_or_above: celsius(40), duty: percent(30) }, @@ -227,7 +237,13 @@ data asrock_altrad8ud_openbmc_3_22_00_fan_policy_observation: BmcFanPolicyObserv observed_at: 1785110400000, } -data asrock_altrad8ud_fan_policy_observation_note: String = "Authenticated read-only observations on srv3 (OpenBMC 2.07.00) and srv4 (OpenBMC 3.22.00), 2026-07-27: both select /usr/share/swampd/config-asrr.json through /usr/share/swampd/config.json and run /usr/bin/swampd under phosphor-pid-control.service. Both factory images carried the same TEMP_SOC curve. That curve is an observation owned here, not fleet intent. What changed between versions is the PID-entry topology and Redfish resource shape, not the five factory points. The 3.22 web UI /Managers/bmc/FanControl resource exposes InitialDuty; it does not own these thermal curve points." +// Authenticated read-only observations on srv3 (OpenBMC 2.07.00) and srv4 (OpenBMC 3.22.00), +// 2026-07-27: both select /usr/share/swampd/config-asrr.json through /usr/share/swampd/config.json +// and run /usr/bin/swampd under phosphor-pid-control.service. Both factory images carried the same +// TEMP_SOC curve. That curve is an observation owned here, not fleet intent. What changed between +// versions is the PID-entry topology and Redfish resource shape, not the five factory points. The +// 3.22 web UI /Managers/bmc/FanControl resource exposes InitialDuty; it does not own these thermal +// curve points. data asrock_altrad8ud_openbmc_2_07_00_nbd_proxy_primary_endpoint: BmcwebNbdProxyEndpointVariant = VmSlotIndexEndpoint @@ -424,7 +440,23 @@ data asrock_altrad8ud_firmware_release_catalog: List = [ asrock_altrad8ud_bmc_3_22_00_release, ] -data asrock_altrad8ud_firmware_release_note: String = "Cited from the ALTRAD8UD-1L2T product download table; all rows dated 2026-04-14. The BIOS rows are the load-bearing ones for the 2026-08-11 fleet incident: 'Updated RC to 0ACRB_021' is the MEMORY REFERENCE CODE, and srv3 fails DDR training on Samsung M386A4G40DM0-CPB 32GB 4Rx4 LRDIMMs while running DRAM FW 220414 (2022) under BL1 built 2023-05-17, emitting per-controller WRLVL errors and 'DDR initialization failure. Can't fall back!' on all eight controllers. ONE RELEASE, TWO PACKAGINGS, and they are not interchangeable: the ROM zip carries a bare 33554432-byte ALTRAD8UD-1L2T_3.10.ROM applied from BIOS setup by Instant Flash, which is unreachable on a host that cannot POST; the TAR zip carries obmc-bios-altrad8ud-1l2t-3.10.all.tar, whose MANIFEST declares purpose=...VersionPurpose.Host, version=3.10, MachineName=altrad8ud-1l2t, Type=bios, and which is what the BMC UpdateService consumes out-of-band. Every byte count and digest here was MEASURED on 2026-08-11 by acquiring the artifact and hashing it, never transcribed: the ROM zip at 5359089 bytes, the TAR zip at 5359341, the inner push tar at 33566720. The TAR locator was not published on the product page — it was found by probing the vendor download host and confirmed by HTTP 200 plus successful extraction, so it is weaker provenance than a page-published link and should be re-derived if the vendor reorganizes. The BMC 03.22.00 row remains digest-Absent with an unobserved locator: its image_bytes is the page's stated 27.71MB converted, NOT a Content-Length, so it must not be treated as a fetchable artifact until its own locator is observed." +// Cited from the ALTRAD8UD-1L2T product download table; all rows dated 2026-04-14. The BIOS rows +// are load-bearing for the 2026-08-11 fleet incident: 'Updated RC to 0ACRB_021' is the MEMORY +// REFERENCE CODE, and srv3 fails DDR training on Samsung M386A4G40DM0-CPB 32GB 4Rx4 LRDIMMs while +// running DRAM FW 220414 (2022) under BL1 built 2023-05-17, emitting per-controller WRLVL errors +// and 'DDR initialization failure. Can't fall back!' on all eight controllers. ONE RELEASE, TWO +// PACKAGINGS, not interchangeable: the ROM zip carries a bare 33554432-byte ALTRAD8UD-1L2T_3.10.ROM +// applied from BIOS setup by Instant Flash, unreachable on a host that cannot POST; the TAR zip +// carries obmc-bios-altrad8ud-1l2t-3.10.all.tar, whose MANIFEST declares +// purpose=...VersionPurpose.Host, version=3.10, MachineName=altrad8ud-1l2t, Type=bios, and which +// the BMC UpdateService consumes out-of-band. Every byte count and digest here was MEASURED on +// 2026-08-11 by acquiring the artifact and hashing it, never transcribed: ROM zip 5359089 bytes, +// TAR zip 5359341, inner push tar 33566720. The TAR locator was not published on the product page — +// it was found by probing the vendor download host and confirmed by HTTP 200 plus successful +// extraction, so it is weaker provenance than a page-published link and should be re-derived if +// the vendor reorganizes. The BMC 03.22.00 row remains digest-Absent with an unobserved locator: +// its image_bytes is the page's stated 27.71MB converted, NOT a Content-Length, so it must not be +// treated as a fetchable artifact until its own locator is observed. // THE EXECUTED PAIR that establishes the BIOS-tar prerequisite. Same 33566720 // bytes, two hosts of this board model, opposite outcomes — which is what makes @@ -457,7 +489,15 @@ data asrock_altrad8ud_bios_openbmc_prerequisite_evidence: List = [ // code table is a different namespace that does not decode P012/E588/P029. data asrock_altrad8ud_scp_console_tcp_port: Nat = 2202 - // --------------------------------------------------------------------------- -// THE BOARD'S MECHANICAL FACTS, converged here from the former -// extdeps.boards.asrock module. Both modules described the SAME physical board -// against two public surfaces of the same vendor — the product page and the -// manual PDF — which is the section 3 fork: one concept, two authorities, and -// nothing structural keeping their slot counts, socket rows or vendor edges in -// agreement. The mechanical facts are rows in this subject module now, and the -// product page they were read from is the authority this module ALREADY -// declared as asrock_altrad8ud_download_authority, so the convergence removes a -// module without adding a second citation of one page. +// THE BOARD'S MECHANICAL FACTS, converged here from the former extdeps.boards.asrock module. Both +// modules described the SAME physical board against two public surfaces of the same vendor — the +// product page and the manual PDF — the section 3 fork: one concept, two authorities, nothing +// structural keeping their slot counts, socket rows or vendor edges in agreement. The mechanical +// facts are rows in this subject module now, and the product page they were read from is the +// authority this module ALREADY declared as asrock_altrad8ud_download_authority, so the +// convergence removes a module without adding a second citation of one page. // --------------------------------------------------------------------------- data asrock_altrad8ud_mechanical_model_scope: ExternalModelScope = ExternalModelScope { diff --git a/dag/extdeps/boards/gigabyte.dag b/dag/extdeps/boards/gigabyte.dag index a5f4af807ec..c67f0371a8d 100644 --- a/dag/extdeps/boards/gigabyte.dag +++ b/dag/extdeps/boards/gigabyte.dag @@ -56,15 +56,15 @@ data gigabyte_mp72_hb0_catalog: BaseboardCatalogRow = BaseboardCatalogRow { } // bmc_firmware is the WEAKEST field in the row above. OpenBmc is asserted from family convention -// for an Ampere Altra server baseboard and is NOT read from a vendor manual; the ASRock row earned -// its OpenBmc through authenticated live probes against srv3 and srv4, and this row has no such -// receipt. Treat it as a placeholder that a first boot replaces, not as a catalog fact. +// for an Ampere Altra server baseboard, NOT read from a vendor manual; the ASRock row earned its +// OpenBmc through authenticated live probes against srv3 and srv4, and this row has no such +// receipt. A placeholder a first boot replaces, not a catalog fact. data gigabyte_mp72_hb0_bmc_firmware_standing_note: NonEmptyStr = "BMC firmware family is convention-asserted, not observed. No MP72-HB0 has been powered by this operator." // Vendor-page facts, independently verified 2026-08-11 against the Gigabyte product page and -// reseller listings rather than accepted from a routed review that asserted them. Every field here -// is a READING, never a measurement: no MP72-HB0 has been held, powered, or measured by this -// operator, and none is cited by any PhysicalAsset. +// reseller listings rather than accepted from a routed review that asserted them. Every field is a +// READING, never a measurement: no MP72-HB0 has been held, powered, or measured by this operator, +// and none is cited by any PhysicalAsset. type BaseboardPhysicalProfile { model_label: NonEmptyStr socket_count: Nat @@ -98,24 +98,23 @@ data gigabyte_mp72_hb0_physical_profile: BaseboardPhysicalProfile = BaseboardPhy } // CORRECTED 2026-08-11 after a routed review challenged the previous row, which said -// atx_24pin_count 1 / eps_8pin_count 2. That was FALSE: it was asserted by this session with -// confidence, carried as SEED DEBT 3 "pending pin-verification", and never closed before a witness -// was built on top of it. The corrected reading is THREE 8-pin ATX12V inputs (ATX12V1/2/3) plus a -// 4-pin Micro-Fit signal connector (ATX4PIN1), NO 24-pin socket, a bundled 24-pin-to-4-pin -// converter for ATX-supply operation, and direct +12 V DC input in which the 4-pin signal path is -// unused. +// atx_24pin_count 1 / eps_8pin_count 2. That was FALSE: asserted by this session with confidence, +// carried as SEED DEBT 3 "pending pin-verification", and never closed before a witness was built on +// it. The corrected reading: THREE 8-pin ATX12V inputs (ATX12V1/2/3) plus a 4-pin Micro-Fit signal +// connector (ATX4PIN1), NO 24-pin socket, a bundled 24-pin-to-4-pin converter for ATX-supply +// operation, and direct +12 V DC input in which the 4-pin signal path is unused. // // PROVENANCE CORRECTION, 2026-08-11, and it matters more than the numbers. An earlier version of -// this comment and its commit message both said this was "verified against the vendor manual". It -// was not. Every attempt to fetch download.asrock.com, asrockrack.com and a mirror was refused by -// this environment's egress policy, so the manual has never been opened here. The correction came -// from a routed review, which is a real and useful source and is NOT a vendor document. +// this comment and its commit message said this was "verified against the vendor manual". It was +// not: every fetch of download.asrock.com, asrockrack.com and a mirror was refused by this +// environment's egress policy, so the manual has never been opened here. The correction came from a +// routed review — a real source, and NOT a vendor document. // -// Writing "verified against the vendor manual" over a reading that came from a reviewer is the -// SAME defect as the original error, one level up: the first mistake invented a fact, and this one -// invented the fact's provenance. A wrong number gets caught the moment someone opens the manual. -// A correct number wearing a manual's authority is never re-checked at all, because the row says -// the checking already happened. The standing below records what actually happened. +// Writing "verified against the vendor manual" over a reviewer's reading is the SAME defect one +// level up: the first mistake invented a fact, this one invented its provenance. A wrong number +// gets caught when someone opens the manual; a correct number wearing a manual's authority is never +// re-checked, because the row says the checking already happened. The standing below records what +// actually happened. data asrock_altrad8ud_1l2t_physical_profile: BaseboardPhysicalProfile = BaseboardPhysicalProfile { model_label: "ASRock Rack ALTRAD8UD-1L2T", socket_count: 1, @@ -134,11 +133,11 @@ data asrock_altrad8ud_1l2t_physical_profile: BaseboardPhysicalProfile = Baseboar data asrock_altrad8ud_power_interface_note: NonEmptyStr = "Three 8-pin ATX12V inputs plus one 4-pin Micro-Fit signal connector; no 24-pin socket. ATX mode uses the bundled 24-pin-to-4-pin converter. Direct +12 V DC input is also supported and does not use the 4-pin signal path." -// This URI is where the answer LIVES, not a document that has been read. It was previously named +// This URI is where the answer LIVES, not a document that has been read. Previously named // asrock_altrad8ud_manual_authority, which asserted the opposite: an ExternalAuthority reads as a -// citation, and citing an unopened PDF manufactures exactly the confidence the row has not earned. -// Renamed to say what it is — an unread target — so that the day someone fetches it, upgrading the -// standing is a deliberate edit rather than a silent no-op against a line that already claimed it. +// citation, and citing an unopened PDF manufactures confidence the row has not earned. Renamed to +// say what it is — an unread target — so that when someone fetches it, upgrading the standing is a +// deliberate edit rather than a silent no-op against a line that already claimed it. data asrock_altrad8ud_manual_unread_target: ExternalAuthority = ExternalAuthority { uri: Uri { scheme: Https @@ -155,14 +154,14 @@ type PowerInputPopulationStanding // UNRESOLVED and load-bearing. The board offers three 8-pin inputs; how many must be POPULATED for // a given processor and workload is not established by anything read so far. A cable diagram must // not draw one, two or three connected inputs until a vendor statement or a fixture measurement -// closes this. Drawing a plausible number here is exactly how the previous false inventory reached -// a witness. +// closes this — drawing a plausible number is exactly how the previous false inventory reached a +// witness. data asrock_altrad8ud_required_input_population: PowerInputPopulationStanding = PowerInputPopulationUnresolved { available_inputs: 3 } -// The ASRock profile above is authored in THIS module rather than beside the ASRock catalog row on -// purpose and against the usual placement rule: it exists only so the two boards can be compared by -// the same shape in one place. If a second consumer ever needs it, move it to -// extdeps.boards.asrock_rack and leave a reference — do not copy it. +// The ASRock profile above is authored in THIS module rather than beside the ASRock catalog row, +// against the usual placement rule: it exists only so the two boards can be compared by one shape +// in one place. If a second consumer needs it, move it to extdeps.boards.asrock_rack and leave a +// reference — do not copy it. data asrock_profile_placement_note: NonEmptyStr = "Co-located for comparison only; migrate to extdeps.boards.asrock_rack on second consumer." type BoardProcurementStanding @@ -175,16 +174,16 @@ type BoardProcurementStanding // MP72-HB0 DDR4" beside the regulatory marks, and the board shows two LGA4926 sockets with white // retention frames, sixteen DIMM slots in four banks, a 24-pin connector at the upper left, two // 8-pin connectors along the top edge, four PCIe slots, two SlimSAS ports, one M.2 socket, and a -// rear I/O panel carrying VGA, USB, two RJ45 ports and a management port. Those are consistent with -// the vendor profile above on every countable field. +// rear I/O panel carrying VGA, USB, two RJ45 ports and a management port — consistent with the +// vendor profile above on every countable field. // // CORRECTION, recorded because it was routed to this operator as a purchase refusal: a review -// asserted that this listing's photograph shows a proprietary "MT JADE SYSTEM BOARD" silkscreen and -// therefore depicts different inventory. Read directly, the image does not support that — the -// silkscreen says MP72-HB0. The refusal as stated does not hold. +// asserted the photograph shows a proprietary "MT JADE SYSTEM BOARD" silkscreen and therefore +// different inventory. Read directly, the silkscreen says MP72-HB0. The refusal as stated does not +// hold. // -// The real caveat is duller and still binding: the image is a watermarked reseller stock photograph. -// It establishes what the listing DEPICTS, never what any particular unit IS, so it cannot advance +// The real caveat is duller and still binding: the image is a watermarked reseller stock +// photograph. It establishes what the listing DEPICTS, never what any unit IS, so it cannot advance // standing past ListingObserved. Price, condition, revision, firmware, included items, and landed // duty remain unread, and no board may enter a fleet carrier from a stock photograph. data gigabyte_mp72_hb0_procurement_standing: BoardProcurementStanding = ListingObserved @@ -202,9 +201,9 @@ data gigabyte_mp72_hb0_purchase_gate: List = [ ] // Landed cost is UNMODELLED and deliberately so. A routed review offered a 25 percent duty reserve -// drawn from a comparable classification, not from this part's confirmed code, and quoted domestic -// prices near USD 1445 that this session has not verified. Authoring either as a number here would -// launder a hypothesis into the catalog. The gate row above says what must be read first. +// drawn from a comparable classification, not this part's confirmed code, and quoted domestic +// prices near USD 1445 that this session has not verified. Authoring either here would launder a +// hypothesis into the catalog. The gate row above says what must be read first. data gigabyte_mp72_hb0_landed_cost_standing_note: NonEmptyStr = "Landed cost unmodelled: no confirmed tariff classification, no verified quote, no freight or brokerage figure." fn board_profile_power_headers_match(a: BaseboardPhysicalProfile, b: BaseboardPhysicalProfile) -> Bool { diff --git a/dag/extdeps/browser/browser.dag b/dag/extdeps/browser/browser.dag index d0a1d69de4e..b7eae3c9199 100644 --- a/dag/extdeps/browser/browser.dag +++ b/dag/extdeps/browser/browser.dag @@ -3,7 +3,18 @@ module extdeps.browser import extdeps.external_authority { ExternalAuthority } import extdeps.uri { Uri, Https } import std.types { Bool, FilePath, Int, String } -data extdeps_browser_non_precedent_disposition: String = "NON-PRECEDENT — DO NOT COPY THIS MODULE'S SHAPE FOR NEW BROWSER-PRODUCT MODELING (operator verdict on gunbc#7556; DESIGN §3 'External upstream decomposition'). This module conflates three independently governed things into one authority: the Playwright automation interface (its anchor cites playwright.dev while the module is named 'browser'), a generic browser abstraction (BrowserContext/Page/Element as bare brands), and a shell transport realization (every operation hardwired to a playwright-runner argv — the §3 interface/transport fusion). Its eventual split, staged behind the ExternalModelScope enrollment frontier (gunbc.extdeps_scope_frontier): extdeps.automation.playwright owns the Playwright dependency's real cited interface; the agnostic browser interaction shape lives as a shared standard/interface module with no product enumeration; the shell transport becomes a bound realization handler, one of N, beside the SDK/CDP handlers. Until that split lands, new browser-product facts go in one-module-per-subject authorities (extdeps.browser.chromium, extdeps.browser.google_chrome, ...), never here." +// NON-PRECEDENT — DO NOT COPY THIS MODULE'S SHAPE FOR NEW BROWSER-PRODUCT MODELING (operator +// verdict on gunbc#7556; DESIGN §3 'External upstream decomposition'). This module conflates three +// independently governed things into one authority: the Playwright automation interface (its anchor +// cites playwright.dev while the module is named 'browser'), a generic browser abstraction +// (BrowserContext/Page/Element as bare brands), and a shell transport realization (every operation +// hardwired to a playwright-runner argv — the §3 interface/transport fusion). Its eventual split, +// staged behind the ExternalModelScope enrollment frontier (gunbc.extdeps_scope_frontier): +// extdeps.automation.playwright owns the Playwright dependency's real cited interface; the agnostic +// browser interaction shape lives as a shared standard/interface module with no product +// enumeration; the shell transport becomes a bound realization handler, one of N, beside the +// SDK/CDP handlers. Until that split lands, new browser-product facts go in one-module-per-subject +// authorities (extdeps.browser.chromium, extdeps.browser.google_chrome, ...), never here. data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { uri: Uri { diff --git a/dag/extdeps/browser/google_chrome.dag b/dag/extdeps/browser/google_chrome.dag index 5948b0def79..d014a9d2856 100644 --- a/dag/extdeps/browser/google_chrome.dag +++ b/dag/extdeps/browser/google_chrome.dag @@ -44,6 +44,10 @@ fn value_is_frozen_ua_token(v: String) -> Bool { v == frozen_macos_platform_token || v == frozen_macos_os_version_token } -data minor_version_is_zeroed: Bool = true +// A Google Chrome build is not a Chromium build, not Microsoft Edge, not Opera, and not a +// Playwright-driven browser, even where all of them share an engine revision. A platform limitation +// observed in Chrome is attributable to Chrome and to whatever engine revision was separately +// resolved; it is not attributable to the Chromium project unless the engine identity was actually +// read. -data identity_boundary_note: String = "A Google Chrome build is not a Chromium build, not Microsoft Edge, not Opera, and not a Playwright-driven browser, even where all of them share an engine revision. A platform limitation observed in Chrome is attributable to Chrome and to whatever engine revision was separately resolved; it is not attributable to the Chromium project unless the engine identity was actually read." +data minor_version_is_zeroed: Bool = true diff --git a/dag/extdeps/cache/materialization.dag b/dag/extdeps/cache/materialization.dag index 7466d0d9712..254232b1127 100644 --- a/dag/extdeps/cache/materialization.dag +++ b/dag/extdeps/cache/materialization.dag @@ -49,9 +49,31 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data catalog_materialization_note: String = "C0 (operator ruling, 2026-07-09): extdeps stays the ground-truth/interface layer - the catalog cites what a cache system IS - but every USE of a cache goes through materialization, so nobody re-invents memo. This projection is the only door from a catalog row into the ladder: provider = catalog row + role (scope, coverage); tier and keying are DERIVED from the cited facts, never restated at the provider site. RETENTION IS CARRIED, NOT VERDICTED (2026-08-06). The former mapping asserted two things it had not established: InProcess locality => ScopeExit by construction, which DISCARDED the row placement retention outright so an in-process provider could never carry a byte bound; and Ttl/Lru/SizeBounded => SpacePacked, which minted a space guarantee from an age bound and from a replacement ORDER. Both are gone. provider_from_catalog now carries facts.placement.retention through unchanged for EVERY locality, and the boundedness question moved to the role doors provider_for_growth_bounded_role / provider_for_capacity_bounded_role / provider_for_exact_byte_budget_role, whose refusals name the missing axis. That split is deliberate: a witness-frame memo may be scope-released with unbounded capacity and remain legitimate under its frame envelope, while a promoted entry-scoped memo or a cross-run store must show a real ceiling - and only the consumer knows which it is. The EvictionPolicy/EvictionClass homonym the 2026-07-11 ambiguity wall found is dissolved rather than renamed: both vocabularies are now std.cache_interface.ProviderRetention, and required class is the derived predicate retention_growth_is_bounded." +// C0 (operator ruling, 2026-07-09): extdeps stays the ground-truth/interface layer - the catalog +// cites what a cache system IS - but every USE of a cache goes through materialization, so nobody +// re-invents memo. This projection is the only door from a catalog row into the ladder: provider = +// catalog row + role (scope, coverage); tier and keying are DERIVED from the cited facts, never +// restated at the provider site. RETENTION IS CARRIED, NOT VERDICTED (2026-08-06). The former +// mapping asserted two things it had not established: InProcess locality => ScopeExit by +// construction, which DISCARDED the row placement retention outright so an in-process provider +// could never carry a byte bound; and Ttl/Lru/SizeBounded => SpacePacked, which minted a space +// guarantee from an age bound and from a replacement ORDER. Both are gone. provider_from_catalog +// now carries facts.placement.retention through unchanged for EVERY locality, and the boundedness +// question moved to the role doors provider_for_growth_bounded_role / +// provider_for_capacity_bounded_role / provider_for_exact_byte_budget_role, whose refusals name the +// missing axis. That split is deliberate: a witness-frame memo may be scope-released with unbounded +// capacity and remain legitimate under its frame envelope, while a promoted entry-scoped memo or a +// cross-run store must show a real ceiling - and only the consumer knows which it is. The +// EvictionPolicy/EvictionClass homonym the 2026-07-11 ambiguity wall found is dissolved rather than +// renamed: both vocabularies are now std.cache_interface.ProviderRetention, and required class is +// the derived predicate retention_growth_is_bounded. -data catalog_keying_note: String = "Keying projection is refuse-leaning (section 5): ContentAddressed and NativeInternal (the tool's internal hash of cited inputs) project to ContentKeyed; HandAuthored projects to ExistenceKeyed — a hand-authored key string is not proven content-complete until a key-completeness receipt upgrades it (extdeps.cache.key_completeness is that oracle's home); and prefix_fallback_allowed forces ExistenceKeyed regardless, because a prefix restore names a bucket, not an identity (the actions-cache staleness class, cousin of the #6352 build-if-absent wall)." +// Keying projection is refuse-leaning (section 5): ContentAddressed and NativeInternal (the tool's +// internal hash of cited inputs) project to ContentKeyed; HandAuthored projects to ExistenceKeyed — +// a hand-authored key string is not proven content-complete until a key-completeness receipt +// upgrades it (extdeps.cache.key_completeness is that oracle's home); and prefix_fallback_allowed +// forces ExistenceKeyed regardless, because a prefix restore names a bucket, not an identity (the +// actions-cache staleness class, cousin of the #6352 build-if-absent wall). // Refusal reasons name the MISSING AXIS, because the remedy differs: an // unobserved capacity needs a citation, a known-unbounded one needs a declared diff --git a/dag/extdeps/cache/pin.dag b/dag/extdeps/cache/pin.dag index 26c156e4d62..eea102feb05 100644 --- a/dag/extdeps/cache/pin.dag +++ b/dag/extdeps/cache/pin.dag @@ -26,7 +26,21 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { uri: Uri { scheme: Https, locator: "github.com/mozilla/sccache/releases" } } -data sccache_pin_instantiation_note: String = "THE INSTANTIATION at dag/extdeps/cache/pin.dag for extdeps.cache.sccache multi-artifact release rows. extdeps.pin owns Pin or SubjectProjectedPin when the subject fully determines cited digest identity (review 44999). SccacheBinaryArtifact carries cited extdeps.crypto.hash.Digest for its arch; sccache_binary_artifact_content_identity converts via extdeps.crypto.hash.sha256_digest_content_hash into std.types.ContentHash — rendering, not a second digest concept (deep-eagle-728 oracle msg_fac61885). Identity is projected at admission and bridge; never stored twice on SubjectProjectedPin. Version is projected from subject.binary_release.version at the reconcile bridge (Pin.version required on Pin, msg_fac61885). Pin remains unsound at release grain. extdeps_external_authority_anchor spells the same releases URI as sccache_releases_authority in extdeps.cache.sccache — one upstream fact; literal ExternalAuthority body is mandatory-tag enrollment machinery (corpus_live_clean_tree_wall_holds reads the value subtree structurally; datum alias reds as missing — review 44866). key_of projects published musl arch only — release version is content carried in value_eq, not reconcile identity (review 45019, pin_value_eq_note identity-vs-content split)." +// THE INSTANTIATION at dag/extdeps/cache/pin.dag for extdeps.cache.sccache multi-artifact release +// rows. extdeps.pin owns Pin or SubjectProjectedPin when the subject fully +// determines cited digest identity (review 44999). SccacheBinaryArtifact carries cited +// extdeps.crypto.hash.Digest for its arch; sccache_binary_artifact_content_identity converts via +// extdeps.crypto.hash.sha256_digest_content_hash into std.types.ContentHash — rendering, not a +// second digest concept (deep-eagle-728 oracle msg_fac61885). Identity is projected at admission +// and bridge; never stored twice on SubjectProjectedPin. Version is projected from +// subject.binary_release.version at the reconcile bridge (Pin.version required on Pin, +// msg_fac61885). Pin remains unsound at release grain. +// extdeps_external_authority_anchor spells the same releases URI as sccache_releases_authority in +// extdeps.cache.sccache — one upstream fact; literal ExternalAuthority body is mandatory-tag +// enrollment machinery (corpus_live_clean_tree_wall_holds reads the value subtree structurally; +// datum alias reds as missing — review 44866). key_of projects published musl arch only — release +// version is content carried in value_eq, not reconcile identity (review 45019, pin_value_eq_note +// identity-vs-content split). fn sccache_binary_artifact_pin_version(artifact: SccacheBinaryArtifact) -> VersionIdentity { artifact.binary_release.version as VersionIdentity diff --git a/dag/extdeps/cache/sccache.dag b/dag/extdeps/cache/sccache.dag index 73f1fdfba37..26df8e63598 100644 --- a/dag/extdeps/cache/sccache.dag +++ b/dag/extdeps/cache/sccache.dag @@ -77,7 +77,20 @@ type SccacheBinaryRelease { digest_x86_64: Digest } -data sccache_binary_artifact_grain_note: String = "For multi-artifact releases the pin SUBJECT is the ARTIFACT (release x platform), not the release row alone (review 44662, feature:pin-artifact-grain). SccacheBinaryRelease carries two cited digests because one GitHub release publishes two musl tarballs; Pin.expected_identity is one ContentHash, so Pin was ambiguous about which tarball it pinned. SccacheBinaryArtifact is the release x published-musl-arch product: one cited digest per row, projected from the release's named digest_aarch64 / digest_x86_64 fields — never re-minted. SccachePublishedMuslArch is a CLOSED two-arm coproduct (SccacheMuslAarch64 | SccacheMuslX86_64) so unsupported extdeps.toolchain.types.Architecture variants are UNWRITABLE on the carrier rather than admitted and refused later (review 44866, DESIGN section 5 construction). Distinct variant names avoid the typechecker fork where reusing Aarch64 | X86_64 as a separate coproduct collided with Architecture; sccache_published_musl_arch_of is the Architecture→SccachePublishedMuslArch projection (Present only for published musl arches) used by sccache_binary_artifact_for_arch and sccache_release_digest consumers. CpuArchitecture and TargetArchitecture in std are byte-identical forks (DESIGN section 3) and are deliberately NOT imported here." +// For multi-artifact releases the pin SUBJECT is the ARTIFACT (release x platform), not the release +// row alone (review 44662, feature:pin-artifact-grain). SccacheBinaryRelease carries two cited +// digests because one GitHub release publishes two musl tarballs; Pin.expected_identity is one +// ContentHash, so Pin was ambiguous about which tarball it pinned. +// SccacheBinaryArtifact is the release x published-musl-arch product: one cited digest per row, +// projected from the release's named digest_aarch64 / digest_x86_64 fields — never re-minted. +// SccachePublishedMuslArch is a CLOSED two-arm coproduct (SccacheMuslAarch64 | SccacheMuslX86_64) +// so unsupported extdeps.toolchain.types.Architecture variants are UNWRITABLE on the carrier rather +// than admitted and refused later (review 44866, DESIGN section 5 construction). Distinct variant +// names avoid the typechecker fork where reusing Aarch64 | X86_64 as a separate coproduct collided +// with Architecture; sccache_published_musl_arch_of is the Architecture→SccachePublishedMuslArch +// projection (Present only for published musl arches) used by sccache_binary_artifact_for_arch and +// sccache_release_digest consumers. CpuArchitecture and TargetArchitecture in std are +// byte-identical forks (DESIGN section 3) and are deliberately NOT imported here. type SccachePublishedMuslArch = SccacheMuslAarch64 | SccacheMuslX86_64 @@ -167,7 +180,14 @@ data sccache_install_dissolution_trigger: DissolutionCondition = retires_dissolu ref: decl_ref(module_path: "extdeps.cache.sccache", decl_name: "sccache_install_script") ) -data sccache_installed_binary_path_note: String = "Single authority for WHERE the cited release lands on a host. sccache_install_script writes it and every downstream consumer (the host-standup P1b systemd unit ExecStart, the PATH-shadow read-back check) reads it — so 'which sccache is the modeled one' has exactly one answer. Before this row the P1b provisioning path hand-rolled `apt-get install -y sccache` and a unit with ExecStart=/usr/bin/sccache while this module installed the pinned v0.15.0 tarball to /usr/local/bin: a §3 fork of one concept across two authorities, disagreeing on source, version, digest verification AND path, both firing on the same host (P1:runner-deploy-slot then P1b:build-cache-provision)." +// Single authority for WHERE the cited release lands on a host. sccache_install_script writes it +// and every downstream consumer (the host-standup P1b systemd unit ExecStart, the PATH-shadow +// read-back check) reads it — so 'which sccache is the modeled one' has exactly one answer. Before +// this row the P1b provisioning path hand-rolled `apt-get install -y sccache` and a unit with +// ExecStart=/usr/bin/sccache while this module installed the pinned v0.15.0 tarball to +// /usr/local/bin: a §3 fork of one concept across two authorities, disagreeing on source, version, +// digest verification AND path, both firing on the same host (P1:runner-deploy-slot then +// P1b:build-cache-provision). data sccache_installed_binary_path: String = "/usr/local/bin/sccache" @@ -192,7 +212,11 @@ fn sccache_install_script(rel: SccacheBinaryRelease, arch: NonEmptyStr, digest: ], "\n") } -data sccache_release_arch_selection_note: String = "The release row carries per-arch digests as two named fields, so selecting one by hand is how a caller pins an arch (runner_host_sccache_provision_aarch64). These two selectors make the choice a total function of the modeled Architecture coproduct instead: every arch the cited release does NOT publish a musl tarball for returns Absent, so an unsupported host REFUSES rather than silently receiving an aarch64 binary. Fail-closed by construction (§5) — there is no default arm." +// The release row carries per-arch digests as two named fields, so selecting one by hand is how a +// caller pins an arch (runner_host_sccache_provision_aarch64). These two selectors make the choice +// a total function of the modeled Architecture coproduct instead: every arch the cited release does +// NOT publish a musl tarball for returns Absent, so an unsupported host REFUSES rather than +// silently receiving an aarch64 binary. Fail-closed by construction (§5) — there is no default arm. fn sccache_release_arch_token(arch: Architecture) -> NonEmptyStr? { match arch { @@ -226,9 +250,26 @@ fn sccache_release_digest(rel: SccacheBinaryRelease, arch: Architecture) -> Dige } } -data sccache_server_process_model_doc: String = "sccache is a client/server tool: the client auto-spawns a server on first use if none is listening, the server survives the client that spawned it, and CACHE MISSES ARE COMPILED BY THE SERVER — rustc runs as a CHILD OF THE SERVER PROCESS, not of the invoking client. Consequence (load-bearing for host budgeting): the server's cgroup IS the cgroup every cache-miss compile on the host executes in, so a server that was lazily spawned inside some job's cgroup makes that job's memory caps bound EVERY subsequent compile on the host, including compiles issued by other jobs. SCCACHE_IDLE_TIMEOUT=0 disables the idle shutdown, so such a mis-placed server survives indefinitely." - -data sccache_server_endpoint_doc: String = "The SERVER is identified positively as the process HOLDING THE LISTEN on the configured endpoint — never by process name. The sccache binary is both client and server, so under RUSTC_WRAPPER every in-flight compile also runs a process named 'sccache'; selecting by name samples a transient client and reads the WRONG cgroup. Upstream exposes two mutually exclusive endpoint realizations: SCCACHE_SERVER_PORT (loopback TCP, default 4226) and SCCACHE_SERVER_UDS (a Unix-domain socket path). Modeling only the port would be a nickname for one realization (DESIGN section 3): the gunbc fleet configures the UDS form, so a port-only probe finds no listener at all. Observed live on srv1 2026-08-01: no TCP 4226 listener exists, while /var/lib/ctrl/sccache-ci/server.sock is held LISTEN by the CI server and /var/lib/ctrl/sccache/server.sock by the session server — endpoint ownership is what distinguished them." +// sccache is a client/server tool: the client auto-spawns a server on first use if none is +// listening, the server survives the client that spawned it, and CACHE MISSES ARE COMPILED BY THE +// SERVER — rustc runs as a CHILD OF THE SERVER PROCESS, not of the invoking client. Consequence +// (load-bearing for host budgeting): the server's cgroup IS the cgroup every cache-miss compile on +// the host executes in, so a server that was lazily spawned inside some job's cgroup makes that +// job's memory caps bound EVERY subsequent compile on the host, including compiles issued by other +// jobs. SCCACHE_IDLE_TIMEOUT=0 disables the idle shutdown, so such a mis-placed server survives +// indefinitely. + +// The SERVER is identified positively as the process HOLDING THE LISTEN on the configured endpoint +// — never by process name. The sccache binary is both client and server, so under RUSTC_WRAPPER +// every in-flight compile also runs a process named 'sccache'; selecting by name samples a +// transient client and reads the WRONG cgroup. Upstream exposes two mutually exclusive endpoint +// realizations: SCCACHE_SERVER_PORT (loopback TCP, default 4226) and SCCACHE_SERVER_UDS (a +// Unix-domain socket path). Modeling only the port would be a nickname for one realization (DESIGN +// section 3): the gunbc fleet configures the UDS form, so a port-only probe finds no listener at +// all. Observed live on srv1 2026-08-01: no TCP 4226 listener exists, while +// /var/lib/ctrl/sccache-ci/server.sock is held LISTEN by the CI server and +// /var/lib/ctrl/sccache/server.sock by the session server — endpoint ownership is what +// distinguished them. data sccache_server_default_tcp_port: Int = 4226 @@ -302,7 +343,12 @@ data sccache_rustc_wrapper_exit_254_log_fragment: String = "(exit status: 254)" data sccache_failed_to_execute_compile_log_fragment: String = "sccache: error: failed to execute compile" -data sccache_misconfigured_log_fragments_note: String = "The cited roster of stdout/stderr fragments that mean 'the daemon answered, but it is misconfigured' — as a LIST, so a consumer classifying an observation folds over it. The three rows above were previously read one-by-one by a hand-unrolled if/elif chain in the host-standup provisioning emitter, which meant citing a fourth fragment here required an edit there too (the emitter naming a concept it does not own). Consumers iterate this roster; adding a row costs zero emitter edits." +// The cited roster of stdout/stderr fragments that mean 'the daemon answered, but it is +// misconfigured' — as a LIST, so a consumer classifying an observation folds over it. The three +// rows above were previously read one-by-one by a hand-unrolled if/elif chain in the host-standup +// provisioning emitter, which meant citing a fourth fragment here required an edit there too (the +// emitter naming a concept it does not own). Consumers iterate this roster; adding a row costs zero +// emitter edits. data sccache_misconfigured_log_fragments: List = [ sccache_fatal_error_log_fragment, @@ -310,7 +356,31 @@ data sccache_misconfigured_log_fragments: List = [ sccache_failed_to_execute_compile_log_fragment, ] -data sccache_server_lifetime_note: String = "sccache's server EXITS ON ITS OWN. Three environment knobs, all documented by the module's cited authority extdeps_external_authority_anchor (github.com/mozilla/sccache/blob/main/docs/Configuration.md), decide the process lifetime a supervisor sees, and they are modeled here because they are facts about SCCACHE — not about systemd, and not about gunbc. Before 2026-07-25 the P1b unit emitter spelled SCCACHE_START_SERVER=1 / SCCACHE_NO_DAEMON=1 as bare literals of its own, which made gunbc.host_build_cache_provision_script a SECOND authority for sccache's environment vocabulary (§3) — and the knob that was missing from that second authority is exactly the one that broke: SCCACHE_IDLE_TIMEOUT. Cited text: 'how long the local daemon process waits for more client requests before exiting, in seconds. Set to 0 to run sccache permanently.' The default is 600 seconds, confirmed by execution rather than read off the doc: on srv3 2026-07-25 the foreground-supervised unit started at 06:28:47 UTC, served no compile request, and EXITED 0 after `Duration: 10min 8.215s`, leaving `inactive (dead)` — Restart=on-failure could not fire because the exit was a success. Consequence for any supervisor: a Type=simple unit whose ExecStart is sccache is only durable when the idle timeout is disabled; otherwise it dies on the first quiet ten minutes and the host reverts to sccache's own unsupervised per-invocation auto-start (the very no-op state the foreground form was introduced to remove). Whether the srv1/srv2 production ctrl-sccache.service sets this knob is UNVERIFIED — neither host is reachable from a session container today, and the only in-tree description of that unit (#7206's note) lists SCCACHE_START_SERVER and SCCACHE_NO_DAEMON and no idle-timeout line, which is an absence in a summary, not evidence of absence in the file. What its month-plus uptime does NOT establish is that the unit is durable: those hosts run CI continuously, so traffic keeps the daemon below the idle threshold whether or not the timer is disabled — the uptime is consistent with both. This row therefore stands on the cited doc plus the srv3 receipt as a DEDUCTION; it is explicitly NOT claimed as a swap toward a production form nobody has read." +// sccache's server EXITS ON ITS OWN. Three environment knobs, all documented by the module's cited +// authority extdeps_external_authority_anchor +// (github.com/mozilla/sccache/blob/main/docs/Configuration.md), decide the process lifetime a +// supervisor sees, and they are modeled here because they are facts about SCCACHE — not about +// systemd, and not about gunbc. Before 2026-07-25 the P1b unit emitter spelled +// SCCACHE_START_SERVER=1 / SCCACHE_NO_DAEMON=1 as bare literals of its own, which made +// gunbc.host_build_cache_provision_script a SECOND authority for sccache's environment vocabulary +// (§3) — and the knob that was missing from that second authority is exactly the one that broke: +// SCCACHE_IDLE_TIMEOUT. Cited text: 'how long the local daemon process waits for more client +// requests before exiting, in seconds. Set to 0 to run sccache permanently.' The default is 600 +// seconds, confirmed by execution rather than read off the doc: on srv3 2026-07-25 the +// foreground-supervised unit started at 06:28:47 UTC, served no compile request, and EXITED 0 after +// `Duration: 10min 8.215s`, leaving `inactive (dead)` — Restart=on-failure could not fire because +// the exit was a success. Consequence for any supervisor: a Type=simple unit whose ExecStart is +// sccache is only durable when the idle timeout is disabled; otherwise it dies on the first quiet +// ten minutes and the host reverts to sccache's own unsupervised per-invocation auto-start (the +// very no-op state the foreground form was introduced to remove). Whether the srv1/srv2 production +// ctrl-sccache.service sets this knob is UNVERIFIED — neither host is reachable from a session +// container today, and the only in-tree description of that unit (#7206's note) lists +// SCCACHE_START_SERVER and SCCACHE_NO_DAEMON and no idle-timeout line, which is an absence in a +// summary, not evidence of absence in the file. What its month-plus uptime does NOT establish is +// that the unit is durable: those hosts run CI continuously, so traffic keeps the daemon below the +// idle threshold whether or not the timer is disabled — the uptime is consistent with both. This +// row therefore stands on the cited doc plus the srv3 receipt as a DEDUCTION; it is explicitly NOT +// claimed as a swap toward a production form nobody has read. data sccache_start_server_env_var: String = "SCCACHE_START_SERVER" @@ -318,7 +388,11 @@ data sccache_no_daemon_env_var: String = "SCCACHE_NO_DAEMON" data sccache_idle_timeout_env_var: String = "SCCACHE_IDLE_TIMEOUT" -data sccache_idle_timeout_never_value_note: String = "The value whose MEANING is 'never exit', which is why it is a named row rather than a literal inside the assignment: '0' is not an ordinary duration on this axis (a zero-second idle timeout would mean the opposite), it is the cited doc's sentinel for running permanently. A future consumer that wants a bounded lifetime supplies its own seconds value; a consumer that wants a supervised daemon reads THIS row." +// The value whose MEANING is 'never exit', which is why it is a named row rather than a literal +// inside the assignment: '0' is not an ordinary duration on this axis (a zero-second idle timeout +// would mean the opposite), it is the cited doc's sentinel for running permanently. A future +// consumer that wants a bounded lifetime supplies its own seconds value; a consumer that wants a +// supervised daemon reads THIS row. data sccache_idle_timeout_never_value: String = "0" diff --git a/dag/extdeps/clock/ti_cdce913.dag b/dag/extdeps/clock/ti_cdce913.dag index ff6342d910c..6470b895b2e 100644 --- a/dag/extdeps/clock/ti_cdce913.dag +++ b/dag/extdeps/clock/ti_cdce913.dag @@ -24,7 +24,16 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data ti_cdce913_note: String = "THIS ROW IS A MECHANICAL CANDIDATE AND NOT A SELECTED PART. Nothing here says the device suits any board role; a selection is a functional-compatibility join done at the product layer, and this row supplies only the package authority that join consumes. MECHANICAL FACTS ONLY, FROM ONE DOCUMENT. The CDCE913 is a programmable clock generator and none of that is modelled here — no output count, no frequency range, no PLL fact. The package body dimensions and the vendor's recommended land pattern are what the drawing states and what were read. The document also carries the sibling part CDCEL913 on the same pages; the ordering part number recorded here is the one whose package this drawing describes, and a reader wanting the low-voltage sibling adds a row rather than editing this one, because they are separately ordered parts even where they share a drawing." +// THIS ROW IS A MECHANICAL CANDIDATE AND NOT A SELECTED PART. Nothing here says the device suits +// any board role; a selection is a functional-compatibility join done at the product layer, and +// this row supplies only the package authority that join consumes. MECHANICAL FACTS ONLY, FROM ONE +// DOCUMENT. The CDCE913 is a programmable clock generator and none of that is modelled here — no +// output count, no frequency range, no PLL fact. The package body dimensions and the vendor's +// recommended land pattern are what the drawing states and what were read. The document also +// carries the sibling part CDCEL913 on the same pages; the ordering part number recorded here is +// the one whose package this drawing describes, and a reader wanting the low-voltage sibling adds a +// row rather than editing this one, because they are separately ordered parts even where they share +// a drawing. data cdce913_pw0014a_drawing: PackageDrawingRef = PackageDrawingRef { drawing_number: "4220202/B", diff --git a/dag/extdeps/cloud/ubicloud.dag b/dag/extdeps/cloud/ubicloud.dag index c9d733450a8..ba9e7e427a8 100644 --- a/dag/extdeps/cloud/ubicloud.dag +++ b/dag/extdeps/cloud/ubicloud.dag @@ -1,6 +1,5 @@ module extdeps.cloud.ubicloud - import extdeps.external_authority { ExternalAuthority } import extdeps.uri { Uri, Https } import extdeps.toolchain.types { Architecture } @@ -39,9 +38,25 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { // than this one's. data ubicloud_runner_types_authority: ExternalAuthority = extdeps_external_authority_anchor -data ubicloud_runner_catalog_citation_note: String = "Cited 2026-07-11 from the runner-types page (the anchor above). Vendor prose kept as facts, not re-derived: x64 runners run on AMD Ryzen 9 7950X3D with every two vCPUs mapped to one physical core; arm64 runners run on Ampere Altra Q80-30 with each vCPU backed by a DEDICATED physical core (contrast: the gunbc fleet is Ampere Altra Max M128-30 — gunbc.fleet_intent fleet_intent_cpu cites altra_max_m12830_catalog, and product.cooling_qualification's installed rows and the machine-shape catalog integration agree; an earlier revision of this sentence said Q64-30, which named a row this fleet does not cite. That contrast is a modeled intent, not a per-host reading: gunbc.fleet_physical_inventory fleet_processor_verdicts stands at ProcessorModelUnconfirmed on all four hosts until a per-host processor observation lands). Memory is the vendor's published figure per label - the vendor writes decimal-suffixed strings ('48GB'); memory_bytes records that figure read as decimal GB. The documented memory-per-vCPU ratio is 4GB on x64 and 3GB on arm64. A Q80-30 catalog row in extdeps/cpu/ampere is the grounding successor for the arm rows' cpu axis; until it lands the architecture field carries the load." +// Cited 2026-07-11 from the runner-types page (the anchor above). Vendor prose kept as facts, not +// re-derived: x64 runners run on AMD Ryzen 9 7950X3D with every two vCPUs mapped to one physical +// core; arm64 runners run on Ampere Altra Q80-30 with each vCPU backed by a DEDICATED physical core +// (contrast: the gunbc fleet is Ampere Altra Max M128-30 — gunbc.fleet_intent fleet_intent_cpu +// cites altra_max_m12830_catalog, and product.cooling_qualification's installed rows and the +// machine-shape catalog integration agree; an earlier revision of this sentence said Q64-30, which +// named a row this fleet does not cite. That contrast is a modeled intent, not a per-host reading: +// gunbc.fleet_physical_inventory fleet_processor_verdicts stands at ProcessorModelUnconfirmed on +// all four hosts until a per-host processor observation lands). Memory is the vendor's published +// figure per label - the vendor writes decimal-suffixed strings ('48GB'); memory_bytes records that +// figure read as decimal GB. The documented memory-per-vCPU ratio is 4GB on x64 and 3GB on arm64. A +// Q80-30 catalog row in extdeps/cpu/ampere is the grounding successor for the arm rows' cpu axis; +// until it lands the architecture field carries the load. -data ubicloud_standard_16_arm_observed_memtotal_note: String = "Observed receipt, distinct from the catalog claim and NOT reconciled into it: a live ubicloud-standard-16-arm VM (gunbc run 29162064482, 2026-07-11) reported MemTotal host_ram=53570453504 bytes (~49.9GiB) with the runner cgroup memory.max=uncapped(RAM-bound) - the whole machine, no slot carve. That is ABOVE the catalog's 48GB decimal reading; the catalog figure and the kernel's MemTotal are different measurement frames and both are kept as-is." +// Observed receipt, distinct from the catalog claim and NOT reconciled into it: a live +// ubicloud-standard-16-arm VM (gunbc run 29162064482, 2026-07-11) reported MemTotal +// host_ram=53570453504 bytes (~49.9GiB) with the runner cgroup memory.max=uncapped(RAM-bound) - the +// whole machine, no slot carve. That is ABOVE the catalog's 48GB decimal reading; the catalog +// figure and the kernel's MemTotal are different measurement frames and both are kept as-is. type UbicloudRunnerType = Standard2 diff --git a/dag/extdeps/cloud_init/cloud_init.dag b/dag/extdeps/cloud_init/cloud_init.dag index 2c9b4987da3..c9370fd5405 100644 --- a/dag/extdeps/cloud_init/cloud_init.dag +++ b/dag/extdeps/cloud_init/cloud_init.dag @@ -21,7 +21,18 @@ type CloudInitDisposition | CloudInitDisabledByMarkerFile { path: NonEmptyStr, reason: NonEmptyStr } | CloudInitDisabledByKernelCmdline -data ubuntu_live_installer_disable_note: String = "THE quirk, cited to the marker file's own contents as observed on srv4 2026-07-25: after a desktop/server ISO install, the Ubuntu live installer writes /etc/cloud/cloud-init.disabled containing verbatim 'Disabled by Ubuntu live installer after first boot.' followed by the re-enable instruction 'sudo cloud-init clean --machine-id'. This is STOCK, EXPECTED BEHAVIOUR on every ISO-installed Ubuntu host — it is not a fault, not a misconfiguration, and not something the fleet did. Recording it matters because its absence from the model cost real diagnostic time: on a host that had lost networking, finding cloud-init disabled reads like a smoking gun to anyone who does not know the installer always does this, and the wrong causal story (generator off, therefore config never applied) survives exactly as long as nobody checks who actually applies the config. Every ISO-installed fleet host carries this marker; a host WITHOUT it was installed by a different path and that difference is the interesting signal, not the marker itself." +// THE quirk, cited to the marker file's own contents as observed on srv4 2026-07-25: after a +// desktop/server ISO install, the Ubuntu live installer writes /etc/cloud/cloud-init.disabled +// containing verbatim 'Disabled by Ubuntu live installer after first boot.' followed by the +// re-enable instruction 'sudo cloud-init clean --machine-id'. This is STOCK, EXPECTED BEHAVIOUR on +// every ISO-installed Ubuntu host — it is not a fault, not a misconfiguration, and not something +// the fleet did. Recording it matters because its absence from the model cost real diagnostic time: +// on a host that had lost networking, finding cloud-init disabled reads like a smoking gun to +// anyone who does not know the installer always does this, and the wrong causal story (generator +// off, therefore config never applied) survives exactly as long as nobody checks who actually +// applies the config. Every ISO-installed fleet host carries this marker; a host WITHOUT it was +// installed by a different path and that difference is the interesting signal, not the marker +// itself. data ubuntu_live_installer_disable_marker_text: NonEmptyStr = "Disabled by Ubuntu live installer after first boot." @@ -30,7 +41,17 @@ data cloud_init_installer_config_paths: List = [ "/etc/cloud/cloud.cfg.d/99-installer.cfg", ] -data cloud_init_is_a_writer_not_an_applier_note: String = "THE fact this module exists to make unfusable. cloud-init GENERATES /etc/netplan/50-cloud-init.yaml; it does not APPLY it. Applying is netplan's job (extdeps.netplan.netplan), performed at every boot by the renderer, and the renderer neither knows nor cares which process wrote the file or whether that process still runs. So disabling cloud-init freezes the file — no future regeneration, and hand edits become authoritative because nothing will overwrite them — and changes NOTHING about whether the network comes up. The tempting inference, 'the generator is disabled, therefore its output is not in effect', is FALSE, and it is false in the direction that produces a confident wrong diagnosis rather than a visible error. It was drawn live on srv4 (2026-07-25) against a host whose netplan was present, correct, and applied, and whose actual fault was a dead physical link. gunbc.host_network_diagnosis carries that inference as an executable RED control so it cannot be drawn again unnoticed." +// THE fact this module exists to make unfusable. cloud-init GENERATES +// /etc/netplan/50-cloud-init.yaml; it does not APPLY it. Applying is netplan's job +// (extdeps.netplan.netplan), performed at every boot by the renderer, and the renderer neither +// knows nor cares which process wrote the file or whether that process still runs. So disabling +// cloud-init freezes the file — no future regeneration, and hand edits become authoritative because +// nothing will overwrite them — and changes NOTHING about whether the network comes up. The +// tempting inference, 'the generator is disabled, therefore its output is not in effect', is FALSE, +// and it is false in the direction that produces a confident wrong diagnosis rather than a visible +// error. It was drawn live on srv4 (2026-07-25) against a host whose netplan was present, correct, +// and applied, and whose actual fault was a dead physical link. gunbc.host_network_diagnosis +// carries that inference as an executable RED control so it cannot be drawn again unnoticed. fn cloud_init_is_disabled(d: CloudInitDisposition) -> Bool { match d { @@ -46,6 +67,15 @@ type CloudInitGeneratedFileState = RegeneratedEachBoot | FrozenAtLastRun { because: CloudInitDisposition } +// A frozen generated file is an AUTHORITY TRANSFER, not merely a stale artifact, and naming it that +// way is what makes it safe to edit. While cloud-init runs, /etc/netplan/50-cloud-init.yaml is +// derived and any hand edit is lost at the next boot. Once cloud-init is disabled, the same path is +// hand-authoritative — the generator that owned it will never run again — which is precisely the +// SelfEmitted-versus-SeedRetained distinction the self-host frontier draws for emitted sources, and +// the same question gunbc.generated_artifact answers for this repo's own generated files. The state +// must be derived from the disposition rather than assumed per-file, because the same path means +// opposite things on two hosts that differ only in whether an installer ran. + fn cloud_init_generated_file_state(d: CloudInitDisposition) -> CloudInitGeneratedFileState { if cloud_init_is_disabled(d: d) { FrozenAtLastRun { because: d } @@ -53,5 +83,3 @@ fn cloud_init_generated_file_state(d: CloudInitDisposition) -> CloudInitGenerate RegeneratedEachBoot } } - -data cloud_init_frozen_authority_note: String = "A frozen generated file is an AUTHORITY TRANSFER, not merely a stale artifact, and naming it that way is what makes it safe to edit. While cloud-init runs, /etc/netplan/50-cloud-init.yaml is derived and any hand edit is lost at the next boot. Once cloud-init is disabled, the same path is hand-authoritative — the generator that owned it will never run again — which is precisely the SelfEmitted-versus-SeedRetained distinction the self-host frontier draws for emitted sources, and the same question gunbc.generated_artifact answers for this repo's own generated files. The state must be derived from the disposition rather than assumed per-file, because the same path means opposite things on two hosts that differ only in whether an installer ran." diff --git a/dag/extdeps/color/okabe_ito.dag b/dag/extdeps/color/okabe_ito.dag index c68ec088cd5..d3aa6e30426 100644 --- a/dag/extdeps/color/okabe_ito.dag +++ b/dag/extdeps/color/okabe_ito.dag @@ -16,7 +16,11 @@ fn okabe_ito_authority() -> ExternalAuthority { extdeps_external_authority_anchor } -data okabe_ito_note: String = "Cited upstream: Okabe, M. & Ito, K. (2008) Color Universal Design (CUD) — eight-color CVD-safe qualitative palette. sRGB values verified against the published table at jfly.uni-koeln.de/color/ (retrieved 2026-07-21). Upstream CUD CLAIM: eight colors mutually distinguishable under simulated CVD — NOT witness-pinned here; witness pins swatch count and filed hex values only; CVD distinguishability witness lands with a simulation kernel." +// Cited upstream: Okabe, M. & Ito, K. (2008) Color Universal Design (CUD) — eight-color CVD-safe +// qualitative palette. sRGB values verified against the published table at jfly.uni-koeln.de/color/ +// (retrieved 2026-07-21). Upstream CUD CLAIM: eight colors mutually distinguishable under simulated +// CVD — NOT witness-pinned here; witness pins swatch count and filed hex values only; CVD +// distinguishability witness lands with a simulation kernel. data okabe_ito_version: String = "2008" diff --git a/dag/extdeps/color/solarized.dag b/dag/extdeps/color/solarized.dag index a14b9bf8552..7d4eeb5cc84 100644 --- a/dag/extdeps/color/solarized.dag +++ b/dag/extdeps/color/solarized.dag @@ -16,7 +16,11 @@ fn solarized_authority() -> ExternalAuthority { extdeps_external_authority_anchor } -data solarized_note: String = "Cited upstream: Ethan Schoonover Solarized v1.0.0beta2 (ethanschoonover.com/solarized/, retrieved 2026-07-21). Designed in CIELAB; sRGB hex values filed as Srgb8. Structure rows: base luminance ladder (base03 darkest .. base3 lightest) and dark/light BASE mode reassignment involution (base03<->base3, base02<->base2, base01<->base1, base00<->base0 — the pairing Schoonover documents for syntax/background roles). Canonical L*a*b rows land with the OKLCH reground lane." +// Cited upstream: Ethan Schoonover Solarized v1.0.0beta2 (ethanschoonover.com/solarized/, retrieved +// 2026-07-21). Designed in CIELAB; sRGB hex values filed as Srgb8. Structure rows: base luminance +// ladder (base03 darkest .. base3 lightest) and dark/light BASE mode reassignment involution +// (base03<->base3, base02<->base2, base01<->base1, base00<->base0 — the pairing Schoonover +// documents for syntax/background roles). Canonical L*a*b rows land with the OKLCH reground lane. data solarized_version: String = "1.0.0beta2" diff --git a/dag/extdeps/color/srgb.dag b/dag/extdeps/color/srgb.dag index 2b3ede083e1..7b958cf9ae0 100644 --- a/dag/extdeps/color/srgb.dag +++ b/dag/extdeps/color/srgb.dag @@ -19,9 +19,27 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data color_srgb_note: String = "Cited upstream: W3C CSS Color Module Level 4 (retrieved 2026-07-21) — the sRGB color space (whose normative definition CSS4 cites to IEC 61966-2-1), 8-bit component notation, hex serialization (#rrggbb, section 'The RGB hexadecimal notations'), and the HSL<->sRGB conversion algorithm (section 'HSL Colors'). This module owns COLOR-SPACE facts only: what a color value IS and how representations convert. Perceptual LAW over colors (relative luminance, contrast thresholds) is extdeps.accessibility.wcag_contrast, which imports this module — accessibility consumes the color space, it does not own it (DESIGN sect 3: the conversion previously lived inside wcag_contrast; re-homed 2026-07-21). Srgb8 is the authored identity for catalogued colors — a hex literal is a SERIALIZATION of Srgb8, never the stored fact (decompress the string, DESIGN sect 2). RgbScaled is the permyriad-scale integer representation (one unit = 0.0001 of a channel) shared by the integer kernels; conversions here are exact at the representation's precision, no floats, no host math. OKLCH coordinates are the named next representation (design-register-library.md sect 10 Class 2, phase A.2): the cube-root kernel follows the same integer-Newton pattern wcag_contrast proves for the 2.4 exponent." - -data color_census_frontier: String = "Staged leaf-census items dissolved in Phase-A arc (msg_e9cfe443): (1-4) srgb_byte_denominator / permyriad / hue_milli / percent-round — grounded on std.integer.uint8_channel_inclusive_max_value_derived (from bits_per_byte), std.measure.basis_point_unity_count (extdeps.units.dimensionless), hue_angle facts below; (5) Achromatic HslProjection variant per CSS4 hue-is-none; (14) hex_digit deduped onto extdeps.numeric.base16. Remaining: OKLCH reground design-register-library.md sect 10 Class 2 (explicit trigger, excluded from this PR)." +// Cited upstream: W3C CSS Color Module Level 4 (retrieved 2026-07-21) — the sRGB color space (whose +// normative definition CSS4 cites to IEC 61966-2-1), 8-bit component notation, hex serialization +// (#rrggbb, section 'The RGB hexadecimal notations'), and the HSL<->sRGB conversion algorithm +// (section 'HSL Colors'). This module owns COLOR-SPACE facts only: what a color value IS and how +// representations convert. Perceptual LAW over colors (relative luminance, contrast thresholds) is +// extdeps.accessibility.wcag_contrast, which imports this module — accessibility consumes the color +// space, it does not own it (DESIGN sect 3: the conversion previously lived inside wcag_contrast; +// re-homed 2026-07-21). Srgb8 is the authored identity for catalogued colors — a hex literal is a +// SERIALIZATION of Srgb8, never the stored fact (DESIGN sect 2). RgbScaled is the permyriad-scale +// integer representation (one unit = 0.0001 of a channel) shared by the integer kernels; +// conversions are exact at that precision, no floats, no host math. OKLCH coordinates are the +// named next representation (design-register-library.md sect 10 Class 2, phase A.2): the cube-root +// kernel follows the integer-Newton pattern wcag_contrast proves for the 2.4 exponent. + +// Staged leaf-census items dissolved in Phase-A arc (msg_e9cfe443): (1-4) srgb_byte_denominator / +// permyriad / hue_milli / percent-round — grounded on +// std.integer.uint8_channel_inclusive_max_value_derived (from bits_per_byte), +// std.measure.basis_point_unity_count (extdeps.units.dimensionless), hue_angle facts below; (5) +// Achromatic HslProjection variant per CSS4 hue-is-none; (14) hex_digit deduped onto +// extdeps.numeric.base16. Remaining: OKLCH reground design-register-library.md sect 10 Class 2 +// (explicit trigger, excluded from this PR). type Srgb8 { r: Int @@ -114,7 +132,11 @@ fn full_turn_millidegrees() -> Int { hue_milli_per_sextant() * hexcone_sextant_count } -// HslProjection: chromatic rows carry h in degrees [0, 360); Achromatic rows omit hue per CSS Color 4 hue-is-none (delta=0). s_pct and l_pct are bounded Int percent counts [0, 100] from the CSS Color 4 integer algorithm. Percent carrier at register binding is deferred — see std.measure.percent_from_computed_int_frontier. OKLCH reground: design-register-library (deleted) sect 10 Class 2. +// HslProjection: chromatic rows carry h in degrees [0, 360); Achromatic rows omit hue per CSS Color +// 4 hue-is-none (delta=0). s_pct and l_pct are bounded Int percent counts [0, 100] from the CSS +// Color 4 integer algorithm. Percent carrier at register binding is deferred — see +// std.measure.percent_from_computed_int_frontier. OKLCH reground: design-register-library +// (deleted) sect 10 Class 2. type ColorConversionFrontier = HslLive | OklchStaged { trigger: String } diff --git a/dag/extdeps/connector/samtec_ftsh_105_01_l_dv.dag b/dag/extdeps/connector/samtec_ftsh_105_01_l_dv.dag index 0352867837e..ccb5f2e2ca6 100644 --- a/dag/extdeps/connector/samtec_ftsh_105_01_l_dv.dag +++ b/dag/extdeps/connector/samtec_ftsh_105_01_l_dv.dag @@ -17,7 +17,16 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data ftsh_105_01_l_dv_note: String = "THIS ROW HAS NO UPSTREAM PRECEDENT BEHIND IT AT ALL, AND SAYING SO IS THE POINT OF CARRYING IT. The other three unread rows in this lane are named by Ampere's Mt. Jade reference for specific jobs; this part number was proposed here as a debug header, and Mt. Jade names no part number for its JTAG headers. So there is nothing upstream that even suggests it — which is the class of pick the operator's ruling on this lane exists to make visible, and it is recorded rather than quietly presented alongside the others. THE ANCHOR POINTS AT A SIBLING VARIANT'S PRINT. Samtec publishes per-variant footprint prints under one path pattern, and the print observed to exist is the -DH one; the anchor records the path that was observed rather than a constructed -DV path, because guessing a locator is how a citation ends up naming a document nobody has seen. The -DV footprint print is what the standing below owes." +// THIS ROW HAS NO UPSTREAM PRECEDENT BEHIND IT AT ALL, AND SAYING SO IS THE POINT OF CARRYING IT. +// The other three unread rows in this lane are named by Ampere's Mt. Jade reference for specific +// jobs; this part number was proposed here as a debug header, and Mt. Jade names no part number for +// its JTAG headers. So there is nothing upstream that even suggests it — which is the class of pick +// the operator's ruling on this lane exists to make visible, and it is recorded rather than quietly +// presented alongside the others. THE ANCHOR POINTS AT A SIBLING VARIANT'S PRINT. Samtec publishes +// per-variant footprint prints under one path pattern, and the print observed to exist is the -DH +// one; the anchor records the path that was observed rather than a constructed -DV path, because +// guessing a locator is how a citation ends up naming a document nobody has seen. The -DV footprint +// print is what the standing below owes. data ftsh_105_01_l_dv: CatalogPartIdentity = CatalogPartIdentity { ordering_part_number: "FTSH-105-01-L-DV", diff --git a/dag/extdeps/container/engine.dag b/dag/extdeps/container/engine.dag index 066bc3c7b18..2b012c726b1 100644 --- a/dag/extdeps/container/engine.dag +++ b/dag/extdeps/container/engine.dag @@ -15,18 +15,17 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { // THE AGNOSTIC SHAPE OF A THING THAT RUNS AN OCI IMAGE, WITH NO PRODUCT ENUMERATED IN IT. // -// This hub defines what a container engine IS. It does not name Docker, Podman or anything else, -// and it deliberately carries no `match` over products: enumerating them here is precisely the case -// DESIGN's external-upstream-decomposition section rules out with "adding Opera must not widen a -// generic browser-product enum". Each engine is an independently versioned upstream product with -// its own module authority and its own cited row; adding one is a new file, never an edit here. +// This hub defines what a container engine IS. It names neither Docker nor Podman and carries no +// `match` over products: enumerating them here is the case DESIGN's external-upstream-decomposition +// section rules out with "adding Opera must not widen a generic browser-product enum". Each engine +// is an independently versioned upstream with its own module authority and cited row; adding one is +// a new file, never an edit here. // -// WHAT THIS REPLACES. `gunbc.fleet_intent` carried -// type ContainerRuntime = Docker | Podman | Gvisor | Ubicloud | GcloudRun -// which fused three unrelated concepts into five members of one enum: container engines (Docker, -// Podman), a sandboxing runtime whose whole point is the isolation guarantee it adds (gVisor), and -// two companies that rent you a machine (Ubicloud, Google Cloud Run). Widening it was the only way -// to add either an engine or a supplier, so the two questions could never be asked separately. +// WHAT THIS REPLACES. `gunbc.fleet_intent` carried type ContainerRuntime = Docker | Podman | Gvisor +// | Ubicloud | GcloudRun fusing three unrelated concepts into one enum: container engines (Docker, +// Podman), a sandboxing runtime whose point is the isolation guarantee it adds (gVisor), and two +// companies that rent you a machine (Ubicloud, Google Cloud Run). Widening it was the only way to +// add an engine or a supplier, so the two questions could never be asked separately. type EnginePrivilegeMode = RootfulDaemon @@ -41,11 +40,10 @@ fn engine_privilege_mode_label(mode: EnginePrivilegeMode) -> String { // A PRODUCT CAPABILITY ROW, NOT A DEPLOYMENT. // -// `privilege_modes` is what the PRODUCT supports, cited to its own docs. Which mode a particular -// host actually runs is a fact about that host and lives on the deployment below -- the -// interface / realization / policy split of DESIGN section 3. Fusing them is how "we run Docker" -// came to mean nothing about whether the daemon is root, which is the one property that decides -// whether a compromised job owns the machine. +// `privilege_modes` is what the PRODUCT supports, cited to its own docs. Which mode a host actually +// runs is a fact about that host and lives on the deployment below — the interface / realization / +// policy split of DESIGN section 3. Fusing them is how "we run Docker" came to say nothing about +// whether the daemon is root, the one property deciding whether a compromised job owns the machine. type ContainerEngine { vendor: Vendor product_name: NonEmptyStr @@ -59,7 +57,7 @@ fn container_engine_supports_privilege_mode(engine: ContainerEngine, mode: Engin type EngineDeploymentRefusal = PrivilegeModeUnsupported { product_name: NonEmptyStr, mode: EnginePrivilegeMode } -// WHAT IS ACTUALLY INSTALLED SOMEWHERE, WHICH IS THE ENGINE PLUS ONE CHOICE ABOUT IT. +// WHAT IS ACTUALLY INSTALLED SOMEWHERE: THE ENGINE PLUS ONE CHOICE ABOUT IT. type ContainerEngineDeployment { engine: ContainerEngine privilege_mode: EnginePrivilegeMode @@ -69,8 +67,8 @@ type ContainerEngineDeploymentOutcome = ContainerEngineDeployed { deployment: ContainerEngineDeployment } | ContainerEngineDeploymentRefused { refusal: EngineDeploymentRefusal } -// Claiming a mode the product does not offer is a declaration that cannot be true, so it refuses -// here rather than being carried as a surface fact nothing ever checks. +// Claiming a mode the product does not offer cannot be true, so it refuses here rather than riding +// as a surface fact nothing checks. fn container_engine_deployment( engine: ContainerEngine, privilege_mode: EnginePrivilegeMode, diff --git a/dag/extdeps/container/oci/digest.dag b/dag/extdeps/container/oci/digest.dag index f1609f33fe4..d8f9aa26e7a 100644 --- a/dag/extdeps/container/oci/digest.dag +++ b/dag/extdeps/container/oci/digest.dag @@ -30,7 +30,18 @@ data extdeps_model_scope: ExternalModelScope = ExternalModelScope { further_citations: [] } -data oci_content_digest_note: String = "OCI digest authority (image-spec descriptor.md; distribution-spec digest): algorithm-qualified content address with wire form algorithm:encoded. Modelled as OciContentDigest coproduct (review 45466) — OciSha256Digest(Sha256Digest) | OciSha512Digest(Sha512Digest) | OciOtherDigest(OciOtherDigestBody) — so algorithm tag and payload cannot be forged independently. Both sha256 and sha512 arms carry std.content_hash digest carriers directly (operator portfolio 2026-08-04: npm is the second validated SHA-512 consumer that dissolved OciSha512DigestHex / OciSha512DigestBody). other body uses OciOtherDigestAlgorithm (String where oci_other_digest_algorithm — construction-walls reserved sha256/sha512 wire tokens and cites image-spec algorithm grammar algorithm-component ([a-z0-9]+) separated by [+._-], review 45505) plus OciOtherDigestEncoded (String where oci_other_digest_encoded — [a-zA-Z0-9=_-]+ per descriptor.md, review 45479/45731). Equality is substrate structural == on the coproduct (no parallel oci_content_digest_eq predicate)." +// OCI digest authority (image-spec descriptor.md; distribution-spec digest): algorithm-qualified +// content address with wire form algorithm:encoded. Modelled as OciContentDigest coproduct (review +// 45466) — OciSha256Digest(Sha256Digest) | OciSha512Digest(Sha512Digest) | +// OciOtherDigest(OciOtherDigestBody) — so algorithm tag and payload cannot be forged independently. +// Both sha256 and sha512 arms carry std.content_hash digest carriers directly (operator portfolio +// 2026-08-04: npm is the second validated SHA-512 consumer that dissolved OciSha512DigestHex / +// OciSha512DigestBody). other body uses OciOtherDigestAlgorithm (String where +// oci_other_digest_algorithm — construction-walls reserved sha256/sha512 wire tokens and cites +// image-spec algorithm grammar algorithm-component ([a-z0-9]+) separated by [+._-], review 45505) +// plus OciOtherDigestEncoded (String where oci_other_digest_encoded — [a-zA-Z0-9=_-]+ per +// descriptor.md, review 45479/45731). Equality is substrate structural == on the coproduct (no +// parallel oci_content_digest_eq predicate). fn oci_encoded_digest_char_allowed(cp: Int) -> Bool { (cp >= 48 && cp <= 57) diff --git a/dag/extdeps/container/oci/reference.dag b/dag/extdeps/container/oci/reference.dag index dea960f1ebb..0a31c3c7627 100644 --- a/dag/extdeps/container/oci/reference.dag +++ b/dag/extdeps/container/oci/reference.dag @@ -29,15 +29,13 @@ data extdeps_model_scope: ExternalModelScope = ExternalModelScope { // HOW AN IMAGE IS NAMED, WHICH THE OCI MODEL IN THIS DIRECTORY DID NOT CARRY. // -// manifest.dag and image_config.dag model what an image IS once you hold its bytes. Nothing -// modelled how a party REFERS to one, so the only way to say "run this image" was a String -- the -// anemic leaf DESIGN section 2 names, hiding a registry host, a repository path and a mutable-or- -// immutable identifier behind one opaque scalar. +// manifest.dag and image_config.dag model what an image IS; nothing modelled how a party REFERS to +// one, so "run this image" was a String -- the anemic leaf of DESIGN section 2, hiding a registry +// host, a repository path and a mutable-or-immutable identifier behind one scalar. // -// BOTH IDENTIFIER ARMS ARE MODELLED, INCLUDING THE MUTABLE ONE, AND THAT IS DELIBERATE. A tag is a -// real thing the distribution spec defines; refusing to admit a tag is POLICY, and policy in an -// extdeps module is the layer inversion DESIGN section 3 rules out. The spec says what may be -// written; the consumer says what it will accept. +// BOTH IDENTIFIER ARMS ARE MODELLED, INCLUDING THE MUTABLE ONE. The distribution spec defines tags; +// refusing a tag is POLICY, and policy in extdeps is the layer inversion DESIGN section 3 rules +// out. The spec says what may be written; the consumer says what it will accept. fn oci_code_point_at(text: String, index: Int) -> Int { code_point(substring(s: text, start: index, end: index + 1)) @@ -54,9 +52,9 @@ fn oci_separator_code_point(cp: Int) -> Bool { // path-component := alpha-numeric [separator alpha-numeric]* // separator := "." | "_" | "__" | "-"+ // -// The separator alternatives are enumerated rather than collapsed to "one or more of ._-" because -// the spec admits a DOUBLE underscore and a single dot, and nothing else doubled: "a..b" and "a___b" -// are not repository names. A permissive reading here would be a fail-open at the naming boundary. +// Separators are enumerated, not collapsed to "one or more of ._-": the spec admits a DOUBLE +// underscore and a single dot and nothing else doubled, so "a..b" and "a___b" are not repository +// names. A permissive reading would fail open at the naming boundary. fn oci_hyphen_run(text: String, index: Int) -> Bool { if index >= text.length() { text.length() > 0 @@ -157,17 +155,16 @@ type OciRepositoryName { // THE ONE AXIS THAT DECIDES WHETHER A REFERENCE NAMES FIXED BYTES. // -// A digest reference resolves to exactly one set of bytes forever; a tag resolves to whatever the -// registry currently points it at, so two pulls of one tag are two different programs. That is a -// difference in KIND, not a nullable field, which is why this is a coproduct: a record with both -// slots optional admits "neither" and "both", neither of which a reference can be. +// A digest resolves to one set of bytes forever; a tag resolves to whatever the registry currently +// points at, so two pulls of one tag are two programs. A difference in KIND, hence a coproduct: a +// record with both slots optional admits "neither" and "both", which no reference can be. type OciReferenceIdentifier = ReferenceByDigest(OciContentDigest) | ReferenceByTag(OciTag) -// registry is optional because the spec's grammar makes the host optional; what a bare name -// resolves to is a CLIENT default (Docker Hub for one client, a mirror for another), so filling it -// in here would author a policy fact as an upstream one. +// registry is optional because the spec's grammar makes the host optional; a bare name resolves to +// a CLIENT default (Docker Hub for one, a mirror for another), so filling it in here would author +// policy as an upstream fact. type OciImageReference { registry: OciRegistry? repository: OciRepositoryName @@ -243,9 +240,9 @@ fn oci_registry_wire(registry: OciRegistry) -> String { } } -// Emission is the structural inverse of the grammar above, not a second spelling of it: the -// separator between repository and identifier is "@" for a digest and ":" for a tag, which is -// exactly the distinction OciReferenceIdentifier carries. +// Emission is the structural inverse of the grammar above, not a second spelling: the separator +// before the identifier is "@" for a digest and ":" for a tag, the distinction +// OciReferenceIdentifier carries. fn render_oci_image_reference(reference: OciImageReference) -> String { let named = match reference.registry { Absent => oci_repository_name_wire(name: reference.repository) diff --git a/dag/extdeps/cooling/types.dag b/dag/extdeps/cooling/types.dag index 82d3a037eda..57ecae64630 100644 --- a/dag/extdeps/cooling/types.dag +++ b/dag/extdeps/cooling/types.dag @@ -29,12 +29,12 @@ data extdeps_model_scope: ExternalModelScope = ExternalModelScope { further_citations: [] } -// supported_motherboards is grounded on the existing BaseboardModel authority rather than a bare -// string: a cooler's supported-board population is a set of the same board identities the fleet -// already binds, so comparing them must not route through label spelling. It was -// List carrying "ALTRAD8UD-1L2T", which matched neither the BaseboardModel arm nor -// the catalog model_label "ASRock Rack ALTRAD8UD-1L2T" — a String leaf hiding a named part, -// decomposed here at its first consumer, product.cooling_qualification. +// supported_motherboards is grounded on the existing BaseboardModel authority, not a bare string: a +// cooler's supported-board population is the same board identities the fleet already binds, so +// comparison must not route through label spelling. It was List carrying +// "ALTRAD8UD-1L2T", matching neither the BaseboardModel arm nor the catalog model_label "ASRock +// Rack ALTRAD8UD-1L2T" — a String leaf hiding a named part, decomposed at its first consumer, +// product.cooling_qualification. type CpuCoolerCatalogRow { model: NonEmptyStr manufacturer: Vendor diff --git a/dag/extdeps/cpu/amd.dag b/dag/extdeps/cpu/amd.dag index e91da67d470..e7c277eeda6 100644 --- a/dag/extdeps/cpu/amd.dag +++ b/dag/extdeps/cpu/amd.dag @@ -33,16 +33,15 @@ data amd_ryzen_7_pro_8700ge_authority: ExternalAuthority = ExternalAuthority { } } -// SOCKET AM5 IS LGA, NOT PGA. Both Zen 4 rows below recorded PinGridArray at 1718 contacts, which -// is the termination AMD used through AM4 and abandoned at AM5 -- the socket is a zero insertion -// force flip-chip land grid array, so the processor presents lands and the socket presents the -// springs. The count was already right; the termination was carried over from the AM4 rows. -// AM5 KEEPS AM4'S COOLER MOUNTING, AND THAT IS WHY THE MOUNT IS NOT THE SOCKET. AM4 and AM5 are -// different sockets by every socket fact -- 1331 pin-grid against 1718 land-grid -- yet AMD retained -// the AM4 retention geometry on AM5 so that AM4 coolers fit. A compatibility rule reading socket -// identity therefore gets a real, common case WRONG in the refusing direction: it rejects a cooler -// that physically fits and that the vendor says fits. This is the discriminating pair for the whole -// correction, and it is why both rows below name one mount. +// SOCKET AM5 IS LGA, NOT PGA. Both Zen 4 rows below recorded PinGridArray at 1718 contacts — the +// termination AMD used through AM4 and abandoned at AM5: the socket is a zero insertion force +// flip-chip land grid array, so the processor presents lands and the socket the springs. The count +// was right; the termination was carried over from the AM4 rows. AM5 KEEPS AM4'S COOLER MOUNTING, +// WHICH IS WHY THE MOUNT IS NOT THE SOCKET. AM4 and AM5 are different sockets by every socket fact +// — 1331 pin-grid against 1718 land-grid — yet AMD retained the AM4 retention geometry on AM5 so +// AM4 coolers fit. A compatibility rule reading socket identity gets a real, common case WRONG in +// the refusing direction: it rejects a cooler that physically fits and that the vendor says fits. +// This is the discriminating pair for the whole correction, and why both rows below name one mount. data amd_am4_cooling_mount: ProcessorCoolingMount = ProcessorCoolingMount { platform_owner: amd, designation: "AM4", @@ -62,7 +61,8 @@ data amd_ryzen_9_7950x3d_authority: ExternalAuthority = ExternalAuthority { } } -// cores and threads are cited from each model's AMD product page (authorities above). threads is stored as HardwareThreadCount — the comparable unit for hosted-runner vcpus in pricing extdeps. +// cores and threads are cited from each model's AMD product page (authorities above). threads is +// stored as HardwareThreadCount — the comparable unit for hosted-runner vcpus in pricing extdeps. data amd_zen2_l1_cache_geometry: CpuL1CacheGeometry = CpuL1CacheGeometry { data_capacity_per_core: byte_size(32768), instruction_capacity_per_core: byte_size(32768), @@ -101,13 +101,13 @@ data amd_zen2_l3_cache_citations: List = [ ] // THE AM5 TERMINATION IS CITED, not asserted. Both Zen 4 rows previously read PinGridArray at the -// correct 1718 count; an uncited socket field is what let a wrong termination sit beside a right -// number without anything to check it against. +// correct 1718 count; an uncited socket field let a wrong termination sit beside a right number +// with nothing to check against. // // THE SOCKET IS A PLATFORM FACT AND NOW HAS ONE HOME PER PLATFORM, the shape extdeps.cpu.ampere // already uses for LGA4926. Each row previously minted its own CpuSocket literal, so the citation -// lists beside them could name no declaration at all -- which is how a citation ends up keyed by a -// field SPELLING ("package", a name CpuSocket has not carried since termination and attachment were +// lists beside them could name no declaration — which is how a citation ends up keyed by a field +// SPELLING ("package", a name CpuSocket has not carried since termination and attachment were // split) instead of by the declaration the containment tree already names. data amd_socket_am4: CpuSocket = CpuSocket { platform_owner: amd, @@ -260,12 +260,11 @@ data ryzen_9_7950x3d_facts: CpuFacts = CpuFacts { }, } -// THE MODULE'S OWN ROSTER OF CATALOG ROWS. It lives here rather than in a witness so that a -// consumer asking "every AMD processor this repository models" reads one authority, and so that -// an author adding a row is editing the roster in the same file rather than remembering a test -// elsewhere. The roster is still hand-authored -- a genuinely closed denominator would have to be -// discovered from the module tree, which is lens work -- so this narrows the gap rather than -// closing it, and says so. +// THE MODULE'S OWN ROSTER OF CATALOG ROWS, here rather than in a witness so a consumer asking +// "every AMD processor this repository models" reads one authority, and an author adding a row +// edits the roster in the same file. The roster is still hand-authored — a genuinely closed +// denominator would have to be discovered from the module tree, which is lens work — so this +// narrows the gap rather than closing it, and says so. data amd_cpu_catalog_rows: List = [ ryzen_5_3600_catalog, ryzen_7_pro_8700ge_catalog, diff --git a/dag/extdeps/cpu/ampere_altra_memory_controller.dag b/dag/extdeps/cpu/ampere_altra_memory_controller.dag index 128eb8ee138..24dd80965ff 100644 --- a/dag/extdeps/cpu/ampere_altra_memory_controller.dag +++ b/dag/extdeps/cpu/ampere_altra_memory_controller.dag @@ -59,7 +59,17 @@ data extdeps_model_scope: ExternalModelScope = ampere_altra_memory_controller_mo // products and their memory sections are not assumed to agree — the Max part has its own document // and would need its own row set. That distinction is load-bearing here rather than pedantic: see // the UDIMM withdrawal below, which is a fact about THIS document's revision history. -data altra_memory_controller_ingestion_note: String = "Section 2.10 only. Deliberately NOT carried: the DDR electrical specifications, the training sequence, the RAS feature list beyond its existence, and the signal tables — an ingestion-scope boundary, not a carriage refusal. The document is publicly reachable at the cited locator without a login, so gunbc.upstream_document_carriage permits its facts; the proprietary page footer is a recorded attribute that decides nothing. Also deliberately NOT carried: the maximum transfer rate the section states as DDR4-3200. It has no consumer here, and the corpus has no carrier for it — transfer rate is a bare Int named data_rate_mts at 30 references across 11 files, so authoring it would mint a 31st instance of a representation that is already forked rather than ground the fact. MT/s is not Hertz: DDR4-3200 transfers 3200 megatransfers per second on a 1600 MHz clock, so the existing Hertz carrier is the wrong grounding and a correct one is a std.measure dimension plus the migration of the whole existing population, at that grain and not this one." +// Section 2.10 only. Deliberately NOT carried: the DDR electrical specifications, the training +// sequence, the RAS feature list beyond its existence, and the signal tables — an ingestion-scope +// boundary, not a carriage refusal. The document is publicly reachable at the cited locator without +// a login, so gunbc.upstream_document_carriage permits its facts; the proprietary page footer is a +// recorded attribute that decides nothing. Also deliberately NOT carried: the maximum transfer rate +// the section states as DDR4-3200. It has no consumer here, and the corpus has no carrier for it — +// transfer rate is a bare Int named data_rate_mts at 30 references across 11 files, so authoring it +// would mint a 31st instance of a representation that is already forked rather than ground the +// fact. MT/s is not Hertz: DDR4-3200 transfers 3200 megatransfers per second on a 1600 MHz clock, +// so the existing Hertz carrier is the wrong grounding and a correct one is a std.measure dimension +// plus the migration of the whole existing population, at that grain and not this one. data altra_memory_generation: DramGeneration = dram_generation_ddr4 @@ -182,7 +192,7 @@ fn channel_count_is_production_qualified(c: ActiveDdrChannelCount) -> Bool { // ACTIVE channels, so reducing the active count is not merely a capacity reduction — it changes // the address distribution, and a board that populates fewer channels is a different memory // system rather than a smaller one. -data altra_memory_interleave_note: String = "Section 2.10 states hashed memory interleave across active channels." +// Section 2.10 states hashed memory interleave across active channels. // The full-population data width, DERIVED from the channel facts rather than authored beside // them, so a corrected channel count cannot leave a stale aggregate behind. It returns BitWidth @@ -225,6 +235,12 @@ type BoardDownDramStanding | BoardDownNearestClassWithdrawn { authority: ExternalAuthority, withdrawal: ModuleClassWithdrawal } | BoardDownSupportUnestablished { required_authority: NonEmptyStr } +// The datasheet admits module classes and never addresses direct-attach topology, so +// BoardDownNearestClassWithdrawn reports what the controller authority establishes and nothing +// more. What remains unestablished, and would need a different authority: whether Ampere firmware +// trains a channel whose SPD is board-provided rather than module-resident, and whether any Ampere +// reference design has ever done so. Neither is answered by absence here. + fn altra_board_down_dram_standing(topology: BoardDownBuffering) -> BoardDownDramStanding { match topology { BoardProvidesNoRegisterOrBuffer => BoardDownNearestClassWithdrawn { @@ -242,4 +258,3 @@ fn altra_board_down_dram_standing(topology: BoardDownBuffering) -> BoardDownDram // connector-less topology at all, in either direction, and firmware acceptance of a board-owned // SPD source is a separate question this document does not reach. So the standing above is a // controller-authority verdict, not a proof of electrical impossibility. -data altra_board_down_residue_note: String = "The datasheet admits module classes and never addresses direct-attach topology, so BoardDownNearestClassWithdrawn reports what the controller authority establishes and nothing more. What remains unestablished, and would need a different authority: whether Ampere firmware trains a channel whose SPD is board-provided rather than module-resident, and whether any Ampere reference design has ever done so. Neither is answered by absence here." diff --git a/dag/extdeps/cpu/ampere_altra_package.dag b/dag/extdeps/cpu/ampere_altra_package.dag index e4e81e38b03..8b40af54dcf 100644 --- a/dag/extdeps/cpu/ampere_altra_package.dag +++ b/dag/extdeps/cpu/ampere_altra_package.dag @@ -24,7 +24,31 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { // Table 4, thirty-five sheets of PIN # to SIGNAL NAME; section 6 carries Signal Descriptions // with 6.1 Reserved Pins naming all thirty-nine explicitly; section 9 is Power Supply // Sequencing, split into SoC and PCP domains. -data ampere_altra_package_note: String = "THE ALTRA PACKAGE PINOUT IS PUBLIC, AND AN EARLIER REVISION OF THIS MODULE SAID IT WAS NOT. That revision declared the per-contact map, the power sequencing and the admitted memory topologies to be NDA-only, and refused on that basis. It was wrong, and the way it was wrong is worth keeping: the claim was reasoned from the fact that Ampere's reference board collateral IS under NDA, and never checked against the datasheet this repository was already citing. The refusal was therefore well-typed, executed on the real acceptance path, defended by a passing witness, and false — a fabricated verdict wearing every mark of a grounded one. THE ACTUAL SPLIT, read from the documents. PUBLIC, in the Rev A1 datasheet Issue 1.15: the complete pin assignment table (section 5, Table 4, thirty-five sheets), the signal functional descriptions with widths, directions and I/O types (section 6.2, Table 6), the thirty-nine reserved pins by designator (section 6.1), the pin summary decomposition (Table 5), mechanical data (section 3), electrical specifications (section 7) and power supply sequencing for both the SoC and PCP domains (section 9). NDA, via Customer Connect: the reference BOARD collateral — the Mt. Jade, Mt. Collins, Mt. Bonnell and Mt. Snow motherboard design packages, plus UEFI, OpenBMC and CPLD source. Those are board designs, not package facts, and conflating the two is what produced the error. THE PROPRIETARY FOOTER IS A RECORDED ATTRIBUTE, NOT A CARRIAGE BARRIER: every page of the datasheet is footed 'Ampere Computing Proprietary', but carriage derives from public availability — the locator returns the PDF without login — not from the footer. Transcribing the publicly reachable datasheet tables is therefore admissible on that axis; Customer Connect board collateral remains barred because it is not public, not because it is proprietary. TWO READING TRAPS IN THE SIGNAL NAMES, recorded because both are easy to get backwards. The DDR4_/DDR5_/DDR6_/DDR7_ prefixes are CHANNEL indices four through seven, not memory generations — section 6.2 groups them as 'DDR[0:7]' across eight DDR4 channels and the shared rail is named VDDQ_DDR4567. And Table 4 states it 'does not list the depopulated pins on the package', so a row count taken from that table is not automatically the package population; the completeness authority is Table 5, carried below." +// THE ALTRA PACKAGE PINOUT IS PUBLIC, AND AN EARLIER REVISION OF THIS MODULE SAID IT WAS NOT. That +// revision declared the per-contact map, the power sequencing and the admitted memory topologies to +// be NDA-only, and refused on that basis. It was wrong, and the way it was wrong is worth keeping: +// the claim was reasoned from the fact that Ampere's reference board collateral IS under NDA, and +// never checked against the datasheet this repository was already citing. The refusal was therefore +// well-typed, executed on the real acceptance path, defended by a passing witness, and false — a +// fabricated verdict wearing every mark of a grounded one. THE ACTUAL SPLIT, read from the +// documents. PUBLIC, in the Rev A1 datasheet Issue 1.15: the complete pin assignment table (section +// 5, Table 4, thirty-five sheets), the signal functional descriptions with widths, directions and +// I/O types (section 6.2, Table 6), the thirty-nine reserved pins by designator (section 6.1), the +// pin summary decomposition (Table 5), mechanical data (section 3), electrical specifications +// (section 7) and power supply sequencing for both the SoC and PCP domains (section 9). NDA, via +// Customer Connect: the reference BOARD collateral — the Mt. Jade, Mt. Collins, Mt. Bonnell and Mt. +// Snow motherboard design packages, plus UEFI, OpenBMC and CPLD source. Those are board designs, +// not package facts, and conflating the two is what produced the error. THE PROPRIETARY FOOTER IS A +// RECORDED ATTRIBUTE, NOT A CARRIAGE BARRIER: every page of the datasheet is footed 'Ampere +// Computing Proprietary', but carriage derives from public availability — the locator returns the +// PDF without login — not from the footer. Transcribing the publicly reachable datasheet tables is +// therefore admissible on that axis; Customer Connect board collateral remains barred because it is +// not public, not because it is proprietary. TWO READING TRAPS IN THE SIGNAL NAMES, recorded +// because both are easy to get backwards. The DDR4_/DDR5_/DDR6_/DDR7_ prefixes are CHANNEL indices +// four through seven, not memory generations — section 6.2 groups them as 'DDR[0:7]' across eight +// DDR4 channels and the shared rail is named VDDQ_DDR4567. And Table 4 states it 'does not list the +// depopulated pins on the package', so a row count taken from that table is not automatically the +// package population; the completeness authority is Table 5, carried below. // Table 5, Pin Summary, cited to the datasheet. This is the completeness authority a contact // map must join against — and it cross-foots exactly, which is why it is carried as its parts diff --git a/dag/extdeps/cpu/ampere_altra_package_table4_raw.dag b/dag/extdeps/cpu/ampere_altra_package_table4_raw.dag index 682834ce80c..fa7cd162589 100644 --- a/dag/extdeps/cpu/ampere_altra_package_table4_raw.dag +++ b/dag/extdeps/cpu/ampere_altra_package_table4_raw.dag @@ -16,7 +16,12 @@ data extdeps_model_scope: ExternalModelScope = ampere_altra_package_model_scope // (extdeps.cpu.ampere ampere_altra_rev_a1_datasheet_authority), matching altra_max_m12830_catalog. // Classic Altra Rev A1 Issue 1.50 is ampere_altra_classic_rev_a1_datasheet_authority — a separate // document; shared 4926-pin FCLGA count does not establish identical Table 4 assignment. -data table4_transcription_provenance_note: String = "ONE-SHOT HAND TRANSCRIPTION. The 4926 Table4PinRow entries in altra_table4_pin_rows are authoritative source data committed directly in .dag; no mechanical regenerator exists on this branch. Electrical classification is derived by extdeps.cpu.ampere_altra_package contacts_from_table4; population laws in altra_contact_map_standing cross-foot against Table 5. Cited document: Ampere Altra Max Rev A1 Issue 1.15 (extdeps.cpu.ampere ampere_altra_rev_a1_datasheet_authority), not the classic Altra Rev A1 Issue 1.50 authority." +// ONE-SHOT HAND TRANSCRIPTION. The 4926 Table4PinRow entries in altra_table4_pin_rows are +// authoritative source data committed directly in .dag; no mechanical regenerator exists on this +// branch. Electrical classification is derived by extdeps.cpu.ampere_altra_package +// contacts_from_table4; population laws in altra_contact_map_standing cross-foot against Table 5. +// Cited document: Ampere Altra Max Rev A1 Issue 1.15 (extdeps.cpu.ampere +// ampere_altra_rev_a1_datasheet_authority), not the classic Altra Rev A1 Issue 1.50 authority. // TRANSCRIBED designator/signal rows from Ampere Altra Max Rev A1 datasheet Issue 1.15, // section 5 Table 4 (extdeps.cpu.ampere ampere_altra_rev_a1_datasheet_authority). diff --git a/dag/extdeps/cpu/ampere_altra_platform_hw_design/platform.dag b/dag/extdeps/cpu/ampere_altra_platform_hw_design/platform.dag index 7dbda3320dd..1f93a47bd03 100644 --- a/dag/extdeps/cpu/ampere_altra_platform_hw_design/platform.dag +++ b/dag/extdeps/cpu/ampere_altra_platform_hw_design/platform.dag @@ -44,7 +44,16 @@ data extdeps_model_scope: ExternalModelScope = ExternalModelScope { further_citations: [] } -data platform_ingestion_note: String = "READ, NOT RECALLED. The v1.00 PDF at the subject module's public CDN locator was fetched and its text extracted on 2026-08-14 — content streams inflated and each string decoded through the PDF's own per-font ToUnicode CMap — so every row below is transcribed from a section this repository actually read, and the section is named. What is deliberately NOT carried yet: tables, figures, layout geometry, impedance targets and electrical limits — an ingestion-scope boundary, not a carriage refusal. The document is footed 'Ampere Computing Proprietary' on every page; that marking is a recorded attribute that decides nothing (gunbc.upstream_document_carriage, extdeps.cpu.ampere_altra_package). This module takes structural statements from running text only; the same public-availability axis that permits the rows below bars Customer Connect collateral, not the footer." +// READ, NOT RECALLED. The v1.00 PDF at the subject module's public CDN locator was fetched and its +// text extracted on 2026-08-14 — content streams inflated and each string decoded through the PDF's +// own per-font ToUnicode CMap — so every row below is transcribed from a section this repository +// actually read, and the section is named. What is deliberately NOT carried yet: tables, figures, +// layout geometry, impedance targets and electrical limits — an ingestion-scope boundary, not a +// carriage refusal. The document is footed 'Ampere Computing Proprietary' on every page; that +// marking is a recorded attribute that decides nothing (gunbc.upstream_document_carriage, +// extdeps.cpu.ampere_altra_package). This module takes structural statements from running text +// only; the same public-availability axis that permits the rows below bars Customer Connect +// collateral, not the footer. // THE REVISION THIS REPOSITORY READ IS SUPERSEDED, AND THE STANDING IS THE // SUBJECT MODULE'S FACT, NOT A SECOND ONE HERE. The subject carries @@ -185,7 +194,14 @@ data altra_ccix_x16_links_per_socket: Int = 2 data altra_pcie_only_lane_count: Int = 96 -data altra_ccix_link_framing_note: String = "TWO STATEMENTS IN ONE DOCUMENT, CARRIED AS THEY STAND. Section 2.3.1 says Altra provides 128 PCIe Gen4 lanes with 48 controllers to support 96 lanes of PCIe and up to 2 x16 Cache Coherent Interconnect for Accelerators links. The overview, section 1.4, instead lists 4 CCIX links beside 192 lanes in 2P configuration. The two are NOT reconciled here and no third number is derived from them: the section 2.3.1 figures are per socket at a stated split, the overview bullets are family-level and their grain is not stated, and inventing a reconciliation would be the fabricated-plausible-output failure DESIGN section 5 forbids. Consumers take the per-socket rows; a grain the document itself states is the only thing that retires this note." +// TWO STATEMENTS IN ONE DOCUMENT, CARRIED AS THEY STAND. Section 2.3.1 says Altra provides 128 PCIe +// Gen4 lanes with 48 controllers to support 96 lanes of PCIe and up to 2 x16 Cache Coherent +// Interconnect for Accelerators links. The overview, section 1.4, instead lists 4 CCIX links beside +// 192 lanes in 2P configuration. The two are NOT reconciled here and no third number is derived +// from them: the section 2.3.1 figures are per socket at a stated split, the overview bullets are +// family-level and their grain is not stated, and inventing a reconciliation would be the +// fabricated-plausible-output failure DESIGN section 5 forbids. Consumers take the per-socket rows; +// a grain the document itself states is the only thing that retires this note. data altra_overview_ccix_link_count: Int = 4 @@ -351,7 +367,12 @@ fn pad_grid_exceeds_pad_population() -> Bool { altra_pad_grid_position_count() > altra_pad_count } -data altra_pcb_installation_note: String = "Section 1.9 states PCB installation: Using socket. That is a requirement on the board, not a description of the package: an Altra board attaches the processor through a socket, so the land pattern a board needs is the SOCKET's, and the package outline drawing and the socket drawing are separate references (both Figure 3). The datasheet names the designation ILM4926 in its thermal notes, and that designation is deliberately not re-declared here — it is named by two independent documents and belongs to neither of their modules." +// Section 1.9 states PCB installation: Using socket. That is a requirement on the board, not a +// description of the package: an Altra board attaches the processor through a socket, so the land +// pattern a board needs is the SOCKET's, and the package outline drawing and the socket drawing are +// separate references (both Figure 3). The datasheet names the designation ILM4926 in its thermal +// notes, and that designation is deliberately not re-declared here — it is named by two independent +// documents and belongs to neither of their modules. // AN AVAILABLE FACT AND A POSITIONABLE ONE ARE DIFFERENT THINGS, and this is // the row that keeps a permissive ruling from being read as more than it is: @@ -449,6 +470,16 @@ fn names_a_board_layout_document() -> Bool { any(altra_platform_referenced_documents, d => referenced_document_is_board_layout(d: d)) } +// WHAT THIS DOCUMENT'S IMPEDANCE TARGETS ARE NOT. Section 4.1.2 is 'Decoupling Requirements to +// Achieve Target Impedance' and section 4.4 is 'Power Integrity Simulations': every impedance +// figure in this specification is POWER-DELIVERY network target impedance, per rail, reached by +// decoupling. None of it is DDR trace impedance, and this document states no DDR routing rules, no +// trace impedance target, no length matching and no stackup. The layout guidance it does carry is +// power (4.1.1 Physical Design, 4.2.2 and 4.3.2 vendor reference layouts) and I2C (7.1.4). A +// downstream consumer reaching for a number from section 4 as a signal-integrity target would get a +// category error with plausible numbers attached, which is why the boundary is stated here rather +// than left to be inferred from what is absent. + fn board_layout_document_is_reachable() -> Bool { any(altra_platform_referenced_documents, d => referenced_document_is_board_layout(d: d) @@ -458,6 +489,26 @@ fn board_layout_document_is_reachable() -> Bool { }) } -data altra_platform_impedance_scope_note: String = "WHAT THIS DOCUMENT'S IMPEDANCE TARGETS ARE NOT. Section 4.1.2 is 'Decoupling Requirements to Achieve Target Impedance' and section 4.4 is 'Power Integrity Simulations': every impedance figure in this specification is POWER-DELIVERY network target impedance, per rail, reached by decoupling. None of it is DDR trace impedance, and this document states no DDR routing rules, no trace impedance target, no length matching and no stackup. The layout guidance it does carry is power (4.1.1 Physical Design, 4.2.2 and 4.3.2 vendor reference layouts) and I2C (7.1.4). A downstream consumer reaching for a number from section 4 as a signal-integrity target would get a category error with plausible numbers attached, which is why the boundary is stated here rather than left to be inferred from what is absent." - -data altra_platform_section_coverage_note: String = "WHAT THIS MODULE MODELS AND WHAT IT DOES NOT, so absence is never read as a claim about the document. MODELLED, from running text: the memory topologies and slot ordering (2.2), the root-complex structure and admitted lane groupings (2.3.1), the reference-clock inputs (2.3.2), the PCIe reset scheme (2.3.3), hot-plug realization (2.3.5) and the dual-socket CCIX interconnect (2.1). NOT MODELLED, each a real section rather than an oversight: the BMC subsystem (3), power supply design, decoupling, regulator selection and power sequencing (4), clock distribution (5), the PERST encode table (6.1), miscellaneous I/O interfaces (7), system boot sequences (8) and the hardware bring-up guide (9). Those are tables, figures and electrical limits not yet transcribed; carriage is permitted because the document is publicly published (altra_platform_hw_design_publication_status), and a consumer that needs one is what earns its transcription. CORRECTED BY A COMPLETE READ ON 2026-08-15: an earlier revision of this sentence said the socket land pattern was in that second group of not-yet-transcribed sections. It is not in this document at all, in any section, in text or in a figure — the document instead names an access-gated layout document (altra_platform_referenced_documents), and that naming is WEAKER than it first reads: both citing sections are vendor VRD reference layouts, so what the pointer routes to is voltage-regulator layout and whether the gated document also carries the socket land pattern is unverified rather than known. Corrected again on 2026-08-16 after extracting this document's own table of contents, because the previous wording called the gated document the authority for the land pattern, which conflates a document being named with a document being read. Keepout, DDR4 routing rules, impedance targets and stackup guidance remain genuinely untranscribed here, and some may exist only in the unretrieved 1.20 revision; the land pattern is a different case and must not be filed with them, because waiting for a transcription that can never arrive is indistinguishable from progress." +// WHAT THIS MODULE MODELS AND WHAT IT DOES NOT, so absence is never read as a claim about the +// document. MODELLED, from running text: the memory topologies and slot ordering (2.2), the +// root-complex structure and admitted lane groupings (2.3.1), the reference-clock inputs (2.3.2), +// the PCIe reset scheme (2.3.3), hot-plug realization (2.3.5) and the dual-socket CCIX interconnect +// (2.1). NOT MODELLED, each a real section rather than an oversight: the BMC subsystem (3), power +// supply design, decoupling, regulator selection and power sequencing (4), clock distribution (5), +// the PERST encode table (6.1), miscellaneous I/O interfaces (7), system boot sequences (8) and the +// hardware bring-up guide (9). Those are tables, figures and electrical limits not yet transcribed; +// carriage is permitted because the document is publicly published +// (altra_platform_hw_design_publication_status), and a consumer that needs one is what earns its +// transcription. CORRECTED BY A COMPLETE READ ON 2026-08-15: an earlier revision of this sentence +// said the socket land pattern was in that second group of not-yet-transcribed sections. It is not +// in this document at all, in any section, in text or in a figure — the document instead names an +// access-gated layout document (altra_platform_referenced_documents), and that naming is WEAKER +// than it first reads: both citing sections are vendor VRD reference layouts, so what the pointer +// routes to is voltage-regulator layout and whether the gated document also carries the socket land +// pattern is unverified rather than known. Corrected again on 2026-08-16 after extracting this +// document's own table of contents, because the previous wording called the gated document the +// authority for the land pattern, which conflates a document being named with a document being +// read. Keepout, DDR4 routing rules, impedance targets and stackup guidance remain genuinely +// untranscribed here, and some may exist only in the unretrieved 1.20 revision; the land pattern is +// a different case and must not be filed with them, because waiting for a transcription that can +// never arrive is indistinguishable from progress. diff --git a/dag/extdeps/cpu/ampere_altra_platform_hw_design/subject.dag b/dag/extdeps/cpu/ampere_altra_platform_hw_design/subject.dag index 0f379111347..6a760673279 100644 --- a/dag/extdeps/cpu/ampere_altra_platform_hw_design/subject.dag +++ b/dag/extdeps/cpu/ampere_altra_platform_hw_design/subject.dag @@ -30,7 +30,16 @@ data ampere_technical_document_index_authority: ExternalAuthority = ExternalAuth } } -data altra_platform_hw_design_subject_note: String = "THE HIGHEST-VALUE PUBLIC DOCUMENT THIS MOTHERBOARD LANE HAS FOUND. extdeps.cpu.ampere_altra_package carries the processor package pinout; extdeps.ocp.mt_jade carries one OCP reference-board specification; this module is the third axis — the vendor's platform hardware design guide for boards built around Altra rather than around a named reference platform. THE ANCHOR CITES THE PUBLIC CDN v1.00 COPY; AMPERE'S TECHNICAL-DOCUMENT INDEX LISTS A NEWER v1.20 REVISION DATED 2025-07-15 THAT THIS REPOSITORY HAS NOT RETRIEVED. HOW THIS REPOSITORY READ THAT LOCATOR IS RECORDED AS A GUNBC RECEIPT WITH A DeclarationRef TO altra_platform_hw_design_specification, NOT AS A FIELD ON THIS RECORD. THIS MODULE IS THE SUBJECT ANCHOR ONLY; PLATFORM FACTS ARE OWNED BY extdeps.cpu.ampere_altra_platform_hw_design.platform AND LAND IN #8260." +// THE HIGHEST-VALUE PUBLIC DOCUMENT THIS MOTHERBOARD LANE HAS FOUND. +// extdeps.cpu.ampere_altra_package carries the processor package pinout; extdeps.ocp.mt_jade +// carries one OCP reference-board specification; this module is the third axis — the vendor's +// platform hardware design guide for boards built around Altra rather than around a named reference +// platform. THE ANCHOR CITES THE PUBLIC CDN v1.00 COPY; AMPERE'S TECHNICAL-DOCUMENT INDEX LISTS A +// NEWER v1.20 REVISION DATED 2025-07-15 THAT THIS REPOSITORY HAS NOT RETRIEVED. HOW THIS REPOSITORY +// READ THAT LOCATOR IS RECORDED AS A GUNBC RECEIPT WITH A DeclarationRef TO +// altra_platform_hw_design_specification, NOT AS A FIELD ON THIS RECORD. THIS MODULE IS THE SUBJECT +// ANCHOR ONLY; PLATFORM FACTS ARE OWNED BY extdeps.cpu.ampere_altra_platform_hw_design.platform AND +// LAND IN #8260. type AltraPlatformHwDesignRev1_00 {} diff --git a/dag/extdeps/cpu_attachment/ilm4926.dag b/dag/extdeps/cpu_attachment/ilm4926.dag index df69d45921d..b5c45eb0d8d 100644 --- a/dag/extdeps/cpu_attachment/ilm4926.dag +++ b/dag/extdeps/cpu_attachment/ilm4926.dag @@ -231,7 +231,10 @@ data lotes_part_attestations: List = [ }, ] -data lotes_azif0222_catalogue_note: String = "Lotes catalogue page for ARM P/N AZIF0222. Product Overview: SKT 4926 ILM and BP, Top-mount, HF. Component Type: CPU Socket ILM and BP. RoHS/Halogen-Free: HF. Packaging: Tray. The page states no dimensions, contact count, pitch, land pattern or mating socket-body part number, so it establishes the retention assembly's identity and nothing about board geometry." +// Lotes catalogue page for ARM P/N AZIF0222. Product Overview: SKT 4926 ILM and BP, Top-mount, HF. +// Component Type: CPU Socket ILM and BP. RoHS/Halogen-Free: HF. Packaging: Tray. The page states no +// dimensions, contact count, pitch, land pattern or mating socket-body part number, so it +// establishes the retention assembly's identity and nothing about board geometry. // THE SOCKET BODY IS A SEPARATE, UNNAMED PART, and this row exists so that resolving the // retention assembly cannot be mistaken for resolving the attachment. It is the layer that @@ -242,7 +245,11 @@ data lotes_azif0222_catalogue_note: String = "Lotes catalogue page for ARM P/N A // of one fact. What the old row got RIGHT is preserved: the socket body is a different item from // AZIF0222, and resolving it is what made the land pattern obtainable. That is exactly what // happened, by exactly the route lga4926_land_pattern_public_route predicted. -data socket_body_part_resolution_note: String = "Lotes supplied AZIFA072-P001CCS, drawing GAP-AZIFA072 rev 2, in direct response to the request recorded in lga4926_land_pattern_public_route: the socket-body part that mates with AZIF0222, with its customer drawing and the board land pattern. The part, its geometry and its remaining gaps are modelled in extdeps.cpu_attachment.lotes_azifa072. The public route resolved and the access-gated route was never taken, so the geometry is carriable rather than merely known." +// Lotes supplied AZIFA072-P001CCS, drawing GAP-AZIFA072 rev 2, in direct response to the request +// recorded in lga4926_land_pattern_public_route: the socket-body part that mates with AZIF0222, +// with its customer drawing and the board land pattern. The part, its geometry and its remaining +// gaps are modelled in extdeps.cpu_attachment.lotes_azifa072. The public route resolved and the +// access-gated route was never taken, so the geometry is carriable rather than merely known. // THE MECHANICAL DRAWING THE SAME THREAD CARRIES, AND WHAT IT IS NOT. A dimensioned plan view of // the retention frame was posted alongside the part numbers: a bezel with a central opening, a cam @@ -259,7 +266,11 @@ type MechanicalDrawingScope data lga4926_retention_drawing_scope: MechanicalDrawingScope = MountingAndOutlineGeometry -data lga4926_retention_drawing_note: String = "Community-posted dimensioned plan view of the LGA4926 retention frame: twelve mounting holes, datums A and B, symmetric callouts 2X97.00 +/-0.1 and 2X94.00 +/-0.05 across the width, 2X72.00 +/-0.1 and 2X60.00 +/-0.05 vertically, 150 degree corner chamfer. Mounting and outline geometry only. It carries no pad array, pitch or contact count, so it establishes nothing about the board land pattern." +// Community-posted dimensioned plan view of the LGA4926 retention frame: twelve mounting holes, +// datums A and B, symmetric callouts 2X97.00 +/-0.1 and 2X94.00 +/-0.05 across the width, 2X72.00 +// +/-0.1 and 2X60.00 +/-0.05 vertically, 150 degree corner chamfer. Mounting and outline geometry +// only. It carries no pad array, pitch or contact count, so it establishes nothing about the board +// land pattern. fn part_candidate_is_officially_attested(c: PartNumberCandidate) -> Bool { match c.standing { diff --git a/dag/extdeps/crm/hubspot_lifecycle.dag b/dag/extdeps/crm/hubspot_lifecycle.dag index b0d4c54f989..2f8b110ed0f 100644 --- a/dag/extdeps/crm/hubspot_lifecycle.dag +++ b/dag/extdeps/crm/hubspot_lifecycle.dag @@ -19,7 +19,14 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { data hubspot_lifecycle_subject: Vendor = hubspot -data hubspot_lifecycle_provenance_note: String = "Vocabulary facts live-fetched 2026-08-01 from the HubSpot Knowledge Base page 'Use contact and company lifecycle stages' (the anchor): the eight default lifecycle stages in the vendor's own order, display labels and definition sentences verbatim. The stages apply to both contact and company records upstream (the page title says so). The cited page does NOT publish the internal API values for these stages, so no internal-value field exists on the row -- a plausible internal value from training data would be fabrication, and the field is minted only when a readable API-facing citation lands. These are vocabulary facts about the upstream product; no sales-process opinion lives in this module." +// Vocabulary facts live-fetched 2026-08-01 from the HubSpot Knowledge Base page 'Use contact and +// company lifecycle stages' (the anchor): the eight default lifecycle stages in the vendor's own +// order, display labels and definition sentences verbatim. The stages apply to both contact and +// company records upstream (the page title says so). The cited page does NOT publish the internal +// API values for these stages, so no internal-value field exists on the row -- a plausible internal +// value from training data would be fabrication, and the field is minted only when a readable +// API-facing citation lands. These are vocabulary facts about the upstream product; no +// sales-process opinion lives in this module. type HubspotLifecycleStage = HubspotSubscriber diff --git a/dag/extdeps/crm/salesforce_opportunity.dag b/dag/extdeps/crm/salesforce_opportunity.dag index 0ccc6ab6cd5..715fcd92c39 100644 --- a/dag/extdeps/crm/salesforce_opportunity.dag +++ b/dag/extdeps/crm/salesforce_opportunity.dag @@ -20,7 +20,12 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { data salesforce_opportunity_subject: Vendor = salesforce -data salesforce_opportunity_provenance_note: String = "Vocabulary facts live-fetched 2026-08-01 from the Salesforce Platform object reference (en-us, API version 252.0, read through the docs content endpoint get_document_content because the doc site shell is client-rendered): the Opportunity object page for StageName and the OpportunityStage object page (the anchor) for the stage row schema and the ForecastCategory value set. These are vocabulary facts about the upstream product's published surface -- no sales-process opinion lives in this module." +// Vocabulary facts live-fetched 2026-08-01 from the Salesforce Platform object reference (en-us, +// API version 252.0, read through the docs content endpoint get_document_content because the doc +// site shell is client-rendered): the Opportunity object page for StageName and the +// OpportunityStage object page (the anchor) for the stage row schema and the ForecastCategory value +// set. These are vocabulary facts about the upstream product's published surface -- no +// sales-process opinion lives in this module. // The load-bearing upstream fact: StageName is a required picklist on Opportunity that the reference says controls ForecastCategoryName, IsClosed, IsWon, and Probability, and the reference directs, quoted: 'To obtain the stage name values in the picklist, query the OpportunityStage object.' The stage VOCABULARY is therefore org-configurable DATA in the vendor's own model, not a published universal roster -- what Salesforce publishes as vocabulary is the stage row SCHEMA and the closed ForecastCategory value set, both modeled here under their real names. OpportunityStage is read-only via the API upstream. type SalesforceForecastCategory diff --git a/dag/extdeps/crypto/hash.dag b/dag/extdeps/crypto/hash.dag index f7b568b6668..09560c805de 100644 --- a/dag/extdeps/crypto/hash.dag +++ b/dag/extdeps/crypto/hash.dag @@ -50,7 +50,14 @@ fn digest_shell_verify_line(digest: Digest, file_path: NonEmptyStr) -> String { } } -data sha256_digest_content_hash_convergence_note: String = "THE BRIDGE from extdeps.crypto.hash.Digest to std.content_hash.ContentHash for artifact pins — NOT a merge with std.content_hash's fnv1a64 structural fingerprint (dag/std/content_hash.dag, dag/extdeps/provisioning/ubuntu_install_media_fetch.dag install_media_fetch_hash_convergence_note). Digest is a cited cryptographic SHA-256 authority value; ContentHash is the family-grounded coproduct carrier (Sha256Hash arm for this bridge). sha256_digest_content_hash validates hex via sha256_hex_digest then mints through as_content_hash_cryptographic — construction-walled, refuses invalid hex. Wire serialization (sha256:) is std.content_hash.serialize_content_hash, not a second rendering rule here." +// THE BRIDGE from extdeps.crypto.hash.Digest to std.content_hash.ContentHash for artifact pins — +// NOT a merge with std.content_hash's fnv1a64 structural fingerprint (dag/std/content_hash.dag, +// dag/extdeps/provisioning/ubuntu_install_media_fetch.dag +// install_media_fetch_hash_convergence_note). Digest is a cited cryptographic SHA-256 authority +// value; ContentHash is the family-grounded coproduct carrier (Sha256Hash arm for this bridge). +// sha256_digest_content_hash validates hex via sha256_hex_digest then mints through +// as_content_hash_cryptographic — construction-walled, refuses invalid hex. Wire serialization +// (sha256:) is std.content_hash.serialize_content_hash, not a second rendering rule here. fn sha256_digest_content_hash(digest: Digest) -> ContentHash? { match digest.algorithm { diff --git a/dag/extdeps/darwin/rusage.dag b/dag/extdeps/darwin/rusage.dag index b3598f6a9f0..7d89a650c66 100644 --- a/dag/extdeps/darwin/rusage.dag +++ b/dag/extdeps/darwin/rusage.dag @@ -13,7 +13,18 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data darwin_maxrss_unit_note: String = "Darwin answers the unit POSIX leaves open differently from Linux: ru_maxrss is the maximum resident set size in BYTES, inherited from the BSD lineage. CORROBORATED BY EXECUTION on the operator's macOS 15 machine rather than read off the manual alone (2026-08-05): getrusage(RUSAGE_SELF).ru_maxrss returned 15417344 for a bare python3 interpreter. Read as bytes that is 14.7 MiB, which is an ordinary interpreter footprint; read as kibibytes it would be 14.7 GiB, which is absurd on a 16 GiB machine and would have been caught by nothing. The discriminating check was the magnitude, and it is recorded because the two readings differ by 1024x in the direction that looks survivable.\n\nWHY THIS MATTERS BEYOND TIDINESS: the same divergence is what makes a single hardwired transport unsafe. A portable getrusage call that assumed one unit would silently mis-scale every peak-residency fact on half the fleet — and peak residency is exactly the input the memory governor sizes admission against." +// Darwin answers the unit POSIX leaves open differently from Linux: ru_maxrss is the maximum +// resident set size in BYTES, from the BSD lineage. CORROBORATED BY EXECUTION on the operator's +// macOS 15 machine, not read off the manual alone (2026-08-05): getrusage(RUSAGE_SELF).ru_maxrss +// returned 15417344 for a bare python3 interpreter. As bytes that is 14.7 MiB, an ordinary +// interpreter footprint; as kibibytes it would be 14.7 GiB, absurd on a 16 GiB machine and caught +// by nothing. The discriminating check was the magnitude, recorded because the two readings differ +// by 1024x in the direction that looks survivable. +// +// WHY THIS MATTERS BEYOND TIDINESS: the same divergence makes a single hardwired transport unsafe. +// A portable getrusage call assuming one unit would silently mis-scale every peak-residency fact on +// half the fleet — and peak residency is exactly the input the memory governor sizes admission +// against. data darwin_rusage_maxrss_unit: RusageMaxrssUnit = MaxrssBytes diff --git a/dag/extdeps/darwin/sysctl.dag b/dag/extdeps/darwin/sysctl.dag index 42fe87aead7..c6ed1607496 100644 --- a/dag/extdeps/darwin/sysctl.dag +++ b/dag/extdeps/darwin/sysctl.dag @@ -13,7 +13,27 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data darwin_sysctl_subject_note: String = "sysctl IS DARWIN'S ANSWER TO /proc/meminfo, and modeling it is what lets the memory governor stop treating macOS as a platform with no memory facts. The corpus previously reached a terminal arm on any host without /proc and reported having read a file that does not exist there; the honest repair is not a better label for the absence, it is to read the interface the platform actually provides.\n\nWHAT THIS MODULE OWNS: the named MIB entries this repository reads and their units. hw.memsize is total physical RAM in BYTES — note the contrast with /proc/meminfo, whose fields are kibibytes, so a consumer that shared one parser across both would be wrong by 1024x in the same direction as the ru_maxrss trap one module over. hw.pagesize is the VM page size in bytes, required to turn Mach page counts into byte quantities.\n\nOBSERVED LIVE on the operator's machine, 2026-08-05: hw.memsize = 17179869184 (exactly 16 GiB) and hw.pagesize = 16384 (Apple silicon's 16 KiB pages, not the 4096 an x86 habit would assume — which is precisely the constant a hand-written page-count conversion gets wrong).\n\nWHAT THIS MODULE DOES NOT OWN: an available-memory figure. Darwin exposes no single MemAvailable equivalent; the nearest construction sums free, inactive and speculative page counts from Mach VM statistics, and whether that sum is the right analogue is a judgement this module declines to make silently. Total physical RAM is a fact; a derived availability estimate would be a model, and it belongs where its assumptions can be stated." +// sysctl IS DARWIN'S ANSWER TO /proc/meminfo, and modeling it is what lets the memory governor stop +// treating macOS as a platform with no memory facts. The corpus previously reached a terminal arm +// on any host without /proc and reported having read a file that does not exist there; the honest +// repair is not a better label for the absence, it is to read the interface the platform actually +// provides. +// +// WHAT THIS MODULE OWNS: the named MIB entries this repository reads and their units. hw.memsize is +// total physical RAM in BYTES — note the contrast with /proc/meminfo, whose fields are kibibytes, +// so a consumer that shared one parser across both would be wrong by 1024x in the same direction as +// the ru_maxrss trap one module over. hw.pagesize is the VM page size in bytes, required to turn +// Mach page counts into byte quantities. +// +// OBSERVED LIVE on the operator's machine, 2026-08-05: hw.memsize = 17179869184 (exactly 16 GiB) +// and hw.pagesize = 16384 (Apple silicon's 16 KiB pages, not the 4096 an x86 habit would assume — +// which is precisely the constant a hand-written page-count conversion gets wrong). +// +// WHAT THIS MODULE DOES NOT OWN: an available-memory figure. Darwin exposes no single MemAvailable +// equivalent; the nearest construction sums free, inactive and speculative page counts from Mach VM +// statistics, and whether that sum is the right analogue is a judgement this module declines to +// make silently. Total physical RAM is a fact; a derived availability estimate would be a model, +// and it belongs where its assumptions can be stated. type DarwinSysctlMib = HwMemsize diff --git a/dag/extdeps/dhcp/v4.dag b/dag/extdeps/dhcp/v4.dag index 661d8a80234..f01a5e45f25 100644 --- a/dag/extdeps/dhcp/v4.dag +++ b/dag/extdeps/dhcp/v4.dag @@ -56,7 +56,13 @@ data mac_address_anemic_brand_frontier_rows: List = [ ) ] -data ipv4_address_brand_dissolved_note: String = "The Ipv4Address anemic brand (NonEmptyStr where brand) was dissolved 2026-07-24: it is now the structured four-octet carrier in extdeps.network.ipv4 (dissolve_on_ipv4_address_anemic_brand fired — its named trigger, extdeps/network/ipv4.dag + parse_ipv4_address witness, landed). Consumers import Ipv4Address from extdeps.network.ipv4 directly (ipv4 is a network concept, not a DHCP one — the prior re-home through this module was a layering artifact). The MacAddress brand above remains anemic; its dissolution (extdeps/network/mac.dag, hex-octet parse) is the fast-follow — same consumer files, deferred to keep the IPv4 reach-lane change focused." +// The Ipv4Address anemic brand (NonEmptyStr where brand) was dissolved 2026-07-24: it is now the +// structured four-octet carrier in extdeps.network.ipv4 (dissolve_on_ipv4_address_anemic_brand +// fired — its named trigger, extdeps/network/ipv4.dag + parse_ipv4_address witness, landed). +// Consumers import Ipv4Address from extdeps.network.ipv4 directly (ipv4 is a network concept, not a +// DHCP one — the prior re-home through this module was a layering artifact). The MacAddress brand +// above remains anemic; its dissolution (extdeps/network/mac.dag, hex-octet parse) is the +// fast-follow — same consumer files, deferred to keep the IPv4 reach-lane change focused. type DhcpClientHostNameOption12 = HostName diff --git a/dag/extdeps/exec/command.dag b/dag/extdeps/exec/command.dag index 570ba79d815..1903d8011ca 100644 --- a/dag/extdeps/exec/command.dag +++ b/dag/extdeps/exec/command.dag @@ -119,7 +119,13 @@ fn argv_words(command: ArgvCommand) -> List { append([command.program as String], items: command.arguments) } -data ssh_exec_interpretation_note: String = "SshExec now carries the target's RemoteExecCommandInterpretation as a field, not an inferred fact: DESIGN §5 construction-over-validation -- whether the far side hands the RFC 4254 command string to a POSIX shell is a per-deployment implementation fact (gunbc.remote_shell_command remote_exec_command_interpretation_note), so the caller who knows which deployment it is targeting must declare it rather than command_over_transport guessing from the target string. A target this module cannot classify names Unknown explicitly; command_over_transport then refuses rather than picking a default (operator directive, 2026-08-19)." +// SshExec now carries the target's RemoteExecCommandInterpretation as a field, not an inferred +// fact: DESIGN §5 construction-over-validation -- whether the far side hands the RFC 4254 command +// string to a POSIX shell is a per-deployment implementation fact (gunbc.remote_shell_command +// remote_exec_command_interpretation_note), so the caller who knows which deployment it is +// targeting must declare it rather than command_over_transport guessing from the target string. A +// target this module cannot classify names Unknown explicitly; command_over_transport then refuses +// rather than picking a default (operator directive, 2026-08-19). type CommandTransport = LocalExec @@ -132,7 +138,14 @@ data sshpass_authority: ExternalAuthority = ExternalAuthority { } } -data ssh_host_key_verification_posture: String = "🟡 dissolve-on: ssh_host_key_override_opts — DECLARED SECURITY POSTURE, not an accident of argv building: StrictHostKeyChecking=no + UserKnownHostsFile=/dev/null disables SSH host-key verification for these fleet/BMC transports, so a LAN-position MITM would not be detected; accepted interim because the targets are LAN-scoped fleet members and BMCs whose host keys are not yet provisioned facts. sshpass (its own upstream, anchor above) feeds the password via -e (SSHPASS env), never argv. DISSOLVES WHEN known-hosts provisioning lands on gunbc.fleet_ssh_access (pinned host key per fleet member) and these options drop both overrides in favor of a pinned UserKnownHostsFile." +// 🟡 dissolve-on: ssh_host_key_override_opts — DECLARED SECURITY POSTURE, not an accident of argv +// building: StrictHostKeyChecking=no + UserKnownHostsFile=/dev/null disables SSH host-key +// verification for these fleet/BMC transports, so a LAN-position MITM would not be detected; +// accepted interim because the targets are LAN-scoped fleet members and BMCs whose host keys are +// not yet provisioned facts. sshpass (its own upstream, anchor above) feeds the password via -e +// (SSHPASS env), never argv. DISSOLVES WHEN known-hosts provisioning lands on +// gunbc.fleet_ssh_access (pinned host key per fleet member) and these options drop both overrides +// in favor of a pinned UserKnownHostsFile. // The transport's own program, split from its flags for the same reason ArgvCommand splits them: // sshpass is what gets executed, `-e` is what it is told. @@ -181,7 +194,13 @@ fn command_over_transport(command: ArgvCommand, transport: CommandTransport) -> } } -data shell_quote_provenance_note: String = "shell_quote delegates to extdeps.posix.shell_command_language posix_single_quote (operator finding, 2026-08-19): this was an independently hand-rolled THIRD copy of the single-quote wrap-and-escape encoding already consolidated once from v2.extdeps.languages.bash into the POSIX authority, and cited there against IEEE 1003.1-2017 section 2.2.2 -- split(s, delimiter: \"'\") then join with \"'\\''\" is the same operation as replace(s, \"'\", \"'\\''\"), so this is a pure internal-implementation swap: byte-identical output, unchanged signature, no diff for any of shell_command_render's 13+ existing consumers. DESIGN §2/§3: one escaping fact, one authority." +// shell_quote delegates to extdeps.posix.shell_command_language posix_single_quote (operator +// finding, 2026-08-19): this was an independently hand-rolled THIRD copy of the single-quote +// wrap-and-escape encoding already consolidated once from v2.extdeps.languages.bash into the POSIX +// authority, and cited there against IEEE 1003.1-2017 section 2.2.2 -- split(s, delimiter: "'") +// then join with "'\''" is the same operation as replace(s, "'", "'\''"), so this is a pure +// internal-implementation swap: byte-identical output, unchanged signature, no diff for any of +// shell_command_render's 13+ existing consumers. DESIGN §2/§3: one escaping fact, one authority. fn shell_quote(arg: String) -> String { posix_single_quote(spelling: arg) @@ -193,7 +212,13 @@ fn shell_command_render(command: ArgvCommand) -> String { data program_spelling_is_an_observation_claim: String = "WHICH SPELLING OF A PROGRAM A BUILDER CARRIES IS A CLAIM, AND THE TWO FORMS ANSWER DIFFERENT QUESTIONS (still-seal-394, 2026-08-22 -- recorded here once rather than repeated at every row it governs). An ABSOLUTE PATH is a claim that this repository has OBSERVED the target's filesystem, and where that claim is true it is the STRONGER form and the one to prefer: it is what a sudoers NOPASSWD rule can name and what a preflight can test for. extdeps.tools.mkdir, extdeps.tools.chmod, extdeps.tools.stat, extdeps.tools.id, extdeps.tools.chown and extdeps.sudo.elevation carry absolute paths for exactly that reason, each with a consumer that needs the path. PATH RESOLUTION is correct where the host is UNOBSERVED -- a BMC, a fleet member, a runner image nobody has read -- because there an absolute path fabricates a host fact (DESIGN section 5), and it is what the hand-spelled call sites this seal converted were already doing. THE RULE IS NOT 'BARE IS THE SAFE DEFAULT', and an earlier revision of this lane got that backwards in both directions within one afternoon: nine new rows were first written with invented absolute paths on the reasoning that a bare name declines to say which binary, then reverted wholesale on the reasoning that PATH is safer. Neither is the rule. Each row states which question it answers, and a row is only as good as the observation behind it. THE COUNTER-EXAMPLE THAT MAKES THIS LOAD-BEARING, and it is why a wrong absolute path is not merely cosmetic: gunbc.roadmap_dashboard_instance_apply carried /usr/bin/chmod both as the program its install step execs and in the required-executable roster its preflight tests, while extdeps.tools.chmod records /bin/chmod for the same fleet -- so the preflight gated a path no sanctioned caller runs, and passed only because those hosts are usrmerged. NEXT-RUNG TRIGGER for every PATH-resolved row this lane landed: a real filesystem observation of the target -- a preflight receipt or a provisioning fact naming the binary -- at which point that row becomes an absolute path with the observation as its citation, and the class climbs from 'we did not look' to 'we looked and this is where it is'." -data argv_builder_note: String = "Typed ArgvCommand builders (§2): each derives the argv for one tool operation so the opaque flags (dash-f, dash-p, --strip-components, ...) live in ONE place, not hand-spelled per call site. A caller names the operation (curl_download_command, tar_extract_command, ...); the flags are the derived realization, not a magic string the caller must remember. THE BUILDERS THEMSELVES ARE NOT HERE: each is homed in its tool's own extdeps module beside that tool's cited upstream authority, and admitted by name on argv_command above, because a generic hub may not enumerate concrete products (DESIGN §3). This module owns the SHAPE and the seal; the products own their flags." +// Typed ArgvCommand builders (§2): each derives the argv for one tool operation so the opaque flags +// (dash-f, dash-p, --strip-components, ...) live in ONE place, not hand-spelled per call site. A +// caller names the operation (curl_download_command, tar_extract_command, ...); the flags are the +// derived realization, not a magic string the caller must remember. THE BUILDERS THEMSELVES ARE NOT +// HERE: each is homed in its tool's own extdeps module beside that tool's cited upstream authority, +// and admitted by name on argv_command above, because a generic hub may not enumerate concrete +// products (DESIGN §3). This module owns the SHAPE and the seal; the products own their flags. // THE ENV-PREFIXED INVOCATION LIVES HERE, IN THE CARRIER'S OWN MODULE, AND THE DEPENDENCY RUNS ONE // WAY ONLY. env(1) is modeled at extdeps.tools.env -- its binary path, its NAME=VALUE and -u diff --git a/dag/extdeps/exec/exec_arg_limit.dag b/dag/extdeps/exec/exec_arg_limit.dag index 4ee1faf19a2..d80b91c36d1 100644 --- a/dag/extdeps/exec/exec_arg_limit.dag +++ b/dag/extdeps/exec/exec_arg_limit.dag @@ -20,9 +20,20 @@ fn host_exec_arg_max_strlen_from_page(page: ByteSize, page_factor: Nat) -> ByteS byte_size(count: byte_size_count(page) * page_factor) } +// Linux execve(2) per-argument ceiling: MAX_ARG_STRLEN = linux_execve_max_arg_strlen_page_factor +// (32) * PAGE_SIZE. host_exec_arg_max_strlen is DERIVED from +// extdeps.runtime.architecture.x86_64_linux.page_size — not an independent 131072 literal. A single +// argv or envp string longer than this makes execve fail with E2BIG ("Argument list too long") — +// the opaque os error 7 the srv1 deploy hit when a >128KB script was embedded in one sh -c argv +// element. Distinct from ARG_MAX (~2 MiB total argv+envp; getconf ARG_MAX) — not modeled here +// (using MAX_ARG_STRLEN as ARG_MAX would be an over-approximation, §5). Consumer seam: +// extdeps.shell shell.Exec.Check embeds the command input as one argv token (sh -c); shell.Exec.Run +// routes payload via stdin (sh -s) and does not hit this ceiling. The v1 interpreter shell dispatch +// (dispatch_shell) realizes the wall: refuse with InterpError::ArgvExceedsHostArgMax before spawn. +// Part A (route deploy scripts through Run/stdin) is #6711; this authority is the general +// host-boundary guard for every remaining argv-embedding site. + data host_exec_arg_max_strlen: ByteSize = host_exec_arg_max_strlen_from_page( page: x86_64_linux_architecture_profile.page_size, page_factor: linux_execve_max_arg_strlen_page_factor, ) - -data host_exec_arg_max_strlen_note: String = "Linux execve(2) per-argument ceiling: MAX_ARG_STRLEN = linux_execve_max_arg_strlen_page_factor (32) * PAGE_SIZE. host_exec_arg_max_strlen is DERIVED from extdeps.runtime.architecture.x86_64_linux.page_size — not an independent 131072 literal. A single argv or envp string longer than this makes execve fail with E2BIG (\"Argument list too long\") — the opaque os error 7 the srv1 deploy hit when a >128KB script was embedded in one sh -c argv element. Distinct from ARG_MAX (~2 MiB total argv+envp; getconf ARG_MAX) — not modeled here (using MAX_ARG_STRLEN as ARG_MAX would be an over-approximation, §5). Consumer seam: extdeps.shell shell.Exec.Check embeds the command input as one argv token (sh -c); shell.Exec.Run routes payload via stdin (sh -s) and does not hit this ceiling. The v1 interpreter shell dispatch (dispatch_shell) realizes the wall: refuse with InterpError::ArgvExceedsHostArgMax before spawn. Part A (route deploy scripts through Run/stdin) is #6711; this authority is the general host-boundary guard for every remaining argv-embedding site." diff --git a/dag/extdeps/exec/xargs.dag b/dag/extdeps/exec/xargs.dag index 7dc22a46f2b..d5548e3ec72 100644 --- a/dag/extdeps/exec/xargs.dag +++ b/dag/extdeps/exec/xargs.dag @@ -18,9 +18,27 @@ data xargs_null_delimited_external_authority_anchor: ExternalAuthority = Externa } } -data xargs_null_delimited_fidelity_note: String = "TWO AUTHORITIES BECAUSE `-0` IS NOT POSIX. The POSIX xargs utility (anchor above) reads a whitespace-delimited, quote-honouring argument stream and defines no null-delimited mode; `-0` / `--null` is a GNU findutils extension (second anchor, xargs(1)), also present in BSD xargs. Declaring only the POSIX anchor would cite an authority that does not contain the option this module spells, so both are named and the extension is attributed to the one that defines it (DESIGN §3 — keep the real upstream's names and say which spec a flag comes from).\n\nWHY NULL DELIMITING IS THE ONLY MODE MODELED HERE. Under the default delimiting rules a path containing a space, a tab, a newline, or a quote character is split or de-quoted into the wrong arguments — silently, producing operations against paths that were never in the input set. That is the fabricated-plausible-output arm §5 forbids, so the whitespace mode is deliberately absent rather than offered and warned about: the producer side must emit NUL-separated records (git's `-z`, find's `-print0`) and this shape consumes exactly that. A caller wanting the POSIX default mode is asking for the unsafe reading and should be refused, not accommodated." +// TWO AUTHORITIES BECAUSE `-0` IS NOT POSIX. POSIX xargs (anchor above) reads a +// whitespace-delimited, quote-honouring argument stream and defines no null-delimited mode; `-0` / +// `--null` is a GNU findutils extension (second anchor, xargs(1)), also present in BSD xargs. +// Citing only POSIX would name an authority that lacks the option this module spells, so both are +// named and the extension attributed to the spec defining it (DESIGN §3 — keep the real upstream's +// names and say which spec a flag comes from). +// +// WHY NULL DELIMITING IS THE ONLY MODE MODELED HERE. Under default delimiting a path containing a +// space, tab, newline, or quote is silently split or de-quoted into wrong arguments, producing +// operations against paths never in the input set — the fabricated-plausible-output arm §5 +// forbids. So the whitespace mode is absent, not offered and warned about: the producer must emit +// NUL-separated records (git's `-z`, find's `-print0`) and this shape consumes exactly that. A +// caller wanting the POSIX default mode asks for the unsafe reading and is refused, not +// accommodated. -// WHAT XARGS IS FOR, AND WHY IT LIVES BESIDE exec_arg_limit. xargs exists because execve(2) bounds how much argv a single spawn may carry: it batches a long input stream into as many invocations of the sink command as the limit requires. extdeps.exec.exec_arg_limit models the per-argument ceiling (MAX_ARG_STRLEN) of that same boundary, so the two are facts about one host interface and share this folder. This module does NOT re-derive a batch size: xargs computes its own from the live limit, and a second copy here would be a §3 fork of a number the tool already owns — the reason the shape below carries no `-s` / `-n` operand. +// WHAT XARGS IS FOR, AND WHY IT LIVES BESIDE exec_arg_limit. execve(2) bounds how much argv one +// spawn may carry; xargs batches a long input stream into as many sink invocations as the limit +// requires. extdeps.exec.exec_arg_limit models the per-argument ceiling (MAX_ARG_STRLEN) of that +// same boundary, so both are facts about one host interface and share this folder. This module +// does NOT re-derive a batch size: xargs computes its own from the live limit, and a copy here +// would be a §3 fork of a number the tool owns — hence no `-s` / `-n` operand below. fn xargs_null_delimited_args() -> List { ["xargs", "-0"] } diff --git a/dag/extdeps/external_authority.dag b/dag/extdeps/external_authority.dag index faa18dec22a..c3a93c8a3f4 100644 --- a/dag/extdeps/external_authority.dag +++ b/dag/extdeps/external_authority.dag @@ -17,7 +17,13 @@ type ExternalSubjectRef { declaration: DeclarationRef } -data external_model_scope_revision_grain_note: String = "ExternalModelScope deliberately carries NO revision field (operator verdict on gunbc#7571): a subject module legitimately models multiple releases of one upstream (Chromium 151/152/153 are one subject, not one module each), so an exact revision is a fact on the specific build/release/fact row, symbolically referenced where needed — never a property of the module scope. An earlier revision carried 'revision: ExternalRevision?' with a free label; both the optional module-grain slot and the label-typed revision (which erased semver vs commit vs release vs specification-edition distinctions) were removed." +// ExternalModelScope deliberately carries NO revision field (operator verdict on gunbc#7571): a +// subject module legitimately models multiple releases of one upstream (Chromium 151/152/153 are +// one subject, not one module each), so an exact revision is a fact on the specific +// build/release/fact row, symbolically referenced where needed — never a property of the module +// scope. An earlier revision carried 'revision: ExternalRevision?' with a free label; both the +// optional module-grain slot and the label-typed revision (which erased semver vs commit vs release +// vs specification-edition distinctions) were removed. type ExternalModelScope { subject: ExternalSubjectRef @@ -25,7 +31,10 @@ type ExternalModelScope { further_citations: List } -data external_model_scope_citations_shape_note: String = "citations is nonempty BY CONSTRUCTION (first_citation a required scalar + further_citations), the gunbc.doc_graph_roots primary_work/additional_works precedent — the operator sketch wrote NonEmptyList; no such generic carrier exists in std today, and the split-field shape makes the zero-citation scope unwritable rather than checked (DESIGN §5)." +// citations is nonempty BY CONSTRUCTION (first_citation a required scalar + further_citations), the +// gunbc.doc_graph_roots primary_work/additional_works precedent — the operator sketch wrote +// NonEmptyList; no such generic carrier exists in std today, and the split-field +// shape makes the zero-citation scope unwritable rather than checked (DESIGN §5). fn external_model_scope_citations(scope: ExternalModelScope) -> List { concat([scope.first_citation], scope.further_citations) @@ -35,7 +44,15 @@ fn external_subject_ref_eq(a: ExternalSubjectRef, b: ExternalSubjectRef) -> Bool declaration_ref_eq(a: a.declaration, b: b.declaration) } -data fact_citation_note: String = "FactCitation is the ONE carrier for 'this modeled fact is attested by that external authority' (operator verdicts on gunbc#7556 and gunbc#7571, the §3 cite-the-symbol rule). It replaces three forks that were the same fact under three names: FactAuthorityOverride, and extdeps.cpu.types' byte-identical SocketFactCitation / CacheGeometryCitation pair — whose 'field: NonEmptyStr' was a POSITIONAL naming scheme for something the containment tree already names, so a renamed field left the citation green over a name that no longer existed, and a GPU module had to import a CPU type to cite a cache fact. The cited fact is a DeclarationRef: module + declaration + field in the containment tree's own key space, so a stale citation is decidable by symbol resolution rather than rotting as free text." +// FactCitation is the ONE carrier for 'this modeled fact is attested by that external authority' +// (operator verdicts on gunbc#7556 and gunbc#7571, the §3 cite-the-symbol rule). It replaces three +// forks that were the same fact under three names: FactAuthorityOverride, and extdeps.cpu.types' +// byte-identical SocketFactCitation / CacheGeometryCitation pair — whose 'field: NonEmptyStr' was a +// POSITIONAL naming scheme for something the containment tree already names, so a renamed field +// left the citation green over a name that no longer existed, and a GPU module had to import a CPU +// type to cite a cache fact. The cited fact is a DeclarationRef: module + declaration + field in +// the containment tree's own key space, so a stale citation is decidable by symbol resolution +// rather than rotting as free text. type FactCitation { fact: DeclarationRef @@ -44,7 +61,10 @@ type FactCitation { data external_model_scope_decision_kernel_note: String = "HONESTY BOUNDARY (operator verdict on gunbc#7571, 2026-08-01): external_model_scope_decision is a pure DECISION KERNEL over DECLARED fact rows — it is NOT an admission wall, because its inputs (DeclaredScopeFacts) are caller-authored: a module whose declarations carry three subjects admits if the caller declares one. What IS mechanically enforced today is scope PRESENCE at the storage grain (gunbc.extdeps_scope_frontier's manifest + live cover witness). Subject-content coherence — that a module's actual declarations attribute to exactly its declared subject — is the named enforcement frontier feature:extdeps-subject-content-derived: it dissolves when a Node-tree projection derives DeclaredScopeFacts from the real module tree (the module's extdeps_model_scope declaration, the subject attribution of every product-/engine-/distribution-specific declaration, and any consumer-coverage declarations actually present) and feeds THIS kernel, at which point the kernel's verdict becomes an admission fact rather than a coherence check of what a caller chose to say. The RED/GREEN controls below the kernel exercise the decision shape the projection will feed; they are controls of the kernel, not proof of a wall." -data declared_scope_facts_note: String = "DeclaredScopeFacts is one extdeps module's caller-declared scope facts: its declared scope, the per-row subject attribution of the product-/engine-/distribution-specific fact rows it carries, and the consumer-coverage/observation-state declarations it stores (each a DeclarationRef — coverage identity is symbolic for the same reason subject identity is)." +// DeclaredScopeFacts is one extdeps module's caller-declared scope facts: its declared scope, the +// per-row subject attribution of the product-/engine-/distribution-specific fact rows it carries, +// and the consumer-coverage/observation-state declarations it stores (each a DeclarationRef — +// coverage identity is symbolic for the same reason subject identity is). type DeclaredScopeFacts { module: NonEmptyStr @@ -110,7 +130,11 @@ fn external_model_scope_portfolio_decision(mods: List) -> Sc ) } -data downstream_support_roster_note: String = "Where the refused rows GO: a support/coverage roster is a downstream (product/workflow layer) fact join over the per-subject upstream modules — DESIGN §3's gunbc.served_surface_browser_support example. It is deliberately NOT judged by external_model_scope_decision: consumer policy and coverage state are exactly what an upstream module may not carry and a downstream module exists to carry." +// Where the refused rows GO: a support/coverage roster is a downstream (product/workflow layer) +// fact join over the per-subject upstream modules — DESIGN §3's +// gunbc.served_surface_browser_support example. It is deliberately NOT judged by +// external_model_scope_decision: consumer policy and coverage state are exactly what an upstream +// module may not carry and a downstream module exists to carry. type DownstreamSupportRoster { module: NonEmptyStr diff --git a/dag/extdeps/filesystem/filesystem_io.dag b/dag/extdeps/filesystem/filesystem_io.dag index 5d091879f57..f4f9cb99490 100644 --- a/dag/extdeps/filesystem/filesystem_io.dag +++ b/dag/extdeps/filesystem/filesystem_io.dag @@ -1,6 +1,5 @@ module extdeps.filesystem.filesystem_io - import extdeps.external_authority { ExternalAuthority } import extdeps.uri { Uri, Https } import std.types { Bool, Int, List, String } @@ -11,7 +10,8 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data write_owner_only_realization_note: String = "v1 realization: Unix OpenOptions create_new + mode 0600 at creation (O_EXCL); non-Unix hosts refuse rather than degrading to unmodeled write." +// v1 realization: Unix OpenOptions create_new + mode 0600 at creation (O_EXCL); non-Unix hosts +// refuse rather than degrading to unmodeled write. // The operation below carries the transport observation as independent channels: the read // either supplied content or supplied an error, but the wire itself does not make those states @@ -82,7 +82,24 @@ fn filesystem_listing_names_entry(listing: String, name: String) -> Bool { // that section's open item and not this module's to close. And it is a wall over the consumers that // ROUTE THROUGH IT, not over the corpus: the raw operations remain callable, so adoption is the // separate obligation recorded in `filesystem_absence_establishment_adoption_standing`. -data filesystem_absence_establishment_wall_receipt: String = "THE WALL ABOVE IS MEASURED RATHER THAN ASSERTED, and it is recorded here because no enrolled witness can hold it: the claim is about what the COMPILER REFUSES, its subject is source text rather than a value, and a hermetic witness that asserted it would be a permanently-green arm over a state it cannot express -- the decoration section 4b calls worse than absent. MEASURED BY EXECUTION: a fixture module importing FilesystemEstablishedAbsence and writing the record literal `FilesystemEstablishedAbsence { directory: \"/nowhere\", name: \"marker.json\" }` in a function body, resolved by `claim_batch --source-root dag --source-root src/v2 --source-root --entry `, is REFUSED at the literal's own position with `sole_constructor type 'FilesystemEstablishedAbsence' cannot be constructed outside its defining module`. THE POSITIVE CONTROL IS ENROLLED AND IS NOT A SECOND FIXTURE: test.claim.filesystem_absence_establishment_witness_test an_established_absence_names_its_directory_and_its_entry obtains an absence FROM THE MINT, cross-module, and reads its fields -- so the refusal above is the construction being confined and not the type being unusable. WHAT THIS DOES NOT ESTABLISH: the mechanism's general coverage, which is gunbc.guarantee_probe_corpus sole_constructor_cross_module_class and its probes -- DESIGN records that family as discovered and declined by the floor, so this measurement is one execution of a wall whose corpus-wide arms nothing currently re-runs. It is the fixture-boundary red section 4b asks for, taken once, not a standing gate." +// THE WALL ABOVE IS MEASURED RATHER THAN ASSERTED, and it is recorded here because no enrolled +// witness can hold it: the claim is about what the COMPILER REFUSES, its subject is source text +// rather than a value, and a hermetic witness that asserted it would be a permanently-green arm +// over a state it cannot express -- the decoration section 4b calls worse than absent. MEASURED BY +// EXECUTION: a fixture module importing FilesystemEstablishedAbsence and writing the record literal +// `FilesystemEstablishedAbsence { directory: "/nowhere", name: "marker.json" }` in a function body, +// resolved by `claim_batch --source-root dag --source-root src/v2 --source-root +// --entry `, is REFUSED at the literal's own position with `sole_constructor type +// 'FilesystemEstablishedAbsence' cannot be constructed outside its defining module`. THE POSITIVE +// CONTROL IS ENROLLED AND IS NOT A SECOND FIXTURE: +// test.claim.filesystem_absence_establishment_witness_test +// an_established_absence_names_its_directory_and_its_entry obtains an absence FROM THE MINT, +// cross-module, and reads its fields -- so the refusal above is the construction being confined and +// not the type being unusable. WHAT THIS DOES NOT ESTABLISH: the mechanism's general coverage, +// which is gunbc.guarantee_probe_corpus sole_constructor_cross_module_class and its probes -- +// DESIGN records that family as discovered and declined by the floor, so this measurement is one +// execution of a wall whose corpus-wide arms nothing currently re-runs. It is the fixture-boundary +// red section 4b asks for, taken once, not a standing gate. type FilesystemDirectoryListing sole_constructor { directory: String diff --git a/dag/extdeps/filesystem/linux.dag b/dag/extdeps/filesystem/linux.dag index 11eb62ac5f9..ca1bd00fbed 100644 --- a/dag/extdeps/filesystem/linux.dag +++ b/dag/extdeps/filesystem/linux.dag @@ -148,15 +148,15 @@ fn linux_proc_mount_unescape(field: String) -> String { } // THE ELIMINATION `first()` OWES. `first` answers `NonEmptyStr?` because a list may be empty, and -// the six sites below reached the value with `as String`, which does NOT eliminate the absence -- it -// re-types the wrapper, so a short row would have rendered `Present { value: /dev/sda }` (or the bare -// word `Absent`) INTO the parsed mount row. The compiler now refuses that cast outright -// (`OptionalCastNotEliminated`), and this is the elimination it demands. The Absent arm is the empty -// string on purpose rather than a fresh refusal variant: every consumer of a field already refuses on -// it -- source/target/mount_type through `LinuxProcMountEmptyRequiredField`, the two numeric fields -// through `parse_int` returning Absent -- so absence routes into a refusal that already exists -// instead of minting a second one beside it. It is unreachable in any case: the caller has already -// refused a row whose field count is not six. +// the six sites below reached the value with `as String`, which does NOT eliminate the absence -- +// it re-types the wrapper, so a short row would have rendered `Present { value: /dev/sda }` (or the +// bare word `Absent`) INTO the parsed mount row. The compiler now refuses that cast outright +// (`OptionalCastNotEliminated`), and this is the elimination it demands. The Absent arm is the +// empty string rather than a fresh refusal variant: every consumer of a field already refuses on +// it -- source/target/mount_type through `LinuxProcMountEmptyRequiredField`, the two numeric +// fields through `parse_int` returning Absent -- so absence routes into an existing refusal +// instead of minting a second. It is unreachable in any case: the caller has already refused a +// row whose field count is not six. fn linux_proc_mount_field(fields: List, index: Int) -> String { match fields.skip(n: index).first() { Present { value: field } => field as String @@ -507,6 +507,13 @@ fn linux_overlay_observation_is_flash_backed( ) } -data linux_file_publication_semantics_note: String = "Literal mechanism facts precede any durability judgment. Linux rename replacement is atomic when replacing an existing pathname on the same filesystem; Linux sync writes pending filesystem metadata and cached file data to the underlying filesystems and waits for I/O completion. OverlayFS names the writable upper directory separately from the lower directory. Synchronized durability commit is admitted only through the LinuxSynchronizedRenameReplacePublication unit variant; partial protocol records remain explicitly unsynchronized. This module records those operations and observed mount relationships; it does not itself claim survival across a lifecycle boundary." +// Literal mechanism facts precede any durability judgment. Linux rename replacement is atomic when +// replacing an existing pathname on the same filesystem; Linux sync writes pending filesystem +// metadata and cached file data to the underlying filesystems and waits for I/O completion. +// OverlayFS names the writable upper directory separately from the lower directory. Synchronized +// durability commit is admitted only through the LinuxSynchronizedRenameReplacePublication unit +// variant; partial protocol records remain explicitly unsynchronized. This module records those +// operations and observed mount relationships; it does not itself claim survival across a lifecycle +// boundary. data linux_proc_mounts_parser_dissolve_on: DissolutionCondition = unbound_dissolution(description: "🟡 dissolve-on: a cited proc_mounts grammar row set lands in the shared grammar substrate with forward ingest and escape decoding. linux_proc_mount_row_from_line then becomes that grammar forward fold, and the same rows own any later serialization; until then this parser is the declared six-field Linux wire scaffold, fail-closed through LinuxProcMountLineRefusal rather than a second typed represent") diff --git a/dag/extdeps/firmware/megarac/subject.dag b/dag/extdeps/firmware/megarac/subject.dag index c8e363ccabf..1a4f642f1ab 100644 --- a/dag/extdeps/firmware/megarac/subject.dag +++ b/dag/extdeps/firmware/megarac/subject.dag @@ -11,7 +11,15 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data megarac_subject_note: String = "AMI MEGARAC, THE PROPRIETARY BMC FIRMWARE PRODUCT LINE, on the extdeps.firmware.openbmc.subject pattern: this module is the SUBJECT the MegaRAC-shaped facts under extdeps.bmc are about. NAMING, JOINING A RECORDED DEBT RATHER THAN MINTING A NEW ONE: extdeps.bmc.endpoint carries AmiMegaRac as an arm of the closed BmcFirmwareFamily enum, the same section-3 nickname pattern the openbmc subject module already records for its OpenBmc arm - a generic hub enumerating concrete independently governed products. That consolidation (enum arms to subject authorities) is one job across both arms and their dispatch call sites; this module extends the population of the ALREADY-DECLARED debt row by one arm instead of leaving the new arm's subject unmodeled, and nothing here gained a third spelling." +// AMI MEGARAC, THE PROPRIETARY BMC FIRMWARE PRODUCT LINE, on the extdeps.firmware.openbmc.subject +// pattern: this module is the SUBJECT the MegaRAC-shaped facts under extdeps.bmc are about. NAMING, +// JOINING A RECORDED DEBT RATHER THAN MINTING A NEW ONE: extdeps.bmc.endpoint carries AmiMegaRac as +// an arm of the closed BmcFirmwareFamily enum, the same section-3 nickname pattern the openbmc +// subject module already records for its OpenBmc arm - a generic hub enumerating concrete +// independently governed products. That consolidation (enum arms to subject authorities) is one job +// across both arms and their dispatch call sites; this module extends the population of the +// ALREADY-DECLARED debt row by one arm instead of leaving the new arm's subject unmodeled, and +// nothing here gained a third spelling. type AmiMegaRacProduct = | MegaRacStack diff --git a/dag/extdeps/firmware/openbmc/subject.dag b/dag/extdeps/firmware/openbmc/subject.dag index 031fa560fa3..070de1c65ad 100644 --- a/dag/extdeps/firmware/openbmc/subject.dag +++ b/dag/extdeps/firmware/openbmc/subject.dag @@ -12,7 +12,23 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data openbmc_subject_note: String = "OPENBMC, THE BASEBOARD MANAGEMENT FIRMWARE STACK. Fetched live 2026-08-12: the page states the project community 'is supporting an open source BMC firmware stack that will work across heterogeneous systems, including enterprise, HPC, telco, and cloud-scale data centers'. It is the management controller's own software, running on its own processor, updated on its own schedule and reachable while the host is off — which is why it is a separate role in the board's firmware contract rather than a stage of host boot. This repository already carries OpenBMC-shaped facts under extdeps.bmc; this module is the SUBJECT those facts are about. NAMING, AND A PRE-EXISTING FORK THIS MODULE DOES NOT DEEPEN: the type here is OpenBmcProject, and extdeps.bmc.endpoint (formerly std.bmc) separately carries OpenBmc as the sole arm of BmcFirmwareFamily — a closed enum arm naming the very project this module cites, which is the section 3 nickname pattern and which predates this module by a long way. The two are not two spellings of one thing in this file: this is a subject marker, in the shape every cited subject in extdeps uses, and that is an enum arm standing in for the subject that now exists. The consolidation is real work with roughly thirty-five call sites across fifteen files and it changes a content-hash atom, so it is named here as debt already in the tree rather than introduced by it, and no dissolution obligation is minted for something this module did not create. What IS fixed here is that nothing gained a third spelling." +// OPENBMC, THE BASEBOARD MANAGEMENT FIRMWARE STACK. Fetched live 2026-08-12: the page states the +// project community 'is supporting an open source BMC firmware stack that will work across +// heterogeneous systems, including enterprise, HPC, telco, and cloud-scale data centers'. It is the +// management controller's own software, running on its own processor, updated on its own schedule +// and reachable while the host is off — which is why it is a separate role in the board's firmware +// contract rather than a stage of host boot. This repository already carries OpenBMC-shaped facts +// under extdeps.bmc; this module is the SUBJECT those facts are about. NAMING, AND A PRE-EXISTING +// FORK THIS MODULE DOES NOT DEEPEN: the type here is OpenBmcProject, and extdeps.bmc.endpoint +// (formerly std.bmc) separately carries OpenBmc as the sole arm of BmcFirmwareFamily — a closed +// enum arm naming the very project this module cites, which is the section 3 nickname pattern and +// which predates this module by a long way. The two are not two spellings of one thing in this +// file: this is a subject marker, in the shape every cited subject in extdeps uses, and that is an +// enum arm standing in for the subject that now exists. The consolidation is real work with roughly +// thirty-five call sites across fifteen files and it changes a content-hash atom, so it is named +// here as debt already in the tree rather than introduced by it, and no dissolution obligation is +// minted for something this module did not create. What IS fixed here is that nothing gained a +// third spelling. type OpenBmcProject = | OpenBmcStack diff --git a/dag/extdeps/firmware/tianocore_edk2/subject.dag b/dag/extdeps/firmware/tianocore_edk2/subject.dag index 112527040be..ccc156bcf9d 100644 --- a/dag/extdeps/firmware/tianocore_edk2/subject.dag +++ b/dag/extdeps/firmware/tianocore_edk2/subject.dag @@ -12,7 +12,13 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data tianocore_edk2_subject_note: String = "TIANOCORE EDK II, THE UEFI IMPLEMENTATION. Fetched live 2026-08-12: the page describes TianoCore as 'the community supporting an open source implementation of the Unified Extensible Firmware Interface (UEFI)' and EDK II as 'a modern, feature-rich, cross-platform firmware development environment for the UEFI and UEFI Platform Initialization (PI) specifications'. THE DISTINCTION THAT EARNS THE SEPARATE MODULE: UEFI is a specification and EDK II is an implementation of it, which is the same split this repository already draws between the SPICE medium and ngspice, and between a standard and a browser engine. A board that says 'host UEFI firmware' is naming a role that an implementation fills, not the specification." +// TIANOCORE EDK II, THE UEFI IMPLEMENTATION. Fetched live 2026-08-12: the page describes TianoCore +// as 'the community supporting an open source implementation of the Unified Extensible Firmware +// Interface (UEFI)' and EDK II as 'a modern, feature-rich, cross-platform firmware development +// environment for the UEFI and UEFI Platform Initialization (PI) specifications'. THE DISTINCTION +// THAT EARNS THE SEPARATE MODULE: UEFI is a specification, EDK II an implementation -- the split +// already drawn between the SPICE medium and ngspice, and between a standard and a browser engine. +// A board saying 'host UEFI firmware' names a role an implementation fills, not the specification. type TianoCoreEdk2 = | TianoCoreEdk2Implementation diff --git a/dag/extdeps/firmware/trusted_firmware_a/subject.dag b/dag/extdeps/firmware/trusted_firmware_a/subject.dag index 2dd22bd9985..47fd5611f3e 100644 --- a/dag/extdeps/firmware/trusted_firmware_a/subject.dag +++ b/dag/extdeps/firmware/trusted_firmware_a/subject.dag @@ -12,7 +12,15 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data trusted_firmware_a_subject_note: String = "TRUSTED FIRMWARE-A, THE SECURE-WORLD BOOT CHAIN. Fetched live 2026-08-12: the project page states TF-A 'provides a reference implementation of secure world software for Armv7-A, Armv8-A and Armv9-A processors'. WHY IT IS ITS OWN SUBJECT: it is separately governed and separately versioned from the UEFI implementation that runs after it and from the system control processor firmware that runs before it, so an Ampere platform's boot chain is several upstreams in sequence rather than one 'firmware version'. The board article references this as a ROLE; what version a given host runs is a fleet observation and is not a fact this module owns. NOT MODELLED HERE: the boot-stage decomposition (BL1/BL2/BL31) — the cited page names no stages, and naming them from memory would be exactly the fabricated citation this repository refuses." +// TRUSTED FIRMWARE-A, THE SECURE-WORLD BOOT CHAIN. Fetched live 2026-08-12: the project page states +// TF-A 'provides a reference implementation of secure world software for Armv7-A, Armv8-A and +// Armv9-A processors'. WHY ITS OWN SUBJECT: separately governed and versioned from the UEFI +// implementation after it and the system control processor firmware before it, so an Ampere +// platform's boot chain is several upstreams in sequence, not one 'firmware version'. The board +// article references this as a ROLE; the version a host runs is a fleet observation, not a fact +// this module owns. NOT MODELLED HERE: the boot-stage decomposition (BL1/BL2/BL31) — the cited page +// names no stages, and naming them from memory would be the fabricated citation this repository +// refuses. type TrustedFirmwareA = | TrustedFirmwareASecureWorld diff --git a/dag/extdeps/firmware/types.dag b/dag/extdeps/firmware/types.dag index cbcd1b34942..e9d19b4721b 100644 --- a/dag/extdeps/firmware/types.dag +++ b/dag/extdeps/firmware/types.dag @@ -29,7 +29,14 @@ data extdeps_model_scope: ExternalModelScope = ExternalModelScope { further_citations: [] } -data types_scope_subject_note: String = "THE SUBJECT IS THE VENDOR FIRMWARE RELEASE AND ITS APPLICABILITY PRECONDITION. A release row names a board, an independently versioned firmware target, a packaging, and the bytes; the prerequisite names what must already be running before those bytes can be applied. NOT the flashing mechanism, NOT any BMC API surface, NOT product policy about which release a fleet should run — those are extdeps.bmc.* upstream facts and gunbc product decisions respectively. The prerequisite minimum here is bounded by executed pushes carried as FirmwarePrerequisiteObservation rows, never asserted from a vendor page, which documents no such dependency." +// THE SUBJECT IS THE VENDOR FIRMWARE RELEASE AND ITS APPLICABILITY PRECONDITION. A release row +// names a board, an independently versioned firmware target, a packaging, and the bytes; the +// prerequisite names what must already be running before those bytes can be applied. NOT the +// flashing mechanism, NOT any BMC API surface, NOT product policy about which release a fleet +// should run — those are extdeps.bmc.* upstream facts and gunbc product decisions respectively. The +// prerequisite minimum here is bounded by executed pushes carried as +// FirmwarePrerequisiteObservation rows, never asserted from a vendor page, which documents no such +// dependency. // A version triple is one concept. It was previously spelled // BmcFirmwareSemanticVersion inside the BMC module, which made the SAME diff --git a/dag/extdeps/firmware/uefi_shell.dag b/dag/extdeps/firmware/uefi_shell.dag index 0602896b2d8..6f7d95a334a 100644 --- a/dag/extdeps/firmware/uefi_shell.dag +++ b/dag/extdeps/firmware/uefi_shell.dag @@ -127,11 +127,20 @@ data uefi_ipv4_unspecified: String = "0.0.0.0" data uefi_ipv4_link_local_prefix: String = "169.254." -data uefi_ipv4_link_local_prefix_doc: String = "RFC 3927 link-local subnet is 169.254.0.0/16; on the dotted-quad rendering the UEFI shell ifconfig emits, membership is exactly the string prefix 169.254. — a prefix test, never a substring scan (a substring would also match 10.169.254.x)." +// RFC 3927 link-local subnet is 169.254.0.0/16; on the dotted-quad rendering the UEFI shell +// ifconfig emits, membership is exactly the string prefix 169.254. — a prefix test, never a +// substring scan (a substring would also match 10.169.254.x). data uefi_shell_removable_media_device_path_tokens: List = ["CDROM", "CDDVD", "USB"] -data uefi_shell_removable_media_tokens_doc: String = "🟡 dissolve-on: uefi_shell_removable_media_device_path_tokens — the removable-install-media classification reads the UEFI shell 'map -r' device-path RENDERING for these node tokens (CDROM/CDDVD messaging device-path nodes, USB messaging node — UEFI spec §10 device path protocol as the shell renders it). Token-in-rendering is a classification over upstream text, not a parsed device-path model; a mapping whose rendering uses an unlisted token classifies as not-removable and the decision layer refuses with NoBootSourceAvailable (loud refusal, never a fabricated boot source). DISSOLVES WHEN UefiDeviceMapping.device_path becomes a parsed device-path node list and the class is a match over node kinds." +// 🟡 dissolve-on: uefi_shell_removable_media_device_path_tokens — the removable-install-media +// classification reads the UEFI shell 'map -r' device-path RENDERING for these node tokens +// (CDROM/CDDVD messaging device-path nodes, USB messaging node — UEFI spec §10 device path protocol +// as the shell renders it). Token-in-rendering is a classification over upstream text, not a parsed +// device-path model; a mapping whose rendering uses an unlisted token classifies as not-removable +// and the decision layer refuses with NoBootSourceAvailable (loud refusal, never a fabricated boot +// source). DISSOLVES WHEN UefiDeviceMapping.device_path becomes a parsed device-path node list and +// the class is a match over node kinds. type UefiShellEnvironment { mappings: List diff --git a/dag/extdeps/git/git.dag b/dag/extdeps/git/git.dag index 05633cecda2..413a5e172c0 100644 --- a/dag/extdeps/git/git.dag +++ b/dag/extdeps/git/git.dag @@ -1,6 +1,5 @@ module extdeps.git - import extdeps.external_authority { ExternalAuthority } import extdeps.uri { Uri, Https } import extdeps.exec.command { ArgvCommand, argv_command } @@ -458,18 +457,48 @@ data git_worktree_external_authority_anchor: ExternalAuthority = ExternalAuthori } } -data advertised_ref_note: String = "WHY THIS EXISTS BESIDE RemoteBranches, which already lists remote branches. RemoteBranches runs `git branch -r`, whose output is ref NAMES and nothing else, and it reads the local remote-tracking refs rather than asking the remote. Neither property is a defect in it - it answers `which remote branches does this clone know about` - but it cannot answer `what commit is the remote branch AT`, and a consumer that needs an exact head therefore cannot be built on it at all. `git ls-remote --heads` is the ref-advertisement protocol reading: it contacts the remote and returns object id plus full ref name per line, which is the pair an exact-head judgment needs.\\n\\nThe two are kept separate rather than one being widened because they answer different questions with different freshness: `branch -r` is local and offline, ls-remote is a network read that can fail on its own. Collapsing them would make every name listing pay a network round trip and inherit its failure modes." +// WHY THIS EXISTS BESIDE RemoteBranches, which already lists remote branches. RemoteBranches runs +// `git branch -r`, whose output is ref NAMES and nothing else, and it reads the local +// remote-tracking refs rather than asking the remote. Neither property is a defect in it - it +// answers `which remote branches does this clone know about` - but it cannot answer `what commit is +// the remote branch AT`, and a consumer that needs an exact head therefore cannot be built on it at +// all. `git ls-remote --heads` is the ref-advertisement protocol reading: it contacts the remote +// and returns object id plus full ref name per line, which is the pair an exact-head judgment +// needs.\n\nThe two are kept separate rather than one being widened because they answer different +// questions with different freshness: `branch -r` is local and offline, ls-remote is a network read +// that can fail on its own. Collapsing them would make every name listing pay a network round trip +// and inherit its failure modes. type AdvertisedRef { object_id: CommitSha ref_name: String } -data advertised_ref_scan_note: String = "The scan REFUSES on a line it cannot read rather than skipping it, and the located line is carried so the refusal names what it choked on. Dropping unparseable lines is the shape DESIGN 5 calls the absorbing fallback: the answer would still be a well-formed ref list, so a consumer could not tell a complete advertisement from a partial one, and the deficit's frequency would be zero by construction. A partial ref list is exactly the input that makes an exact-head judgment answer `not published` about a branch that is in fact published." +// The scan REFUSES on a line it cannot read rather than skipping it, and the located line is +// carried so the refusal names what it choked on. Dropping unparseable lines is the shape DESIGN 5 +// calls the absorbing fallback: the answer would still be a well-formed ref list, so a consumer +// could not tell a complete advertisement from a partial one, and the deficit's frequency would be +// zero by construction. A partial ref list is exactly the input that makes an exact-head judgment +// answer `not published` about a branch that is in fact published. data ls_remote_reports_termination_not_success_note: String = "IT REPORTS THE EXIT CODE AND NOT A SUCCESS BOOL, WHICH IS A NARROWING FROM ITS FIRST CUT. That cut declared `success: Bool from \"exit_success\"` beside `exit_code`, and the two are not independent: the seed derives exit_success as exactly `exit_code == 0` (v1_interpreter.rs, the shell dispatch output-source match), so the pair carried one fact twice.\\n\\nCARRYING IT TWICE WAS NOT MERELY REDUNDANT, IT MADE A CONTRADICTION WRITABLE. A caller of the deciding function could pass success true beside a nonzero exit code, and the function branched on success - so a failed advertisement read as a successful one and the branch reported unpublished rather than unreadable. That is the ignorance-as-answer conflation this operation exists to prevent, reachable through the operation own output shape. Dropping the Bool makes the contradictory pair unrepresentable rather than merely unlikely, which is why this is construction and not a check.\\n\\nWHICH LAYER OWNS SUCCESS, since something still has to answer it: the caller EXPECTATION does. Process termination, operation observation, caller expectation and log disposition are four facts, and a probe may legitimately use a nonzero exit as an ANSWER rather than a verdict - so an operation that pre-judges its own exit code as success has decided something only its caller can. See gunbc.plans.transport_argv_anemia_dissolution, which measures 148 sibling operations in dag/extdeps still carrying the Bool while 51 preserve the integer. This operation is the first consumer of that correction, not a local exception." -data remote_url_takes_its_repository_as_a_parameter_note: String = "BOTH THE REPOSITORY AND THE REMOTE NAME ARE PARAMETERS, AND NEITHER IS A LITERAL IN THE ARGV. `origin` is the obvious literal to bake in, because almost every caller wants it - and baking it in would make this operation a model of one checkout's configuration rather than of git's remote-url lookup. Which remote a caller cares about is a caller fact; the operation models the read.\\n\\nTHE PATH IS A PARAMETER FOR A SHARPER REASON: without it this operation reads whatever repository the process happens to be standing in. Its first consumer asks whether a specific attempt's worktree is bound to the repository a publication subject names, and a read that silently answered for the process's own checkout would confirm the binding for the wrong repository while looking entirely ordinary - the precise class of coincidence the consumer exists to refuse. `git -C ` is git's own mechanism for this, so the repository is named rather than inherited.\\n\\nIt reports exit_code and stderr rather than a success Bool, for the reason ls_remote_reports_termination_not_success_note gives at length: the seed derives exit_success as exactly `exit_code == 0`, so the pair would carry one fact twice and make a contradictory pair writable. It declares no exit block for the reason the note below gives: those arms are parsed, re-emitted, and never realized, so authoring one here would mint a fresh declared-but-unrealized instance." +// BOTH THE REPOSITORY AND THE REMOTE NAME ARE PARAMETERS, AND NEITHER IS A LITERAL IN THE ARGV. +// `origin` is the obvious literal to bake in, because almost every caller wants it - and baking it +// in would make this operation a model of one checkout's configuration rather than of git's +// remote-url lookup. Which remote a caller cares about is a caller fact; the operation models the +// read.\n\nTHE PATH IS A PARAMETER FOR A SHARPER REASON: without it this operation reads whatever +// repository the process happens to be standing in. Its first consumer asks whether a specific +// attempt's worktree is bound to the repository a publication subject names, and a read that +// silently answered for the process's own checkout would confirm the binding for the wrong +// repository while looking entirely ordinary - the precise class of coincidence the consumer exists +// to refuse. `git -C ` is git's own mechanism for this, so the repository is named rather +// than inherited.\n\nIt reports exit_code and stderr rather than a success Bool, for the reason +// ls_remote_reports_termination_not_success_note gives at length: the seed derives exit_success as +// exactly `exit_code == 0`, so the pair would carry one fact twice and make a contradictory pair +// writable. It declares no exit block for the reason the note below gives: those arms are parsed, +// re-emitted, and never realized, so authoring one here would mint a fresh declared-but-unrealized +// instance. data ls_remote_carries_no_exit_block_note: String = "WHY LsRemoteHeads DECLARES NO `exit` BLOCK WHILE EVERY SIBLING OPERATION DOES, which is the first thing a reader will notice. The `exit` arms are INERT AT RUN TIME - established by reading the seed, not assumed. The shell dispatch in v1_interpreter.rs maps declared output sources (`stdout`, `stderr`, `exit_success`, `exit_code`, `stdout_lines`) onto the process result and returns a record; it never consults the operation's exit arms and never raises on a nonzero exit. The only consumer of exit_props anywhere outside the parser is v1_compiler_emit_rust.rs, which round-trips them back to source so the self-host fixed point holds. So an exit arm is a declaration that is parsed, re-emitted, and never realized.\\n\\nThat makes this operation's OUTPUTS the realized authority for its outcome: a caller learns whether the read succeeded from `exit_code`/`stderr`, which are values it can match on. (It carried a `success` Bool too until this operation narrowed - see ls_remote_reports_termination_not_success_note directly above, which is the authority for why. Left unfixed, this sentence would have been a stale citation inside the very PR that made it false.) Adding an exit block beside them would be a second representation of the same fact where only one of the two executes - and since this operation is new, authoring one knowing it is inert would be minting a fresh declared-but-unrealized instance rather than inheriting an existing one. The corpus-wide inertness of exit blocks is a real finding and a separate correction; it is not fixed by making one operation diverge from a hundred siblings, and it is not made worse by declining to add the hundred-and-first." @@ -661,7 +690,25 @@ fn shape_git_branch_show_current_argv() -> List { ["git", "branch", "--show-current"] } -data git_pathspec_exclude_note: String = "PATHSPEC MAGIC IS GIT'S INTERFACE; WHICH PATHS TO EXCLUDE IS NOT (DESIGN §3 (a)/(c) split). gitglossary(7) 'pathspec' defines the long-form magic word `exclude` — a pathspec of the form `:(exclude)` removes matching paths from the set an otherwise-matching pathspec selected, and git requires at least one positive pathspec beside it for there to be a set to subtract from (hence the leading `.` that shape_git_diff_unmerged_pathspec_args emits). This module owns only that SPELLING. The exclusion LIST is a caller's business policy — a workflow fact — and passing pathspec WORDS as a parameter is what keeps this row from becoming the layer inversion §3 names (an argv carrying a literal it should receive as an argument).\n\nA PATHSPEC WORD IS NOT A SHELL WORD, AND THIS MODULE DELIBERATELY DOES NOT QUOTE IT. `:(exclude)p` contains `(` and `)`, which are bash metacharacters, so any shell realization that joins this word into a command line unquoted emits a SYNTAX ERROR — proven by execution, not reasoned: the first cut of the heal skew guard did exactly that and `bash -n` rejected the emitted line. git_shell_join_argv is a join over already-spelled words, not a quoter, and must not become one (its existing callers pass words that are already quoted, so quoting there would double-quote them). Quoting therefore belongs to the emitting realization, which is the only layer that knows the target shell — see v2.workflow.ci_heal_skew_guard_emit ci_heal_skew_shell_single_quoted. Callers composing a pathspec into shell MUST quote the whole word." +// PATHSPEC MAGIC IS GIT'S INTERFACE; WHICH PATHS TO EXCLUDE IS NOT (DESIGN §3 (a)/(c) split). +// gitglossary(7) 'pathspec' defines the long-form magic word `exclude` — a pathspec of the form +// `:(exclude)` removes matching paths from the set an otherwise-matching pathspec +// selected, and git requires at least one positive pathspec beside it for there to be a set to +// subtract from (hence the leading `.` that shape_git_diff_unmerged_pathspec_args emits). This +// module owns only that SPELLING. The exclusion LIST is a caller's business policy — a workflow +// fact — and passing pathspec WORDS as a parameter is what keeps this row from becoming the layer +// inversion §3 names (an argv carrying a literal it should receive as an argument). +// +// A PATHSPEC WORD IS NOT A SHELL WORD, AND THIS MODULE DELIBERATELY DOES NOT QUOTE IT. +// `:(exclude)p` contains `(` and `)`, which are bash metacharacters, so any shell realization that +// joins this word into a command line unquoted emits a SYNTAX ERROR — proven by execution, not +// reasoned: the first cut of the heal skew guard did exactly that and `bash -n` rejected the +// emitted line. git_shell_join_argv is a join over already-spelled words, not a quoter, and must +// not become one (its existing callers pass words that are already quoted, so quoting there would +// double-quote them). Quoting therefore belongs to the emitting realization, which is the only +// layer that knows the target shell — see v2.workflow.ci_heal_skew_guard_emit +// ci_heal_skew_shell_single_quoted. Callers composing a pathspec into shell MUST quote the whole +// word. fn git_pathspec_exclude(path: String) -> String { concat(":(exclude)", path) @@ -765,11 +812,38 @@ fn git_observe_meta_shell_fragment() -> String { ) } -data forced_ref_exchange_operations_note: String = "FORCED, REPOSITORY-ADDRESSED REF EXCHANGE -- FetchForcedRefInRepo and PushForcedRefInRepo. `--force` with an explicit refspec is git's own interface for `make the destination equal the source`, which is what a scratch exchange channel between two processes needs and what a publication does NOT: these are deliberately not the operations git.PublicationTransport declares, and neither is a lease-bearing form. `--quiet` suppresses progress on a channel whose only consumer reads exit_code and stderr. Both take their repository as a parameter rather than inheriting the process working directory, per remote_url_takes_its_repository_as_a_parameter_note, and both report exit_code and stderr rather than a success Bool, per ls_remote_reports_termination_not_success_note." - -data integration_write_operations_note: String = "THE SIX WRITE/READ OPERATIONS BELOW MODEL GIT'S ORDINARY INTEGRATION SURFACE — stage, commit, branch checkout, merge, and the unmerged-stage read — each taking its repository as a parameter rather than inheriting the process's checkout, for the reason remote_url_takes_its_repository_as_a_parameter_note gives: an operation that answered for whatever directory the process happens to stand in would confirm a result for the wrong repository while looking entirely ordinary. They report exit_code and stderr and NOT a success Bool, per ls_remote_reports_termination_not_success_note: the seed derives exit_success as exactly exit_code == 0, so the pair would carry one fact twice and make a contradictory pair writable. That narrowing matters most for MergeNoEditInRepo, whose nonzero exit is an ANSWER rather than a failure — a caller asking whether a merge left the tree conflicted reads the code, and an operation pre-judging it as unsuccessful would decide something only the caller can. They declare no exit block, per ls_remote_carries_no_exit_block_note: those arms are parsed, re-emitted, and never realized, so authoring one here would mint a fresh declared-but-unrealized instance. Committer identity is deliberately not an input — a caller sets user.email and user.name through ConfigLocalSetInRepo, so identity stays one modeled binding rather than a second per-commit representation." - -data repository_coherence_operations_note: String = "Independent repository-coherence observations. WriteTreeInRepo asks git to serialize the repository's actual index; TrackedWorktreeDiffFromInRepo compares tracked working-tree content directly with a named treeish. The latter deliberately does not use status, diff-index or diff-files: each incorporates the index and can therefore report a false difference when the index is the stale side of the three-way state being diagnosed." +// FORCED, REPOSITORY-ADDRESSED REF EXCHANGE -- FetchForcedRefInRepo and PushForcedRefInRepo. +// `--force` with an explicit refspec is git's own interface for `make the destination equal the +// source`, which is what a scratch exchange channel between two processes needs and what a +// publication does NOT: these are deliberately not the operations git.PublicationTransport +// declares, and neither is a lease-bearing form. `--quiet` suppresses progress on a channel whose +// only consumer reads exit_code and stderr. Both take their repository as a parameter rather than +// inheriting the process working directory, per +// remote_url_takes_its_repository_as_a_parameter_note, and both report exit_code and stderr rather +// than a success Bool, per ls_remote_reports_termination_not_success_note. + +// THE SIX WRITE/READ OPERATIONS BELOW MODEL GIT'S ORDINARY INTEGRATION SURFACE — stage, commit, +// branch checkout, merge, and the unmerged-stage read — each taking its repository as a parameter +// rather than inheriting the process's checkout, for the reason +// remote_url_takes_its_repository_as_a_parameter_note gives: an operation that answered for +// whatever directory the process happens to stand in would confirm a result for the wrong +// repository while looking entirely ordinary. They report exit_code and stderr and NOT a success +// Bool, per ls_remote_reports_termination_not_success_note: the seed derives exit_success as +// exactly exit_code == 0, so the pair would carry one fact twice and make a contradictory pair +// writable. That narrowing matters most for MergeNoEditInRepo, whose nonzero exit is an ANSWER +// rather than a failure — a caller asking whether a merge left the tree conflicted reads the code, +// and an operation pre-judging it as unsuccessful would decide something only the caller can. They +// declare no exit block, per ls_remote_carries_no_exit_block_note: those arms are parsed, +// re-emitted, and never realized, so authoring one here would mint a fresh declared-but-unrealized +// instance. Committer identity is deliberately not an input — a caller sets user.email and +// user.name through ConfigLocalSetInRepo, so identity stays one modeled binding rather than a +// second per-commit representation. + +// Independent repository-coherence observations. WriteTreeInRepo asks git to serialize the +// repository's actual index; TrackedWorktreeDiffFromInRepo compares tracked working-tree content +// directly with a named treeish. The latter deliberately does not use status, diff-index or +// diff-files: each incorporates the index and can therefore report a false difference when the +// index is the stale side of the three-way state being diagnosed. service git.Core { operation CurrentBranch { diff --git a/dag/extdeps/git/inspect.dag b/dag/extdeps/git/inspect.dag index 46a110579cf..d604ff82288 100644 --- a/dag/extdeps/git/inspect.dag +++ b/dag/extdeps/git/inspect.dag @@ -12,7 +12,11 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data git_inspect_service_note: String = "Read-only repository-root, revision, and tree inspection operations are de-fused from the broad git.Core service so consumers pay only the upstream facts they inspect. Toplevel, HeadCommit, and ShowTree move here with their existing shapes; MergeBase and ListTreePathsAtRevision extend this same inspection authority for revision-addressed Rust-source observation. No second Git inspection surface remains in extdeps.git." +// Read-only repository-root, revision, and tree inspection operations are de-fused from the broad +// git.Core service so consumers pay only the upstream facts they inspect. Toplevel, HeadCommit, and +// ShowTree move here with their existing shapes; MergeBase and ListTreePathsAtRevision extend this +// same inspection authority for revision-addressed Rust-source observation. No second Git +// inspection surface remains in extdeps.git. // `--no-optional-locks` IS WHAT MAKES `readonly` TRUE, AND ITS ABSENCE MADE THIS DECLARATION FALSE. // Measured: running this operation's own argv without the flag against a repository CHANGES the index @@ -419,7 +423,6 @@ fn merge_tree_outcome(exit_code: Int, stdout: String, stderr: String) -> MergeTr } } - // A CONFIG KEY THAT IS UNSET IS AN ANSWER, NOT A FAILURE, which is why the exit table above declares // 1 as its own arm: `git config --get` exits 1 when the key has no value, and a caller comparing // only against 0 would render "this repository does not configure that" identically to "the read diff --git a/dag/extdeps/git/object_store.dag b/dag/extdeps/git/object_store.dag index 6c6d1c4fd8e..161aeaf6d6e 100644 --- a/dag/extdeps/git/object_store.dag +++ b/dag/extdeps/git/object_store.dag @@ -351,7 +351,10 @@ fn git_sha256_object_id(hex: String) -> GitObjectId? { } } -data git_object_id_eq_note: String = "Format-aware equality, held beside the type rather than in a consumer: two ids of different families are never equal, so a repository mid SHA-1/SHA-256 transition cannot produce a false match on hex alone. It lived briefly in gunbc.merge_admission, which is generic Git behaviour sitting in one consumer — the §3 fork this move closes." +// Format-aware equality, held beside the type rather than in a consumer: two ids of different +// families are never equal, so a repository mid SHA-1/SHA-256 transition cannot produce a false +// match on hex alone. It lived briefly in gunbc.merge_admission, which is generic Git behaviour +// sitting in one consumer — the §3 fork this move closes. data git_object_id_untagged_hex_note: String = "THE ONE DECODER FOR UNTAGGED PLUMBING OUTPUT (review 47517, §3). Git's own plumbing (show --format=%T and friends) prints a bare lowercase-hex object id with no family tag; the family is decidable from the canonical text width alone because the two supported hash functions have distinct digest lengths (40 for SHA-1, 64 for SHA-256, per git's hash-function-transition design). That inference is a fact about GIT'S wire, so it lives here at the GitObjectId authority — a consumer re-deriving it by measuring hex lengths is a second representation of the family axis (the capture tool did exactly this). Our OWN wires stay family-prefixed (:, render/parse_git_object_id_wire) and never route through this decoder; this fn exists only for text git itself produced. A width that is neither 40 nor 64 refuses." @@ -402,7 +405,10 @@ fn git_object_id_eq(left: GitObjectId, right: GitObjectId) -> Bool { } } -data git_sha1_oid_deleted_note: String = "git_sha1_oid and git_sha256_oid deleted (review 45376): GitObjectId is constructed only through git_sha1_object_id / git_sha256_object_id, which enforce length and lowercase-hex invariants. Witness and merge-admission fixture helpers that need a total GitObjectId from known-valid corpus literals live beside their consumers." +// git_sha1_oid and git_sha256_oid deleted (review 45376): GitObjectId is constructed only through +// git_sha1_object_id / git_sha256_object_id, which enforce length and lowercase-hex invariants. +// Witness and merge-admission fixture helpers that need a total GitObjectId from known-valid corpus +// literals live beside their consumers. type Blob { content: Bytes diff --git a/dag/extdeps/git/plumbing.dag b/dag/extdeps/git/plumbing.dag index 16c270e095a..386a05645f7 100644 --- a/dag/extdeps/git/plumbing.dag +++ b/dag/extdeps/git/plumbing.dag @@ -37,7 +37,12 @@ data extdeps_model_scope: ExternalModelScope = ExternalModelScope { // plumbing is a different reading of git from porcelain even though it is the same // upstream product (DESIGN, external upstream decomposition -- one product may be // grounded by several modules; it may not be forked across several authorities). -data git_plumbing_service_note: String = "Git's object-store plumbing surface: object database (hash-object, cat-file, unpack-file), index and tree (read-tree, update-index, write-tree, checkout-index), commit (commit-tree) and ref store (rev-parse --verify, update-ref). Every operation names its repository as a typed input rather than inheriting the process working directory, per extdeps.git remote_url_takes_its_repository_as_a_parameter_note. No plumbing argv is spelled outside this service." +// Git's object-store plumbing surface: object database (hash-object, cat-file, unpack-file), index +// and tree (read-tree, update-index, write-tree, checkout-index), commit (commit-tree) and ref +// store (rev-parse --verify, update-ref). Every operation names its repository as a typed input +// rather than inheriting the process working directory, per extdeps.git +// remote_url_takes_its_repository_as_a_parameter_note. No plumbing argv is spelled outside this +// service. // EVERY OPERATION REPORTS `exit_code` AND `stderr`, NEVER A `success` BOOL. The seed // derives `exit_success` as exactly `exit_code == 0` (v1_interpreter.rs, the shell dispatch @@ -51,7 +56,9 @@ data git_plumbing_service_note: String = "Git's object-store plumbing surface: o // NO OPERATION DECLARES AN `exit` BLOCK, per extdeps.git ls_remote_carries_no_exit_block_note: // those arms are parsed, re-emitted, and never realized, so authoring one here would mint // fresh declared-but-unrealized instances rather than inherit existing ones. -data git_plumbing_outcome_shape_note: String = "Operations report exit_code and stderr and declare no exit block. Two of them - CatFileExists and ObserveRef - answer a QUESTION with a nonzero exit, which is why pre-judging termination as success is not available to this layer at all." +// Operations report exit_code and stderr and declare no exit block. Two of them - CatFileExists and +// ObserveRef - answer a QUESTION with a nonzero exit, which is why pre-judging termination as +// success is not available to this layer at all. // REPOSITORY ADDRESSING IS ONE AXIS, NOT TWO OPERATION FAMILIES. Git accepts a repository // two ways and they are not interchangeable: `-C ` changes directory and then diff --git a/dag/extdeps/github/actions.dag b/dag/extdeps/github/actions.dag index ee7a1818a39..6e95ed00cfb 100644 --- a/dag/extdeps/github/actions.dag +++ b/dag/extdeps/github/actions.dag @@ -33,7 +33,15 @@ data github_actions_runtime: GitHubActionsRuntime = { oidc_request_token_env: "ACTIONS_ID_TOKEN_REQUEST_TOKEN" } -data github_ci_event_surface_note: String = "GITHUB_EVENT_NAME and GITHUB_BASE_REF are default environment variables GitHub Actions sets on every job (docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables, read 2026-07-24). GITHUB_EVENT_NAME carries the wire name of the triggering event (the runtime projection of WorkflowTrigger: push / pull_request / workflow_dispatch / schedule / merge_group). GITHUB_BASE_REF carries the base (target) branch name of a pull request — set ONLY on pull_request / pull_request_target events, empty for every other event. extdeps owns these names (the shape GitHub provides); the policy that maps them to a diff baseline is a workflow-layer fact (gunbc.diff_baseline), not modeled here (extdeps must not depend upward — DESIGN §3(c))." +// GITHUB_EVENT_NAME and GITHUB_BASE_REF are default environment variables GitHub Actions sets on +// every job +// (docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables, read +// 2026-07-24). GITHUB_EVENT_NAME carries the wire name of the triggering event (the runtime +// projection of WorkflowTrigger: push / pull_request / workflow_dispatch / schedule / merge_group). +// GITHUB_BASE_REF carries the base (target) branch name of a pull request — set ONLY on +// pull_request / pull_request_target events, empty for every other event. extdeps owns these names +// (the shape GitHub provides); the policy that maps them to a diff baseline is a workflow-layer +// fact (gunbc.diff_baseline), not modeled here (extdeps must not depend upward — DESIGN §3(c)). data github_event_name_env: NonEmptyStr = "GITHUB_EVENT_NAME" @@ -41,7 +49,17 @@ data github_base_ref_env: NonEmptyStr = "GITHUB_BASE_REF" data github_event_path_env: NonEmptyStr = "GITHUB_EVENT_PATH" -data github_event_path_home_note: String = "GITHUB_EVENT_PATH is a GitHub Actions DEFAULT ENVIRONMENT VARIABLE — the path to the file holding the full webhook payload for whatever event triggered the run (docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables, read 2026-08-06). It is therefore a fact about the Actions RUNTIME, exactly like github_event_name_env beside it, and not a fact about any one event. It previously lived in extdeps.github.push_event, which made the push payload reader the authority for a variable that carries the pull_request, schedule, and workflow_run payloads just as much. That homing only looked right while push was the sole reader; adding a second event reader made it a §3 fork in waiting — two modules declaring one upstream name, drifting independently. Moved here rather than copied: push_event and workflow_run_event both import it, and there is exactly one declaration of this name in the tree." +// GITHUB_EVENT_PATH is a GitHub Actions DEFAULT ENVIRONMENT VARIABLE — the path to the file holding +// the full webhook payload for whatever event triggered the run +// (docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables, read +// 2026-08-06). It is therefore a fact about the Actions RUNTIME, exactly like github_event_name_env +// beside it, and not a fact about any one event. It previously lived in extdeps.github.push_event, +// which made the push payload reader the authority for a variable that carries the pull_request, +// schedule, and workflow_run payloads just as much. That homing only looked right while push was +// the sole reader; adding a second event reader made it a §3 fork in waiting — two modules +// declaring one upstream name, drifting independently. Moved here rather than copied: push_event +// and workflow_run_event both import it, and there is exactly one declaration of this name in the +// tree. data github_event_name_push: String = "push" @@ -93,9 +111,32 @@ type PullRequestActivity | ReadyForReview | Closed -data workflow_run_trigger_citation_note: String = "workflow_run is the event GitHub delivers when a workflow run of ANOTHER workflow reaches a listed activity type (docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#workflow_run, read 2026-08-06). Two facts about the shape, both load-bearing. THE VARIANT IS COMPLETED-ONLY BY NAME, not by a types field. GitHub documents three activity types (requested, in_progress, completed) and the variant admits exactly one of them, so the activity is carried in the CONSTRUCTOR rather than as a list — a workflow that also wanted `requested` is a different trigger with a different admission story, not a longer list on this one, and an alert holding issues: write must not be able to widen its own activation surface by appending to a list. The day this repo needs a second activity, it is a second variant whose admission is argued separately, and every serializer match answers it fail-closed. THE WORKFLOWS FIELD IS List AND NOT A MINTED WorkflowName. Workflow.name in this module is String, and the workflows filter refers to exactly that name, so a WorkflowName alias would be a second name for one carrier that adds no refinement — the hollow-alias class DESIGN carries as an open construction-wall thread. The referent is the name, so it shares the name's carrier. If Workflow.name ever gains a refinement, this field follows it there rather than being refined separately." - -data merge_group_event_citation_note: String = "merge_group is the event GitHub delivers when a merge queue needs checks on a queue-constructed tree (docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#merge_group, read 2026-07-16). The variant is nullary because checks_requested is the ONLY documented activity type and GitHub's default when the types filter is omitted — a types parameter with exactly one inhabitant carries no information (the phantom-parameter collapse ruling); the day upstream documents a second activity, the field returns as a closed modeled enum and every serializer match must answer it, fail-closed." +// workflow_run is the event GitHub delivers when a workflow run of ANOTHER workflow reaches a +// listed activity type +// (docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#workflow_run, +// read 2026-08-06). Two facts about the shape, both load-bearing. THE VARIANT IS COMPLETED-ONLY BY +// NAME, not by a types field. GitHub documents three activity types (requested, in_progress, +// completed) and the variant admits exactly one of them, so the activity is carried in the +// CONSTRUCTOR rather than as a list — a workflow that also wanted `requested` is a different +// trigger with a different admission story, not a longer list on this one, and an alert holding +// issues: write must not be able to widen its own activation surface by appending to a list. The +// day this repo needs a second activity, it is a second variant whose admission is argued +// separately, and every serializer match answers it fail-closed. THE WORKFLOWS FIELD IS +// List AND NOT A MINTED WorkflowName. Workflow.name in this module is String, and the +// workflows filter refers to exactly that name, so a WorkflowName alias would be a second name for +// one carrier that adds no refinement — the hollow-alias class DESIGN carries as an open +// construction-wall thread. The referent is the name, so it shares the name's carrier. If +// Workflow.name ever gains a refinement, this field follows it there rather than being refined +// separately. + +// merge_group is the event GitHub delivers when a merge queue needs checks on a queue-constructed +// tree +// (docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#merge_group, +// read 2026-07-16). The variant is nullary because checks_requested is the ONLY documented activity +// type and GitHub's default when the types filter is omitted — a types parameter with exactly one +// inhabitant carries no information (the phantom-parameter collapse ruling); the day upstream +// documents a second activity, the field returns as a closed modeled enum and every serializer +// match must answer it, fail-closed. type DispatchInput { name: String diff --git a/dag/extdeps/github/actions_environment.dag b/dag/extdeps/github/actions_environment.dag index f6eef27be2d..8380452036f 100644 --- a/dag/extdeps/github/actions_environment.dag +++ b/dag/extdeps/github/actions_environment.dag @@ -12,7 +12,27 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data actions_environment_note: String = "GitHub Actions default environment variables, modeled as the upstream contract rather than read ad hoc. Only the fact this lane needs is carried: GITHUB_SHA, which the cited reference defines as the commit SHA that triggered the workflow run.\n\nWHY THIS EXISTS AS AN UPSTREAM FACT AND NOT A git READ. A liveness claim of the form 'the observation reached the exact revision under test' is only evidence if the EXPECTED revision comes from somewhere other than the thing being tested. Deriving it with git rev-parse and then comparing it to an observation that also ran git rev-parse compares git to itself: it is true by construction, cannot fail, and would be reflection evidence standing in for the structural proof (DESIGN recurring failure modes). GITHUB_SHA is supplied by the workflow runner, independently of the repository checkout, so the comparison can actually fail -- which is what makes it a control.\n\nEVENT SEMANTICS, STATED BECAUSE THEY DECIDE WHETHER THE COMPARISON IS EVEN WELL-FORMED. For a pull_request event the cited reference defines GITHUB_SHA as the LAST MERGE COMMIT of the pull request merge branch, which is exactly what actions/checkout leaves at HEAD under its default ref. For a push event it is the pushed commit, likewise HEAD. So on both events the runner-supplied value and the checked-out HEAD denote the same commit, and a mismatch is a real defect (a shallow or pinned-ref checkout, a mid-run mutation, or an observation that read a different tree) rather than an expected difference. Any event whose checkout ref is configured away from the default breaks that correspondence, and the honest response there is a typed refusal rather than a relaxed comparison." +// GitHub Actions default environment variables, modeled as the upstream contract rather than read +// ad hoc. Only the fact this lane needs is carried: GITHUB_SHA, which the cited reference defines +// as the commit SHA that triggered the workflow run. +// +// WHY THIS EXISTS AS AN UPSTREAM FACT AND NOT A git READ. A liveness claim of the form 'the +// observation reached the exact revision under test' is only evidence if the EXPECTED revision +// comes from somewhere other than the thing being tested. Deriving it with git rev-parse and then +// comparing it to an observation that also ran git rev-parse compares git to itself: it is true by +// construction, cannot fail, and would be reflection evidence standing in for the structural proof +// (DESIGN recurring failure modes). GITHUB_SHA is supplied by the workflow runner, independently of +// the repository checkout, so the comparison can actually fail -- which is what makes it a control. +// +// EVENT SEMANTICS, STATED BECAUSE THEY DECIDE WHETHER THE COMPARISON IS EVEN WELL-FORMED. For a +// pull_request event the cited reference defines GITHUB_SHA as the LAST MERGE COMMIT of the pull +// request merge branch, which is exactly what actions/checkout leaves at HEAD under its default +// ref. For a push event it is the pushed commit, likewise HEAD. So on both events the +// runner-supplied value and the checked-out HEAD denote the same commit, and a mismatch is a real +// defect (a shallow or pinned-ref checkout, a mid-run mutation, or an observation that read a +// different tree) rather than an expected difference. Any event whose checkout ref is configured +// away from the default breaks that correspondence, and the honest response there is a typed +// refusal rather than a relaxed comparison. type ActionsShaObservation = ActionsShaObserved { sha: CommitSha } diff --git a/dag/extdeps/github/actions_token.dag b/dag/extdeps/github/actions_token.dag index d1693eb8417..6a65fd3bcc7 100644 --- a/dag/extdeps/github/actions_token.dag +++ b/dag/extdeps/github/actions_token.dag @@ -66,19 +66,18 @@ fn axis_actions_permissions_key(axis: GitHubPermissionAxis) -> NonEmptyStr? { } } -// Two independent facts about one axis, deliberately NOT fused (operator -// correction 2026-08-06). `axis_actions_permissions_key` answers whether an -// axis is DECLARABLE in a workflow `permissions:` block. This answers what the -// job token carries when nothing is declared. Absence of a configurable key is -// therefore not absence of a grant, and conflating the two made the model call -// a grant observed on the live token structurally impossible. +// Two independent facts about one axis, deliberately NOT fused (operator correction 2026-08-06). +// `axis_actions_permissions_key` answers whether an axis is DECLARABLE in a workflow `permissions:` +// block; this answers what the job token carries when nothing is declared. Absence of a +// configurable key is not absence of a grant, and conflating the two made the model call a grant +// observed on the live token structurally impossible. // -// Metadata is the live specimen: it has no configurable `permissions:` key, yet -// the exact-head Actions job reports `Metadata: read`. +// Metadata is the live specimen: no configurable `permissions:` key, yet the exact-head Actions job +// reports `Metadata: read`. // -// Read-only by construction — there is no write variant, so an intrinsic grant -// can never authorize a write, and observing a write-carrying intrinsic axis -// would be a compile error at every consumer rather than a silent widening. +// Read-only by construction — no write variant, so an intrinsic grant can never authorize a write, +// and observing a write-carrying intrinsic axis is a compile error at every consumer, not a silent +// widening. type IntrinsicTokenGrant = IntrinsicTokenGrantAbsent | IntrinsicTokenGrantRead @@ -161,10 +160,10 @@ fn actions_access_set_admits(set: ActionsAccessSet, access: GitHubAppPermissionA } } -// DECLARABILITY, not possession — renamed from actions_token_can_carry with the -// intrinsic-grant correction. The token carries Metadata: read while Metadata -// is undeclarable, so a single "can carry" predicate answered two questions and -// had to be wrong about one of them. Ask effective_actions_grant for possession. +// DECLARABILITY, not possession — renamed from actions_token_can_carry with the intrinsic-grant +// correction. The token carries Metadata: read while Metadata is undeclarable, so one "can carry" +// predicate answered two questions and was wrong about one. Ask effective_actions_grant for +// possession. fn actions_token_may_declare_permission(name: GitHubPermissionAxis) -> Bool { match axis_actions_permissions_key(axis: name) { Absent => false @@ -179,9 +178,9 @@ fn actions_token_may_declare_permission(name: GitHubPermissionAxis) -> Bool { data workflows_permission_name: GitHubPermissionAxis = WorkflowsAxis -// Declarability is sufficient here because an intrinsic grant is read-only by -// construction (IntrinsicTokenGrant has no write variant), so no intrinsic -// grant can authorize a workflow-file write. Workflows carries neither. +// Declarability suffices here because an intrinsic grant is read-only by construction +// (IntrinsicTokenGrant has no write variant), so none can authorize a workflow-file write. +// Workflows carries neither. fn actions_token_may_write_workflow_files() -> Bool { actions_token_may_declare_permission(name: workflows_permission_name) } diff --git a/dag/extdeps/github/auth.dag b/dag/extdeps/github/auth.dag index 826ae5ea7e0..170bafa52a9 100644 --- a/dag/extdeps/github/auth.dag +++ b/dag/extdeps/github/auth.dag @@ -44,14 +44,14 @@ data structural_coverage_gap_github_token_metadata_verification: List) -> Bool { fold(templates, init: false, f: (acc, template) => acc || template_accesses_runner_var_via_env(template: template)) } - -data expression_operator_extension_note: String = "LogicalAnd and Equals were added because a consumer needed to LOWER a job condition rather than concatenate one (review 49400 on gunbc#7903: the falsifier alert's trust-wall condition was being built by nested concat of raw expression text — a foreign-language executable string in normal form, authored outside the module that owns that language, in the very change that declares that class as residue to shrink). Both are ordinary GitHub Actions expression syntax (docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/evaluate-expressions-in-workflows-and-actions#operators, read 2026-08-06), so this models what upstream already has.\n\nNO GROUPING CONSTRUCT WAS ADDED, AND THAT IS THE LOAD-BEARING DECISION. GitHub binds && tighter than ||, so an Or nested under an And needs parentheses to keep its meaning, and the first attempt here made LogicalOr parenthesize unconditionally. review 49430 refused it, correctly and on the strongest possible ground: this module is READ IN BOTH DIRECTIONS — parse_expression_with is the inverse of serialize_expression_with — and the change added a construct to emission that ingest could not read, so `(a || b)` serialized out and would not parse back. That is precisely the one-grammar-both-directions invariant DESIGN §4 states, broken by editing one direction alone. Three round-trip witnesses and an emission golden would have caught it; they were not run, and the earlier claim on the PR that they had been was wrong.\n\nSo emission is UNCHANGED from before this row, and the precedence hazard is answered where it actually arises — at the authoring site, by choosing a shape that needs no grouping. gunbc.falsifier_alert_admission builds its condition in DISTRIBUTED form, `A && B && s || A && B && d`, which under GitHub's own precedence already parses as the intended `(A && B && s) || (A && B && d)`. Adding parentheses to this grammar remains legitimate future work, but it is a change to BOTH directions plus the goldens that pin them, not a serializer edit.\n\nDECLARED ASYMMETRY, NAMED RATHER THAN LEFT IMPLIED (review 49460): parse_expression_with reads Or, calls, context accesses and string literals; it does NOT read && or ==. So these two operators are EMISSION-ONLY today, and this module is bidirectional for the constructs that predate them and one-way for these. That is a real §4 asymmetry, admissible here only because no consumer reads these forms BACK — the sole use is lowering a job condition that GitHub evaluates and nothing in this tree re-ingests. It stops being admissible the moment any consumer parses a condition containing && or ==, which is the trigger: ingest arms for both operators plus round-trip witnesses covering them land before that consumer, not after it. Stated explicitly because the previous revision of this row broke bidirectionality by accident and shipped it; an asymmetry that is declared and triggered is a frontier, one that is merely true is a defect waiting to be discovered." \ No newline at end of file diff --git a/dag/extdeps/github/github.dag b/dag/extdeps/github/github.dag index 12e1206f364..721c62af5d0 100644 --- a/dag/extdeps/github/github.dag +++ b/dag/extdeps/github/github.dag @@ -28,7 +28,17 @@ type Repository { default_branch: String } -data repository_remote_url_note: String = "GitHub's documented HTTPS clone URL for a repository is https://github.com/OWNER/REPO.git (docs.github.com/en/get-started/git-basics/about-remote-repositories, read 2026-08-01). It is projected from owner and name rather than stored, because storing it would be a second representation of a fact those two fields already fix, free to disagree with them.\\n\\nWHY THIS PROJECTION IS WORTH HAVING AT ALL, since git will equally accept the local alias `origin`. An alias is a fact about one checkout's configuration, not about a repository: two checkouts can point `origin` at different repositories and the same repository can be reached under a different alias or none. A caller that reads refs from `origin` and pull requests from an owner/name pair has performed two reads that are only COINCIDENTALLY about the same repository, and nothing in either result would reveal it if they were not. Projecting the remote from the same Repository that names the API target makes them the same subject by construction." +// GitHub's documented HTTPS clone URL for a repository is https://github.com/OWNER/REPO.git +// (docs.github.com/en/get-started/git-basics/about-remote-repositories, read 2026-08-01). It is +// projected from owner and name rather than stored, because storing it would be a second +// representation of a fact those two fields already fix, free to disagree with them.\n\nWHY THIS +// PROJECTION IS WORTH HAVING AT ALL, since git will equally accept the local alias `origin`. An +// alias is a fact about one checkout's configuration, not about a repository: two checkouts can +// point `origin` at different repositories and the same repository can be reached under a different +// alias or none. A caller that reads refs from `origin` and pull requests from an owner/name pair +// has performed two reads that are only COINCIDENTALLY about the same repository, and nothing in +// either result would reveal it if they were not. Projecting the remote from the same Repository +// that names the API target makes them the same subject by construction. fn github_https_clone_url(repo: Repository) -> NonEmptyStr { join(["https://github.com/", repo.owner, "/", repo.name, ".git"], "") as NonEmptyStr diff --git a/dag/extdeps/github/hosted_runners.dag b/dag/extdeps/github/hosted_runners.dag index 58914cd9e66..ee3a52937c6 100644 --- a/dag/extdeps/github/hosted_runners.dag +++ b/dag/extdeps/github/hosted_runners.dag @@ -19,7 +19,18 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data github_hosted_runner_catalog_citation_note: String = "Cited 2026-07-11 from the hosted-runner hardware tables (the anchor above). GitHub publishes DIFFERENT specs for the same label by repository visibility - standard Linux/Windows runners get 4 vCPU / 16GB on public repos and 2 vCPU / 8GB on private repos - so visibility is a first-class axis of the row, never folded into the label (a row without it would be a state-space conflation). Only the Ubuntu (Linux) family is modeled: it is the family this repo can select; Windows/macOS shapes extend on demand from the same tables. memory_bytes records the vendor's decimal-GB figure. The runs_on_label is a RunnerLabel row from the actions grammar (extdeps.github.actions) - the single hosted-label vocabulary, spelled once by runner_label_string - never a free string beside it (a string here would be a second representation of the same label, and hosted labels serialize as the SCALAR runs-on form, which only the HostedRunner variant carries). Version-pinned variants only (Ubuntu2404, Ubuntu2404Arm); ubuntu-latest is a vendor-floating alias of the current x64 row and is deliberately not a row here - pinning by alias would let the vendor move the fact." +// Cited 2026-07-11 from the hosted-runner hardware tables (the anchor above). GitHub publishes +// DIFFERENT specs for one label by repository visibility - standard Linux/Windows runners get +// 4 vCPU / 16GB on public repos and 2 vCPU / 8GB on private - so visibility is a first-class axis +// of the row, never folded into the label (a state-space conflation). Only the Ubuntu (Linux) +// family is modeled, the family this repo can select; Windows/macOS extend on demand from the same +// tables. memory_bytes records the vendor's decimal-GB figure. runs_on_label is a RunnerLabel row +// from extdeps.github.actions - the single hosted-label vocabulary, spelled once by +// runner_label_string, never a free string beside it (a second representation; hosted labels +// serialize as the SCALAR runs-on form, which only the HostedRunner variant carries). +// Version-pinned variants only (Ubuntu2404, Ubuntu2404Arm); ubuntu-latest is a vendor-floating +// alias of the current x64 row and deliberately not a row - pinning by alias lets the vendor move +// the fact. type RepoVisibility = PublicRepo | PrivateRepo diff --git a/dag/extdeps/github/issues.dag b/dag/extdeps/github/issues.dag index 42a416f661a..5339961bd76 100644 --- a/dag/extdeps/github/issues.dag +++ b/dag/extdeps/github/issues.dag @@ -21,9 +21,33 @@ data issue_comments_authority: ExternalAuthority = ExternalAuthority { } } -data issues_module_homing_note: String = "WHY THIS MODULE EXISTS AND WHAT MOVED INTO IT. IssueComment and CreateComment were declared in extdeps.github.pulls, and that was a real external-surface homing defect rather than a filing preference. CreateComment's own transport is POST /repos/\{owner\}/\{repo\}/issues/\{issue_number\}/comments — the ISSUES API, named in its path, parameterized by an issue number — so the pulls module was declaring an operation against an upstream surface it does not model. GitHub's own documentation splits them: the pulls API documents review comments (which are a different resource, anchored to a diff position), while every comment on the conversation timeline of an issue OR a pull request belongs to the issues API. That a pull request is addressable as an issue is a fact about GitHub's model, and modeling it correctly means the comment operation lives with the issues surface and the pulls surface imports nothing to get it.\n\nTHE MOVE IS ATOMIC AND LEAVES NO ALIAS. A compatibility re-export in extdeps.github.pulls would be exactly the second name for one concept that DESIGN §3 forbids, and the usual justification for one — a migration window for consumers out of our control — does not apply, because every consumer is in this tree and moved in the same change. The operation key changes from github.Pulls.CreateComment to github.Issues.CreateComment, which is a visible, greppable rename with a finite consumer set (the mock corpus row, the mock totality lens case, and the Rust REST transport witness), not a silent one." +// WHY THIS MODULE EXISTS AND WHAT MOVED INTO IT. IssueComment and CreateComment were declared in +// extdeps.github.pulls, and that was a real external-surface homing defect rather than a filing +// preference. CreateComment's own transport is POST +// /repos/{owner}/{repo}/issues/{issue_number}/comments — the ISSUES API, named in its path, +// parameterized by an issue number — so the pulls module was declaring an operation against an +// upstream surface it does not model. GitHub's own documentation splits them: the pulls API +// documents review comments (which are a different resource, anchored to a diff position), while +// every comment on the conversation timeline of an issue OR a pull request belongs to the issues +// API. That a pull request is addressable as an issue is a fact about GitHub's model, and modeling +// it correctly means the comment operation lives with the issues surface and the pulls surface +// imports nothing to get it. +// +// THE MOVE IS ATOMIC AND LEAVES NO ALIAS. A compatibility re-export in extdeps.github.pulls would +// be exactly the second name for one concept that DESIGN §3 forbids, and the usual justification +// for one — a migration window for consumers out of our control — does not apply, because every +// consumer is in this tree and moved in the same change. The operation key changes from +// github.Pulls.CreateComment to github.Issues.CreateComment, which is a visible, greppable rename +// with a finite consumer set (the mock corpus row, the mock totality lens case, and the Rust REST +// transport witness), not a silent one. -data issue_state_note: String = "IssueState is Open | Closed and deliberately does NOT carry the state_reason axis (completed / not_planned / reopened) that GitHub added beside it. Those are two independent facts upstream — an issue is closed, and separately there is a recorded reason it was closed — and fusing them into one enum would produce a coproduct where Closed appears three times and every consumer that only cares whether the issue is open has to enumerate reasons it does not use. The alert consumer needs the open/closed axis alone. When a consumer needs the reason, it lands as its own optional field on Issue, cited to the same authority, and this enum is untouched." +// IssueState is Open | Closed and deliberately does NOT carry the state_reason axis (completed / +// not_planned / reopened) that GitHub added beside it. Those are two independent facts upstream — +// an issue is closed, and separately there is a recorded reason it was closed — and fusing them +// into one enum would produce a coproduct where Closed appears three times and every consumer that +// only cares whether the issue is open has to enumerate reasons it does not use. The alert consumer +// needs the open/closed axis alone. When a consumer needs the reason, it lands as its own optional +// field on Issue, cited to the same authority, and this enum is untouched. data extdeps_model_scope: ExternalModelScope = ExternalModelScope { subject: ExternalSubjectRef { @@ -46,7 +70,19 @@ fn issue_state_wire_label(state: IssueState) -> String { } } -data issue_state_read_note: String = "The wire decode returns a THREE-state read rather than an optional, because `IssueState?` conflates two different facts behind one Absent: the member was missing, and the member was present carrying a value this module does not model. Those have different remedies — the first is a malformed or partial payload, the second is GitHub having added a state and this enum needing to grow — and an optional makes them indistinguishable at every call site. That is the empty-observation narrow DESIGN names: an observation that could not express what it saw rendered as the verdict `nothing was there`.\n\nIt is fixed BEFORE a consumer exists rather than after (flagged by review 49457, which reasonably suggested deferring until one lands). The reason not to defer: this same PR removes three Bool projections for exactly this class, and leaving a wildcard-to-Absent decode beside them would make the corpus teach both rules at once. The unmodelled value is carried in the refusal so a future consumer reports what it saw instead of what it failed to find." +// The wire decode returns a THREE-state read rather than an optional, because `IssueState?` +// conflates two different facts behind one Absent: the member was missing, and the member was +// present carrying a value this module does not model. Those have different remedies — the first is +// a malformed or partial payload, the second is GitHub having added a state and this enum needing +// to grow — and an optional makes them indistinguishable at every call site. That is the +// empty-observation narrow DESIGN names: an observation that could not express what it saw rendered +// as the verdict `nothing was there`. +// +// It is fixed BEFORE a consumer exists rather than after (flagged by review 49457, which reasonably +// suggested deferring until one lands). The reason not to defer: this same PR removes three Bool +// projections for exactly this class, and leaving a wildcard-to-Absent decode beside them would +// make the corpus teach both rules at once. The unmodelled value is carried in the refusal so a +// future consumer reports what it saw instead of what it failed to find. type IssueStateRead = IssueStateFound { state: IssueState } @@ -77,9 +113,48 @@ type IssueComment { created_at: Timestamp } -data issues_labels_and_raw_listing_note: String = "TWO NAMING-AND-SHAPE CORRECTIONS, both the same defect seen from different sides (review on the ALERT-0..3 PR).\n\nlabels IS A LIST, NOT A STRING. GitHub's issues API takes labels as a JSON array, and modeling it as one String meant the caller owned the join — which is to say the caller owned a piece of the wire format, in a module whose whole job is to own the wire format. A label containing the separator would have silently become two labels, and the shape the type advertised was not the shape the endpoint accepts.\n\nListForRepo IS RENAMED ListForRepoRaw BECAUSE IT RETURNS UNPARSED TEXT. An operation named for a typed resource whose output is an undecoded String is the naming half of rung inflation: the name asserts a guarantee — you get issues — that the signature does not deliver, and every consumer then writes its own ad-hoc parse while believing it was handed a resource. The honest options were to decode it here or to say what it actually returns; decoding a list of issues needs a total Issue decoder, which is real work with no consumer yet, so the name now matches the output. When a consumer lands, ListForRepo returns List with a decoder beside it and this operation becomes its transport rather than its interface.\n\nTHE TWO labels FIELDS ARE DIFFERENT SHAPES BECAUSE UPSTREAM MAKES THEM DIFFERENT, and this is deliberate rather than an oversight. Create sends labels in the request BODY, where GitHub takes a JSON array, so it is List. ListForRepoRaw sends labels as a QUERY parameter, which GitHub documents as `a list of comma separated label names` (docs.github.com/en/rest/issues/issues#list-repository-issues, read 2026-08-06) — a query string cannot carry a JSON array, so comma-separated text IS the wire form there and typing it as a list would be modeling a shape the endpoint does not accept. The residue this leaves is that the caller still performs the join for the query case; the right end state is a transport that models list-valued query parameters and encodes them per the operation's declared separator, at which point both fields are List and neither caller joins anything. Dissolve-on: the rest transport carries a list-valued query encoding." +// TWO NAMING-AND-SHAPE CORRECTIONS, both the same defect seen from different sides (review on the +// ALERT-0..3 PR). +// +// labels IS A LIST, NOT A STRING. GitHub's issues API takes labels as a JSON array, and modeling it +// as one String meant the caller owned the join — which is to say the caller owned a piece of the +// wire format, in a module whose whole job is to own the wire format. A label containing the +// separator would have silently become two labels, and the shape the type advertised was not the +// shape the endpoint accepts. +// +// ListForRepo IS RENAMED ListForRepoRaw BECAUSE IT RETURNS UNPARSED TEXT. An operation named for a +// typed resource whose output is an undecoded String is the naming half of rung inflation: the name +// asserts a guarantee — you get issues — that the signature does not deliver, and every consumer +// then writes its own ad-hoc parse while believing it was handed a resource. The honest options +// were to decode it here or to say what it actually returns; decoding a list of issues needs a +// total Issue decoder, which is real work with no consumer yet, so the name now matches the output. +// When a consumer lands, ListForRepo returns List with a decoder beside it and this +// operation becomes its transport rather than its interface. +// +// THE TWO labels FIELDS ARE DIFFERENT SHAPES BECAUSE UPSTREAM MAKES THEM DIFFERENT, and this is +// deliberate rather than an oversight. Create sends labels in the request BODY, where GitHub takes +// a JSON array, so it is List. ListForRepoRaw sends labels as a QUERY parameter, which +// GitHub documents as `a list of comma separated label names` +// (docs.github.com/en/rest/issues/issues#list-repository-issues, read 2026-08-06) — a query string +// cannot carry a JSON array, so comma-separated text IS the wire form there and typing it as a list +// would be modeling a shape the endpoint does not accept. The residue this leaves is that the +// caller still performs the join for the query case; the right end state is a transport that models +// list-valued query parameters and encodes them per the operation's declared separator, at which +// point both fields are List and neither caller joins anything. Dissolve-on: the rest +// transport carries a list-valued query encoding. -data issues_list_comments_absent_note: String = "THERE IS DELIBERATELY NO ListComments OPERATION HERE, and its absence is a modeling decision worth stating because the obvious next edit is to add one. The only thing that ever wanted it was a throttle that recovered `when did we last notify` by fetching an issue's comment list and reading the newest one's timestamp. That is a second source of truth for a fact the alert itself produced: the alert knows exactly when it last notified, because it did the notifying. Recovering it by scanning comments made the throttle depend on pagination (the first version read comments[length-1] from one per_page=100 page, so past 100 comments it read the 100th and the throttle inverted into notify-every-run), on nobody else commenting on the issue, and on the comment list being readable at all. The fact belongs in the alert's own typed state marker instead, where it is written by the same step that sends the notification and read back with no API call, no pagination and no boundary. Adding ListComments would re-open all three failure modes to serve a question that no longer needs asking." +// THERE IS DELIBERATELY NO ListComments OPERATION HERE, and its absence is a modeling decision +// worth stating because the obvious next edit is to add one. The only thing that ever wanted it was +// a throttle that recovered `when did we last notify` by fetching an issue's comment list and +// reading the newest one's timestamp. That is a second source of truth for a fact the alert itself +// produced: the alert knows exactly when it last notified, because it did the notifying. Recovering +// it by scanning comments made the throttle depend on pagination (the first version read +// comments[length-1] from one per_page=100 page, so past 100 comments it read the 100th and the +// throttle inverted into notify-every-run), on nobody else commenting on the issue, and on the +// comment list being readable at all. The fact belongs in the alert's own typed state marker +// instead, where it is written by the same step that sends the notification and read back with no +// API call, no pagination and no boundary. Adding ListComments would re-open all three failure +// modes to serve a question that no longer needs asking. service github.Issues { config { diff --git a/dag/extdeps/github/pulls.dag b/dag/extdeps/github/pulls.dag index b1ee2ce2f42..eaafe2cc719 100644 --- a/dag/extdeps/github/pulls.dag +++ b/dag/extdeps/github/pulls.dag @@ -63,7 +63,13 @@ type PullRequest { merge_commit_sha: CommitSha? } -data pull_timing_observation_provenance_note: String = "One-time GitHub REST pull on 2026-08-01 under this module's existing GET-pull authority. Each row is the PullRequest.created_at and PullRequest.merged_at pair returned by github.Pulls.Get, normalized once from the wire Timestamp strings to Unix epoch milliseconds so arithmetic consumers do not pretend Timestamp is subtractable. The source URL remains on every row and fetched_on makes staleness inspectable. PullRequest stays the one wire-response authority; PullTimingObservation is its cited, immutable arithmetic projection, not a second endpoint or response model." +// One-time GitHub REST pull on 2026-08-01 under this module's existing GET-pull authority. Each row +// is the PullRequest.created_at and PullRequest.merged_at pair returned by github.Pulls.Get, +// normalized once from the wire Timestamp strings to Unix epoch milliseconds so arithmetic +// consumers do not pretend Timestamp is subtractable. The source URL remains on every row and +// fetched_on makes staleness inspectable. PullRequest stays the one wire-response authority; +// PullTimingObservation is its cited, immutable arithmetic projection, not a second endpoint or +// response model. type PullTimingObservation { pull_number: Int @@ -130,7 +136,12 @@ data gunbc_pr_7542_timing: PullTimingObservation = pull_timing_observation( source_url: "https://github.com/gunb-ai/gunbc/pull/7542", ) -data github_cli_exact_head_merge_note: String = "GitHub CLI documents --match-head-commit as the commit SHA the pull-request head must match to allow merge and --squash as the repository's squash strategy. The operation exposes termination as data because a nonzero result is a domain refusal the landing workflow must render and receipt, not an interpreter crash. It deliberately carries no --admin and no --auto: landing workflow v1 performs the one operator-clicked merge already admitted by the dashboard summary; merge-queue enqueue remains a later realization." +// GitHub CLI documents --match-head-commit as the commit SHA the pull-request head must match to +// allow merge and --squash as the repository's squash strategy. The operation exposes termination +// as data because a nonzero result is a domain refusal the landing workflow must render and +// receipt, not an interpreter crash. It deliberately carries no --admin and no --auto: landing +// workflow v1 performs the one operator-clicked merge already admitted by the dashboard summary; +// merge-queue enqueue remains a later realization. service github.CliPulls { operation ListOpenJson { diff --git a/dag/extdeps/github/push_event.dag b/dag/extdeps/github/push_event.dag index dd73a9fd983..9e13a07bd63 100644 --- a/dag/extdeps/github/push_event.dag +++ b/dag/extdeps/github/push_event.dag @@ -28,7 +28,10 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data push_event_note: String = "The push webhook payload's `before` member: the full commit SHA of the branch tip immediately before this push. GitHub documents that this is the zero SHA when a branch or tag is created. extdeps owns the wire shape; gunbc.diff_baseline owns the policy that maps it to an affected-set baseline." +// The push webhook payload's `before` member: the full commit SHA of the branch tip immediately +// before this push. GitHub documents that this is the zero SHA when a branch or tag is created. +// extdeps owns the wire shape; gunbc.diff_baseline owns the policy that maps it to an affected-set +// baseline. data push_event_before_member: String = "before" diff --git a/dag/extdeps/github/rulesets.dag b/dag/extdeps/github/rulesets.dag index 51ae27ab718..dc526db0dde 100644 --- a/dag/extdeps/github/rulesets.dag +++ b/dag/extdeps/github/rulesets.dag @@ -15,20 +15,20 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -// THIS MODULE OWNS THE INTERFACE SHAPE OF GITHUB'S REPOSITORY RULESETS API AND NOTHING ELSE. -// Which ruleset this repository wants, which contexts it requires, and what to do when the live -// ruleset disagrees are business policy and live in gunbc.repo_ruleset -- modelling them here -// would be the layer inversion DESIGN section 3 names, and it would make the upstream authority -// carry a fact GitHub has no opinion about. +// THIS MODULE OWNS THE INTERFACE SHAPE OF GITHUB'S REPOSITORY RULESETS API AND NOTHING ELSE. Which +// ruleset this repository wants, which contexts it requires, and what to do when the live ruleset +// disagrees are business policy and live in gunbc.repo_ruleset — modelling them here would be the +// layer inversion DESIGN section 3 names, making the upstream authority carry a fact GitHub has no +// opinion about. // // THE WIRE RECORDS ARE THE DECODE SURFACE, THE COPRODUCTS ARE THE DOMAIN. GitHub's rule list is -// heterogeneous -- a `deletion` rule carries no parameters and a `required_status_checks` rule -// carries a nested object -- and it is open: GitHub adds rule types without asking. A closed -// coproduct decoded straight off the wire would therefore either refuse on an upstream addition -// this repository does not care about, or silently drop it. So the transport decodes into records -// whose type tag is the String GitHub sent, and the projections below carry every tag this module -// recognizes into a closed type WITH an explicit unrecognized arm. An unmodeled rule then arrives -// as a value a consumer must match on, never as an absence. +// heterogeneous — a `deletion` rule carries no parameters, a `required_status_checks` rule a nested +// object — and open: GitHub adds rule types without asking. A closed coproduct decoded straight off +// the wire would either refuse on an upstream addition this repository does not care about or +// silently drop it. So the transport decodes into records whose type tag is the String GitHub sent, +// and the projections below carry every recognized tag into a closed type WITH an explicit +// unrecognized arm. An unmodeled rule arrives as a value a consumer must match on, never as an +// absence. type RulesetEnforcement = RulesetDisabled @@ -54,8 +54,8 @@ type RulesetRuleType | UnrecognizedRuleType { raw: String } // current_user_can_bypass is documented as one of never / pull_requests_only / always. It is the -// only field in the response that answers "can the rule be walked past", which is the question a -// required context exists to make unanswerable, so it is decoded rather than skipped. +// only response field answering "can the rule be walked past" — the question a required context +// exists to make unanswerable — so it is decoded rather than skipped. type RulesetBypassStanding = BypassNever | BypassPullRequestsOnly @@ -88,11 +88,11 @@ type RulesetConditionsWire { } // THE DOCUMENTED 200 SHAPE OF A SINGLE RULESET. The response block below names it; the operations' -// `output` blocks flatten its top-level keys instead of carrying it as one field, and the reason is -// mechanical rather than stylistic: the REST realization projects a JSON object into the output -// record BY KEY, so an output field named `ruleset` looks for a `ruleset` key GitHub does not send -// and binds Null. Flattening keeps every field the documented shape declares, in the documented -// spelling, reachable -- and the response block keeps the shape itself named in one place. +// `output` blocks flatten its top-level keys instead of carrying it as one field, for a mechanical +// reason: the REST realization projects a JSON object into the output record BY KEY, so an output +// field named `ruleset` looks for a `ruleset` key GitHub does not send and binds Null. Flattening +// keeps every documented field, in the documented spelling, reachable — and the response block +// keeps the shape itself named in one place. type RulesetWire { id: Int name: String @@ -195,12 +195,12 @@ fn rule_is_required_status_checks(rule: RulesetRuleWire) -> Bool { } } -// THREE STATES, NOT AN EMPTY LIST. "This ruleset requires no status checks", "this ruleset -// requires exactly these", and "this ruleset carries more than one required_status_checks rule" -// are different facts with different remedies, and the first and third both render as [] under a -// projection that returns a bare roster. The third is a refusal rather than a merge of the two -// rosters: GitHub admits at most one such rule per ruleset today, so encountering two means the -// upstream shape moved and this module's reading of it is no longer established. +// THREE STATES, NOT AN EMPTY LIST. "Requires no status checks", "requires exactly these", and +// "carries more than one required_status_checks rule" are different facts with different remedies, +// and the first and third both render as [] under a projection returning a bare roster. The third +// is a refusal rather than a merge of the two rosters: GitHub admits at most one such rule per +// ruleset today, so two means the upstream shape moved and this module's reading of it is no longer +// established. type RequiredStatusCheckStanding = NoRequiredStatusChecksRule | RequiredStatusChecks { @@ -263,8 +263,8 @@ fn ref_name_conditions(include: List, exclude: List) -> RulesetC } // GitHub's documented alias for "whatever this repository's default branch currently is". Naming -// the branch literally would fork the fact with the repository's own default_branch and would go -// silently wrong the day it is renamed. +// the branch literally would fork the fact with the repository's own default_branch and go silently +// wrong the day it is renamed. data default_branch_ref_alias: String = "~DEFAULT_BRANCH" service github.Rulesets { diff --git a/dag/extdeps/github/workflow_run_event.dag b/dag/extdeps/github/workflow_run_event.dag index 3303c9f010d..5de376cb297 100644 --- a/dag/extdeps/github/workflow_run_event.dag +++ b/dag/extdeps/github/workflow_run_event.dag @@ -27,9 +27,30 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data workflow_run_event_surface_note: String = "THIS IS THE EVENT PAYLOAD, NOT THE REST RESOURCE, AND THE TWO ARE DELIBERATELY NOT ONE CARRIER. extdeps.github.workflow_runs models what `GET /repos/\{owner\}/\{repo\}/actions/runs` RETURNS — a WorkflowRun resource, listed and filtered by an API client that asked for it. This module models what GitHub DELIVERS to a workflow_run-triggered job through GITHUB_EVENT_PATH: a webhook envelope whose members overlap the resource's but whose shape, nullability and surrounding context are the webhook's own. Forcing the event's fields onto WorkflowRun would make one carrier answer to two upstream authorities that version independently — the external-decomposition rule in DESIGN §3 — and it would also be a lie about nullability, because the resource is fetched after the fact while the event is delivered at an activity boundary where conclusion may legitimately be null. WHAT IS SHARED IS SHARED: WorkflowRunConclusion is imported, not re-declared, because the conclusion vocabulary genuinely is one upstream vocabulary and a second copy of it would be the §3 nickname. The envelope, not the vocabulary, is what forks." +// THIS IS THE EVENT PAYLOAD, NOT THE REST RESOURCE, AND THE TWO ARE DELIBERATELY NOT ONE CARRIER. +// extdeps.github.workflow_runs models what `GET /repos/{owner}/{repo}/actions/runs` RETURNS — a +// WorkflowRun resource, listed and filtered by an API client that asked for it. This module models +// what GitHub DELIVERS to a workflow_run-triggered job through GITHUB_EVENT_PATH: a webhook +// envelope whose members overlap the resource's but whose shape, nullability and surrounding +// context are the webhook's own. Forcing the event's fields onto WorkflowRun would make one carrier +// answer to two upstream authorities that version independently — the external-decomposition rule +// in DESIGN §3 — and it would also be a lie about nullability, because the resource is fetched +// after the fact while the event is delivered at an activity boundary where conclusion may +// legitimately be null. WHAT IS SHARED IS SHARED: WorkflowRunConclusion is imported, not +// re-declared, because the conclusion vocabulary genuinely is one upstream vocabulary and a second +// copy of it would be the §3 nickname. The envelope, not the vocabulary, is what forks. -data workflow_run_event_run_id_carrier_note: String = "run_id is carried as the decimal digits VERBATIM rather than decoded to an Int, and that is a modeling decision with a receipt on both sides. There is no JSON-number-to-Int decode anywhere in this tree — extdeps.languages.json.emit keeps every number as a JsonNumberLexeme (a branded String) precisely so that parsing never silently reinterprets a lexeme — so decoding here would mint the repo's first such decode inside a webhook reader, which is the wrong place to introduce it and would need its own overflow and leading-zero story. It is also unnecessary: the only consumer question is IDENTITY — does the receipt this alert downloaded come from the run this event describes — and gunbc.floor_component_receipt already carries its subject run_id as NonEmptyStr, so the join is string-to-string at both ends with no decode on either side. A decode would add a lossy step between two carriers that already agree. If arithmetic on run ids is ever needed, the decode lands in the JSON authority with its own witness, not here." +// run_id is carried as the decimal digits VERBATIM rather than decoded to an Int, and that is a +// modeling decision with a receipt on both sides. There is no JSON-number-to-Int decode anywhere in +// this tree — extdeps.languages.json.emit keeps every number as a JsonNumberLexeme (a branded +// String) precisely so that parsing never silently reinterprets a lexeme — so decoding here would +// mint the repo's first such decode inside a webhook reader, which is the wrong place to introduce +// it and would need its own overflow and leading-zero story. It is also unnecessary: the only +// consumer question is IDENTITY — does the receipt this alert downloaded come from the run this +// event describes — and gunbc.floor_component_receipt already carries its subject run_id as +// NonEmptyStr, so the join is string-to-string at both ends with no decode on either side. A decode +// would add a lossy step between two carriers that already agree. If arithmetic on run ids is ever +// needed, the decode lands in the JSON authority with its own witness, not here. data workflow_run_event_member: String = "workflow_run" @@ -97,7 +118,31 @@ type WorkflowRunEventText = WorkflowRunEventTextFound { text: String } | WorkflowRunEventTextFailed { observation: WorkflowRunEventObservation } -data workflow_run_event_field_specific_decode_note: String = "TWO DECODERS, NOT ONE, AND THE NUMERIC WIDENING BELONGS TO EXACTLY ONE OF THEM. An earlier revision had a single helper that admitted a JSON string OR a JSON number and used it for every member — workflow name, conclusion, html_url, run_started_at, head_sha, head_branch, repository, originating event, and run id alike. The widening was introduced for run id, which GitHub genuinely delivers as a number, and it then applied to eight members for which a number is not a representation at all but a sign the payload shape changed or something else is answering.\n\nThat is the absorbing fallback in decoder form: the arm that should refuse instead widens, so `\"conclusion\": 0` or `\"head_branch\": 42` decodes to the text \"0\" and \"42\" and flows into a TRUST WALL that compares it against an expected value. It would not match, so it would refuse — but it would refuse with the WRONG reason (branch is not main) instead of the true one (branch is not a string), and a decoder whose diagnostics are wrong about why is a decoder nobody can debug against.\n\nSo: workflow_run_event_required_string_at admits ONLY a JSON string, and workflow_run_event_numeric_identity_at is the single place a numeric lexeme is accepted.\n\nIT HAS TWO CONSUMERS, AND ITS NAME NOW SAYS SO. It was called workflow_run_event_run_id_at while serving both `id` and `workflow_id`, and this paragraph described the widening as belonging to `exactly one member` — true of the decoder, false of the members, which is the prose-contradicting-mechanism class this file has already been caught by once. Both consumers are GitHub numeric identities and both are genuinely delivered as numbers, so one reader is correct; what was wrong was a name asserting a narrower scope than the code had. The widening is bounded by that reader's call sites, which are these two, and planting a number at any other member produces a located refusal naming that member." +// TWO DECODERS, NOT ONE, AND THE NUMERIC WIDENING BELONGS TO EXACTLY ONE OF THEM. An earlier +// revision had a single helper that admitted a JSON string OR a JSON number and used it for every +// member — workflow name, conclusion, html_url, run_started_at, head_sha, head_branch, repository, +// originating event, and run id alike. The widening was introduced for run id, which GitHub +// genuinely delivers as a number, and it then applied to eight members for which a number is not a +// representation at all but a sign the payload shape changed or something else is answering. +// +// That is the absorbing fallback in decoder form: the arm that should refuse instead widens, so +// `"conclusion": 0` or `"head_branch": 42` decodes to the text "0" and "42" and flows into a TRUST +// WALL that compares it against an expected value. It would not match, so it would refuse — but it +// would refuse with the WRONG reason (branch is not main) instead of the true one (branch is not a +// string), and a decoder whose diagnostics are wrong about why is a decoder nobody can debug +// against. +// +// So: workflow_run_event_required_string_at admits ONLY a JSON string, and +// workflow_run_event_numeric_identity_at is the single place a numeric lexeme is accepted. +// +// IT HAS TWO CONSUMERS, AND ITS NAME NOW SAYS SO. It was called workflow_run_event_run_id_at while +// serving both `id` and `workflow_id`, and this paragraph described the widening as belonging to +// `exactly one member` — true of the decoder, false of the members, which is the +// prose-contradicting-mechanism class this file has already been caught by once. Both consumers are +// GitHub numeric identities and both are genuinely delivered as numbers, so one reader is correct; +// what was wrong was a name asserting a narrower scope than the code had. The widening is bounded +// by that reader's call sites, which are these two, and planting a number at any other member +// produces a located refusal naming that member. fn workflow_run_event_required_string_at(v: JsonValue, key: String) -> WorkflowRunEventText { match workflow_run_event_member_at(v: v, key: key) { @@ -137,7 +182,12 @@ fn workflow_run_event_required_string_at(v: JsonValue, key: String) -> WorkflowR } } -data workflow_run_event_run_id_decode_note: String = "THE ONLY MEMBER WHOSE DECODER ADMITS A NUMBER. GitHub delivers workflow_run.id as a JSON number, and the lexeme is the digits the payload actually carried, unchanged — accepting it is not a coercion. A string is admitted beside it because the identity comparison downstream is string-to-string either way and a payload quoting the id is still unambiguously the same id. Every non-textual, non-numeric shape refuses, so this decoder is exactly one wire form wider than the required-string decoder and not one form wider than that." +// THE ONLY MEMBER WHOSE DECODER ADMITS A NUMBER. GitHub delivers workflow_run.id as a JSON number, +// and the lexeme is the digits the payload actually carried, unchanged — accepting it is not a +// coercion. A string is admitted beside it because the identity comparison downstream is +// string-to-string either way and a payload quoting the id is still unambiguously the same id. +// Every non-textual, non-numeric shape refuses, so this decoder is exactly one wire form wider than +// the required-string decoder and not one form wider than that. fn workflow_run_event_numeric_identity_at(v: JsonValue, key: String) -> WorkflowRunEventText { match workflow_run_event_member_at(v: v, key: key) { @@ -189,9 +239,27 @@ fn workflow_run_event_nested_text(v: JsonValue, outer: String, inner: String) -> } } -data workflow_run_event_conclusion_required_note: String = "conclusion is REQUIRED by this decoder and a null one is a located malformation rather than an Absent field, because of what the envelope is FOR. GitHub delivers workflow_run at three activities and leaves conclusion null until the run completes; this module is read only from a WorkflowRunCompleted trigger, where the activity is `completed` and a conclusion therefore exists. Modeling it as optional would push a state that cannot occur on this path onto every consumer as an arm they must invent an answer for — and the plausible answer (treat null as `not finished yet, do nothing`) is indistinguishable from the real defect (the payload changed shape underneath us, or the trigger was widened without revisiting this reader). Both must be loud. The activity member is decoded and carried BESIDE the conclusion for the same reason: the admission wall re-checks it rather than trusting the trigger filter, so a widened trigger is caught by the reader instead of silently admitted." +// conclusion is REQUIRED by this decoder and a null one is a located malformation rather than an +// Absent field, because of what the envelope is FOR. GitHub delivers workflow_run at three +// activities and leaves conclusion null until the run completes; this module is read only from a +// WorkflowRunCompleted trigger, where the activity is `completed` and a conclusion therefore +// exists. Modeling it as optional would push a state that cannot occur on this path onto every +// consumer as an arm they must invent an answer for — and the plausible answer (treat null as `not +// finished yet, do nothing`) is indistinguishable from the real defect (the payload changed shape +// underneath us, or the trigger was widened without revisiting this reader). Both must be loud. The +// activity member is decoded and carried BESIDE the conclusion for the same reason: the admission +// wall re-checks it rather than trusting the trigger filter, so a widened trigger is caught by the +// reader instead of silently admitted. -data workflow_run_event_total_decode_note: String = "The decode is a nested chain of total matches with NO catch-all and NO default value anywhere. Every member that cannot be read short-circuits to the SAME observation coproduct carrying the member name that failed and why, so a payload-shape change reports which field moved rather than surfacing as a downstream nonsense value. This is the push-event precedent (extdeps.github.push_event push_before_from_event_json) applied to a larger record: one Absent arm, one Malformed arm, one read-refusal arm, no arm that guesses. The nesting is the price of threading a short-circuit through a language with no early return, and it is deliberately paid here — in the reader — rather than by giving fields defaults that consumers would then have to distrust." +// The decode is a nested chain of total matches with NO catch-all and NO default value anywhere. +// Every member that cannot be read short-circuits to the SAME observation coproduct carrying the +// member name that failed and why, so a payload-shape change reports which field moved rather than +// surfacing as a downstream nonsense value. This is the push-event precedent +// (extdeps.github.push_event push_before_from_event_json) applied to a larger record: one Absent +// arm, one Malformed arm, one read-refusal arm, no arm that guesses. The nesting is the price of +// threading a short-circuit through a language with no early return, and it is deliberately paid +// here — in the reader — rather than by giving fields defaults that consumers would then have to +// distrust. fn workflow_run_event_assemble( activity: String, diff --git a/dag/extdeps/github/workflows.dag b/dag/extdeps/github/workflows.dag index 7f2ae0330b2..9ca42490bfd 100644 --- a/dag/extdeps/github/workflows.dag +++ b/dag/extdeps/github/workflows.dag @@ -16,7 +16,13 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { data workflows_api_version: NonEmptyStr = "2026-03-10" -data create_dispatch_version_fidelity_note: String = "Create Workflow Dispatch has two live response contracts across GitHub REST versions. Requests without X-GitHub-Api-Version default to 2022-11-28 and return 204 No Content. Version 2026-03-10 returns 200 with workflow_run_id, run_url, and html_url. This service explicitly pins 2026-03-10 in the transport header, so WorkflowDispatchReceipt is the cited response rather than a fabricated body projected from an unversioned 204. Authority: docs.github.com/en/rest/about-the-rest-api/api-versions and the Create a workflow dispatch event response schema, read 2026-08-01." +// Create Workflow Dispatch has two live response contracts across GitHub REST versions. Requests +// without X-GitHub-Api-Version default to 2022-11-28 and return 204 No Content. Version 2026-03-10 +// returns 200 with workflow_run_id, run_url, and html_url. This service explicitly pins 2026-03-10 +// in the transport header, so WorkflowDispatchReceipt is the cited response rather than a +// fabricated body projected from an unversioned 204. Authority: +// docs.github.com/en/rest/about-the-rest-api/api-versions and the Create a workflow dispatch event +// response schema, read 2026-08-01. type WorkflowDispatchReceipt { workflow_run_id: Int @@ -24,6 +30,12 @@ type WorkflowDispatchReceipt { html_url: NonEmptyStr } +// CreateDispatch is intentionally single-attempt. Once the heal push has created a new subject, a +// dispatch refusal must be visible as HealDispatchRefused and leave revalidation required; silently +// retrying a permission, rate-limit, or server refusal into apparent success would erase the +// classification operators need. The owning workflow may be retriggered deliberately after +// diagnosis, but this operation never assumes that the GITHUB_TOKEN push created a run. + service github.Workflows { config { endpoint: default_api_base @@ -66,5 +78,3 @@ service github.Workflows { } } } - -data create_dispatch_retry_policy_note: String = "CreateDispatch is intentionally single-attempt. Once the heal push has created a new subject, a dispatch refusal must be visible as HealDispatchRefused and leave revalidation required; silently retrying a permission, rate-limit, or server refusal into apparent success would erase the classification operators need. The owning workflow may be retriggered deliberately after diagnosis, but this operation never assumes that the GITHUB_TOKEN push created a run." diff --git a/dag/extdeps/http/server.dag b/dag/extdeps/http/server.dag index b39b0485519..a21928a6f14 100644 --- a/dag/extdeps/http/server.dag +++ b/dag/extdeps/http/server.dag @@ -44,7 +44,18 @@ data charset_registry_authority: ExternalAuthority = ExternalAuthority { } } -data media_type_note: String = "Cited upstream: a media type is type \"/\" subtype with optional \"; \" parameters (RFC 9110 section 8.3.1 media-type grammar; the Content-Type header carries exactly one). The top-level type is a CLOSED registry (RFC 6838 section 4.2 + the IANA media-types registry: application/audio/example/font/image/message/model/multipart/text/video); the subtype is an upstream-spelled string (each its own IANA registration); parameters are name=value pairs, of which charset (value from the IANA character-sets registry, utf-8 per RFC 3629) is the one this served stack uses. The WIRE STRING is a SERIALIZATION of this structure (DESIGN section 2, decompress the string): the fused HttpContentType enum that hid media-type x charset inside variant NAMES is deleted, media_type_wire_label is the sole serializer, and ApplicationOctetStream's charset-less form falls out as an EMPTY parameter list rather than a special variant. Forward reading (parsing an incoming Content-Type through these same rows) is the NAMED later direction (DESIGN section 4, one grammar both ways), not built here." +// Cited upstream: a media type is type "/" subtype with optional "; " parameters (RFC 9110 section +// 8.3.1 media-type grammar; the Content-Type header carries exactly one). The top-level type is a +// CLOSED registry (RFC 6838 section 4.2 + the IANA media-types registry: +// application/audio/example/font/image/message/model/multipart/text/video); the subtype is an +// upstream-spelled string (each its own IANA registration); parameters are name=value pairs, of +// which charset (value from the IANA character-sets registry, utf-8 per RFC 3629) is the one this +// served stack uses. The WIRE STRING is a SERIALIZATION of this structure (DESIGN section 2, +// decompress the string): the fused HttpContentType enum that hid media-type x charset inside +// variant NAMES is deleted, media_type_wire_label is the sole serializer, and +// ApplicationOctetStream's charset-less form falls out as an EMPTY parameter list rather than a +// special variant. Forward reading (parsing an incoming Content-Type through these same rows) is +// the NAMED later direction (DESIGN section 4, one grammar both ways), not built here. type MediaTopLevel = TopLevelApplication diff --git a/dag/extdeps/instrument/pip_boy_3000.dag b/dag/extdeps/instrument/pip_boy_3000.dag index bca36cea859..dc71530f7e3 100644 --- a/dag/extdeps/instrument/pip_boy_3000.dag +++ b/dag/extdeps/instrument/pip_boy_3000.dag @@ -57,7 +57,15 @@ data subject_separation_note: String = "This module models the Fallout 3 / New V data manual_read_path_note: String = "PROVENANCE: every setting row below is quoted from a DIRECT read of the manual URI above (2026-08-04). An earlier revision of this module carried search-index quotations because the fetcher was refused HTTP 403; the refusal was the fetcher's user agent, not the site, and a browser user agent retrieves the page at HTTP 200. The typed read receipt lives downstream in gunbc.design.reference_instrument." -data compliance_block_names_the_mk_v_note: String = "THE FACT THAT INVERTED THE FIRST REVISION OF THIS MODULE, recorded because it will mislead the next reader the same way. This manual's regulatory block reads: 'Model name: PIP-BOY 3000 Mk V, Model number: WRC18423. Issuing Party: The Wand Company Limited' — the FALLOUT 3 / NEW VEGAS manual page carries the Mk V model name and number in its compliance footer, identically to the TV-series manual. A search index reading this page therefore has 'Mk V' and 'WRC18423' in scope beside every setting on it, and duly attributed Glitch on tap and Tap threshold to the Mk V. They are settings of THIS product. The model number is consequently NOT a product discriminator and no module should carry it as one; the discriminators that hold are the manual URI, the licensor on the retail listing, and the settings menus themselves." +// THE FACT THAT INVERTED THE FIRST REVISION OF THIS MODULE, recorded because it will mislead the +// next reader the same way. This manual's regulatory block reads: 'Model name: PIP-BOY 3000 Mk V, +// Model number: WRC18423. Issuing Party: The Wand Company Limited' — the FALLOUT 3 / NEW VEGAS +// manual page carries the Mk V model name and number in its compliance footer, identically to the +// TV-series manual. A search index reading this page therefore has 'Mk V' and 'WRC18423' in scope +// beside every setting on it, and duly attributed Glitch on tap and Tap threshold to the Mk V. They +// are settings of THIS product. The model number is consequently NOT a product discriminator and no +// module should carry it as one; the discriminators that hold are the manual URI, the licensor on +// the retail listing, and the settings menus themselves. type PipBoy3000Setting = DisplayTimeout @@ -167,7 +175,10 @@ data pip_boy_3000_glitch_modes: List = [ GlitchPersistent ] -data settings_roster_closure_note: String = "The Display and Sound settings groups as the manual enumerates them, read directly. Absence from this roster is still not evidence of absence from the product — the manual carries further groups this module does not model — so a consumer asking whether some other setting exists gets a coverage obligation downstream, never a denial from a short roster." +// The Display and Sound settings groups as the manual enumerates them, read directly. Absence from +// this roster is still not evidence of absence from the product — the manual carries further groups +// this module does not model — so a consumer asking whether some other setting exists gets a +// coverage obligation downstream, never a denial from a short roster. type PipBoy3000MotionFeature = TiltDerivedGeigerSimulation @@ -192,12 +203,21 @@ data pip_boy_3000_motion_features: List = [ ImpulseGlitchOnTap ] -data motion_feature_note: String = "THREE accelerometer-derived behaviours, all on this product: the tilt-driven Geiger simulation, a raw engineering readout that graphs the accelerometer measurements, and the impulse glitch that a tap causes or resolves. Together they are why this product — the one the frontend directive actually named — is the grounded realization of the impulse behaviour the design lane wants, not merely a unit that happens to contain a sensor." +// THREE accelerometer-derived behaviours, all on this product: the tilt-driven Geiger simulation, a +// raw engineering readout that graphs the accelerometer measurements, and the impulse glitch that a +// tap causes or resolves. Together they are why this product — the one the frontend directive +// actually named — is the grounded realization of the impulse behaviour the design lane wants, not +// merely a unit that happens to contain a sensor. data retail_listing_shape_note: String = "The listing facts below are flat cited leaves rather than a RetailListingFacts record, deliberately. The record shape has no single home today: it cannot live in either product module without the sibling re-declaring it (the section 2 failure — net concepts growing by re-invention), it cannot live in a generic extdeps hub because a hub may not carry product-specific rows, and it cannot live in the downstream join without inverting the citation direction. Two verbatim quotations beside an already-typed authority do not need a shape to be grounded. Dissolve-on: a second consumer needs the listing as a subject, at which point the shape gets a real home and both products reference it." data pip_boy_3000_retail_listing_title: NonEmptyStr = "The Wand Company Pip-Boy 3000 Fallout 3 New Vegas die-cast Replica. FM Radio and Alarm, LCD Screen, with Memory Foam Cuff and Stand. Officially Licensed by Bethesda." -data pip_boy_3000_licensing_statement: NonEmptyStr = "Officially Licensed by Bethesda." +// licensing_statement is the listing's own sentence, quoted — deliberately NOT a modeled licensor +// entity. Bethesda has no declaration in this repository, and minting a Vendor row for a company we +// reference exactly once, in order to hold a fact whose whole job is to distinguish two products, +// would invent an authority instead of citing one (DESIGN section 2: net concepts must not grow by +// re-invention). Dissolve-on: a consumer needs the licensor as a subject in its own right, at which +// point it gets a cited extdeps.vendor row and this field references it. -data licensing_statement_grain_note: String = "licensing_statement is the listing's own sentence, quoted — deliberately NOT a modeled licensor entity. Bethesda has no declaration in this repository, and minting a Vendor row for a company we reference exactly once, in order to hold a fact whose whole job is to distinguish two products, would invent an authority instead of citing one (DESIGN section 2: net concepts must not grow by re-invention). Dissolve-on: a consumer needs the licensor as a subject in its own right, at which point it gets a cited extdeps.vendor row and this field references it." +data pip_boy_3000_licensing_statement: NonEmptyStr = "Officially Licensed by Bethesda." diff --git a/dag/extdeps/instrument/pip_boy_3000_mk_v.dag b/dag/extdeps/instrument/pip_boy_3000_mk_v.dag index 644e85f4e35..654e864c13a 100644 --- a/dag/extdeps/instrument/pip_boy_3000_mk_v.dag +++ b/dag/extdeps/instrument/pip_boy_3000_mk_v.dag @@ -59,7 +59,21 @@ data manual_read_path_note: String = "PROVENANCE: read directly from the manual data maintenance_submenu_wording: NonEmptyStr = "The Pip-Boy's various settings can be adjusted via the MAINTENANCE submenu." -data variant_identifiers_must_be_corpus_unique_note: String = "MkVClock IS THE MANUAL'S 'Clock', RENAMED BECAUSE THE IDENTIFIER COLLIDED — the cited wording is untouched in the label below and in the retail listing, so nothing about the upstream claim moved. The bare identifier had to move because the interpreter resolves a data item's free names through a FLAT, corpus-wide registry: when the floor re-evaluates an imported data item to compute the affected set, it evaluates the item's body in the IMPORTING entry's closure with an empty environment, so every free name resolves against that closure's registry rather than this module's scope. `Clock` is already taken there — extdeps.clock declares a service Clock and std.resources declares a resource Clock — so `pip_boy_mk_v_capabilities` re-evaluated inside a witness bound the service, found no variant, and refused with `undefined variable: Clock`. It typechecked and ran green everywhere EXCEPT that path, which is why it reached CI: the collision is invisible from inside the module that owns the name. The other seven identifiers are corpus-unique and stay as authored. The rule this leaves behind for anyone adding a capability: a nullary variant identifier is a corpus-wide name, not a module-local one, so check it is unique before authoring it — the type-stem prefix the rest of the corpus uses (SalienceFocal, ReadingStatus, AuditionRestrained) is the convention that makes that automatic." +// MkVClock IS THE MANUAL'S 'Clock', RENAMED BECAUSE THE IDENTIFIER COLLIDED — the cited wording is +// untouched in the label below and in the retail listing, so nothing about the upstream claim +// moved. The bare identifier had to move because the interpreter resolves a data item's free names +// through a FLAT, corpus-wide registry: when the floor re-evaluates an imported data item to +// compute the affected set, it evaluates the item's body in the IMPORTING entry's closure with an +// empty environment, so every free name resolves against that closure's registry rather than this +// module's scope. `Clock` is already taken there — extdeps.clock declares a service Clock and +// std.resources declares a resource Clock — so `pip_boy_mk_v_capabilities` re-evaluated inside a +// witness bound the service, found no variant, and refused with `undefined variable: Clock`. It +// typechecked and ran green everywhere EXCEPT that path, which is why it reached CI: the collision +// is invisible from inside the module that owns the name. The other seven identifiers are +// corpus-unique and stay as authored. The rule this leaves behind for anyone adding a capability: a +// nullary variant identifier is a corpus-wide name, not a module-local one, so check it is unique +// before authoring it — the type-stem prefix the rest of the corpus uses (SalienceFocal, +// ReadingStatus, AuditionRestrained) is the convention that makes that automatic. type PipBoyMkVCapability = DisplayColourSelection @@ -95,7 +109,12 @@ data pip_boy_mk_v_capabilities: List = [ FmRadio ] -data capability_roster_grain_note: String = "These are the manual's own section headings for what the MAINTENANCE submenu and the mode selector expose, not a settings-menu roster transcribed entry by entry as the sibling module carries. The grain differs because the two manuals are written differently, and forcing this product's facts into the sibling's shape would be modeling the sibling rather than this product. Absence of a display-effect capability here is not a denial: the downstream join holds that as a typed coverage obligation." +// These are the manual's own section headings for what the MAINTENANCE submenu and the mode +// selector expose, not a settings-menu roster transcribed entry by entry as the sibling module +// carries. The grain differs because the two manuals are written differently, and forcing this +// product's facts into the sibling's shape would be modeling the sibling rather than this product. +// Absence of a display-effect capability here is not a denial: the downstream join holds that as a +// typed coverage obligation. // The regulatory block — 'Model name: PIP-BOY 3000 Mk V, Model number: WRC18423. Issuing Party: The Wand Company Limited' — appears IDENTICALLY in this manual and in the Fallout 3 / New Vegas manual. It therefore does NOT discriminate the two products, and an earlier revision of this module carried WRC18423 as this product's identifying model number on exactly that mistake. No model-number row is declared here. The discriminators that actually hold are the manual URI, the licensor on the retail listing, and the capability rosters themselves. data pip_boy_mk_v_retail_listing_title: NonEmptyStr = "Fallout from The Wand Company: Pip-Boy Life Size Wearable Cosplay die-cast Replica. Memory Foam Cuff, LCD Screen, FM Radio, Alarm Clock and Stand. Officially Licensed Collectible by Amazon Studios." diff --git a/dag/extdeps/ipc/dpmx.dag b/dag/extdeps/ipc/dpmx.dag index adf91df53be..a915a05bf70 100644 --- a/dag/extdeps/ipc/dpmx.dag +++ b/dag/extdeps/ipc/dpmx.dag @@ -18,7 +18,19 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { // page does NOT state a current revision designation, so no revision is recorded here; a // revision is a fact this repository does not yet hold, and inventing "Revision C" from // recollection would be exactly the fabricated citation DESIGN §3 rules against. -data ipc_dpmx_subject_note: String = "IPC-DPMX (IPC-2581), the PCB and assembly manufacturing description exchange format, cited to its own consortium. Purpose, quoted exactly from the fetched page: 'a generic standard for printed circuit board and assembly manufacturing description data and transfer methodology'. THE REVISION IS DELIBERATELY ABSENT: the consortium landing page states no revision designation, and this repository has not read the standard document itself, so the revision — which a manufacturing handoff genuinely depends on — is an unresolved obligation rather than a field filled from memory. WHY THIS IS A SEPARATE SUBJECT FROM GERBER: they are independently governed by different organizations (IPC-2581 Consortium here, Ucamco for Gerber), so they are two upstream authorities under DESIGN §3 external upstream decomposition, never one 'PCB output format' enum. WHAT THIS SUBJECT DOES NOT ESTABLISH: schema validity of a file in this format is not correctness, completeness, or manufacturability of the board it describes — those are separate admissions carried at the product layer, and conflating them is the nearby-question failure a green emitter test invites." +// IPC-DPMX (IPC-2581), the PCB and assembly manufacturing description exchange format, cited to its +// own consortium. Purpose, quoted exactly from the fetched page: 'a generic standard for printed +// circuit board and assembly manufacturing description data and transfer methodology'. THE REVISION +// IS DELIBERATELY ABSENT: the consortium landing page states no revision designation, and this +// repository has not read the standard document itself, so the revision — which a manufacturing +// handoff genuinely depends on — is an unresolved obligation rather than a field filled from +// memory. WHY THIS IS A SEPARATE SUBJECT FROM GERBER: they are independently governed by different +// organizations (IPC-2581 Consortium here, Ucamco for Gerber), so they are two upstream authorities +// under DESIGN §3 external upstream decomposition, never one 'PCB output format' enum. WHAT THIS +// SUBJECT DOES NOT ESTABLISH: schema validity of a file in this format is not correctness, +// completeness, or manufacturability of the board it describes — those are separate admissions +// carried at the product layer, and conflating them is the nearby-question failure a green emitter +// test invites. type IpcDpmxFormat = | IpcDpmxFormatTarget diff --git a/dag/extdeps/iproute2/ss.dag b/dag/extdeps/iproute2/ss.dag index fcddf400d64..8f82623478b 100644 --- a/dag/extdeps/iproute2/ss.dag +++ b/dag/extdeps/iproute2/ss.dag @@ -3,7 +3,9 @@ module extdeps.iproute2.ss import std.types { String, Bool } import std.algebra { trim } -data ss_authority_note: String = "ss(8) from iproute2, cited to the upstream manual. The socket-statistics query this repository consumes is a LISTENING-SOCKET ENUMERATION filtered to one port, whose answer is the population itself rather than a status code." +// ss(8) from iproute2, cited to the upstream manual. The socket-statistics query this repository +// consumes is a LISTENING-SOCKET ENUMERATION filtered to one port, whose answer is the population +// itself rather than a status code. // ENUMERATION IS THE ONLY SHAPE THAT CAN PROVE NOTHING IS SERVING A PORT. // diff --git a/dag/extdeps/jobserver.dag b/dag/extdeps/jobserver.dag index 7020b8daa07..e978d1bdebd 100644 --- a/dag/extdeps/jobserver.dag +++ b/dag/extdeps/jobserver.dag @@ -47,7 +47,29 @@ type JobserverCitation = JobserverCited { config: JobserverConfig, provenance: String } | JobserverAbsent -data jobserver_config_citation_note: String = "CITED 2026-08-06 from the installed jobserver on srv1, replacing JobserverAbsent. The three knobs are read from /usr/local/sbin/ctrl-jobserver's own defaults — CTRL_JOBSERVER_MARGIN=8, CTRL_JOBSERVER_MEM_RESERVE_GIB=16, CTRL_JOBSERVER_MIB_PER_JOB=1792 — and the script documents its derivation as tokens = max(1, min(cores - MARGIN, floor(usable_mib / MIB_PER_JOB))) with usable_mib = (MemTotal_gib - MEM_RESERVE_GIB) * 1024.\n\nThat is derive_jobserver_tokens above, term for term, which is why this is a citation rather than an approximation: the model was already carrying the installer's formula and lacked only its inputs. The absence was never a modeling gap — it was a reach gap, and it closed the moment the host became readable.\n\nWHAT CITING DOES AND DOES NOT CHANGE: it does not move any current number. gunbc_compile_pool_placement is CompilePoolInRunnerSlots, so compile_pool_bytes_for still derives zero regardless of this citation; what it unblocks is the flip to CompilePoolSliceDeclared, which previously refused with CompilePoolUnresolved and made the whole runner_deployment_plan PlanUnsound. That refusal was correct and is now discharged.\n\nPLACEMENT CAVEAT, stated rather than silently accepted: these are facts about THIS FLEET's installed jobserver, not about the GNU make jobserver protocol this module cites, so a stricter section 3 reading puts the row in a gunbc module and leaves the protocol shape here. It stays put because every consumer already reads it here and moving it is a rename across a live seam — the same class of migration whose hazards are recorded in gunbc.host_converge. Dissolve-on: the row moves when a gunbc-owned jobserver replaces ctrl-jobserver and the citation follows it." +// CITED 2026-08-06 from the installed jobserver on srv1, replacing JobserverAbsent. The three knobs +// are read from /usr/local/sbin/ctrl-jobserver's own defaults — CTRL_JOBSERVER_MARGIN=8, +// CTRL_JOBSERVER_MEM_RESERVE_GIB=16, CTRL_JOBSERVER_MIB_PER_JOB=1792 — and the script documents its +// derivation as tokens = max(1, min(cores - MARGIN, floor(usable_mib / MIB_PER_JOB))) with +// usable_mib = (MemTotal_gib - MEM_RESERVE_GIB) * 1024. +// +// That is derive_jobserver_tokens above, term for term, which is why this is a citation rather than +// an approximation: the model was already carrying the installer's formula and lacked only its +// inputs. The absence was never a modeling gap — it was a reach gap, and it closed the moment the +// host became readable. +// +// WHAT CITING DOES AND DOES NOT CHANGE: it does not move any current number. +// gunbc_compile_pool_placement is CompilePoolInRunnerSlots, so compile_pool_bytes_for still derives +// zero regardless of this citation; what it unblocks is the flip to CompilePoolSliceDeclared, which +// previously refused with CompilePoolUnresolved and made the whole runner_deployment_plan +// PlanUnsound. That refusal was correct and is now discharged. +// +// PLACEMENT CAVEAT, stated rather than silently accepted: these are facts about THIS FLEET's +// installed jobserver, not about the GNU make jobserver protocol this module cites, so a stricter +// section 3 reading puts the row in a gunbc module and leaves the protocol shape here. It stays put +// because every consumer already reads it here and moving it is a rename across a live seam — the +// same class of migration whose hazards are recorded in gunbc.host_converge. Dissolve-on: the row +// moves when a gunbc-owned jobserver replaces ctrl-jobserver and the citation follows it. data jobserver_config: JobserverCitation = JobserverCited { config: JobserverConfig { diff --git a/dag/extdeps/languages/bash/subject.dag b/dag/extdeps/languages/bash/subject.dag index 52014d6dc8e..eca2e9d0344 100644 --- a/dag/extdeps/languages/bash/subject.dag +++ b/dag/extdeps/languages/bash/subject.dag @@ -12,9 +12,36 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data bash_subject_note: String = "THE CANONICAL BASH SUBJECT — the one declaration every Bash-bearing surface in this repository joins (operator verdict, 2026-08-04). A target is its UPSTREAM SUBJECT, symbolically identified per extdeps.external_authority's external_subject_symbolic_identity_note; the grammar, build helpers, command fold, orchestration emission, and runtime transport modules are SURFACES that describe or realize that subject, not five targets. This is why the subject is homed apart from any one of them: a surface may move or split without changing what Bash is, and a module path is a discriminator rather than an identity (DESIGN section 3). WHY THIS FILE EXISTS AT ALL: before it, Bash was the target this repository emits most and the only one with no cited subject anywhere — v2.extdeps.languages.bash carries neither an extdeps_model_scope nor an upstream citation, so v2.lens.realization_vocabulary_containment has been confining the module PREFIX 'v2.extdeps.languages.bash' around a name rather than an authority. Expect that to be the common case rather than the exception as the registry totality wall runs (loyal-ant-382, 2026-08-04)." +// THE CANONICAL BASH SUBJECT — the one declaration every Bash-bearing surface in this repository +// joins (operator verdict, 2026-08-04). A target is its UPSTREAM SUBJECT, symbolically identified +// per extdeps.external_authority's external_subject_symbolic_identity_note; the grammar, build +// helpers, command fold, orchestration emission, and runtime transport modules are SURFACES that +// describe or realize that subject, not five targets. This is why the subject is homed apart from +// any one of them: a surface may move or split without changing what Bash is, and a module path is +// a discriminator rather than an identity (DESIGN section 3). WHY THIS FILE EXISTS AT ALL: before +// it, Bash was the target this repository emits most and the only one with no cited subject +// anywhere — v2.extdeps.languages.bash carries neither an extdeps_model_scope nor an upstream +// citation, so v2.lens.realization_vocabulary_containment has been confining the module PREFIX +// 'v2.extdeps.languages.bash' around a name rather than an authority. Expect that to be the common +// case rather than the exception as the registry totality wall runs (loyal-ant-382, 2026-08-04). -data bash_implementation_versus_specification_note: String = "ONE SUBJECT, ONE CITATION — THE POSIX SPECIFICATION IS NOT ONE OF THIS SUBJECT'S CITATIONS (corrected 2026-08-04, review relayed through the operator). An earlier revision of this file stored the POSIX Shell Command Language as a further_citation on this scope while asserting, in this very row, that GNU Bash and POSIX are independently governed entities that DESIGN section 3 keeps separate. The row was right and the declaration below contradicted it: a citation grounds THIS subject, and the Open Group's specification is not a source about GNU Bash — it is a different subject with a different governing body. The rule the earlier text quoted correctly and then broke: multiple citations may ground one subject; a second independently governed subject is never a citation of the first. WHERE EACH PIECE LIVES NOW: the specification is its own subject at extdeps.posix.shell_command_language, and the conformance relation between them is a product-layer join at gunbc.shell_target_conformance, following the whatwg.html_navigation / browser.chromium / served_surface_browser_support shape that section works through. The distinction stays load-bearing for emission: this repository's bash grammar emits Bash-specific constructs while extdeps.shell.exec routes shell.Exec.Check through 'sh -c', so the two subjects are genuinely both in play and a support profile has to say which one it means. Revision identity stays off the scope by construction (external_model_scope_revision_grain_note): a specific Bash release is a fact on a release row, not a property of the subject." +// ONE SUBJECT, ONE CITATION — THE POSIX SPECIFICATION IS NOT ONE OF THIS SUBJECT'S CITATIONS +// (corrected 2026-08-04, review relayed through the operator). An earlier revision of this file +// stored the POSIX Shell Command Language as a further_citation on this scope while asserting, in +// this very row, that GNU Bash and POSIX are independently governed entities that DESIGN section 3 +// keeps separate. The row was right and the declaration below contradicted it: a citation grounds +// THIS subject, and the Open Group's specification is not a source about GNU Bash — it is a +// different subject with a different governing body. The rule the earlier text quoted correctly and +// then broke: multiple citations may ground one subject; a second independently governed subject is +// never a citation of the first. WHERE EACH PIECE LIVES NOW: the specification is its own subject +// at extdeps.posix.shell_command_language, and the conformance relation between them is a +// product-layer join at gunbc.shell_target_conformance, following the whatwg.html_navigation / +// browser.chromium / served_surface_browser_support shape that section works through. The +// distinction stays load-bearing for emission: this repository's bash grammar emits Bash-specific +// constructs while extdeps.shell.exec routes shell.Exec.Check through 'sh -c', so the two subjects +// are genuinely both in play and a support profile has to say which one it means. Revision identity +// stays off the scope by construction (external_model_scope_revision_grain_note): a specific Bash +// release is a fact on a release row, not a property of the subject. type GnuBash = | GnuBashShell diff --git a/dag/extdeps/languages/c/subject.dag b/dag/extdeps/languages/c/subject.dag index e78c22b43ac..1953432d5d1 100644 --- a/dag/extdeps/languages/c/subject.dag +++ b/dag/extdeps/languages/c/subject.dag @@ -12,7 +12,13 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data c_language_subject_note: String = "THE C LANGUAGE, cited to ISO/IEC JTC1/SC22/WG14 (verified live 2026-08-04 by fetching the cited locator and confirming its title and publisher from the response, per the D0 verify-or-refuse rule): the fetched committee page identifies itself as 'the international standardization working group for the programming language C' and designates the standard as 'ISO/IEC 9899: Programming Language C', noting C23 was adopted by ISO and IEC in 2024. WHY THE COMMITTEE PAGE AND NOT iso.org: the ISO catalogue refused this fetch with HTTP 403, so an iso.org locator could not be verified and is therefore not carried — the committee page is the authority that WAS readable, and citing what was actually verified is the rule this whole batch follows." +// THE C LANGUAGE, cited to ISO/IEC JTC1/SC22/WG14 (verified live 2026-08-04 by fetching the cited +// locator and confirming title and publisher, per the D0 verify-or-refuse rule): the committee page +// calls itself 'the international standardization working group for the programming language C' +// and designates the standard 'ISO/IEC 9899: Programming Language C', noting C23 was adopted by ISO +// and IEC in 2024. WHY THE COMMITTEE PAGE AND NOT iso.org: the ISO catalogue refused the fetch with +// HTTP 403, so an iso.org locator could not be verified and is not carried — cite only what was +// verified. type C = | CTarget diff --git a/dag/extdeps/languages/cpp/subject.dag b/dag/extdeps/languages/cpp/subject.dag index 90607956dad..72524b89912 100644 --- a/dag/extdeps/languages/cpp/subject.dag +++ b/dag/extdeps/languages/cpp/subject.dag @@ -12,7 +12,15 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data cpp_language_subject_note: String = "THE C++ LANGUAGE, cited to ISO/IEC JTC1/SC22/WG21 (verified live 2026-08-04 by fetching the cited locator and confirming its title and publisher from the response, per the D0 verify-or-refuse rule): the fetched committee page identifies itself as 'the international standardization working group for the programming language C++' and designates the standard as 'ISO/IEC 14882: Programming Language C++'. Same iso.org 403 caveat as the C subject. NOTE ON PROFILE, not decided here: DESIGN's open decision 2 asks whether this repository targets bounded system/kernel C++ or general-purpose C++; the disposition on this subject's registration reflects that the decision is open, and whichever profile is chosen must be named honestly rather than labelled 'full C++ support'." +// THE C++ LANGUAGE, cited to ISO/IEC JTC1/SC22/WG21 (verified live 2026-08-04 by fetching the cited +// locator and confirming its title and publisher from the response, per the D0 verify-or-refuse +// rule): the fetched committee page identifies itself as 'the international standardization working +// group for the programming language C++' and designates the standard as 'ISO/IEC 14882: +// Programming Language C++'. Same iso.org 403 caveat as the C subject. NOTE ON PROFILE, not decided +// here: DESIGN's open decision 2 asks whether this repository targets bounded system/kernel C++ or +// general-purpose C++; the disposition on this subject's registration reflects that the decision is +// open, and whichever profile is chosen must be named honestly rather than labelled 'full C++ +// support'. type Cpp = | CppTarget diff --git a/dag/extdeps/languages/css/properties.dag b/dag/extdeps/languages/css/properties.dag index 229e2b093a9..3dcf28d95c8 100644 --- a/dag/extdeps/languages/css/properties.dag +++ b/dag/extdeps/languages/css/properties.dag @@ -11,7 +11,17 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data css_property_note: String = "Cited upstream: the W3C CSS property index (the union of the CSS module specs — box, fonts, flexbox, backgrounds-and-borders, ...). This is the BOUNDED grain (D5): CssPropertyName enumerates EXACTLY the properties the register emits today (upstream spellings), so a property name is a typed row, not a raw string inside an otherwise-typed rule — the wall makes a misspelled property unwritable for the modeled set. It is deliberately NOT the whole CSS property ocean (DESIGN section 6, priced by displaced cost, not completeness). Two escapes, each honest: CustomProperty carries a --custom-property author-defined name (CSS Custom Properties / cascading variables, the register's token/theme vars — an open namespace, correctly not enumerable); RawProperty is a COUNTED frontier for a name outside the modeled set (today: the single non-standard 'code-spacing' in the accent study — a raw property is a flag that its name is unmodeled or invalid, dissolving as the set grows or the site lane fixes it). The full CSS VALUE grammar (a decl's right-hand side) stays a named arc, out of scope." +// Cited upstream: the W3C CSS property index (the union of the CSS module specs — box, fonts, +// flexbox, backgrounds-and-borders, ...). This is the BOUNDED grain (D5): CssPropertyName +// enumerates EXACTLY the properties the register emits today (upstream spellings), so a property +// name is a typed row, not a raw string inside an otherwise-typed rule — a misspelled property is +// unwritable for the modeled set. Deliberately NOT the whole CSS property ocean (DESIGN section 6, +// priced by displaced cost, not completeness). Two honest escapes: CustomProperty carries a +// --custom-property author-defined name (CSS Custom Properties / cascading variables, the register's +// token/theme vars — an open namespace, correctly not enumerable); RawProperty is a COUNTED frontier +// for a name outside the modeled set (today: the single non-standard 'code-spacing' in the accent +// study — a flag that its name is unmodeled or invalid, dissolving as the set grows or the site lane +// fixes it). The full CSS VALUE grammar (a decl's right-hand side) stays a named arc, out of scope. type CssPropertyName = AlignItems diff --git a/dag/extdeps/languages/css/selectors.dag b/dag/extdeps/languages/css/selectors.dag index 1e674dba81c..6e092888920 100644 --- a/dag/extdeps/languages/css/selectors.dag +++ b/dag/extdeps/languages/css/selectors.dag @@ -11,7 +11,19 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data css_selector_note: String = "Cited upstream: W3C Selectors Level 4. The BOUNDED grain (D5): a rule's subject is a typed SelectorSubject, not a raw string inside an otherwise-typed rule. Three modeled shapes cover the register's simple subjects — ClassSelector (a single .class), ElementSelector (a type/tag selector), PseudoSuffix (a compound of a base selector and one pseudo-class, e.g. a:hover) — and RawSelector is the COUNTED frontier for everything else (descendant combinators like '.frontier-bucket h2', compound type.class like 'li.node', :root and its functional pseudo-classes, the universal '*'). RawSelector carries the exact text so emission is byte-identical, and its COUNT is the honest residue number: the fraction of the register still expressed as raw selector text. dissolve_on: the combinator / compound / functional-pseudo grammar lands (Selectors 4 has the structure — descendant/child combinators, compound selectors, :not()/:is()), at which point each RawSelector row migrates to a modeled shape and the count falls. The full CSS VALUE grammar (decl right-hand sides) stays a separate named arc, out of scope." +// Cited upstream: W3C Selectors Level 4. The BOUNDED grain (D5): a rule's subject is a typed +// SelectorSubject, not a raw string inside an otherwise-typed rule. Three modeled shapes cover the +// register's simple subjects — ClassSelector (a single .class), ElementSelector (a type/tag +// selector), PseudoSuffix (a compound of a base selector and one pseudo-class, e.g. a:hover) — and +// RawSelector is the COUNTED frontier for everything else (descendant combinators like +// '.frontier-bucket h2', compound type.class like 'li.node', :root and its functional +// pseudo-classes, the universal '*'). RawSelector carries the exact text so emission is +// byte-identical, and its COUNT is the honest residue number: the fraction of the register still +// expressed as raw selector text. dissolve_on: the combinator / compound / functional-pseudo +// grammar lands (Selectors 4 has the structure — descendant/child combinators, compound selectors, +// :not()/:is()), at which point each RawSelector row migrates to a modeled shape and the count +// falls. The full CSS VALUE grammar (decl right-hand sides) stays a separate named arc, out of +// scope. type SelectorSubject = ClassSelector { name: String } diff --git a/dag/extdeps/languages/css/subject.dag b/dag/extdeps/languages/css/subject.dag index d8fe883dbfc..37c3264ed3f 100644 --- a/dag/extdeps/languages/css/subject.dag +++ b/dag/extdeps/languages/css/subject.dag @@ -19,7 +19,15 @@ data css_language_further_authority_1: ExternalAuthority = ExternalAuthority { } } -data css_language_subject_note: String = "CSS, cited to the CSS Snapshot (verified live 2026-08-04 by fetching the cited locator and confirming its title and publisher from the response, per the D0 verify-or-refuse rule): the fetched document is 'CSS Snapshot 2026', a W3C Group Note published by the CSS Working Group, which compiles the CSS specifications considered stable. THE STRUCTURAL FACT WORTH CARRYING: CSS is not one specification. The W3C publishes it as independent modules at independent maturity levels, and the Snapshot is the annual document that says which modules count as stable CSS. So a CSS support claim in this repository must name the MODULES it covers; 'supports CSS' is not a checkable statement, and the further citation is the working group's live module index that any such claim has to be read against." +// CSS, cited to the CSS Snapshot (verified live 2026-08-04 by fetching the cited locator and +// confirming its title and publisher from the response, per the D0 verify-or-refuse rule): the +// fetched document is 'CSS Snapshot 2026', a W3C Group Note published by the CSS Working Group, +// which compiles the CSS specifications considered stable. THE STRUCTURAL FACT WORTH CARRYING: CSS +// is not one specification. The W3C publishes it as independent modules at independent maturity +// levels, and the Snapshot is the annual document that says which modules count as stable CSS. So a +// CSS support claim in this repository must name the MODULES it covers; 'supports CSS' is not a +// checkable statement, and the further citation is the working group's live module index that any +// such claim has to be read against. type Css = | CssTarget diff --git a/dag/extdeps/languages/css/values.dag b/dag/extdeps/languages/css/values.dag index e77e53f7538..c40d9cfad75 100644 --- a/dag/extdeps/languages/css/values.dag +++ b/dag/extdeps/languages/css/values.dag @@ -11,7 +11,15 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data css_length_note: String = "Cited upstream: W3C CSS Values and Units Module Level 4. A is a magnitude PLUS a unit (section 6 Distance Units): px an absolute unit, rem and ch font-relative units — so a length literal like \"4px\" is a SERIALIZATION of that structure (DESIGN section 2, decompress the string), never the stored fact. The zero-length rule (section 6.2: for a zero length the unit identifier is optional) is why css_length_wire renders a zero magnitude as bare \"0\" regardless of unit — grounded spec behavior, not a special case, and the reason authored \"0\" round-trips byte-exact. (section 5) rides the same wire for the register's dimension needs. Forward reading (parsing a CSS value through these rows) is the NAMED later direction (DESIGN section 4, one grammar both ways), not built here." +// Cited upstream: W3C CSS Values and Units Module Level 4. A is a magnitude PLUS a unit +// (section 6 Distance Units): px an absolute unit, rem and ch font-relative units — so a length +// literal like "4px" is a SERIALIZATION of that structure (DESIGN section 2, decompress the +// string), never the stored fact. The zero-length rule (section 6.2: for a zero length the unit +// identifier is optional) is why css_length_wire renders a zero magnitude as bare "0" regardless of +// unit — grounded spec behavior, not a special case, and the reason authored "0" round-trips +// byte-exact. (section 5) rides the same wire for the register's dimension needs. +// Forward reading (parsing a CSS value through these rows) is the NAMED later direction (DESIGN +// section 4, one grammar both ways), not built here. type CssLengthUnit = Px @@ -56,7 +64,11 @@ data css_font_family_authority: ExternalAuthority = ExternalAuthority { } } -data css_font_family_note: String = "Cited upstream: W3C CSS Fonts Module Level 4, font-family property. A font-family value is a LIST of families: quoted strings (NamedFamily, e.g. 'Noto Sans') and bare keywords (GenericFamily: serif/sans-serif/monospace/cursive/fantasy/system-ui), joined by \", \". The stringly \"'Noto Sans', system-ui, sans-serif\" is the serialization of this list, decompressed here (DESIGN section 2)." +// Cited upstream: W3C CSS Fonts Module Level 4, font-family property. A font-family value is a LIST +// of families: quoted strings (NamedFamily, e.g. 'Noto Sans') and bare +// keywords (GenericFamily: serif/sans-serif/monospace/cursive/fantasy/system-ui), +// joined by ", ". The stringly "'Noto Sans', system-ui, sans-serif" is the serialization of this +// list, decompressed here (DESIGN section 2). type CssGenericFamily = GenericSerif diff --git a/dag/extdeps/languages/ecmascript/subject.dag b/dag/extdeps/languages/ecmascript/subject.dag index be3de4bbf63..6525284a45e 100644 --- a/dag/extdeps/languages/ecmascript/subject.dag +++ b/dag/extdeps/languages/ecmascript/subject.dag @@ -19,7 +19,16 @@ data ecmascript_language_further_authority_1: ExternalAuthority = ExternalAuthor } } -data ecmascript_language_subject_note: String = "ECMASCRIPT, the Ecma-262 standard (verified live 2026-08-04 by fetching the cited locator and confirming its title and publisher from the response, per the D0 verify-or-refuse rule): the fetched document is the 'ECMAScript 2027 Language Specification', published by Ecma International and edited under TC39. This subject exists SEPARATELY from the TypeScript subject on purpose: ECMAScript is the runtime-semantic standard, TypeScript is an independently governed product that compiles to it, and DESIGN section 3's external-upstream decomposition keeps independently governed entities apart even when one targets the other. DESIGN's open decision 1 — whether ECMAScript is the runtime-semantic base the TypeScript target projects onto, or a deliberately separate model — is exactly a question about the relationship between these two subjects, and it stays open here; registering the subject does not decide it." +// ECMASCRIPT, the Ecma-262 standard (verified live 2026-08-04 by fetching the cited locator and +// confirming its title and publisher from the response, per the D0 verify-or-refuse rule): the +// fetched document is the 'ECMAScript 2027 Language Specification', published by Ecma International +// and edited under TC39. This subject exists SEPARATELY from the TypeScript subject on purpose: +// ECMAScript is the runtime-semantic standard, TypeScript is an independently governed product that +// compiles to it, and DESIGN section 3's external-upstream decomposition keeps independently +// governed entities apart even when one targets the other. DESIGN's open decision 1 — whether +// ECMAScript is the runtime-semantic base the TypeScript target projects onto, or a deliberately +// separate model — is exactly a question about the relationship between these two subjects, and it +// stays open here; registering the subject does not decide it. type EcmaScript = | EcmaScriptTarget diff --git a/dag/extdeps/languages/go/subject.dag b/dag/extdeps/languages/go/subject.dag index 4d89e20ad12..ca837405b9d 100644 --- a/dag/extdeps/languages/go/subject.dag +++ b/dag/extdeps/languages/go/subject.dag @@ -12,7 +12,13 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data go_language_subject_note: String = "GO, cited to The Go Programming Language Specification (verified live 2026-08-04 by fetching the cited locator and confirming its title and publisher from the response, per the D0 verify-or-refuse rule): the fetched document is titled 'The Go Programming Language Specification', self-describes as 'the reference manual for the Go programming language', and carries an explicit language version in its header. Go is one of the few targets here whose upstream publishes a single normative specification document for the language proper, which is why this subject needs only one citation." +// GO, cited to The Go Programming Language Specification (verified live 2026-08-04 by fetching the +// cited locator and confirming its title and publisher from the response, per the D0 +// verify-or-refuse rule): the fetched document is titled 'The Go Programming Language +// Specification', self-describes as 'the reference manual for the Go programming language', and +// carries an explicit language version in its header. Go is one of the few targets here whose +// upstream publishes a single normative specification document for the language proper, which is +// why this subject needs only one citation. type Go = | GoTarget diff --git a/dag/extdeps/languages/html.dag b/dag/extdeps/languages/html.dag index 31b17273678..411aeaf6a0c 100644 --- a/dag/extdeps/languages/html.dag +++ b/dag/extdeps/languages/html.dag @@ -252,7 +252,14 @@ data html5_void_tags: List = [ "input", "link", "meta", "param", "source", "track", "wbr", ] -data html5_raw_text_tags_note: String = "The HTML spec's raw-text elements (HTML Standard section 13.1.2, 'Raw text elements'): script and style. Their text content is NOT entity-decoded by browsers, so entity-ESCAPING it at serialization corrupts it — a CSS child combinator '>' emitted as '>' silently kills the rule in every browser (the roadmap strikethrough incident, 2026-07-24; same escape class as the dispatch-button inline-script incident). Serialization emits their text raw; the one hazard — content containing the element's own close sequence — REFUSES at try_serialize_html_source (typed, located), never emits corrupted output. textarea/title are the spec's escapable-raw-text class (entities ARE decoded there) and deliberately stay in the escaping default." +// The HTML spec's raw-text elements (HTML Standard section 13.1.2, 'Raw text elements'): script and +// style. Their text content is NOT entity-decoded by browsers, so entity-ESCAPING it at +// serialization corrupts it — a CSS child combinator '>' emitted as '>' silently kills the rule +// in every browser (the roadmap strikethrough incident, 2026-07-24; same escape class as the +// dispatch-button inline-script incident). Serialization emits their text raw; the one hazard — +// content containing the element's own close sequence — REFUSES at try_serialize_html_source +// (typed, located), never emits corrupted output. textarea/title are the spec's escapable-raw-text +// class (entities ARE decoded there) and deliberately stay in the escaping default. data html5_raw_text_tags: List = ["script", "style"] diff --git a/dag/extdeps/languages/html/subject.dag b/dag/extdeps/languages/html/subject.dag index d898ddf5ff9..7cc17fb0a9b 100644 --- a/dag/extdeps/languages/html/subject.dag +++ b/dag/extdeps/languages/html/subject.dag @@ -12,7 +12,13 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data html_language_subject_note: String = "HTML, cited to the WHATWG HTML Living Standard (verified live 2026-08-04 by fetching the cited locator and confirming its title and publisher from the response, per the D0 verify-or-refuse rule): the fetched specification is titled 'HTML', is published by the WHATWG, and explicitly carries Living Standard status with a last-updated date rather than a version number. THE LIVING-STANDARD SHAPE IS WHY THIS SUBJECT CARRIES NO REVISION: there is no edition to pin, which is precisely the case external_model_scope_revision_grain_note describes — a dated snapshot is a fact on a specific observation row, never a property of the subject." +// HTML, cited to the WHATWG HTML Living Standard (verified live 2026-08-04 by fetching the cited +// locator and confirming its title and publisher from the response, per the D0 verify-or-refuse +// rule): the fetched specification is titled 'HTML', is published by the WHATWG, and explicitly +// carries Living Standard status with a last-updated date rather than a version number. THE +// LIVING-STANDARD SHAPE IS WHY THIS SUBJECT CARRIES NO REVISION: there is no edition to pin, which +// is precisely the case external_model_scope_revision_grain_note describes — a dated snapshot is a +// fact on a specific observation row, never a property of the subject. type Html = | HtmlTarget diff --git a/dag/extdeps/languages/java/subject.dag b/dag/extdeps/languages/java/subject.dag index 5ffc5cc3762..2c27f315131 100644 --- a/dag/extdeps/languages/java/subject.dag +++ b/dag/extdeps/languages/java/subject.dag @@ -12,7 +12,14 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data java_language_subject_note: String = "JAVA, cited to The Java Language Specification (verified live 2026-08-04 by fetching the cited locator and confirming its title and publisher from the response, per the D0 verify-or-refuse rule): the fetched index publishes 'The Java Language Specification' per Java SE edition, alongside the separately titled Java Virtual Machine Specification, under Oracle copyright and released through the Java Community Process. THE LANGUAGE AND THE VM ARE TWO DOCUMENTS AND POTENTIALLY TWO SUBJECTS: this declaration is the LANGUAGE. If this repository ever targets JVM bytecode directly rather than Java source, that is the JVM specification's subject and takes its own declaration — it is not an arm added here." +// JAVA, cited to The Java Language Specification (verified live 2026-08-04 by fetching the cited +// locator and confirming its title and publisher from the response, per the D0 verify-or-refuse +// rule): the fetched index publishes 'The Java Language Specification' per Java SE edition, +// alongside the separately titled Java Virtual Machine Specification, under Oracle copyright and +// released through the Java Community Process. THE LANGUAGE AND THE VM ARE TWO DOCUMENTS AND +// POTENTIALLY TWO SUBJECTS: this declaration is the LANGUAGE. If this repository ever targets JVM +// bytecode directly rather than Java source, that is the JVM specification's subject and takes its +// own declaration — it is not an arm added here. type Java = | JavaTarget diff --git a/dag/extdeps/languages/json/emit.dag b/dag/extdeps/languages/json/emit.dag index c416953bc65..5a4691cd268 100644 --- a/dag/extdeps/languages/json/emit.dag +++ b/dag/extdeps/languages/json/emit.dag @@ -60,7 +60,6 @@ fn json_object(members: List) -> JsonValue { JsonObject { members: members } } - fn json_unicode_escape_hex4(cp: Int) -> String { let h = int_to_upper_hex(n: cp) let len = h.length() @@ -112,7 +111,12 @@ fn serialize_json_object_members(members: List) -> String { }) } -data json_object_prefix_note: String = "serialize_json_object_prefix is the canonical serialization of an object's OPENING plus its leading members, with no closing brace — the exact byte prefix every document beginning with those members must carry. It exists so a consumer can identify a document by its leading members POSITIONALLY (starts_with) instead of scanning the serialized text for a fragment: a nested value can carry any fragment a scan looks for, but no nested value can occupy the document's prefix. serialize_json_object is defined as this prefix plus the closing brace, so the two cannot drift." +// serialize_json_object_prefix is the canonical serialization of an object's OPENING plus its +// leading members, no closing brace — the exact byte prefix every document beginning with those +// members must carry. A consumer can thus identify a document by its leading members POSITIONALLY +// (starts_with) instead of scanning for a fragment: a nested value can carry any fragment a scan +// looks for, but none can occupy the document's prefix. serialize_json_object is defined as this +// prefix plus the closing brace, so the two cannot drift. fn serialize_json_object_prefix(members: List) -> String { concat("{", serialize_json_object_members(members: members)) diff --git a/dag/extdeps/languages/json/grammar.dag b/dag/extdeps/languages/json/grammar.dag index 3b78cff79e7..62aeb60374e 100644 --- a/dag/extdeps/languages/json/grammar.dag +++ b/dag/extdeps/languages/json/grammar.dag @@ -213,7 +213,24 @@ fn json_text_starts_with_at(s: String, i: Int, lit: String) -> Bool { substring(s: s, start: i, end: i + lit.length()) == lit } -data json_escape_production_note: String = "RFC 8259 section 7: escape = %x22 / %x5C / %x2F / %x62 / %x66 / %x6E / %x72 / %x74 / %x75 4HEXDIG. The set is CLOSED and \\u carries exactly four hex digits. This scanner previously consumed the backslash and whatever followed it without deciding whether that character named an escape at all, so every unknown escape was accepted (review 45642 RED). The consequence was not cosmetic and not confined to the scanner: the unescaper's unknown-escape arm dropped the backslash, so `\"p\\assed\"` - which is not JSON - decoded to the string `passed` and satisfied the validation receipt's verdict identity. A malformed document fabricated a pass, which is the fail-open DESIGN 5 forbids at exactly the boundary that exists to prevent it.\\n\\nThe refusal belongs HERE rather than in the unescaper because the escape set is a grammar production, and deciding it at the span scan means the invalid state never reaches the value builder - construction over validation. It also fixes both readings in one place: parse_json's strings come through this scanner, and so does the span question extdeps.llm.cli asks, so the two cannot disagree about what an escape is.\\n\\nNOT closed here, declared rather than left silent: RFC 8259 also forbids UNESCAPED control characters (%x00-1F) inside a string, and this scanner still admits them. That gap cannot fabricate an identity the way an unknown escape can - a raw control character inside `passed` makes the string unequal to `passed` rather than equal to it - so it is a lesser member of the same family. Dissolve-on: the char production is scanned as a closed set rather than as everything-but-quote-and-backslash." +// RFC 8259 section 7: escape = %x22 / %x5C / %x2F / %x62 / %x66 / %x6E / %x72 / %x74 / %x75 +// 4HEXDIG. The set is CLOSED and \u carries exactly four hex digits. This scanner previously +// consumed the backslash and whatever followed it without deciding whether that character named an +// escape at all, so every unknown escape was accepted (review 45642 RED). The consequence was not +// cosmetic and not confined to the scanner: the unescaper's unknown-escape arm dropped the +// backslash, so `"p\assed"` - which is not JSON - decoded to the string `passed` and satisfied the +// validation receipt's verdict identity. A malformed document fabricated a pass, which is the +// fail-open DESIGN 5 forbids at exactly the boundary that exists to prevent it.\n\nThe refusal +// belongs HERE rather than in the unescaper because the escape set is a grammar production, and +// deciding it at the span scan means the invalid state never reaches the value builder - +// construction over validation. It also fixes both readings in one place: parse_json's strings come +// through this scanner, and so does the span question extdeps.llm.cli asks, so the two cannot +// disagree about what an escape is.\n\nNOT closed here, declared rather than left silent: RFC 8259 +// also forbids UNESCAPED control characters (%x00-1F) inside a string, and this scanner still +// admits them. That gap cannot fabricate an identity the way an unknown escape can - a raw control +// character inside `passed` makes the string unequal to `passed` rather than equal to it - so it is +// a lesser member of the same family. Dissolve-on: the char production is scanned as a closed set +// rather than as everything-but-quote-and-backslash. fn is_json_hex_digit_char(c: String) -> Bool { (c != "") && string_contains(s: "0123456789abcdefABCDEF", pattern: c) @@ -279,7 +296,15 @@ fn json_text_parse_number_digits(s: String, i: Int) -> Int { } } -data json_text_exponent_digit_note: String = "RFC 8259 number = [minus] int [frac] [exp], and exp = (e/E) [minus/plus] 1*DIGIT — the exponent's digits are REQUIRED. This branch previously consumed the marker and optional sign and then accepted whatever json_text_parse_number_digits returned, so `1e` and `1e+` parsed as complete numbers; a receipt carrying one read as well-formed JSON (review 44043). The guard now mirrors the fraction branch directly above it: no digits means the exponent was never part of the number, so the scan backtracks to after_frac and the stray marker surfaces as unconsumed trailing input, which json_text_parseable and every enclosing production refuse. The sibling lexeme surface json_number_consume_optional_exp always required the digit — the two readings of one RFC production had forked (DESIGN 3), and this restores them to agreement." +// RFC 8259 number = [minus] int [frac] [exp], and exp = (e/E) [minus/plus] 1*DIGIT — the exponent's +// digits are REQUIRED. This branch previously consumed the marker and optional sign and then +// accepted whatever json_text_parse_number_digits returned, so `1e` and `1e+` parsed as complete +// numbers; a receipt carrying one read as well-formed JSON (review 44043). The guard now mirrors +// the fraction branch directly above it: no digits means the exponent was never part of the number, +// so the scan backtracks to after_frac and the stray marker surfaces as unconsumed trailing input, +// which json_text_parseable and every enclosing production refuse. The sibling lexeme surface +// json_number_consume_optional_exp always required the digit — the two readings of one RFC +// production had forked (DESIGN 3), and this restores them to agreement. fn json_text_parse_number(s: String, i: Int) -> JsonTextParseResult { let start = i @@ -392,6 +417,23 @@ fn json_text_parse_object(s: String, i: Int) -> JsonTextParseResult { } } +// json_text_parseable used to live here, deciding on the span scanner alone whether a whole +// document was JSON. That is the same question extdeps.languages.json.parse answers by CONSTRUCTING +// the value, so the corpus had one procedure deciding whether text is JSON and another deciding +// what it contains - two readings of one RFC that can disagree (DESIGN 3; review 44043 was that +// disagreement, on the exponent production, found only because a witness pinned the surfaces +// together). The whole-document reading now derives from parse_json and lives beside it. What stays +// here is the SPAN question, which is a different question and has its own consumer: +// json_text_parse_value answers where a value ends at an offset INSIDE a larger text, which +// extdeps.llm.cli needs to walk a config document and which parse_json cannot answer because it +// requires full consumption. RESIDUAL FORK, declared rather than left silent: +// json_text_parse_object/_array and their counterparts in parse.dag still decide object and array +// syntax independently - parse.dag reuses the string and number scanners here but recurses on its +// own for the two container productions. Dissolve-on: extdeps.llm.cli takes its span from +// parse_json_value_at's next field, at which point +// json_text_parse_value/_object/_object_members/_array/_array_values/_literal have no consumer and +// delete, leaving one reading of every production. + fn json_text_parse_value(s: String, i: Int) -> JsonTextParseResult { let p = json_text_skip_ws(s: s, i: i) if p >= s.length() { @@ -408,5 +450,3 @@ fn json_text_parse_value(s: String, i: Int) -> JsonTextParseResult { else { json_text_parse_fail(end: p) } } } - -data json_text_whole_document_reading_note: String = "json_text_parseable used to live here, deciding on the span scanner alone whether a whole document was JSON. That is the same question extdeps.languages.json.parse answers by CONSTRUCTING the value, so the corpus had one procedure deciding whether text is JSON and another deciding what it contains - two readings of one RFC that can disagree (DESIGN 3; review 44043 was that disagreement, on the exponent production, found only because a witness pinned the surfaces together). The whole-document reading now derives from parse_json and lives beside it. What stays here is the SPAN question, which is a different question and has its own consumer: json_text_parse_value answers where a value ends at an offset INSIDE a larger text, which extdeps.llm.cli needs to walk a config document and which parse_json cannot answer because it requires full consumption. RESIDUAL FORK, declared rather than left silent: json_text_parse_object/_array and their counterparts in parse.dag still decide object and array syntax independently - parse.dag reuses the string and number scanners here but recurses on its own for the two container productions. Dissolve-on: extdeps.llm.cli takes its span from parse_json_value_at's next field, at which point json_text_parse_value/_object/_object_members/_array/_array_values/_literal have no consumer and delete, leaving one reading of every production." diff --git a/dag/extdeps/languages/json/parse.dag b/dag/extdeps/languages/json/parse.dag index dbcef41f065..f16fad46c25 100644 --- a/dag/extdeps/languages/json/parse.dag +++ b/dag/extdeps/languages/json/parse.dag @@ -13,7 +13,27 @@ import extdeps.languages.json.grammar { json_number_lexeme_unchecked, is_json_digit_char, } -data json_parse_note: String = "parse_json is the FORWARD reading of RFC 8259: text -> JsonValue, the inverse of extdeps.languages.json.emit.serialize_json. It reuses the recursive-descent span scanner in extdeps.languages.json.grammar (json_text_parse_string / json_text_parse_number / json_text_skip_ws) to find value boundaries, and builds the JsonValue at each span. Interim standalone parser (a second reading alongside emit); DISSOLVES WHEN json_production_rows are wired into the general GrammarRelationRow ingest machinery (src/v2/compiler/03_ingest) so parse is a witnessed instance of emit^-1 (DESIGN 4). Escapes: the span scanner in grammar.dag decides the escape SET (json_text_escape_end, RFC 8259 section 7), so an unknown escape or a short \\u refuses the parse before a value is built and never reaches the unescaper below - see json_escape_production_note for why that refusal lives at the scan and what it prevents (review 45642). The unknown-escape arm in json_unescape_from is therefore unreachable for any span this parser accepts; it is retained as a total fallback because json_unescape is exported and a caller could hand it an unvalidated span. \\uXXXX IS decoded to its code point, which is what makes the round-trip total against escape_json_string: that emitter maps code points 0-31 other than the six with short escapes to \\uXXXX, so a parser that passed \\u through would silently corrupt every control character a receipt detail field can carry. Surrogate PAIRS are not recombined - serialize_json never emits one (it escapes only 0-31), so a lone \\uXXXX decode is exact over this emitter's output; foreign JSON carrying an astral character as a surrogate pair is outside the declared fragment. A malformed \\u (fewer than four hex digits, or a non-hex digit) refuses at the scanner, so json_hex4_at's negative arm is likewise unreachable from parse_json and kept only for the exported-caller case." +// parse_json is the FORWARD reading of RFC 8259: text -> JsonValue, the inverse of +// extdeps.languages.json.emit.serialize_json. It reuses the recursive-descent span scanner in +// extdeps.languages.json.grammar (json_text_parse_string / json_text_parse_number / +// json_text_skip_ws) to find value boundaries, and builds the JsonValue at each span. Interim +// standalone parser (a second reading alongside emit); DISSOLVES WHEN json_production_rows are +// wired into the general GrammarRelationRow ingest machinery (src/v2/compiler/03_ingest) so parse +// is a witnessed instance of emit^-1 (DESIGN 4). Escapes: the span scanner in grammar.dag decides +// the escape SET (json_text_escape_end, RFC 8259 section 7), so an unknown escape or a short \u +// refuses the parse before a value is built and never reaches the unescaper below - see +// json_escape_production_note for why that refusal lives at the scan and what it prevents (review +// 45642). The unknown-escape arm in json_unescape_from is therefore unreachable for any span this +// parser accepts; it is retained as a total fallback because json_unescape is exported and a caller +// could hand it an unvalidated span. \uXXXX IS decoded to its code point, which is what makes the +// round-trip total against escape_json_string: that emitter maps code points 0-31 other than the +// six with short escapes to \uXXXX, so a parser that passed \u through would silently corrupt every +// control character a receipt detail field can carry. Surrogate PAIRS are not recombined - +// serialize_json never emits one (it escapes only 0-31), so a lone \uXXXX decode is exact over this +// emitter's output; foreign JSON carrying an astral character as a surrogate pair is outside the +// declared fragment. A malformed \u (fewer than four hex digits, or a non-hex digit) refuses at the +// scanner, so json_hex4_at's negative arm is likewise unreachable from parse_json and kept only for +// the exported-caller case. data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { uri: Uri { @@ -146,7 +166,15 @@ fn json_string_value_or_empty(v: JsonValue) -> String { } } -data json_member_lookup_note: String = "RFC 8259 permits duplicate names in an object and does not say which one wins, so JsonObject keeps members as an authored LIST rather than a map. A reader that folds that list into last-wins silently picks a value the document never committed to - and for an identity field like schema or verdict, last-wins is exactly how a document carrying two verdicts reads as whichever one an attacker put second. This lookup therefore reports duplication as its own outcome instead of resolving it, leaving the choice to refuse with the domain decoder that knows the field is an identity. JsonMemberNotAnObject is separate from JsonMemberAbsent for the same reason: a scalar document has not omitted a field, it has no fields at all, and collapsing the two would let a bare string satisfy an absence check." +// RFC 8259 permits duplicate names in an object and does not say which one wins, so JsonObject +// keeps members as an authored LIST rather than a map. A reader that folds that list into last-wins +// silently picks a value the document never committed to - and for an identity field like schema or +// verdict, last-wins is exactly how a document carrying two verdicts reads as whichever one an +// attacker put second. This lookup therefore reports duplication as its own outcome instead of +// resolving it, leaving the choice to refuse with the domain decoder that knows the field is an +// identity. JsonMemberNotAnObject is separate from JsonMemberAbsent for the same reason: a scalar +// document has not omitted a field, it has no fields at all, and collapsing the two would let a +// bare string satisfy an absence check. type JsonMemberLookup = JsonMemberFound { value: JsonValue } @@ -183,7 +211,14 @@ type JsonArrayAcc = JsonArrayAccOk { elements: List, next: Int } | JsonArrayAccFail { at: Int } -data json_trailing_comma_note: String = "RFC 8259's array production is `[ ws ]` or `[ value *( , value ) ]` - the empty form is reachable only at the OPENING bracket, never after a separator. An accumulator that checks for the closing bracket at the top of each round reaches that check again after consuming a comma, which silently accepts a trailing comma; the same shape accepts `\{\"a\": 1,}`. That is not a cosmetic leniency: json_text_parseable is the guard the Codex JSONL reader uses to refuse a malformed provider line, and a parser that accepts a trailing comma lets a truncated-then-closed event read as well-formed. The element and member folds below therefore REQUIRE a value, and the empty-container case is decided once by the opening reader." +// RFC 8259's array production is `[ ws ]` or `[ value *( , value ) ]` - the empty form is reachable +// only at the OPENING bracket, never after a separator. An accumulator that checks for the closing +// bracket at the top of each round reaches that check again after consuming a comma, which silently +// accepts a trailing comma; the same shape accepts `{"a": 1,}`. That is not a cosmetic leniency: +// json_text_parseable is the guard the Codex JSONL reader uses to refuse a malformed provider line, +// and a parser that accepts a trailing comma lets a truncated-then-closed event read as +// well-formed. The element and member folds below therefore REQUIRE a value, and the +// empty-container case is decided once by the opening reader. fn parse_json_array_elements(s: String, i: Int, acc: List) -> JsonArrayAcc { match parse_json_value_at(s: s, i: i) { @@ -348,7 +383,16 @@ fn parse_json_document(s: String) -> JsonDocumentParse { } } -data json_text_parseable_derivation_note: String = "This is the single recognition authority: whether text IS JSON is decided by whether it PARSES, not by a second scanner that answers the same question independently. The span scanner in extdeps.languages.json.grammar previously carried its own json_text_parseable, which meant one procedure decided whether a document was JSON and another decided what it contained - two readings of one RFC that can disagree, which is the fork DESIGN 3 names (the exponent defect in review 44043 was exactly that, caught only because a witness pinned the two surfaces to each other). Recognition now derives from construction, so a document this returns true for is a document parse_json produced a value from, by definition rather than by agreement. The scanner keeps its span productions, which parse_json calls to find value boundaries; what it no longer keeps is an opinion about the whole document." +// This is the single recognition authority: whether text IS JSON is decided by whether it PARSES, +// not by a second scanner that answers the same question independently. The span scanner in +// extdeps.languages.json.grammar previously carried its own json_text_parseable, which meant one +// procedure decided whether a document was JSON and another decided what it contained - two +// readings of one RFC that can disagree, which is the fork DESIGN 3 names (the exponent defect in +// review 44043 was exactly that, caught only because a witness pinned the two surfaces to each +// other). Recognition now derives from construction, so a document this returns true for is a +// document parse_json produced a value from, by definition rather than by agreement. The scanner +// keeps its span productions, which parse_json calls to find value boundaries; what it no longer +// keeps is an opinion about the whole document. // THE ONE CONSUMER THAT HONESTLY DISCARDS THE GAP, and its licence is that its entire question IS // whether the text parses -- the Bool is the domain fact here, not a collapse of one. It binds the diff --git a/dag/extdeps/languages/json/subject.dag b/dag/extdeps/languages/json/subject.dag index 2ac477160c5..a17211618b8 100644 --- a/dag/extdeps/languages/json/subject.dag +++ b/dag/extdeps/languages/json/subject.dag @@ -19,7 +19,13 @@ data json_format_further_authority_1: ExternalAuthority = ExternalAuthority { } } -data json_format_subject_note: String = "JSON, cited to RFC 8259 (verified live 2026-08-04 by fetching the cited locator and confirming its title and publisher from the response, per the D0 verify-or-refuse rule): the fetched document is 'The JavaScript Object Notation (JSON) Data Interchange Format', RFC 8259, an IETF Standards Track document edited by Tim Bray, published December 2017, obsoleting RFC 7159. The further citation is ECMA-404, the parallel Ecma International standard for the same syntax — two independently governed documents describing one interchange format, carried as two citations of ONE subject exactly as external_model_scope_law permits, rather than forked into two subjects." +// JSON, cited to RFC 8259 (verified live 2026-08-04 by fetching the cited locator and confirming +// its title and publisher from the response, per the D0 verify-or-refuse rule): the fetched +// document is 'The JavaScript Object Notation (JSON) Data Interchange Format', RFC 8259, an IETF +// Standards Track document edited by Tim Bray, published December 2017, obsoleting RFC 7159. The +// further citation is ECMA-404, the parallel Ecma International standard for the same syntax — two +// independently governed documents describing one interchange format, carried as two citations of +// ONE subject exactly as external_model_scope_law permits, rather than forked into two subjects. type Json = | JsonTarget diff --git a/dag/extdeps/languages/jsx/subject.dag b/dag/extdeps/languages/jsx/subject.dag index 4530e064261..ee8ed095b1e 100644 --- a/dag/extdeps/languages/jsx/subject.dag +++ b/dag/extdeps/languages/jsx/subject.dag @@ -12,7 +12,15 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data jsx_language_subject_note: String = "JSX, cited to the JSX Specification (verified live 2026-08-04 by fetching the cited locator and confirming its title and publisher from the response, per the D0 verify-or-refuse rule): the fetched repository hosts 'DRAFT: JSX Specification', maintained by Meta, defining JSX as 'an XML-like syntax extension to ECMAScript without any defined semantics'. THREE FACTS FROM THE UPSTREAM THAT THE REGISTRY NEEDS: it is explicitly a DRAFT; it is explicitly NOT intended to be implemented by engines or browsers and NOT a proposal to add JSX to ECMAScript; and it is independent of React. That last point is why this subject is jsx and not react — React is a separate library with separate governance, and the census surface named extdeps.languages.react_jsx fuses the two names in one module path. The syntax this repository emits is JSX; React would be a distinct subject if it is ever modelled." +// JSX, cited to the JSX Specification (verified live 2026-08-04 by fetching the cited locator and +// confirming title and publisher, per the D0 verify-or-refuse rule): the repository hosts 'DRAFT: +// JSX Specification', maintained by Meta, defining JSX as 'an XML-like syntax extension to +// ECMAScript without any defined semantics'. THREE FACTS THE REGISTRY NEEDS: it is explicitly a +// DRAFT; it is explicitly NOT to be implemented by engines or browsers and NOT a proposal to add +// JSX to ECMAScript; and it is independent of React. Hence the subject is jsx, not react — React is +// a separate library with separate governance, and the census surface extdeps.languages.react_jsx +// fuses the two names in one module path. This repository emits JSX; React would be a distinct +// subject if ever modelled. type Jsx = | JsxTarget diff --git a/dag/extdeps/languages/kotlin/subject.dag b/dag/extdeps/languages/kotlin/subject.dag index e782e18f083..edadc2efaa1 100644 --- a/dag/extdeps/languages/kotlin/subject.dag +++ b/dag/extdeps/languages/kotlin/subject.dag @@ -19,7 +19,14 @@ data kotlin_language_further_authority_1: ExternalAuthority = ExternalAuthority } } -data kotlin_language_subject_note: String = "KOTLIN, cited to the Kotlin language specification (verified live 2026-08-04 by fetching the cited locator and confirming its title and publisher from the response, per the D0 verify-or-refuse rule): the fetched document is titled 'Kotlin language specification: Kotlin/Core', authored by Marat Akhin and Mikhail Belyaev et al. and maintained by JetBrains / JetBrains Research. HONESTY NOTE CARRIED FROM UPSTREAM: the specification declares itself at experimental stability level, stating no compatibility should be expected between incremental releases. That is a property of the cited document, not of the Kotlin language or compiler, and it is recorded because a support claim resting on an experimental-stability specification is weaker evidence than one resting on a ratified standard." +// KOTLIN, cited to the Kotlin language specification (verified live 2026-08-04 by fetching the +// cited locator and confirming title and publisher from the response, per the D0 verify-or-refuse +// rule): titled 'Kotlin language specification: Kotlin/Core', authored by Marat Akhin and Mikhail +// Belyaev et al., maintained by JetBrains / JetBrains Research. HONESTY NOTE CARRIED FROM UPSTREAM: +// the specification declares itself at experimental stability level, with no compatibility expected +// between incremental releases. That is a property of the cited document, not of the Kotlin language +// or compiler; recorded because a support claim resting on an experimental-stability specification +// is weaker evidence than one resting on a ratified standard. type Kotlin = | KotlinTarget diff --git a/dag/extdeps/languages/lean/overflow.dag b/dag/extdeps/languages/lean/overflow.dag index d1d9c7e853b..24e04faf405 100644 --- a/dag/extdeps/languages/lean/overflow.dag +++ b/dag/extdeps/languages/lean/overflow.dag @@ -11,6 +11,10 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data lean_fixed_width_overflow_provenance_note: String = "Upstream Lean 4 fixed-precision integer overflow (extdeps_external_authority_anchor, cited 2026-08-01): literals that overflow their type's precision are interpreted modulo the precision; signed types use twos-complement (lean-lang Fixed-Precision-Integers). Init.Data.UInt.Basic documents UInt8.add, UInt8.mul, UInt8.pow, and width conversions as wrapping around on overflow. Catalog fixed-width scalars ground on ReturnWrapping — not saturating, trap, or undefined." +// Upstream Lean 4 fixed-precision integer overflow (extdeps_external_authority_anchor, cited +// 2026-08-01): literals that overflow their type's precision are interpreted modulo the precision; +// signed types use twos-complement (lean-lang Fixed-Precision-Integers). Init.Data.UInt.Basic +// documents UInt8.add, UInt8.mul, UInt8.pow, and width conversions as wrapping around on overflow. +// Catalog fixed-width scalars ground on ReturnWrapping — not saturating, trap, or undefined. data lean_upstream_fixed_width_overflow_returns_rule: OverflowReturnValueRule = ReturnWrapping diff --git a/dag/extdeps/languages/lean/subject.dag b/dag/extdeps/languages/lean/subject.dag index 23031d2b643..118da509804 100644 --- a/dag/extdeps/languages/lean/subject.dag +++ b/dag/extdeps/languages/lean/subject.dag @@ -19,7 +19,15 @@ data lean_language_further_authority_1: ExternalAuthority = ExternalAuthority { } } -data lean_language_subject_note: String = "LEAN, cited to The Lean Language Reference (verified live 2026-08-04 by fetching the cited locator and confirming its title and publisher from the response, per the D0 verify-or-refuse rule): the fetched document is titled 'The Lean Language Reference', describes Lean version 4.33.0-rc2, and is published by the Lean project with source at github.com/leanprover/reference-manual. NOTE: dag/extdeps/languages/lean/overflow.dag already carries a cited upstream fact about Lean fixed-precision integer overflow. That module is a FACT carrier about this subject and remains in the frozen legacy scope manifest; this file is the SUBJECT it is about. The two are not duplicates — one says what Lean is, the other records a specific semantic fact cited from it." +// LEAN, cited to The Lean Language Reference (verified live 2026-08-04 by fetching the cited +// locator and confirming its title and publisher from the response, per the D0 verify-or-refuse +// rule): the fetched document is titled 'The Lean Language Reference', describes Lean version +// 4.33.0-rc2, and is published by the Lean project with source at +// github.com/leanprover/reference-manual. NOTE: dag/extdeps/languages/lean/overflow.dag already +// carries a cited upstream fact about Lean fixed-precision integer overflow. That module is a FACT +// carrier about this subject and remains in the frozen legacy scope manifest; this file is the +// SUBJECT it is about. The two are not duplicates — one says what Lean is, the other records a +// specific semantic fact cited from it. type Lean = | LeanTarget diff --git a/dag/extdeps/languages/llvm_ir/subject.dag b/dag/extdeps/languages/llvm_ir/subject.dag index ccbee010039..28dc55ad7d2 100644 --- a/dag/extdeps/languages/llvm_ir/subject.dag +++ b/dag/extdeps/languages/llvm_ir/subject.dag @@ -12,7 +12,15 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data llvm_ir_language_subject_note: String = "LLVM IR, cited to the LLVM Language Reference Manual (verified live 2026-08-04 by fetching the cited locator and confirming its title and publisher from the response, per the D0 verify-or-refuse rule): the fetched document is titled 'LLVM Language Reference Manual' and self-describes as 'a reference manual for the LLVM assembly language', documenting the human-readable notation of LLVM's three equivalent forms (in-memory IR, bitcode, assembly). SUBJECT SCOPE: the intermediate representation as documented by the LLVM project. LLVM IR is explicitly NOT a stable standard across releases, which is why the subject carries no revision — per external_model_scope_revision_grain_note a specific LLVM release is a fact on a release row, not a property of this subject." +// LLVM IR, cited to the LLVM Language Reference Manual (verified live 2026-08-04 by fetching the +// cited locator and confirming its title and publisher from the response, per the D0 +// verify-or-refuse rule): the fetched document is titled 'LLVM Language Reference Manual' and +// self-describes as 'a reference manual for the LLVM assembly language', documenting the +// human-readable notation of LLVM's three equivalent forms (in-memory IR, bitcode, assembly). +// SUBJECT SCOPE: the intermediate representation as documented by the LLVM project. LLVM IR is +// explicitly NOT a stable standard across releases, which is why the subject carries no revision — +// per external_model_scope_revision_grain_note a specific LLVM release is a fact on a release row, +// not a property of this subject. type LlvmIr = | LlvmIrTarget diff --git a/dag/extdeps/languages/markdown/subject.dag b/dag/extdeps/languages/markdown/subject.dag index f3c583bfe5b..79f4edee976 100644 --- a/dag/extdeps/languages/markdown/subject.dag +++ b/dag/extdeps/languages/markdown/subject.dag @@ -12,7 +12,16 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data commonmark_format_subject_note: String = "COMMONMARK, cited to the CommonMark Spec (verified live 2026-08-04 by fetching the cited locator and confirming its title and publisher from the response, per the D0 verify-or-refuse rule): the fetched index gives the CommonMark Spec at version 0.31.2, released 28 January 2024, maintained by John MacFarlane. WHY THE SUBJECT IS COMMONMARK AND NOT 'MARKDOWN': Markdown has no single governing specification — the original 2004 Perl implementation left many constructs undefined, which is the reason CommonMark exists — so 'Markdown' names a family, not an independently governed upstream. This repository's markdown surface must therefore state WHICH dialect it implements; CommonMark is the one with a testable specification, and registering it under that name rather than the family name is the DESIGN section 3 move of citing the real upstream instead of a convenient nickname." +// COMMONMARK, cited to the CommonMark Spec (verified live 2026-08-04 by fetching the cited locator +// and confirming its title and publisher from the response, per the D0 verify-or-refuse rule): the +// fetched index gives the CommonMark Spec at version 0.31.2, released 28 January 2024, maintained +// by John MacFarlane. WHY THE SUBJECT IS COMMONMARK AND NOT 'MARKDOWN': Markdown has no single +// governing specification — the original 2004 Perl implementation left many constructs undefined, +// which is the reason CommonMark exists — so 'Markdown' names a family, not an independently +// governed upstream. This repository's markdown surface must therefore state WHICH dialect it +// implements; CommonMark is the one with a testable specification, and registering it under that +// name rather than the family name is the DESIGN section 3 move of citing the real upstream instead +// of a convenient nickname. type CommonMark = | CommonMarkTarget diff --git a/dag/extdeps/languages/ptx/subject.dag b/dag/extdeps/languages/ptx/subject.dag index fd1b4da4bba..298a8aadb0c 100644 --- a/dag/extdeps/languages/ptx/subject.dag +++ b/dag/extdeps/languages/ptx/subject.dag @@ -12,7 +12,14 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data ptx_language_subject_note: String = "PTX, cited to the NVIDIA Parallel Thread Execution ISA (verified live 2026-08-04 by fetching the cited locator and confirming its title and publisher from the response, per the D0 verify-or-refuse rule): the fetched document is titled 'Parallel Thread Execution ISA Version 9.3', published by NVIDIA, and describes PTX as 'a low-level parallel thread execution virtual machine and instruction set architecture' that is 'a stable ISA that spans multiple GPU generations'. PTX is a VIRTUAL ISA translated at install time to a target-specific instruction set, which is why it is a language-target subject here and not a machine architecture subject — the physical GPU ISA it lowers to is a different upstream fact." +// PTX, cited to the NVIDIA Parallel Thread Execution ISA (verified live 2026-08-04 by fetching the +// cited locator and confirming its title and publisher from the response, per the D0 +// verify-or-refuse rule): the fetched document is titled 'Parallel Thread Execution ISA Version +// 9.3', published by NVIDIA, and describes PTX as 'a low-level parallel thread execution virtual +// machine and instruction set architecture' that is 'a stable ISA that spans multiple GPU +// generations'. PTX is a VIRTUAL ISA translated at install time to a target-specific instruction +// set, which is why it is a language-target subject here and not a machine architecture subject — +// the physical GPU ISA it lowers to is a different upstream fact. type Ptx = | PtxTarget diff --git a/dag/extdeps/languages/python/subject.dag b/dag/extdeps/languages/python/subject.dag index cb8d752a7c6..ed7ea0c848f 100644 --- a/dag/extdeps/languages/python/subject.dag +++ b/dag/extdeps/languages/python/subject.dag @@ -19,7 +19,24 @@ data python_language_further_authority_1: ExternalAuthority = ExternalAuthority } } -data python_language_subject_note: String = "PYTHON, cited to The Python Language Reference (verified live 2026-08-04 by fetching the cited locator and confirming its title and publisher from the response, per the D0 verify-or-refuse rule): the fetched document is titled 'The Python Language Reference', is published by the Python Software Foundation, and states that it 'describes the syntax and core semantics of the language'. SUBJECT SCOPE: this is the LANGUAGE as specified by the PSF, not CPython the implementation, which is a separate upstream product and would take its own cited subject declaration if it were ever registered as a target in its own right. THE RUNTIME-CHECKER CENSUS ROW FOR THE INTERPRETER IS NEVERTHELESS JOINED TO THIS SUBJECT, and an earlier revision of this sentence said the opposite and named the wrong module besides (cursor review 48393). It argued the row was deliberately unidentified because an interpreter is a separate product. That argument is true and irrelevant: a census row's subject answers WHICH TARGET THIS SURFACE REALIZES, not whether the tool IS the language. The interpreter realizes Python. The authority for that policy is one place, gunbc.language_target_registry runtime_checker_subject_note, and this carrier should never have carried a second, contradicting copy of it. The row's module path is extdeps.python; the extdeps.python.interpreter this sentence used to name is not a module at all — it was the file path dag/extdeps/python/python.dag transcribed as a module path, one of three such phantoms corrected in the registry's census_path_resolution_note, so the stale sentence was handing a reader both the wrong join fact and a module that does not exist." +// PYTHON, cited to The Python Language Reference (verified live 2026-08-04 by fetching the cited +// locator and confirming its title and publisher from the response, per the D0 verify-or-refuse +// rule): the fetched document is titled 'The Python Language Reference', is published by the Python +// Software Foundation, and states that it 'describes the syntax and core semantics of the +// language'. SUBJECT SCOPE: this is the LANGUAGE as specified by the PSF, not CPython the +// implementation, which is a separate upstream product and would take its own cited subject +// declaration if it were ever registered as a target in its own right. THE RUNTIME-CHECKER CENSUS +// ROW FOR THE INTERPRETER IS NEVERTHELESS JOINED TO THIS SUBJECT, and an earlier revision of this +// sentence said the opposite and named the wrong module besides (cursor review 48393). It argued +// the row was deliberately unidentified because an interpreter is a separate product. That argument +// is true and irrelevant: a census row's subject answers WHICH TARGET THIS SURFACE REALIZES, not +// whether the tool IS the language. The interpreter realizes Python. The authority for that policy +// is one place, gunbc.language_target_registry runtime_checker_subject_note, and this carrier +// should never have carried a second, contradicting copy of it. The row's module path is +// extdeps.python; the extdeps.python.interpreter this sentence used to name is not a module at all +// — it was the file path dag/extdeps/python/python.dag transcribed as a module path, one of three +// such phantoms corrected in the registry's census_path_resolution_note, so the stale sentence was +// handing a reader both the wrong join fact and a module that does not exist. type Python = | PythonTarget diff --git a/dag/extdeps/languages/react_jsx.dag b/dag/extdeps/languages/react_jsx.dag index 101bd2978cb..6661a4efc05 100644 --- a/dag/extdeps/languages/react_jsx.dag +++ b/dag/extdeps/languages/react_jsx.dag @@ -24,7 +24,14 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data react_jsx_keep_mark_note: String = "KEEP with mark (#7272 A3; gpu/wgsl treatment). Live CI keystone corpus_ci_gate_row_react_jsx_emit_keystone + hostile-href XSS control + void_element_test consumer — not dead vocabulary. No production consumer today. Sole owner: gunb.ai site-subsumption. Dissolution is react_jsx_site_subsumption_jsx_page_emission below — not an already-landed HTML helper, not belt-B, not an unnamed census. If that lane closes without adopting JSX, delete the removal set: this module, dag/test/claim/react_jsx_emit_test.dag, corpus_ci_gate_row_react_jsx_emit_keystone enrollment (+ roster row at :580), and the void_element_test import of serialize_jsx_component." +// KEEP with mark (#7272 A3; gpu/wgsl treatment). Live CI keystone +// corpus_ci_gate_row_react_jsx_emit_keystone + hostile-href XSS control + void_element_test +// consumer — not dead vocabulary. No production consumer today. Sole owner: gunb.ai +// site-subsumption. Dissolution is react_jsx_site_subsumption_jsx_page_emission below — not an +// already-landed HTML helper, not belt-B, not an unnamed census. If that lane closes without +// adopting JSX, delete the removal set: this module, dag/test/claim/react_jsx_emit_test.dag, +// corpus_ci_gate_row_react_jsx_emit_keystone enrollment (+ roster row at :580), and the +// void_element_test import of serialize_jsx_component. data react_jsx_site_subsumption_jsx_page_emission: String = "dissolve-on: gunb.ai site-subsumption emits ≥1 production page through serialize_jsx_component (a non-test import under gunbc.site.*). That call is the dissolution event for react_jsx_awaiting_production_consumer." diff --git a/dag/extdeps/languages/riscv/subject.dag b/dag/extdeps/languages/riscv/subject.dag index 1ea94b8bb10..907004ffd48 100644 --- a/dag/extdeps/languages/riscv/subject.dag +++ b/dag/extdeps/languages/riscv/subject.dag @@ -19,7 +19,18 @@ data riscv_isa_further_authority_1: ExternalAuthority = ExternalAuthority { } } -data riscv_isa_subject_note: String = "THE RISC-V ISA, cited to RISC-V International (verified live 2026-08-04 by fetching the cited locator and confirming its title and publisher from the response, per the D0 verify-or-refuse rule): the fetched page states the specifications are 'collaboratively developed, ratified, and maintained by contributing members of RISC-V International' and lists 'The RISC-V Instruction Set Manual, Volume I' (unprivileged/user-level ISA) and 'Volume II: Privileged Architecture', available through the ratified specification library at docs.riscv.org. WHY THIS SUBJECT EXISTS ALONE AMONG THE THREE ISAs: v2.extdeps.languages.machine_code carries one Isa coproduct with three arms — X86_64Isa, Aarch64Isa, RiscV64Isa — which are three independently governed upstreams fused into one surface, the exact shape DESIGN section 3 forbids a generic hub from taking. RISC-V is registered because its specification authority verified; x86-64 and Aarch64 are NOT registered because their authorities refused this fetch, and that refusal is recorded rather than papered over with a plausible locator." +// THE RISC-V ISA, cited to RISC-V International (verified live 2026-08-04 by fetching the cited +// locator and confirming its title and publisher from the response, per the D0 verify-or-refuse +// rule): the fetched page states the specifications are 'collaboratively developed, ratified, and +// maintained by contributing members of RISC-V International' and lists 'The RISC-V Instruction Set +// Manual, Volume I' (unprivileged/user-level ISA) and 'Volume II: Privileged Architecture', +// available through the ratified specification library at docs.riscv.org. WHY THIS SUBJECT EXISTS +// ALONE AMONG THE THREE ISAs: v2.extdeps.languages.machine_code carries one Isa coproduct with +// three arms — X86_64Isa, Aarch64Isa, RiscV64Isa — which are three independently governed upstreams +// fused into one surface, the exact shape DESIGN section 3 forbids a generic hub from taking. +// RISC-V is registered because its specification authority verified; x86-64 and Aarch64 are NOT +// registered because their authorities refused this fetch, and that refusal is recorded rather than +// papered over with a plausible locator. type RiscV = | RiscVTarget diff --git a/dag/extdeps/languages/rust/capabilities.dag b/dag/extdeps/languages/rust/capabilities.dag index 1e04e2c33d7..ef0b607b059 100644 --- a/dag/extdeps/languages/rust/capabilities.dag +++ b/dag/extdeps/languages/rust/capabilities.dag @@ -207,7 +207,6 @@ type RustCapability data rust_capability_alphabet_note: String = "CLOSED Rust capability alphabet. The B1 keyed contract makes TargetCapabilityKey an OPEN Symbol brand so unrelated modules can share one key space without a coproduct edit -- but an open brand cannot be matched exhaustively, and rust_trait_derive_spelling was TOTAL by exhaustive match before this migration. Deleting the coproduct outright would have traded a structural guarantee (DESIGN section 4b: no Accepted program can spell an unknown capability) for a runtime refusal, which is a rung DROP dressed as a migration. So the coproduct is RE-HOMED rather than deleted: this closed alphabet is Rust s own realization vocabulary, exhaustive at every spelling site, and rust_capability_key is the single total producer that lifts it into the shared open key space. Interface speaks keys; realization speaks the alphabet (DESIGN section 3)." - fn record_derive_traits_heap() -> List { Cons { head: RustDebug, @@ -424,8 +423,21 @@ fn kernel_int_arithmetic_traits() -> List { } } - -data rust_capability_list_ops_home_note: String = "derive_traits_contain and derive_traits_union live HERE, typed on the closed RustCapability alphabet, and not in std.trait_derive_shape. They arrived in this migration by relocation rather than by authoring: in std they were typed on the OPEN TargetCapabilityKey brand while their only consumer, v1.compiler.trait_derive_emit v1_with_map_key_requirement, threads RustCapability end to end -- so the signature laundered the closed alphabet into the open key space, which is exactly the guarantee the alphabet exists to hold (DESIGN section 4b). Caught in review, not by the compiler. THE ALTERNATIVE REPAIR WAS WORSE AND IS RECORDED SO IT IS NOT RETRIED: keeping them in std and lifting to keys at the call site would have forced the union result back through a key-to-spelling step that no longer exists, since spelling is total only over the alphabet. DECLARED SECTION 2 RESIDUE: these are a set-union and a membership test over a list, specialized to one element type because this corpus carries no generic list union to instance. That is duplication waiting to happen -- the next element type that needs a union will either copy these or finally write the generic. Dissolves into a generic std list union when a second element type asks for one; deliberately not minted here, because authoring a new std primitive is a wider change than the review finding that produced this move." +// derive_traits_contain and derive_traits_union live HERE, typed on the closed RustCapability +// alphabet, and not in std.trait_derive_shape. They arrived in this migration by relocation rather +// than by authoring: in std they were typed on the OPEN TargetCapabilityKey brand while their only +// consumer, v1.compiler.trait_derive_emit v1_with_map_key_requirement, threads RustCapability end +// to end -- so the signature laundered the closed alphabet into the open key space, which is +// exactly the guarantee the alphabet exists to hold (DESIGN section 4b). Caught in review, not by +// the compiler. THE ALTERNATIVE REPAIR WAS WORSE AND IS RECORDED SO IT IS NOT RETRIED: keeping them +// in std and lifting to keys at the call site would have forced the union result back through a +// key-to-spelling step that no longer exists, since spelling is total only over the alphabet. +// DECLARED SECTION 2 RESIDUE: these are a set-union and a membership test over a list, specialized +// to one element type because this corpus carries no generic list union to instance. That is +// duplication waiting to happen -- the next element type that needs a union will either copy these +// or finally write the generic. Dissolves into a generic std list union when a second element type +// asks for one; deliberately not minted here, because authoring a new std primitive is a wider +// change than the review finding that produced this move. fn derive_traits_contain( traits: List, diff --git a/dag/extdeps/languages/rust/derive_contracts.dag b/dag/extdeps/languages/rust/derive_contracts.dag index b876a20645a..080f68dc330 100644 --- a/dag/extdeps/languages/rust/derive_contracts.dag +++ b/dag/extdeps/languages/rust/derive_contracts.dag @@ -112,7 +112,20 @@ data serde_1_0_228_hash_set_deserialize_authority: ExternalAuthority = ExternalA } } -data rust_vec_freemonoid_supplemental_generic_bound_rows_note: String = "SINGLE AUTHORITY for the trait -> supplemental-generic-bound facts of the FreeMonoid/Vec realization (im-15.1.0 im::Vector): deriving Debug, PartialEq, Serialize or Deserialize for an item carrying an im::Vector

field additionally requires P: Clone, because each of those upstream impls is conditional on A: Clone (cited per row). Clone itself is deliberately NOT a row: derive(Clone) emits a conditional impl whose own A: Clone requirement is exactly im::Vector's, so no supplemental fact exists for it (im_15_1_0_vector_clone_impl_authority documents the upstream impl, consumed by the v2 contract map's requirement-free Clone entry). This module is dag-rooted so BOTH pools read one authority: the v1 seed emitter (regen pool [src/v1, dag]) consumes these rows in v1.compiler.trait_derive_emit, and v2.extdeps.languages.rust builds its typed target_model contract map (rust_vec_supplemental_generic_bound_contracts) by folding these same rows — the mapping formerly restated there is collapsed onto this list (coordinating-session ruling, smart-ram-730 (a session, not the operator) 2026-08-19: consume, do not mint a second copy or a witness to police one)." +// SINGLE AUTHORITY for the trait -> supplemental-generic-bound facts of the FreeMonoid/Vec +// realization (im-15.1.0 im::Vector): deriving Debug, PartialEq, Serialize or Deserialize for an +// item carrying an im::Vector

field additionally requires P: Clone, because each of those +// upstream impls is conditional on A: Clone (cited per row). Clone itself is deliberately NOT a +// row: derive(Clone) emits a conditional impl whose own A: Clone requirement is exactly +// im::Vector's, so no supplemental fact exists for it (im_15_1_0_vector_clone_impl_authority +// documents the upstream impl, consumed by the v2 contract map's requirement-free Clone entry). +// This module is dag-rooted so BOTH pools read one authority: the v1 seed emitter (regen pool +// [src/v1, dag]) consumes these rows in v1.compiler.trait_derive_emit, and +// v2.extdeps.languages.rust builds its typed target_model contract map +// (rust_vec_supplemental_generic_bound_contracts) by folding these same rows — the mapping formerly +// restated there is collapsed onto this list (coordinating-session ruling, smart-ram-730 (a +// session, not the operator) 2026-08-19: consume, do not mint a second copy or a witness to police +// one). type RustVecSupplementalGenericBoundRow { derive_trait: RustCapability @@ -143,7 +156,24 @@ data rust_vec_freemonoid_supplemental_generic_bound_rows: List supplemental-generic-bound facts of the Set/BTreeSet +// realization -- CORRECTED 2026-08-21: the realized type is NOT std::collections::BTreeSet, it is +// im-15.1.0's im::OrdSet, aliased `as BTreeSet` in every emitted use statement +// (src/v1/stage0/src/lib.rs: `use im::{OrdSet as BTreeSet, Vector as Vec};`), so this row list must +// cite OrdSet's own impls, not std's. Verified against the vendored im-15.1.0 crate source: +// `impl Clone for OrdSet` (src/ord/set.rs#830) is unconditional, no row. `impl +// PartialEq for OrdSet` (set.rs#844) and `impl Debug for OrdSet` (set.rs#937) +// each require only P: Ord. `impl Serialize for OrdSet` and +// `impl<'de, A: Deserialize<'de> + Ord + Clone> Deserialize<'de> for OrdSet` (src/ser.rs#134, +// #125) each require BOTH P: Ord and P: Clone -- two rows per trait, sharing one upstream +// authority, merged into one requirement set by v2.extdeps.languages.rust +// rust_collection_supplemental_generic_bound_contracts_merge_row rather than the earlier +// single-row-per-derive_trait fold (which would have silently dropped one of the two on insert). +// This module is dag-rooted so both pools read one authority: the v1 seed emitter (regen pool +// [src/v1, dag]) consumes these rows in v1.compiler.trait_derive_emit, and +// v2.extdeps.languages.rust folds them into rust_btree_set_supplemental_generic_bound_contracts -- +// mirroring rust_vec_freemonoid_supplemental_generic_bound_rows's row-authority split rather than +// restating the requirement inline a second time. data rust_btree_set_supplemental_generic_bound_rows: List = [ RustVecSupplementalGenericBoundRow { diff --git a/dag/extdeps/languages/rust/emit.dag b/dag/extdeps/languages/rust/emit.dag index b16fbbe65cd..e5fb8769d98 100644 --- a/dag/extdeps/languages/rust/emit.dag +++ b/dag/extdeps/languages/rust/emit.dag @@ -322,7 +322,10 @@ fn rust_qualified_module_mod_filename(qualified_module: String) -> String { concat(rust_qualified_module_mod_basename(qualified_module: qualified_module), ".rs") } -data rust_pair_completion_spelling_note: String = "Rust spellings for the target-agnostic pair-completion rows in std.trait_derive_shape. Terminals only: trait paths, method names, where-bounds, carrier field syntax. The ARITHMETIC is not spelled here — each impl body is rendered from the row's polynomial arms, so a change to the Grothendieck construction is a row edit, never a string edit." +// Rust spellings for the target-agnostic pair-completion rows in std.trait_derive_shape. Terminals +// only: trait paths, method names, where-bounds, carrier field syntax. The ARITHMETIC is not +// spelled here — each impl body is rendered from the row's polynomial arms, so a change to the +// Grothendieck construction is a row edit, never a string edit. type RustPairCompletionSpelling { capability_key: PairCompletionOp @@ -341,7 +344,23 @@ data rust_pair_completion_spellings: List = [ { capability_key: PairCompletionOpDiv, method: "div", trait_path: "std::ops::Div", where_bounds: "where M: std::ops::Add + std::ops::Sub + std::ops::Div + Default," } ] -data rust_pair_completion_key_join_note: String = "The spelling rows carry the TargetCapabilityKey they answer for, so the join to std.trait_derive_shape pair_completion_op_rows is an identity match on the key itself. This REPLACES a sixteen-arm rust_pair_completion_op_key that mapped every capability to a method-name String purely to compare Strings -- a second, positional naming scheme for something the key already names (DESIGN section 3). That function was total-with-no-wildcard, which was the right repair for the coproduct it matched on; once the key became the open TargetCapabilityKey brand an exhaustive match was no longer available, and reconstructing one would have meant re-closing the brand. Joining on the key is the construction the totality argument was standing in for: a capability with no pair-completion spelling still resolves to Absent and still surfaces through the compile_error! arm. ONE THING GOT WEAKER AND IT IS NOT PAPERED OVER: the old arm printed the missing op's own method name, which it could only do because op_key was total over a closed coproduct. TargetCapabilityKey is an open Symbol brand and this corpus has no Symbol-to-String projection, so the arm now prints the keys that ARE spelled and says one row is unmatched, rather than naming the unmatched key. That is less precise and it is a real loss; it is not a silent one -- the arm still refuses at Rust compile time, still identifies the drifted pair of tables, and the reader can difference the printed list against pair_completion_op_rows in one step. NEXT-RUNG TRIGGER: a Symbol-to-String projection in v2.std.node, at which point the key names itself again." +// The spelling rows carry the TargetCapabilityKey they answer for, so the join to +// std.trait_derive_shape pair_completion_op_rows is an identity match on the key itself. This +// REPLACES a sixteen-arm rust_pair_completion_op_key that mapped every capability to a method-name +// String purely to compare Strings -- a second, positional naming scheme for something the key +// already names (DESIGN section 3). That function was total-with-no-wildcard, which was the right +// repair for the coproduct it matched on; once the key became the open TargetCapabilityKey brand an +// exhaustive match was no longer available, and reconstructing one would have meant re-closing the +// brand. Joining on the key is the construction the totality argument was standing in for: a +// capability with no pair-completion spelling still resolves to Absent and still surfaces through +// the compile_error! arm. ONE THING GOT WEAKER AND IT IS NOT PAPERED OVER: the old arm printed the +// missing op's own method name, which it could only do because op_key was total over a closed +// coproduct. TargetCapabilityKey is an open Symbol brand and this corpus has no Symbol-to-String +// projection, so the arm now prints the keys that ARE spelled and says one row is unmatched, rather +// than naming the unmatched key. That is less precise and it is a real loss; it is not a silent one +// -- the arm still refuses at Rust compile time, still identifies the drifted pair of tables, and +// the reader can difference the printed list against pair_completion_op_rows in one step. NEXT-RUNG +// TRIGGER: a Symbol-to-String projection in v2.std.node, at which point the key names itself again. fn rust_pair_completion_spelling_for(op: PairCompletionOp) -> RustPairCompletionSpelling? { rust_pair_completion_spellings diff --git a/dag/extdeps/languages/rust/representation.dag b/dag/extdeps/languages/rust/representation.dag index 18c41f386bb..5d43e55ef37 100644 --- a/dag/extdeps/languages/rust/representation.dag +++ b/dag/extdeps/languages/rust/representation.dag @@ -13,15 +13,15 @@ data rust_representation_external_authority: ExternalAuthority = ExternalAuthori } } -// THE REPRESENTATIONS THIS TARGET CAN GIVE A SOURCE TYPE, named as target facts and nothing else. -// A row here says how Rust realizes a representation; WHICH source declaration takes which -// representation is the corpus's fact (gunbc.rust_source_type_bindings), so no source name appears -// in this module. Two variants deliberately carry no realization row: RustSymbolNewtype -// (`struct Symbol(std::string::String)`, nominal) and RustInternedSymbol (a table index with a real -// intern table). They exist so the alternatives the symbol-semantics decision weighed are -// REPRESENTABLE and a binding to either refuses loudly at spelling time rather than being -// unwritable prose -- the newtype is the declared next rung for v2.std.node Symbol, triggered by -// the .dag checker distinguishing Symbol from String (gunbc.symbol_identity_census). +// THE REPRESENTATIONS THIS TARGET CAN GIVE A SOURCE TYPE, named as target facts only. A row says +// how Rust realizes a representation; WHICH source declaration takes which is the corpus's fact +// (gunbc.rust_source_type_bindings), so no source name appears here. Two variants deliberately +// carry no realization row: RustSymbolNewtype (`struct Symbol(std::string::String)`, nominal) and +// RustInternedSymbol (a table index with a real intern table). They exist so the alternatives the +// symbol-semantics decision weighed are REPRESENTABLE and a binding to either refuses loudly at +// spelling time rather than being unwritable prose — the newtype is the declared next rung for +// v2.std.node Symbol, triggered by the .dag checker distinguishing Symbol from String +// (gunbc.symbol_identity_census). type RustRepresentation = RustStdString | RustSymbolNewtype @@ -51,10 +51,10 @@ fn rust_representation_eq(a: RustRepresentation, b: RustRepresentation) -> Bool rust_representation_slug(representation: a) == rust_representation_slug(representation: b) } -// A realization is the spelling plus the three facts the old checkpoint row also carried and the -// emitter still consumes (v1.compiler.coercion is_copy / literal_suffix / default). They ride on the -// representation, not on the source name: `std::string::String` is not Copy whichever declaration -// it realizes. +// A realization is the spelling plus the three facts the old checkpoint row carried and the emitter +// still consumes (v1.compiler.coercion is_copy / literal_suffix / default). They ride on the +// representation, not the source name: `std::string::String` is not Copy whichever declaration it +// realizes. type RustRepresentationRealization { spelling: RepresentationSpelling default_expr: String? @@ -76,10 +76,10 @@ fn rust_realization(representation: RustRepresentation, reference_position: Stri } // FULLY QUALIFIED IN BOTH POSITIONS, INCLUDING WHERE THE BARE TOKEN WOULD COMPILE TODAY. The -// capture that motivated this module needs two things to coincide -- a bare token and an import -// binding the same token -- and qualification removes the first regardless of whether the second -// exists yet in any module. Primitive tokens (i64, f64, bool, ()) have no path form in Rust and no -// .dag declaration can shadow them through a use-line, so they are already unshadowable as written. +// motivating capture needs a bare token and an import binding the same token to coincide; +// qualification removes the first regardless of whether the second exists yet in any module. +// Primitive tokens (i64, f64, bool, ()) have no path form in Rust and no .dag declaration can +// shadow them through a use-line, so they are already unshadowable as written. data rust_representation_realizations: List = [ rust_realization(representation: RustStdString, reference_position: "std::string::String", grounding_position: "std::string::String", default_expr: Present { value: "std::string::String::new()" }, is_copy: false, literal_suffix: Present { value: ".to_string()" }), rust_realization(representation: RustI64, reference_position: "i64", grounding_position: "i64", default_expr: Present { value: "0" }, is_copy: true, literal_suffix: none), @@ -97,8 +97,8 @@ fn rust_representation_realization_for(representation: RustRepresentation) -> Ru // THE DECISION SHAPE THE EMITTER AND std.reference_realization ALREADY CONSUME, produced from an // exact binding: a TypeCheckpoint whose dag_name is the DECLARATION's own name and whose two // spellings are the representation's qualified reference and grounding spellings. Absent means the -// bound representation has no realization row on this target (RustSymbolNewtype, RustInternedSymbol) -// -- the caller refuses; nothing here invents a spelling. +// bound representation has no realization row on this target (RustSymbolNewtype, +// RustInternedSymbol) — the caller refuses; nothing here invents a spelling. fn rust_exact_type_checkpoint(binding: SourceTypeTargetBinding) -> TypeCheckpoint? { match rust_representation_realization_for(representation: binding.representation) { Present { value: r } => diff --git a/dag/extdeps/languages/rust/subject.dag b/dag/extdeps/languages/rust/subject.dag index 0f4dc67c243..017783ad931 100644 --- a/dag/extdeps/languages/rust/subject.dag +++ b/dag/extdeps/languages/rust/subject.dag @@ -19,7 +19,25 @@ data rust_language_further_authority_1: ExternalAuthority = ExternalAuthority { } } -data rust_language_subject_note: String = "THE RUST LANGUAGE, cited to The Rust Reference (verified live 2026-08-04 by fetching the cited locator and confirming its title and publisher from the response, per the D0 verify-or-refuse rule): the fetched document self-describes as 'the primary reference for the Rust programming language', is published by the Rust project at github.com/rust-lang/reference, and tracks each six-week language release. Rust has no ISO-style specification, so the Reference is the authority rather than a standard document — that is a fact about Rust's governance, not a gap in this citation. SUBJECT SCOPE: this is the LANGUAGE. Cargo and rustc are separate upstream products with their own governance and would take their own cited subject declarations if either is ever registered as a target in its own right. THE RUNTIME-CHECKER CENSUS ROW FOR CARGO IS NEVERTHELESS JOINED TO THIS SUBJECT, and an earlier revision of this sentence said the opposite (cursor review 48393). It argued the row was deliberately unidentified because a toolchain is not a language. That argument is true and irrelevant, which is the whole defect: a census row's subject answers WHICH TARGET THIS SURFACE REALIZES, not whether the tool IS the language. Cargo realizes Rust. The authority for that policy is one place, gunbc.language_target_registry runtime_checker_subject_note, and this carrier should never have carried a second, contradicting copy of it — the note reversed the reasoning in the same change that landed the join, and this sentence was left describing the world before it. Binding a surface to a target does not merge their identities; the module path is extdeps.cargo, not extdeps.rust.cargo, which was a file path transcribed as a module path and is corrected in the registry's census_path_resolution_note." +// THE RUST LANGUAGE, cited to The Rust Reference (verified live 2026-08-04 by fetching the cited +// locator and confirming its title and publisher from the response, per the D0 verify-or-refuse +// rule): the fetched document self-describes as 'the primary reference for the Rust programming +// language', is published by the Rust project at github.com/rust-lang/reference, and tracks each +// six-week language release. Rust has no ISO-style specification, so the Reference is the authority +// rather than a standard document — that is a fact about Rust's governance, not a gap in this +// citation. SUBJECT SCOPE: this is the LANGUAGE. Cargo and rustc are separate upstream products +// with their own governance and would take their own cited subject declarations if either is ever +// registered as a target in its own right. THE RUNTIME-CHECKER CENSUS ROW FOR CARGO IS NEVERTHELESS +// JOINED TO THIS SUBJECT, and an earlier revision of this sentence said the opposite (cursor review +// 48393). It argued the row was deliberately unidentified because a toolchain is not a language. +// That argument is true and irrelevant, which is the whole defect: a census row's subject answers +// WHICH TARGET THIS SURFACE REALIZES, not whether the tool IS the language. Cargo realizes Rust. +// The authority for that policy is one place, gunbc.language_target_registry +// runtime_checker_subject_note, and this carrier should never have carried a second, contradicting +// copy of it — the note reversed the reasoning in the same change that landed the join, and this +// sentence was left describing the world before it. Binding a surface to a target does not merge +// their identities; the module path is extdeps.cargo, not extdeps.rust.cargo, which was a file path +// transcribed as a module path and is corrected in the registry's census_path_resolution_note. type Rust = | RustTarget diff --git a/dag/extdeps/languages/rust/types.dag b/dag/extdeps/languages/rust/types.dag index f5de45d9e21..88b6c90a8b7 100644 --- a/dag/extdeps/languages/rust/types.dag +++ b/dag/extdeps/languages/rust/types.dag @@ -24,7 +24,23 @@ data rust_type_checkpoints: List = [ { dag_name: "Json", target_type: "serde_json::Value", grounding_type: "serde_json::Value", default_expr: "serde_json::Value::Null", is_copy: false, literal_suffix: none } ] -data rust_checkpoint_symbol_row_migrated_note: String = "The Symbol row is MIGRATED, not retired: v2.std.node Symbol -- the only declaration of that spelling in the corpus -- now carries an exact row in gunbc.rust_source_type_bindings (RustStdString, spelled std::string::String in both positions by extdeps.languages.rust.representation), and this table no longer answers for the spelling at all. WHAT THE ROW COST WHILE IT STOOD, measured on the first self-host typeck run (XL-0, gunbc#9665 freeze): it answered the bare token `String` for a reference to an OPAQUE declaration, and in every module that also imports v2.std.text String (a structural FreeMonoid the emitter renders as its own alias) that token re-resolved to the text carrier, so `fn f(identity: Symbol)` came out expecting Rc> while every caller passed the host string -- the dominant E0308 population, one defect. Two independent faults were in the row: it was keyed on a spelling (the bypass checkpoint_table_bypasses_identity_note in v1.compiler.emit_rust records for every row here) and it carried a spelling a use-line can shadow. Exact keying closes the first, qualification closes the second, and both were required. The verdict on every remaining row of this table is in gunbc.rust_source_type_bindings checkpoint_row_migration_rows, held to this table by test.claim.symbol_identity_binding_witness. The kernel Symbol literal (`^x`) never needed this row: v1.compiler.types types it string_type and v1.compiler.emit reads its suffix from the String row when no Symbol row exists." +// The Symbol row is MIGRATED, not retired: v2.std.node Symbol -- the only declaration of that +// spelling in the corpus -- now carries an exact row in gunbc.rust_source_type_bindings +// (RustStdString, spelled std::string::String in both positions by +// extdeps.languages.rust.representation), and this table no longer answers for the spelling at all. +// WHAT THE ROW COST WHILE IT STOOD, measured on the first self-host typeck run (XL-0, gunbc#9665 +// freeze): it answered the bare token `String` for a reference to an OPAQUE declaration, and in +// every module that also imports v2.std.text String (a structural FreeMonoid the emitter +// renders as its own alias) that token re-resolved to the text carrier, so `fn f(identity: Symbol)` +// came out expecting Rc> while every caller passed the host string -- the dominant +// E0308 population, one defect. Two independent faults were in the row: it was keyed on a spelling +// (the bypass checkpoint_table_bypasses_identity_note in v1.compiler.emit_rust records for every +// row here) and it carried a spelling a use-line can shadow. Exact keying closes the first, +// qualification closes the second, and both were required. The verdict on every remaining row of +// this table is in gunbc.rust_source_type_bindings checkpoint_row_migration_rows, held to this +// table by test.claim.symbol_identity_binding_witness. The kernel Symbol literal (`^x`) never +// needed this row: v1.compiler.types types it string_type and v1.compiler.emit reads its suffix +// from the String row when no Symbol row exists. data rust_checkpoint_hash_row_retired_note: String = "The Hash row is RETIRED, not rebound, and the distinction is the rule it follows: a target row survives only where some exact source declaration genuinely realizes that way. A census of every `type Hash` declaration in the corpus finds exactly ONE -- v2.std.node.Hash = Fnv1a64Structural (src/v2/std/node.dag) -- and it must NOT realize as v1_rt::Hash. Every remaining occurrence of that spelling in the seed is inside a STRING LITERAL in v1.runtime_rust, which is generated v1_rt.rs text rather than a .dag type reference, so no declaration is left for the row to bind. WHAT THE ROW COST WHILE IT STOOD, measured rather than predicted: Fnv1a64Structural's public record constructors are deleted precisely so the `where lower_hex_16` refinement IS its construction wall (std.content_hash content_hash_family_constructor_note), and this row rendered that carrier away as a bare String alias, taking the wall with it -- a DESIGN 4b construction rung demoted with no diagnostic anywhere. The live specimen is the emitted 05_eval closure: v2_std_node.rs carried `pub type Hash = v1_rt::Hash` while functions in the SAME file returned Rc, and the two disagreed at 70 distinct E0308 sites. checkpoint_table_bypasses_identity_note recorded this class from declarations at both ends and stated that no live site had been produced by anyone; that emission is the site. Retiring the row is INDEPENDENT of keying the remaining rows on declaration identity: those rows still bypass identity, still answer one spelling for two declarations, and the emitter's type renderer carries no module plumbing to key them on -- see checkpoint_table_bypasses_identity_note for that residue, which this change does not close." diff --git a/dag/extdeps/languages/spice/subject.dag b/dag/extdeps/languages/spice/subject.dag index afe5ad11311..a4dce45ab91 100644 --- a/dag/extdeps/languages/spice/subject.dag +++ b/dag/extdeps/languages/spice/subject.dag @@ -12,7 +12,38 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data spice_netlist_language_subject_note: String = "THE SPICE NETLIST LANGUAGE — THE TARGET MEDIUM, SPLIT FROM THE SIMULATOR THAT EXECUTES IT (cursor review 48122, P0; the split IS this file's reason to exist). An earlier revision registered ngspice as the target itself, which collapsed an interface into one of its implementations — the same fusion this registry refuses for machine_code, committed one row over. The surface being modelled makes the split obvious once looked at: v2.extdeps.formats.spice carries a generic SpiceNetlist with a generic spice_emit AND a separate spice_emit_ngspice, which is exactly the shape of a medium plus a per-implementation realization. So the medium is the target and ngspice is a RUNTIME bound to it, carried at gunbc.language_target_registry as a TargetRuntimeBinding rather than as a second language. VERIFIED LIVE 2026-08-04: the cited Berkeley page states SPICE was developed at UC Berkeley and describes the 1972 release as the industry-standard tool for integrated circuit design. THE HONEST WEAKNESS OF THIS CITATION, stated rather than smoothed over: SPICE has NO single normative netlist specification. Berkeley is the origin authority, the fetched page references a Spice3 README and Postscript documentation without naming a formal netlist grammar, and there is no SPICE standards committee. That is a real property of this target, not a gap in the lookup. A support claim for SPICE must therefore name the dialect it implements, for the same reason the CommonMark subject must not be called 'Markdown'. THE DIALECT AUTHORITY IS DELIBERATELY NOT CITED HERE, AND AN EARLIER REVISION OF THIS FILE GOT IT WRONG (cursor review 48361). That revision carried the ngspice manual as a second citation of this subject, reasoning that a weak origin citation should be shored up by the readable authority for the concrete dialect. That is the exact violation this same PR wrote the rule against one file over: extdeps.languages.bash.subject records that a citation grounds THIS subject and that a second independently governed subject is never a citation of the first, which is why the POSIX specification is its own subject with conformance joined at the product layer. The ngspice manual is not a document about the SPICE netlist medium — it is ngspice's OWN governing document, and it is literally the authority anchor of extdeps.simulators.ngspice.subject. Borrowing it here claimed ngspice's upstream grounds this one, and it re-forked the single fact the medium/runtime split had just separated. The self-contradiction is the tell: the split was made BECAUSE the two are independently governed, so the citation could not consistently treat them as one. WHERE THE DIALECT FACT LIVES INSTEAD: gunbc.language_target_registry's TargetRuntimeBinding already relates this medium to ngspice as the runtime that would execute it, which is the product-layer join the Bash/POSIX case uses for the same shape. A weak citation stated honestly is better than a strong one belonging to somebody else." +// THE SPICE NETLIST LANGUAGE — THE TARGET MEDIUM, SPLIT FROM THE SIMULATOR THAT EXECUTES IT (cursor +// review 48122, P0; the split IS this file's reason to exist). An earlier revision registered +// ngspice as the target itself, which collapsed an interface into one of its implementations — the +// same fusion this registry refuses for machine_code, committed one row over. The surface being +// modelled makes the split obvious once looked at: v2.extdeps.formats.spice carries a generic +// SpiceNetlist with a generic spice_emit AND a separate spice_emit_ngspice, which is exactly the +// shape of a medium plus a per-implementation realization. So the medium is the target and ngspice +// is a RUNTIME bound to it, carried at gunbc.language_target_registry as a TargetRuntimeBinding +// rather than as a second language. VERIFIED LIVE 2026-08-04: the cited Berkeley page states SPICE +// was developed at UC Berkeley and describes the 1972 release as the industry-standard tool for +// integrated circuit design. THE HONEST WEAKNESS OF THIS CITATION, stated rather than smoothed +// over: SPICE has NO single normative netlist specification. Berkeley is the origin authority, the +// fetched page references a Spice3 README and Postscript documentation without naming a formal +// netlist grammar, and there is no SPICE standards committee. That is a real property of this +// target, not a gap in the lookup. A support claim for SPICE must therefore name the dialect it +// implements, for the same reason the CommonMark subject must not be called 'Markdown'. THE DIALECT +// AUTHORITY IS DELIBERATELY NOT CITED HERE, AND AN EARLIER REVISION OF THIS FILE GOT IT WRONG +// (cursor review 48361). That revision carried the ngspice manual as a second citation of this +// subject, reasoning that a weak origin citation should be shored up by the readable authority for +// the concrete dialect. That is the exact violation this same PR wrote the rule against one file +// over: extdeps.languages.bash.subject records that a citation grounds THIS subject and that a +// second independently governed subject is never a citation of the first, which is why the POSIX +// specification is its own subject with conformance joined at the product layer. The ngspice manual +// is not a document about the SPICE netlist medium — it is ngspice's OWN governing document, and it +// is literally the authority anchor of extdeps.simulators.ngspice.subject. Borrowing it here +// claimed ngspice's upstream grounds this one, and it re-forked the single fact the medium/runtime +// split had just separated. The self-contradiction is the tell: the split was made BECAUSE the two +// are independently governed, so the citation could not consistently treat them as one. WHERE THE +// DIALECT FACT LIVES INSTEAD: gunbc.language_target_registry's TargetRuntimeBinding already relates +// this medium to ngspice as the runtime that would execute it, which is the product-layer join the +// Bash/POSIX case uses for the same shape. A weak citation stated honestly is better than a strong +// one belonging to somebody else. type SpiceNetlistLanguage = | SpiceNetlistLanguageTarget diff --git a/dag/extdeps/languages/swift/subject.dag b/dag/extdeps/languages/swift/subject.dag index c64951280e0..a8450d61110 100644 --- a/dag/extdeps/languages/swift/subject.dag +++ b/dag/extdeps/languages/swift/subject.dag @@ -19,7 +19,14 @@ data swift_language_further_authority_1: ExternalAuthority = ExternalAuthority { } } -data swift_language_subject_note: String = "SWIFT, cited to The Swift Programming Language (verified live 2026-08-04 by fetching the cited locator and confirming its title and publisher from the response, per the D0 verify-or-refuse rule): swift.org describes 'The Swift Programming Language' (TSPL) as 'the authoritative reference for Swift', offering a guided tour, a comprehensive guide, and a formal reference of the language; Swift is maintained by Apple Inc. and published through swift.org. WHY swift.org AND NOT docs.swift.org: the docs.swift.org book pages render their content through JavaScript and returned no readable title to this fetch, so the locator carried here is the one that actually verified." +// SWIFT, cited to The Swift Programming Language (verified live 2026-08-04 by fetching the cited +// locator and confirming its title and publisher from the response, per the D0 verify-or-refuse +// rule): swift.org describes 'The Swift Programming Language' (TSPL) as 'the authoritative +// reference for Swift', offering a guided tour, a comprehensive guide, and a formal reference of +// the language; Swift is maintained by Apple Inc. and published through swift.org. WHY swift.org +// AND NOT docs.swift.org: the docs.swift.org book pages render their content through JavaScript and +// returned no readable title to this fetch, so the locator carried here is the one that actually +// verified. type Swift = | SwiftTarget diff --git a/dag/extdeps/languages/typescript/program.dag b/dag/extdeps/languages/typescript/program.dag index 07b6568e517..33a20d79502 100644 --- a/dag/extdeps/languages/typescript/program.dag +++ b/dag/extdeps/languages/typescript/program.dag @@ -83,7 +83,15 @@ fn ts_call(callee: TsExpr, args: List) -> TsExpr { Call { callee: callee, args: args } } -data ts_new_note: String = "`new C(args)` — the NewExpression production, part of the cited language and previously absent from this grammar, so a constructor call had no representation at all. The gap is worth naming because the tempting alternative was Ident { name: \"new AudioContext()\" }: a hand-authored JS string smuggled through an identifier, which would have type-checked, serialized correctly, and defeated the entire point of modeling the program as a tree. That is the unmarked-workaround shape DESIGN rules a line-stop, so the production was added instead. Serialization does NOT parenthesize the callee, so a callee that is itself an operator expression (`new (a || b)()`) must be bound to a name first — the grammar has no Paren production and one is not minted speculatively (DESIGN section 6: priced by displaced cost, and no consumer needs it)." +// `new C(args)` — the NewExpression production, part of the cited language and previously absent +// from this grammar, so a constructor call had no representation at all. The gap is worth naming +// because the tempting alternative was Ident { name: "new AudioContext()" }: a hand-authored JS +// string smuggled through an identifier, which would have type-checked, serialized correctly, and +// defeated the entire point of modeling the program as a tree. That is the unmarked-workaround +// shape DESIGN rules a line-stop, so the production was added instead. Serialization does NOT +// parenthesize the callee, so a callee that is itself an operator expression (`new (a || b)()`) +// must be bound to a name first — the grammar has no Paren production and one is not minted +// speculatively (DESIGN section 6: priced by displaced cost, and no consumer needs it). fn ts_new(callee: TsExpr, args: List) -> TsExpr { New { callee: callee, args: args } @@ -222,7 +230,48 @@ fn join_array_elements(elements: List) -> String { }) } -data ts_operator_precedence_note: String = "THE SERIALIZER USED TO EMIT OPERAND TEXT WITHOUT REGARD FOR PRECEDENCE, which made it possible to build a well-typed TsExpr that serializes to syntactically valid JavaScript computing something ELSE. That is the sharpest failure shape a translator has: not a crash, not a refusal, a silent semantic substitution downstream of a green typecheck (DESIGN section 5). Every consumer of this module inherited it, because the defect is in the printer, not in any one program.\\n\\nFOUR LIVE PRODUCERS, found by reading the emitted asset rather than the model (2026-07-30, gunbc.roadmap_component). Every one of them was in a REASON channel — a tooltip, an accessible name, a refusal string — which is not a coincidence: a reason channel is read by a person on a bad day, never by a test, so it is exactly where a silent substitution survives longest.\\n BinOp('+') over Cond -> `seg.label + ': ' + seg.state + seg.detail ? ' — ' + seg.detail : ''`, which parses as `(a+b+c+d) ? e : f`. A concatenation is a non-empty string, so the test was ALWAYS true and the workflow stage's accessible name became the bare detail, losing its label and state.\\n BinOp('+') over Cond again, on the workflow tooltip: same shape, same always-true test.\\n BinOp('+') over BinOp('||') -> `'workflow observation refused: ' + wj.reason || 'no reason on the wire'`, which parses as `('...' + wj.reason) || '...'`. The left side is truthy even when wj.reason is undefined, so the fallback could never fire and a reasonless refusal rendered as the word undefined.\\n BinOp('+') over BinOp('||') -> `'host ' + ij.host_identity || 'unknown' + ' · repo ' + ij.repo_root || 'unknown'`, same mechanism, and here the entire repo half of the instance label was unreachable.\\n\\nThe model was never wrong in any of the four. The printer was. That is why the fix is a precedence-aware printer and not parentheses hand-added at four call sites: hand-parenthesizing concedes that the next author can write the same shape and get the same substitution, which is a validation standing where a construction was available. Grouping is now DERIVED from the table below.\\n\\nWHAT THE CHANGE COSTS, stated precisely because the tempting claim — 'only the defects move' — is FALSE and was written here before the diff was read. A group is emitted when an operand binds looser than its parent demands, AND when it ties under a parent that is not associative. The second case moves bytes at expressions that were already correct: right-nested `+` now renders as `a + (b + c)`, which appeared at seven sites in the roadmap client. That is not avoidable by declaring `+` associative, because it is not — `1 + (2 + 'x')` is '12x' and `(1 + 2) + 'x'` is '3x' — and a printer that cannot see operand types must therefore preserve the model's own grouping. Extra parentheses around a correct expression are noise; dropped parentheses around one are a wrong answer, so the trade is not close.\\n\\nAssociativity is honored where it is real: `||` and `&&` are associative, so a same-precedence right operand needs no group, and `=` is right-associative, which is exactly why the extremely common `x = c ? a : b` keeps its bare form rather than growing parentheses at every assignment in the corpus.\\n\\nUNRECOGNIZED OPERATORS FAIL CLOSED IN BOTH ROLES, and the two roles need opposite answers, which is why one table is read through two functions. As a PARENT an unknown operator claims the tightest binding, so any compound operand under it is grouped; as a CHILD it claims the loosest, so it is grouped inside its parent. Both directions over-parenthesize, which is the safe direction — the alternative is to guess a binding and silently re-associate, which is the defect this note exists to close. An ATOM operand is still left bare under an unknown parent, because an identifier or a literal has no internal structure to re-associate and grouping it would be noise standing in for a property." +// THE SERIALIZER USED TO EMIT OPERAND TEXT WITHOUT REGARD FOR PRECEDENCE, which made it possible to +// build a well-typed TsExpr that serializes to syntactically valid JavaScript computing something +// ELSE. That is the sharpest failure shape a translator has: not a crash, not a refusal, a silent +// semantic substitution downstream of a green typecheck (DESIGN section 5). Every consumer of this +// module inherited it, because the defect is in the printer, not in any one program.\n\nFOUR LIVE +// PRODUCERS, found by reading the emitted asset rather than the model (2026-07-30, +// gunbc.roadmap_component). Every one of them was in a REASON channel — a tooltip, an accessible +// name, a refusal string — which is not a coincidence: a reason channel is read by a person on a +// bad day, never by a test, so it is exactly where a silent substitution survives longest.\n +// BinOp('+') over Cond -> `seg.label + ': ' + seg.state + seg.detail ? ' — ' + seg.detail : ''`, +// which parses as `(a+b+c+d) ? e : f`. A concatenation is a non-empty string, so the test was +// ALWAYS true and the workflow stage's accessible name became the bare detail, losing its label and +// state.\n BinOp('+') over Cond again, on the workflow tooltip: same shape, same always-true +// test.\n BinOp('+') over BinOp('||') -> `'workflow observation refused: ' + wj.reason || 'no +// reason on the wire'`, which parses as `('...' + wj.reason) || '...'`. The left side is truthy +// even when wj.reason is undefined, so the fallback could never fire and a reasonless refusal +// rendered as the word undefined.\n BinOp('+') over BinOp('||') -> `'host ' + ij.host_identity || +// 'unknown' + ' · repo ' + ij.repo_root || 'unknown'`, same mechanism, and here the entire repo +// half of the instance label was unreachable.\n\nThe model was never wrong in any of the four. The +// printer was. That is why the fix is a precedence-aware printer and not parentheses hand-added at +// four call sites: hand-parenthesizing concedes that the next author can write the same shape and +// get the same substitution, which is a validation standing where a construction was available. +// Grouping is now DERIVED from the table below.\n\nWHAT THE CHANGE COSTS, stated precisely because +// the tempting claim — 'only the defects move' — is FALSE and was written here before the diff was +// read. A group is emitted when an operand binds looser than its parent demands, AND when it ties +// under a parent that is not associative. The second case moves bytes at expressions that were +// already correct: right-nested `+` now renders as `a + (b + c)`, which appeared at seven sites in +// the roadmap client. That is not avoidable by declaring `+` associative, because it is not — `1 + +// (2 + 'x')` is '12x' and `(1 + 2) + 'x'` is '3x' — and a printer that cannot see operand types +// must therefore preserve the model's own grouping. Extra parentheses around a correct expression +// are noise; dropped parentheses around one are a wrong answer, so the trade is not +// close.\n\nAssociativity is honored where it is real: `||` and `&&` are associative, so a +// same-precedence right operand needs no group, and `=` is right-associative, which is exactly why +// the extremely common `x = c ? a : b` keeps its bare form rather than growing parentheses at every +// assignment in the corpus.\n\nUNRECOGNIZED OPERATORS FAIL CLOSED IN BOTH ROLES, and the two roles +// need opposite answers, which is why one table is read through two functions. As a PARENT an +// unknown operator claims the tightest binding, so any compound operand under it is grouped; as a +// CHILD it claims the loosest, so it is grouped inside its parent. Both directions +// over-parenthesize, which is the safe direction — the alternative is to guess a binding and +// silently re-associate, which is the defect this note exists to close. An ATOM operand is still +// left bare under an unknown parent, because an identifier or a literal has no internal structure +// to re-associate and grouping it would be noise standing in for a property. data ts_atom_precedence: Int = 20 @@ -300,7 +349,15 @@ fn ts_operand_tie_needs_group(parent_op: String, is_right: Bool) -> Bool { } } -data atom_operand_never_grouped_note: String = "An operand that is already an atom — an identifier, a literal, a call, a member access — is NEVER grouped, and this guard states that invariant directly rather than letting it fall out of the arithmetic. Without it the numbers produce a genuinely wrong answer at one corner: an unrecognized parent operator claims atom precedence so that compound operands get grouped, which makes it TIE with atom operands, and the left-associative tie rule then parenthesized the right-hand atom — `a ?? (c)` — while leaving the left one bare. Asymmetric, pointless, and a sign the rule was being read off a number instead of the property. The property is that grouping exists to stop RE-ASSOCIATION, and an expression with no internal operator has nothing to re-associate." +// An operand that is already an atom — an identifier, a literal, a call, a member access — is NEVER +// grouped, and this guard states that invariant directly rather than letting it fall out of the +// arithmetic. Without it the numbers produce a genuinely wrong answer at one corner: an +// unrecognized parent operator claims atom precedence so that compound operands get grouped, which +// makes it TIE with atom operands, and the left-associative tie rule then parenthesized the +// right-hand atom — `a ?? (c)` — while leaving the left one bare. Asymmetric, pointless, and a sign +// the rule was being read off a number instead of the property. The property is that grouping +// exists to stop RE-ASSOCIATION, and an expression with no internal operator has nothing to +// re-associate. fn serialize_ts_operand(e: TsExpr, parent_op: String, is_right: Bool) -> String { let parent = ts_precedence_as_parent(op: parent_op) @@ -311,7 +368,14 @@ fn serialize_ts_operand(e: TsExpr, parent_op: String, is_right: Bool) -> String ts_group_if(text: serialize_ts_expr(e: e), needed: needed) } -data receiver_grouping_note: String = "A member access, index, or call binds tighter than any operator, so a receiver that IS an operator expression must be grouped or the emitted bytes re-associate: `ts_member(ts_binop(a, '||', b), 'forEach')` serialized as `a || b.forEach(...)`, which is not a syntax error but a SILENT no-op when `a` is truthy — the fabricated-plausible-output shape at the emitter layer, found live when the roadmap client's `(attempt.segments || []).forEach` ledger fill appended nothing and no console error fired. The receiver is grouped exactly when it is not an atom: atoms (identifiers, literals, calls, members) have nothing to re-associate, per atom_operand_never_grouped_note." +// A member access, index, or call binds tighter than any operator, so a receiver that IS an +// operator expression must be grouped or the emitted bytes re-associate: `ts_member(ts_binop(a, +// '||', b), 'forEach')` serialized as `a || b.forEach(...)`, which is not a syntax error but a +// SILENT no-op when `a` is truthy — the fabricated-plausible-output shape at the emitter layer, +// found live when the roadmap client's `(attempt.segments || []).forEach` ledger fill appended +// nothing and no console error fired. The receiver is grouped exactly when it is not an atom: atoms +// (identifiers, literals, calls, members) have nothing to re-associate, per +// atom_operand_never_grouped_note. fn serialize_ts_receiver(e: TsExpr) -> String { ts_group_if( diff --git a/dag/extdeps/languages/typescript/subject.dag b/dag/extdeps/languages/typescript/subject.dag index 2f799020143..0f9395c5d46 100644 --- a/dag/extdeps/languages/typescript/subject.dag +++ b/dag/extdeps/languages/typescript/subject.dag @@ -19,7 +19,16 @@ data typescript_language_further_authority_1: ExternalAuthority = ExternalAuthor } } -data typescript_language_subject_note: String = "TYPESCRIPT, AND THE FINDING THAT IT HAS NO FORMAL SPECIFICATION (verified live 2026-08-04 by fetching the cited locator and confirming its title and publisher from the response, per the D0 verify-or-refuse rule). The cited handbook states in its own text that it 'isn't intended to be a replacement for a language specification', and no separate normative TypeScript specification is published today — the formal spec was retired upstream. So the honest citation is Microsoft's handbook plus the implementation repository, and the authority for TypeScript's semantics is the compiler. This matters to the P0 product-browser profile: a bar-c support claim for TypeScript can be checked against tsc acceptance but NOT against a normative document, so 'conforms to the spec' is not a claim this repository can make about TypeScript. Recording that here keeps the profile honest rather than discovering it during a completion review." +// TYPESCRIPT, AND THE FINDING THAT IT HAS NO FORMAL SPECIFICATION (verified live 2026-08-04 by +// fetching the cited locator and confirming title and publisher from the response, per the D0 +// verify-or-refuse rule). The handbook says in its own text it 'isn't intended to be a replacement +// for a language specification', and no separate normative specification is published today — the +// formal spec was retired upstream. So the honest citation is Microsoft's handbook plus the +// implementation repository, and the authority for TypeScript's semantics is the compiler. For the +// P0 product-browser profile: a bar-c support claim for TypeScript can be checked against tsc +// acceptance but NOT against a normative document, so 'conforms to the spec' is not a claim this +// repository can make about TypeScript. Recorded here so the profile stays honest rather than +// discovering it in a completion review. type TypeScript = | TypeScriptTarget diff --git a/dag/extdeps/languages/verilog/subject.dag b/dag/extdeps/languages/verilog/subject.dag index 4f467e7c344..e4000cef59f 100644 --- a/dag/extdeps/languages/verilog/subject.dag +++ b/dag/extdeps/languages/verilog/subject.dag @@ -19,7 +19,18 @@ data verilog_language_further_authority_1: ExternalAuthority = ExternalAuthority } } -data verilog_language_subject_note: String = "VERILOG, cited to IEEE Std 1364-2005 (verified live 2026-08-04 by fetching the cited locator and confirming its title and publisher from the response, per the D0 verify-or-refuse rule): the fetched standard page gives 'IEEE Standard for Verilog Hardware Description Language', IEEE 1364-2005, published 2006. THE SUPERSESSION IS PART OF THE FACT AND IS WHY THERE ARE TWO CITATIONS: that same page records 1364-2005 as superseded by IEEE Std 1800-2009, the SystemVerilog unified language, and the further citation is IEEE 1800-2023, 'IEEE Standard for SystemVerilog--Unified Hardware Design, Specification, and Verification Language' (also fetched and confirmed). Both are carried because this repository's verilog surface models plain Verilog — a scan for SystemVerilog-only vocabulary (always_ff, always_comb, the 1800 designation) found none in it — while the living upstream standard is now 1800. A target modelling a superseded standard is a real support fact that belongs in the registry's evidence, not a detail to smooth over." +// VERILOG, cited to IEEE Std 1364-2005 (verified live 2026-08-04 by fetching the cited locator and +// confirming its title and publisher from the response, per the D0 verify-or-refuse rule): the +// fetched standard page gives 'IEEE Standard for Verilog Hardware Description Language', IEEE +// 1364-2005, published 2006. THE SUPERSESSION IS PART OF THE FACT AND IS WHY THERE ARE TWO +// CITATIONS: that same page records 1364-2005 as superseded by IEEE Std 1800-2009, the +// SystemVerilog unified language, and the further citation is IEEE 1800-2023, 'IEEE Standard for +// SystemVerilog--Unified Hardware Design, Specification, and Verification Language' (also fetched +// and confirmed). Both are carried because this repository's verilog surface models plain Verilog — +// a scan for SystemVerilog-only vocabulary (always_ff, always_comb, the 1800 designation) found +// none in it — while the living upstream standard is now 1800. A target modelling a superseded +// standard is a real support fact that belongs in the registry's evidence, not a detail to smooth +// over. type Verilog = | VerilogTarget diff --git a/dag/extdeps/languages/wasm/subject.dag b/dag/extdeps/languages/wasm/subject.dag index a8653e53dab..d2c467e069b 100644 --- a/dag/extdeps/languages/wasm/subject.dag +++ b/dag/extdeps/languages/wasm/subject.dag @@ -19,7 +19,13 @@ data webassembly_language_further_authority_1: ExternalAuthority = ExternalAutho } } -data webassembly_language_subject_note: String = "WEBASSEMBLY, cited to the WebAssembly Specification (verified live 2026-08-04 by fetching the cited locator and confirming its title and publisher from the response, per the D0 verify-or-refuse rule): the fetched document is titled 'WebAssembly Specification' at WebAssembly 3.0, edited by Andreas Rossberg and published by the WebAssembly Community Group under W3C. The core specification is the subject here; the JavaScript and Web embedding interfaces are separate documents and, if this repository ever targets them, separate concerns rather than arms of this subject." +// WEBASSEMBLY, cited to the WebAssembly Specification (verified live 2026-08-04 by fetching the +// cited locator and confirming its title and publisher from the response, per the D0 +// verify-or-refuse rule): the fetched document is titled 'WebAssembly Specification' at WebAssembly +// 3.0, edited by Andreas Rossberg and published by the WebAssembly Community Group under W3C. The +// core specification is the subject here; the JavaScript and Web embedding interfaces are separate +// documents and, if this repository ever targets them, separate concerns rather than arms of this +// subject. type WebAssembly = | WebAssemblyTarget diff --git a/dag/extdeps/languages/yaml/gha_workflow.dag b/dag/extdeps/languages/yaml/gha_workflow.dag index 65da484c21e..6ec37d9d60d 100644 --- a/dag/extdeps/languages/yaml/gha_workflow.dag +++ b/dag/extdeps/languages/yaml/gha_workflow.dag @@ -148,7 +148,13 @@ fn workflow_dispatch_yaml(inputs: List) -> YamlValue { } } -data workflow_dispatch_input_projection_note: String = "WorkflowDispatch.inputs was modeled in extdeps.github.actions but this serializer discarded the entire list and emitted workflow_dispatch: null. That specification-without-execution gap is now closed: every modeled input is projected into the workflow syntax, while the empty list deliberately retains the compact null form. InputChoice carries a non-empty options list and projects it as the required GitHub Actions options: mapping entry — a type: choice input without options is undispatchable. The CI heal revalidation input is the first production consumer and its witness reads the projected YamlValue rather than trusting generated text." +// WorkflowDispatch.inputs was modeled in extdeps.github.actions but this serializer discarded the +// entire list and emitted workflow_dispatch: null. That specification-without-execution gap is now +// closed: every modeled input is projected into the workflow syntax, while the empty list +// deliberately retains the compact null form. InputChoice carries a non-empty options list and +// projects it as the required GitHub Actions options: mapping entry — a type: choice input without +// options is undispatchable. The CI heal revalidation input is the first production consumer and +// its witness reads the projected YamlValue rather than trusting generated text. fn workflow_trigger_entry(trigger: WorkflowTrigger) -> YamlKeyValue { match trigger { @@ -196,7 +202,18 @@ fn cancel_in_progress_when_queue_max_yaml(spec: CancelInProgressWhenQueueMax) -> } } -data workflow_run_branches_emission_note: String = "GitHub filters workflow_run by the branch of the TRIGGERING run, documented as `You can use the branches or branches-ignore filter to specify what branches the triggering workflow must run on in order to trigger your workflow` (docs.github.com Events that trigger workflows, workflow_run, read 2026-08-21). That is the same fact a job-level `head_branch ==` condition tests, one grain earlier: the condition skips the job AFTER the run has been created and has entered its concurrency group, while the filter stops the event creating a run at all. The distinction is load-bearing rather than cosmetic -- a created-then-skipped run still occupies a concurrency slot, and in the default group an arriving entry evicts the pending one, so an irrelevant branch could cancel a real pending admission and then do nothing. The member is REQUIRED and emitted unconditionally, matching the Push and PullRequest siblings: a caller declaring this trigger must decide which branches it answers to, and an omitted filter meaning `every branch` is exactly the state that produced the eviction." +// GitHub filters workflow_run by the branch of the TRIGGERING run, documented as `You can use the +// branches or branches-ignore filter to specify what branches the triggering workflow must run on +// in order to trigger your workflow` (docs.github.com Events that trigger workflows, workflow_run, +// read 2026-08-21). That is the same fact a job-level `head_branch ==` condition tests, one grain +// earlier: the condition skips the job AFTER the run has been created and has entered its +// concurrency group, while the filter stops the event creating a run at all. The distinction is +// load-bearing rather than cosmetic -- a created-then-skipped run still occupies a concurrency +// slot, and in the default group an arriving entry evicts the pending one, so an irrelevant branch +// could cancel a real pending admission and then do nothing. The member is REQUIRED and emitted +// unconditionally, matching the Push and PullRequest siblings: a caller declaring this trigger must +// decide which branches it answers to, and an omitted filter meaning `every branch` is exactly the +// state that produced the eviction. data concurrency_queue_max_emission_note: String = "ConcurrencyMappingQueueMax exists to express GitHub's `queue: max`, and until this row it emitted NO queue key — so it serialized byte-identically to ConcurrencyMappingQueueNotMax and the modeled distinction had no realization difference at all (srv1-deploy-serialization-and-false-greens.md §4, second drop). That matters because the two variants mean DIFFERENT THINGS on the platform: the default (QueueNotMax) holds at most one running and one pending entry, and a newly-arriving entry CANCELS the existing pending one, whereas `queue: max` permits multiple pending up to a documented cap of 100 with cancel-in-progress forced false. A caller reaching for QueueMax is asking for the no-cancel queue and was silently getting pending-replacement. `queue` is emitted FIRST so the mapping reads queue-then-group-then-cancel; CancelInProgressWhenQueueMax already models the platform's constraint that cancel-in-progress cannot be true here, so that arm needs no extra guard. Witness: gha_job_projection_witness_test asserts the emitted mapping CONTAINS the queue key and that the two variants no longer serialize identically." @@ -396,7 +413,29 @@ fn optional_job_permissions_kv(perms: WorkflowPermissions?) -> List), and concurrency is load-bearing for the srv1 lane. strategy +// is DELETED from the model instead, along with the MatrixStrategy type it referenced: no site +// anywhere in the corpus ever set a MatrixStrategy VALUE — every one of the eight occurrences was a +// field initializer supplying the empty optional — and realizing it would require iterating +// MatrixStrategy.dimensions (a Map), which std offers no order-stable traversal for — a bare +// map_keys reach is exactly what v2.lens.determinism.determinism_compile_gate reds, so the honest +// options were an unrealizable declaration or removal, and DESIGN §5 picks removal. It returns as a +// modeled field when a real matrix consumer arrives WITH a determinism-safe map projection, not +// before. BYTE-STABILITY: every Job in the corpus currently sets name/outputs/concurrency to none, +// so adding these three projections changes no generated artifact — the new keys are proven to work +// by a synthetic all-fields job in gha_job_projection_witness_test rather than by any change to +// ci.yml or falsifier.yml, and that witness also pins that an absent field stays absent so the +// projections cannot start emitting empty keys. fn optional_job_concurrency_kv(spec: ConcurrencySpec?) -> List { match spec { diff --git a/dag/extdeps/languages/yaml/subject.dag b/dag/extdeps/languages/yaml/subject.dag index de5dc5d92a4..5b11f7ae6e1 100644 --- a/dag/extdeps/languages/yaml/subject.dag +++ b/dag/extdeps/languages/yaml/subject.dag @@ -12,7 +12,13 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data yaml_format_subject_note: String = "YAML, cited to the YAML 1.2.2 specification (verified live 2026-08-04 by fetching the cited locator and confirming its title and publisher from the response, per the D0 verify-or-refuse rule): the fetched document is 'YAML Ain't Markup Language (YAML) version 1.2', revision 1.2.2 dated 1 October 2021, maintained by the YAML Language Development Team. The upstream states this revision makes no normative changes from YAML 1.2 and corrects errors and clarity only — so 1.2 is the normative content and 1.2.2 is the readable edition of it, which is why the locator names the revision while the subject stays revision-free." +// YAML, cited to the YAML 1.2.2 specification (verified live 2026-08-04 by fetching the cited +// locator and confirming its title and publisher from the response, per the D0 verify-or-refuse +// rule): the fetched document is 'YAML Ain't Markup Language (YAML) version 1.2', revision 1.2.2 +// dated 1 October 2021, maintained by the YAML Language Development Team. The upstream states this +// revision makes no normative changes from YAML 1.2 and corrects errors and clarity only — so 1.2 +// is the normative content and 1.2.2 is the readable edition of it, which is why the locator names +// the revision while the subject stays revision-free. type Yaml = | YamlTarget diff --git a/dag/extdeps/linux/cgroup_v2_memory.dag b/dag/extdeps/linux/cgroup_v2_memory.dag index 34e86fd290b..80060d29a5e 100644 --- a/dag/extdeps/linux/cgroup_v2_memory.dag +++ b/dag/extdeps/linux/cgroup_v2_memory.dag @@ -29,18 +29,18 @@ data extdeps_model_scope: ExternalModelScope = ExternalModelScope { } // THE THREE KINDS ARE THE KERNEL'S, NOT OURS. cgroup-v2 documents each memory interface file with a -// distinct temporal meaning, and a carrier storing every one as a bare ByteSize would erase the -// distinction that decides whether a number may be compared, summed, or differenced at all: +// distinct temporal meaning; storing every one as a bare ByteSize would erase the distinction that +// decides whether a number may be compared, summed, or differenced at all: // // Instantaneous -- true at the moment of the read and at no other moment. // LifetimeEpochScoped -- an extremum accumulated since the cgroup was created or the value reset. -// It is NOT "the maximum during my observation window" unless the epoch is +// NOT "the maximum during my observation window" unless the epoch is // carried beside it, which is why the epoch is a field rather than prose. // CumulativeCounter -- monotone since cgroup creation. A single end snapshot is not an interval // measurement; only a start/end pair yields a delta. // -// Grounding the kinds here rather than in the consumer is DESIGN 3: the upstream spec already draws -// this line, so re-coining it downstream would nickname a fact the kernel owns. +// Grounding the kinds here rather than in the consumer is DESIGN 3: the upstream spec draws this +// line, so re-coining it downstream would nickname a fact the kernel owns. type CgroupMemoryMetricKind = Instantaneous | LifetimeEpochScoped @@ -79,10 +79,10 @@ fn cgroup_memory_metric_kind(f: CgroupMemoryInterfaceFile) -> CgroupMemoryMetric } // A NEGATIVE BODY IS MALFORMED, NOT A MAGNITUDE. parse_int admits negatives, and every file modeled -// here is documented as a non-negative byte count, so a leading minus is a malformed read rather -// than a value to take the absolute value of. nat_magnitude is applied only AFTER the sign is -// excluded, where it is the identity -- using it as the crossing on an unguarded Int would silently -// turn "-1" into 1. +// here is documented as a non-negative byte count, so a leading minus is a malformed read, not a +// value to take the absolute value of. nat_magnitude is applied only AFTER the sign is excluded, +// where it is the identity -- as the crossing on an unguarded Int it would silently turn "-1" +// into 1. fn parse_cgroup_nonnegative_bytes(text: String) -> ByteSize? { match parse_int(s: trim(text)) { Present { value: n } => @@ -96,10 +96,9 @@ fn parse_cgroup_nonnegative_bytes(text: String) -> ByteSize? { } // THE `max` SENTINEL IS A THIRD STATE, NOT A LARGE NUMBER. memory.max and memory.high hold either a -// byte count or the literal string "max", which the kernel documents as "no limit". Rendering that -// as a huge ByteSize would fabricate a limit the host does not have, and rendering it as absent -// would conflate it with a read that failed -- so it is its own arm, and an unparseable body is a -// fourth. +// byte count or the literal "max", which the kernel documents as "no limit". A huge ByteSize would +// fabricate a limit the host does not have; absent would conflate it with a failed read -- so it is +// its own arm, and an unparseable body is a fourth. type CgroupMemoryLimitValue = CgroupMemoryLimited { bytes: ByteSize } | CgroupMemoryUnlimited @@ -117,9 +116,9 @@ fn parse_cgroup_memory_limit(body: String) -> CgroupMemoryLimitValue { } // A COUNT FILE HAS NO `max` ARM. memory.current, memory.peak and memory.swap.current are documented -// as byte counts with no sentinel, so admitting one here would widen the parse past what the -// authority says the file can hold. Two arms, not three -- and a body of "max" in a count file is -// therefore Unparseable, which is the correct reading of a kernel that should never have written it. +// as byte counts with no sentinel, so admitting one would widen the parse past what the authority +// says the file can hold. Two arms, not three -- a body of "max" in a count file is Unparseable, +// the correct reading of a kernel that should never have written it. type CgroupMemoryCountValue = CgroupMemoryCount { bytes: ByteSize } | CgroupMemoryCountUnparseable { body: String } diff --git a/dag/extdeps/linux/proc_self_cgroup.dag b/dag/extdeps/linux/proc_self_cgroup.dag index 2d0e385ae97..790d798e59d 100644 --- a/dag/extdeps/linux/proc_self_cgroup.dag +++ b/dag/extdeps/linux/proc_self_cgroup.dag @@ -278,7 +278,12 @@ fn read_unified_cgroup_membership_drift_sketch(content: String) -> ProcPidCgroup } } -data proc_self_cgroup_cross_realization_law_note: String = "PERMANENT CROSS-REALIZATION FIXTURE LAW: unified_membership_outcome_tag_from_content is the parser-side projection; bash_proc_self_cgroup classifies executed shell results into the same tag. proc_self_cgroup_is_unified_v2_line_drift_sketch and read_unified_cgroup_membership_drift_sketch are mutation sketches ONLY — they omit the empty-controller guard so a witness can prove drift in that branch reds without shipping the defect." +// PERMANENT CROSS-REALIZATION FIXTURE LAW: unified_membership_outcome_tag_from_content is the +// parser-side projection; bash_proc_self_cgroup classifies executed shell results into the same +// tag. proc_self_cgroup_is_unified_v2_line_drift_sketch and +// read_unified_cgroup_membership_drift_sketch are mutation sketches ONLY — they omit the +// empty-controller guard so a witness can prove drift in that branch reds without shipping the +// defect. fn proc_self_cgroup_mount_root_text(mount_root: FilePathParts) -> String { fold(mount_root.segments, init: "", f: fn(acc, seg) { diff --git a/dag/extdeps/linux/rusage.dag b/dag/extdeps/linux/rusage.dag index 7ed588722df..de7621b8a0f 100644 --- a/dag/extdeps/linux/rusage.dag +++ b/dag/extdeps/linux/rusage.dag @@ -13,7 +13,16 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data linux_maxrss_unit_note: String = "Linux answers the unit POSIX leaves open: getrusage.2's NOTES state ru_maxrss is the maximum resident set size in KILOBYTES. This row exists so that number has a home in the implementation that produces it rather than as a branch inside a portable helper — the same reason extdeps.linux.proc_meminfo owns /proc/meminfo's fields instead of a generic memory module owning them.\n\nDeliberately narrow: this module speaks only for ru_maxrss's unit on Linux. It does NOT re-model getrusage's shape, which is POSIX's and is owned by extdeps.posix.rusage, and it does not claim the value is measured any better or worse here than elsewhere. One implementation fact, cited to the implementation's own manual." +// Linux answers the unit POSIX leaves open: getrusage.2's NOTES state ru_maxrss is the maximum +// resident set size in KILOBYTES. This row exists so that number has a home in the implementation +// that produces it rather than as a branch inside a portable helper — the same reason +// extdeps.linux.proc_meminfo owns /proc/meminfo's fields instead of a generic memory module owning +// them. +// +// Deliberately narrow: this module speaks only for ru_maxrss's unit on Linux. It does NOT re-model +// getrusage's shape, which is POSIX's and is owned by extdeps.posix.rusage, and it does not claim +// the value is measured any better or worse here than elsewhere. One implementation fact, cited to +// the implementation's own manual. data linux_rusage_maxrss_unit: RusageMaxrssUnit = MaxrssKibibytes diff --git a/dag/extdeps/llm/claude_agent_sdk_stream.dag b/dag/extdeps/llm/claude_agent_sdk_stream.dag index 70e6177c783..5ac425ccc46 100644 --- a/dag/extdeps/llm/claude_agent_sdk_stream.dag +++ b/dag/extdeps/llm/claude_agent_sdk_stream.dag @@ -46,7 +46,11 @@ data extdeps_model_scope: ExternalModelScope = ExternalModelScope { further_citations: [] } -data claude_agent_sdk_stream_authority_note: String = "Upstream @anthropic-ai/claude-agent-sdk 0.3.220 (extdeps_external_authority_anchor): SDKRateLimitInfo is a closed enum surface with no raw escape hatch, and the SDK parser drops unknown top-level stream-json message types. gunbc must capture beneath the parser and model ClaudeNativeRateLimitType as NonEmptyStr with recognised projections layered over it. Internal audit receipt: provider-control-interface-audit.md section 4.1." +// Upstream @anthropic-ai/claude-agent-sdk 0.3.220 (extdeps_external_authority_anchor): +// SDKRateLimitInfo is a closed enum surface with no raw escape hatch, and the SDK parser drops +// unknown top-level stream-json message types. gunbc must capture beneath the parser and model +// ClaudeNativeRateLimitType as NonEmptyStr with recognised projections layered over it. Internal +// audit receipt: provider-control-interface-audit.md section 4.1. data claude_stream_event_type_control_response: String = "control_response" data claude_stream_event_type_rate_limit_event: String = "rate_limit_event" @@ -219,7 +223,9 @@ fn claude_sdk_control_response_parser_drop_holds( // bankruptcy existed to end. If the paired probe is ever modeled as an entry point, the honest // repair is to re-run it against the then-current SDK and let these rows follow the new receipt -- // not to restore the deleted bytes. -data claude_sdk_parser_drop_witness_fixture_note: String = "Positive control derived from the 2026-08-06 wet paired probe receipt on Claude Code 2.1.223 with SDK 0.3.220. The subprocess raw stream carried control_response; the SDK query() parsed surface did not." +// Positive control derived from the 2026-08-06 wet paired probe receipt on Claude Code 2.1.223 with +// SDK 0.3.220. The subprocess raw stream carried control_response; the SDK query() parsed surface +// did not. data parser_drop_fixture_subprocess_raw_types: List = [ "control_response", diff --git a/dag/extdeps/llm/claude_setup_token_cli.dag b/dag/extdeps/llm/claude_setup_token_cli.dag index 71c44e47576..984458bdd20 100644 --- a/dag/extdeps/llm/claude_setup_token_cli.dag +++ b/dag/extdeps/llm/claude_setup_token_cli.dag @@ -25,7 +25,12 @@ data claude_setup_token_command: NonEmptyStr = "setup-token" as NonEmptyStr data claude_code_oauth_token_env_var: NonEmptyStr = "CLAUDE_CODE_OAUTH_TOKEN" as NonEmptyStr -data claude_setup_token_cli_authority_note: String = "Upstream Claude Code setup-token (code.claude.com/docs/en/cli-reference): generates a long-lived OAuth token for CI/scripts, prints to terminal, requires subscription. Managed enrollment uses ONLY this path plus CLAUDE_CODE_OAUTH_TOKEN — never ambient ~/.claude OAuth refresh home, never ANTHROPIC_API_KEY alongside it. Token prefix/length bounds are NOT assumed here; gunbc.claude_setup_token_enrollment admits captures only against ClaudeSetupTokenFormatMeasured receipts from the pinned executable." +// Upstream Claude Code setup-token (code.claude.com/docs/en/cli-reference): generates a long-lived +// OAuth token for CI/scripts, prints to terminal, requires subscription. Managed enrollment uses +// ONLY this path plus CLAUDE_CODE_OAUTH_TOKEN — never ambient ~/.claude OAuth refresh home, never +// ANTHROPIC_API_KEY alongside it. Token prefix/length bounds are NOT assumed here; +// gunbc.claude_setup_token_enrollment admits captures only against ClaudeSetupTokenFormatMeasured +// receipts from the pinned executable. data extdeps_model_scope: ExternalModelScope = ExternalModelScope { subject: ExternalSubjectRef { @@ -41,7 +46,9 @@ data extdeps_model_scope: ExternalModelScope = ExternalModelScope { data claude_config_dir_env_var: NonEmptyStr = "CLAUDE_CONFIG_DIR" as NonEmptyStr -data claude_config_dir_consistency_gap_note: String = "CLAUDE_CONFIG_DIR has known upstream consistency issues (anthropics/claude-code issue 4739). Isolation for managed enrollment is established by execution-time path observation receipts, never by assuming this env var redirects every file Claude reads." +// CLAUDE_CONFIG_DIR has known upstream consistency issues (anthropics/claude-code issue 4739). +// Isolation for managed enrollment is established by execution-time path observation receipts, +// never by assuming this env var redirects every file Claude reads. fn shape_claude_setup_token_argv(executable: NonEmptyStr) -> List { [executable as String, claude_setup_token_command as String] diff --git a/dag/extdeps/llm/cli.dag b/dag/extdeps/llm/cli.dag index edc5cdd88d8..aa48fe295df 100644 --- a/dag/extdeps/llm/cli.dag +++ b/dag/extdeps/llm/cli.dag @@ -34,7 +34,15 @@ data claude_cli_external_authority_anchor: ExternalAuthority = ExternalAuthority } } -data codex_cli_program_note: String = "The one program-token authority for the Codex CLI. Codex dispatch preflight and shape_codex_exec_argv both consume this row. The external non-interactive contract is explicit: codex exec is the automation surface; --json emits JSONL progress; workspace-write is the least sandbox that permits implementation; approval never makes blocked actions return to the agent instead of hanging an unattended worker; CODEX_HOME selects the dedicated persisted auth/config/session state. codex_cli_identity_contains is only a weak codex-cli substring marker — callers that must pin an exact codex-cli version use gunbc.package_delivery codex_runtime_version_identity_marker and codex_runtime_version_identity_satisfied; a bare substring cannot separate the pinned bundled runtime from an ambient PATH shim." +// The one program-token authority for the Codex CLI. Codex dispatch preflight and +// shape_codex_exec_argv both consume this row. The external non-interactive contract is explicit: +// codex exec is the automation surface; --json emits JSONL progress; workspace-write is the least +// sandbox that permits implementation; approval never makes blocked actions return to the agent +// instead of hanging an unattended worker; CODEX_HOME selects the dedicated persisted +// auth/config/session state. codex_cli_identity_contains is only a weak codex-cli substring marker +// — callers that must pin an exact codex-cli version use gunbc.package_delivery +// codex_runtime_version_identity_marker and codex_runtime_version_identity_satisfied; a bare +// substring cannot separate the pinned bundled runtime from an ambient PATH shim. data codex_cli_program: String = "codex" @@ -44,7 +52,25 @@ data codex_cli_identity_contains: String = "codex-cli" // The one program-token authority for claude code's CLI (DESIGN §3): shape_claude_invoke_argv's argv head and any caller preflighting claude-on-PATH consume THIS row, never a re-minted literal. (The `service claude.Invoke` transport argv below carries its own literal because the service DSL argv rows are self-contained transport realizations — that literal is the shell-transport handler's spelling of the same upstream binary, not a second authority.) data claude_cli_program: String = "claude" -data claude_workspace_trust_note: String = "Claude code's per-user config is `~/.claude.json`; its `projects` map keys ABSOLUTE workspace paths to per-project rows, and `hasTrustDialogAccepted: true` on a row is the accepted workspace-trust dialog. Two facts proven by execution (srv1, 2026-07-23, roadmap-dispatch go-live): (1) `--dangerously-skip-permissions` does NOT cover the trust dialog — an unseeded workspace blocks interactively on first launch (same finding as ctrl's container entrypoint trust seed); (2) trust INHERITS from ancestor directories — seeding one parent root covers every child workspace spawned under it, so a dispatcher seeds its worktree ROOT once, not per-worktree. claude_workspace_trust_seeded is the pure classifier over (config text, project path): it locates the quoted path as a projects-map KEY (next non-ws char must be ':' — a path appearing as an array VALUE, e.g. in githubRepoPaths, is not a key; ALL occurrences are tried), delimits the entry with the JSON grammar's string-aware value parser (extdeps.languages.json.grammar — one grammar, both directions, DESIGN §4; a naive first-brace scan lies on entries carrying nested objects or brace-in-string values like mcpServers/lastSessionFirstPrompt), and requires the flag's own value to be the literal `true` (a `false` row or a `true` elsewhere in the entry does not count). Fail-closed: anything unparseable or absent classifies NOT seeded — the consumer refuses loudly instead of spawning a session that hangs on the dialog. Scaffold residue: the slice-and-scan inside the located entry dissolves into a projection over the parsed tree when the JSON grammar grows its forward ingest fold (JsonValue built by ingest, the ingest_yaml_source analogue)." +// Claude code's per-user config is `~/.claude.json`; its `projects` map keys ABSOLUTE workspace +// paths to per-project rows, and `hasTrustDialogAccepted: true` on a row is the accepted +// workspace-trust dialog. Two facts proven by execution (srv1, 2026-07-23, roadmap-dispatch +// go-live): (1) `--dangerously-skip-permissions` does NOT cover the trust dialog — an unseeded +// workspace blocks interactively on first launch (same finding as ctrl's container entrypoint trust +// seed); (2) trust INHERITS from ancestor directories — seeding one parent root covers every child +// workspace spawned under it, so a dispatcher seeds its worktree ROOT once, not per-worktree. +// claude_workspace_trust_seeded is the pure classifier over (config text, project path): it locates +// the quoted path as a projects-map KEY (next non-ws char must be ':' — a path appearing as an +// array VALUE, e.g. in githubRepoPaths, is not a key; ALL occurrences are tried), delimits the +// entry with the JSON grammar's string-aware value parser (extdeps.languages.json.grammar — one +// grammar, both directions, DESIGN §4; a naive first-brace scan lies on entries carrying nested +// objects or brace-in-string values like mcpServers/lastSessionFirstPrompt), and requires the +// flag's own value to be the literal `true` (a `false` row or a `true` elsewhere in the entry does +// not count). Fail-closed: anything unparseable or absent classifies NOT seeded — the consumer +// refuses loudly instead of spawning a session that hangs on the dialog. Scaffold residue: the +// slice-and-scan inside the located entry dissolves into a projection over the parsed tree when the +// JSON grammar grows its forward ingest fold (JsonValue built by ingest, the ingest_yaml_source +// analogue). fn claude_trust_store_path(home: String) -> String { concat(home, "/.claude.json") @@ -145,7 +171,18 @@ type CodexCliApprovalPolicy | CodexApprovalOnRequest | CodexApprovalNever -data reasoning_effort_note: String = "review 44058. Reasoning effort is a CLOSED three-valued axis, not free text. It was a String on both provider surfaces at once — spliced into Codex's `-c model_reasoning_effort=\"…\"` config expression and passed as Claude's `--effort` value — so a typo reached the provider as a value it rejects at run time, a host-side failure standing where a construction refusal was available (DESIGN 5). One type, not two: gunbc.roadmap_dispatch_actuator.dispatch_applied_effort projects the selection kernel's applied_effort to both provider argv builders, which is the proven coincidence that earns a shared carrier rather than a bare alias (DESIGN 6); each provider keeps its own flag spelling in its own argv row, and reasoning_effort_label is the single place the wire word exists. It sits beside CodexCliSandbox and CodexCliApprovalPolicy, which model the sibling closed axes of the same cited surface the same way. If one provider ever admits a value the other does not, that is a new variant plus a per-provider label refusal — never a re-widening to String." +// review 44058. Reasoning effort is a CLOSED three-valued axis, not free text. It was a String on +// both provider surfaces at once — spliced into Codex's `-c model_reasoning_effort="…"` config +// expression and passed as Claude's `--effort` value — so a typo reached the provider as a value it +// rejects at run time, a host-side failure standing where a construction refusal was available +// (DESIGN 5). One type, not two: gunbc.roadmap_dispatch_actuator.dispatch_applied_effort projects +// the selection kernel's applied_effort to both provider argv builders, which is the proven +// coincidence that earns a shared carrier rather than a bare alias (DESIGN 6); each provider keeps +// its own flag spelling in its own argv row, and reasoning_effort_label is the single place the +// wire word exists. It sits beside CodexCliSandbox and CodexCliApprovalPolicy, which model the +// sibling closed axes of the same cited surface the same way. If one provider ever admits a value +// the other does not, that is a new variant plus a per-provider label refusal — never a re-widening +// to String. type ReasoningEffort = ReasoningLow @@ -216,7 +253,19 @@ fn shape_codex_login_status_env_args(codex_home: String) -> List { ) } -data codex_device_login_note: String = "THE ONLY LOGIN FLOW A REMOTE HOST CAN COMPLETE. Plain `codex login` starts a callback server and prints redirect_uri=http://localhost:1455/auth/callback; on a headless machine that redirect can never resolve from the operator's browser, because localhost is the server rather than their laptop. codex states this itself in its own final line: 'On a remote or headless machine? Use codex login --device-auth instead.' The device flow needs no inbound port and no browser on the host — a URL plus a short code, entered from anywhere — so it is the only flow a dashboard on another machine can usefully surface.\\n\\nCited against codex-cli 0.145.0, whose `login --help` lists exactly -c/--config, --with-api-key and --device-auth, with `status` and `help` as the subcommands. There is NO --json on login or on login status, so the URL and code are readable only from a text surface. A parser over that surface is therefore the honest realization rather than a shortcut: the richer structured source DESIGN would prefer does not exist to be read, and the receipt at dag/test/fixture/codex_device_login records the bytes the tool actually emits so the parser is written against them and not against memory." +// THE ONLY LOGIN FLOW A REMOTE HOST CAN COMPLETE. Plain `codex login` starts a callback server and +// prints redirect_uri=http://localhost:1455/auth/callback; on a headless machine that redirect can +// never resolve from the operator's browser, because localhost is the server rather than their +// laptop. codex states this itself in its own final line: 'On a remote or headless machine? Use +// codex login --device-auth instead.' The device flow needs no inbound port and no browser on the +// host — a URL plus a short code, entered from anywhere — so it is the only flow a dashboard on +// another machine can usefully surface.\n\nCited against codex-cli 0.145.0, whose `login --help` +// lists exactly -c/--config, --with-api-key and --device-auth, with `status` and `help` as the +// subcommands. There is NO --json on login or on login status, so the URL and code are readable +// only from a text surface. A parser over that surface is therefore the honest realization rather +// than a shortcut: the richer structured source DESIGN would prefer does not exist to be read, and +// the receipt at dag/test/fixture/codex_device_login records the bytes the tool actually emits so +// the parser is written against them and not against memory. fn shape_codex_device_login_env_args_for_program( program: String, @@ -230,7 +279,23 @@ fn shape_codex_device_login_env_args_for_program( ] } -data codex_device_prompt_input_contract_note: String = "WHAT codex EMITS IS A codex FACT; WHO STRIPS IT IS NOT THIS MODULE'S BUSINESS. Written to a terminal, the device prompt wraps the URL and the one-time code in SGR sequences and the expiry note in a dim attribute. A parser reading that stream directly would extract a code carrying escape bytes, which the operator pastes and OpenAI rejects with no sign that the dashboard mangled it rather than the code being wrong.\\n\\nSo the parser's input is typed std.types.RenderedTerminalText — text with the control sequences already consumed — and this module names no transport that could provide it. An earlier draft explained here what a terminal multiplexer strips; that was a fact filed under the wrong authority, and it would have gone stale the moment a second capture path existed (operator, 2026-07-30). The brand is the seam: whatever renders a terminal grid produces the type, this parser consumes it, and the composition is admissible because the types line up rather than because either side was written knowing about the other.\\n\\nThe format itself is cited against codex-cli 0.145.0, whose `login --help` lists exactly -c/--config, --with-api-key and --device-auth with `status` and `help` as subcommands. There is NO --json on login or login status, so a text surface is the only surface: a parser over it is the honest realization rather than a shortcut, because the structured source DESIGN would prefer does not exist to be read. dag/test/fixture/codex_device_login holds the bytes the tool actually emits, in both renderings, so the parser is written against them and not against memory." +// WHAT codex EMITS IS A codex FACT; WHO STRIPS IT IS NOT THIS MODULE'S BUSINESS. Written to a +// terminal, the device prompt wraps the URL and the one-time code in SGR sequences and the expiry +// note in a dim attribute. A parser reading that stream directly would extract a code carrying +// escape bytes, which the operator pastes and OpenAI rejects with no sign that the dashboard +// mangled it rather than the code being wrong.\n\nSo the parser's input is typed +// std.types.RenderedTerminalText — text with the control sequences already consumed — and this +// module names no transport that could provide it. An earlier draft explained here what a terminal +// multiplexer strips; that was a fact filed under the wrong authority, and it would have gone stale +// the moment a second capture path existed (operator, 2026-07-30). The brand is the seam: whatever +// renders a terminal grid produces the type, this parser consumes it, and the composition is +// admissible because the types line up rather than because either side was written knowing about +// the other.\n\nThe format itself is cited against codex-cli 0.145.0, whose `login --help` lists +// exactly -c/--config, --with-api-key and --device-auth with `status` and `help` as subcommands. +// There is NO --json on login or login status, so a text surface is the only surface: a parser over +// it is the honest realization rather than a shortcut, because the structured source DESIGN would +// prefer does not exist to be read. dag/test/fixture/codex_device_login holds the bytes the tool +// actually emits, in both renderings, so the parser is written against them and not against memory. type CodexDeviceLoginPrompt = CodexDeviceLoginReady { @@ -242,7 +307,16 @@ type CodexDeviceLoginPrompt data codex_device_prompt_marker_url: String = "https://auth.openai.com/codex/device" data codex_device_prompt_marker_code_line: String = "Enter this one-time code" -data codex_device_code_scan_note: String = "THE CODE IS POSITIONAL — the line after the marker — so reading it needs a state machine rather than a filter. codex prints 'Enter this one-time code (expires in 15 minutes)' and then the code on its own following line, so no predicate over a single line identifies it: the code itself is an opaque token with no distinguishing shape a matcher could key on, and keying on one (four characters, a hyphen, five characters) would silently stop finding it the day the format widens.\\n\\nThree states rather than an Optional accumulator, because 'have not seen the marker' and 'seen the marker, still waiting for a non-empty line' are different situations with different meanings for what comes next — collapsing them loses the ability to say WHICH failure the pane shows. A pane captured mid-render legitimately sits in AfterMarker, and reporting that as 'no code line' would be wrong about a prompt that is merely still arriving." +// THE CODE IS POSITIONAL — the line after the marker — so reading it needs a state machine rather +// than a filter. codex prints 'Enter this one-time code (expires in 15 minutes)' and then the code +// on its own following line, so no predicate over a single line identifies it: the code itself is +// an opaque token with no distinguishing shape a matcher could key on, and keying on one (four +// characters, a hyphen, five characters) would silently stop finding it the day the format +// widens.\n\nThree states rather than an Optional accumulator, because 'have not seen the marker' +// and 'seen the marker, still waiting for a non-empty line' are different situations with different +// meanings for what comes next — collapsing them loses the ability to say WHICH failure the pane +// shows. A pane captured mid-render legitimately sits in AfterMarker, and reporting that as 'no +// code line' would be wrong about a prompt that is merely still arriving. type CodexCodeScan = CodeScanSeeking diff --git a/dag/extdeps/llm/cli_lifecycle.dag b/dag/extdeps/llm/cli_lifecycle.dag index cf931421e7a..55dea63f9fd 100644 --- a/dag/extdeps/llm/cli_lifecycle.dag +++ b/dag/extdeps/llm/cli_lifecycle.dag @@ -86,6 +86,15 @@ type ToolExecutionReadiness | SandboxProbeFailed { reason: NonEmptyStr } | SandboxShellSucceeded +// CLI-provider lifecycle facts are upstream-shaped and independent. ProviderInstance identifies +// WHICH installation and credential chain an observation belongs to. ProviderReadiness answers +// whether that installation can obtain a model response; a recorded login is deliberately distinct +// from ProviderAuthenticated because a real request can still refuse on refresh. +// ToolExecutionReadiness answers the later sandbox/tool question; a successful model response +// cannot imply SandboxShellSucceeded. No stdout/stderr classifier lands until a real CLI transcript +// supplies its exact wire signal. Unknown evidence stays typed as Unobserved, never widened to a +// healthy state. + fn tool_execution_readiness_label(readiness: ToolExecutionReadiness) -> String { match readiness { ToolExecutionReadinessUnobserved { reason: _ } => "tool readiness unobserved" @@ -95,5 +104,3 @@ fn tool_execution_readiness_label(readiness: ToolExecutionReadiness) -> String { SandboxShellSucceeded => "sandbox shell ready" } } - -data cli_lifecycle_fact_separation_note: String = "CLI-provider lifecycle facts are upstream-shaped and independent. ProviderInstance identifies WHICH installation and credential chain an observation belongs to. ProviderReadiness answers whether that installation can obtain a model response; a recorded login is deliberately distinct from ProviderAuthenticated because a real request can still refuse on refresh. ToolExecutionReadiness answers the later sandbox/tool question; a successful model response cannot imply SandboxShellSucceeded. No stdout/stderr classifier lands until a real CLI transcript supplies its exact wire signal. Unknown evidence stays typed as Unobserved, never widened to a healthy state." diff --git a/dag/extdeps/llm/codex_app_server.dag b/dag/extdeps/llm/codex_app_server.dag index ef0c6307ba7..59bf4b8c5f6 100644 --- a/dag/extdeps/llm/codex_app_server.dag +++ b/dag/extdeps/llm/codex_app_server.dag @@ -58,7 +58,9 @@ fn codex_app_server_method_wire_name(method: CodexAppServerMethod) -> NonEmptySt } } -data codex_app_server_method_note: String = "FIRST PRESS SLICE method sequence from the generated ClientRequest schema: initialize → account/read → account/rateLimits/read → thread/start → turn/start. There is no `thread/turn` or bare `rateLimits/read` method in 0.146.0." +// FIRST PRESS SLICE method sequence from the generated ClientRequest schema: initialize → +// account/read → account/rateLimits/read → thread/start → turn/start. There is no `thread/turn` or +// bare `rateLimits/read` method in 0.146.0. type CodexAppServerExecutable { path: FilePath @@ -85,7 +87,14 @@ service codex_app_server.Cli { } } -data codex_app_server_request_line_emit_note: String = "Request lines are BUILT through extdeps.languages.json.emit, not hand-authored as escaped string literals (review 50540). The literals they replace carried their own JSON grammar — braces, quoting and separators spelled out in \\{ and \\\" escapes — which is a second JSON representation sitting beside the module that already owns one (DESIGN §3 dual representation), and it is the representation that cannot escape a value: a method name or client field containing a quote would have produced a malformed frame with nothing to refuse it. serialize_json applies the escaping, so the wire bytes are a consequence of the modeled value rather than of an author getting the backslashes right." +// Request lines are BUILT through extdeps.languages.json.emit, not hand-authored as escaped string +// literals (review 50540). The literals they replace carried their own JSON grammar — braces, +// quoting and separators spelled out in \{ and \" escapes — which is a second JSON representation +// sitting beside the module that already owns one (DESIGN §3 dual representation), and it is the +// representation that cannot escape a value: a method name or client field containing a quote would +// have produced a malformed frame with nothing to refuse it. serialize_json applies the escaping, +// so the wire bytes are a consequence of the modeled value rather than of an author getting the +// backslashes right. fn codex_app_server_jsonrpc_request(id: Int, method: String, params: JsonValue) -> NonEmptyStr { serialize_json( @@ -149,7 +158,12 @@ fn codex_app_server_rate_limits_read_request_line() -> NonEmptyStr { ) } -data codex_supervised_session_rpc_id_note: String = "Supervised turn session ids: initialize=1, account/read=2, account/rateLimits/read=3, thread/start=4, turn/start=5. account/read and account/rateLimits/read run in the same app-server occurrence as turn/start; standing is parsed from that session stdout, not from a prior preflight receipt. turn/start json-rpc id is fixed at 5; client_command_id on CodexRunnerTurnTerminalReceipt is the attempt/turn identity string for exactly-once correlation, not necessarily equal to the wire id." +// Supervised turn session ids: initialize=1, account/read=2, account/rateLimits/read=3, +// thread/start=4, turn/start=5. account/read and account/rateLimits/read run in the same app-server +// occurrence as turn/start; standing is parsed from that session stdout, not from a prior preflight +// receipt. turn/start json-rpc id is fixed at 5; client_command_id on +// CodexRunnerTurnTerminalReceipt is the attempt/turn identity string for exactly-once correlation, +// not necessarily equal to the wire id. data codex_supervised_thread_start_id: Int = 4 data codex_supervised_turn_start_wire_id: Int = 5 @@ -211,9 +225,14 @@ fn codex_app_server_turn_start_request_line( ) } -data codex_app_server_initialized_handshake_note: String = "After initialize succeeds the client MUST send the initialized ClientNotification (method initialized, no id) before account/rateLimits/read. Without it the server may return no id=3 response at all — a missing-handshake Incomplete, not a wrong method name." +// After initialize succeeds the client MUST send the initialized ClientNotification (method +// initialized, no id) before account/rateLimits/read. Without it the server may return no id=3 +// response at all — a missing-handshake Incomplete, not a wrong method name. -data codex_app_server_response_shape_note: String = "Response carriers for initialize / account/read / account/rateLimits/read are grounded here as cited wire field keys from the 0.146 generate-json-schema ClientRequest companion responses. Full AST decoder types that bind protocol_schema_identity are the dissolve-on; until then product modules MUST consume these keys rather than invent alternate spellings." +// Response carriers for initialize / account/read / account/rateLimits/read are grounded here as +// cited wire field keys from the 0.146 generate-json-schema ClientRequest companion responses. Full +// AST decoder types that bind protocol_schema_identity are the dissolve-on; until then product +// modules MUST consume these keys rather than invent alternate spellings. data codex_initialize_result_user_agent_key: NonEmptyStr = "userAgent" as NonEmptyStr data codex_account_read_result_requires_openai_auth_key: NonEmptyStr = "requiresOpenaiAuth" as NonEmptyStr @@ -234,9 +253,54 @@ data codex_structured_error_code_rate_limit_reached: NonEmptyStr = "rate_limit_r data codex_structured_error_code_quota_exceeded: NonEmptyStr = "quota_exceeded" as NonEmptyStr data codex_structured_error_code_authentication_required: NonEmptyStr = "authentication_required" as NonEmptyStr -data codex_account_union_note: String = "GetAccountResponse.account IS A CLOSED DISCRIMINATED UNION, not an opaque object. Read from `codex app-server generate-json-schema --out` against @openai/codex 0.146.0 — the version pinned by gunbc.package_delivery codex_required_package_version and materialized by the committed provider-runtime/codex lock — v2/GetAccountResponse.json: Account is a oneOf over ApiKeyAccount \{type:'apiKey'\}, ChatgptAccount \{type:'chatgpt', planType, email\} and AmazonBedrockAccount \{type:'amazonBedrock', usesCodexManagedCredentials\}, discriminated by the required `type` member. `account` itself is nullable and, unlike requiresOpenaiAuth, is NOT in the response's required list — an absent account and a null account are both reachable.\n\nTHESE ARE THREE DIFFERENT CREDENTIAL REALIZATIONS, not three spellings of one account. An apiKey account bills a platform key; an amazonBedrock account bills a cloud contract; only chatgpt is the personal OAuth subscription. A consumer whose profile requires personal ChatGPT OAuth is not satisfied by the other two, so decoding this member to a bare `the object was nonempty` discards the only fact that distinguishes them.\n\nplanType is REQUIRED on ChatgptAccount. A chatgpt account without one is a malformed frame, not an account on an unnamed plan." - -data codex_plan_type_note: String = "PlanType IS A CLOSED ENUM of exactly 13 values, cited from the same v2/GetAccountResponse.json definitions block at 0.146.0. It is enumerated here rather than kept as an open string because upstream closes it, and modeling a closed upstream vocabulary as an open one is the anemic leaf §2 names.\n\nTHE LOAD-BEARING MEMBER IS `unknown`. It is a real enum value, so the wire can carry a plan whose identity upstream itself does not assert. Treating any decoded planType as a positive entitlement signal would therefore read upstream's own I-do-not-know as a yes — the fabricated-plausible-output failure with an authoritative-looking source. `unknown` decodes successfully and entitles nothing.\n\nA plan string OUTSIDE this enum is a different state again: upstream added a plan this pin does not know. That is CodexPlanTypeUnrecognized, carrying the native token, never silently folded into CodexPlanUnknown — the first says our model is stale, the second says upstream is uncertain, and the remedies differ.\n\nONE RUNTIME PRODUCES ALL PROTOCOL FACTS. This enum was first authored with 15 members read from a 0.147.0 executable that happened to be installed on the build host, while every other fact in this module — and the materialized bundle the turn actually executes — is 0.146.0. Measured, the two differ: 0.147.0 adds self_serve_business_prolite and enterprise_cbp_automation, so the modeled interface described plans the pinned executable cannot emit and no exact runtime implemented the composite. The account union is structurally identical across both, which is precisely why the defect was easy to miss — the part that changed was the part nobody diffed.\n\nThe rule the correction establishes is not which version wins. It is that a protocol fact is cited from the exact executable the runtime receipt admits; a version bump moves the manifest, lock, admitted artifacts, runtime receipt and every fact in this module together, or it does not happen. Under 0.146.0 the two 0.147.0-only plan strings decode as CodexPlanTypeUnrecognized, which is the honest reading — a bumped runtime would then surface them as a stale pin rather than silently widening the model." +// GetAccountResponse.account IS A CLOSED DISCRIMINATED UNION, not an opaque object. Read from +// `codex app-server generate-json-schema --out` against @openai/codex 0.146.0 — the version pinned +// by gunbc.package_delivery codex_required_package_version and materialized by the committed +// provider-runtime/codex lock — v2/GetAccountResponse.json: Account is a oneOf over ApiKeyAccount +// {type:'apiKey'}, ChatgptAccount {type:'chatgpt', planType, email} and AmazonBedrockAccount +// {type:'amazonBedrock', usesCodexManagedCredentials}, discriminated by the required `type` member. +// `account` itself is nullable and, unlike requiresOpenaiAuth, is NOT in the response's required +// list — an absent account and a null account are both reachable. +// +// THESE ARE THREE DIFFERENT CREDENTIAL REALIZATIONS, not three spellings of one account. An apiKey +// account bills a platform key; an amazonBedrock account bills a cloud contract; only chatgpt is +// the personal OAuth subscription. A consumer whose profile requires personal ChatGPT OAuth is not +// satisfied by the other two, so decoding this member to a bare `the object was nonempty` discards +// the only fact that distinguishes them. +// +// planType is REQUIRED on ChatgptAccount. A chatgpt account without one is a malformed frame, not +// an account on an unnamed plan. + +// PlanType IS A CLOSED ENUM of exactly 13 values, cited from the same v2/GetAccountResponse.json +// definitions block at 0.146.0. It is enumerated here rather than kept as an open string because +// upstream closes it, and modeling a closed upstream vocabulary as an open one is the anemic leaf +// §2 names. +// +// THE LOAD-BEARING MEMBER IS `unknown`. It is a real enum value, so the wire can carry a plan whose +// identity upstream itself does not assert. Treating any decoded planType as a positive entitlement +// signal would therefore read upstream's own I-do-not-know as a yes — the +// fabricated-plausible-output failure with an authoritative-looking source. `unknown` decodes +// successfully and entitles nothing. +// +// A plan string OUTSIDE this enum is a different state again: upstream added a plan this pin does +// not know. That is CodexPlanTypeUnrecognized, carrying the native token, never silently folded +// into CodexPlanUnknown — the first says our model is stale, the second says upstream is uncertain, +// and the remedies differ. +// +// ONE RUNTIME PRODUCES ALL PROTOCOL FACTS. This enum was first authored with 15 members read from a +// 0.147.0 executable that happened to be installed on the build host, while every other fact in +// this module — and the materialized bundle the turn actually executes — is 0.146.0. Measured, the +// two differ: 0.147.0 adds self_serve_business_prolite and enterprise_cbp_automation, so the +// modeled interface described plans the pinned executable cannot emit and no exact runtime +// implemented the composite. The account union is structurally identical across both, which is +// precisely why the defect was easy to miss — the part that changed was the part nobody diffed. +// +// The rule the correction establishes is not which version wins. It is that a protocol fact is +// cited from the exact executable the runtime receipt admits; a version bump moves the manifest, +// lock, admitted artifacts, runtime receipt and every fact in this module together, or it does not +// happen. Under 0.146.0 the two 0.147.0-only plan strings decode as CodexPlanTypeUnrecognized, +// which is the honest reading — a bumped runtime would then surface them as a stale pin rather than +// silently widening the model. type CodexAccountType = CodexAccountTypeApiKey @@ -287,9 +351,24 @@ fn codex_plan_type_wire_name(p: CodexPlanType) -> NonEmptyStr { data codex_account_type_key: NonEmptyStr = "type" as NonEmptyStr data codex_account_plan_type_key: NonEmptyStr = "planType" as NonEmptyStr -data codex_rate_limits_capacity_decode_note: String = "GetAccountRateLimitsResponse carries rateLimits (backward-compatible single-bucket mirror) AND rateLimitsByLimitId (multi-bucket map keyed by limit_id, e.g. codex vs codex_bengalfox). Live exhausted Pro accounts can have codex at usedPercent 100 with rateLimitReachedType rate_limit_reached while another limit_id remains at 0 — a single account-level Ready|QuotaReached is therefore false. Decode every by-limit-id entry; keep native limit tokens open. spendControlReached true is an exhaustion signal when present, but live exhaustion often has spendControlReached false — usedPercent 100 and rateLimitReachedType are the operative facts. Empty rateLimits:\{\} answers the endpoint without proving capacity." - -data codex_rate_limit_subject_encoding_note: String = "Standing bridge subject encoding (coord with gunbc.provider_standing / #7926, deep-raven-684 2026-08-06): each rateLimitsByLimitId entry projects to ProviderNativeLimitBucket \{ native_key: limit_id \} — NEVER ProviderAccountLimit. Promoting an exhausted codex bucket to account scope would block for the wrong reason, erase bucket identity, and wrongly refuse work that a sibling bucket (e.g. codex_bengalfox) could serve. If a truthful native-bucket observation does not block a dispatch that draws on that bucket, the defect is in limit_observation_blocks_selection coverage, not in this encoding. Discriminating control from the live capture: one inventory with codex at 100 percent and codex_bengalfox at 0 percent must refuse a dispatch that draws on codex and allow a dispatch that draws on codex_bengalfox." +// GetAccountRateLimitsResponse carries rateLimits (backward-compatible single-bucket mirror) AND +// rateLimitsByLimitId (multi-bucket map keyed by limit_id, e.g. codex vs codex_bengalfox). Live +// exhausted Pro accounts can have codex at usedPercent 100 with rateLimitReachedType +// rate_limit_reached while another limit_id remains at 0 — a single account-level +// Ready|QuotaReached is therefore false. Decode every by-limit-id entry; keep native limit tokens +// open. spendControlReached true is an exhaustion signal when present, but live exhaustion often +// has spendControlReached false — usedPercent 100 and rateLimitReachedType are the operative facts. +// Empty rateLimits:{} answers the endpoint without proving capacity. + +// Standing bridge subject encoding (coord with gunbc.provider_standing / #7926, deep-raven-684 +// 2026-08-06): each rateLimitsByLimitId entry projects to ProviderNativeLimitBucket { native_key: +// limit_id } — NEVER ProviderAccountLimit. Promoting an exhausted codex bucket to account scope +// would block for the wrong reason, erase bucket identity, and wrongly refuse work that a sibling +// bucket (e.g. codex_bengalfox) could serve. If a truthful native-bucket observation does not block +// a dispatch that draws on that bucket, the defect is in limit_observation_blocks_selection +// coverage, not in this encoding. Discriminating control from the live capture: one inventory with +// codex at 100 percent and codex_bengalfox at 0 percent must refuse a dispatch that draws on codex +// and allow a dispatch that draws on codex_bengalfox. data codex_model_to_limit_bucket_gap_note: String = "TYPED GAP — model → native limit_id is NOT observable on the 0.146 app-server surface. Model has no limitId; TurnStartParams.model is a free string with no bucket edge; RateLimitSnapshot carries limitId/limitName but no model join. Live limitName GPT-5.3-Codex-Spark on codex_bengalfox is suggestive folklore, not a cited join. dissolve-on: an upstream field or documented map that binds turn model (or service tier) to metered limit_id. Until then, consumers must not invent a closed mapping. The symbol codex_default_rate_limit_id names the backward-compatible rateLimits mirror's native key (live limitId=codex), which is the press AccountReady gate key — not a proven claim that every unspecified-model turn/start consumes that bucket." diff --git a/dag/extdeps/llm/codex_app_server_stdio_session.dag b/dag/extdeps/llm/codex_app_server_stdio_session.dag index dd89dec34d0..a9fbe09b4ab 100644 --- a/dag/extdeps/llm/codex_app_server_stdio_session.dag +++ b/dag/extdeps/llm/codex_app_server_stdio_session.dag @@ -24,9 +24,21 @@ data extdeps_model_scope: ExternalModelScope = ExternalModelScope { further_citations: [] } -data codex_app_server_stdio_session_authority_note: String = "Typed transport shape for a held-open Codex app-server --stdio JSON-RPC session (initialize through turn/start). Wire lines are modeled values serialized through extdeps.llm.codex_app_server serialize_json emitters — never hand-built JSON in shell. Coprocess mechanics (fifo, setsid, held stdin fd, poll-to-terminal, process-group kill) realize through host_effect_apply on a session driver that writes preamble_lines, polls stdout for thread/start id=4, builds turn/start via JSON parse+re-serialize (no sed), persists client_command_id, then captures stdout/stderr/exit. Operator verdict #8166 RejectAndFinishNow: hand-built bash coprocess scaffold deleted; this shape is the replacement authority. Subject: the Codex app-server stdio session transport (same upstream as extdeps.llm.codex_app_server @openai/codex 0.146.0 ClientRequest slice through turn/start)." +// Typed transport shape for a held-open Codex app-server --stdio JSON-RPC session (initialize +// through turn/start). Wire lines are modeled values serialized through +// extdeps.llm.codex_app_server serialize_json emitters — never hand-built JSON in shell. Coprocess +// mechanics (fifo, setsid, held stdin fd, poll-to-terminal, process-group kill) realize through +// host_effect_apply on a session driver that writes preamble_lines, polls stdout for thread/start +// id=4, builds turn/start via JSON parse+re-serialize (no sed), persists client_command_id, then +// captures stdout/stderr/exit. Operator verdict #8166 RejectAndFinishNow: hand-built bash coprocess +// scaffold deleted; this shape is the replacement authority. Subject: the Codex app-server stdio +// session transport (same upstream as extdeps.llm.codex_app_server @openai/codex 0.146.0 +// ClientRequest slice through turn/start). -data codex_supervised_worker_turn_driver_frontier_note: String = "LANDED (#8166): native session driver at target/release/codex_app_server_stdio_session, invoked through gunbc.host_effect_codex_supervised_turn + host_effect_apply CodexSupervisedWorkerTurn. Coprocess uses pipe stdin, setsid process group, JSON turn/start (no sed). Wet terminal receipt must execute through this path." +// LANDED (#8166): native session driver at target/release/codex_app_server_stdio_session, invoked +// through gunbc.host_effect_codex_supervised_turn + host_effect_apply CodexSupervisedWorkerTurn. +// Coprocess uses pipe stdin, setsid process group, JSON turn/start (no sed). Wet terminal receipt +// must execute through this path. type CodexAppServerStdioSessionIntent { executable: FilePath diff --git a/dag/extdeps/llm/codex_auth.dag b/dag/extdeps/llm/codex_auth.dag index 5076476b6e1..c249bec7ea8 100644 --- a/dag/extdeps/llm/codex_auth.dag +++ b/dag/extdeps/llm/codex_auth.dag @@ -16,7 +16,20 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data codex_auth_file_note: String = "THE FILE THE CODEX CLI WRITES WHEN IT AUTHENTICATES, modeled because treating it as an opaque blob loses every fact that decides how it may be handled. CODEX_HOME/auth.json is what a device login produces and what every later invocation reads; a SecretRef pointing at it would say a credential lives there and nothing about what it grants, how long any part of it lasts, or whether it can be copied.\\n\\nOBSERVED, NOT REMEMBERED. Every field below was read from a real file on srv2 (codex-cli 0.145.0, 2026-07-30) by inspecting structure and never values — key names, string lengths, JWT segment counts, and the decoded id_token claim set. codex publishes no schema for this file, so its shape is a fact about the artifact and carries the version it was seen at.\\n\\nWHAT IS NOT HERE IS THE POINT. The id_token's registered claims are RFC 7519's, its email and amr are OIDC Core's and RFC 8176's, its identifiers are RFC 9562 UUIDs and its timestamps are RFC 3339 — all of which live in extdeps modules owned by those specifications. Only two things in this file are genuinely OpenAI's: the auth.json layout itself, and the private claim namespace below. Re-typing the standard leaves here would have coined a seventh local copy of sub and exp, each free to drift about whether a NumericDate is seconds or milliseconds." +// THE FILE THE CODEX CLI WRITES WHEN IT AUTHENTICATES, modeled because treating it as an opaque +// blob loses every fact that decides how it may be handled. CODEX_HOME/auth.json is what a device +// login produces and what every later invocation reads; a SecretRef pointing at it would say a +// credential lives there and nothing about what it grants, how long any part of it lasts, or +// whether it can be copied.\n\nOBSERVED, NOT REMEMBERED. Every field below was read from a real +// file on srv2 (codex-cli 0.145.0, 2026-07-30) by inspecting structure and never values — key +// names, string lengths, JWT segment counts, and the decoded id_token claim set. codex publishes no +// schema for this file, so its shape is a fact about the artifact and carries the version it was +// seen at.\n\nWHAT IS NOT HERE IS THE POINT. The id_token's registered claims are RFC 7519's, its +// email and amr are OIDC Core's and RFC 8176's, its identifiers are RFC 9562 UUIDs and its +// timestamps are RFC 3339 — all of which live in extdeps modules owned by those specifications. +// Only two things in this file are genuinely OpenAI's: the auth.json layout itself, and the private +// claim namespace below. Re-typing the standard leaves here would have coined a seventh local copy +// of sub and exp, each free to drift about whether a NumericDate is seconds or milliseconds. // THE FILE HOLDS TWO MUTUALLY EXCLUSIVE MODES AS ONE RECORD, and modeling that faithfully means a coproduct rather than a record with nulls. The observed file carries auth_mode: chatgpt alongside OPENAI_API_KEY: null — the other mode's slot, present and empty. Read as a record it invites the exact bug the shape encourages: code that checks whether OPENAI_API_KEY is set, finds null, and concludes the file is unauthenticated when it is a perfectly good subscription login.\n\nSo the discriminant becomes the constructor. CodexChatGptAuth carries tokens and no key, CodexApiKeyAuth carries a key and no tokens, and the states where both or neither are populated are unwritable rather than merely unlikely. The conflation is the vendor's; representing the same reachable states without the unreachable ones is not disagreeing with upstream. type CodexAuthMode @@ -36,7 +49,19 @@ fn codex_auth_mode_of_wire(raw: String) -> CodexAuthMode? { } } -data codex_token_durability_note: String = "THREE TOKENS IN ONE FILE WITH LIFETIMES THREE ORDERS OF MAGNITUDE APART, which is why auth.json cannot be one SecretRef. Measured on the observed file: id_token and access_token carry iat and exp exactly 3600 seconds apart, so both are gone within the hour. The refresh_token is the durable root and is NOT a JWT — decoding its middle segment fails — so it is opaque and its expiry is not readable from the token at all.\\n\\nThat asymmetry is the whole handling story, and it is why the two token fields carry different types. Anything reasoning about whether this credential is still good must consult the refresh token, and the only way to learn whether an opaque token is live is to USE it: existence is not validity, the same lesson secret_existence_is_not_validity_note records for Secret Manager versions. A liveness check that read exp from the access token would report a dead credential every hour and a live one never.\\n\\nThe short-lived pair is derived state, reconstructible from the refresh token and not worth storing. If any part of this file is ever placed in a secret store, the refresh token is the part that matters and the other two are cache." +// THREE TOKENS IN ONE FILE WITH LIFETIMES THREE ORDERS OF MAGNITUDE APART, which is why auth.json +// cannot be one SecretRef. Measured on the observed file: id_token and access_token carry iat and +// exp exactly 3600 seconds apart, so both are gone within the hour. The refresh_token is the +// durable root and is NOT a JWT — decoding its middle segment fails — so it is opaque and its +// expiry is not readable from the token at all.\n\nThat asymmetry is the whole handling story, and +// it is why the two token fields carry different types. Anything reasoning about whether this +// credential is still good must consult the refresh token, and the only way to learn whether an +// opaque token is live is to USE it: existence is not validity, the same lesson +// secret_existence_is_not_validity_note records for Secret Manager versions. A liveness check that +// read exp from the access token would report a dead credential every hour and a live one +// never.\n\nThe short-lived pair is derived state, reconstructible from the refresh token and not +// worth storing. If any part of this file is ever placed in a secret store, the refresh token is +// the part that matters and the other two are cache. type CodexOpaqueToken = NonEmptyStr where brand("CodexOpaqueToken") @@ -56,7 +81,16 @@ type CodexAuthFile api_key: NonEmptyStr, } -data codex_state_root_isolation_note: String = "CODEX_HOME IS THE ISOLATION MECHANISM, AND IT IS CODEX'S, NOT OURS. The CLI resolves auth.json, config, logs and session state beneath whatever CODEX_HOME names, defaulting to ~/.codex — so two invocations with different CODEX_HOME values authenticate as different accounts with no interference, and that capability exists whether or not anyone builds a naming scheme on it.\\n\\nThe distinction matters for where a multi-account model lives. The MECHANISM belongs here because codex owns it; a convention for what to call the roots is a downstream choice that this module must not encode. Anything reading a state root path out of this module would be reading somebody's naming policy as though the CLI required it.\\n\\nObserved on srv2: three roots coexist under one user account, and seventeen concurrent sessions bind to one of them with no cross-talk to the others." +// CODEX_HOME IS THE ISOLATION MECHANISM, AND IT IS CODEX'S, NOT OURS. The CLI resolves auth.json, +// config, logs and session state beneath whatever CODEX_HOME names, defaulting to ~/.codex — so two +// invocations with different CODEX_HOME values authenticate as different accounts with no +// interference, and that capability exists whether or not anyone builds a naming scheme on +// it.\n\nThe distinction matters for where a multi-account model lives. The MECHANISM belongs here +// because codex owns it; a convention for what to call the roots is a downstream choice that this +// module must not encode. Anything reading a state root path out of this module would be reading +// somebody's naming policy as though the CLI required it.\n\nObserved on srv2: three roots coexist +// under one user account, and seventeen concurrent sessions bind to one of them with no cross-talk +// to the others. type CodexStateRoot = NonEmptyStr where brand("CodexStateRoot") @@ -64,7 +98,22 @@ data codex_state_root_env_var: NonEmptyStr = "CODEX_HOME" as NonEmptyStr data codex_state_root_default_relative: NonEmptyStr = ".codex" as NonEmptyStr data codex_auth_file_relative: NonEmptyStr = "auth.json" as NonEmptyStr -data codex_refresh_persistence_note: String = "WHEN THE CREDENTIAL FILE IS WRITTEN DECIDES WHETHER IT CAN BE SHARED, and this is measured rather than assumed. Observed on srv2, 2026-07-30: the default state root's auth.json carries last_refresh 2026-07-27T04:22 and an mtime matching it, while codex sessions bound to that same root have been running for over two hours — well past the 3600-second access-token lifetime the id_token declares. Seventeen concurrent processes, three days, and the file was never rewritten.\\n\\nThe inference is narrow and worth stating precisely. If refresh rotated the refresh token, the CLI would have to persist the replacement or lose access on the next exchange; it did not write, and access did not lapse. So the durable token survives use and the file is written at LOGIN, not at refresh — which makes it read-mostly in normal operation, and read-mostly is what makes concurrent binding safe. The write contention this row was expected to document does not occur, because after login there are no writers.\\n\\nWHAT THIS IS NOT: proof. It is three days of one host, and it does not establish that a refresh exchange happens at all (a long-lived session may hold its connection), that the authorization server never rotates, or how simultaneous cold starts behave when several processes exchange at once. CodexPersistenceObservedLoginOnly records what was seen with its evidence; it is deliberately a different value from a proven CodexPersistsAtLoginOnly, because an observation that survived one workload is not a contract." +// WHEN THE CREDENTIAL FILE IS WRITTEN DECIDES WHETHER IT CAN BE SHARED, and this is measured rather +// than assumed. Observed on srv2, 2026-07-30: the default state root's auth.json carries +// last_refresh 2026-07-27T04:22 and an mtime matching it, while codex sessions bound to that same +// root have been running for over two hours — well past the 3600-second access-token lifetime the +// id_token declares. Seventeen concurrent processes, three days, and the file was never +// rewritten.\n\nThe inference is narrow and worth stating precisely. If refresh rotated the refresh +// token, the CLI would have to persist the replacement or lose access on the next exchange; it did +// not write, and access did not lapse. So the durable token survives use and the file is written at +// LOGIN, not at refresh — which makes it read-mostly in normal operation, and read-mostly is what +// makes concurrent binding safe. The write contention this row was expected to document does not +// occur, because after login there are no writers.\n\nWHAT THIS IS NOT: proof. It is three days of +// one host, and it does not establish that a refresh exchange happens at all (a long-lived session +// may hold its connection), that the authorization server never rotates, or how simultaneous cold +// starts behave when several processes exchange at once. CodexPersistenceObservedLoginOnly records +// what was seen with its evidence; it is deliberately a different value from a proven +// CodexPersistsAtLoginOnly, because an observation that survived one workload is not a contract. type CodexRefreshPersistence = CodexPersistenceUnobserved @@ -75,7 +124,18 @@ data codex_observed_refresh_persistence: CodexRefreshPersistence = CodexPersiste data codex_observed_concurrent_sessions_per_root: Int = 17 -data codex_refresh_rotation_note: String = "WHETHER REFRESH ROTATES IS UNKNOWN AND MUST STAY UNKNOWN UNTIL OBSERVED, because the answer decides whether one login can serve many hosts and guessing it either way is expensive. If OpenAI rotates the refresh token on use — standard in OAuth 2.1 and the default in many implementations — then copying auth.json to a second host means the first refresh silently invalidates every other copy, and the others fail about an hour later with no indication a sibling host caused it. If it does not rotate, one interactive login can provision a fleet.\\n\\nThis was asserted optimistically in conversation before it was checked, which is why it is a typed row now rather than an assumption in someone's head. CodexRotationUnobserved is not a placeholder for 'probably fine'; it is a refusal to let fleet-wide credential replication rest on an unverified premise.\\n\\nDISSOLVES ON a two-host experiment: authenticate once, copy the file, force a refresh on host A, then exercise host B. B still working proves CodexRefreshStableAcrossUse; B failing proves CodexRefreshRotatesOnUse and settles that codex needs one login per host." +// WHETHER REFRESH ROTATES IS UNKNOWN AND MUST STAY UNKNOWN UNTIL OBSERVED, because the answer +// decides whether one login can serve many hosts and guessing it either way is expensive. If OpenAI +// rotates the refresh token on use — standard in OAuth 2.1 and the default in many implementations +// — then copying auth.json to a second host means the first refresh silently invalidates every +// other copy, and the others fail about an hour later with no indication a sibling host caused it. +// If it does not rotate, one interactive login can provision a fleet.\n\nThis was asserted +// optimistically in conversation before it was checked, which is why it is a typed row now rather +// than an assumption in someone's head. CodexRotationUnobserved is not a placeholder for 'probably +// fine'; it is a refusal to let fleet-wide credential replication rest on an unverified +// premise.\n\nDISSOLVES ON a two-host experiment: authenticate once, copy the file, force a refresh +// on host A, then exercise host B. B still working proves CodexRefreshStableAcrossUse; B failing +// proves CodexRefreshRotatesOnUse and settles that codex needs one login per host. type CodexRefreshRotationSemantics = CodexRotationUnobserved @@ -116,11 +176,31 @@ type CodexIdToken { data codex_id_token_issuer: NonEmptyStr = "https://auth.openai.com" as NonEmptyStr -data codex_id_token_lifetime_carrier_note: String = "THE OBSERVED LIFETIME IS A std.measure.Second (review 45056). It was a bare Int named ..._seconds, which puts the unit in the identifier where no consumer can check it — the same flat-scalar duration fork the tree grounds everywhere else. The number is what decides how often a long-running host must refresh, so it is read by things that also handle milliseconds, and the name was the only thing keeping the two apart." +// THE OBSERVED LIFETIME IS A std.measure.Second (review 45056). It was a bare Int named +// ..._seconds, which puts the unit in the identifier where no consumer can check it — the same +// flat-scalar duration fork the tree grounds everywhere else. The number is what decides how often +// a long-running host must refresh, so it is read by things that also handle milliseconds, and the +// name was the only thing keeping the two apart. data codex_id_token_observed_lifetime: Second = second(count: 3600) -data codex_observed_profile_note: String = "WHAT ONE TOKEN CARRIED IS NOT WHAT THE STANDARD REQUIRES, and separating the two is the whole point of this row. The extdeps.auth.jwt and extdeps.auth.oidc types were first written by reading the codex id_token observed on srv2 on 2026-07-30 and marking every claim it contained as mandatory. That produced two modules that named IETF and OIDF documents as their authority while actually encoding one sample from one provider on one day — the precise failure the extdeps citation duty exists to prevent, because a reader would take this vendor's grant as the specification's demand.\\n\\nThe specs now say what they say: RFC 7519's registered claims are all optional, OIDC Core requires five in an ID Token and makes auth_time, acr, amr, azp, nonce and sid optional, and the standard claims arrive only with the scope that asks for them. This profile records, separately and as an OBSERVATION, which of those optional claims this provider's token was seen to carry.\\n\\nWHY IT IS WORTH KEEPING RATHER THAN DELETING: a consumer reading a codex credential needs to know whether acr is reliably there before writing code that depends on it, and the answer is a fact about OpenAI's issuer rather than about OIDC. Recording it as an observation with its date and version means a later reader can tell that it was measured rather than promised, and can re-measure it. It is the same distinction CodexPersistenceObservedLoginOnly draws for refresh behaviour: an observation that survived one workload is not a contract, and it must not be spelled like one." +// WHAT ONE TOKEN CARRIED IS NOT WHAT THE STANDARD REQUIRES, and separating the two is the whole +// point of this row. The extdeps.auth.jwt and extdeps.auth.oidc types were first written by reading +// the codex id_token observed on srv2 on 2026-07-30 and marking every claim it contained as +// mandatory. That produced two modules that named IETF and OIDF documents as their authority while +// actually encoding one sample from one provider on one day — the precise failure the extdeps +// citation duty exists to prevent, because a reader would take this vendor's grant as the +// specification's demand.\n\nThe specs now say what they say: RFC 7519's registered claims are all +// optional, OIDC Core requires five in an ID Token and makes auth_time, acr, amr, azp, nonce and +// sid optional, and the standard claims arrive only with the scope that asks for them. This profile +// records, separately and as an OBSERVATION, which of those optional claims this provider's token +// was seen to carry.\n\nWHY IT IS WORTH KEEPING RATHER THAN DELETING: a consumer reading a codex +// credential needs to know whether acr is reliably there before writing code that depends on it, +// and the answer is a fact about OpenAI's issuer rather than about OIDC. Recording it as an +// observation with its date and version means a later reader can tell that it was measured rather +// than promised, and can re-measure it. It is the same distinction +// CodexPersistenceObservedLoginOnly draws for refresh behaviour: an observation that survived one +// workload is not a contract, and it must not be spelled like one. type CodexObservedClaimPresence { claim: NonEmptyStr @@ -156,8 +236,18 @@ fn codex_default_organization(token: CodexIdToken) -> CodexOrganizationMembershi token.openai_auth.organizations |> filter(o => o.is_default) |> first } +// THIS MODEL DESCRIBES THE ARTIFACT; IT DOES NOT PARSE OR VERIFY IT. There is no ingest fold from +// auth.json bytes to CodexAuthFile and no signature check on the id_token, and both omissions are +// deliberate rather than pending.\n\nVerification needs OpenAI's JWKS over the network, and a model +// that decoded claims WITHOUT verifying would hand callers values that look authoritative and are +// merely base64 — extdeps.auth.jwt's module verification-boundary annotation states the same rule +// at the layer that owns it. Reading unverified claims is fine for showing which account a +// credential names and illegitimate for authorization, and once the values sit in a typed record +// that difference is invisible. So the parse lands with the verification or not at all.\n\nWhat the +// shape enables today, with no parse: a consumer can state which part it depends on, whether that +// part is perishable or durable, and whether an operation is scoped to an organization it has +// actually named — three questions that were unanswerable while this file was a String. + fn codex_organization_ids(token: CodexIdToken) -> List { token.openai_auth.organizations |> map(o => o.id) } - -data codex_auth_parse_boundary_note: String = "THIS MODEL DESCRIBES THE ARTIFACT; IT DOES NOT PARSE OR VERIFY IT. There is no ingest fold from auth.json bytes to CodexAuthFile and no signature check on the id_token, and both omissions are deliberate rather than pending.\\n\\nVerification needs OpenAI's JWKS over the network, and a model that decoded claims WITHOUT verifying would hand callers values that look authoritative and are merely base64 — extdeps.auth.jwt's module verification-boundary annotation states the same rule at the layer that owns it. Reading unverified claims is fine for showing which account a credential names and illegitimate for authorization, and once the values sit in a typed record that difference is invisible. So the parse lands with the verification or not at all.\\n\\nWhat the shape enables today, with no parse: a consumer can state which part it depends on, whether that part is perishable or durable, and whether an operation is scoped to an organization it has actually named — three questions that were unanswerable while this file was a String." diff --git a/dag/extdeps/llm/cursor_cli.dag b/dag/extdeps/llm/cursor_cli.dag index fe979931acd..e5b55c34753 100644 --- a/dag/extdeps/llm/cursor_cli.dag +++ b/dag/extdeps/llm/cursor_cli.dag @@ -11,7 +11,10 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data cursor_cli_program_note: String = "The one program-token authority for the Cursor agent CLI, the sibling of codex_cli_program and claude_cli_program. Observed installed on srv2 at ~/.local/bin/cursor-agent; every surface below was read from that binary's own --help and --list-models output on 2026-07-30 rather than from documentation, so the rows describe the interface actually present on the host." +// The one program-token authority for the Cursor agent CLI, the sibling of codex_cli_program and +// claude_cli_program. Observed installed on srv2 at ~/.local/bin/cursor-agent; every surface below +// was read from that binary's own --help and --list-models output on 2026-07-30 rather than from +// documentation, so the rows describe the interface actually present on the host. data cursor_cli_program: String = "cursor-agent" @@ -22,7 +25,18 @@ type CursorAuth data cursor_api_key_env_var: NonEmptyStr = "CURSOR_API_KEY" as NonEmptyStr -data cursor_account_isolation_note: String = "THERE IS NO CURSOR_CONFIG_DIR, and the absence is the fact worth recording. codex isolates accounts by state root and this CLI has no equivalent environment variable — ~/.cursor holds agent-cli-state.json, cli-config.json and a chats directory for whichever account last logged in, with no way to point the process at a different one.\\n\\nSo cursor's account axis is the API KEY, not a directory. Two accounts are two keys passed to two invocations, sharing one ~/.cursor for non-credential state. That means concurrent sessions under different accounts are possible in a way codex's directory model does not require any care about, while concurrent sessions writing chat history to one shared directory is a contention question this module cannot answer and has not observed.\\n\\nRecorded rather than papered over: gunbc.provider_account's @label suffix scheme is a CODEX convention and does not transfer here. A cursor account selection is a secret reference, and forcing it into a state-root shape would invent a directory the tool does not read." +// THERE IS NO CURSOR_CONFIG_DIR, and the absence is the fact worth recording. codex isolates +// accounts by state root and this CLI has no equivalent environment variable — ~/.cursor holds +// agent-cli-state.json, cli-config.json and a chats directory for whichever account last logged in, +// with no way to point the process at a different one.\n\nSo cursor's account axis is the API KEY, +// not a directory. Two accounts are two keys passed to two invocations, sharing one ~/.cursor for +// non-credential state. That means concurrent sessions under different accounts are possible in a +// way codex's directory model does not require any care about, while concurrent sessions writing +// chat history to one shared directory is a contention question this module cannot answer and has +// not observed.\n\nRecorded rather than papered over: gunbc.provider_account's @label suffix scheme +// is a CODEX convention and does not transfer here. A cursor account selection is a secret +// reference, and forcing it into a state-root shape would invent a directory the tool does not +// read. // --print is the non-interactive surface and it is the only mode that reaches the automation flags. Its own help states that print mode has access to all tools including write and shell, so it is the analogue of codex exec rather than a read-only dump. --output-format admits text, json or stream-json, and stream-json is the JSONL progress stream this repository already consumes from codex — so provider events are available on the same shape, from a different vendor.\n\n--trust is bound to the same mode: 'Trust the current workspace without prompting (only works with --print/headless mode)'. That is the same class of first-launch obstacle claude_workspace_trust_seeded already models for claude code, where an unseeded workspace blocks interactively and no permission flag covers it. Cursor exposes it as a flag rather than as config state, which is strictly easier — but it must be passed, and omitting it is how an unattended run hangs waiting for a human. type CursorOutputFormat @@ -38,7 +52,15 @@ fn cursor_output_format_wire(f: CursorOutputFormat) -> String { } } -data cursor_execution_mode_note: String = "The mode axis is a CLOSED choice on the CLI's own surface: --mode admits plan (read-only, proposes without editing) and ask (read-only Q&A), while omitting it leaves the default editing behaviour. Modeled with an explicit default arm rather than an Optional, because 'no mode flag' is a real third behaviour and not the absence of information.\\n\\nSeparately, --force (aliased --yolo) allows commands unless explicitly denied, and --sandbox admits enabled or disabled while overriding config. Force and sandbox are ORTHOGONAL to mode and to each other, so they are separate fields: a read-only plan run needs neither, and an unattended editing run needs a deliberate decision about both. Fusing them into one permission enum would make the sensible combinations unrepresentable." +// The mode axis is a CLOSED choice on the CLI's own surface: --mode admits plan (read-only, +// proposes without editing) and ask (read-only Q&A), while omitting it leaves the default editing +// behaviour. Modeled with an explicit default arm rather than an Optional, because 'no mode flag' +// is a real third behaviour and not the absence of information.\n\nSeparately, --force (aliased +// --yolo) allows commands unless explicitly denied, and --sandbox admits enabled or disabled while +// overriding config. Force and sandbox are ORTHOGONAL to mode and to each other, so they are +// separate fields: a read-only plan run needs neither, and an unattended editing run needs a +// deliberate decision about both. Fusing them into one permission enum would make the sensible +// combinations unrepresentable. type CursorExecutionMode = CursorDefaultEditMode @@ -66,7 +88,17 @@ fn cursor_sandbox_args(choice: CursorSandboxChoice) -> List { } } -data cursor_model_selection_note: String = "MODEL IS A PER-INVOCATION ARGUMENT AND THE ROSTER IS AN ACCOUNT FACT. --model takes an identifier and --list-models prints what THIS ACCOUNT may use — the subcommand is literally documented as 'List available models for this account' — so the set is entitlement-scoped and cannot be modeled as a fixed closed enum without going wrong the moment a plan changes.\\n\\n193 identifiers were listed on srv2 on 2026-07-30, spanning several vendors' models behind one CLI. Enumerating them here would be a snapshot pretending to be a specification, so the type stays an identifier and the rows below name only the ones this repository has a reason to select, each recorded as OBSERVED AVAILABLE rather than as guaranteed.\\n\\ncursor_model_grok_high answers a direct operator question: Cursor Grok 4.5 is available on this account as cursor-grok-4.5-high, with a -fast sibling. The -fast variants throughout the roster are a latency tier on the same model, which is a selection axis rather than a different capability." +// MODEL IS A PER-INVOCATION ARGUMENT AND THE ROSTER IS AN ACCOUNT FACT. --model takes an identifier +// and --list-models prints what THIS ACCOUNT may use — the subcommand is literally documented as +// 'List available models for this account' — so the set is entitlement-scoped and cannot be modeled +// as a fixed closed enum without going wrong the moment a plan changes.\n\n193 identifiers were +// listed on srv2 on 2026-07-30, spanning several vendors' models behind one CLI. Enumerating them +// here would be a snapshot pretending to be a specification, so the type stays an identifier and +// the rows below name only the ones this repository has a reason to select, each recorded as +// OBSERVED AVAILABLE rather than as guaranteed.\n\ncursor_model_grok_high answers a direct operator +// question: Cursor Grok 4.5 is available on this account as cursor-grok-4.5-high, with a -fast +// sibling. The -fast variants throughout the roster are a latency tier on the same model, which is +// a selection axis rather than a different capability. type CursorModelId = NonEmptyStr where brand("CursorModelId") @@ -88,7 +120,21 @@ type CursorInvocation { prompt: NonEmptyStr } -data cursor_invocation_carries_environment_note: String = "THE AUTH FIELD WAS NEVER READ, SO BOTH ARMS PRODUCED THE SAME PROCESS. shape_cursor_agent_argv built an argv and returned a List of String, and its accompanying note explained at length why the key is deliberately absent from that argv — which was true, and which meant a CursorApiKeyArgument invocation and a CursorApiKeyEnvironment invocation were byte-identical and equally credential-free. The safe arm was justified in prose and never realized: nothing set CURSOR_API_KEY, so an invocation shaped from either arm authenticates as nobody.\\n\\nThe missing piece is that an argv is not a process. A process is a program, an argv AND an environment, and a credential passed by environment is invisible in a model whose output stops at the argv — there was no field for it to appear in, so 'the key is not in the argv' was simultaneously the security property and the reason the mechanism did not work.\\n\\nSo the shape is the whole invocation. The argument arm puts the key in argv where the CLI accepts it and where every ps and audit log records it; the environment arm puts it in the environment and leaves argv clean. The two are now observably different values, which is what makes the safety claim checkable instead of decorative — a witness can assert that the environment arm leaks nothing into argv AND that it actually binds the variable, and neither assertion was expressible before." +// THE AUTH FIELD WAS NEVER READ, SO BOTH ARMS PRODUCED THE SAME PROCESS. shape_cursor_agent_argv +// built an argv and returned a List of String, and its accompanying note explained at length why +// the key is deliberately absent from that argv — which was true, and which meant a +// CursorApiKeyArgument invocation and a CursorApiKeyEnvironment invocation were byte-identical and +// equally credential-free. The safe arm was justified in prose and never realized: nothing set +// CURSOR_API_KEY, so an invocation shaped from either arm authenticates as nobody.\n\nThe missing +// piece is that an argv is not a process. A process is a program, an argv AND an environment, and a +// credential passed by environment is invisible in a model whose output stops at the argv — there +// was no field for it to appear in, so 'the key is not in the argv' was simultaneously the security +// property and the reason the mechanism did not work.\n\nSo the shape is the whole invocation. The +// argument arm puts the key in argv where the CLI accepts it and where every ps and audit log +// records it; the environment arm puts it in the environment and leaves argv clean. The two are now +// observably different values, which is what makes the safety claim checkable instead of decorative +// — a witness can assert that the environment arm leaks nothing into argv AND that it actually +// binds the variable, and neither assertion was expressible before. type CursorEnvBinding { name: NonEmptyStr diff --git a/dag/extdeps/llm/cursor_sdk.dag b/dag/extdeps/llm/cursor_sdk.dag index a454a89203b..56542c317de 100644 --- a/dag/extdeps/llm/cursor_sdk.dag +++ b/dag/extdeps/llm/cursor_sdk.dag @@ -33,7 +33,8 @@ data extdeps_model_scope: ExternalModelScope = ExternalModelScope { data cursor_sdk_authority_note: String = "Slice-E modeling only (provider-control-interface-audit §6, §12). No press trip, inventory wiring, or WorkerTurn fork — those wait on slices B/C. OBSERVED @cursor/sdk 1.0.26 via docs/probes/provider_interface_acquisition_2026-08-03.json. LocalAgentOptions.cwd is the belt-owned worktree; CloudAgentOptions is a separate workspace realization. RunStatus / Run.supports(operation) land with the press trip when slices B/C wire live SDK control — not modeled as dead types in this slice." -data cursor_sdk_belt_worktree_note: String = "Belt-owned local worktree (audit slice E): LocalAgentOptions.cwd points at the dashboard attempt worktree. cursor-agent -w/--worktree is deliberately unused — recorded in extdeps.llm.cursor_cli." +// Belt-owned local worktree (audit slice E): LocalAgentOptions.cwd points at the dashboard attempt +// worktree. cursor-agent -w/--worktree is deliberately unused — recorded in extdeps.llm.cursor_cli. type CursorSdkLocalWorkspace { cwd: FilePath @@ -42,7 +43,11 @@ type CursorSdkLocalWorkspace { type CursorSdkLocalCredential = CursorSdkApiKeySecretRef { secret_ref: SecretRef } -data cursor_sdk_credential_note: String = "API key materializes INTO the SDK call (Agent.create / Agent.prompt apiKey or CURSOR_API_KEY at process env), NEVER into argv. CursorApiKeySecretRef is the automation path; CursorLocalLoginRef is interactive-only and refuses at SDK local binding. Contrast extdeps.llm.cursor_cli CursorApiKeyArgument which puts --api-key on argv for the legacy CLI — that path is not the SDK local interface." +// API key materializes INTO the SDK call (Agent.create / Agent.prompt apiKey or CURSOR_API_KEY at +// process env), NEVER into argv. CursorApiKeySecretRef is the automation path; CursorLocalLoginRef +// is interactive-only and refuses at SDK local binding. Contrast extdeps.llm.cursor_cli +// CursorApiKeyArgument which puts --api-key on argv for the legacy CLI — that path is not the SDK +// local interface. type CursorSdkApiKeyMaterialization = CursorSdkApiKeyFromSecretRef { @@ -65,7 +70,9 @@ fn cursor_sdk_environment_api_key_materialization() -> CursorSdkApiKeyMaterializ CursorSdkApiKeyFromEnvironment { env_var: cursor_api_key_env_var } } -data cursor_sdk_argv_projection_note: String = "SDK local credential delivery is in-call apiKey or process environment only. argv projection is structurally refused — contrast extdeps.llm.cursor_cli CursorApiKeyArgument, which is the legacy CLI argv leak surface and is not an admitted SDK-local arm." +// SDK local credential delivery is in-call apiKey or process environment only. argv projection is +// structurally refused — contrast extdeps.llm.cursor_cli CursorApiKeyArgument, which is the legacy +// CLI argv leak surface and is not an admitted SDK-local arm. type CursorSdkCredentialProjection = CursorSdkCredentialProjected { diff --git a/dag/extdeps/llm/cursor_sdk_errors.dag b/dag/extdeps/llm/cursor_sdk_errors.dag index b5ea3af52db..ee5a829340e 100644 --- a/dag/extdeps/llm/cursor_sdk_errors.dag +++ b/dag/extdeps/llm/cursor_sdk_errors.dag @@ -24,7 +24,9 @@ data extdeps_model_scope: ExternalModelScope = ExternalModelScope { further_citations: [] } -data cursor_sdk_error_codes_note: String = "Stable backend codes from @cursor/sdk errors.d.ts @ 1.0.26. RunError.code carries the wire spelling on terminal turns. Single authority for SDK wire codes — consumed by extdeps.llm.cursor_stream." +// Stable backend codes from @cursor/sdk errors.d.ts @ 1.0.26. RunError.code carries the wire +// spelling on terminal turns. Single authority for SDK wire codes — consumed by +// extdeps.llm.cursor_stream. type CursorSdkErrorCode = CursorSdkAuthenticationError diff --git a/dag/extdeps/llm/cursor_sdk_facts.dag b/dag/extdeps/llm/cursor_sdk_facts.dag index 374accc7d37..7d1e3d435d4 100644 --- a/dag/extdeps/llm/cursor_sdk_facts.dag +++ b/dag/extdeps/llm/cursor_sdk_facts.dag @@ -37,7 +37,11 @@ data extdeps_model_scope: ExternalModelScope = ExternalModelScope { data cursor_sdk_authority_note: String = "Slice-E modeling only (provider-control-interface-audit §6, §12). Package closure and usage-capability facts without SecretRef so witnesses compile in isolation. Credential binding lives in extdeps.llm.cursor_sdk." -data cursor_sdk_package_closure_note: String = "Exact-lock facts from provider_interface_acquisition_2026-08-03.json via extdeps.probes.provider_interface_acquisition — requirement versus exact selection, archive integrity, zero install scripts. Dissolves into gunbc.package_delivery when slice B lands; this module owns only the Cursor @cursor/sdk closure projection the delivery spine will consume. No acquisition on a deployed host in this slice." +// Exact-lock facts from provider_interface_acquisition_2026-08-03.json via +// extdeps.probes.provider_interface_acquisition — requirement versus exact selection, archive +// integrity, zero install scripts. Dissolves into gunbc.package_delivery when slice B lands; this +// module owns only the Cursor @cursor/sdk closure projection the delivery spine consumes. No +// acquisition on a deployed host in this slice. type CursorSdkPackageClosure { package_name: NonEmptyStr @@ -68,9 +72,16 @@ fn cursor_sdk_local_package_closure() -> CursorSdkPackageClosure { } } -data cursor_sdk_api_key_never_in_argv_note: String = "Slice E: modeled SDK local credential must not project to argv tokens. CursorSdkApiKeyFromSecretRef and CursorSdkApiKeyFromEnvironment are the only admitted materialization arms for automation; argv projection refuses via cursor_sdk_refuse_argv_api_key_projection." +// Slice E: modeled SDK local credential must not project to argv tokens. +// CursorSdkApiKeyFromSecretRef and CursorSdkApiKeyFromEnvironment are the only admitted +// materialization arms for automation; argv projection refuses via +// cursor_sdk_refuse_argv_api_key_projection. -data cursor_sdk_usage_capability_gap_note: String = "Cursor local exposes NO account-level remaining capacity. The SDK usage API is per-agent and cloud-only (provider-control-interface-audit §6, §10). Local usage standing must be CursorSdkLocalAccountUsageUnobservable — a typed capability gap naming the missing capability — never an estimated or assumed healthy balance. Coordinate arm adoption with valiant-stag-845 (ProviderLimitObservation decomposition). Silence is not a positive observation (DESIGN §5)." +// Cursor local exposes NO account-level remaining capacity; the SDK usage API is per-agent and +// cloud-only (provider-control-interface-audit §6, §10). Local usage standing must be +// CursorSdkLocalAccountUsageUnobservable — a typed capability gap — never an estimated or assumed +// healthy balance. Coordinate arm adoption with valiant-stag-845 (ProviderLimitObservation +// decomposition). Silence is not a positive observation (DESIGN §5). type CursorSdkLocalAccountUsageUnobservable { reason: NonEmptyStr @@ -88,7 +99,10 @@ fn cursor_sdk_local_account_usage_unobservable() -> CursorSdkLocalAccountUsageUn } } -data cursor_sdk_srv1_presence_note: String = "Audit §6/§7: @cursor/sdk was acquired and inspected in a disposable probe directory only — not in srv1's runtime dependency closure at audit time. Until gunbc.package_delivery admits a ProviderInterfaceAdmissionReceipt for the local SDK export, provision on a deployed host is a typed capability gap, never an assumed offer." +// Audit §6/§7: @cursor/sdk was acquired and inspected in a disposable probe directory only, not in +// srv1's runtime dependency closure. Until gunbc.package_delivery admits a +// ProviderInterfaceAdmissionReceipt for the local SDK export, provision on a deployed host is a +// typed capability gap, never an assumed offer. type CursorSdkLocalProvisionGap { reason: NonEmptyStr @@ -97,11 +111,13 @@ type CursorSdkLocalProvisionGap { data cursor_sdk_local_provision_missing_capability: NonEmptyStr = "cursor-sdk-local-package-provisioned" as NonEmptyStr +// SecretRef binding is modeled in extdeps.llm.cursor_sdk and gunbc.cursor_sdk_local_binding; live +// key materialization is not wired in slice E. Whether a bound SecretRef resolves on a deployed +// host is a MaterializedSecret consumer — not observable here, never assumed healthy. + fn cursor_sdk_local_package_unprovisioned_on_deployed_host() -> CursorSdkLocalProvisionGap { CursorSdkLocalProvisionGap { reason: "Provider-interface audit §7: @cursor/sdk 1.0.26 inspected in disposable probe only; not admitted in srv1 runtime dependency closure" as NonEmptyStr, missing_capability: cursor_sdk_local_provision_missing_capability, } } - -data cursor_sdk_api_key_binding_note: String = "SecretRef binding is modeled in extdeps.llm.cursor_sdk and gunbc.cursor_sdk_local_binding; live key materialization is not wired in slice E. Whether a bound SecretRef resolves to material on a deployed host is a MaterializedSecret consumer — not observable here, and never assumed healthy." diff --git a/dag/extdeps/llm/cursor_stream.dag b/dag/extdeps/llm/cursor_stream.dag index 7442836c6e5..98c9c183444 100644 --- a/dag/extdeps/llm/cursor_stream.dag +++ b/dag/extdeps/llm/cursor_stream.dag @@ -38,7 +38,8 @@ data extdeps_model_scope: ExternalModelScope = ExternalModelScope { further_citations: [] } -data cursor_stream_authority_note: String = "WIRE AUTHORITY for @cursor/sdk local stream vocabulary (messages.d.ts @ 1.0.26). Terminal RunResult and LocalRunStreamEvent envelopes only — no retry, switch, or escalation decisions." +// WIRE AUTHORITY for @cursor/sdk local stream vocabulary (messages.d.ts @ 1.0.26). Terminal +// RunResult and LocalRunStreamEvent envelopes only — no retry, switch, or escalation decisions. type CursorSdkMessageKind = CursorSdkMessageSystem diff --git a/dag/extdeps/memory/macronix_mx25l25673g.dag b/dag/extdeps/memory/macronix_mx25l25673g.dag index c0adc32a289..6e648985bb1 100644 --- a/dag/extdeps/memory/macronix_mx25l25673g.dag +++ b/dag/extdeps/memory/macronix_mx25l25673g.dag @@ -16,7 +16,14 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data mx25l25673g_note: String = "THE REVISION IN THIS LOCATOR WAS READ FROM THE PUBLISHED ATTACHMENT NAME, NOT FROM A COVER PAGE, and the distinction is recorded because it is exactly the kind of shortcut that later reads as a citation. The attachment is named for revision v1.9; a reader who opens it and finds a different revision is correcting a filename-derived claim, not a document-derived one. No package, capacity or interface fact is recorded here. WHY THIS ROW IS NOT A SELECTION: Ampere's Mt. Jade reference names this part for a firmware store, which is a reference-design precedent — a relation between a part and a particular board, and so a product-layer fact carried with that board rather than inside an upstream module." +// THE REVISION IN THIS LOCATOR WAS READ FROM THE PUBLISHED ATTACHMENT NAME, NOT FROM A COVER PAGE, +// and the distinction is recorded because it is exactly the kind of shortcut that later reads as a +// citation. The attachment is named for revision v1.9; a reader who opens it and finds a different +// revision is correcting a filename-derived claim, not a document-derived one. No package, capacity +// or interface fact is recorded here. WHY THIS ROW IS NOT A SELECTION: Ampere's Mt. Jade reference +// names this part for a firmware store, which is a reference-design precedent — a relation between +// a part and a particular board, and so a product-layer fact carried with that board rather than +// inside an upstream module. data mx25l25673g: CatalogPartIdentity = CatalogPartIdentity { ordering_part_number: "MX25L25673G", diff --git a/dag/extdeps/memory/microchip_at24cm02.dag b/dag/extdeps/memory/microchip_at24cm02.dag index 08b7f856491..34a6ddc35aa 100644 --- a/dag/extdeps/memory/microchip_at24cm02.dag +++ b/dag/extdeps/memory/microchip_at24cm02.dag @@ -16,7 +16,15 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data at24cm02_note: String = "THE DOCUMENT EXISTS AND ITS GEOMETRY IS NOT TRANSCRIBED, AND THIS ROW CARRIES ONLY THE FIRST OF THOSE. Datasheet DS20006197 is served at the recorded locator and publishes recommended land patterns beside its package outlines, so the facts this row owes are published rather than unobtainable. Nothing about the part's capacity, interface or package is recorded here, because recording any of it would mean supplying it from expectation. WHY THIS ROW IS NOT A SELECTION: Ampere's Mt. Jade reference names this part for a firmware EEPROM role, which is a reference-design precedent — a relation between a part and a particular board, and therefore a product-layer fact that belongs with the board that consumes it. An upstream module knows nothing about our board and must not carry a justification for it." +// THE DOCUMENT EXISTS AND ITS GEOMETRY IS NOT TRANSCRIBED, AND THIS ROW CARRIES ONLY THE FIRST OF +// THOSE. Datasheet DS20006197 is served at the recorded locator and publishes recommended land +// patterns beside its package outlines, so the facts this row owes are published rather than +// unobtainable. Nothing about the part's capacity, interface or package is recorded here, because +// recording any of it would mean supplying it from expectation. WHY THIS ROW IS NOT A SELECTION: +// Ampere's Mt. Jade reference names this part for a firmware EEPROM role, which is a +// reference-design precedent — a relation between a part and a particular board, and therefore a +// product-layer fact that belongs with the board that consumes it. An upstream module knows nothing +// about our board and must not carry a justification for it. data at24cm02: CatalogPartIdentity = CatalogPartIdentity { ordering_part_number: "AT24CM02", diff --git a/dag/extdeps/memory/te_ddr4_dimm_socket.dag b/dag/extdeps/memory/te_ddr4_dimm_socket.dag index 66f58ab3801..b0cb5ff0017 100644 --- a/dag/extdeps/memory/te_ddr4_dimm_socket.dag +++ b/dag/extdeps/memory/te_ddr4_dimm_socket.dag @@ -25,7 +25,23 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data te_ddr4_dimm_socket_note: String = "A PRODUCT FAMILY, NOT A SELECTED PART, AND NOT AN ORDERABLE ONE EITHER. 2308107 is the base product number the specification uses to identify this product line; the orderable variants differ in tail length for different board thicknesses and are carried on the customer drawing. So this row names a family whose mechanical facts are cited, and it does not say that this socket has been chosen for anything. THE SPECIFICATION STATES what is recorded here: 288 positions, 0.80 mm contact spacing, solder tail through plated holes, FR-4 or G-10 board material. IT ALSO PUBLISHES A PC BOARD LAYOUT AS FIGURE 3, and that is a fact about the document rather than about any reader: the hole pattern this part needs EXISTS and is published, it is simply not transcribed here. THE CUSTOMER DRAWING GOVERNS WHERE THE TWO CONFLICT, by the specification own terms, so a later reader who obtains that drawing is reading a higher authority for this part and not a second opinion. A SOLDER TAIL NEEDS A DRILL SCHEDULE, NOT A LAND PATTERN, which is why the obligation below is a plated-hole obligation carrying no success arm at all. An earlier revision of this row used a standing whose only established arm carried surface-mount land geometry, which this part can never inhabit — the type promised a success that was unreachable for the very part it described, and it also stored this session extraction failure as though it were a vendor fact. What a fetch on 2026-08-14 could or could not read is an observation this repository made, and it belongs in the observing layer downstream, never inside an upstream module." +// A PRODUCT FAMILY, NOT A SELECTED PART, AND NOT AN ORDERABLE ONE EITHER. 2308107 is the base +// product number the specification uses to identify this product line; the orderable variants +// differ in tail length for different board thicknesses and are carried on the customer drawing. So +// this row names a family whose mechanical facts are cited, and it does not say that this socket +// has been chosen for anything. THE SPECIFICATION STATES what is recorded here: 288 positions, 0.80 +// mm contact spacing, solder tail through plated holes, FR-4 or G-10 board material. IT ALSO +// PUBLISHES A PC BOARD LAYOUT AS FIGURE 3, and that is a fact about the document rather than about +// any reader: the hole pattern this part needs EXISTS and is published, it is simply not +// transcribed here. THE CUSTOMER DRAWING GOVERNS WHERE THE TWO CONFLICT, by the specification own +// terms, so a later reader who obtains that drawing is reading a higher authority for this part and +// not a second opinion. A SOLDER TAIL NEEDS A DRILL SCHEDULE, NOT A LAND PATTERN, which is why the +// obligation below is a plated-hole obligation carrying no success arm at all. An earlier revision +// of this row used a standing whose only established arm carried surface-mount land geometry, which +// this part can never inhabit — the type promised a success that was unreachable for the very part +// it described, and it also stored this session extraction failure as though it were a vendor fact. +// What a fetch on 2026-08-14 could or could not read is an observation this repository made, and it +// belongs in the observing layer downstream, never inside an upstream module. type DimmSocketCatalogRow { base_part_number: NonEmptyStr diff --git a/dag/extdeps/memory/training.dag b/dag/extdeps/memory/training.dag index c1be0a87e98..19f7080d592 100644 --- a/dag/extdeps/memory/training.dag +++ b/dag/extdeps/memory/training.dag @@ -37,7 +37,12 @@ data extdeps_model_scope: ExternalModelScope = ExternalModelScope { further_citations: [] } -data training_scope_subject_note: String = "THE SUBJECT IS THE JEDEC DDR4 INITIALIZATION CALIBRATION SEQUENCE — which calibration steps a module must execute, derived from its construction. Grounded on JESD79-4A. The load-reduced steps exist only where a data buffer is interposed, which is what makes the step set a difference in KIND between module classes rather than a reliability preference. NOT platform failure receipts (those are gunbc observations of a board), NOT purchasing policy, NOT the DRAM module catalog shape itself (extdeps.memory.types)." +// THE SUBJECT IS THE JEDEC DDR4 INITIALIZATION CALIBRATION SEQUENCE — which calibration steps a +// module must execute, derived from its construction. Grounded on JESD79-4A. The load-reduced steps +// exist only where a data buffer is interposed, which is what makes the step set a difference in +// KIND between module classes rather than a reliability preference. NOT platform failure receipts +// (those are gunbc observations of a board), NOT purchasing policy, NOT the DRAM module catalog +// shape itself (extdeps.memory.types). // WHY A TRAINING-STEP MODEL RATHER THAN A BLOCKLIST. // @@ -142,6 +147,18 @@ fn hypothetical_module_at_rank_count( } } +// The step sets here are the structural claim this model rests on: a load-reduced module interposes +// a data buffer between the DRAM and the host, so it must calibrate that buffer, and a registered +// module has no such buffer and therefore cannot fail a buffer-calibration step. That is a +// difference in KIND, not in reliability, which is what makes it usable as a purchase filter rather +// than as a probability. It is also why the same failure reproduced on two different modules of one +// family and never on the registered parts the host ran previously. MREP is the DDR4 data-buffer +// read-phase calibration step; it is named explicitly because it is the step observed failing on +// the ALTRAD8UD-1L2T platform, both on gunbc's own hosts and in an independent public report +// carrying byte-identical controller status words. The PHY steps are listed for completeness and to +// keep the model honest in the other direction: a PHY-step failure would implicate every module +// class equally and would NOT support a buffering-based purchase rule. + // Can this capacity be built as an ordinary low-rank registered module in this // generation, or does reaching it REQUIRE extra ranks or die stacking? fn capacity_reachable_as_dual_rank_registered( @@ -153,5 +170,3 @@ fn capacity_reachable_as_dual_rank_registered( jedec: jedec, ) } - -data memory_training_step_note: String = "The step sets here are the structural claim this model rests on: a load-reduced module interposes a data buffer between the DRAM and the host, so it must calibrate that buffer, and a registered module has no such buffer and therefore cannot fail a buffer-calibration step. That is a difference in KIND, not in reliability, which is what makes it usable as a purchase filter rather than as a probability. It is also why the same failure reproduced on two different modules of one family and never on the registered parts the host ran previously. MREP is the DDR4 data-buffer read-phase calibration step; it is named explicitly because it is the step observed failing on the ALTRAD8UD-1L2T platform, both on gunbc's own hosts and in an independent public report carrying byte-identical controller status words. The PHY steps are listed for completeness and to keep the model honest in the other direction: a PHY-step failure would implicate every module class equally and would NOT support a buffering-based purchase rule." diff --git a/dag/extdeps/netplan/netplan.dag b/dag/extdeps/netplan/netplan.dag index 478310051b5..8ab0d21575d 100644 --- a/dag/extdeps/netplan/netplan.dag +++ b/dag/extdeps/netplan/netplan.dag @@ -11,12 +11,23 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -// netplan was entirely unmodeled before this — zero occurrences corpus-wide — and its absence is why the fleet model can RECORD an address without being able to say anything about whether a host holds it. gunbc.fleet_intent_network carries srv1..srv4's addresses as NetworkEndpoint rows, which are topology facts: what the address IS, not that the host has it. netplan is the party that decides the latter, so the ensure side of host networking has nowhere to live until this module exists. This is the applier half of the writer/applier pair whose fusion produced a wrong diagnosis on srv4 (see extdeps.cloud_init.cloud_init). +// netplan was entirely unmodeled before this — zero occurrences corpus-wide — and its absence is +// why the fleet model can RECORD an address without saying whether a host holds it. +// gunbc.fleet_intent_network carries srv1..srv4's addresses as NetworkEndpoint rows, topology +// facts: what the address IS, not that the host has it. netplan decides the latter, so the ensure +// side of host networking has nowhere to live until this module exists. This is the applier half +// of the writer/applier pair whose fusion produced a wrong diagnosis on srv4 (see +// extdeps.cloud_init.cloud_init). data netplan_config_dir: NonEmptyStr = "/etc/netplan" type NetplanRenderer = SystemdNetworkd | NetworkManager -// THE applier fact, and the counterpart to cloud-init's writer fact. netplan reads EVERY *.yaml under /etc/netplan at boot, merges them in lexical order, and renders the result to its backend — with no knowledge of and no dependency on which process wrote any of them. A file whose generator has been uninstalled, disabled, or deleted is applied exactly as a hand-written one is. That is the whole content of the inference the srv4 diagnosis got wrong, stated positively so it can be consumed rather than merely warned about. +// THE applier fact, the counterpart to cloud-init's writer fact. netplan reads EVERY *.yaml under +// /etc/netplan at boot, merges them in lexical order, and renders the result to its backend — with +// no knowledge of or dependency on which process wrote any of them. A file whose generator has been +// uninstalled, disabled, or deleted is applied exactly as a hand-written one is. That is the whole +// content of the inference the srv4 diagnosis got wrong, stated positively so it can be consumed +// rather than merely warned about. type NetplanConfigWriter = WrittenByCloudInit | WrittenByInstaller @@ -32,9 +43,22 @@ fn netplan_file_is_applied(f: NetplanConfigFile) -> Bool { true } -data netplan_file_is_applied_note: String = "Deliberately total, and deliberately ignoring its argument's writer field. This looks like a function that should not exist until you notice it is the exact proposition the srv4 misdiagnosis denied: presence in /etc/netplan is sufficient for application, and the writer is irrelevant to it. Writing it as a total function makes the writer-dependent version unwritable in any consumer that routes through here, and gives the RED control in gunbc.host_network_diagnosis something concrete to contradict. If netplan ever gains a genuine non-application case — a malformed file the renderer rejects, say — this becomes a real fold with a typed refusal, and that is a change to THIS module rather than to every consumer that had rolled its own rule." +// Deliberately total, and deliberately ignoring its argument's writer field. It is the exact +// proposition the srv4 misdiagnosis denied: presence in /etc/netplan is sufficient for application, +// and the writer is irrelevant. Writing it as a total function makes the writer-dependent version +// unwritable in any consumer routing through here, and gives the RED control in +// gunbc.host_network_diagnosis something concrete to contradict. If netplan ever gains a genuine +// non-application case — a malformed file the renderer rejects, say — this becomes a real fold with +// a typed refusal, a change to THIS module rather than to every consumer with its own rule. -data netplan_link_state_note: String = "Link state is the axis netplan CANNOT influence, and separating it is what lets a diagnosis point at the right layer. An interface that netplan has configured is administratively UP with a real qdisc; whether it has CARRIER is a fact about the cable, the switch port and the PHY. The two are independently observable and were observed to disagree on srv4 (2026-07-25): enP3p3s0f1 read with qdisc mq — configured and applied, no link partner — while its unused siblings read with qdisc noop, which is what an interface no configuration mentions looks like. Those two shapes are easy to confuse by eye and mean opposite things about whether the model did its job, which is exactly why they are separate variants here rather than one 'down' flag." +// Link state is the axis netplan CANNOT influence, and separating it lets a diagnosis point at the +// right layer. An interface netplan has configured is administratively UP with a real qdisc; +// whether it has CARRIER is a fact about the cable, the switch port and the PHY. The two are +// independently observable and were observed to disagree on srv4 (2026-07-25): enP3p3s0f1 read +// with qdisc mq — configured and applied, no link partner — +// while its unused siblings read with qdisc noop, the look of an interface +// no configuration mentions. The two shapes are easy to confuse by eye and mean opposite things +// about whether the model did its job, hence separate variants rather than one 'down' flag. type InterfaceAdminState = AdminUpConfigured diff --git a/dag/extdeps/network/address.dag b/dag/extdeps/network/address.dag index 5c385f6796a..e935e9baabb 100644 --- a/dag/extdeps/network/address.dag +++ b/dag/extdeps/network/address.dag @@ -13,7 +13,12 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data ip_address_note: String = "An IP address is v4 OR v6 (RFC 791 / RFC 4291) — the honestly CLOSED coproduct over the two grounded address leaves, not a string. The v4 arm is fully grounded with a wire codec (extdeps.network.ipv4); the v6 arm's type is grounded but its string codec is staged (extdeps.network.ipv6, zero fleet consumers). render_ip_address is fail-closed on the v6 arm — it returns Absent rather than fabricating a v6 string until that codec lands (§5), so no consumer can accidentally emit a wrong v6 literal. Constructors ip_v4 / ip_v6 are the tagged injections." +// An IP address is v4 OR v6 (RFC 791 / RFC 4291) — the honestly CLOSED coproduct over the two +// grounded address leaves, not a string. The v4 arm is fully grounded with a wire codec +// (extdeps.network.ipv4); the v6 arm's type is grounded but its string codec is staged +// (extdeps.network.ipv6, zero fleet consumers). render_ip_address is fail-closed on the v6 arm — it +// returns Absent rather than fabricating a v6 string until that codec lands (§5), so no consumer +// can accidentally emit a wrong v6 literal. Constructors ip_v4 / ip_v6 are the tagged injections. type IpAddress = IpV4 { v4: Ipv4Address } diff --git a/dag/extdeps/network/endpoint.dag b/dag/extdeps/network/endpoint.dag index eb00f2aea34..73c8dde4c23 100644 --- a/dag/extdeps/network/endpoint.dag +++ b/dag/extdeps/network/endpoint.dag @@ -14,7 +14,12 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data network_host_note: String = "A reachable host reference is a NAME or a literal ADDRESS — the two ways ssh/connect targets are actually written (an ssh_host is exactly this, today a bare NonEmptyStr). Reuses the already-grounded authorities: extdeps.dns.domain_name.HostName (RFC 1123, with its real label constraints) for the name arm, extdeps.network.address.IpAddress for the address arm — no fresh nickname. This is the leaf the transport model's `ssh_host: NonEmptyStr` grounds onto, and the payload of a network Reach (extdeps.network.reach)." +// A reachable host reference is a NAME or a literal ADDRESS — the two ways ssh/connect targets are +// actually written (an ssh_host is exactly this, today a bare NonEmptyStr). Reuses the +// already-grounded authorities: extdeps.dns.domain_name.HostName (RFC 1123, with its real label +// constraints) for the name arm, extdeps.network.address.IpAddress for the address arm — no fresh +// nickname. This is the leaf the transport model's `ssh_host: NonEmptyStr` grounds onto, and the +// payload of a network Reach (extdeps.network.reach). type NetworkHost = HostByName { name: HostName } diff --git a/dag/extdeps/network/ipv4.dag b/dag/extdeps/network/ipv4.dag index 3c0f06cab73..d7361ec37ba 100644 --- a/dag/extdeps/network/ipv4.dag +++ b/dag/extdeps/network/ipv4.dag @@ -18,7 +18,17 @@ data ipv4_dotted_decimal_authority: ExternalAuthority = ExternalAuthority { } } -data ipv4_grounding_note: String = "Structured IPv4 address — the dissolution of extdeps.dhcp.v4.Ipv4Address's anemic 'NonEmptyStr where brand(Ipv4Address)' (its dissolve_on_ipv4_address_anemic_brand named THIS module and a parse_ipv4_address witness). An IPv4 address is four octets (RFC 791 §3.1 — a 32-bit address), written in dotted-decimal (RFC 790 conventions); the octet is the grounded unit, a 0..255 bound modeled with the same `Int where range` refinement std.types.Port already uses for network numerics (NOT a fresh nickname — reuses the established bound idiom). Two entry points, per the model/realization split: ipv4_address(...) constructs from four octets for AUTHORED literals (byte-known at author time), parse_ipv4_address(String) recovers structure from WIRE text (DHCP tables, ip output) and REFUSES malformed input with a typed, located Ipv4ParseError rather than fabricating an address (§5 fail-closed). Round-trip (parse ∘ render == id on valid; render ∘ parse == id) is the witness." +// Structured IPv4 address — the dissolution of extdeps.dhcp.v4.Ipv4Address's anemic 'NonEmptyStr +// where brand(Ipv4Address)' (its dissolve_on_ipv4_address_anemic_brand named THIS module and a +// parse_ipv4_address witness). An IPv4 address is four octets (RFC 791 §3.1 — a 32-bit address), +// written in dotted-decimal (RFC 790 conventions); the octet is the grounded unit, a 0..255 bound +// modeled with the same `Int where range` refinement std.types.Port already uses for network +// numerics (NOT a fresh nickname — reuses the established bound idiom). Two entry points, per the +// model/realization split: ipv4_address(...) constructs from four octets for AUTHORED literals +// (byte-known at author time), parse_ipv4_address(String) recovers structure from WIRE text (DHCP +// tables, ip output) and REFUSES malformed input with a typed, located Ipv4ParseError rather than +// fabricating an address (§5 fail-closed). Round-trip (parse ∘ render == id on valid; render ∘ +// parse == id) is the witness. type Octet = Int where range(min: 0, max: 255) diff --git a/dag/extdeps/network/ipv6.dag b/dag/extdeps/network/ipv6.dag index 8573c011528..7b942fb5a5d 100644 --- a/dag/extdeps/network/ipv6.dag +++ b/dag/extdeps/network/ipv6.dag @@ -21,7 +21,17 @@ data ipv6_text_representation_authority: ExternalAuthority = ExternalAuthority { } } -data ipv6_grounding_note: String = "Structured IPv6 address (RFC 4291 §2.5): eight 16-bit groups. The group is the grounded unit — a 0..65535 bound modeled with the same `Int where range` refinement std.types.Port uses (reuses the bound idiom, not a fresh nickname), parallel to extdeps.network.ipv4's octet. STAGED CODEC (deliberate, §6): the TYPE and constructor are grounded, but the string codec (canonical text form, RFC 5952 — lowercase hex groups with :: run-length compression) is NOT built here. It is a decode/encode over extdeps.numeric.base16 (the cited hex authority — render via its int-to-hex, parse via a reverse-fold over base16_digit_rows), and there are ZERO IPv6 addresses in the fleet today, so building a wire parser now would be building for a host that does not diverge. The codec lands when a real v6 address needs (de)serializing — dissolution trigger below. This keeps IpAddress an HONESTLY CLOSED coproduct (an IP is v4 or v6) with a grounded v6 arm, without an anemic string or a speculative parser." +// Structured IPv6 address (RFC 4291 §2.5): eight 16-bit groups. The group is the grounded unit — a +// 0..65535 bound modeled with the same `Int where range` refinement std.types.Port uses (reuses the +// bound idiom, not a fresh nickname), parallel to extdeps.network.ipv4's octet. STAGED CODEC +// (deliberate, §6): the TYPE and constructor are grounded, but the string codec (canonical text +// form, RFC 5952 — lowercase hex groups with :: run-length compression) is NOT built here. It is a +// decode/encode over extdeps.numeric.base16 (the cited hex authority — render via its int-to-hex, +// parse via a reverse-fold over base16_digit_rows), and there are ZERO IPv6 addresses in the fleet +// today, so building a wire parser now would be building for a host that does not diverge. The +// codec lands when a real v6 address needs (de)serializing — dissolution trigger below. This keeps +// IpAddress an HONESTLY CLOSED coproduct (an IP is v4 or v6) with a grounded v6 arm, without an +// anemic string or a speculative parser. data ipv6_text_codec_staged_frontier_rows: List = [ frontier_row_decl( diff --git a/dag/extdeps/network/reach.dag b/dag/extdeps/network/reach.dag index 15d246b2771..a269363f5ec 100644 --- a/dag/extdeps/network/reach.dag +++ b/dag/extdeps/network/reach.dag @@ -19,7 +19,21 @@ data reach_proxycommand_authority: ExternalAuthority = ExternalAuthority { } } -data reach_note: String = "How packets get to a host — a network-reach fact, DELIBERATELY INDEPENDENT of the shell/execution transport (operator rule 2026-07-24: 'the network transport should be independent of the shell transport'). This module names NO shell mechanism; a Reach says only how the host is reachable, and the same Reach can carry any interpreter. Three grounded arms, cited to OpenSSH ssh_config: DirectLan (the host is directly on the segment); ProxyJump (reach the target THROUGH one or more jump hosts — the real, documented `-J` / ProxyJump feature, ProxyCommand being its general form); FabricTunnel (the intended long-term reach over the network fabric). FabricTunnel does not work yet (operator: 'none of that actually works'), so it is modeled but REFUSES realization — reach_realizability returns ReachRefused{FabricTunnelNotOperational}, a typed/located/counted refusal, never a silent fall-through (§5). The operator-sanctioned interim for a fabric-only target (e.g. srv3) is a ProxyJump through something actually reachable (srv2) that stands in for the fabric — a NAMED interim with a dissolution trigger, not a hidden hack: the ProxyJump reach dissolves to FabricTunnel when tunneling is operational. The srv-specific reach ROWS live in the gunbc layer (a fleet host is not an extdeps concept); this module owns only the cited Reach model + its realizability gate." +// How packets get to a host — a network-reach fact, DELIBERATELY INDEPENDENT of the shell/execution +// transport (operator rule 2026-07-24: 'the network transport should be independent of the shell +// transport'). This module names NO shell mechanism; a Reach says only how the host is reachable, +// and the same Reach can carry any interpreter. Three grounded arms, cited to OpenSSH ssh_config: +// DirectLan (the host is directly on the segment); ProxyJump (reach the target THROUGH one or more +// jump hosts — the real, documented `-J` / ProxyJump feature, ProxyCommand being its general form); +// FabricTunnel (the intended long-term reach over the network fabric). FabricTunnel does not work +// yet (operator: 'none of that actually works'), so it is modeled but REFUSES realization — +// reach_realizability returns ReachRefused{FabricTunnelNotOperational}, a typed/located/counted +// refusal, never a silent fall-through (§5). The operator-sanctioned interim for a fabric-only +// target (e.g. srv3) is a ProxyJump through something actually reachable (srv2) that stands in for +// the fabric — a NAMED interim with a dissolution trigger, not a hidden hack: the ProxyJump reach +// dissolves to FabricTunnel when tunneling is operational. The srv-specific reach ROWS live in the +// gunbc layer (a fleet host is not an extdeps concept); this module owns only the cited Reach model +// + its realizability gate. type Reach = DirectLan { host: NetworkHost } diff --git a/dag/extdeps/network/ti_dp83867.dag b/dag/extdeps/network/ti_dp83867.dag index 87f8323c33c..b5e3b444323 100644 --- a/dag/extdeps/network/ti_dp83867.dag +++ b/dag/extdeps/network/ti_dp83867.dag @@ -25,7 +25,17 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data ti_dp83867_note: String = "THIS ROW IS A MECHANICAL CANDIDATE AND NOT A SELECTED PART. Nothing here says the device suits any board role; a selection is a functional-compatibility join done at the product layer, and this row supplies only the package authority that join consumes. TWO PACKAGES ON ONE DATASHEET IS THE HAZARD THIS ROW IS SHAPED AROUND. The document reprints PAP0064M and RGZ0048B, each with its own body dimensions and its own land pattern, and nothing about a record field name stops a body from one being bound beside a land pattern from the other — the two would look entirely plausible together and would produce a board with sixteen missing pads. So the designator is carried on BOTH the body and the pattern and joined by extdeps.land_pattern.types, which refuses the mismatch rather than trusting the author's attention. Only the RGZ0048B part is recorded; adding the PAP0064M ordering part number later is a second row, not an edit to this one. Mechanical facts only: this row asserts nothing about the PHY's media interfaces, its speeds or its power rails." +// THIS ROW IS A MECHANICAL CANDIDATE AND NOT A SELECTED PART. Nothing here says the device suits +// any board role; selection is a functional-compatibility join at the product layer, and this row +// supplies only the package authority that join consumes. TWO PACKAGES ON ONE DATASHEET IS THE +// HAZARD THIS ROW IS SHAPED AROUND. The document reprints PAP0064M and RGZ0048B, each with its own +// body dimensions and land pattern, and no record field name stops a body from one being bound +// beside a land pattern from the other — plausible together, and a board with sixteen missing +// pads. So the designator is carried on BOTH the body and the pattern and joined by +// extdeps.land_pattern.types, which refuses the mismatch rather than trusting the author's +// attention. Only the RGZ0048B part is recorded; adding the PAP0064M ordering part number later is +// a second row, not an edit to this one. Mechanical facts only: nothing here about the PHY's media +// interfaces, speeds or power rails. data dp83867_rgz0048b_drawing: PackageDrawingRef = PackageDrawingRef { drawing_number: "4218795/B", diff --git a/dag/extdeps/npm.dag b/dag/extdeps/npm.dag index c6cf4d378d3..b70d895f9d4 100644 --- a/dag/extdeps/npm.dag +++ b/dag/extdeps/npm.dag @@ -69,11 +69,24 @@ data extdeps_model_scope: ExternalModelScope = ExternalModelScope { further_citations: [npm_sri_w3c_authority] } -data npm_scope_subject_note: String = "THE SUBJECT IS the npm package-lock format — path keys, node metadata, declared dependency maps, and integrity wire — not the npm registry HTTP API and not gunbc Codex product policy. Citation pins the v10 package-lock docs matching the measured Codex npm 10.x line; lockfileVersion is observed as an integer and closed to NpmPackageLockV3 by typed decode. SRI decode is a BOUNDED first profile (single expression, no options, sha512|sha1 only) — not a claim to implement the full W3C SRI grammar." - -data npm_integrity_decode_note: String = "THREE STAGES, DO NOT FUSE. (1) npm_decode_integrity: raw SRI wire → NpmIntegrityDecode (typed wire outcomes). (2) npm_registry_integrity_from_decode: successful decode → NpmRegistryIntegrity; wire refusals stay typed, never Option. (3) gunbc.package_delivery.codex_admit_npm_integrity: product policy over NpmRegistryIntegrity only. Decoding EXPECTED digest is not artifact verification." - -data npm_resolved_graph_boundary_note: String = "TWO npm-owned facts, do not fuse. A parsed roster carries path-keyed nodes and declared dependency maps from the lock JSON. Exact consumer→dependency PackageLockGraph edges require an npm-owned realization receipt (resolver / Arborist / post-npm-ci read-back) — never name→first-matching-node inventing inside gunbc. npm remains the resolver; gunbc models and verifies its result." +// THE SUBJECT IS the npm package-lock format — path keys, node metadata, declared dependency maps, +// and integrity wire — not the npm registry HTTP API and not gunbc Codex product policy. Citation +// pins the v10 package-lock docs matching the measured Codex npm 10.x line; lockfileVersion is +// observed as an integer and closed to NpmPackageLockV3 by typed decode. SRI decode is a BOUNDED +// first profile (single expression, no options, sha512|sha1 only) — not a claim to implement the +// full W3C SRI grammar. + +// THREE STAGES, DO NOT FUSE. (1) npm_decode_integrity: raw SRI wire → NpmIntegrityDecode (typed +// wire outcomes). (2) npm_registry_integrity_from_decode: successful decode → NpmRegistryIntegrity; +// wire refusals stay typed, never Option. (3) gunbc.package_delivery.codex_admit_npm_integrity: +// product policy over NpmRegistryIntegrity only. Decoding EXPECTED digest is not artifact +// verification. + +// TWO npm-owned facts, do not fuse. A parsed roster carries path-keyed nodes and declared +// dependency maps from the lock JSON. Exact consumer→dependency PackageLockGraph edges require an +// npm-owned realization receipt (resolver / Arborist / post-npm-ci read-back) — never +// name→first-matching-node inventing inside gunbc. npm remains the resolver; gunbc models and +// verifies its result. type NpmPackageLockFormat = | NpmPackageLockV3 @@ -104,14 +117,19 @@ type NpmLockPathKey key: NonEmptyStr } -data npm_lock_path_key_note: String = "npm package-lock packages map uses the empty string \"\" for the root project — NpmRootLockPath is derived only from that exact empty JSON object key. Every other key is an opaque upstream lock key (NpmRelativeLockPath.key). It is NOT automatically a safe filesystem-relative host path; materialization later decides whether and how to project it." +// npm package-lock packages map uses the empty string "" for the root project — NpmRootLockPath is +// derived only from that exact empty JSON object key. Every other key is an opaque upstream lock +// key (NpmRelativeLockPath.key). It is NOT automatically a safe filesystem-relative host path; +// materialization later decides whether and how to project it. type NpmDependencyKind = NpmRuntimeDependency | NpmOptionalDependency | NpmPeerDependency -data npm_dependency_kind_note: String = "Declared edge kinds come from the lock's dependency maps once an npm realization supplies exact node-key endpoints. A node marked dev/optional is NODE METADATA, not a second edge category invented as NpmDevDependency." +// Declared edge kinds come from the lock's dependency maps once an npm realization supplies exact +// node-key endpoints. A node marked dev/optional is NODE METADATA, not a second edge category +// invented as NpmDevDependency. type NpmLockNodeFlags { dev: Bool @@ -136,13 +154,17 @@ type NpmPackageLockManager { format: NpmPackageLockFormat } -data npm_package_lock_manager_note: String = "FIRST SLICE: the lockfile carries lockfileVersion, not an npm CLI version. NpmPackageLockManager is format-only. A separate npm-version observation (e.g. npm --version at materialization) may join later — do not invent a VersionIdentity from silence." +// FIRST SLICE: the lockfile carries lockfileVersion, not an npm CLI version. NpmPackageLockManager +// is format-only. A separate npm-version observation (e.g. npm --version at materialization) may +// join later — do not invent a VersionIdentity from silence. type NpmResolvedRegistryArtifact { locator: NonEmptyStr } -data npm_resolved_registry_source_note: String = "FIRST SLICE: locator remains NonEmptyStr until acquisition types a registry tarball URL vs configured-registry marker vs file: source. Do not treat those as interchangeable forever — typed source identity lands with archive acquisition." +// FIRST SLICE: locator remains NonEmptyStr until acquisition types a registry tarball URL vs +// configured-registry marker vs file: source. Do not treat those as interchangeable forever — typed +// source identity lands with archive acquisition. type NpmRegistryIntegrity = NpmRegistrySha512Integrity { @@ -176,7 +198,10 @@ type NpmLockNodeDecode native_form: NonEmptyStr } -data npm_lock_node_selection_note: String = "FIRST CODEX PROFILE supports only root + registry-artifact nodes. Git, link, and local forms decode as NpmLockNodeFormUnsupported — add typed arms only when a real supported consumer needs them. NpmRegistryArtifact is the native selection record; Pin> is DERIVED (npm_registry_artifact_pin), never stored beside native integrity." +// FIRST CODEX PROFILE supports only root + registry-artifact nodes. Git, link, and local forms +// decode as NpmLockNodeFormUnsupported — add typed arms only when a real supported consumer needs +// them. NpmRegistryArtifact is the native selection record; Pin> is +// DERIVED (npm_registry_artifact_pin), never stored beside native integrity. fn npm_registry_integrity_content_hash(integrity: NpmRegistryIntegrity) -> ContentHash { match integrity { @@ -338,7 +363,11 @@ fn npm_decode_integrity(wire: String) -> NpmIntegrityDecode { } } -data npm_package_lock_parse_note: String = "FORWARD READ of package-lock.json v3 via parse_json. Produces a path-keyed roster of NpmLockNodeDecode outcomes and a PackageLockGraph with EMPTY edges — exact consumer→dependency endpoints require an npm-owned realization (npm_resolved_graph_boundary_note). Do not invent edges by name matching. Package name absent on a node is derived from the lock path key after the last node_modules/ segment (npm docs)." +// FORWARD READ of package-lock.json v3 via parse_json. Produces a path-keyed roster of +// NpmLockNodeDecode outcomes and a PackageLockGraph with EMPTY edges — exact consumer→dependency +// endpoints require an npm-owned realization (npm_resolved_graph_boundary_note). Do not invent +// edges by name matching. Package name absent on a node is derived from the lock path key after the +// last node_modules/ segment (npm docs). type NpmPackageLockGraph = PackageLockGraph @@ -401,7 +430,9 @@ type NpmBooleanMemberObservation } | NpmBooleanMemberNotAnObject -data npm_boolean_member_observation_note: String = "Absent / observed / duplicate / wrong-type are DISTINCT. package-lock defaults apply only on Absent (npm docs: missing Boolean flags are false). Duplicate and wrong-type refuse — never collapse to false." +// Absent / observed / duplicate / wrong-type are DISTINCT. package-lock defaults apply only on +// Absent (npm docs: missing Boolean flags are false). Duplicate and wrong-type refuse — never +// collapse to false. fn npm_json_boolean_member(v: JsonValue, key: String) -> NpmBooleanMemberObservation { match json_object_unique_member(v: v, key: key) { @@ -432,7 +463,8 @@ type NpmStringListMemberObservation } | NpmStringListMemberNotAnObject -data npm_string_list_member_observation_note: String = "Platform os/cpu lists: Absent means unconstrained ([]). Duplicate, wrong container type, or non-string element refuse — never silently project to []." +// Platform os/cpu lists: Absent means unconstrained ([]). Duplicate, wrong container type, or +// non-string element refuse — never silently project to []. type NpmStringListFoldAcc = NpmStringListFoldOk { diff --git a/dag/extdeps/numeric/base16.dag b/dag/extdeps/numeric/base16.dag index 457f79a1951..c6f3c9f901d 100644 --- a/dag/extdeps/numeric/base16.dag +++ b/dag/extdeps/numeric/base16.dag @@ -11,7 +11,10 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data base16_note: String = "Cited upstream: RFC 4648 §3 Base 16 Encoding (retrieved 2026-07-22) — the 0-9a-f digit alphabet. Single authority for hexadecimal digit serialization (census item 14, msg_e9cfe443). Table-as-rows + fold — both extdeps.color.srgb byte_hex and extdeps.languages.json.emit int_to_upper_hex route here; the per-site 16-arm if-chains are dissolved." +// Cited upstream: RFC 4648 §3 Base 16 Encoding (retrieved 2026-07-22) — the 0-9a-f digit alphabet. +// Single authority for hexadecimal digit serialization (census item 14, msg_e9cfe443). +// Table-as-rows + fold — both extdeps.color.srgb byte_hex and extdeps.languages.json.emit +// int_to_upper_hex route here; the per-site 16-arm if-chains are dissolved. type Base16DigitRow { value: Int @@ -40,7 +43,9 @@ data base16_digit_rows: List = [ data base16_digit_refusal_sentinel: String = "!" -data base16_digit_miss_note: String = "Lookup miss returns base16_digit_refusal_sentinel (fail-loud: corrupt hex/JSON emission, never silent empty string). Callers gate d to [0,15] via /16 and %16; witness_base16_digit_refuses_out_of_range pins the miss arm." +// Lookup miss returns base16_digit_refusal_sentinel (fail-loud: corrupt hex/JSON emission, never +// silent empty string). Callers gate d to [0,15] via /16 and %16; +// witness_base16_digit_refuses_out_of_range pins the miss arm. fn base16_digit_char(rows: List, d: Int, upper: Bool) -> String { let found = fold(rows, init: "", f: (acc, row) => diff --git a/dag/extdeps/ocp/mt_jade/subject.dag b/dag/extdeps/ocp/mt_jade/subject.dag index 4e3b3d7edb0..31eb241dd7a 100644 --- a/dag/extdeps/ocp/mt_jade/subject.dag +++ b/dag/extdeps/ocp/mt_jade/subject.dag @@ -32,7 +32,17 @@ data mt_jade_subject_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data mt_jade_subject_note: String = "MT. JADE AS AN OCP-ACCEPTED REFERENCE PLATFORM, NOT AS A PACKAGE FACT. extdeps.cpu.ampere_altra_package correctly separates the public Altra package datasheet from the NDA reference-board collateral listing Mt. Jade among four Ampere boards. This module is the third axis: the OCP-published motherboard specification, which names platform architecture facts without being the full Customer Connect design-file drop. THE PUBLISHER LOCATOR REMAINS THE CITATION EVEN WHEN UNREACHABLE. HOW THIS REPOSITORY READ OR FAILED TO READ THAT LOCATOR IS RECORDED AS A GUNBC RECEIPT WITH A DeclarationRef TO mt_jade_reference_platform, NOT AS A FIELD ON THIS RECORD. THE LICENCE IS WHAT MAKES PUBLIC MODELLING ADMISSIBLE: usage is under OWFa 1.0 (Open Web Foundation Final Specification Agreement), contributions are under the OCP Contribution License Agreement, and the licence explicitly does NOT apply to the Appendices — they are reference-only and non-normative, so no fact in this repository may be attributed to Appendix A or Appendix B." +// MT. JADE AS AN OCP-ACCEPTED REFERENCE PLATFORM, NOT AS A PACKAGE FACT. +// extdeps.cpu.ampere_altra_package separates the public Altra package datasheet from the NDA +// reference-board collateral listing Mt. Jade among four Ampere boards. This module is the third +// axis: the OCP-published motherboard specification, naming platform architecture facts without +// being the full Customer Connect design-file drop. THE PUBLISHER LOCATOR REMAINS THE CITATION EVEN +// WHEN UNREACHABLE. HOW THIS REPOSITORY READ OR FAILED TO READ THAT LOCATOR IS A GUNBC RECEIPT WITH +// A DeclarationRef TO mt_jade_reference_platform, NOT A FIELD ON THIS RECORD. THE LICENCE IS WHAT +// MAKES PUBLIC MODELLING ADMISSIBLE: usage under OWFa 1.0 (Open Web Foundation Final Specification +// Agreement), contributions under the OCP Contribution License Agreement, and the licence +// explicitly does NOT apply to the Appendices — reference-only and non-normative, so no fact here +// may be attributed to Appendix A or Appendix B. type MtJadeRev1_0 {} diff --git a/dag/extdeps/ocp/mt_mitchell/platform.dag b/dag/extdeps/ocp/mt_mitchell/platform.dag index 21790326f9e..32ebff01fa5 100644 --- a/dag/extdeps/ocp/mt_mitchell/platform.dag +++ b/dag/extdeps/ocp/mt_mitchell/platform.dag @@ -38,7 +38,20 @@ data extdeps_model_scope: ExternalModelScope = ExternalModelScope { further_citations: [internet_archive_mt_mitchell_capture_authority] } -data mt_mitchell_ingestion_note: String = "READ, NOT RECALLED — AND READ FROM A CAPTURE, NOT FROM THE PUBLISHER. The publisher locator is the anchor because that is where the document lives, and an unauthenticated fetch of it returned HTTP 403 on 2026-08-14; the bytes behind every row here came from the dated Internet Archive capture of that same URL, which the subject module owns as internet_archive_mt_mitchell_capture_authority and this module cites rather than re-mints (review 51969). Saying we read Mt. Mitchell without that distinction would assert we read the publisher copy, which we did not. The licence is what makes carrying it admissible: usage is under the Open Web Foundation Modified Final Specification Agreement quoted in the document's own licence section, and that section states the licence does NOT apply to the Appendices, which are reference-only and non-normative. No row in this module is attributed to an appendix. The revision-table rows below are the document's section 3 and are carried because the subject module fixes ONE revision label: the series is what makes 0.80 the current one rather than a bare string, and a later revision would join against these rows instead of silently replacing a literal." +// READ, NOT RECALLED — AND READ FROM A CAPTURE, NOT FROM THE PUBLISHER. The publisher locator is +// the anchor because that is where the document lives, and an unauthenticated fetch of it returned +// HTTP 403 on 2026-08-14; the bytes behind every row here came from the dated Internet Archive +// capture of that same URL, which the subject module owns as +// internet_archive_mt_mitchell_capture_authority and this module cites rather than re-mints (review +// 51969). Saying we read Mt. Mitchell without that distinction would assert we read the publisher +// copy, which we did not. The licence is what makes carrying it admissible: usage is under the Open +// Web Foundation Modified Final Specification Agreement quoted in the document's own licence +// section, and that section states the licence does NOT apply to the Appendices, which are +// reference-only and non-normative. No row in this module is attributed to an appendix. The +// revision-table rows below are the document's section 3 and are carried because the subject module +// fixes ONE revision label: the series is what makes 0.80 the current one rather than a bare +// string, and a later revision would join against these rows instead of silently replacing a +// literal. // THIS DOCUMENT'S OWN PUBLICATION STATUS. An OCP contribution is public under // an explicit open licence its publisher wrote down, which is a different KIND @@ -221,8 +234,22 @@ data mt_mitchell_board_width: Micrometer = micrometer(count: 424900) data mt_mitchell_board_depth: Micrometer = micrometer(count: 422900) -data mt_mitchell_chassis_height: RackUnit = rack_unit(count: 2) +// Section 7.2: the master CPU socket sits on the right side of the motherboard and is REQUIRED to +// boot the system when a single CPU is populated. That is an asymmetry between the two sockets, so +// a dual-socket board with one socket filled is not the one-socket architecture option — it is a +// specific population of the dual-socket board, and which socket is filled decides whether it boots +// at all. -data mt_mitchell_master_socket_boot_note: String = "Section 7.2: the master CPU socket sits on the right side of the motherboard and is REQUIRED to boot the system when a single CPU is populated. That is an asymmetry between the two sockets, so a dual-socket board with one socket filled is not the one-socket architecture option — it is a specific population of the dual-socket board, and which socket is filled decides whether it boots at all." +data mt_mitchell_chassis_height: RackUnit = rack_unit(count: 2) -data mt_mitchell_coverage_note: String = "WHAT THIS MODULE MODELS AND WHAT IT LEAVES IN THE DOCUMENT. MODELLED: the revision table; the three architecture options and their memory generation, buffering and ECC requirement; the expansion generation; the MCIO and OCP NIC connector populations and host modes; the inter-processor link ports; BMC part and placement; board outline and chassis height. NOT MODELLED: the connector pin definition tables (7.5.2.1 through 7.5.2.5), the I2C network, GPIO and debug interface maps (7.6 through 7.8), clocks, reset and power-on sequencing (7.10), thermal requirements (8), the I/O and rear-side subsystems (9 and 10), the power delivery block diagram (12), environmental and prescribed-materials sections (13 and 14), and the appendices, which the licence itself excludes as non-normative. The per-pin tables are exactly the material a board bring-up will need; they are not carried on speculation, and the consumer that needs one is what earns its transcription." +// WHAT THIS MODULE MODELS AND WHAT IT LEAVES IN THE DOCUMENT. MODELLED: the revision table; the +// three architecture options and their memory generation, buffering and ECC requirement; the +// expansion generation; the MCIO and OCP NIC connector populations and host modes; the +// inter-processor link ports; BMC part and placement; board outline and chassis height. NOT +// MODELLED: the connector pin definition tables (7.5.2.1 through 7.5.2.5), the I2C network, GPIO +// and debug interface maps (7.6 through 7.8), clocks, reset and power-on sequencing (7.10), thermal +// requirements (8), the I/O and rear-side subsystems (9 and 10), the power delivery block diagram +// (12), environmental and prescribed-materials sections (13 and 14), and the appendices, which the +// licence itself excludes as non-normative. The per-pin tables are exactly the material a board +// bring-up will need; they are not carried on speculation, and the consumer that needs one is what +// earns its transcription. diff --git a/dag/extdeps/ocp/mt_mitchell/subject.dag b/dag/extdeps/ocp/mt_mitchell/subject.dag index 2ae838e1119..9c5ef40fb96 100644 --- a/dag/extdeps/ocp/mt_mitchell/subject.dag +++ b/dag/extdeps/ocp/mt_mitchell/subject.dag @@ -35,7 +35,13 @@ data internet_archive_mt_mitchell_capture_authority: ExternalAuthority = Externa } } -data mt_mitchell_subject_note: String = "OCP-CONTRIBUTED REFERENCE PLATFORM SPECIFICATION FOR MT. MITCHELL. Usage is under the same OWFa 1.0 / OCP CLA family as Mt. Jade when the document is admitted. THE PUBLISHER LOCATOR REMAINS THE CITATION EVEN WHEN UNREACHABLE. HOW THIS REPOSITORY READ OR FAILED TO READ THAT LOCATOR — INCLUDING ANY MIRROR CAPTURE CITED IN further_citations — IS RECORDED AS GUNBC RECEIPTS WITH DeclarationRefs TO mt_mitchell_reference_platform, NOT AS FIELDS ON THIS RECORD. THIS MODULE IS THE SUBJECT ANCHOR ONLY; PLATFORM FACTS ARE OWNED BY extdeps.ocp.mt_mitchell.platform AND LAND IN #8260." +// OCP-CONTRIBUTED REFERENCE PLATFORM SPECIFICATION FOR MT. MITCHELL. Usage is under the same OWFa +// 1.0 / OCP CLA family as Mt. Jade when the document is admitted. THE PUBLISHER LOCATOR REMAINS THE +// CITATION EVEN WHEN UNREACHABLE. HOW THIS REPOSITORY READ OR FAILED TO READ THAT LOCATOR — +// INCLUDING ANY MIRROR CAPTURE CITED IN further_citations — IS RECORDED AS GUNBC RECEIPTS WITH +// DeclarationRefs TO mt_mitchell_reference_platform, NOT AS FIELDS ON THIS RECORD. THIS MODULE IS +// THE SUBJECT ANCHOR ONLY; PLATFORM FACTS ARE OWNED BY extdeps.ocp.mt_mitchell.platform AND LAND IN +// #8260. type MtMitchellRev0_80 {} diff --git a/dag/extdeps/ocp/publication.dag b/dag/extdeps/ocp/publication.dag index 3b5bde34577..8d8ca171143 100644 --- a/dag/extdeps/ocp/publication.dag +++ b/dag/extdeps/ocp/publication.dag @@ -12,7 +12,11 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data ocp_publication_vocabulary_note: String = "OCP LICENCE VOCABULARY GROUNDED ON OWFa 1.0. Owfa1_0FinalSpecificationAgreement and OcpContributionLicenseAgreement are closed variants for subject anchors; the anchor cites the Open Web Foundation Final Specification Agreement text that defines the open-licence arm. Upstream document facts and read receipts live in subject modules and gunbc.specification_citation_read_provenance respectively." +// OCP LICENCE VOCABULARY GROUNDED ON OWFa 1.0. Owfa1_0FinalSpecificationAgreement and +// OcpContributionLicenseAgreement are closed variants for subject anchors; the anchor cites the +// Open Web Foundation Final Specification Agreement text that defines the open-licence arm. +// Upstream document facts and read receipts live in subject modules and +// gunbc.specification_citation_read_provenance respectively. type Owfa1_0FinalSpecificationAgreement {} diff --git a/dag/extdeps/ollama/binary_release.dag b/dag/extdeps/ollama/binary_release.dag index 2eb383585a1..43aba353768 100644 --- a/dag/extdeps/ollama/binary_release.dag +++ b/dag/extdeps/ollama/binary_release.dag @@ -15,7 +15,11 @@ import std.types { NonEmptyStr, String, List } import std.measure { ByteSize, byte_size } import std.decl_ref { DeclarationRef, WholeDeclaration } -data extdeps_ollama_binary_release_note: String = "CITED UPSTREAM FACTS ONLY. Install and extract live in gunbc.package_delivery once #8225 lands (Ollama as the second runtime-bundle consumer beside Codex). This module owns the pinned release row the closure digest derives from — never a shell leaf that would fork a parallel install path. Asset filenames derive from version x os x arch x variant x archive format — never stored as hand-authored literals." +// CITED UPSTREAM FACTS ONLY. Install and extract live in gunbc.package_delivery once #8225 lands +// (Ollama as the second runtime-bundle consumer beside Codex). This module owns the pinned release +// row the closure digest derives from — never a shell leaf that would fork a parallel install path. +// Asset filenames derive from version x os x arch x variant x archive format — never stored as +// hand-authored literals. data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { uri: Uri { @@ -124,7 +128,10 @@ fn ollama_binary_release_eq(a: OllamaBinaryRelease, b: OllamaBinaryRelease) -> B && (a.digest.hex as String) == (b.digest.hex as String) } -data ollama_v0_11_0_feature_floor_note: String = "Feature-introduction floor for OpenAI gpt-oss model support is ollama_gpt_oss_feature_introduction_floor in extdeps.ollama.capability — a semantic-version relation, not a materialisable binary-release row in this module. Cited release: https://github.com/ollama/ollama/releases/tag/v0.11.0 published 2025-08-05." +// Feature-introduction floor for OpenAI gpt-oss model support is +// ollama_gpt_oss_feature_introduction_floor in extdeps.ollama.capability — a semantic-version +// relation, not a materialisable binary-release row in this module. Cited release: +// https://github.com/ollama/ollama/releases/tag/v0.11.0 published 2025-08-05. data ollama_v0_32_9_release: OllamaBinaryRelease = OllamaBinaryRelease { version: "v0.32.9" as NonEmptyStr, diff --git a/dag/extdeps/ollama/gpu_support.dag b/dag/extdeps/ollama/gpu_support.dag index 0a04abfea04..f4cc317c38e 100644 --- a/dag/extdeps/ollama/gpu_support.dag +++ b/dag/extdeps/ollama/gpu_support.dag @@ -50,7 +50,9 @@ data ollama_gb10_minimum_nvidia_driver: OllamaNvidiaDriverVersion = OllamaNvidia major: 550, } -data ollama_gb10_driver_requirement_note: String = "docs.ollama.com/gpu documents GB10 (DGX Spark) at compute capability 12.1 with NVIDIA driver 550 or newer. An old driver does not refuse install — Ollama falls back to CPU — so an unobserved driver must not read as satisfied." +// docs.ollama.com/gpu documents GB10 (DGX Spark) at compute capability 12.1 with NVIDIA driver 550 +// or newer. An old driver does not refuse install — Ollama falls back to CPU — so an unobserved +// driver must not read as satisfied. type OllamaNvidiaDriverRequirementVerdict = OllamaNvidiaDriverRequirementSatisfied diff --git a/dag/extdeps/os/ubuntu_autoinstall.dag b/dag/extdeps/os/ubuntu_autoinstall.dag index 62423374a6f..f2d0577feb0 100644 --- a/dag/extdeps/os/ubuntu_autoinstall.dag +++ b/dag/extdeps/os/ubuntu_autoinstall.dag @@ -35,7 +35,12 @@ data nocloud_datasource_authority: ExternalAuthority = ExternalAuthority { } } -// The ds= kernel-cmdline token grammar (ds=nocloud;s= / ds=nocloud-net;s=) is owned by cloud-init's NoCloud datasource (anchor above) — a DISTINCT upstream from subiquity's autoinstall reference (the module anchor), which owns the autoinstall payload the seed delivers. The ';' inside this token is the character that forced grub_cmdline_arg_render's quoting rule: unquoted, grub treats ';' as a command separator and silently truncates the seedfrom, degrading autoinstall to interactive (root-caused live on srv3/srv4). +// The ds= kernel-cmdline token grammar (ds=nocloud;s= / ds=nocloud-net;s=) is owned +// by cloud-init's NoCloud datasource (anchor above) — a DISTINCT upstream from subiquity's +// autoinstall reference (the module anchor), which owns the autoinstall payload the seed delivers. +// The ';' in this token forced grub_cmdline_arg_render's quoting rule: unquoted, grub treats ';' as +// a command separator and silently truncates the seedfrom, degrading autoinstall to interactive +// (root-caused live on srv3/srv4). type UbuntuAutoinstallPayload { autoinstall_version: Int delivery: AutoinstallDeliveryMethod diff --git a/dag/extdeps/package.dag b/dag/extdeps/package.dag index 5fbd7189138..f68c706e490 100644 --- a/dag/extdeps/package.dag +++ b/dag/extdeps/package.dag @@ -24,11 +24,29 @@ data extdeps_model_scope: ExternalModelScope = ExternalModelScope { further_citations: [] } -data package_scope_subject_note: String = "THE SUBJECT IS THE EXACT DEPENDENCY / LOCK-GRAPH RELATION — PackageLockGraph: nodes are package selections at the manager's node grain; edges are consumer→dependency relations at that same grain. Grounded on the Reproducible Builds definition (dependencies and their versions as build-environment attributes), the same external citation extdeps.pin spells for Pin — two DISTINCT CITATIONS of one upstream page, not duplicated semantic authority (extdeps.pin pin_citation_note: the structural authority lens reads each module's anchor locally, so neither module may alias the other's anchor declaration).\\n\\nNOT Package_format: an archive-plus-metadata subject is a different external fact. Defer a generic package-artifact module until a second ecosystem needs it; do not leave PackageLockGraph cited as a package archive.\\n\\nTHE CITATION BOUNDS WHAT MAY LIVE HERE. It does NOT ground JavaScript module formats, npm lifecycle-script policy, artifact-admission outcomes, or gunbc materialization — those live in extdeps.npm (upstream manager facts) and gunbc.package_delivery (product policy and mechanism). Exact package/version/content identity for a downloaded release is Pin> (extdeps.pin); ecosystems derive that pin from native observations — they do not mint a parallel selection carrier beside it." - -data package_hub_agnostic_note: String = "THE AGNOSTIC SHAPE OF A DEPENDENCY GRAPH. The hub parameterizes Selection — ecosystems define their own selection shapes. The hub never enumerates package managers. No synthetic PinnedPackageArtifactSelection convenience: the first real consumer (npm) derives Pin from native lock observations." - -data package_lock_node_grain_note: String = "THE LOCK NODE, NOT name@version, IS THE IDENTITY. NodeKey is a type parameter from the ecosystem. Edges name keys; selections live on nodes." +// THE SUBJECT IS THE EXACT DEPENDENCY / LOCK-GRAPH RELATION — PackageLockGraph: nodes are package +// selections at the manager's node grain; edges are consumer→dependency relations at that same +// grain. Grounded on the Reproducible Builds definition (dependencies and their versions as +// build-environment attributes), the same external citation extdeps.pin spells for Pin — two +// DISTINCT CITATIONS of one upstream page, not duplicated semantic authority (extdeps.pin +// pin_citation_note: the structural authority lens reads each module's anchor locally, so neither +// module may alias the other's anchor declaration).\n\nNOT Package_format: an archive-plus-metadata +// subject is a different external fact. Defer a generic package-artifact module until a second +// ecosystem needs it; do not leave PackageLockGraph cited as a package archive.\n\nTHE CITATION +// BOUNDS WHAT MAY LIVE HERE. It does NOT ground JavaScript module formats, npm lifecycle-script +// policy, artifact-admission outcomes, or gunbc materialization — those live in extdeps.npm +// (upstream manager facts) and gunbc.package_delivery (product policy and mechanism). Exact +// package/version/content identity for a downloaded release is Pin> +// (extdeps.pin); ecosystems derive that pin from native observations — they do not mint a parallel +// selection carrier beside it. + +// THE AGNOSTIC SHAPE OF A DEPENDENCY GRAPH. The hub parameterizes Selection — ecosystems define +// their own selection shapes. The hub never enumerates package managers. No synthetic +// PinnedPackageArtifactSelection convenience: the first real consumer (npm) derives Pin from native +// lock observations. + +// THE LOCK NODE, NOT name@version, IS THE IDENTITY. NodeKey is a type parameter from the ecosystem. +// Edges name keys; selections live on nodes. type PackageCoordinate { ecosystem: Ecosystem @@ -40,7 +58,9 @@ type PackageRequirement { spec: RequirementSpec } -data package_artifact_source_note: String = "WHERE THE BYTES CAME FROM IS EVIDENCE, NOT A FALLBACK LADDER. One locator, never an ordered mirror list. Ecosystem modules own typed source identities; this hub does not mint a parallel selection record." +// WHERE THE BYTES CAME FROM IS EVIDENCE, NOT A FALLBACK LADDER. One locator, never an ordered +// mirror list. Ecosystem modules own typed source identities; this hub does not mint a parallel +// selection record. type PackageLockNode { key: NodeKey @@ -61,7 +81,8 @@ type PackageLockGraph { edges: List> } -data package_graph_identity_absent_note: String = "NO authored lock_identity field. Canonical graph digest is a fold over parts, computed by the consumer." +// NO authored lock_identity field. Canonical graph digest is a fold over parts, computed by the +// consumer. type PackageLockNodeLookup = PackageLockNodeAbsent { key: NodeKey } @@ -73,7 +94,9 @@ type PackageLockNodeLookup count: Int } -data package_lock_lookup_note: String = "EXACT CARDINALITY, NOT BOOLEAN MEMBERSHIP (operator portfolio 2026-08-04). any(key==) was fail-open on duplicates: one key and three duplicate keys both returned true, so roots and edges resolved against an ambiguous graph. Lookup is 0 / 1 / many." +// EXACT CARDINALITY, NOT BOOLEAN MEMBERSHIP (operator portfolio 2026-08-04). any(key==) was +// fail-open on duplicates: one key and three duplicate keys both returned true, so roots and edges +// resolved against an ambiguous graph. Lookup is 0 / 1 / many. fn package_lock_lookup_node( graph: PackageLockGraph, @@ -110,7 +133,14 @@ type PackageLockGraphAdmission( graph: PackageLockGraph, diff --git a/dag/extdeps/physics/bach_et_al_2013_double_slit.dag b/dag/extdeps/physics/bach_et_al_2013_double_slit.dag index 44450ca8bf4..04b0dc76d10 100644 --- a/dag/extdeps/physics/bach_et_al_2013_double_slit.dag +++ b/dag/extdeps/physics/bach_et_al_2013_double_slit.dag @@ -30,13 +30,20 @@ data extdeps_model_scope: ExternalModelScope = ExternalModelScope { further_citations: [] } -data bach_et_al_2013_citation_note: String = "Cited upstream: Bach, R., Pope, D., Liou, S.-H. and Batelaan, H., 'Controlled double-slit electron diffraction', New Journal of Physics volume 15, article 033018, 2013 (doi 10.1088/1367-2630/15/3/033018). arXiv:1210.6243. WHAT THIS MODULE OWNS: Bach et al. 2013 apparatus vocabulary and source-grounded categorical and buildup observation rows." +// Cited upstream: Bach, R., Pope, D., Liou, S.-H. and Batelaan, H., 'Controlled double-slit +// electron diffraction', New Journal of Physics volume 15, article 033018, 2013 (doi +// 10.1088/1367-2630/15/3/033018). arXiv:1210.6243. WHAT THIS MODULE OWNS: Bach et al. 2013 +// apparatus vocabulary and source-grounded categorical and buildup observation rows. -data abstract_observed_distributions_note: String = "Abstract verbatim: By placing a movable mask in front of a double-slit to control the transmission through the individual slits, probability distributions for single- and double-slit arrangements were observed." +// Abstract verbatim: By placing a movable mask in front of a double-slit to control the +// transmission through the individual slits, probability distributions for single- and double-slit +// arrangements were observed. -data figures_one_and_two_normalized_distributions_note: String = "Figures 1 and 2 present normalized probability distributions; the paper sums frames then normalizes." +// Figures 1 and 2 present normalized probability distributions; the paper sums frames then +// normalizes. -data slit_mask_control_not_which_path_readout_note: String = "Bach et al. 2013 controlled which slits were open via the movable mask; the cited experiment did not perform a which-path distinguishability readout condition." +// Bach et al. 2013 controlled which slits were open via the movable mask; the cited experiment did +// not perform a which-path distinguishability readout condition. // Mask can block slit 1, slit 2, both, or neither (figure caption). BothSlitsBlocked is an apparatus configuration only — not an arrival observation. type SlitMaskConfiguration @@ -45,7 +52,8 @@ type SlitMaskConfiguration | SlitTwoOpen | BothSlitsBlocked -data single_electron_rate_note: String = "Paper states roughly one detected electron per second and only one electron in the apparatus at a time." +// Paper states roughly one detected electron per second and only one electron in the apparatus at a +// time. data trial_versus_ensemble_grain_note: String = "Trial grain: one localized detection event. Ensemble grain: accumulated buildup panels and summed normalized distributions." diff --git a/dag/extdeps/physics/chapman_et_al_1995_atom_interferometer_which_path.dag b/dag/extdeps/physics/chapman_et_al_1995_atom_interferometer_which_path.dag index b75008af264..f763131fd4f 100644 --- a/dag/extdeps/physics/chapman_et_al_1995_atom_interferometer_which_path.dag +++ b/dag/extdeps/physics/chapman_et_al_1995_atom_interferometer_which_path.dag @@ -29,9 +29,17 @@ data extdeps_model_scope: ExternalModelScope = ExternalModelScope { further_citations: [] } -data chapman_et_al_1995_citation_note: String = "Cited upstream: Chapman, M. S., Hammond, T. D., Lenef, A., Schmiedmayer, J. and Pritchard, D. E., 'Photon Scattering from Atoms in an Atom Interferometer: Recoil Patterns', Physical Review Letters volume 75, pages 3783-3787, 1995 (doi 10.1103/PhysRevLett.75.3783). WHAT THIS MODULE OWNS: Chapman et al. 1995 atom-interferometer vocabulary and source-grounded categorical which-path distinguishability observations. Path ontology is underdetermined — no asserted which-path labels." +// Cited upstream: Chapman, M. S., Hammond, T. D., Lenef, A., Schmiedmayer, J. and Pritchard, D. E., +// 'Photon Scattering from Atoms in an Atom Interferometer: Recoil Patterns', Physical Review +// Letters volume 75, pages 3783-3787, 1995 (doi 10.1103/PhysRevLett.75.3783). WHAT THIS MODULE +// OWNS: Chapman et al. 1995 atom-interferometer vocabulary and source-grounded categorical +// which-path distinguishability observations. Path ontology is underdetermined — no asserted +// which-path labels. -data distinguishable_record_axis_note: String = "Which-path axis is modeled as DistinguishableWhichPathRecordProduced | DistinguishableWhichPathRecordNotProduced — the physical distinguishability record, not an apparatus switch such as photon source on/off. A source on with no distinguishable record does not destroy coherence." +// Which-path axis is modeled as DistinguishableWhichPathRecordProduced | +// DistinguishableWhichPathRecordNotProduced — the physical distinguishability record, not an +// apparatus switch such as photon source on/off. A source on with no distinguishable record does +// not destroy coherence. data trial_versus_ensemble_grain_note: String = "Trial grain: one atom detection or one scattering event. Ensemble grain: interference fringe visibility over the accumulated pattern. Visibility observations in this module are ensemble grain only." diff --git a/dag/extdeps/pin.dag b/dag/extdeps/pin.dag index 40246edc4c2..1de9158cc8a 100644 --- a/dag/extdeps/pin.dag +++ b/dag/extdeps/pin.dag @@ -24,15 +24,127 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data pin_citation_note: String = "Cites the Reproducible Builds definition, whose stated relevant attributes of a build environment include 'dependencies and their versions' — which is exactly what this module models: a dependency named by an exact version identity plus the expected content identity of the artifact, so a build's inputs are recreatable rather than whatever the host happened to have. The authority is deliberately LANGUAGE-AGNOSTIC and SUBJECT-AGNOSTIC. Cargo.lock and flake.lock are named in pin_selection_note as PRECEDENTS for the discipline, which is the honest role for them: instances of the practice, not the authority over it. Precedent for a concept-level anchor in extdeps: extdeps.version cites en.wikipedia.org/wiki/Software_versioning rather than any one ecosystem's version scheme. Not en.wikipedia.org/wiki/Lock_file — that page is file locking for mutual exclusion, a homonym, not dependency pinning. WHY THIS MODULE AND ITS CliTool INSTANTIATION EACH SPELL THEIR OWN ANCHOR RATHER THAN ONE REFERENCING THE OTHER, carried forward from the pre-split carrier because it is MEASURED and would otherwise be rediscovered the hard way: review 44329 proposed referencing a sibling module anchor instead of re-spelling it, and that remedy is NOT AVAILABLE — src/v2/lens/mandatory_tag reads the anchor structurally from the Node tree, so an aliased declaration reds corpus_live_clean_tree_wall_holds. Two anchors here are therefore two distinct CITATIONS (this module cites the reproducible-builds discipline; extdeps.tools.pin cites the GNU command-line conventions that make a CliTool a distinct subject), not one fact spelled twice, and the structural read is why neither can be an alias of the other." - -data pin_cited_symbol_verification_note: String = "review 44920, and the discipline it forced, recorded because the defect recurred three times in one PR and prose is where it hides. THE DEFECT: this carrier's notes instruct a future P2 worker to call a specific function, and TWO of the names asserted were wrong — observed_tool_identity_for, which never existed (I invented the _for suffix without reading the declaration), and observe_tool, which #7444 renamed to observe_tool_identity. Both were fabricated in the strict sense DESIGN section 5 means: a described surface never checked against the surface. It is worse than a typo because the sentence is an INSTRUCTION — a worker following it would grep, find nothing, and either guess or re-derive a digest, which is precisely the section 3 fork these notes exist to prevent. THE DISCIPLINE, applied to every symbol in this module rather than only the two reported: each cited name was grep-verified against dag/ and src/ to have a real declaration. ObservedToolIdentity, ObservedToolIdentityFound, ObservedToolIdentityMissing, ObservedToolIdentityDuplicate, observed_tool_identities, observed_tool_identity, toolchain_identity and ResolvedBuildContext all resolve to dag/extdeps/realization/emit_on_demand_host.dag; membership_reconcile to dag/gunbc/membership_reconcile.dag:59; corpus_live_clean_tree_wall_holds to dag/gunbc/instruments/extdeps_external_authority_transport.dag; observe_tool_identity to src/v1/stage0/src/v1_interpreter.rs:8672. THE DISCIPLINE WAS NOT ENOUGH, AND THAT IS THE POINT (review 45088): every name above resolves, but the LINE beside the last one did not — 8598 is inside emit_host_artifact_realization_digest, an unrelated artifact-store helper, and observe_tool_identity actually begins at 8672 with its digest folded at 8712-8719. The stale pointer was copied into gunbc.design_document as this row's evidence, so a name-level grep passed while the located claim was false in the canonical design authority. A line number is the one part of a citation that grep-verifying the SYMBOL cannot check and that ordinary edits above it silently invalidate, which is exactly why the remedy below is a resolver rather than more diligence. WHY THIS IS A LENS CANDIDATE AND NOT A HABIT: a note naming a symbol is an ordinary reference that the substrate could resolve, so a stale citation is DECIDABLE — it is the same Node-tree read the namespace authority already performs, applied to identifiers appearing inside note strings. Today nothing checks it, which is why three of them shipped. dissolve-on: feature:cited-symbol-resolution — a lens resolves identifiers cited in note strings against the containment tree and reds an unresolvable one; at that point the NAME half of this note deletes and that class becomes unwritable rather than reviewed. THE TRIGGER AS ORIGINALLY WRITTEN WOULD NOT HAVE CAUGHT review 45088, and saying otherwise would overclaim it: an identifier resolver checks names against the .dag containment tree, while the defect was a LINE pointer into a .rs file — outside that tree entirely, so no amount of identifier resolution reds it. The two halves are separate classes with separate remedies. dissolve-on: feature:cited-source-location-checked — a cross-language file:line citation is verified against the named file, either by resolving the symbol the line is claimed to inhabit or by deleting the line half so the SYMBOL is the whole citation. The second shape is the cheaper and likelier fix and it is worth naming as the default: a function name is stable under edits above it, a line number is not, so a citation that carries only the name is the one that stays true. This note deletes when both triggers have fired." - -data pin_dimension_note: String = "PINNING IS A DIMENSION, NOT A PROPERTY OF CLI TOOLS (operator, 2026-07-29). The first version of this model was a ToolPin record keyed by tool_name: NonEmptyStr, which was wrong three ways and the operator caught it by reading the shape. (1) It referred to a tool by NAME STRING while extdeps.tools already passes the modeled row by value everywhere else — resolve(tool: CliTool), ResolvedTool { tool: CliTool }, NotFound { tool: CliTool } — so ToolPin was the single site in that namespace joining to a modeled type through string equality, the anemic-leaf failure of DESIGN section 2 with tool identity then living in two places. (2) It fixed the GRAIN at CLI tools, when the corpus already contains other pinnable subjects that are not CliTool at all: extdeps.cache.sccache SccacheBinaryArtifact (release x published musl arch — NOT SccacheBinaryRelease, which carries two digests per row and is ambiguous at Pin grain), extdeps/github ActionRef, extdeps/docker and extdeps/container/docker_ce images, apt packages, GitHub releases. A type per pinnable thing is the same mistake as ten hand-rolled integer types before Compose> — N types where there is one axis. (3) Naming the type after its first consumer is how the grain got fixed too narrow, and the general shape was available from the start. So Pin is parameterized by its SUBJECT, following the Vendor precedent (dag/extdeps/vendor.dag): the subject is a type argument, the pin is one concept at every breadth. Pin, Pin, Pin, Pin are instantiations, not siblings." - -data pin_subject_must_not_be_self_identifying_note: String = "review 44850, and the reviewer found a real defect in the previous version of this proof. THE SOUNDNESS CONDITION ON THE DIMENSION, sharpened after review 44999 and deep-eagle-728 oracle msg_fac61885. Two subject cases, two carriers, one axis — duplication unwritable rather than forbidden-after-the-fact. (1) Subject does NOT carry content identity (CliTool, ActionRef): Pin with required version and expected_identity — the pin SUPPLIES identity the subject lacks; expected_identity is new information with one home. (2) Subject fully determines cited digest identity for its grain (SccacheBinaryArtifact — release x published musl arch, not the ambiguous SccacheBinaryRelease row): SubjectProjectedPin carries subject + selection only; identity and version are projected by the instantiation module and supplied at the reconcile bridge — never stored twice on the pin carrier. Pin copying descriptor.digest into expected_identity was case (2) wrongly modeled as case (1) — two independently writable fields for one fact. OCI descriptors with ContentHash on the subject remain dissolve-on feature:pin-subject-self-identity-lens targets; they must not be Pin instantiations. DECIDABILITY: 'subject declares ContentHash' is structural inspection; until the lens lands, review plus the SubjectProjectedPin split for digest-on-subject artifacts is the disposition. What is NOT claimed: that Pin literals are unwritable today — saying otherwise would be the section 5 trap of editing a declaration while the realization lies." - -data pin_authority_note: String = "The single authority for pinned dependency identity. Pin names an exact VersionIdentity selection and expected ContentHash — BOTH REQUIRED, never a location, never a range — so a digest-less or version-less Pin is unwritable rather than validated (DESIGN section 5 construction, not a post-check). Subjects that fully determine cited digest identity use SubjectProjectedPin instead; the instantiation bridge projects version and expected_identity explicitly onto Pin at the generic boundary (deep-eagle-728 oracle msg_fac61885) — Pin.version Absent is not representable. This module mints NO new digest concept: expected_identity is the same std.types.ContentHash that extdeps.realization.emit_on_demand_host produces on the OBSERVED side, where the per-tool authority is ResolvedBuildContext.observed_tool_identities and its rows are digested from logical name + executable bytes + version-probe output (#7388, per-tool rows named by #7444). Pin declares expected and that side observes actual, so this is one identity concept with two ends rather than a second authority. review 44920 caught an earlier version of this sentence naming toolchain_identity as the observed half. That is now WRONG in a way that would actively mislead: since #7444, toolchain_identity is a DERIVED FOLD over the per-tool rows, so a P2 worker reading only this note could wire a reconcile against the aggregate — the exact grain mismatch review 44388 raised and #7444 discharged. The observed half a pin compares against is a per-tool row, never the fold. It is NOT yet a reconcile, and this note deliberately does not say what the remaining gap IS: the grain relationship between the two ends, its history and its current status are owned entirely by pin_observed_grain_prerequisite_note below. review 44910 caught this row asserting the prerequisite still had to land while that note already recorded it discharged — two copies of one status in one module, which is the section 3 duplication this lane exists to remove, so the copy is deleted rather than re-synced. Re-syncing is what failed: the same defect was corrected twice on this PR before (review 44422, review 44875) by editing a second copy, and a fact with two homes will always drift again." +// Cites the Reproducible Builds definition, whose stated relevant attributes of a build environment +// include 'dependencies and their versions' — which is exactly what this module models: a +// dependency named by an exact version identity plus the expected content identity of the artifact, +// so a build's inputs are recreatable rather than whatever the host happened to have. The authority +// is deliberately LANGUAGE-AGNOSTIC and SUBJECT-AGNOSTIC. Cargo.lock and flake.lock are named in +// pin_selection_note as PRECEDENTS for the discipline, which is the honest role for them: instances +// of the practice, not the authority over it. Precedent for a concept-level anchor in extdeps: +// extdeps.version cites en.wikipedia.org/wiki/Software_versioning rather than any one ecosystem's +// version scheme. Not en.wikipedia.org/wiki/Lock_file — that page is file locking for mutual +// exclusion, a homonym, not dependency pinning. WHY THIS MODULE AND ITS CliTool INSTANTIATION EACH +// SPELL THEIR OWN ANCHOR RATHER THAN ONE REFERENCING THE OTHER, carried forward from the pre-split +// carrier because it is MEASURED and would otherwise be rediscovered the hard way: review 44329 +// proposed referencing a sibling module anchor instead of re-spelling it, and that remedy is NOT +// AVAILABLE — src/v2/lens/mandatory_tag reads the anchor structurally from the Node tree, so an +// aliased declaration reds corpus_live_clean_tree_wall_holds. Two anchors here are therefore two +// distinct CITATIONS (this module cites the reproducible-builds discipline; extdeps.tools.pin cites +// the GNU command-line conventions that make a CliTool a distinct subject), not one fact spelled +// twice, and the structural read is why neither can be an alias of the other. + +// review 44920, and the discipline it forced, recorded because the defect recurred three times in +// one PR and prose is where it hides. THE DEFECT: this carrier's notes instruct a future P2 worker +// to call a specific function, and TWO of the names asserted were wrong — +// observed_tool_identity_for, which never existed (I invented the _for suffix without reading the +// declaration), and observe_tool, which #7444 renamed to observe_tool_identity. Both were +// fabricated in the strict sense DESIGN section 5 means: a described surface never checked against +// the surface. It is worse than a typo because the sentence is an INSTRUCTION — a worker following +// it would grep, find nothing, and either guess or re-derive a digest, which is precisely the +// section 3 fork these notes exist to prevent. THE DISCIPLINE, applied to every symbol in this +// module rather than only the two reported: each cited name was grep-verified against dag/ and src/ +// to have a real declaration. ObservedToolIdentity, ObservedToolIdentityFound, +// ObservedToolIdentityMissing, ObservedToolIdentityDuplicate, observed_tool_identities, +// observed_tool_identity, toolchain_identity and ResolvedBuildContext all resolve to +// dag/extdeps/realization/emit_on_demand_host.dag; membership_reconcile to +// dag/gunbc/membership_reconcile.dag:59; corpus_live_clean_tree_wall_holds to +// dag/gunbc/instruments/extdeps_external_authority_transport.dag; observe_tool_identity to +// src/v1/stage0/src/v1_interpreter.rs:8672. THE DISCIPLINE WAS NOT ENOUGH, AND THAT IS THE POINT +// (review 45088): every name above resolves, but the LINE beside the last one did not — 8598 is +// inside emit_host_artifact_realization_digest, an unrelated artifact-store helper, and +// observe_tool_identity actually begins at 8672 with its digest folded at 8712-8719. The stale +// pointer was copied into gunbc.design_document as this row's evidence, so a name-level grep passed +// while the located claim was false in the canonical design authority. A line number is the one +// part of a citation that grep-verifying the SYMBOL cannot check and that ordinary edits above it +// silently invalidate, which is exactly why the remedy below is a resolver rather than more +// diligence. WHY THIS IS A LENS CANDIDATE AND NOT A HABIT: a note naming a symbol is an ordinary +// reference that the substrate could resolve, so a stale citation is DECIDABLE — it is the same +// Node-tree read the namespace authority already performs, applied to identifiers appearing inside +// note strings. Today nothing checks it, which is why three of them shipped. dissolve-on: +// feature:cited-symbol-resolution — a lens resolves identifiers cited in note strings against the +// containment tree and reds an unresolvable one; at that point the NAME half of this note deletes +// and that class becomes unwritable rather than reviewed. THE TRIGGER AS ORIGINALLY WRITTEN WOULD +// NOT HAVE CAUGHT review 45088, and saying otherwise would overclaim it: an identifier resolver +// checks names against the .dag containment tree, while the defect was a LINE pointer into a .rs +// file — outside that tree entirely, so no amount of identifier resolution reds it. The two halves +// are separate classes with separate remedies. dissolve-on: feature:cited-source-location-checked — +// a cross-language file:line citation is verified against the named file, either by resolving the +// symbol the line is claimed to inhabit or by deleting the line half so the SYMBOL is the whole +// citation. The second shape is the cheaper and likelier fix and it is worth naming as the default: +// a function name is stable under edits above it, a line number is not, so a citation that carries +// only the name is the one that stays true. This note deletes when both triggers have fired. + +// PINNING IS A DIMENSION, NOT A PROPERTY OF CLI TOOLS (operator, 2026-07-29). The first version of +// this model was a ToolPin record keyed by tool_name: NonEmptyStr, which was wrong three ways and +// the operator caught it by reading the shape. (1) It referred to a tool by NAME STRING while +// extdeps.tools already passes the modeled row by value everywhere else — resolve(tool: CliTool), +// ResolvedTool { tool: CliTool }, NotFound { tool: CliTool } — so ToolPin was the single site in +// that namespace joining to a modeled type through string equality, the anemic-leaf failure of +// DESIGN section 2 with tool identity then living in two places. (2) It fixed the GRAIN at CLI +// tools, when the corpus already contains other pinnable subjects that are not CliTool at all: +// extdeps.cache.sccache SccacheBinaryArtifact (release x published musl arch — NOT +// SccacheBinaryRelease, which carries two digests per row and is ambiguous at Pin grain), +// extdeps/github ActionRef, extdeps/docker and extdeps/container/docker_ce images, apt packages, +// GitHub releases. A type per pinnable thing is the same mistake as ten hand-rolled integer types +// before Compose> — N types where there is one axis. (3) Naming the type after +// its first consumer is how the grain got fixed too narrow, and the general shape was available +// from the start. So Pin is parameterized by its SUBJECT, following the Vendor precedent +// (dag/extdeps/vendor.dag): the subject is a type argument, the pin is one concept at every +// breadth. Pin, Pin, Pin, Pin are +// instantiations, not siblings. + +// review 44850, and the reviewer found a real defect in the previous version of this proof. THE +// SOUNDNESS CONDITION ON THE DIMENSION, sharpened after review 44999 and deep-eagle-728 oracle +// msg_fac61885. Two subject cases, two carriers, one axis — duplication unwritable rather than +// forbidden-after-the-fact. (1) Subject does NOT carry content identity (CliTool, ActionRef): +// Pin with required version and expected_identity — the pin SUPPLIES identity the subject +// lacks; expected_identity is new information with one home. (2) Subject fully determines cited +// digest identity for its grain (SccacheBinaryArtifact — release x published musl arch, not the +// ambiguous SccacheBinaryRelease row): SubjectProjectedPin carries subject + selection +// only; identity and version are projected by the instantiation module and supplied at the +// reconcile bridge — never stored twice on the pin carrier. Pin copying +// descriptor.digest into expected_identity was case (2) wrongly modeled as case (1) — two +// independently writable fields for one fact. OCI descriptors with ContentHash on the subject +// remain dissolve-on feature:pin-subject-self-identity-lens targets; they must not be Pin +// instantiations. DECIDABILITY: 'subject declares ContentHash' is structural inspection; until the +// lens lands, review plus the SubjectProjectedPin split for digest-on-subject artifacts is the +// disposition. What is NOT claimed: that Pin literals are unwritable today — saying +// otherwise would be the section 5 trap of editing a declaration while the realization lies. + +// The single authority for pinned dependency identity. Pin names an exact VersionIdentity +// selection and expected ContentHash — BOTH REQUIRED, never a location, never a range — so a +// digest-less or version-less Pin is unwritable rather than validated (DESIGN section 5 +// construction, not a post-check). Subjects that fully determine cited digest identity use +// SubjectProjectedPin instead; the instantiation bridge projects version and expected_identity +// explicitly onto Pin at the generic boundary (deep-eagle-728 oracle msg_fac61885) — Pin.version +// Absent is not representable. This module mints NO new digest concept: expected_identity is the +// same std.types.ContentHash that extdeps.realization.emit_on_demand_host produces on the OBSERVED +// side, where the per-tool authority is ResolvedBuildContext.observed_tool_identities and its rows +// are digested from logical name + executable bytes + version-probe output (#7388, per-tool rows +// named by #7444). Pin declares expected and that side observes actual, so this is one identity +// concept with two ends rather than a second authority. review 44920 caught an earlier version of +// this sentence naming toolchain_identity as the observed half. That is now WRONG in a way that +// would actively mislead: since #7444, toolchain_identity is a DERIVED FOLD over the per-tool rows, +// so a P2 worker reading only this note could wire a reconcile against the aggregate — the exact +// grain mismatch review 44388 raised and #7444 discharged. The observed half a pin compares against +// is a per-tool row, never the fold. It is NOT yet a reconcile, and this note deliberately does not +// say what the remaining gap IS: the grain relationship between the two ends, its history and its +// current status are owned entirely by pin_observed_grain_prerequisite_note below. review 44910 +// caught this row asserting the prerequisite still had to land while that note already recorded it +// discharged — two copies of one status in one module, which is the section 3 duplication this lane +// exists to remove, so the copy is deleted rather than re-synced. Re-syncing is what failed: the +// same defect was corrected twice on this PR before (review 44422, review 44875) by editing a +// second copy, and a fact with two homes will always drift again. data pin_observed_grain_prerequisite_note: String = "review 44388 raised this and #7444 DISCHARGED it; the row is kept as the record of what the grain argument was, because the reconcile still has to honour it. THE ORIGINAL FINDING: expected_identity is PER SUBJECT, while ResolvedBuildContext.toolchain_identity was an aggregate — seeded from a constant and hash_combined over every argv in build_argvs, plus a second observe_tool_identity call for rustc whenever the argv was cargo — so it moved when an unrelated tool in the same build moved and could not say WHICH dependency drifted. Comparing one Pin against it would have been a grain mismatch wearing the shape of a reconcile. WHAT LANDED (#7444, merged 2026-07-29, dag/extdeps/realization/emit_on_demand_host.dag): ResolvedBuildContext now carries observed_tool_identities: List as its first field, toolchain_identity is re-expressed as the derived FOLD over those rows rather than a stored aggregate, and the per-tool read is observed_tool_identity with three arms — ObservedToolIdentityFound, ObservedToolIdentityMissing { tool_name }, ObservedToolIdentityDuplicate { tool_name, count }. The miss and the duplicate are DISTINCT typed answers, not one Option, which is what makes the lookup usable at a fail-closed boundary. So the two ends now meet at the same grain and no second per-subject digest was minted beside observe_tool_identity — the section 3 fork this lane exists to remove was avoided. WHAT IS STILL NOT WRITTEN: the reconcile itself. This carrier declares expected and #7444 observes actual; the gunbc.membership_reconcile instantiation that relates them, with a bundle whose value_eq is pin_value_eq and whose key_of is the per-subject projection, is P2 and belongs to no row here. A consumer relating the two MUST route through observed_tool_identity and answer its Missing and Duplicate arms explicitly — collapsing either into a match failure or a widened rerun would be the absorbing fallback DESIGN section 5 forbids, and would waste exactly the precision #7444 bought." @@ -54,7 +166,18 @@ type Pin { selection: PinSelection } -data subject_projected_pin_note: String = "When a pin subject fully determines cited digest identity for its grain, Pin would duplicate one fact in independently writable fields — the OciDescriptor failure class and review 44999 on SccacheBinaryArtifact. SubjectProjectedPin carries subject + selection only; identity and version are projected by the instantiation module and supplied at the reconcile bridge — never stored twice (construction, deep-eagle-728 oracle msg_36c1fcf1). admit_subject_projected_pin_integrity and subject_projected_pin_value_eq live HERE as the single admission and value_eq authority for every SubjectProjectedPin instantiation; instantiation modules own projection fns (content_identity, pin_version, key_of) only. P2 membership_reconcile bridges via subject_projected_pin_as_pin(pin, expected_identity, version) — both fields passed explicitly, symmetric with identity projection, so Pin.version Absent is never representable (msg_fac61885 regression fix). Pin over subjects that declare ContentHash remain dissolve-on feature:pin-subject-self-identity-lens targets." +// When a pin subject fully determines cited digest identity for its grain, Pin would +// duplicate one fact in independently writable fields — the OciDescriptor failure class and review +// 44999 on SccacheBinaryArtifact. SubjectProjectedPin carries subject + selection only; identity +// and version are projected by the instantiation module and supplied at the reconcile bridge — +// never stored twice (construction, deep-eagle-728 oracle msg_36c1fcf1). +// admit_subject_projected_pin_integrity and subject_projected_pin_value_eq live HERE as the single +// admission and value_eq authority for every SubjectProjectedPin instantiation; instantiation +// modules own projection fns (content_identity, pin_version, key_of) only. P2 membership_reconcile +// bridges via subject_projected_pin_as_pin(pin, expected_identity, version) — both fields passed +// explicitly, symmetric with identity projection, so Pin.version Absent is never representable +// (msg_fac61885 regression fix). Pin over subjects that declare ContentHash remain +// dissolve-on feature:pin-subject-self-identity-lens targets. // A MOVING REFERENCE IS NOT A PIN SUBJECT, and this is the carrier for the case Pin and // SubjectProjectedPin both refuse. Pin supplies an identity the subject lacks and requires an exact @@ -170,7 +293,19 @@ fn subject_projected_pin_value_eq( (a.subject == b.subject) && (a.selection == b.selection) } -data integrity_versus_currency_note: String = "Two questions were once fused under the word 'fresh', and separating them is load-bearing (review 44274). INTEGRITY asks: is the artifact in front of us the one this pin declares? That is decidable NOW — compare the observed digest to expected_identity — and it is what admission establishes. CURRENCY asks: is the declared version still the one we should be running? That is NOT decidable here for EITHER variant, and an earlier shape fabricated a positive answer for one of them: ExactPin returned PinFresh unconditionally. Authorship proves reproducibility, not currency — an exact pin authored three years ago is exactly as reproducible and exactly as rotten, and reporting it fresh forever is the 'pinned and never updated' failure this lane's refresh axis exists to prevent (operator's framing when the axis was requested: 'some way to avoid pinning and never updating'). So no arm anywhere returns a positive currency verdict: pin_currency_gap is TOTAL and always names a gap, and the word 'fresh' does not appear in this module. A consumer that needs currency reads that gap and refuses; it cannot be handed a green it did not earn." +// Two questions were once fused under the word 'fresh', and separating them is load-bearing (review +// 44274). INTEGRITY asks: is the artifact in front of us the one this pin declares? That is +// decidable NOW — compare the observed digest to expected_identity — and it is what admission +// establishes. CURRENCY asks: is the declared version still the one we should be running? That is +// NOT decidable here for EITHER variant, and an earlier shape fabricated a positive answer for one +// of them: ExactPin returned PinFresh unconditionally. Authorship proves reproducibility, not +// currency — an exact pin authored three years ago is exactly as reproducible and exactly as +// rotten, and reporting it fresh forever is the 'pinned and never updated' failure this lane's +// refresh axis exists to prevent (operator's framing when the axis was requested: 'some way to +// avoid pinning and never updating'). So no arm anywhere returns a positive currency verdict: +// pin_currency_gap is TOTAL and always names a gap, and the word 'fresh' does not appear in this +// module. A consumer that needs currency reads that gap and refuses; it cannot be handed a green it +// did not earn. type CurrencyGap = AuthoredPinHasNoReviewDate @@ -184,7 +319,21 @@ fn pin_currency_gap(pin: Pin) -> CurrencyGap { } } -data pin_refusal_cause_note: String = "Every cause is SUBJECT-AGNOSTIC, which is why they live on the generic carrier rather than beside any one instantiation. ConstraintIsNotIdentity and PinAbsent are the two ingest deficits — a range is not an identity, and no version stated at all is a DIFFERENT deficit from a stated range, so collapsing them would be a state-space conflation. TrackedResolutionUnrecorded and ObservedIdentityMismatch are the two admission refusals on Pin. CitedDigestIdentityUnprojectable is the SubjectProjectedPin admission refusal when the subject's cited digest cannot be bridged to ContentHash (review 45470 — sha256_digest_content_hash Absent must refuse, never absorb a hardcoded fallback). CrossFamilyIdentityIncomparable is the sixth: expected and observed are both real identities but sit in different hash families, so no comparison is defined and answering `false` would be a fabricated mismatch rather than a refusal. All six name their deficit and are countable, so a migration frontier burns down visibly instead of being silently assumed complete. Subjects that fully determine cited digest identity use SubjectProjectedPin instead (no second writable expected_identity field); review 44999 and deep-eagle-728 oracle msg_36c1fcf1 — a PinRestatesSubjectMismatch validation arm had no live producer for shipped CliTool/ActionRef subjects and would have been coverage-by-illusion." +// Every cause is SUBJECT-AGNOSTIC, which is why they live on the generic carrier rather than beside +// any one instantiation. ConstraintIsNotIdentity and PinAbsent are the two ingest deficits — a +// range is not an identity, and no version stated at all is a DIFFERENT deficit from a stated +// range, so collapsing them would be a state-space conflation. TrackedResolutionUnrecorded and +// ObservedIdentityMismatch are the two admission refusals on Pin. +// CitedDigestIdentityUnprojectable is the SubjectProjectedPin admission refusal when the subject's +// cited digest cannot be bridged to ContentHash (review 45470 — sha256_digest_content_hash Absent +// must refuse, never absorb a hardcoded fallback). CrossFamilyIdentityIncomparable is the sixth: +// expected and observed are both real identities but sit in different hash families, so no +// comparison is defined and answering `false` would be a fabricated mismatch rather than a refusal. +// All six name their deficit and are countable, so a migration frontier burns down visibly instead +// of being silently assumed complete. Subjects that fully determine cited digest identity use +// SubjectProjectedPin instead (no second writable expected_identity field); review 44999 and +// deep-eagle-728 oracle msg_36c1fcf1 — a PinRestatesSubjectMismatch validation arm had no live +// producer for shipped CliTool/ActionRef subjects and would have been coverage-by-illusion. type PinRefusalCause = ConstraintIsNotIdentity { constraint: VersionConstraint } @@ -199,7 +348,16 @@ type PinAdmission = PinIntegrityAdmitted { pin: Pin } | PinRefused { subject: Subject, cause: PinRefusalCause } -data pin_integrity_admission_note: String = "Establishes INTEGRITY ONLY, and is named so it cannot be misread as currency (review 44274). PinIntegrityAdmitted asserts exactly one thing: the observed artifact is the one this pin declares. It asserts NOTHING about whether the declared version is still the right one — that is pin_currency_gap, which is total and always names a gap, so currency can never be silently inherited from an integrity pass. A TrackedChannel pin refuses integrity admission outright (review 44242): its expected_identity was resolved from a moving reference and no record of that resolution is carried, so there is no evidence the stored digest corresponds to any decision anyone made. ExactPin proceeds to the digest comparison, which is the real, decidable check. Both arms refuse rather than widen; neither substitutes a superset, silently re-resolves, nor proceeds on mismatch." +// Establishes INTEGRITY ONLY, and is named so it cannot be misread as currency (review 44274). +// PinIntegrityAdmitted asserts exactly one thing: the observed artifact is the one this pin +// declares. It asserts NOTHING about whether the declared version is still the right one — that is +// pin_currency_gap, which is total and always names a gap, so currency can never be silently +// inherited from an integrity pass. A TrackedChannel pin refuses integrity admission outright +// (review 44242): its expected_identity was resolved from a moving reference and no record of that +// resolution is carried, so there is no evidence the stored digest corresponds to any decision +// anyone made. ExactPin proceeds to the digest comparison, which is the real, decidable check. Both +// arms refuse rather than widen; neither substitutes a superset, silently re-resolves, nor proceeds +// on mismatch. fn admit_pin_integrity( pin: Pin, @@ -235,7 +393,23 @@ fn admit_pin_integrity( } } -data pin_value_eq_note: String = "The bundle member the reconcile hands to gunbc.membership_reconcile as value_eq, and the reason it does NOT compare the subject. Identity-vs-content split per that spine's authority note: the SUBJECT is the stable identity and therefore the key_of, so a version, digest, or selection change is a Modified hunk and re-upserts — an ensure that reinstalls in place — rather than a Removed plus Added pair that would tear the dependency down and reinstall it. value_eq is therefore presence-plus-content over every field that changes what gets installed or when it is re-resolved: the pinned version, the expected identity, AND the selection policy. Comparing the subject here would be redundant with the key and would make every hunk a no-op. Selection is content, not metadata: flipping ExactPin to TrackedChannel, or widening a refresh window, changes the ensure behaviour with no version or digest movement, so omitting it would emit no hunk for a real drift (review 44329). expected_identity comparison routes through std.content_hash.compare_content_hash (review 45407) — the canonical typed outcome, not a consumer-local coproduct predicate. Selection is compared with the substrate's structural equality on the coproduct rather than a hand-written per-variant predicate — the closed vocabulary already decides it, so a predicate would be a second representation of a decision the substrate makes (review 44341). key_of is NOT declared here: projecting an identity out of a Subject is subject-specific, so it belongs with each instantiation, not on the generic carrier." +// The bundle member the reconcile hands to gunbc.membership_reconcile as value_eq, and the reason +// it does NOT compare the subject. Identity-vs-content split per that spine's authority note: the +// SUBJECT is the stable identity and therefore the key_of, so a version, digest, or selection +// change is a Modified hunk and re-upserts — an ensure that reinstalls in place — rather than a +// Removed plus Added pair that would tear the dependency down and reinstall it. value_eq is +// therefore presence-plus-content over every field that changes what gets installed or when it is +// re-resolved: the pinned version, the expected identity, AND the selection policy. Comparing the +// subject here would be redundant with the key and would make every hunk a no-op. Selection is +// content, not metadata: flipping ExactPin to TrackedChannel, or widening a refresh window, changes +// the ensure behaviour with no version or digest movement, so omitting it would emit no hunk for a +// real drift (review 44329). expected_identity comparison routes through +// std.content_hash.compare_content_hash (review 45407) — the canonical typed outcome, not a +// consumer-local coproduct predicate. Selection is compared with the substrate's structural +// equality on the coproduct rather than a hand-written per-variant predicate — the closed +// vocabulary already decides it, so a predicate would be a second representation of a decision the +// substrate makes (review 44341). key_of is NOT declared here: projecting an identity out of a +// Subject is subject-specific, so it belongs with each instantiation, not on the generic carrier. fn pin_value_eq(a: Pin, b: Pin) -> Bool { (a.version == b.version) diff --git a/dag/extdeps/posix/rusage.dag b/dag/extdeps/posix/rusage.dag index c6358ad222a..c360af053d0 100644 --- a/dag/extdeps/posix/rusage.dag +++ b/dag/extdeps/posix/rusage.dag @@ -13,7 +13,26 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data posix_rusage_subject_note: String = "getrusage(2) AS THE PORTABLE RESOURCE-USAGE INTERFACE, modeled because the repository was reading a Linux-only file for a fact POSIX already exposes everywhere. Peak resident set size was taken from /proc/self/status VmHWM, which exists on exactly one kernel family; getrusage with RUSAGE_SELF returns the same quantity in ru_maxrss on every conforming implementation, and the corpus's own v1 seed already depends on libc, so the portable call needs no new dependency.\n\nTHE SECTION 3 POINT THIS CORRECTS, because it is the general lesson rather than a macOS detail: reading VmHWM through procfs is a TRANSPORT, one of N, not a fact about the observation. Treating the transport's platform limits as the observation's limits is how 'peak RSS is unavailable on macOS' came to be believed — it is not, it was simply never asked for the portable way. The right shape is not two transports selected by platform either; it is ONE portable call, with the platform divergence pushed down to the single axis where it genuinely exists.\n\nTHAT AXIS IS THE UNIT, and it is a specification-level fact rather than an implementation quirk: POSIX defines ru_maxrss's meaning and DOES NOT SPECIFY ITS UNIT. Implementations therefore differ legitimately and permanently — no future release reconciles them — which is why RusageMaxrssUnit is modeled here as an open question this module refuses to answer, and answered instead by extdeps.linux.rusage and extdeps.darwin.rusage each speaking for their own implementation. A conversion that assumed either unit would be wrong on half the fleet by a factor of 1024, silently, in the direction that looks plausible." +// getrusage(2) AS THE PORTABLE RESOURCE-USAGE INTERFACE, modeled because the repository was reading +// a Linux-only file for a fact POSIX already exposes everywhere. Peak resident set size was taken +// from /proc/self/status VmHWM, which exists on exactly one kernel family; getrusage with +// RUSAGE_SELF returns the same quantity in ru_maxrss on every conforming implementation, and the +// corpus's own v1 seed already depends on libc, so the portable call needs no new dependency. +// +// THE SECTION 3 POINT THIS CORRECTS, because it is the general lesson rather than a macOS detail: +// reading VmHWM through procfs is a TRANSPORT, one of N, not a fact about the observation. Treating +// the transport's platform limits as the observation's limits is how 'peak RSS is unavailable on +// macOS' came to be believed — it is not, it was simply never asked for the portable way. The right +// shape is not two transports selected by platform either; it is ONE portable call, with the +// platform divergence pushed down to the single axis where it genuinely exists. +// +// THAT AXIS IS THE UNIT, and it is a specification-level fact rather than an implementation quirk: +// POSIX defines ru_maxrss's meaning and DOES NOT SPECIFY ITS UNIT. Implementations therefore differ +// legitimately and permanently — no future release reconciles them — which is why RusageMaxrssUnit +// is modeled here as an open question this module refuses to answer, and answered instead by +// extdeps.linux.rusage and extdeps.darwin.rusage each speaking for their own implementation. A +// conversion that assumed either unit would be wrong on half the fleet by a factor of 1024, +// silently, in the direction that looks plausible. type RusageWho = RusageSelf diff --git a/dag/extdeps/posix/shell_command_language.dag b/dag/extdeps/posix/shell_command_language.dag index 896a9812feb..25cf3c7255f 100644 --- a/dag/extdeps/posix/shell_command_language.dag +++ b/dag/extdeps/posix/shell_command_language.dag @@ -5,7 +5,72 @@ import std.decl_ref { DeclarationRef, WholeDeclaration } import extdeps.external_authority { ExternalAuthority, ExternalModelScope, ExternalSubjectRef } import extdeps.uri { Uri, Https } -data posix_authority_duplication_note: String = "THE LOCATOR IS DECLARED IN FIVE MODULES AND THE CONSOLIDATION WAS ATTEMPTED, EXECUTED, AND REVERTED — a measured incompatibility, not an unexamined duplication (cursor review 48163 and the CI receipt that refuted its fix, 2026-08-04). The review is CORRECT that this locator appears verbatim in extdeps.shell, extdeps.shell.exec, extdeps.transports.shell, extdeps.process.posix_exit and here, and correct that consume-never-fork is the governing rule. The consolidation was built the right way round — this subject module as the authority, the four consumers aliasing it, so a transport module never owns the specification citation its own subject module consumes — and it reduced the corpus from five copies to one. IT ALSO TURNED v2.lens.mandatory_tag's corpus_live_clean_tree_wall_holds RED, measured by execution rather than predicted: the wall projects each extdeps module's extdeps_external_authority_anchor and reads a scheme identity off it, and that projection resolves a LITERAL construction, not an alias to one. So a module whose anchor is an alias projects no readable scheme and counts as a violation. THIS IS THE THIRD INDEPENDENT REDISCOVERY OF A MEASURED CONSTRAINT THE CORPUS ALREADY RECORDS, and citing it is the point of this sentence (extdeps.pin pin_citation_note; fierce-heron-301 supplied the pointer, 2026-08-04). That note says the same thing from reviews 44329 and 44866: a review proposed referencing a sibling module's anchor instead of re-spelling it, the remedy is NOT AVAILABLE because src/v2/lens/mandatory_tag reads the anchor structurally from the Node tree, and an aliased declaration reds corpus_live_clean_tree_wall_holds. It even says it is carried forward because the constraint is measured and would otherwise be rediscovered the hard way. It was, here, by turning CI red. A constraint that is documented in a carrier note but enforced nowhere gets rediscovered once per lane that trips it, and three rediscoveries is the argument for resolving it rather than documenting it a fourth time. THE FINDING, which is worth more than the cleanup: the mandatory-tag corpus scan STRUCTURALLY REQUIRES the duplication section 3 forbids. As mechanized today, 'every extdeps module declares its own anchor' is satisfiable only by re-declaring the literal per module, so any two modules governed by one upstream must copy its URI. That is a real conflict between two live rules and it is NOT mine to resolve inside a review round: it turns on whether the anchor is a CITATION VALUE (one authority, shared) or a PER-MODULE DECLARATION of which upstream governs this module (inherently repeated, and then five copies are five different facts that share a string). Escalated; the wall stays green meanwhile. THE CORPUS ALREADY LEANS ONE WAY, and it would be dishonest to escalate as though the question were open at both ends: pin_citation_note resolves its own instance toward PER-MODULE CITATION — its two anchors are 'two distinct CITATIONS, not one fact spelled twice' — A SECOND CORROBORATING INSTANCE WAS CLAIMED HERE AND IS WITHDRAWN (2026-08-05, retracted by its own author). This row previously cited fierce-heron-301's SPICE and ngspice carriers as reaching the same reading independently, on the grounds that two modules spelling one locator represented a dialect citation and a simulator's own subject anchor — genuinely different facts about different subjects. That is FALSE, and the reason is this file's own rule violated one lane over: the SPICE carrier was listing the ngspice manual as a further_citation while that same locator is the ngspice subject module's authority anchor, which is exactly the independently-governed-subject-as-citation error bash_implementation_versus_specification_note forbids. Review 48361 showed that the SPICE occurrence was itself invalid; gunbc#7803 removes that occurrence rather than defending it, so the pair cannot corroborate per-module duplication. THE TENSE HERE IS DELIBERATE AND WAS CORRECTED (operator review, 2026-08-05): an earlier revision of this sentence read 'the citation was deleted rather than defended, and the locator now appears once', which is a PRESENT-TENSE claim about a state that exists only on an OPEN BRANCH — not in this tree and not on main. That is the same stale-authority class this whole note is about, committed by the retraction correcting it: a carrier that asserts an unlanded branch's state as current is false the moment that branch is abandoned or rewritten, and nothing in this file would ever detect it. What is durable is the EVIDENTIARY fact — the review found the occurrence invalid and it was not defended — so that is what the sentence now carries, with the removal attributed to the pull request that performs it rather than narrated as an accomplished fact of the corpus. So the pair never spelled one locator for a good reason; it spelled it for a bad one. The corroboration is retracted and the per-module-citation reading rests on pin_citation_note alone. THE POSIX CASE IS THE WEAKEST ONE FOR THAT READING and that is exactly why it is the one worth deciding: all five modules cite this document for the SAME reason — shell command semantics — so unlike pin's two anchors, there is no second fact to point at. The SPICE/ngspice pair is deliberately NOT named in that contrast, because the retraction above moved it to the other side of the argument: it was withdrawn precisely for failing to be a second fact, so citing it here as a case where a second fact existed would re-assert on one line what the same row retracts on another. If per-module citation is the rule, then cursor review 48163's finding was a false positive on a text coincidence and the five copies are correct. If it is not, the projection has to resolve aliases before any of them can be consolidated. DISSOLVE-ON: the corpus-scan projection resolves an aliased anchor to its literal, or the anchor's role is settled as per-module declaration and this note records that instead. Reverting was the only landing state that did not either ship a red wall or leave the consolidation half-applied — the four modules keep their literals and so does this one." +// THE LOCATOR IS DECLARED IN FIVE MODULES AND THE CONSOLIDATION WAS ATTEMPTED, EXECUTED, AND +// REVERTED — a measured incompatibility, not an unexamined duplication (cursor review 48163 and the +// CI receipt that refuted its fix, 2026-08-04). The review is CORRECT that this locator appears +// verbatim in extdeps.shell, extdeps.shell.exec, extdeps.transports.shell, +// extdeps.process.posix_exit and here, and correct that consume-never-fork is the governing rule. +// The consolidation was built the right way round — this subject module as the authority, the four +// consumers aliasing it, so a transport module never owns the specification citation its own +// subject module consumes — and it reduced the corpus from five copies to one. IT ALSO TURNED +// v2.lens.mandatory_tag's corpus_live_clean_tree_wall_holds RED, measured by execution rather than +// predicted: the wall projects each extdeps module's extdeps_external_authority_anchor and reads a +// scheme identity off it, and that projection resolves a LITERAL construction, not an alias to one. +// So a module whose anchor is an alias projects no readable scheme and counts as a violation. THIS +// IS THE THIRD INDEPENDENT REDISCOVERY OF A MEASURED CONSTRAINT THE CORPUS ALREADY RECORDS, and +// citing it is the point of this sentence (extdeps.pin pin_citation_note; fierce-heron-301 supplied +// the pointer, 2026-08-04). That note says the same thing from reviews 44329 and 44866: a review +// proposed referencing a sibling module's anchor instead of re-spelling it, the remedy is NOT +// AVAILABLE because src/v2/lens/mandatory_tag reads the anchor structurally from the Node tree, and +// an aliased declaration reds corpus_live_clean_tree_wall_holds. It even says it is carried forward +// because the constraint is measured and would otherwise be rediscovered the hard way. It was, +// here, by turning CI red. A constraint that is documented in a carrier note but enforced nowhere +// gets rediscovered once per lane that trips it, and three rediscoveries is the argument for +// resolving it rather than documenting it a fourth time. THE FINDING, which is worth more than the +// cleanup: the mandatory-tag corpus scan STRUCTURALLY REQUIRES the duplication section 3 forbids. +// As mechanized today, 'every extdeps module declares its own anchor' is satisfiable only by +// re-declaring the literal per module, so any two modules governed by one upstream must copy its +// URI. That is a real conflict between two live rules and it is NOT mine to resolve inside a review +// round: it turns on whether the anchor is a CITATION VALUE (one authority, shared) or a PER-MODULE +// DECLARATION of which upstream governs this module (inherently repeated, and then five copies are +// five different facts that share a string). Escalated; the wall stays green meanwhile. THE CORPUS +// ALREADY LEANS ONE WAY, and it would be dishonest to escalate as though the question were open at +// both ends: pin_citation_note resolves its own instance toward PER-MODULE CITATION — its two +// anchors are 'two distinct CITATIONS, not one fact spelled twice' — A SECOND CORROBORATING +// INSTANCE WAS CLAIMED HERE AND IS WITHDRAWN (2026-08-05, retracted by its own author). This row +// previously cited fierce-heron-301's SPICE and ngspice carriers as reaching the same reading +// independently, on the grounds that two modules spelling one locator represented a dialect +// citation and a simulator's own subject anchor — genuinely different facts about different +// subjects. That is FALSE, and the reason is this file's own rule violated one lane over: the SPICE +// carrier was listing the ngspice manual as a further_citation while that same locator is the +// ngspice subject module's authority anchor, which is exactly the +// independently-governed-subject-as-citation error bash_implementation_versus_specification_note +// forbids. Review 48361 showed that the SPICE occurrence was itself invalid; gunbc#7803 removes +// that occurrence rather than defending it, so the pair cannot corroborate per-module duplication. +// THE TENSE HERE IS DELIBERATE AND WAS CORRECTED (operator review, 2026-08-05): an earlier revision +// of this sentence read 'the citation was deleted rather than defended, and the locator now appears +// once', which is a PRESENT-TENSE claim about a state that exists only on an OPEN BRANCH — not in +// this tree and not on main. That is the same stale-authority class this whole note is about, +// committed by the retraction correcting it: a carrier that asserts an unlanded branch's state as +// current is false the moment that branch is abandoned or rewritten, and nothing in this file would +// ever detect it. What is durable is the EVIDENTIARY fact — the review found the occurrence invalid +// and it was not defended — so that is what the sentence now carries, with the removal attributed +// to the pull request that performs it rather than narrated as an accomplished fact of the corpus. +// So the pair never spelled one locator for a good reason; it spelled it for a bad one. The +// corroboration is retracted and the per-module-citation reading rests on pin_citation_note alone. +// THE POSIX CASE IS THE WEAKEST ONE FOR THAT READING and that is exactly why it is the one worth +// deciding: all five modules cite this document for the SAME reason — shell command semantics — so +// unlike pin's two anchors, there is no second fact to point at. The SPICE/ngspice pair is +// deliberately NOT named in that contrast, because the retraction above moved it to the other side +// of the argument: it was withdrawn precisely for failing to be a second fact, so citing it here as +// a case where a second fact existed would re-assert on one line what the same row retracts on +// another. If per-module citation is the rule, then cursor review 48163's finding was a false +// positive on a text coincidence and the five copies are correct. If it is not, the projection has +// to resolve aliases before any of them can be consolidated. DISSOLVE-ON: the corpus-scan +// projection resolves an aliased anchor to its literal, or the anchor's role is settled as +// per-module declaration and this note records that instead. Reverting was the only landing state +// that did not either ship a red wall or leave the consolidation half-applied — the four modules +// keep their literals and so does this one. data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { uri: Uri { @@ -14,7 +79,22 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data posix_shell_subject_note: String = "THE POSIX SHELL COMMAND LANGUAGE, as its own subject rather than as a citation of GNU Bash (review relayed through the operator, 2026-08-04). It was previously stored as a further_citation on the GNU Bash ExternalModelScope, while the Bash carrier's own prose said the two were independently governed entities — so the file asserted the decomposition and then violated it one declaration later. DESIGN section 3's external-upstream decomposition rules on exactly this: multiple citations may ground ONE subject, but a second INDEPENDENTLY GOVERNED subject is not a citation of the first. The Open Group governs this specification; the GNU project governs Bash; neither versions with the other, and a change to one must not require editing the other's module — the Chromium/Safari test from that section. WHAT THIS SUBJECT IS: the Shell Command Language chapter of IEEE Std 1003.1-2017 (POSIX.1-2017), the shell syntax and semantics an implementation may conform to. It is NOT registered as a language target today and this module makes no such claim; it exists so that Bash's conformance can be stated as a relation between two named subjects instead of being smuggled into one scope. If a POSIX-shell target is ever claimed in its own right — a real possibility, since extdeps.shell.exec already routes shell.Exec.Check through 'sh -c' rather than through bash — it registers against THIS declaration and needs no new grounding." +// THE POSIX SHELL COMMAND LANGUAGE, as its own subject rather than as a citation of GNU Bash +// (review relayed through the operator, 2026-08-04). It was previously stored as a further_citation +// on the GNU Bash ExternalModelScope, while the Bash carrier's own prose said the two were +// independently governed entities — so the file asserted the decomposition and then violated it one +// declaration later. DESIGN section 3's external-upstream decomposition rules on exactly this: +// multiple citations may ground ONE subject, but a second INDEPENDENTLY GOVERNED subject is not a +// citation of the first. The Open Group governs this specification; the GNU project governs Bash; +// neither versions with the other, and a change to one must not require editing the other's module +// — the Chromium/Safari test from that section. WHAT THIS SUBJECT IS: the Shell Command Language +// chapter of IEEE Std 1003.1-2017 (POSIX.1-2017), the shell syntax and semantics an implementation +// may conform to. It is NOT registered as a language target today and this module makes no such +// claim; it exists so that Bash's conformance can be stated as a relation between two named +// subjects instead of being smuggled into one scope. If a POSIX-shell target is ever claimed in its +// own right — a real possibility, since extdeps.shell.exec already routes shell.Exec.Check through +// 'sh -c' rather than through bash — it registers against THIS declaration and needs no new +// grounding. type PosixShellCommandLanguage = | PosixShellCommandLanguageSpecification @@ -35,7 +115,17 @@ data posix_shell_model_scope: ExternalModelScope = ExternalModelScope { data extdeps_model_scope: ExternalModelScope = posix_shell_model_scope -data posix_single_quote_citation_note: String = "IEEE 1003.1-2017 section 2.2.2 (Single-Quotes): 'A single-quote shall preserve the literal value of each character within the single-quotes. A single-quote cannot occur within single-quotes.' That sentence is a complete correctness proof for posix_single_quote's encoding -- wrap the word in single quotes, and represent each embedded single quote as the four characters quote-backslash-quote-quote (close the quoting, emit an escaped literal quote via backslash, reopen the quoting) -- for every byte sequence, with no metacharacter table and no word-expansion model. This function moved here from v2.extdeps.languages.bash bash_single_quote (operator finding, 2026-08-19): the encoding is a POSIX shell command language fact, not a bash-specific one -- GNU Bash and gunbc.remote_shell_command both cite it for the same reason, so the rule has one home matching its one cited authority, and a bash-specific change to Bash's own emitter can no longer silently invalidate a POSIX claim made outside that module." +// IEEE 1003.1-2017 section 2.2.2 (Single-Quotes): 'A single-quote shall preserve the literal value +// of each character within the single-quotes. A single-quote cannot occur within single-quotes.' +// That sentence is a complete correctness proof for posix_single_quote's encoding -- wrap the word +// in single quotes, and represent each embedded single quote as the four characters +// quote-backslash-quote-quote (close the quoting, emit an escaped literal quote via backslash, +// reopen the quoting) -- for every byte sequence, with no metacharacter table and no word-expansion +// model. This function moved here from v2.extdeps.languages.bash bash_single_quote (operator +// finding, 2026-08-19): the encoding is a POSIX shell command language fact, not a bash-specific +// one -- GNU Bash and gunbc.remote_shell_command both cite it for the same reason, so the rule has +// one home matching its one cited authority, and a bash-specific change to Bash's own emitter can +// no longer silently invalidate a POSIX claim made outside that module. fn posix_single_quote(spelling: String) -> String { concat("'", concat(replace(spelling, "'", "'\\''"), "'")) diff --git a/dag/extdeps/power/ti_tps3808.dag b/dag/extdeps/power/ti_tps3808.dag index e856f0f0056..5df797e9900 100644 --- a/dag/extdeps/power/ti_tps3808.dag +++ b/dag/extdeps/power/ti_tps3808.dag @@ -24,7 +24,17 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data ti_tps3808_note: String = "THIS ROW IS A MECHANICAL CANDIDATE AND NOT A SELECTED PART. Nothing here says the device suits any board role; a selection is a functional-compatibility join done at the product layer, and this row supplies only the package authority that join consumes. WHAT THIS MODULE CARRIES AND WHAT IT DOES NOT. The TPS3808 is a supply voltage supervisor, and this row carries NONE of that: no threshold, no delay, no supply range. It carries the part's mechanical facts — the package body dimensions and the vendor's recommended land pattern — because those are the facts that were read from the document. A row naming a supervisor while carrying no supervision fact would be thin if it claimed to be a device model; it does not. It is the answer to one question, which is what a board has to etch to mount this part, and a later reader adding the electrical facts is adding to a cited row rather than correcting an invented one. THE PITCH IS AN ARITHMETIC READING OF THE DRAWING, and is recorded as such rather than taken from a family memory: the body drawing prints 1.9 across two pitches and the land pattern prints 0.95 twice, and 2 x 0.95 = 1.9, so the two pages of one drawing agree and neither number was supplied by the reader." +// THIS ROW IS A MECHANICAL CANDIDATE AND NOT A SELECTED PART. Nothing here says the device suits +// any board role; selection is a functional-compatibility join at the product layer, and this row +// supplies only the package authority that join consumes. WHAT THIS MODULE CARRIES AND WHAT IT +// DOES NOT. The TPS3808 is a supply voltage supervisor, and this row carries NONE of that: no +// threshold, no delay, no supply range. It carries the mechanical facts read from the document — +// package body dimensions and the vendor's recommended land pattern. It does not claim to be a +// device model; it answers one question, what a board must etch to mount this part, and a later +// reader adding electrical facts adds to a cited row rather than correcting an invented one. THE +// PITCH IS AN ARITHMETIC READING OF THE DRAWING, not a family memory: the body drawing prints 1.9 +// across two pitches and the land pattern prints 0.95 twice, and 2 x 0.95 = 1.9, so the two pages +// of one drawing agree and neither number was supplied by the reader. data tps3808_dbv0006a_drawing: PackageDrawingRef = PackageDrawingRef { drawing_number: "4214840/G", diff --git a/dag/extdeps/power/ti_tps53688.dag b/dag/extdeps/power/ti_tps53688.dag index d8cb4d368b4..2e36d650c58 100644 --- a/dag/extdeps/power/ti_tps53688.dag +++ b/dag/extdeps/power/ti_tps53688.dag @@ -33,7 +33,19 @@ data tps53688rsbr_part_details_authority: ExternalAuthority = ExternalAuthority } } -data ti_tps53688_note: String = "THIS ROW IS A MECHANICAL CANDIDATE AND NOT A SELECTED PART. Nothing here says the device suits any board role; a selection is a functional-compatibility join done at the product layer, and this row supplies only the package authority that join consumes. THE PACKAGE IDENTITY IS ESTABLISHED BY TWO CITED READINGS, NOT BY WHERE THE FILE WAS FOUND. The fetched datasheet yielded its mechanical pages cleanly but not its title page, so binding drawing RSB0040A to this device on the strength of the URL alone would have been a fact established by a file's location. The vendor's orderable part-details listing gives TPS53688RSBR as WQFN (RSB) 40, which is the package that drawing describes, and it is carried as a second authority in this module's scope rather than recounted in prose — an earlier revision of this note asserted the confirmation while the scope row listed no second citation, which left the load-bearing part-number-to-drawing join prose-backed. What this row carries is mechanical only: the multiphase controller's electrical facts, its phase count and its SVID and PMBus interfaces are not modelled here and are not implied by its presence." +// THIS ROW IS A MECHANICAL CANDIDATE AND NOT A SELECTED PART. Nothing here says the device suits +// any board role; a selection is a functional-compatibility join done at the product layer, and +// this row supplies only the package authority that join consumes. THE PACKAGE IDENTITY IS +// ESTABLISHED BY TWO CITED READINGS, NOT BY WHERE THE FILE WAS FOUND. The fetched datasheet yielded +// its mechanical pages cleanly but not its title page, so binding drawing RSB0040A to this device +// on the strength of the URL alone would have been a fact established by a file's location. The +// vendor's orderable part-details listing gives TPS53688RSBR as WQFN (RSB) 40, which is the package +// that drawing describes, and it is carried as a second authority in this module's scope rather +// than recounted in prose — an earlier revision of this note asserted the confirmation while the +// scope row listed no second citation, which left the load-bearing part-number-to-drawing join +// prose-backed. What this row carries is mechanical only: the multiphase controller's electrical +// facts, its phase count and its SVID and PMBus interfaces are not modelled here and are not +// implied by its presence. data tps53688_rsb0040a_drawing: PackageDrawingRef = PackageDrawingRef { drawing_number: "4215000/A", diff --git a/dag/extdeps/pricing/aws_ec2.dag b/dag/extdeps/pricing/aws_ec2.dag index c9dfabd8749..433183b653e 100644 --- a/dag/extdeps/pricing/aws_ec2.dag +++ b/dag/extdeps/pricing/aws_ec2.dag @@ -20,7 +20,9 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data aws_ec2_pricing_provenance_note: String = "On-demand Linux Shared tenancy prices live-fetched 2026-07-21 from the AWS Price List API (anchor above, us-east-1, preInstalledSw=NA). m7a general-purpose family only — 3-5 comparable instances, not full catalog coverage." +// On-demand Linux Shared tenancy prices live-fetched 2026-07-21 from the AWS Price List API (anchor +// above, us-east-1, preInstalledSw=NA). m7a general-purpose family only — 3-5 comparable instances, +// not full catalog coverage. type AwsEc2InstanceFamily = | M7a diff --git a/dag/extdeps/pricing/billing_units.dag b/dag/extdeps/pricing/billing_units.dag index e96250a9f31..780f56b9a13 100644 --- a/dag/extdeps/pricing/billing_units.dag +++ b/dag/extdeps/pricing/billing_units.dag @@ -12,9 +12,24 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data storage_billing_unit_note: String = "Object-storage vendors bill on three axes that are NOT reducible to a time period: capacity held over time (per stored unit-month), bytes moved (per transferred unit), and operation counts (per million requests). They instantiate std.measure MoneyRate

's billing-unit axis as three more phantom markers rather than a parallel record family (the money_rate_carrier_representation_note move). The BYTE BASIS is deliberately absent from these markers: vendors quote incompatible bases for the same word (AWS S3 quotes 'GB-Mo', Google Cloud Storage quotes '1 gibibyte hour'), so the basis is a per-row declared fact in extdeps.pricing.object_storage, never baked into the unit type -- a rate carrier that silently fixed one basis would make a cross-vendor comparison off by 7.4 percent and unwritable-as-honest." - -data billing_unit_layer_placement_note: String = "These markers live in extdeps, not std (review ruling 2026-07-25). MoneyRate

's parameter is the reusable universal carrier and stays in std.measure; the specific P markers below are VENDOR-BILLING vocabulary -- what cloud providers choose to meter -- not universal-framework vocabulary, so they belong beside their citations. Generics instantiate downstream, so declaring the markers here and writing MoneyRate here compiles identically to declaring them in std. The placement also has a measured cost: std/measure.dag is seed-emitted, so every billing axis added there drags a std_measure.rs regeneration (this change's own first attempt did exactly that, and redded the regen fixed-point gate), while an extdeps module drags none." +// Object-storage vendors bill on three axes that are NOT reducible to a time period: capacity held +// over time (per stored unit-month), bytes moved (per transferred unit), and operation counts (per +// million requests). They instantiate std.measure MoneyRate

's billing-unit axis as three more +// phantom markers rather than a parallel record family (the money_rate_carrier_representation_note +// move). The BYTE BASIS is deliberately absent from these markers: vendors quote incompatible bases +// for the same word (AWS S3 quotes 'GB-Mo', Google Cloud Storage quotes '1 gibibyte hour'), so the +// basis is a per-row declared fact in extdeps.pricing.object_storage, never baked into the unit +// type -- a rate carrier that silently fixed one basis would make a cross-vendor comparison off by +// 7.4 percent and unwritable-as-honest. + +// These markers live in extdeps, not std (review ruling 2026-07-25). MoneyRate

's parameter is +// the reusable universal carrier and stays in std.measure; the specific P markers below are +// VENDOR-BILLING vocabulary -- what cloud providers choose to meter -- not universal-framework +// vocabulary, so they belong beside their citations. Generics instantiate downstream, so declaring +// the markers here and writing MoneyRate here compiles identically to declaring +// them in std. The placement also has a measured cost: std/measure.dag is seed-emitted, so every +// billing axis added there drags a std_measure.rs regeneration (this change's own first attempt did +// exactly that, and redded the regen fixed-point gate), while an extdeps module drags none. type PerStoredUnitMonth @@ -26,6 +41,8 @@ type MoneyPerStoredUnitMonth = MoneyRate type MoneyPerTransferredUnit = MoneyRate -type MoneyPerMillionRequests = MoneyRate +// No per-unit micros accessors are declared here: std.measure money_rate_micros

already reads +// any MoneyRate

, so a money_per_stored_unit_month_micros wrapper would be a hollow alias -- a +// second name for one fact (DESIGN section 3). Consumers call money_rate_micros directly. -data billing_unit_accessor_note: String = "No per-unit micros accessors are declared here: std.measure money_rate_micros

already reads any MoneyRate

, so a money_per_stored_unit_month_micros wrapper would be a hollow alias -- a second name for one fact (DESIGN section 3). Consumers call money_rate_micros directly." +type MoneyPerMillionRequests = MoneyRate diff --git a/dag/extdeps/pricing/github_actions.dag b/dag/extdeps/pricing/github_actions.dag index 2d7e88f8065..11e650613b7 100644 --- a/dag/extdeps/pricing/github_actions.dag +++ b/dag/extdeps/pricing/github_actions.dag @@ -20,7 +20,8 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data github_actions_pricing_provenance_note: String = "Per-minute rates live-fetched 2026-07-21 from the Actions runner pricing reference (anchor above). Baseline hosted-runner SKUs only; larger-runner tables extend on demand as rows." +// Per-minute rates live-fetched 2026-07-21 from the Actions runner pricing reference (anchor +// above). Baseline hosted-runner SKUs only; larger-runner tables extend on demand as rows. type GithubActionsBillingSku = ActionsLinuxSlim diff --git a/dag/extdeps/pricing/gitlab_subscription.dag b/dag/extdeps/pricing/gitlab_subscription.dag index 620bb4434ed..d06d6d21a23 100644 --- a/dag/extdeps/pricing/gitlab_subscription.dag +++ b/dag/extdeps/pricing/gitlab_subscription.dag @@ -21,11 +21,30 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data pricing_subject_edge_note: String = "Subject and source are ORTHOGONAL axes and a filename encodes neither (operator ruling 2026-07-25): this module's ExternalAuthority anchor above is the SOURCE (the vendor's own pricing page — unchanged), and the subject row below is the typed edge naming the ENTITY these prices are about. It is the same entity node extdeps.sec.issuer.gitlab_facts reaches through its IssuerCatalogRow, so the two fact families (filed financials from EDGAR, list prices from the vendor site) are correlated by an edge in the graph rather than by a shared filename prefix. KNOWN MISMATCH, filed not fixed: the subject row's type reads Vendor because Vendor parameterizes the ENTITY by domain, and GitLab is the tree's first two-domain entity (SEC issuer and SaaS vendor) -- one company cannot inhabit two domains without forking the entity row. The edge is correct on identity and wrong on domain; the fix (domain moves onto the relationship rows, Vendor un-parameterized) is a ~14-file migration filed on the grounding-inventory lane with this module as its discriminating receipt. dissolve-on: that migration landing, after which this row's type names no domain." +// Subject and source are ORTHOGONAL axes and a filename encodes neither (operator ruling +// 2026-07-25): this module's ExternalAuthority anchor above is the SOURCE (the vendor's own pricing +// page — unchanged), and the subject row below is the typed edge naming the ENTITY these prices are +// about. It is the same entity node extdeps.sec.issuer.gitlab_facts reaches through its +// IssuerCatalogRow, so the two fact families (filed financials from EDGAR, list prices from the +// vendor site) are correlated by an edge in the graph rather than by a shared filename prefix. +// KNOWN MISMATCH, filed not fixed: the subject row's type reads Vendor because Vendor +// parameterizes the ENTITY by domain, and GitLab is the tree's first two-domain entity (SEC issuer +// and SaaS vendor) -- one company cannot inhabit two domains without forking the entity row. The +// edge is correct on identity and wrong on domain; the fix (domain moves onto the relationship +// rows, Vendor un-parameterized) is a ~14-file migration filed on the grounding-inventory lane with +// this module as its discriminating receipt. dissolve-on: that migration landing, after which this +// row's type names no domain. data gitlab_subscription_subject: Vendor = gitlab -data gitlab_subscription_pricing_provenance_note: String = "Tier list prices and usage meters live-fetched 2026-07-25 from the pricing page (anchor above; session gitlab-10k-scm-costs-baz90x). SaaS (gitlab.com) list pricing; self-managed uses the same tiers and Dedicated/enterprise terms are sales-negotiated. The 10-Ks describe the three-tier open-core model qualitatively but publish no dollar prices (FY2023 10-K: 'we recently announced a price increase in our Premium tier product offering'; FY2024 10-K: 'during fiscal year 2024 we implemented a price increase in our Premium tier'), so the pricing page and GitLab's own announcements are the price authorities, and extdeps.sec.issuer.gitlab_facts carries the filed financial consequences." +// Tier list prices and usage meters live-fetched 2026-07-25 from the pricing page (anchor above; +// session gitlab-10k-scm-costs-baz90x). SaaS (gitlab.com) list pricing; self-managed uses the same +// tiers and Dedicated/enterprise terms are sales-negotiated. The 10-Ks describe the three-tier +// open-core model qualitatively but publish no dollar prices (FY2023 10-K: 'we recently announced a +// price increase in our Premium tier product offering'; FY2024 10-K: 'during fiscal year 2024 we +// implemented a price increase in our Premium tier'), so the pricing page and GitLab's own +// announcements are the price authorities, and extdeps.sec.issuer.gitlab_facts carries the filed +// financial consequences. // Per-seat-month price on a tier row follows the extdeps.pricing precedent of the implicit billing unit living on the row (github_actions per_minute is per runner-minute; here per_seat_month is per user-month). Ultimate has NO public list price on the live page ('Get custom pricing -- Contact us for custom pricing'): CustomNegotiatedPricing keeps that a typed fact instead of fabricating a number (the pre-2026 99 USD list price is not on the page and is deliberately absent here). FreeOfCharge keeps the Free tier's 0 USD out of the listed arm so a billing term is never asserted for an unbilled tier. type GitlabSubscriptionTier diff --git a/dag/extdeps/pricing/hetzner_dedicated.dag b/dag/extdeps/pricing/hetzner_dedicated.dag index e5329e959b9..75854d33c4f 100644 --- a/dag/extdeps/pricing/hetzner_dedicated.dag +++ b/dag/extdeps/pricing/hetzner_dedicated.dag @@ -32,7 +32,9 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data hetzner_dedicated_pricing_provenance_note: String = "Monthly prices excl. VAT live-fetched 2026-07-21 from Hetzner price-adjustment doc GE-D9256 (anchor above, Limited offerings table). Hardware specs cross-checked against robot.hetzner.com/order (same date). EUR only — no currency conversion." +// Monthly prices excl. VAT live-fetched 2026-07-21 from Hetzner price-adjustment doc GE-D9256 +// (anchor above, Limited offerings table). Hardware specs cross-checked against +// robot.hetzner.com/order (same date). EUR only — no currency conversion. data cpu_description_string_leaf_frontier_rows: List = [ frontier_row_decl( diff --git a/dag/extdeps/pricing/object_storage.dag b/dag/extdeps/pricing/object_storage.dag index f9a520804c1..550a917eb2c 100644 --- a/dag/extdeps/pricing/object_storage.dag +++ b/dag/extdeps/pricing/object_storage.dag @@ -20,9 +20,26 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data object_storage_pricing_provenance_note: String = "Live-fetched 2026-07-25 (session gitlab-10k-scm-costs-baz90x). Machine-readable sources are cited exactly: AWS S3 storage tiers and the durability attribute come from the AWS Price List API regional CSV (anchor above -- the SAME authority extdeps.pricing.aws_ec2 already cites), and internet egress tiers from the AWSDataTransfer price list (us-east-1 to External). Google Cloud Storage rates come from the published pricing page tables; Cloudflare R2 from its pricing docs tables. Backblaze B2 and Hetzner Object Storage quote their headline rates through client-rendered markup that this fetch cannot read, so those rates are RateUnavailable rows with a stated cause -- not remembered numbers (DESIGN section 5: a plausible price fabricated from training data is exactly the wrong answer, and a vendor rate is precisely the kind of fact that goes stale)." - -data byte_basis_note: String = "The unit word is not the unit. AWS S3 bills 'GB-Mo', Google Cloud Storage bills '1 gibibyte hour', Cloudflare R2 bills 'GB-month' -- and none of these pricing surfaces states whether its G is 10^9 or 2^30 bytes. The difference is 7.4 percent, which is larger than the spread between several of these vendors, so a model that silently picked one basis would produce comparisons that are wrong in a way no test would catch. Each row therefore carries the vendor's quoted unit label VERBATIM plus a typed ByteBasis, and every row here is ByteBasisUnstated because none of the cited surfaces states it. Cross-vendor comparison is only possible under a DECLARED basis assumption (gunbc.econ.scm_serving_model), never silently. Dissolve-on: citing each vendor's own definition page where one exists." +// Live-fetched 2026-07-25 (session gitlab-10k-scm-costs-baz90x). Machine-readable sources are cited +// exactly: AWS S3 storage tiers and the durability attribute come from the AWS Price List API +// regional CSV (anchor above -- the SAME authority extdeps.pricing.aws_ec2 already cites), and +// internet egress tiers from the AWSDataTransfer price list (us-east-1 to External). Google Cloud +// Storage rates come from the published pricing page tables; Cloudflare R2 from its pricing docs +// tables. Backblaze B2 and Hetzner Object Storage quote their headline rates through +// client-rendered markup this fetch cannot read, so those rates are RateUnavailable rows with a +// stated cause -- not remembered numbers (DESIGN section 5: a plausible price fabricated from +// training data is exactly the wrong answer, and a vendor rate is precisely the kind of fact that +// goes stale). + +// The unit word is not the unit. AWS S3 bills 'GB-Mo', Google Cloud Storage bills '1 gibibyte +// hour', Cloudflare R2 bills 'GB-month' -- and none states whether its G is 10^9 or 2^30 bytes. +// The difference is 7.4 percent, larger than the spread between several of these vendors, so a +// model that silently picked one basis would produce comparisons wrong in a way no test would +// catch. Each row therefore carries the vendor's quoted unit label VERBATIM plus a typed +// ByteBasis, and every row here is ByteBasisUnstated because none of the cited surfaces states +// it. Cross-vendor comparison is only possible under a DECLARED basis assumption +// (gunbc.econ.scm_serving_model), never silently. Dissolve-on: citing each vendor's own definition +// page where one exists. type ByteBasis = BinaryGibibyte @@ -54,7 +71,15 @@ fn storage_vendor_label(v: StorageVendor) -> NonEmptyStr { data durability_standard_note: String = "The industry 'normal standard' for replicated object storage is ELEVEN NINES of annual durability, and for AWS this is not folklore -- it is a machine-readable attribute on the price-list row itself (Durability=99.999999999% on S3 Standard AND Standard-Infrequent-Access; the same file carries 99.99% rows for reduced-redundancy classes). Durability at that level is achieved by erasure-coding each object across multiple facilities, which is WHY the price already includes redundancy: an S3 Standard GB-month is not one copy, so multiplying a cited object-storage rate by a replica count double-counts the redundancy the vendor already sells. That is the modeling trap this row exists to name -- replica multiplication belongs on SELF-MANAGED block storage (the Hetzner dedicated rows in extdeps.pricing.hetzner_dedicated), never on a durability-priced object-storage rate." -// Redundancy SCOPE is a separate fact from durability nines, and this enum admits only what a vendor's own pricing surface NAMES -- never what the modeler remembers. Azure names it in the SKU itself (LRS locally redundant / ZRS zone redundant / GRS geo redundant / GZRS geo-zone redundant / RA- read-access), which is why Azure is the one vendor whose redundancy ladder is priced row by row below. Storj's pricing page states global distribution across storage locations. Every other vendor here states durability or nothing on the fetched surface, so their scope is RedundancyUnstated -- and a reliability requirement over scope therefore returns Unknown for them rather than a pass (DESIGN section 5: absence of evidence is not a satisfied requirement). +// Redundancy SCOPE is a separate fact from durability nines, and this enum admits only what a +// vendor's own pricing surface NAMES -- never what the modeler remembers. Azure names it in the +// SKU itself (LRS locally redundant / ZRS zone redundant / GRS geo redundant / GZRS geo-zone +// redundant / RA- read-access), which is why Azure is the one vendor whose redundancy ladder is +// priced row by row below. Storj's pricing page states global distribution across storage +// locations. Every other vendor here states durability or nothing on the fetched surface, so +// their scope is RedundancyUnstated -- and a reliability requirement over scope returns Unknown +// for them rather than a pass (DESIGN section 5: absence of evidence is not a satisfied +// requirement). type RedundancyScope = SingleFacility | MultiFacilityOneRegion @@ -164,7 +189,12 @@ data s3_standard_infrequent_access: ObjectStorageClassRow = ObjectStorageClassRo pricing_citation: "AmazonS3/us-east-1#TimedStorage-SIA-ByteHrs", } -data gcs_standard_regional_note: String = "Google Cloud Storage quotes Standard storage as 0.000027397 USD per gibibyte HOUR, which is a different billing period as well as a different byte word from AWS's GB-Mo. The row below carries the monthly equivalent at the conventional 730-hour billing month (std.measure billing_month_as_hour_count): 0.000027397 * 730 = 0.0200 USD, i.e. 20000 micro-USD. That multiplication is a period conversion within ONE vendor's own quote, not a cross-vendor byte-basis conversion -- the latter still refuses without a declared assumption." +// Google Cloud Storage quotes Standard storage as 0.000027397 USD per gibibyte HOUR -- a different +// billing period as well as a different byte word from AWS's GB-Mo. The row below carries the +// monthly equivalent at the conventional 730-hour billing month (std.measure +// billing_month_as_hour_count): 0.000027397 * 730 = 0.0200 USD, i.e. 20000 micro-USD. That is a +// period conversion within ONE vendor's own quote, not a cross-vendor byte-basis conversion -- +// the latter still refuses without a declared assumption. data gcs_standard_regional: ObjectStorageClassRow = ObjectStorageClassRow { vendor: GoogleCloudStorage, @@ -232,7 +262,10 @@ data hetzner_object_storage_rate_unavailable: ObjectStorageClassRow = ObjectStor pricing_citation: "hetzner.com/storage/object-storage#rate-not-machine-readable", } -// What the Hetzner Object Storage page DOES state in fetched text, and the only quota fact stored from it: 'This base price includes 1 TB of storage (up to 744 TB-hours) and 1 TB of egress traffic.' The 744 TB-hours figure is itself the 31-day month, a third billing-period convention beside AWS's GB-Mo and Google's gibibyte-hour. +// What the Hetzner Object Storage page DOES state in fetched text, and the only quota fact stored +// from it: 'This base price includes 1 TB of storage (up to 744 TB-hours) and 1 TB of egress +// traffic.' The 744 TB-hours figure is itself the 31-day month, a third billing-period convention +// beside AWS's GB-Mo and Google's gibibyte-hour. data s3_egress_first_10tb: ObjectStorageEgressRow = ObjectStorageEgressRow { vendor: AwsS3, egress_rate: TransferCited { @@ -297,7 +330,11 @@ data b2_egress_beyond_free_allowance: ObjectStorageEgressRow = ObjectStorageEgre pricing_citation: "backblaze.com/cloud-storage/pricing#egress-beyond-3x", } -// Google Cloud Storage prices REPLICATION explicitly and separately: writing to a dual-region or multi-region bucket costs 0.02 USD per GiB replicated in North America and Europe (0.04 turbo; 0.08 in Asia). This is the one cited row in the set that puts a number on geographic replication as its own axis rather than folding it into the storage rate -- and it is charged per byte WRITTEN, not per byte stored, so it prices the ingest of new content, not the holding of it. +// Google Cloud Storage prices REPLICATION explicitly and separately: writing to a dual-region or +// multi-region bucket costs 0.02 USD per GiB replicated in North America and Europe (0.04 turbo; +// 0.08 in Asia). This is the one cited row that puts a number on geographic replication as its +// own axis rather than folding it into the storage rate -- and it is charged per byte WRITTEN, +// not per byte stored, so it prices the ingest of new content, not the holding of it. data gcs_default_replication: ObjectStorageEgressRow = ObjectStorageEgressRow { vendor: GoogleCloudStorage, egress_rate: TransferCited { @@ -391,7 +428,15 @@ fn durability_nines(row: ObjectStorageClassRow) -> Nat? { } } -data azure_redundancy_ladder_note: String = "Azure is the one vendor in this set that prices REDUNDANCY as an explicit product axis rather than folding it into a single rate, and it publishes the whole ladder through an unauthenticated retail price API (prices.azure.com/api/retail/prices, eastus, General Block Blob v2, Hot access tier, live-fetched 2026-07-25). Reading the ladder answers 'what does more redundancy cost' directly: locally redundant 0.0208, zone redundant 0.026 (+25 percent), geo redundant 0.0458 (+120 percent), geo-zone redundant 0.0468, read-access geo-zone redundant 0.0585 (+181 percent) USD per GB-month at the first tier. Each also steps down at 50 TB and 500 TB, so the rows below carry all three tiers for the ladder positions a serving model actually chooses between." +// Azure is the one vendor in this set that prices REDUNDANCY as an explicit product axis rather +// than folding it into a single rate, and it publishes the whole ladder through an +// unauthenticated retail price API (prices.azure.com/api/retail/prices, eastus, General Block +// Blob v2, Hot access tier, live-fetched 2026-07-25). The ladder answers 'what does more +// redundancy cost' directly: locally redundant 0.0208, zone redundant 0.026 (+25 percent), geo +// redundant 0.0458 (+120 percent), geo-zone redundant 0.0468, read-access geo-zone redundant +// 0.0585 (+181 percent) USD per GB-month at the first tier. Each also steps down at 50 TB and +// 500 TB, so the rows below carry all three tiers for the ladder positions a serving model +// actually chooses between. data azure_hot_lrs_tier0: ObjectStorageClassRow = ObjectStorageClassRow { vendor: AzureBlobStorage, @@ -533,7 +578,13 @@ data azure_hot_ra_gzrs_tier0: ObjectStorageClassRow = ObjectStorageClassRow { pricing_citation: "prices.azure.com#eastus/GeneralBlockBlobv2/HotRA-GZRSDataStored[0]", } -// Wasabi is the flat-rate shape: one storage price, and the pricing page states 'No fees for egress or API requests'. The quoted rate is per TB-month ('starting at 7.99 USD per TB/month'), which this row carries converted to the same per-unit-month carrier as everyone else by dividing by 1000 -- a WITHIN-vendor unit conversion, and the reason the row's byte basis stays unstated is that whether that TB is 10^12 or 2^40 is exactly what the page does not say (the page's own calculator footnote says estimates 'use base-2 system', which is evidence but not a rate definition). 'Starting at' is itself a hedge: the row carries the stated floor, and a commitment discount is not modeled. +// Wasabi is the flat-rate shape: one storage price, and the pricing page states 'No fees for +// egress or API requests'. The quoted rate is per TB-month ('starting at 7.99 USD per TB/month'), +// carried here converted to the shared per-unit-month carrier by dividing by 1000 -- a +// WITHIN-vendor unit conversion. The byte basis stays unstated because whether that TB is 10^12 +// or 2^40 is exactly what the page does not say (its calculator footnote says estimates 'use +// base-2 system', which is evidence but not a rate definition). 'Starting at' is itself a hedge: +// the row carries the stated floor, and a commitment discount is not modeled. data wasabi_hot_storage: ObjectStorageClassRow = ObjectStorageClassRow { vendor: Wasabi, storage_class_label: "Hot Cloud Storage (pay as you go)", @@ -557,7 +608,12 @@ data wasabi_egress_free: ObjectStorageEgressRow = ObjectStorageEgressRow { pricing_citation: "wasabi.com/pricing#no-egress-or-api-fees", } -// Storj is the erasure-coded distributed shape and the only vendor here whose pricing page states its redundancy posture in the same breath as its price ('Distributed globally across tens of thousands of storage locations'), which is why its redundancy scope is cited rather than unstated. It meters egress at the same 7 USD per TB as storage, so unlike the hyperscalers its network term never dominates by an order of magnitude, and unlike the zero-egress vendors it never vanishes. +// Storj is the erasure-coded distributed shape and the only vendor here whose pricing page states +// its redundancy posture in the same breath as its price ('Distributed globally across tens of +// thousands of storage locations'), which is why its redundancy scope is cited rather than +// unstated. It meters egress at the same 7 USD per TB as storage, so unlike the hyperscalers its +// network term never dominates by an order of magnitude, and unlike the zero-egress vendors it +// never vanishes. data storj_standard_storage: ObjectStorageClassRow = ObjectStorageClassRow { vendor: Storj, storage_class_label: "Standard (distributed)", @@ -583,7 +639,10 @@ data storj_egress: ObjectStorageEgressRow = ObjectStorageEgressRow { pricing_citation: "storj.io/pricing#standard-egress", } -// Google Cloud Storage states its byte basis outright -- 'per 1 gibibyte' -- unlike AWS, Azure, and Cloudflare, so the GCS rows carry BinaryGibibyte and a serving model must NOT reinterpret them under a decimal assumption. Worldwide destinations excluding Asia and Australia, first tier (0 to 10 TiB). +// Google Cloud Storage states its byte basis outright -- 'per 1 gibibyte' -- unlike AWS, Azure, +// and Cloudflare, so the GCS rows carry BinaryGibibyte and a serving model must NOT reinterpret +// them under a decimal assumption. Worldwide destinations excluding Asia and Australia, first +// tier (0 to 10 TiB). data gcs_egress_worldwide_first_10tib: ObjectStorageEgressRow = ObjectStorageEgressRow { vendor: GoogleCloudStorage, egress_rate: TransferCited { @@ -595,7 +654,13 @@ data gcs_egress_worldwide_first_10tib: ObjectStorageEgressRow = ObjectStorageEgr pricing_citation: "cloud.google.com/storage/pricing#worldwide-destinations-0-10TiB", } -data azure_egress_tier_ladder_note: String = "Azure's retail API returns MORE THAN ONE Standard Data Transfer Out ladder for eastus (one anchored at tierMinimumUnits 100, matching the documented first-100-GB-free allowance, and another anchored at 10100/50100/150100 with different rates). These rows carry the 100-anchored ladder because its anchor matches the published free allowance; the existence of the second ladder is recorded here rather than silently discarded, and picking between them by execution needs a billed account, which this model does not have. Dissolve-on: a receipted Azure invoice line, or a Microsoft doc naming which ladder applies to blob egress." +// Azure's retail API returns MORE THAN ONE Standard Data Transfer Out ladder for eastus (one +// anchored at tierMinimumUnits 100, matching the documented first-100-GB-free allowance, and +// another anchored at 10100/50100/150100 with different rates). These rows carry the 100-anchored +// ladder because its anchor matches the published free allowance; the second ladder is recorded +// here rather than silently discarded, and picking between them by execution needs a billed +// account, which this model does not have. Dissolve-on: a receipted Azure invoice line, or a +// Microsoft doc naming which ladder applies to blob egress. data azure_egress_first_10tb: ObjectStorageEgressRow = ObjectStorageEgressRow { vendor: AzureBlobStorage, @@ -641,7 +706,14 @@ data azure_egress_over_150tb: ObjectStorageEgressRow = ObjectStorageEgressRow { pricing_citation: "prices.azure.com#eastus/Bandwidth/StandardDataTransferOut[153695]", } -// The operator's framing: decide the reliability posture FIRST, then price only what satisfies it. A requirement is a floor on two independent axes (durability nines, redundancy scope), and the verdict is three-valued on purpose -- Meets, Fails, or RequirementUnknown when the vendor's cited surface does not state the axis at all. Unknown is NOT a pass: a vendor that says nothing about geo-redundancy has not satisfied a geo-redundancy requirement, and collapsing that into a pass is the absorbing fallback DESIGN section 5 forbids. It is also not a fail -- the vendor may well qualify, the pricing surface simply does not say -- so the verdict names what is missing and the operator resolves it with a citation, not a guess. +// The operator's framing: decide the reliability posture FIRST, then price only what satisfies +// it. A requirement is a floor on two independent axes (durability nines, redundancy scope), and +// the verdict is three-valued on purpose -- Meets, Fails, or RequirementUnknown when the vendor's +// cited surface does not state the axis at all. Unknown is NOT a pass: a vendor that says nothing +// about geo-redundancy has not satisfied a geo-redundancy requirement, and collapsing that into a +// pass is the absorbing fallback DESIGN section 5 forbids. It is also not a fail -- the vendor may +// well qualify, the pricing surface simply does not say -- so the verdict names what is missing +// and the operator resolves it with a citation, not a guess. type ReliabilityRequirement { min_durability_nines: Nat min_redundancy: RedundancyScope @@ -696,7 +768,12 @@ fn evaluate_redundancy_only(row: ObjectStorageClassRow, req: ReliabilityRequirem } } -// Azure quotes blob operations per 10,000, not per million, so these rows carry the vendor figure multiplied by 100 into the shared per-million carrier (a within-vendor unit conversion). Note the redundancy ladder reaches operations too, not just capacity: a Hot LRS write is 0.05 per 10K while the geo-zone-redundant write is 0.1175 -- writes cost more when every write must reach more places, which is the mechanism the storage ladder prices showing up on a second axis. Reads do not vary by redundancy. +// Azure quotes blob operations per 10,000, not per million, so these rows carry the vendor figure +// multiplied by 100 into the shared per-million carrier (a within-vendor unit conversion). The +// redundancy ladder reaches operations too, not just capacity: a Hot LRS write is 0.05 per 10K +// while the geo-zone-redundant write is 0.1175 -- writes cost more when every write must reach +// more places, the storage-ladder mechanism showing up on a second axis. Reads do not vary by +// redundancy. data azure_hot_lrs_write_operations: ObjectStorageRequestRow = ObjectStorageRequestRow { vendor: AzureBlobStorage, operation_class_label: "Hot LRS Write Operations", diff --git a/dag/extdeps/pricing/regional_compute.dag b/dag/extdeps/pricing/regional_compute.dag index 541fe5a5335..749eacb5fac 100644 --- a/dag/extdeps/pricing/regional_compute.dag +++ b/dag/extdeps/pricing/regional_compute.dag @@ -23,9 +23,24 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data regional_compute_provenance_note: String = "Live-fetched 2026-07-25 (session gitlab-10k-scm-costs-baz90x). Two machine-readable authorities, one SKU each, held constant across every geography so a price difference is a REGION fact and not a hardware fact: AWS m7i.large (2 vCPU / 8 GiB, on-demand Linux, shared tenancy, no pre-installed software) streamed from the AWS Price List API regional CSVs -- the same authority extdeps.pricing.aws_ec2 already cites -- and Azure Standard_D2s_v5 (2 vCPU / 8 GiB, Linux pay-as-you-go) from the unauthenticated Azure retail price API (anchor above). Holding the SKU fixed is what makes the comparison honest; the one place it could not be held is recorded on its own row (Bahrain, below)." - -data local_provider_readability_note: String = "A structural finding about this market, recorded as rows rather than prose: the HYPERSCALERS publish machine-readable price APIs covering every geography, and essentially no regional or local provider does. Attempted and refused on 2026-07-25: E2E Networks (India) serves a GPU-only fragment and 404s its compute page; Magalu Cloud (Brazil) 404; Afrihost (South Africa) 404; Contabo 403s a non-browser fetch; Alibaba Cloud renders prices client-side; Oracle's price-list API returns an HTML error page. Sakura Internet (Japan) is the one local provider whose ladder IS readable, and it is blocked on a different axis (currency, below). Consequence for anyone building on this: a regionally-diverse cost model can be CITED only for the hyperscalers; local-provider quotes are hand-entered facts with a human in the loop, and should be typed as such rather than silently trusted." +// Live-fetched 2026-07-25 (session gitlab-10k-scm-costs-baz90x). Two machine-readable authorities, +// one SKU each, held constant across every geography so a price difference is a REGION fact and not +// a hardware fact: AWS m7i.large (2 vCPU / 8 GiB, on-demand Linux, shared tenancy, no pre-installed +// software) streamed from the AWS Price List API regional CSVs -- the same authority +// extdeps.pricing.aws_ec2 already cites -- and Azure Standard_D2s_v5 (2 vCPU / 8 GiB, Linux +// pay-as-you-go) from the unauthenticated Azure retail price API (anchor above). Holding the SKU +// fixed is what makes the comparison honest; the one place it could not be held is recorded on its +// own row (Bahrain, below). + +// A structural finding about this market, recorded as rows rather than prose: the HYPERSCALERS +// publish machine-readable price APIs covering every geography, and essentially no regional or +// local provider does. Attempted and refused on 2026-07-25: E2E Networks (India) serves a GPU-only +// fragment and 404s its compute page; Magalu Cloud (Brazil) 404; Afrihost (South Africa) 404; +// Contabo 403s a non-browser fetch; Alibaba Cloud renders prices client-side; Oracle's price-list +// API returns an HTML error page. Sakura Internet (Japan) is the one local provider whose ladder IS +// readable, blocked on a different axis (currency, below). Consequence: a regionally-diverse cost +// model can be CITED only for the hyperscalers; local-provider quotes are hand-entered facts with +// a human in the loop, and should be typed as such rather than silently trusted. type Geography = NorthAmerica @@ -73,7 +88,12 @@ fn compute_provider_label(p: ComputeProvider) -> NonEmptyStr { } } -data hourly_rate_availability_note: String = "Same refusal discipline as extdeps.pricing.object_storage StorageRateAvailability: a rate this fetch could not read is a typed row with a stated cause, never a remembered number. HourlyUnrepresentable is a DISTINCT third arm from HourlyUnavailable because the obstacle is different in kind -- the price was read successfully but cannot be written down in the carrier (Sakura quotes JPY and extdeps.currency.currency admits only Eur and Usd today). Collapsing 'could not read it' and 'read it but cannot represent it' into one arm would hide a modeling gap behind a fetch failure." +// Same refusal discipline as extdeps.pricing.object_storage StorageRateAvailability: a rate this +// fetch could not read is a typed row with a stated cause, never a remembered number. +// HourlyUnrepresentable is a DISTINCT third arm from HourlyUnavailable because the obstacle differs +// in kind -- the price was read but cannot be written down in the carrier (Sakura quotes JPY and +// extdeps.currency.currency admits only Eur and Usd today). Collapsing 'could not read it' and +// 'read it but cannot represent it' into one arm would hide a modeling gap behind a fetch failure. type HourlyRateAvailability = HourlyCited { hourly: MoneyPerHour } @@ -178,7 +198,13 @@ data aws_ap_northeast_1: RegionalComputeRow = RegionalComputeRow { pricing_citation: "AmazonEC2/ap-northeast-1#m7i.large-OnDemand-Linux-Shared", } -data aws_bahrain_sku_substitution_note: String = "The one place the fixed-SKU rule could not hold, recorded rather than smoothed over: me-south-1 (Bahrain) does not offer m7i.large, so this row carries m6i.large -- a PREVIOUS-generation 2 vCPU / 8 GiB instance. Its 0.1320 USD/hr is therefore NOT directly comparable to the m7i rows above (an older generation usually prices lower for the same vCPU count, so the true m7i-equivalent Bahrain premium would likely be higher still, not lower). Instance-family availability differing by region is itself the finding: newer silicon reaches the largest regions first, so a smaller region pays more AND gets older hardware." +// The one place the fixed-SKU rule could not hold, recorded rather than smoothed over: me-south-1 +// (Bahrain) does not offer m7i.large, so this row carries m6i.large -- a PREVIOUS-generation 2 vCPU +// / 8 GiB instance. Its 0.1320 USD/hr is therefore NOT directly comparable to the m7i rows above +// (an older generation usually prices lower for the same vCPU count, so the true m7i-equivalent +// Bahrain premium would likely be higher still, not lower). Instance-family availability differing +// by region is itself the finding: newer silicon reaches the largest regions first, so a smaller +// region pays more AND gets older hardware. data aws_me_south_1: RegionalComputeRow = RegionalComputeRow { provider: AwsEc2, diff --git a/dag/extdeps/pricing/ubicloud.dag b/dag/extdeps/pricing/ubicloud.dag index 7b86e2740a6..35b8ecb3c59 100644 --- a/dag/extdeps/pricing/ubicloud.dag +++ b/dag/extdeps/pricing/ubicloud.dag @@ -21,7 +21,19 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data ubicloud_pricing_provenance_note: String = "Per-minute managed-runner prices refreshed 2026-08-22 from the Ubicloud pricing page (anchor above), superseding a 2026-07-21 read that had premium x64 standard-2 at 1600 and arm64 standard-2 at 1000 micros. READ FIRST-PARTY AT THAT DATE: the two standard-2 rows, premium x64 at 2000 micros/min and arm64 at 1250. PROJECTED, NOT READ: the two standard-16 rows, carried at eight times their standard-2 row because Ubicloud prices these families strictly per vCPU and the superseded pair held the same exact ratio; that ratio is the vendor's published structure rather than an observation at the 16 size, and a first-party read at that size would replace it. Standard x64 is NOT represented here at all and its absence is not a judgement: Ubicloud's own two surfaces disagree on whether it remains purchasable, the price-adjustment post describing it as withdrawn from new customers while the live docs describe premium as a default with an opt-out, and a contradiction between first-party sources is a fact about the vendor that needs its own carrier rather than a row picking a side. Hardware facts project from extdeps.cloud.ubicloud catalog rows - not re-stated here." +// Per-minute managed-runner prices refreshed 2026-08-22 from the Ubicloud pricing page (anchor +// above), superseding a 2026-07-21 read that had premium x64 standard-2 at 1600 and arm64 +// standard-2 at 1000 micros. READ FIRST-PARTY AT THAT DATE: the two standard-2 rows, premium x64 at +// 2000 micros/min and arm64 at 1250. PROJECTED, NOT READ: the two standard-16 rows, carried at +// eight times their standard-2 row because Ubicloud prices these families strictly per vCPU and the +// superseded pair held the same exact ratio; that ratio is the vendor's published structure rather +// than an observation at the 16 size, and a first-party read at that size would replace it. +// Standard x64 is NOT represented here at all and its absence is not a judgement: Ubicloud's own +// two surfaces disagree on whether it remains purchasable, the price-adjustment post describing it +// as withdrawn from new customers while the live docs describe premium as a default with an +// opt-out, and a contradiction between first-party sources is a fact about the vendor that needs +// its own carrier rather than a row picking a side. Hardware facts project from +// extdeps.cloud.ubicloud catalog rows - not re-stated here. type UbicloudRunnerPricingTier = PremiumX64 | Arm64 diff --git a/dag/extdeps/probes/provider_interface_acquisition.dag b/dag/extdeps/probes/provider_interface_acquisition.dag index 284ded088b0..69c1ce59da6 100644 --- a/dag/extdeps/probes/provider_interface_acquisition.dag +++ b/dag/extdeps/probes/provider_interface_acquisition.dag @@ -24,7 +24,9 @@ data extdeps_model_scope: ExternalModelScope = ExternalModelScope { further_citations: [] } -data provider_interface_acquisition_authority_note: String = "Cited rows from provider_interface_acquisition_2026-08-03.json — disposable npm acquisition probe receipt (2026-08-03). Independent oracle for @cursor/sdk package-closure witnesses; consumers must join here, never re-assert cursor_sdk_facts constructor inputs." +// Cited rows from provider_interface_acquisition_2026-08-03.json — disposable npm acquisition probe +// receipt (2026-08-03). Independent oracle for @cursor/sdk package-closure witnesses; consumers +// must join here, never re-assert cursor_sdk_facts constructor inputs. data provider_interface_acquisition_lockfile_sha256: String = "d45372139f3b3cfec9b32728a1e6994d4a212d9d286854e795ad67ed12ee2d70" data provider_interface_acquisition_lockfile_package_entry_count: Int = 124 diff --git a/dag/extdeps/procps/pgrep.dag b/dag/extdeps/procps/pgrep.dag index f744a737ef8..2e2a927654c 100644 --- a/dag/extdeps/procps/pgrep.dag +++ b/dag/extdeps/procps/pgrep.dag @@ -2,7 +2,8 @@ module extdeps.procps.pgrep import std.types { String, Int } -data pgrep_authority_note: String = "pgrep(1) from procps-ng, cited to the upstream manual's EXIT STATUS section. Only the codes this repository reads are modelled." +// pgrep(1) from procps-ng, cited to the upstream manual's EXIT STATUS section. Only the codes this +// repository reads are modelled. // PGREP EXITS 1 WHEN NOTHING MATCHED, AND THAT IS A SUCCESSFUL ANSWER WEARING A FAILURE STATUS. // diff --git a/dag/extdeps/programmable_logic/ice40/configuration.dag b/dag/extdeps/programmable_logic/ice40/configuration.dag index 90064254e97..188ee5f8c89 100644 --- a/dag/extdeps/programmable_logic/ice40/configuration.dag +++ b/dag/extdeps/programmable_logic/ice40/configuration.dag @@ -18,11 +18,25 @@ import extdeps.programmable_logic.ice40.supply { configuration_modes_equal, } -data ice40_configuration_note: String = "THE CONFIGURATION PORT, from Table 4.28 SPI Master or NVCM Configuration Time and Table 4.29 sysCONFIG Port Timing Specifications of iCE40 LP/HX Family Data Sheet FPGA-DS-02029, Version 4.3, February 2025. TWO THINGS IN THESE TABLES ARE NOT NUMBERS AND WOULD HAVE BECOME ONE. First, a timing row APPLIES TO SOME MODES AND NOT OTHERS: tCR_SCK is a slave-SPI row and asking for it on a master-SPI board is asking about a transaction that board never performs, which is a different state from the specification being unknown. Second, the high-frequency tCONFIG values were DELETED by a revision of this document — the change history records the deletion for every device — and note 3 says high frequency is supported only on SPI Master. A blank cell in a published table is not zero, not the low-frequency figure, and not an oversight to fill in from a previous revision; it is the vendor withdrawing a number, and a model that carried the old value would be asserting a specification the vendor has retracted. A READING NOTE ON THE TABLE ITSELF: Table 4.29 carries a condition column between Parameter and Min, so the 200 ns against tCRESET_B sits in the MINIMUM column and not the typical one. That was checked rather than assumed, because reading it one column left would have turned a minimum pulse width into a typical one — and a design built to a typical where a minimum is required fails intermittently, which is the worst way to fail." - -// THE SIGNALS THE CONFIGURATION PORT PRESENTS. SPI_SS carries the name this revision gives it: -// the change history records that SPI_SS_B was renamed to SPI_SS in the Signal Descriptions table, -// so a model spelling it the old way is citing a document that no longer exists under that name. +// THE CONFIGURATION PORT, from Table 4.28 SPI Master or NVCM Configuration Time and Table 4.29 +// sysCONFIG Port Timing Specifications of iCE40 LP/HX Family Data Sheet FPGA-DS-02029, Version 4.3, +// February 2025. TWO THINGS IN THESE TABLES ARE NOT NUMBERS AND WOULD HAVE BECOME ONE. First, a +// timing row APPLIES TO SOME MODES AND NOT OTHERS: tCR_SCK is a slave-SPI row, and asking for it on +// a master-SPI board asks about a transaction that board never performs — a different state from +// the specification being unknown. Second, the high-frequency tCONFIG values were DELETED by a +// revision of this document — the change history records the deletion for every device — and note 3 +// says high frequency is supported only on SPI Master. A blank cell in a published table is not +// zero, not the low-frequency figure, and not an oversight to fill from a previous revision; it is +// the vendor withdrawing a number, and a model carrying the old value would assert a retracted +// specification. A READING NOTE ON THE TABLE ITSELF: Table 4.29 carries a condition column between +// Parameter and Min, so the 200 ns against tCRESET_B sits in the MINIMUM column, not the typical. +// Checked rather than assumed, because reading one column left would turn a minimum pulse width +// into a typical — and a design built to a typical where a minimum is required fails +// intermittently, the worst way to fail. + +// THE SIGNALS THE CONFIGURATION PORT PRESENTS. SPI_SS carries the name this revision gives it: the +// change history records SPI_SS_B renamed to SPI_SS in the Signal Descriptions table, so the old +// spelling cites a document that no longer exists under that name. // This module's own citation of the same data sheet. It is a literal because the corpus wall reads // the anchor structurally and cannot see a binding to a sibling's value - see the note in @@ -53,9 +67,9 @@ fn configuration_signal_name(s: Ice40ConfigurationSignal) -> NonEmptyStr { } } -// A TIMING ROW HAS A SCOPE, and the scope is part of the fact. A row that applies to every mode -// and a row that applies only to slave SPI are different claims, and a consumer asking a -// master-SPI board about tCR_SCK is asking about a transaction that board does not perform. +// A TIMING ROW HAS A SCOPE, and the scope is part of the fact. A row applying to every mode and one +// applying only to slave SPI are different claims; asking a master-SPI board about tCR_SCK asks +// about a transaction it does not perform. type TimingScope = AllConfigurationModes | OnlyUnderMode { mode: Ice40ConfigurationMode } @@ -70,25 +84,24 @@ fn scope_admits(s: TimingScope, m: Ice40ConfigurationMode) -> Bool { // THE COLUMN A VALUE CAME FROM IS CARRIED, because a minimum and a typical are different // obligations and the table states both. A design built to a typical where the document gives a -// minimum meets the specification on average and violates it intermittently, which is the failure -// mode hardest to find. Carrying the column means a consumer wanting a guaranteed bound can -// refuse a typical rather than silently accept it. +// minimum meets the specification on average and violates it intermittently — the failure hardest +// to find. Carrying the column lets a consumer wanting a guaranteed bound refuse a typical rather +// than silently accept it. type SpecColumn = MinimumColumn | TypicalColumn | MaximumColumn // THE QUANTITY IS TYPED, and the heterogeneity is what makes it a coproduct rather than a number -// beside a unit string. An earlier revision of this module carried value: Int with unit: -// NonEmptyStr holding "ns", "us", "MHz" and "clock cycles" - a unit written in a name, which is a -// unit no consumer can read, so a nanosecond and a megahertz became the same type and comparing -// them typechecked. This module's sibling says exactly that about package dimensions; the excuse -// here was that the rows genuinely differ in kind, and rows differing in kind is the definition of -// a coproduct, not a licence for a string. +// beside a unit string. An earlier revision carried value: Int with unit: NonEmptyStr holding "ns", +// "us", "MHz" and "clock cycles" — a unit written in a name, which no consumer can read, so a +// nanosecond and a megahertz became one type and comparing them typechecked. This module's sibling +// says exactly that about package dimensions; the excuse here was that the rows differ in kind, and +// rows differing in kind is the definition of a coproduct, not a licence for a string. // -// A clock-cycle count is deliberately NOT a Measure. It is a cardinality, like the contact count -// in the subject module: forty-nine cycles is a number of things, and how long they take depends -// on a clock this table does not name. +// A clock-cycle count is deliberately NOT a Measure. It is a cardinality, like the contact count in +// the subject module: forty-nine cycles is a number of things, and how long they take depends on a +// clock this table does not name. type TimingQuantity = BoundInNanoseconds { value: Nanosecond } | BoundInMicroseconds { value: Microsecond } @@ -152,10 +165,10 @@ fn timings_for_mode(m: Ice40ConfigurationMode) -> List { // THE CONFIGURATION CLOCK SPEED SELECTION, and the arm that matters is the third one. The vendor // published high-frequency tCONFIG values and then DELETED them: the change history records -// removing the high-frequency figure for every device in the family, and the surviving note says -// high frequency is supported only on SPI Master. A withdrawn value is not an absent one — the -// remedy for absent is to go and read; the remedy for withdrawn is that there is nothing to read, -// and carrying the retired number would assert a specification the vendor has retracted. +// removing the figure for every device in the family, and the surviving note says high frequency is +// supported only on SPI Master. A withdrawn value is not an absent one — the remedy for absent is +// to go and read; for withdrawn there is nothing to read, and carrying the retired number would +// assert a retracted specification. type ConfigurationClockSelection = LowFrequencyDefault | MediumFrequency diff --git a/dag/extdeps/programmable_logic/ice40/subject.dag b/dag/extdeps/programmable_logic/ice40/subject.dag index 26e1bfb6c68..ed63a8e61eb 100644 --- a/dag/extdeps/programmable_logic/ice40/subject.dag +++ b/dag/extdeps/programmable_logic/ice40/subject.dag @@ -24,7 +24,30 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data ice40_subject_note: String = "THE iCE40 LP/HX FAMILY, FROM THE VENDOR'S OWN DATA SHEET. Pulled and read 2026-08-13: iCE40 LP/HX Family Data Sheet, document FPGA-DS-02029, Version 4.3, February 2025. THE DOCUMENT NUMBER ITSELF CHANGED and that is a modelled fact rather than a detail: this repository previously cited DS1040 Version 3.4, October 2017, and the current revision's own change history records 'Changed document number from DS1040 to FPGA-DS-02029'. So the same subject is reachable under two identifiers, and a citation naming only the old one does not fail loudly — it resolves to a real, stale document. Two substantive changes land with the refresh. The revision history records that the dedicated inputs for Bank 1 of the HX1K in CB132 and TQ144 were modified, which is exactly the population this repository joins against Project IceStorm's roster, and the configuration signal SPI_SS_B was renamed SPI_SS. WHAT IS AND IS NOT TRANSCRIBED HERE, and the distinction is deliberate rather than cautious. Short factual statements a design has to consult — the ordering part number, the package type and its physical dimensions, the per-bank I/O population, the I/O standards the buffer supports — are carried. The full pin assignment table is NOT: it is thirty-odd sheets of a copyrighted vendor document, and the pin-to-tile map this repository actually needs for place and route comes from Project IceStorm, which is an independently governed open-source project with its own license and its own module. Those are different authorities and modelling one as the other would fuse a specification with a reverse-engineering of it. The same reasoning is already recorded for the Altra package, where the split between public package facts and licensed board collateral was got wrong once and corrected. WHY THIS PART: the article needs a programmable device in a package a prototype assembler can place, driven by a toolchain this repository can pin and execute. TQ144 is a 20 by 20 mm quad flat pack on a half-millimetre pitch — not a ball grid array — and the HX1K in it is carried end to end by an open synthesis, place-and-route and bitstream chain. No NDA fact is required to design with it." +// THE iCE40 LP/HX FAMILY, FROM THE VENDOR'S OWN DATA SHEET. Pulled and read 2026-08-13: iCE40 LP/HX +// Family Data Sheet, document FPGA-DS-02029, Version 4.3, February 2025. THE DOCUMENT NUMBER ITSELF +// CHANGED and that is a modelled fact rather than a detail: this repository previously cited DS1040 +// Version 3.4, October 2017, and the current revision's own change history records 'Changed +// document number from DS1040 to FPGA-DS-02029'. So the same subject is reachable under two +// identifiers, and a citation naming only the old one does not fail loudly — it resolves to a real, +// stale document. Two substantive changes land with the refresh. The revision history records that +// the dedicated inputs for Bank 1 of the HX1K in CB132 and TQ144 were modified, which is exactly +// the population this repository joins against Project IceStorm's roster, and the configuration +// signal SPI_SS_B was renamed SPI_SS. WHAT IS AND IS NOT TRANSCRIBED HERE, and the distinction is +// deliberate rather than cautious. Short factual statements a design has to consult — the ordering +// part number, the package type and its physical dimensions, the per-bank I/O population, the I/O +// standards the buffer supports — are carried. The full pin assignment table is NOT: it is +// thirty-odd sheets of a copyrighted vendor document, and the pin-to-tile map this repository +// actually needs for place and route comes from Project IceStorm, which is an independently +// governed open-source project with its own license and its own module. Those are different +// authorities and modelling one as the other would fuse a specification with a reverse-engineering +// of it. The same reasoning is already recorded for the Altra package, where the split between +// public package facts and licensed board collateral was got wrong once and corrected. WHY THIS +// PART: the article needs a programmable device in a package a prototype assembler can place, +// driven by a toolchain this repository can pin and execute. TQ144 is a 20 by 20 mm quad flat pack +// on a half-millimetre pitch — not a ball grid array — and the HX1K in it is carried end to end by +// an open synthesis, place-and-route and bitstream chain. No NDA fact is required to design with +// it. type Ice40Family = Ice40LowPower diff --git a/dag/extdeps/programmable_logic/ice40/supply.dag b/dag/extdeps/programmable_logic/ice40/supply.dag index 7842b9f8829..6ba8433c12c 100644 --- a/dag/extdeps/programmable_logic/ice40/supply.dag +++ b/dag/extdeps/programmable_logic/ice40/supply.dag @@ -8,8 +8,20 @@ import std.decl_ref { DeclarationRef, WholeDeclaration } import extdeps.uri { Uri, Https } import extdeps.external_authority { ExternalAuthority, ExternalModelScope, ExternalSubjectRef } - -data ice40_supply_note: String = "THE SUPPLY RAILS THE DEVICE REQUIRES, from Table 4.2 Recommended Operating Conditions and Table 4.12 sysI/O Recommended Operating Conditions of iCE40 LP/HX Family Data Sheet FPGA-DS-02029, Version 4.3, February 2025. It shares the subject module's citation anchor rather than minting a second one: these are rows of the same document about the same device, and a second anchor would be a second name for one authority. WHY THIS IS NOT A LIST OF VOLTAGES. Three facts in this table have shapes that a min/max pair cannot hold, and each was a fabrication waiting to happen. VPP_2V5's window DEPENDS ON THE CONFIGURATION MODE the board selects — 1.71 V to 3.46 V under slave SPI, but 2.30 V to 3.46 V under master SPI or NVCM configuration, and a narrower 2.30 V to 3.00 V while NVCM is being programmed — so a design that has not declared its configuration mode has not established what this rail must supply, and a single stored window would answer the question anyway. VPP_FAST has NO window at all: the document says leave it unconnected, which is not a voltage and must not be rendered as one. And two of the notes are constraints BETWEEN rails rather than properties of any one of them — like supplies at the same voltage must be the same supply, and an unused bank's VCCIO goes to VCC — which no per-rail record can carry." +// THE SUPPLY RAILS THE DEVICE REQUIRES, from Table 4.2 Recommended Operating Conditions and Table +// 4.12 sysI/O Recommended Operating Conditions of iCE40 LP/HX Family Data Sheet FPGA-DS-02029, +// Version 4.3, February 2025. It shares the subject module's citation anchor rather than minting a +// second one: these are rows of the same document about the same device, and a second anchor would +// be a second name for one authority. WHY THIS IS NOT A LIST OF VOLTAGES. Three facts in this table +// have shapes that a min/max pair cannot hold, and each was a fabrication waiting to happen. +// VPP_2V5's window DEPENDS ON THE CONFIGURATION MODE the board selects — 1.71 V to 3.46 V under +// slave SPI, but 2.30 V to 3.46 V under master SPI or NVCM configuration, and a narrower 2.30 V to +// 3.00 V while NVCM is being programmed — so a design that has not declared its configuration mode +// has not established what this rail must supply, and a single stored window would answer the +// question anyway. VPP_FAST has NO window at all: the document says leave it unconnected, which is +// not a voltage and must not be rendered as one. And two of the notes are constraints BETWEEN rails +// rather than properties of any one of them — like supplies at the same voltage must be the same +// supply, and an unused bank's VCCIO goes to VCC — which no per-rail record can carry. // EVERY RAIL THE PACKAGE BONDS OUT, under the document's own symbol. These are not an enum of // convenience: each is a distinct physical contact class on the package with its own count, and diff --git a/dag/extdeps/project_schedule/cpm_pert.dag b/dag/extdeps/project_schedule/cpm_pert.dag index 92e4ec1f131..978f8d4644b 100644 --- a/dag/extdeps/project_schedule/cpm_pert.dag +++ b/dag/extdeps/project_schedule/cpm_pert.dag @@ -17,9 +17,20 @@ data rcpsp_lower_bound_external_authority: ExternalAuthority = ExternalAuthority } } -data schedule_method_grounding_note: String = "GAO-16-89G, Schedule Assessment Guide: Best Practices for Project Schedules, is the cited scheduling authority. Best Practice 6 defines the critical path as the longest continuous sequence of activities and the program's earliest completion/minimum duration. Its schedule-risk guidance calls for three-point duration estimates (minimum, most likely, maximum). This module names those two standard method roles only; the roadmap instantiation supplies its own observed distribution and dependency graph." +// GAO-16-89G, Schedule Assessment Guide: Best Practices for Project Schedules, is the cited +// scheduling authority. Best Practice 6 defines the critical path as the longest continuous +// sequence of activities and the program's earliest completion/minimum duration. Its schedule-risk +// guidance calls for three-point duration estimates (minimum, most likely, maximum). This module +// names those two standard method roles only; the roadmap instantiation supplies its own observed +// distribution and dependency graph. -data resource_capacity_lower_bound_grounding_note: String = "Baptiste and Demassey, On linear lower bounds for the resource constrained project scheduling problem, European Journal of Operational Research 149(2), 2003, DOI 10.1016/S0377-2217(02)00763-4, grounds makespan lower bounds for RCPSP. For one declared renewable slot resource, if each activity continuously occupies one slot for its modeled duration, every feasible schedule is bounded below by ceil(total slot-time / slot capacity). Taking the maximum of that work-volume bound and the precedence critical-path bound remains a lower bound; it is not a resource-leveled schedule." +// Baptiste and Demassey, On linear lower bounds for the resource constrained project scheduling +// problem, European Journal of Operational Research 149(2), 2003, DOI +// 10.1016/S0377-2217(02)00763-4, grounds makespan lower bounds for RCPSP. For one declared +// renewable slot resource, if each activity continuously occupies one slot for its modeled +// duration, every feasible schedule is bounded below by ceil(total slot-time / slot capacity). +// Taking the maximum of that work-volume bound and the precedence critical-path bound remains a +// lower bound; it is not a resource-leveled schedule. type CriticalPathMethod {} type ThreePointEstimateMethod {} diff --git a/dag/extdeps/provisioning/ubuntu_install_media_fetch.dag b/dag/extdeps/provisioning/ubuntu_install_media_fetch.dag index c74dec20ff9..f6731f83c4e 100644 --- a/dag/extdeps/provisioning/ubuntu_install_media_fetch.dag +++ b/dag/extdeps/provisioning/ubuntu_install_media_fetch.dag @@ -28,7 +28,13 @@ data install_media_fetch_bash_emit_dissolution_trigger: Disposition = Terminal { reason: "in-process typed reconcile (shell.Test.IsFile, sha256sum_verify_via_shell, http.Client.DownloadToFile, shell.Move.File) replaces ShellProgram/serialize_bash - no shell text is built or serialized" } -data install_media_fetch_hash_convergence_note: String = "§3 convergence note: this file verifies fetched content against Canonical's published cryptographic SHA-256 digest (extdeps_external_authority_anchor above) via the Sha256Verify shape - a genuine external-authority digest check, distinct from std.content_hash's fnv1a64 internal structural fingerprint (dag/std/content_hash.dag, used for our own generated-artifact freshness, e.g. srv3_seeded_install_media's remaster sidecar). Do not unify the two: one is a cryptographic authority citation, the other is an internal identity fingerprint with no external-authority meaning." +// §3 convergence note: this file verifies fetched content against Canonical's published +// cryptographic SHA-256 digest (extdeps_external_authority_anchor above) via the Sha256Verify shape +// - a genuine external-authority digest check, distinct from std.content_hash's fnv1a64 internal +// structural fingerprint (dag/std/content_hash.dag, used for our own generated-artifact freshness, +// e.g. srv3_seeded_install_media's remaster sidecar). Do not unify the two: one is a cryptographic +// authority citation, the other is an internal identity fingerprint with no external-authority +// meaning. type InstallMediaFetchObservation = InstallMediaFetchAbsent @@ -41,7 +47,15 @@ type InstallMediaFetchReconcileOutcome | InstallMediaFetchFetched { path: NonEmptyStr, mirror_url: NonEmptyStr } | InstallMediaFetchRefused { reason: NonEmptyStr } -data refusal_shape_doc: String = "DESIGN §5 refusal-shape fix (cursor review, PR #6596): the fetch reconcile path now matches Sha256VerifyVerdict directly instead of collapsing it through a Bool predicate, so a mktemp/checksum-write infra failure stays distinguishable from a genuine digest mismatch (InstallMediaFetchVerifyInfraFailure vs InstallMediaFetchHashMismatch), and a post-download shell.Move.File failure aborts with its own typed refusal instead of falling through the mirror fold to a misleading 'all mirrors unreachable'. The Bool-collapsing install_media_hash_matches helper was deleted (cursor review, PR #6596) - its two wet witnesses now assert on observe_install_media_fetch's Sha256VerifyVerdict-derived outcome directly, so no predicate-dissolution surface remains anywhere in this module." +// DESIGN §5 refusal-shape fix (cursor review, PR #6596): the fetch reconcile path now matches +// Sha256VerifyVerdict directly instead of collapsing it through a Bool predicate, so a +// mktemp/checksum-write infra failure stays distinguishable from a genuine digest mismatch +// (InstallMediaFetchVerifyInfraFailure vs InstallMediaFetchHashMismatch), and a post-download +// shell.Move.File failure aborts with its own typed refusal instead of falling through the mirror +// fold to a misleading 'all mirrors unreachable'. The Bool-collapsing install_media_hash_matches +// helper was deleted (cursor review, PR #6596) - its two wet witnesses now assert on +// observe_install_media_fetch's Sha256VerifyVerdict-derived outcome directly, so no +// predicate-dissolution surface remains anywhere in this module. type MirrorAttempt = MirrorTerminal { outcome: InstallMediaFetchReconcileOutcome } @@ -50,7 +64,13 @@ type MirrorAttempt type MirrorFoldAcc = MirrorFoldAcc { terminal: InstallMediaFetchReconcileOutcome?, saw_mismatch: Bool } -data mirror_fold_refusal_doc: String = "DESIGN §5 fix (cursor review, PR #6596): install_media_fetch_try_mirror previously returned `none` for BOTH an unreachable mirror (transport miss, keep trying) AND a Sha256Mismatch (a genuine digest failure), so a run where every mirror downloaded but none verified fell through the fold to 'all mirrors unreachable' - a state-space conflation between reachability and digest correctness. MirrorAttempt now names the three outcomes distinctly, and the fold tracks saw_mismatch so the final refusal reads 'digest verification failed' when that's what actually happened." +// DESIGN §5 fix (cursor review, PR #6596): install_media_fetch_try_mirror previously returned +// `none` for BOTH an unreachable mirror (transport miss, keep trying) AND a Sha256Mismatch (a +// genuine digest failure), so a run where every mirror downloaded but none verified fell through +// the fold to 'all mirrors unreachable' - a state-space conflation between reachability and digest +// correctness. MirrorAttempt now names the three outcomes distinctly, and the fold tracks +// saw_mismatch so the final refusal reads 'digest verification failed' when that's what actually +// happened. // The presence probe is OutcomeIsData: a non-zero `test -f` here means the media is ABSENT, which is one of this fn's four declared answers (InstallMediaFetchAbsent), not a fault. The type already draws the distinction the arm formalises — InstallMediaFetchVerifyInfraFailure is the separate 'the probe itself broke' arm — so subject-state and probe-breakage were never conflated here; only the effect layer's glyph conflated them. Admissible under the §5 guard: this fn returns InstallMediaFetchObservation, a typed observation whose arms ARE the adjudication, and the exit selects among them. fn observe_install_media_fetch(path: NonEmptyStr, expected_hash: NonEmptyStr) -> InstallMediaFetchObservation { diff --git a/dag/extdeps/provisioning/ubuntu_seeded_install_media_remaster.dag b/dag/extdeps/provisioning/ubuntu_seeded_install_media_remaster.dag index 04cdf4b9b25..def21330c3e 100644 --- a/dag/extdeps/provisioning/ubuntu_seeded_install_media_remaster.dag +++ b/dag/extdeps/provisioning/ubuntu_seeded_install_media_remaster.dag @@ -49,7 +49,10 @@ data install_media_remaster_chmod_access_upsert_dissolution_trigger: Disposition data install_media_remaster_probe_refusal_shape_doc: String = "DESIGN §5 refusal-shape fix (cursor review, PR #6596): install_media_remaster_probe_verdict now returns a 3-way InstallMediaRemasterProbeVerdict (Valid|Stale|InfraFailure\{reason\}) instead of collapsing the mktemp probe into a Bool, so a shell.Mktemp.Dir failure stays distinguishable from a genuine content/grub/seed mismatch (InstallMediaRemasterVerifyInfraFailure vs InstallMediaRemasterStale). observe_install_media_remaster matches this verdict directly, and install_media_remaster_reconcile refuses fail-closed on infra failure instead of silently treating an indeterminate probe as stale-and-rebuild." -data install_media_remaster_boot_fidelity_doc: String = "xorriso -boot_image any replay modifies the stock ISO by mapping in only the NoCloud seed dir and grub.cfg, so the arm64 El Torito/ESP boot structure survives; an -as mkisofs rebuild destroyed it and the media stopped booting (root-caused live on srv4, PR #7027); instance-id derives from the artifact's nocloud dir basename, never a hardcoded host name" +// xorriso -boot_image any replay modifies the stock ISO by mapping in only the NoCloud seed dir and +// grub.cfg, so the arm64 El Torito/ESP boot structure survives; an -as mkisofs rebuild destroyed it +// and the media stopped booting (root-caused live on srv4, PR #7027); instance-id derives from the +// artifact's nocloud dir basename, never a hardcoded host name data el_torito_authority: ExternalAuthority = ExternalAuthority { uri: Uri { @@ -65,7 +68,10 @@ data esp_boot_structure_authority: ExternalAuthority = ExternalAuthority { } } -// The boot structure the replay preserves is owned by two specs the fidelity doc names in prose: the El Torito Bootable CD-ROM Format Specification 1.0 (Phoenix/IBM 1995; anchor is a hosted copy of the original) and the UEFI spec's EFI System Partition / media-access section (UEFI 2.10 §13). These are the entities the mkisofs rebuild violated. +// The boot structure the replay preserves is owned by two specs the fidelity doc names in prose: +// the El Torito Bootable CD-ROM Format Specification 1.0 (Phoenix/IBM 1995; anchor is a hosted copy +// of the original) and the UEFI spec's EFI System Partition / media-access section (UEFI 2.10 §13). +// These are the entities the mkisofs rebuild violated. type InstallMediaRemasterProbeVerdict = InstallMediaRemasterProbeValid | InstallMediaRemasterProbeStale @@ -140,7 +146,12 @@ fn observe_install_media_remaster(artifact: UbuntuSeededInstallMediaArtifactRow) } } -// grep exits non-zero when the pattern is ABSENT, and absence is precisely the question this fn asks — it is the idempotence probe that decides whether to substitute or leave the file alone. Both answers are ordinary: on a fresh image the cmdline is missing and gets inserted, on a re-run it is present and nothing happens. The §5 guard holds on the belt_program_available shape: the Bool is consumed as the branch discriminator, never discarded. A grep that could not RUN is a different event and does not travel this arm. +// grep exits non-zero when the pattern is ABSENT, and absence is precisely the question this fn +// asks — the idempotence probe deciding whether to substitute or leave the file alone. Both answers +// are ordinary: on a fresh image the cmdline is missing and gets inserted; on a re-run it is present +// and nothing happens. The §5 guard holds on the belt_program_available shape: the Bool is consumed +// as the branch discriminator, never discarded. A grep that could not RUN is a different event and +// does not travel this arm. fn install_media_remaster_ensure_grub_cmdline(grub_cfg: NonEmptyStr, cmdline: NonEmptyStr) -> Bool { let already_present = grep.Grep.MatchesFixedString(pattern: cmdline, path: grub_cfg, expect: OutcomeIsData).matches let substituted = if already_present { diff --git a/dag/extdeps/publication.dag b/dag/extdeps/publication.dag index 9deed1f437d..87e65a7005d 100644 --- a/dag/extdeps/publication.dag +++ b/dag/extdeps/publication.dag @@ -4,7 +4,12 @@ import std.integer { Int } import std.logic { Bool } import std.types { NonEmptyStr, String } -data publication_status_axis_note: String = "PURE PUBLICATION VOCABULARY — no upstream document subject of its own (same class as extdeps.external_authority and extdeps.uri). SpecificationDocumentDate and DocumentPublicationStatus are shared types. Document rows, anchors, and status values live on per-document modules. Machinery admission is via external_authority_machinery_exempt_module_paths in cli_run.rs. Read observations live in gunbc.specification_citation_read_provenance; the carriage rule lives in gunbc.upstream_document_carriage." +// PURE PUBLICATION VOCABULARY — no upstream document subject of its own (same class as +// extdeps.external_authority and extdeps.uri). SpecificationDocumentDate and +// DocumentPublicationStatus are shared types. Document rows, anchors, and status values live on +// per-document modules. Machinery admission is via external_authority_machinery_exempt_module_paths +// in cli_run.rs. Read observations live in gunbc.specification_citation_read_provenance; the +// carriage rule lives in gunbc.upstream_document_carriage. type SpecificationDocumentDate { year: Int diff --git a/dag/extdeps/realization/artifact_store_fs.dag b/dag/extdeps/realization/artifact_store_fs.dag index c0b2f456c51..25305aa2e2a 100644 --- a/dag/extdeps/realization/artifact_store_fs.dag +++ b/dag/extdeps/realization/artifact_store_fs.dag @@ -26,7 +26,27 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data artifact_store_fs_transport_note: String = "P2 host-transport half (witness-realization plan): the filesystem realization of std.artifact_store's persistent tier. The keyed path ENCODES identity - store_root/.artifact - so presence-at-path is a ContentKeyed hit for exactly that identity, which is NOT the #6352 existence-keying (that red keyed on OUTPUT presence while the INPUTS went unhashed; here the path IS the input hash). Layering: this module is one transport handler bound to the pure spec (std.artifact_store, the single authority); a second handler (e.g. remote CAS) binds the same shape. Grant refusals surface as typed ArtifactFs*Refused variants (verb, target, frame) — never widened to miss/false/[] (DESIGN 5). SCOPE (corrected 2026-07-29, operator-flagged on PR 7385): this sentence previously claimed the cited Filesystem service exposed no Delete/List and that SpacePacked enforcement was therefore unrealizable. That was FALSE and had been for a long time - extdeps.filesystem.filesystem_io declares both operations, artifact_fs_delete and artifact_fs_list below realize them, and artifact_store_fs_witness_test exercises the put-list-delete-list round trip green. It is a parallel-ledger note that sat above the functions refuting it and was believed over them (it was then propagated into a second module before being caught). The REAL gap it obscured was narrower: std.artifact_store's evict_until_within_budget computed a StorePutReceipt carrying evicted keys, and nothing passed those keys to artifact_fs_delete - the eviction DECISION was computed and discarded at the host boundary, so the disk tier grew unbounded because no caller invoked the verb, not because the verb was missing. apply_store_evictions below closes that: it applies those evicted keys through this transport with every per-key outcome typed and counted, so a grant refusal is a recorded EvictionRefused rather than a silent skip." +// P2 host-transport half (witness-realization plan): the filesystem realization of +// std.artifact_store's persistent tier. The keyed path ENCODES identity - +// store_root/.artifact - so presence-at-path is a ContentKeyed hit for exactly that +// identity, which is NOT the #6352 existence-keying (that red keyed on OUTPUT presence while the +// INPUTS went unhashed; here the path IS the input hash). Layering: this module is one transport +// handler bound to the pure spec (std.artifact_store, the single authority); a second handler (e.g. +// remote CAS) binds the same shape. Grant refusals surface as typed ArtifactFs*Refused variants +// (verb, target, frame) — never widened to miss/false/[] (DESIGN 5). SCOPE (corrected 2026-07-29, +// operator-flagged on PR 7385): this sentence previously claimed the cited Filesystem service +// exposed no Delete/List and that SpacePacked enforcement was therefore unrealizable. That was +// FALSE and had been for a long time - extdeps.filesystem.filesystem_io declares both operations, +// artifact_fs_delete and artifact_fs_list below realize them, and artifact_store_fs_witness_test +// exercises the put-list-delete-list round trip green. It is a parallel-ledger note that sat above +// the functions refuting it and was believed over them (it was then propagated into a second module +// before being caught). The REAL gap it obscured was narrower: std.artifact_store's +// evict_until_within_budget computed a StorePutReceipt carrying evicted keys, and nothing passed +// those keys to artifact_fs_delete - the eviction DECISION was computed and discarded at the host +// boundary, so the disk tier grew unbounded because no caller invoked the verb, not because the +// verb was missing. apply_store_evictions below closes that: it applies those evicted keys through +// this transport with every per-key outcome typed and counted, so a grant refusal is a recorded +// EvictionRefused rather than a silent skip. fn artifact_fs_path(store_root: String, key: ContentHash) -> String { concat(store_root, "/", key, ".artifact") @@ -126,9 +146,42 @@ func artifact_fs_list(store_root: String) -> ArtifactFsListResult { } } -data artifact_store_fs_consumer_frontier_note: String = "DECLARED ZERO-CONSUMER FRONTIER, stated so the mechanism is not read as a closed loop (codex review 44298 on PR 7407). Every operation in this module — artifact_fs_put, artifact_fs_get, artifact_fs_delete, artifact_fs_list, and apply_store_evictions — has ZERO non-test callers in the corpus today; the only invocations are witnesses. That is the module's pre-existing state, not something the eviction work introduced, and it is worth writing down because an undeclared zero is exactly the coverage-by-illusion shape: a reader sees a complete-looking transport and assumes something upstream drives it. CORRECTION TO THE FRAMING THE EVICTION WORK WAS FIRST DESCRIBED UNDER: because nothing writes through this transport in production, there is no live divergence between the packed store's accounting and the bytes on disk, and no unbounded production disk growth. What actually existed was a LATENT fail-open — the receipt named evicted keys and no handler applied them — which would have become a live divergence at the moment a first consumer wired the write path. Fixing it before that consumer exists is the model-before-implement order this repo works in, not a claim that a running leak was stopped. WHY NO CONSUMER LANDS HERE: wiring one means editing cli_run, which the operator explicitly split out of the provider-contract lane to preserve the model-first-consumer-later boundary; a consumer added here to satisfy a coverage argument would reverse that ruling. DISSOLUTION TRIGGER: this note deletes when the first production write path binds this transport to std.artifact_store — the materialization-provider consumer flip named in gunbc.materialization_provider_targets — at which point apply_store_evictions must be called on that path and the receipt-to-disk loop becomes executable rather than modeled. Until then this is a modeled transport with counted debt, honestly labelled." - -data artifact_store_fs_eviction_note: String = "The eviction application half: std.artifact_store DECIDES what to evict (a pure fold returning the evicted keys in its receipt); this function APPLIES that decision to the persistent tier. It takes the EVICTED KEY LIST rather than a particular receipt type, so it composes with both carriers the store exposes — evict_until_within_budget's StorePutReceipt.evicted and store_put's StorePutAdmitted arm (the totality split that landed in PR 7406) — without this transport having to know which one the caller folded. The split is the section 2 pure-spec/host-effect seam - the store stays a pure fold with no filesystem reach, and every transport handler bound to it (this one; a remote CAS later) applies the same receipt. Per-key outcomes are TYPED AND KEPT, never reduced to a success Bool: a grant refusal becomes EvictionRefused carrying the verb/target/frame the transport reported, so a store whose evictions are being silently denied is COUNTABLE (eviction_refusal_count) rather than appearing to pack correctly while the disk grows. Deliberately NOT fail-fast on the first refusal: siblings continue and each records its own outcome, so one denied key does not hide the disposition of the rest - the stopped-line ledger shape, not an absorbing skip." +// DECLARED ZERO-CONSUMER FRONTIER, stated so the mechanism is not read as a closed loop (codex +// review 44298 on PR 7407). Every operation in this module — artifact_fs_put, artifact_fs_get, +// artifact_fs_delete, artifact_fs_list, and apply_store_evictions — has ZERO non-test callers in +// the corpus today; the only invocations are witnesses. That is the module's pre-existing state, +// not something the eviction work introduced, and it is worth writing down because an undeclared +// zero is exactly the coverage-by-illusion shape: a reader sees a complete-looking transport and +// assumes something upstream drives it. CORRECTION TO THE FRAMING THE EVICTION WORK WAS FIRST +// DESCRIBED UNDER: because nothing writes through this transport in production, there is no live +// divergence between the packed store's accounting and the bytes on disk, and no unbounded +// production disk growth. What actually existed was a LATENT fail-open — the receipt named evicted +// keys and no handler applied them — which would have become a live divergence at the moment a +// first consumer wired the write path. Fixing it before that consumer exists is the +// model-before-implement order this repo works in, not a claim that a running leak was stopped. WHY +// NO CONSUMER LANDS HERE: wiring one means editing cli_run, which the operator explicitly split out +// of the provider-contract lane to preserve the model-first-consumer-later boundary; a consumer +// added here to satisfy a coverage argument would reverse that ruling. DISSOLUTION TRIGGER: this +// note deletes when the first production write path binds this transport to std.artifact_store — +// the materialization-provider consumer flip named in gunbc.materialization_provider_targets — at +// which point apply_store_evictions must be called on that path and the receipt-to-disk loop +// becomes executable rather than modeled. Until then this is a modeled transport with counted debt, +// honestly labelled. + +// The eviction application half: std.artifact_store DECIDES what to evict (a pure fold returning +// the evicted keys in its receipt); this function APPLIES that decision to the persistent tier. It +// takes the EVICTED KEY LIST rather than a particular receipt type, so it composes with both +// carriers the store exposes — evict_until_within_budget's StorePutReceipt.evicted and store_put's +// StorePutAdmitted arm (the totality split that landed in PR 7406) — without this transport having +// to know which one the caller folded. The split is the section 2 pure-spec/host-effect seam - the +// store stays a pure fold with no filesystem reach, and every transport handler bound to it (this +// one; a remote CAS later) applies the same receipt. Per-key outcomes are TYPED AND KEPT, never +// reduced to a success Bool: a grant refusal becomes EvictionRefused carrying the verb/target/frame +// the transport reported, so a store whose evictions are being silently denied is COUNTABLE +// (eviction_refusal_count) rather than appearing to pack correctly while the disk grows. +// Deliberately NOT fail-fast on the first refusal: siblings continue and each records its own +// outcome, so one denied key does not hide the disposition of the rest - the stopped-line ledger +// shape, not an absorbing skip. type ArtifactEvictionStep = EvictionDeleted { key: ContentHash } diff --git a/dag/extdeps/realization/emit_on_demand_host.dag b/dag/extdeps/realization/emit_on_demand_host.dag index c1ea86870e8..559a9c33cc7 100644 --- a/dag/extdeps/realization/emit_on_demand_host.dag +++ b/dag/extdeps/realization/emit_on_demand_host.dag @@ -22,11 +22,40 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data emit_on_demand_host_note: String = "P3 host-transport helpers (witness-realization kernel): the effective cache path is root / emit_on_demand_key(NativeArtifact) / artifact_realization_digest(materialized workspace files + build argv) / resolved_build_context_identity. The outer closure key groups one authored computation; the input-realization digest makes changed emitted source, dispatcher suffix, manifest, or build argv select a cold workspace even when the inferred tree is byte-identical. The final host-observed segment combines the resolved build tools, the exact constructed subprocess environment, and every Cargo configuration file Cargo can discover from the workspace. run_emit_host_cached supplies the first three segments. The HAND-RUST emit_host_run_transport_cached boundary materializes the exact files at that input-realization root, constructs and hashes the only environment admitted to build/run commands, resolves and probes the build toolchain with that root as current_dir (so workspace rust-toolchain selection is observed), observes Cargo configuration, and appends the context identity before consulting .native_ready. Warm hits skip cargo build via extdeps.cargo and set compile_skipped. Eviction targets the outer workspace through emit_host_native_cache_evict — NOT raw shell.Remove — so it removes every realization and context child and shares the durable rebase authority with the transport." +// P3 host-transport helpers (witness-realization kernel): the effective cache path is root / +// emit_on_demand_key(NativeArtifact) / artifact_realization_digest(materialized workspace files + +// build argv) / resolved_build_context_identity. The outer closure key groups one authored +// computation; the input-realization digest makes changed emitted source, dispatcher suffix, +// manifest, or build argv select a cold workspace even when the inferred tree is byte-identical. +// The final host-observed segment combines the resolved build tools, the exact constructed +// subprocess environment, and every Cargo configuration file Cargo can discover from the workspace. +// run_emit_host_cached supplies the first three segments. The HAND-RUST +// emit_host_run_transport_cached boundary materializes the exact files at that input-realization +// root, constructs and hashes the only environment admitted to build/run commands, resolves and +// probes the build toolchain with that root as current_dir (so workspace rust-toolchain selection +// is observed), observes Cargo configuration, and appends the context identity before consulting +// .native_ready. Warm hits skip cargo build via extdeps.cargo and set compile_skipped. Eviction +// targets the outer workspace through emit_host_native_cache_evict — NOT raw shell.Remove — so it +// removes every realization and context child and shares the durable rebase authority with the +// transport. -data emit_on_demand_host_concurrent_same_key_note: String = "Concurrent same-key story for persistent workspaces: identical outer closure key, input-realization digest, AND resolved build-context identity share a workspace — benign-by-identity because those segments cover the exact materialized workspace files, build argv, resolved tools, constructed environment, and Cargo configuration that realize them. A changed emitter/dispatcher realization, compiler, admitted environment row, or Cargo configuration cannot share the ready marker even when its inferred-tree closure digest is unchanged. Concurrent cold builds of identical inputs may still race before .native_ready; no atomic claim exists in the seed HAND-RUST transport yet. Dissolve-on: the self-emitted transport adds flock/claim when witness-family concurrency requires it." +// Concurrent same-key story for persistent workspaces: identical outer closure key, +// input-realization digest, AND resolved build-context identity share a workspace — +// benign-by-identity because those segments cover the exact materialized workspace files, build +// argv, resolved tools, constructed environment, and Cargo configuration that realize them. A +// changed emitter/dispatcher realization, compiler, admitted environment row, or Cargo +// configuration cannot share the ready marker even when its inferred-tree closure digest is +// unchanged. Concurrent cold builds of identical inputs may still race before .native_ready; no +// atomic claim exists in the seed HAND-RUST transport yet. Dissolve-on: the self-emitted transport +// adds flock/claim when witness-family concurrency requires it. -data emit_on_demand_host_seed_deferral_note: String = "Pure Bootstrap receipt for the bounded existing HAND-RUST boundary: no new Rust file or parallel key authority is admitted. This seed observation/apply arm is explicitly deferred to ROADMAP row 'Make native materialization the shared execution kernel' (docs/plans/witness-realization-plan.md P3/P6) and the concrete dag/gunbc/v1/v1_deletion_plan.dag ^witness_realization_kernel deletion row. Delete it when the self-emitted transport consumes ResolvedBuildContext and the dispatcher-change, environment-change, and cold/warm agreement witnesses stay green with the Rust helpers removed." +// Pure Bootstrap receipt for the bounded existing HAND-RUST boundary: no new Rust file or parallel +// key authority is admitted. This seed observation/apply arm is explicitly deferred to ROADMAP row +// 'Make native materialization the shared execution kernel' (docs/plans/witness-realization-plan.md +// P3/P6) and the concrete dag/gunbc/v1/v1_deletion_plan.dag ^witness_realization_kernel deletion +// row. Delete it when the self-emitted transport consumes ResolvedBuildContext and the +// dispatcher-change, environment-change, and cold/warm agreement witnesses stay green with the Rust +// helpers removed. type ObservedToolIdentity { tool_name: NonEmptyStr @@ -65,7 +94,12 @@ type ToolchainIdentityOutcome identity: ContentHash } -data emit_host_structural_identity_refusal_note: String = "emit_host_structural_identity_outcome refuses cryptographic ContentHash families at the structural projection boundary (review 45661). Sha256Hash, Sha1Hash, and Sha512Hash cannot collapse to a shared fnv1a64 sentinel — that was an absorbing fallback hiding distinct digests behind one cache key. Structural-only inputs proceed; crypto arms return EmitHostStructuralIdentityRefusedCrypto with component and identity (family is derived via content_hash_family, not stored in parallel — review 45721)." +// emit_host_structural_identity_outcome refuses cryptographic ContentHash families at the +// structural projection boundary (review 45661). Sha256Hash, Sha1Hash, and Sha512Hash cannot +// collapse to a shared fnv1a64 sentinel — that was an absorbing fallback hiding distinct digests +// behind one cache key. Structural-only inputs proceed; crypto arms return +// EmitHostStructuralIdentityRefusedCrypto with component and identity (family is derived via +// content_hash_family, not stored in parallel — review 45721). fn emit_host_structural_identity_outcome( component: NonEmptyStr, diff --git a/dag/extdeps/realization/eval_memo.dag b/dag/extdeps/realization/eval_memo.dag index 412dd236b62..d4c151ae335 100644 --- a/dag/extdeps/realization/eval_memo.dag +++ b/dag/extdeps/realization/eval_memo.dag @@ -28,7 +28,40 @@ import std.cache_interface { import std.disposition { Disposition, Scaffold, RealizationDispatch } import std.decl_ref { DeclarationRef, WholeDeclaration } -data eval_memo_note: String = "The eval-frame memo: the ladder's single-site discharge provider, realized in the seed interpreter (v1_interpreter.rs eval_pure_named_call / EvalCallMemo). Serves every repeated pure named-fn demand from its first evaluation within one InterpContext. SOUNDNESS IS CONSTRUCTION, NOT PROBABILITY: buckets by the demand-ledger key (fn identity x argument content identity) but serves ONLY after the stored call's argument names AND values verify equal (Value equality, the one equality authority) — a hash collision degrades to recompute, never to a wrong value. A memo hit still records the DEMAND in the ledger: plurality is the fact the receipt counts; the provider changes its cost, never its count (so the multi-site rewire worklist survives discharge intact). Release is ReleasedAtProviderScopeExit and the provider scope is the WITNESS FRAME (corrected 2026-07-10, same-day regression owned at confession grade: the first landing evicted at ctx lifetime while batch surfaces share one ctx across an entry's witnesses, so retained argument+result values were byte-unbounded by construction — measured single-witness plateau ~3.4GiB vs six-witness climb past ~20GiB to SIGKILL on a quiet host, eager-ram-612's A/B; batch surfaces now call eval_call_memo_frame_exit after each claim fn, counters cumulative). Cross-witness serving is an outer-frame promotion that must arrive as a conscious provider row with byte-bounded admission, never a default; the count cap (1M entries) is admission at the wrong unit and is slated to become a DECLARED byte cap consuming the single value-size authority (one fn beside Value; merry-owl-649's space-lens predictor binds to the same authority). Admission stops at the cap with the refusal COUNTED (memo_overflow), never silent. COUNTER INVARIANT (review-pinned 2026-07-10): a miss means the call was NOT served, so it evaluated — a cap-refused store attempt is still a miss; overflow is a subset of misses, and hits + misses equals the keyed Ok-resulting calls through the memo path, including under overflow (misses is NOT entries-stored). Default ON in every binary; GUNBC_EVAL_MEMO=0 is a diagnostic realization switch (recompute instead of serve, semantics identical) and the receipt discloses memo_hits/memo_misses so a disabled or ineffective memo is visible, never assumed working. The switch is PER-CTX (read at InterpContext construction; set_eval_call_memo_enabled), because a covering provider at an outer frame legitimately quiets an inner provider's hit counters: an inner door's by-execution receipt suite (the parse-table MemoTier amortization tests) pins this provider off on its own ctxs so pass-2 demands re-execute and the door's counters keep discriminating — provider-attribution receipts select their realization explicitly, they do not race a process latch. MEASURED (2026-07-10, artifact-regen workload): single-site duplicated keys 7285 -> 505 (93.1 percent discharged), wasted_ms 586 -> 57, misses == distinct keys (every distinct computation evaluated exactly once), regenerated artifacts byte-identical with the memo on and off — the purity oracle by execution." +// The eval-frame memo: the ladder's single-site discharge provider, realized in the seed +// interpreter (v1_interpreter.rs eval_pure_named_call / EvalCallMemo). Serves every repeated pure +// named-fn demand from its first evaluation within one InterpContext. SOUNDNESS IS CONSTRUCTION, +// NOT PROBABILITY: buckets by the demand-ledger key (fn identity x argument content identity) but +// serves ONLY after the stored call's argument names AND values verify equal (Value equality, the +// one equality authority) — a hash collision degrades to recompute, never to a wrong value. A memo +// hit still records the DEMAND in the ledger: plurality is the fact the receipt counts; the +// provider changes its cost, never its count (so the multi-site rewire worklist survives discharge +// intact). Release is ReleasedAtProviderScopeExit and the provider scope is the WITNESS FRAME +// (corrected 2026-07-10, same-day regression owned at confession grade: the first landing evicted +// at ctx lifetime while batch surfaces share one ctx across an entry's witnesses, so retained +// argument+result values were byte-unbounded by construction — measured single-witness plateau +// ~3.4GiB vs six-witness climb past ~20GiB to SIGKILL on a quiet host, eager-ram-612's A/B; batch +// surfaces now call eval_call_memo_frame_exit after each claim fn, counters cumulative). +// Cross-witness serving is an outer-frame promotion that must arrive as a conscious provider row +// with byte-bounded admission, never a default; the count cap (1M entries) is admission at the +// wrong unit and is slated to become a DECLARED byte cap consuming the single value-size authority +// (one fn beside Value; merry-owl-649's space-lens predictor binds to the same authority). +// Admission stops at the cap with the refusal COUNTED (memo_overflow), never silent. COUNTER +// INVARIANT (review-pinned 2026-07-10): a miss means the call was NOT served, so it evaluated — a +// cap-refused store attempt is still a miss; overflow is a subset of misses, and hits + misses +// equals the keyed Ok-resulting calls through the memo path, including under overflow (misses is +// NOT entries-stored). Default ON in every binary; GUNBC_EVAL_MEMO=0 is a diagnostic realization +// switch (recompute instead of serve, semantics identical) and the receipt discloses +// memo_hits/memo_misses so a disabled or ineffective memo is visible, never assumed working. The +// switch is PER-CTX (read at InterpContext construction; set_eval_call_memo_enabled), because a +// covering provider at an outer frame legitimately quiets an inner provider's hit counters: an +// inner door's by-execution receipt suite (the parse-table MemoTier amortization tests) pins this +// provider off on its own ctxs so pass-2 demands re-execute and the door's counters keep +// discriminating — provider-attribution receipts select their realization explicitly, never racing +// a process latch. MEASURED (2026-07-10, artifact-regen workload): single-site duplicated keys 7285 +// -> 505 (93.1 percent discharged), wasted_ms 586 -> 57, misses == distinct keys (every distinct +// computation evaluated exactly once), regenerated artifacts byte-identical with the memo on and +// off — the purity oracle by execution. data eval_memo_seed_handler_dissolution_trigger: Disposition = Scaffold { dissolves_to: RealizationDispatch, diff --git a/dag/extdeps/realization/frame_boundary_simulated.dag b/dag/extdeps/realization/frame_boundary_simulated.dag index b5747eb8da2..388da4f4538 100644 --- a/dag/extdeps/realization/frame_boundary_simulated.dag +++ b/dag/extdeps/realization/frame_boundary_simulated.dag @@ -14,7 +14,19 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data frame_boundary_simulated_note: String = "The Simulated boundary MATERIALIZATION handler (realization side of std.frame_boundary, docs/plans/effect-namespace-grants.md 6, owned silent-ibex-417): the always-available baseline that closes a frame's layers by CONVENTION -- 'everything outside the layer is assumed-absent / offline-configured', with nothing to provision. It is one of N boundary handlers (netns/container/seccomp are the ByConstruction siblings that spawn a real boundary); §3 keeps the mechanism HERE in extdeps and the agnostic spec in std.frame_boundary. Every closure it produces is honestly LifecycleByConvention -- an offline flag / vendored deps, not a kernel wall (a build subprocess is not structurally prevented from reaching the layer, only configured not to). A consumer that requires LifecycleByConstruction (a real enforced native run) will REFUSE a Simulated boundary via std.frame_boundary.boundary_admits grade-meet, never silently accept it. dissolve-on: netns/container handlers land as the ByConstruction upgrade (boundary PROVISIONING through the realization kernel), constructing a stronger-graded BoundaryMaterialization over the same spec." +// The Simulated boundary MATERIALIZATION handler (realization side of std.frame_boundary, +// docs/plans/effect-namespace-grants.md 6, owned silent-ibex-417): the always-available baseline +// that closes a frame's layers by CONVENTION -- 'everything outside the layer is assumed-absent / +// offline-configured', with nothing to provision. It is one of N boundary handlers +// (netns/container/seccomp are the ByConstruction siblings that spawn a real boundary); §3 keeps +// the mechanism HERE in extdeps and the agnostic spec in std.frame_boundary. Every closure it +// produces is honestly LifecycleByConvention -- an offline flag / vendored deps, not a kernel wall +// (a build subprocess is not structurally prevented from reaching the layer, only configured not +// to). A consumer that requires LifecycleByConstruction (a real enforced native run) will REFUSE a +// Simulated boundary via std.frame_boundary.boundary_admits grade-meet, never silently accept it. +// dissolve-on: netns/container handlers land as the ByConstruction upgrade (boundary PROVISIONING +// through the realization kernel), constructing a stronger-graded BoundaryMaterialization over the +// same spec. fn simulated_close(tree: NamespaceTree) -> BoundaryClosure { BoundaryClosure { tree: tree, grade: LifecycleByConvention } diff --git a/dag/extdeps/realization/native_artifact_cache.dag b/dag/extdeps/realization/native_artifact_cache.dag index 6c279496b90..02a2a7349a0 100644 --- a/dag/extdeps/realization/native_artifact_cache.dag +++ b/dag/extdeps/realization/native_artifact_cache.dag @@ -31,7 +31,18 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data native_artifact_cache_note: String = "The durable native-artifact cache (witness-realization P3 warm tier made DECLARED): effective cargo workspace = root / emit_on_demand_key / artifact_realization_digest(materialized workspace files + build argv) / resolved_build_context_identity, warm-skipped by the child workspace's .native_ready marker. The two inner digests close the srv2-05 cross-branch stale-hit class: a changed emitter output, dispatcher suffix, toolchain manifest, build invocation, actually resolved compiler, admitted subprocess environment, or Cargo configuration cannot read the prior realization's marker even when the inferred-tree closure key is unchanged. Self-hosted runners wipe only RUNNER_TEMP, so the root persists across recurring CI invocations. Cold control forces full cold rebuilds, widen-only. HONEST SCOPE: release is manual (ReleasedManually) and capacity is CapacityUnobserved -- nothing enforces a byte cap on the persistent tier, and no ceiling has been established for it either. Dissolve-on: the P2 store budget realization reaches this tier, then eviction upgrades to SizeBounded." +// The durable native-artifact cache (witness-realization P3 warm tier made DECLARED): effective +// cargo workspace = root / emit_on_demand_key / artifact_realization_digest(materialized workspace +// files + build argv) / resolved_build_context_identity, warm-skipped by the child workspace's +// .native_ready marker. The two inner digests close the srv2-05 cross-branch stale-hit class: a +// changed emitter output, dispatcher suffix, toolchain manifest, build invocation, resolved +// compiler, admitted subprocess environment, or Cargo configuration cannot read the prior +// realization's marker even with an unchanged inferred-tree closure key. Self-hosted runners wipe +// only RUNNER_TEMP, so the root persists across recurring CI invocations. Cold control forces full +// cold rebuilds, widen-only. HONEST SCOPE: release is manual (ReleasedManually) and capacity is +// CapacityUnobserved — nothing enforces a byte cap on the persistent tier and no ceiling has been +// established. Dissolve-on: the P2 store budget realization reaches this tier, then eviction +// upgrades to SizeBounded. data native_artifact_cache_id: CacheInterfaceId = "native_artifact_cache" diff --git a/dag/extdeps/render/ansi.dag b/dag/extdeps/render/ansi.dag index e66c559d891..274cdce477f 100644 --- a/dag/extdeps/render/ansi.dag +++ b/dag/extdeps/render/ansi.dag @@ -99,7 +99,19 @@ fn ansi_erase_entire_line() -> String { concat(csi_esc, "2K") } -data ansi_content_admissible_control_note: String = "LF and TAB are C0 controls but they are not CURSOR controls, and conflating the two cost a located diagnosis. The predicate this replaces (ansi_text_contains_terminal_control, deleted with its last consumer rather than left dead) refused on ANY roster member, so it refused on any multi-line or tab-bearing text — and a captured `systemctl status` diagnostic is multi-line by construction. The srv1 deploy refusal of 2026-07-29 therefore rendered as 'rendered text cannot contain terminal cursor-control bytes' INSTEAD of the ServiceNotReady it had located, which reads as corrupt output rather than as a stale deployment (diagnosed in docs/plans/srv1-deploy-serialization-and-false-greens.md §3.3). The roster is NOT edited — it is the cited ECMA-48 C0/C1 set and stays whole, one authority — so the exclusion is expressed as a predicate over it rather than as a second list, which would fork the citation (§3). TAB is admissible everywhere: it moves the cursor horizontally within a line and cannot escape the erase-line invariant. LF is admissible CONDITIONALLY, and the condition is derived rather than chosen — see terminal.dag's admit-against-projection rule." +// LF and TAB are C0 controls but they are not CURSOR controls, and conflating the two cost a +// located diagnosis. The predicate this replaces (ansi_text_contains_terminal_control, deleted with +// its last consumer rather than left dead) refused on ANY roster member, so it refused on any +// multi-line or tab-bearing text — and a captured `systemctl status` diagnostic is multi-line by +// construction. The srv1 deploy refusal of 2026-07-29 therefore rendered as 'rendered text cannot +// contain terminal cursor-control bytes' INSTEAD of the ServiceNotReady it had located, which reads +// as corrupt output rather than as a stale deployment (diagnosed in +// docs/plans/srv1-deploy-serialization-and-false-greens.md §3.3). The roster is NOT edited — it is +// the cited ECMA-48 C0/C1 set and stays whole, one authority — so the exclusion is expressed as a +// predicate over it rather than as a second list, which would fork the citation (§3). TAB is +// admissible everywhere: it moves the cursor horizontally within a line and cannot escape the +// erase-line invariant. LF is admissible CONDITIONALLY, and the condition is derived rather than +// chosen — see terminal.dag's admit-against-projection rule. fn ansi_control_is_content_admissible(c: String) -> Bool { c == ansi_horizontal_tab || c == ansi_line_feed @@ -119,7 +131,6 @@ fn ansi_text_contains_line_feed(text: String) -> Bool { string_contains(s: text, pattern: ansi_line_feed) } - type PaletteIndex = Int type SgrStyle diff --git a/dag/extdeps/render/glyphs.dag b/dag/extdeps/render/glyphs.dag index c2b6c20ab68..8ead5b61c21 100644 --- a/dag/extdeps/render/glyphs.dag +++ b/dag/extdeps/render/glyphs.dag @@ -117,7 +117,13 @@ fn symbol_color(id: SymbolId) -> SemanticColor? { } } -data reward_animal_note: String = "The reward animal shown on a terminal success lands as ROWS in the one glyph authority, never as a hardcoded function in a renderer — the reference implementation studied for this lane keeps its emoji table mirrored by hand between Go and shell, which is the maintained dual representation we are here to not have. Selection is DETERMINISTIC: the index is derived from the run's own identity, so replaying a captured run reproduces its animal exactly and the event stream stays replayable by construction. A random draw would make the stream unreplayable and would be a non-determinism root of precisely the kind the determinism gate walls." +// The reward animal shown on a terminal success lands as ROWS in the one glyph authority, never as +// a hardcoded function in a renderer — the reference implementation studied for this lane keeps its +// emoji table mirrored by hand between Go and shell, which is the maintained dual representation we +// are here to not have. Selection is DETERMINISTIC: the index is derived from the run's own +// identity, so replaying a captured run reproduces its animal exactly and the event stream stays +// replayable by construction. A random draw would make the stream unreplayable and would be a +// non-determinism root of precisely the kind the determinism gate walls. data reward_animals: List = [ glyphs_for_tiers(emoji: glyph(text: "🦦"), unicode: glyph(text: "✦"), ascii: glyph(text: "otter")), diff --git a/dag/extdeps/render/terminal.dag b/dag/extdeps/render/terminal.dag index 93f3b4390d5..4fe8023610e 100644 --- a/dag/extdeps/render/terminal.dag +++ b/dag/extdeps/render/terminal.dag @@ -141,7 +141,13 @@ type TerminalWriteProjection = TerminalWriteProjected { bytes: String, next: DynamicLineState } | TerminalWriteRefused { cause: NonEmptyStr } -data terminal_wire_projection_note: String = "Stateful terminal projection owns every terminal control byte. Callers supply rendered text only; overwrite, append, and clear are projected through the ECMA-48 CSI authority in extdeps.render.ansi. DynamicLineOpen means the previous write deliberately omitted LF and therefore may only be continued by an addressable terminal. A non-addressable capability paired with Open is contradictory and refuses before downgrade. Overwrite downgrades to Append when cursor addressing is absent; Clear becomes a no-op. Clear also requires empty rendered text, making caller-owned control payloads and ambiguous clear-plus-content states unwritable." +// Stateful terminal projection owns every terminal control byte. Callers supply rendered text only; +// overwrite, append, and clear are projected through the ECMA-48 CSI authority in +// extdeps.render.ansi. DynamicLineOpen means the previous write deliberately omitted LF and +// therefore may only be continued by an addressable terminal. A non-addressable capability paired +// with Open is contradictory and refuses before downgrade. Overwrite downgrades to Append when +// cursor addressing is absent; Clear becomes a no-op. Clear also requires empty rendered text, +// making caller-owned control payloads and ambiguous clear-plus-content states unwritable. // LF admission is DERIVED from the arm's own postcondition, not chosen per CursorAction. DynamicLineOpen means this write deliberately omitted LF so a later Overwrite can reclaim the line with CR + erase-entire-line — and erase-entire-line clears ONE line, so an embedded LF would leave every earlier line of the payload permanently on screen. That is the whole hazard, and it is exactly co-extensive with `next == DynamicLineOpen`. So the rule is: admit LF iff the projection closes the line. Reading it off the produced state rather than matching on the requested action means the downgrade paths (Overwrite-under-non-addressable becomes an Append that terminates with LF) get the right answer for free, and a future arm cannot forget to be listed. Cursor controls proper (ESC/CSI, CR, and the rest of C0/C1) stay refused on every arm — the wall's real purpose is that callers may not smuggle their own overwrite/erase payloads, and project_terminal_write_admitted owns those bytes. fn admit_line_feed_against_projection(projection: TerminalWriteProjection, rendered: String) -> TerminalWriteProjection { diff --git a/dag/extdeps/rust/cargo_build.dag b/dag/extdeps/rust/cargo_build.dag index 11d079916d7..d578f2307ac 100644 --- a/dag/extdeps/rust/cargo_build.dag +++ b/dag/extdeps/rust/cargo_build.dag @@ -13,7 +13,12 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { data cargo_compile_env: Map = empty_map() -data cargo_test_with_cwd_env_note: String = "cargo.Build.TestWithCwdEnv: env composes cwd (-C) and process env vars (leading VAR=val argv words) into ONE argv vector, matching the shell.Find.Files precedent of argv-native env -C workdir substitution — no shell is needed to chain a cd/export prefix onto a cargo invocation (SHELL-DAG dissolution of the interpreter_dispatch_bijection_real_roster_transport retained_foreign multi-line script: set -euo pipefail; cd; export; two cargo test invocations, now two sequential typed calls with caller-side short-circuit)." +// cargo.Build.TestWithCwdEnv: env composes cwd (-C) and process env vars (leading VAR=val argv +// words) into ONE argv vector, matching the shell.Find.Files precedent of argv-native env -C +// workdir substitution — no shell is needed to chain a cd/export prefix onto a cargo invocation +// (SHELL-DAG dissolution of the interpreter_dispatch_bijection_real_roster_transport +// retained_foreign multi-line script: set -euo pipefail; cd; export; two cargo test invocations, +// now two sequential typed calls with caller-side short-circuit). fn cargo_build_jobs_flag(count: Int) -> List { ["--jobs", to_string(count)] diff --git a/dag/extdeps/rust/cargo_message.dag b/dag/extdeps/rust/cargo_message.dag index 5b994d845d1..84a8e62d1fe 100644 --- a/dag/extdeps/rust/cargo_message.dag +++ b/dag/extdeps/rust/cargo_message.dag @@ -43,11 +43,39 @@ data extdeps_model_scope: ExternalModelScope = ExternalModelScope { further_citations: [extdeps.cargo_build.extdeps_external_authority_anchor] } -data cargo_message_subject_note: String = "WHY THIS MODULE EXISTS, stated as the defect it closes rather than the feature it adds. service cargo.Build in extdeps.cargo_build reports exactly three outputs on every operation — success, stdout, stderr — and none of them reports the PATH of the artifact the build produced. A consumer that needs the built binary therefore has to assemble the path by convention (target dir, profile, binary name), and a stale binary sitting at that conventional location satisfies the observation completely: the digest downstream would be honestly computed over real bytes that are the wrong bytes, with nothing in the chain able to tell. Cargo will name the artifact itself if asked, which is what --message-format=json is for, so the honest route is to read the path cargo REPORTED rather than the path we predicted. This module owns that reading; extdeps.cargo_build owns the invocation shape." +// WHY THIS MODULE EXISTS, stated as the defect it closes rather than the feature it adds. service +// cargo.Build in extdeps.cargo_build reports exactly three outputs on every operation — success, +// stdout, stderr — and none of them reports the PATH of the artifact the build produced. A consumer +// that needs the built binary therefore has to assemble the path by convention (target dir, +// profile, binary name), and a stale binary sitting at that conventional location satisfies the +// observation completely: the digest downstream would be honestly computed over real bytes that are +// the wrong bytes, with nothing in the chain able to tell. Cargo will name the artifact itself if +// asked, which is what --message-format=json is for, so the honest route is to read the path cargo +// REPORTED rather than the path we predicted. This module owns that reading; extdeps.cargo_build +// owns the invocation shape. -data cargo_message_boundary_note: String = "SCOPE, deliberately narrow. This decodes ONE fact out of cargo's message stream: which executable a named target produced. It is not a general cargo-message model — compiler-message diagnostics, build-script-executed, and the timing messages are all skipped as OTHER REASONS rather than modeled, because a consumer that needs them should model them under its own subject instead of widening this one. What is NOT skipped is a line this module cannot understand at all: an unparseable line, a line that is not an object, a message carrying no reason, or a reason that is not a string each REFUSE the whole decode rather than being passed over. That asymmetry is the point — skipping a message whose reason we read and recognized is a decision, skipping a line we could not read is an absorbing fallback, and the second one is how a drifted or truncated stream reads as a clean build with no artifact." +// SCOPE, deliberately narrow. This decodes ONE fact out of cargo's message stream: which executable +// a named target produced. It is not a general cargo-message model — compiler-message diagnostics, +// build-script-executed, and the timing messages are all skipped as OTHER REASONS rather than +// modeled, because a consumer that needs them should model them under its own subject instead of +// widening this one. What is NOT skipped is a line this module cannot understand at all: an +// unparseable line, a line that is not an object, a message carrying no reason, or a reason that is +// not a string each REFUSE the whole decode rather than being passed over. That asymmetry is the +// point — skipping a message whose reason we read and recognized is a decision, skipping a line we +// could not read is an absorbing fallback, and the second one is how a drifted or truncated stream +// reads as a clean build with no artifact. -data cargo_message_scan_not_fragment_note: String = "The scan parses each line with the shared RFC 8259 parser and looks members up by NAME, rather than searching the raw text for a reason or executable fragment. The distinction is load-bearing and the corpus has already paid for learning it: gunbc.roadmap_provider_events documents a fragment scan that let an agent message quoting a type token reclassify a pane, and repaired it to an anchored walk. A member-name lookup is stronger than an anchored walk here because it does not depend on cargo emitting members in any particular order — cargo documents the members a message carries, not their order, so a position-anchored read would be correct only by accident and would drift silently. Member lookup routes through json_object_unique_member, which reports a DUPLICATED member as its own outcome instead of resolving last-wins; on an identity field like reason or executable, last-wins is precisely how a stream carrying two values reads as whichever one was written second." +// The scan parses each line with the shared RFC 8259 parser and looks members up by NAME, rather +// than searching the raw text for a reason or executable fragment. The distinction is load-bearing +// and the corpus has already paid for learning it: gunbc.roadmap_provider_events documents a +// fragment scan that let an agent message quoting a type token reclassify a pane, and repaired it +// to an anchored walk. A member-name lookup is stronger than an anchored walk here because it does +// not depend on cargo emitting members in any particular order — cargo documents the members a +// message carries, not their order, so a position-anchored read would be correct only by accident +// and would drift silently. Member lookup routes through json_object_unique_member, which reports a +// DUPLICATED member as its own outcome instead of resolving last-wins; on an identity field like +// reason or executable, last-wins is precisely how a stream carrying two values reads as whichever +// one was written second. data cargo_artifact_reason: String = "compiler-artifact" data cargo_message_reason_member: String = "reason" diff --git a/dag/extdeps/rust/rustc.dag b/dag/extdeps/rust/rustc.dag index cb3e35eb59c..11919048063 100644 --- a/dag/extdeps/rust/rustc.dag +++ b/dag/extdeps/rust/rustc.dag @@ -24,9 +24,29 @@ data extdeps_model_scope: ExternalModelScope = ExternalModelScope { further_citations: [] } -data rustc_stdin_source_note: String = "rustc reads a crate from STANDARD INPUT when the input filename is `-`, which is the cited interface this operation binds. It matters here beyond convenience: the alternative is materializing a candidate's emitted source as a file inside the tree, and the batch executor builds its module index ONCE at startup, so a file appearing mid-run is invisible to that index at best and poisons later entries at worst. Feeding the text through stdin keeps a candidate a value rather than a tree mutation." +// rustc reads a crate from STANDARD INPUT when the input filename is `-`, the cited interface this +// operation binds. Beyond convenience: the alternative materializes a candidate's emitted source as +// a file inside the tree, and the batch executor builds its module index ONCE at startup, so a file +// appearing mid-run is invisible to that index at best and poisons later entries at worst. stdin +// keeps a candidate a value rather than a tree mutation. -data rustc_transport_shape_note: String = "THE SCRIPT IS A FIXED LITERAL WITH POSITIONAL PARAMETERS, WHICH IS THE PROPERTY THAT MATTERS, AND THE SIMPLER SPELLINGS WERE TRIED RATHER THAN ASSUMED AWAY (review 54507 flagged the shape). Source and edition arrive as $1 and $2 and are never spliced into the script body, so no candidate can escape a quote — the same discipline extdeps.shell already applies to its own multi-step operations (FilesAndSymlinksWithMode, FilesByNameSorted), which is the precedent this row follows rather than a new liberty.\n\nWHY EACH SHORTER FORM FAILS, MEASURED ON THIS HOST: rustc writes its temporaries BESIDE its output path, so `-o /dev/null` and `--emit=metadata=/dev/null` both fail with `could not create a temp dir` — and worse, they fail by printing an `error:` line, which an observer reading the diagnostic stream would have read as the CANDIDATE being rejected. A fixed output path under /tmp removes the temp dir but races concurrent scoring, which is the one thing a best-of-N harness does constantly. So a per-invocation directory is not decoration; it is what makes the observation attributable to the candidate.\n\nWHAT WOULD DISSOLVE IT: a modeled composition primitive over the shell surface — a pipe and a scoped temporary directory expressible as nodes — at which point this row is those nodes and the literal goes. No such primitive exists today, so this is the honest bottom transport rather than a routed-around one, and it is recorded here as an observation, not as a self-approved deferral." +// THE SCRIPT IS A FIXED LITERAL WITH POSITIONAL PARAMETERS, WHICH IS THE PROPERTY THAT MATTERS, AND +// THE SIMPLER SPELLINGS WERE TRIED RATHER THAN ASSUMED AWAY (review 54507 flagged the shape). +// Source and edition arrive as $1 and $2 and are never spliced into the script body, so no +// candidate can escape a quote — the discipline extdeps.shell already applies to its own multi-step +// operations (FilesAndSymlinksWithMode, FilesByNameSorted), the precedent this row follows. +// +// WHY EACH SHORTER FORM FAILS, MEASURED ON THIS HOST: rustc writes its temporaries BESIDE its +// output path, so `-o /dev/null` and `--emit=metadata=/dev/null` both fail with `could not create a +// temp dir` — and by printing an `error:` line, which an observer reading the diagnostic stream +// would have read as the CANDIDATE being rejected. A fixed output path under /tmp removes the temp +// dir but races concurrent scoring, which a best-of-N harness does constantly. So the +// per-invocation directory is what makes the observation attributable to the candidate. +// +// WHAT WOULD DISSOLVE IT: a modeled composition primitive over the shell surface — a pipe and a +// scoped temporary directory expressible as nodes — at which point this row is those nodes and the +// literal goes. None exists today, so this is the honest bottom transport rather than a +// routed-around one, recorded as an observation, not a self-approved deferral. service rustc.Check { operation CheckSourceText { diff --git a/dag/extdeps/rust/target_triple.dag b/dag/extdeps/rust/target_triple.dag index 9f0649f7f6e..38e6b8e42e9 100644 --- a/dag/extdeps/rust/target_triple.dag +++ b/dag/extdeps/rust/target_triple.dag @@ -40,9 +40,27 @@ data extdeps_model_scope: ExternalModelScope = ExternalModelScope { further_citations: [] } -data rust_target_triple_scope_subject_note: String = "THE SUBJECT IS RUST'S SPELLING OF A TARGET TRIPLE, and nothing else about Rust. Not the toolchain distribution, not cargo's build configuration, not the compiler's own target definitions -- only the platform-support table's rendering of the four axes as a hyphenated name, which is the one fact this repository needs from that upstream and the one it cites.\\n\\nThe axes themselves are NOT this module's subject: they are modeled once in the toolchain types, agnostic of any upstream's spelling, and this module is a partial projection from those axes onto the strings Rust publishes. Claiming the axes here would fork the concept that every other upstream's spelling also builds on." +// THE SUBJECT IS RUST'S SPELLING OF A TARGET TRIPLE, and nothing else about Rust. Not the toolchain +// distribution, not cargo's build configuration, not the compiler's own target definitions -- only +// the platform-support table's rendering of the four axes as a hyphenated name, which is the one +// fact this repository needs from that upstream and the one it cites.\n\nThe axes themselves are +// NOT this module's subject: they are modeled once in the toolchain types, agnostic of any +// upstream's spelling, and this module is a partial projection from those axes onto the strings +// Rust publishes. Claiming the axes here would fork the concept that every other upstream's +// spelling also builds on. -data rust_target_triple_note: String = "RUST'S OWN SPELLING OF A TARGET TRIPLE, living with the Rust upstream that publishes it rather than with any consumer. The toolchain module's diagnostic-name functions state the rule this module obeys: every upstream spells these axes differently, and each spelling belongs beside its own upstream as a PARTIAL projection that refuses what that upstream does not publish. This is that projection for Rust.\\n\\nIT IS DELIBERATELY PARTIAL AND REFUSING. The table below carries the triples this repository actually builds for; anything else returns absent rather than being assembled from parts. Assembling one would be the failure this shape exists to prevent -- a triple composed by concatenating axis spellings looks plausible for every combination, including the many that Rust does not publish, and the resulting string would be handed to a compiler that rejects it or, worse, to an identity that claims a target nobody can build.\\n\\nGROWING IT IS ADDING A ROW, which is the point: a new supported target is a cited addition here, not an edit to a consumer, and until the row exists every consumer refuses rather than guessing." +// RUST'S OWN SPELLING OF A TARGET TRIPLE, living with the Rust upstream that publishes it rather +// than with any consumer. The toolchain module's diagnostic-name functions state the rule this +// module obeys: every upstream spells these axes differently, and each spelling belongs beside its +// own upstream as a PARTIAL projection that refuses what that upstream does not publish. This is +// that projection for Rust.\n\nIT IS DELIBERATELY PARTIAL AND REFUSING. The table below carries the +// triples this repository actually builds for; anything else returns absent rather than being +// assembled from parts. Assembling one would be the failure this shape exists to prevent -- a +// triple composed by concatenating axis spellings looks plausible for every combination, including +// the many that Rust does not publish, and the resulting string would be handed to a compiler that +// rejects it or, worse, to an identity that claims a target nobody can build.\n\nGROWING IT IS +// ADDING A ROW, which is the point: a new supported target is a cited addition here, not an edit to +// a consumer, and until the row exists every consumer refuses rather than guessing. type RustTargetTripleRow { spelling: NonEmptyStr @@ -78,7 +96,10 @@ fn rust_target_triple_rows() -> List { ] } -data rust_target_triple_lookup_note: String = "The lookup is a fold over the rows in both directions, so one table serves parsing an observed triple and rendering a modeled one. Two functions reading one row set is the same structure read forwards and backwards; a second table for the reverse direction would be the fork this module exists to avoid." +// The lookup is a fold over the rows in both directions, so one table serves parsing an observed +// triple and rendering a modeled one. Two functions reading one row set is the same structure read +// forwards and backwards; a second table for the reverse direction would be the fork this module +// exists to avoid. fn parse_rust_target_triple(spelling: String) -> TargetTriple? { fold( diff --git a/dag/extdeps/sec/facts.dag b/dag/extdeps/sec/facts.dag index e45a2fe624c..dbcb6d04347 100644 --- a/dag/extdeps/sec/facts.dag +++ b/dag/extdeps/sec/facts.dag @@ -59,7 +59,14 @@ fn filed_amount(fact: FiledMonetaryFact) -> UsdWholeDollars? { } } -data signed_usd_group_completion_note: String = "Credit-balance income-statement elements (OperatingIncomeLoss, NetIncomeLoss) file SIGNED values -- a loss is a negative companyconcept val, which the Nat-counted MoneyAmount magnitude cannot inhabit. The signed value is sign-as-sum over the same magnitude authority (the GroupCompletion-of-Nat shape): NonNegativeUsd carries the filed non-negative amount, NegativeUsd carries the strictly positive magnitude of a negative filed val (zero is always NonNegativeUsd). FiledMonetaryFact remains the refinement for lines verified non-negative at authoring time (revenue, costs, expense magnitudes). dissolve-on: std Int = GroupCompletion grounding (DESIGN open thread), at which point this sum collapses onto the grounded signed carrier." +// Credit-balance income-statement elements (OperatingIncomeLoss, NetIncomeLoss) file SIGNED values +// -- a loss is a negative companyconcept val, which the Nat-counted MoneyAmount magnitude cannot +// inhabit. The signed value is sign-as-sum over the same magnitude authority (the +// GroupCompletion-of-Nat shape): NonNegativeUsd carries the filed non-negative amount, NegativeUsd +// carries the strictly positive magnitude of a negative filed val (zero is always NonNegativeUsd). +// FiledMonetaryFact remains the refinement for lines verified non-negative at authoring time +// (revenue, costs, expense magnitudes). dissolve-on: std Int = GroupCompletion grounding +// (DESIGN open thread), at which point this sum collapses onto the grounded signed carrier. type SignedUsdWholeDollars = NonNegativeUsd { amount: UsdWholeDollars } diff --git a/dag/extdeps/sec/issuer/atlassian_facts.dag b/dag/extdeps/sec/issuer/atlassian_facts.dag index a646732a3db..b7966df9977 100644 --- a/dag/extdeps/sec/issuer/atlassian_facts.dag +++ b/dag/extdeps/sec/issuer/atlassian_facts.dag @@ -35,9 +35,22 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { data atlassian_facts_subject: IssuerCatalogRow = atlassian_issuer_catalog -data atlassian_facts_provenance_note: String = "Every amount is a live pull from data.sec.gov companyconcept (2026-07-25, session gitlab-10k-scm-costs-baz90x). CIK 0001650372 verified on the EDGAR submissions API (ticker TEAM, Nasdaq, SIC 7372 Services-Prepackaged Software). Four fiscal years modeled, FY2022 through FY2025; Atlassian fiscal years end June 30. The nearest pure-play comparable to GitLab in the corpus: a developer-collaboration vendor whose products (Jira, Confluence, Bitbucket) include an SCM, sold on the same per-seat subscription shape. The face statement cross-foots exactly in all four years: Revenue - cost of revenue = GrossProfit; ResearchAndDevelopment + SellingAndMarketing + GeneralAndAdministrative = OperatingExpenses; GrossProfit - OperatingExpenses = OperatingIncomeLoss." - -data atlassian_comparative_period_note: String = "The FY2022 rows are the comparative period presented in the FY2023 10-K (accession 0001650372-23-000040). Atlassian re-domiciled to the United States in 2022; before that it filed as a foreign private issuer, so the FY2023 10-K is the earliest annual-report authority on this CIK for the FY2022 period. Same shape as the GitLab comparatives: EDGAR labels every row with the FILING's fiscal-year focus, so the citation carries fiscal_year 2023 while the FiledDuration bounds the fact's own fiscal year." +// Every amount is a live pull from data.sec.gov companyconcept (2026-07-25, session +// gitlab-10k-scm-costs-baz90x). CIK 0001650372 verified on the EDGAR submissions API (ticker TEAM, +// Nasdaq, SIC 7372 Services-Prepackaged Software). Four fiscal years modeled, FY2022 through +// FY2025; Atlassian fiscal years end June 30. The nearest pure-play comparable to GitLab in the +// corpus: a developer-collaboration vendor whose products (Jira, Confluence, Bitbucket) include an +// SCM, sold on the same per-seat subscription shape. The face statement cross-foots exactly in all +// four years: Revenue - cost of revenue = GrossProfit; ResearchAndDevelopment + SellingAndMarketing +// + GeneralAndAdministrative = OperatingExpenses; GrossProfit - OperatingExpenses = +// OperatingIncomeLoss. + +// The FY2022 rows are the comparative period presented in the FY2023 10-K (accession +// 0001650372-23-000040). Atlassian re-domiciled to the United States in 2022; before that it filed +// as a foreign private issuer, so the FY2023 10-K is the earliest annual-report authority on this +// CIK for the FY2022 period. Same shape as the GitLab comparatives: EDGAR labels every row with the +// FILING's fiscal-year focus, so the citation carries fiscal_year 2023 while the FiledDuration +// bounds the fact's own fiscal year. // Atlassian SWITCHED cost-of-revenue elements mid-series: the FY2023 and FY2024 10-Ks tag CostOfRevenue, and the FY2025 10-K tags CostOfGoodsAndServicesSold. Each row below carries the element ITS OWN filing used -- the actually-filed tag is the authority per filing (DESIGN section 3), never normalized to one nickname for series convenience. This is the same element-substitution case as Rollins/Chemed/Microsoft, but observed WITHIN one issuer across time rather than across issuers, which is why a per-row element field (not a per-issuer one) is the right grain. data atlassian_fy2022_comparative_citation: FiledFactCitation = FiledFactCitation { diff --git a/dag/extdeps/sec/issuer/chemed_facts.dag b/dag/extdeps/sec/issuer/chemed_facts.dag index 404437a4457..76c45f41473 100644 --- a/dag/extdeps/sec/issuer/chemed_facts.dag +++ b/dag/extdeps/sec/issuer/chemed_facts.dag @@ -26,9 +26,21 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { data chemed_facts_subject: IssuerCatalogRow = chemed_issuer_catalog -data chemed_facts_provenance_note: String = "Every amount is a live pull from data.sec.gov companyfacts (2026-07-21, session snappy-carp-882). CIK 0000019584 verified against the SEC company_tickers.json ticker-to-CIK map and the EDGAR submissions API (tickers: [\"CHE\"], exchanges: [\"NYSE\"]). Fiscal year ended 2025-12-31, form 10-K, accession 0001562762-26-000020, filed 2026-02-27. Chemed's FY2025 10-K does not tag GrossProfit or CostOfRevenue on the us-gaap axis (same element-substitution case as Rollins) — it tags CostOfGoodsAndServicesSold instead, so the profitability witness below derives operating margin rather than gross margin." +// Every amount is a live pull from data.sec.gov companyfacts (2026-07-21, session snappy-carp-882). +// CIK 0000019584 verified against the SEC company_tickers.json ticker-to-CIK map and the EDGAR +// submissions API (tickers: ["CHE"], exchanges: ["NYSE"]). Fiscal year ended 2025-12-31, form 10-K, +// accession 0001562762-26-000020, filed 2026-02-27. Chemed's FY2025 10-K does not tag GrossProfit +// or CostOfRevenue on the us-gaap axis (same element-substitution case as Rollins) — it tags +// CostOfGoodsAndServicesSold instead, so the profitability witness below derives operating margin +// rather than gross margin. -data chemed_segment_gap_note: String = "Chemed's companyfacts response reports CONSOLIDATED us-gaap facts only (dimensional/segment axes are stripped from the companyfacts and companyconcept APIs). The 10-K's segment-reporting footnote (accession 0001562762-26-000020) discloses VITAS (hospice) and Roto-Rooter (plumbing/drain, NumberOfReportableSegments: 2) separately, but that segment-level revenue/profit split is not reachable as filed top-level XBRL facts through data.sec.gov — it exists only in the filing's narrative/tabular segment-reporting note. No estimated split is stored here; only the consolidated filed facts below are grounded." +// Chemed's companyfacts response reports CONSOLIDATED us-gaap facts only (dimensional/segment axes +// are stripped from the companyfacts and companyconcept APIs). The 10-K's segment-reporting +// footnote (accession 0001562762-26-000020) discloses VITAS (hospice) and Roto-Rooter +// (plumbing/drain, NumberOfReportableSegments: 2) separately, but that segment-level revenue/profit +// split is not reachable as filed top-level XBRL facts through data.sec.gov — it exists only in the +// filing's narrative/tabular segment-reporting note. No estimated split is stored here; only the +// consolidated filed facts below are grounded. data chemed_fy2025_citation: FiledFactCitation = FiledFactCitation { accession: "0001562762-26-000020", diff --git a/dag/extdeps/sec/issuer/gitlab_facts.dag b/dag/extdeps/sec/issuer/gitlab_facts.dag index f87a5fcb6c2..812e155115d 100644 --- a/dag/extdeps/sec/issuer/gitlab_facts.dag +++ b/dag/extdeps/sec/issuer/gitlab_facts.dag @@ -39,17 +39,49 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data facts_subject_edge_note: String = "Subject and source are ORTHOGONAL axes and a filename encodes neither (operator ruling 2026-07-25): the folder says which upstream AUTHORITY produced the facts (extdeps/sec = EDGAR, per this module's ExternalAuthority anchor), while a typed edge says which ENTITY the facts are about. Before this row the only thing correlating this module's facts with extdeps.pricing.gitlab_subscription was the shared filename prefix -- a prose coincidence invisible to the graph. The subject row below is that edge, and it re-grounds the CIK too: the identity now arrives as the typed IssuerCatalogRow, so the CIK inside this module's anchor URL is a locator string, never the identity carrier." +// Subject and source are ORTHOGONAL axes and a filename encodes neither (operator ruling +// 2026-07-25): the folder says which upstream AUTHORITY produced the facts (extdeps/sec = EDGAR, +// per this module's ExternalAuthority anchor), while a typed edge says which ENTITY the facts are +// about. Before this row the only thing correlating this module's facts with +// extdeps.pricing.gitlab_subscription was the shared filename prefix -- a prose coincidence +// invisible to the graph. The subject row below is that edge, and it re-grounds the CIK too: the +// identity now arrives as the typed IssuerCatalogRow, so the CIK inside this module's anchor URL is +// a locator string, never the identity carrier. data gitlab_facts_subject: IssuerCatalogRow = gitlab_issuer_catalog -data gitlab_facts_provenance_note: String = "Every amount is a live pull from data.sec.gov companyconcept (2026-07-25, session gitlab-10k-scm-costs-baz90x). CIK 0001653482 verified on the EDGAR submissions API (ticker GTLB, Nasdaq, SIC 7372 Services-Prepackaged Software). All five GitLab 10-Ks are modeled -- FY2022 (first post-IPO annual report, accession 0001628280-22-008836, filed 2022-04-08) through FY2026 (accession 0001628280-26-018731, filed 2026-03-17); GitLab fiscal years end January 31. Each fact row cites the fiscal year's OWN 10-K (the earliest filing reporting that period). The face income statement cross-foots exactly in every modeled period: GrossProfit = Revenue - CostOfRevenue; SellingAndMarketing + ResearchAndDevelopment + GeneralAndAdministrative = OperatingExpenses (exactly three operating-expense lines in all seven periods); GrossProfit - OperatingExpenses = OperatingIncomeLoss." - -data gitlab_comparative_period_note: String = "FY2020 and FY2021 rows are the comparative periods presented in the FY2022 10-K -- GitLab IPO'd 2021-10-14, so no earlier 10-K exists and that filing is the earliest annual-report authority for those periods. EDGAR labels every row of a filing with the FILING's fiscal-year focus, so those citations carry fiscal_year 2022 while the FiledDuration period bounds the fact's own earlier fiscal year." - -data gitlab_revenue_disaggregation_gap_note: String = "GitLab's statements disaggregate revenue (subscription self-managed and SaaS vs license self-managed and other), but the split is tagged on dimensional axes only -- companyfacts returns us-gaap/dei/ecd taxonomies with no gtlb custom elements, and dimensional facts are stripped from the companyfacts/companyconcept APIs (same API-boundary case as chemed_segment_gap_note). No estimated split is stored. Dissolve-on: ingesting the SEC Financial Statement Data Sets or the filing R-file tables, which carry the dimension members." - -data gitlab_hosting_commitment_note: String = "What serving costs, forward-committed: the FY2022 10-K commitments note discloses that in September 2020 GitLab entered non-cancelable capacity commitments with a hosting infrastructure vendor for a total minimum service commitment of 97.0 million USD (LongTermPurchaseCommitmentAmount row below, instant 2020-09-30). The FY2026 10-K commitments note discloses 228.056 million USD of total non-cancelable purchase commitments as of 2026-01-31 (98.885 million USD due within a year) and states the table 'includes 131 million USD of remaining non-cancelable contractual commitments as of January 31, 2026 related to two of the Company's hosting infrastructure vendors' -- versus 120.7 million USD FY2026 cost of revenue." +// Every amount is a live pull from data.sec.gov companyconcept (2026-07-25, session +// gitlab-10k-scm-costs-baz90x). CIK 0001653482 verified on the EDGAR submissions API (ticker GTLB, +// Nasdaq, SIC 7372 Services-Prepackaged Software). All five GitLab 10-Ks are modeled -- FY2022 +// (first post-IPO annual report, accession 0001628280-22-008836, filed 2022-04-08) through FY2026 +// (accession 0001628280-26-018731, filed 2026-03-17); GitLab fiscal years end January 31. Each fact +// row cites the fiscal year's OWN 10-K (the earliest filing reporting that period). The face income +// statement cross-foots exactly in every modeled period: GrossProfit = Revenue - CostOfRevenue; +// SellingAndMarketing + ResearchAndDevelopment + GeneralAndAdministrative = OperatingExpenses +// (exactly three operating-expense lines in all seven periods); GrossProfit - OperatingExpenses = +// OperatingIncomeLoss. + +// FY2020 and FY2021 rows are the comparative periods presented in the FY2022 10-K -- GitLab IPO'd +// 2021-10-14, so no earlier 10-K exists and that filing is the earliest annual-report authority for +// those periods. EDGAR labels every row of a filing with the FILING's fiscal-year focus, so those +// citations carry fiscal_year 2022 while the FiledDuration period bounds the fact's own earlier +// fiscal year. + +// GitLab's statements disaggregate revenue (subscription self-managed and SaaS vs license +// self-managed and other), but the split is tagged on dimensional axes only -- companyfacts returns +// us-gaap/dei/ecd taxonomies with no gtlb custom elements, and dimensional facts are stripped from +// the companyfacts/companyconcept APIs (same API-boundary case as chemed_segment_gap_note). No +// estimated split is stored. Dissolve-on: ingesting the SEC Financial Statement Data Sets or the +// filing R-file tables, which carry the dimension members. + +// What serving costs, forward-committed: the FY2022 10-K commitments note discloses that in +// September 2020 GitLab entered non-cancelable capacity commitments with a hosting infrastructure +// vendor for a total minimum service commitment of 97.0 million USD +// (LongTermPurchaseCommitmentAmount row below, instant 2020-09-30). The FY2026 10-K commitments +// note discloses 228.056 million USD of total non-cancelable purchase commitments as of 2026-01-31 +// (98.885 million USD due within a year) and states the table 'includes 131 million USD of +// remaining non-cancelable contractual commitments as of January 31, 2026 related to two of the +// Company's hosting infrastructure vendors' -- versus 120.7 million USD FY2026 cost of revenue. data gitlab_fy2020_comparative_citation: FiledFactCitation = FiledFactCitation { accession: "0001628280-22-008836", @@ -456,7 +488,10 @@ data gitlab_fy2024_net_income_loss: FiledSignedMonetaryFact = FiledSignedMonetar citation: gitlab_fy2024_citation, } -data gitlab_fy2024_net_vs_operating_divergence_note: String = "FY2024 is the outlier year: net loss 424.174 million USD against an operating loss of 187.440 million USD. The gap is non-operating (income-tax and JiHu joint-venture related charges below the operating line), not a change in the cost of running the platform -- the trend witness therefore tracks OPERATING loss, and the net-income rows stay faithful to the filed totals." +// FY2024 is the outlier year: net loss 424.174 million USD against an operating loss of 187.440 +// million USD. The gap is non-operating (income-tax and JiHu joint-venture related charges below +// the operating line), not a change in the cost of running the platform -- the trend witness +// therefore tracks OPERATING loss, and the net-income rows stay faithful to the filed totals. data gitlab_fy2025_revenue: FiledMonetaryFact = FiledMonetaryFact { element: us_gaap_revenue_from_contract_with_customer_excluding_assessed_tax, diff --git a/dag/extdeps/sec/issuer/microsoft_facts.dag b/dag/extdeps/sec/issuer/microsoft_facts.dag index 09b2ff99690..2ea13e4855f 100644 --- a/dag/extdeps/sec/issuer/microsoft_facts.dag +++ b/dag/extdeps/sec/issuer/microsoft_facts.dag @@ -33,9 +33,27 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { data microsoft_facts_subject: IssuerCatalogRow = microsoft_issuer_catalog -data microsoft_facts_provenance_note: String = "Every amount is a live pull from data.sec.gov companyconcept (2026-07-25, session gitlab-10k-scm-costs-baz90x). CIK 0000789019 verified on the EDGAR submissions API (ticker MSFT, Nasdaq, SIC 7372 Services-Prepackaged Software -- the same SIC as GitLab and Atlassian). Four fiscal years modeled, FY2022 through FY2025; Microsoft fiscal years end June 30. Element substitution (the Rollins/Chemed case): Microsoft does NOT tag CostOfRevenue -- it tags CostOfGoodsAndServicesSold, and that actually-filed tag is the authority here, never a normalized nickname. The face statement cross-foots exactly in all four years: Revenue - CostOfGoodsAndServicesSold = GrossProfit; ResearchAndDevelopment + SellingAndMarketing + GeneralAndAdministrative = OperatingExpenses; GrossProfit - OperatingExpenses = OperatingIncomeLoss." - -data microsoft_github_disaggregation_gap_note: String = "Microsoft acquired GitHub in 2018, and GitHub is the reason this issuer is in the corpus at all -- but GitHub revenue, cost of revenue, and infrastructure spend are NOT separately disclosed in any Microsoft 10-K. Microsoft reports three segments (Productivity and Business Processes, Intelligent Cloud, More Personal Computing); GitHub sits inside a segment without its own filed line, and segment facts are dimensional so they are stripped from the companyconcept/companyfacts APIs regardless (the chemed_segment_gap_note API boundary). No GitHub-level estimate is stored: the consolidated rows below are what is filed, and the honest read for SCM economics is the pure-play issuers (GitLab, Atlassian), with Microsoft as the scale contrast. Dissolve-on: a filed disaggregation naming GitHub, or ingesting the SEC Financial Statement Data Sets for segment members." +// Every amount is a live pull from data.sec.gov companyconcept (2026-07-25, session +// gitlab-10k-scm-costs-baz90x). CIK 0000789019 verified on the EDGAR submissions API (ticker MSFT, +// Nasdaq, SIC 7372 Services-Prepackaged Software -- the same SIC as GitLab and Atlassian). Four +// fiscal years modeled, FY2022 through FY2025; Microsoft fiscal years end June 30. Element +// substitution (the Rollins/Chemed case): Microsoft does NOT tag CostOfRevenue -- it tags +// CostOfGoodsAndServicesSold, and that actually-filed tag is the authority here, never a normalized +// nickname. The face statement cross-foots exactly in all four years: Revenue - +// CostOfGoodsAndServicesSold = GrossProfit; ResearchAndDevelopment + SellingAndMarketing + +// GeneralAndAdministrative = OperatingExpenses; GrossProfit - OperatingExpenses = +// OperatingIncomeLoss. + +// Microsoft acquired GitHub in 2018, and GitHub is the reason this issuer is in the corpus at all +// -- but GitHub revenue, cost of revenue, and infrastructure spend are NOT separately disclosed in +// any Microsoft 10-K. Microsoft reports three segments (Productivity and Business Processes, +// Intelligent Cloud, More Personal Computing); GitHub sits inside a segment without its own filed +// line, and segment facts are dimensional so they are stripped from the companyconcept/companyfacts +// APIs regardless (the chemed_segment_gap_note API boundary). No GitHub-level estimate is stored: +// the consolidated rows below are what is filed, and the honest read for SCM economics is the +// pure-play issuers (GitLab, Atlassian), with Microsoft as the scale contrast. Dissolve-on: a filed +// disaggregation naming GitHub, or ingesting the SEC Financial Statement Data Sets for segment +// members. data microsoft_operating_expenses_element_gap_note: String = "Microsoft's FY2022 10-K does not tag OperatingExpenses on the us-gaap axis (the element first appears in the FY2025 filing, which carries FY2023-FY2025). The FY2022 total is therefore Unfiled with a stated cause rather than back-computed from its three components -- deriving it and storing it as filed would fabricate a filed fact (DESIGN section 5). The FY2022 components and OperatingIncomeLoss below are all filed, so the component-sum identity is still witnessed for that year without inventing the total." diff --git a/dag/extdeps/sec/issuer/rollins_facts.dag b/dag/extdeps/sec/issuer/rollins_facts.dag index a703f5ac7e9..d80ee1c13db 100644 --- a/dag/extdeps/sec/issuer/rollins_facts.dag +++ b/dag/extdeps/sec/issuer/rollins_facts.dag @@ -26,7 +26,16 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { data rollins_facts_subject: IssuerCatalogRow = rollins_issuer_catalog -data rollins_facts_provenance_note: String = "Every amount is a live pull from data.sec.gov companyfacts (2026-07-21, session snappy-carp-882). CIK 0000084839 verified against the SEC company_tickers.json ticker-to-CIK map and the EDGAR submissions API (tickers: [\"ROL\"], exchanges: [\"NYSE\"]). Fiscal year ended 2025-12-31, form 10-K, accession 0000084839-26-000008, filed 2026-02-12. Rollins' FY2025 10-K does not tag GrossProfit or CostOfRevenue on the us-gaap axis — it tags CostOfGoodsAndServiceExcludingDepreciationDepletionAndAmortization instead (the element-substitution case: the issuer's actually-filed tag is the authority, not a normalized nickname), with depreciation/amortization reported as a separate line. Revenue + cost + SG&A + D&A reconciles exactly to the filed OperatingIncomeLoss (3,761,050,000 - 1,777,006,000 - 1,133,232,000 - 124,744,000 = 726,068,000), so the profitability witness below derives operating margin rather than gross margin." +// Every amount is a live pull from data.sec.gov companyfacts (2026-07-21, session snappy-carp-882). +// CIK 0000084839 verified against SEC company_tickers.json and the EDGAR submissions API (tickers: +// ["ROL"], exchanges: ["NYSE"]). Fiscal year ended 2025-12-31, form 10-K, accession +// 0000084839-26-000008, filed 2026-02-12. The FY2025 10-K tags neither GrossProfit nor +// CostOfRevenue on the us-gaap axis; it tags +// CostOfGoodsAndServiceExcludingDepreciationDepletionAndAmortization (element substitution: the +// filed tag is the authority, not a normalized nickname), with D&A a separate line. Revenue + cost +// + SG&A + D&A reconciles exactly to the filed OperatingIncomeLoss (3,761,050,000 - 1,777,006,000 - +// 1,133,232,000 - 124,744,000 = 726,068,000), so the profitability witness derives operating margin +// rather than gross margin. data rollins_fy2025_citation: FiledFactCitation = FiledFactCitation { accession: "0000084839-26-000008", diff --git a/dag/extdeps/sec/operating_metrics.dag b/dag/extdeps/sec/operating_metrics.dag index 0169cfde77f..3a716b4204a 100644 --- a/dag/extdeps/sec/operating_metrics.dag +++ b/dag/extdeps/sec/operating_metrics.dag @@ -12,7 +12,12 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data operating_metrics_shape_note: String = "Issuer-defined key performance indicators disclosed in 10-K Item 7 (MD&A) under the SEC's KPI interpretive guidance (Release 33-10751, anchor above). These are NOT XBRL-tagged facts -- they exist only in the filing narrative, so rows cite the disclosing filing via the same FiledFactCitation authority as monetary facts (period is FiledInstant at the metric's as-of date). SaaS issuers disclose net-revenue-retention and ARR-threshold customer counts under issuer-specific definitions; the definition belongs to the citing filing, not to this shape." +// Issuer-defined key performance indicators disclosed in 10-K Item 7 (MD&A) under the SEC's KPI +// interpretive guidance (Release 33-10751, anchor above). These are NOT XBRL-tagged facts -- they +// exist only in the filing narrative, so rows cite the disclosing filing via the same +// FiledFactCitation authority as monetary facts (period is FiledInstant at the metric's as-of +// date). SaaS issuers disclose net-revenue-retention and ARR-threshold customer counts under +// issuer-specific definitions; the definition belongs to the citing filing, not to this shape. // Issuers sometimes disclose a rate only as a floor (GitLab fiscal 2020 through 2023: Dollar-Based Net Retention Rate reported as 'above N%'), later as an exact point. RateLowerBoundPercent keeps the floor a floor -- collapsing it to an exact value would fabricate precision the filing never claimed (DESIGN section 5). type DisclosedPercentRate diff --git a/dag/extdeps/sec/types.dag b/dag/extdeps/sec/types.dag index b3f274a88ff..5587275a152 100644 --- a/dag/extdeps/sec/types.dag +++ b/dag/extdeps/sec/types.dag @@ -119,7 +119,15 @@ type XbrlFact { frame: EdgarCalendarDate? } -data issuer_registrant_name_note: String = "registrant_name is EDGAR's NAME OF RECORD for the CIK, not the company's own legal name -- a genuinely distinct cited fact, which is why it sits beside the vendor edge rather than duplicating it (review nit 2026-07-25, resolved by measurement). Every issuer in the corpus differs between the two: EDGAR records 'Gitlab Inc.' where the company writes 'GitLab Inc.', and 'MICROSOFT CORP' / 'ROLLINS INC' / 'CHEMED CORP' / 'Atlassian Corp' against the vendor rows' 'Microsoft Corporation' / 'Rollins, Inc.' / 'Chemed Corporation' / 'Atlassian Corporation'. The registrant name is what an EDGAR full-text or company search matches on, so it is the lookup key for the filings; the vendor edge carries the company's own legal name. Deriving either from the other would fabricate a string neither authority publishes." +// registrant_name is EDGAR's NAME OF RECORD for the CIK, not the company's own legal name -- a +// genuinely distinct cited fact, which is why it sits beside the vendor edge rather than +// duplicating it (review nit 2026-07-25, resolved by measurement). Every issuer in the corpus +// differs between the two: EDGAR records 'Gitlab Inc.' where the company writes 'GitLab Inc.', and +// 'MICROSOFT CORP' / 'ROLLINS INC' / 'CHEMED CORP' / 'Atlassian Corp' against the vendor rows' +// 'Microsoft Corporation' / 'Rollins, Inc.' / 'Chemed Corporation' / 'Atlassian Corporation'. The +// registrant name is what an EDGAR full-text or company search matches on, so it is the lookup key +// for the filings; the vendor edge carries the company's own legal name. Deriving either from the +// other would fabricate a string neither authority publishes. type IssuerCatalogRow { cik: Cik diff --git a/dag/extdeps/shell.dag b/dag/extdeps/shell.dag index 27daba7b832..11adf66afdc 100644 --- a/dag/extdeps/shell.dag +++ b/dag/extdeps/shell.dag @@ -12,12 +12,33 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data find_files_and_symlinks_positional_note: String = "The script body is a FIXED literal owned here, and root arrives as the positional parameter $1 rather than being spliced into it — so no path can escape a quote, which is exactly what a caller-side concat into a single-quoted find string permitted (review 50535). The pipe to sort stays inside the operation because it is load-bearing: find walks directories in filesystem order, and the caller digests the resulting listing into a tree identity, so an unsorted read would make that identity depend on traversal order. It cannot move into the substrate today — std.primitives declares a sort contract but no callable sort exists (probed, 2026-08-08), which is the dissolve-on for folding this ordering back out of the shell. GNU find -printf keeps this operation Linux-only, per the caller's own linux_find_note." - -data files_by_name_sorted_positional_note: String = "shell.Find.FilesByNameSorted: root and name_glob arrive as positional parameters ($1, $2) rather than being spliced into the script body, for the same reason as FilesAndSymlinksWithMode above — no path or glob can escape a quote. The pipe to sort stays inside the operation because the caller digests the listing into a manifest and needs traversal-order independence (SHELL-DAG dissolution of package_delivery.dag's protocol-schema-bundle file listing, previously a retained_foreign concat-built find | sort script)." - - -data socket_inode_holder_bracket_note: String = "WHY THE PATTERN SPELLS THE BRACKETS AS `?` RATHER THAN ESCAPING THEM. A /proc fd symlink for a socket reads `socket:[N]`, and find's -lname argument is a GLOB, in which `[...]` is a character class -- so the obvious `-lname \"socket:[N]\"` silently matches nothing at all, which is the worst possible failure for a probe whose empty result is load-bearing evidence (it is exactly the clause that licenses unlinking a stale socket). Escaping the brackets works but puts backslashes through two layers of quoting; `?` matches the one literal bracket character in each position and is length-anchored, so `socket:?N?` matches `socket:[N]` and cannot match a different inode. Confirmed by execution 2026-08-21 against a live listening socket, WITH the negative control that a non-existent inode returns empty -- a probe whose only observed state is `found nothing` proves nothing, so the pair is the measurement." +// The script body is a FIXED literal owned here, and root arrives as the positional parameter $1 +// rather than being spliced into it — so no path can escape a quote, which is exactly what a +// caller-side concat into a single-quoted find string permitted (review 50535). The pipe to sort +// stays inside the operation because it is load-bearing: find walks directories in filesystem +// order, and the caller digests the resulting listing into a tree identity, so an unsorted read +// would make that identity depend on traversal order. It cannot move into the substrate today — +// std.primitives declares a sort contract but no callable sort exists (probed, 2026-08-08), which +// is the dissolve-on for folding this ordering back out of the shell. GNU find -printf keeps this +// operation Linux-only, per the caller's own linux_find_note. + +// shell.Find.FilesByNameSorted: root and name_glob arrive as positional parameters ($1, $2) rather +// than being spliced into the script body, for the same reason as FilesAndSymlinksWithMode above — +// no path or glob can escape a quote. The pipe to sort stays inside the operation because the +// caller digests the listing into a manifest and needs traversal-order independence (SHELL-DAG +// dissolution of package_delivery.dag's protocol-schema-bundle file listing, previously a +// retained_foreign concat-built find | sort script). + +// WHY THE PATTERN SPELLS THE BRACKETS AS `?` RATHER THAN ESCAPING THEM. A /proc fd symlink for a +// socket reads `socket:[N]`, and find's -lname argument is a GLOB, in which `[...]` is a character +// class -- so the obvious `-lname "socket:[N]"` silently matches nothing at all, which is the worst +// possible failure for a probe whose empty result is load-bearing evidence (it is exactly the +// clause that licenses unlinking a stale socket). Escaping the brackets works but puts backslashes +// through two layers of quoting; `?` matches the one literal bracket character in each position and +// is length-anchored, so `socket:?N?` matches `socket:[N]` and cannot match a different inode. +// Confirmed by execution 2026-08-21 against a live listening socket, WITH the negative control that +// a non-existent inode returns empty -- a probe whose only observed state is `found nothing` proves +// nothing, so the pair is the measurement. // A PIPELINE REPORTS ITS LAST STAGE'S STATUS. // @@ -132,9 +153,29 @@ service shell.Env { } } -data posix_command_v_semantic_note: String = "`command -v` is a POSIX shell builtin (IEEE 1003.1-2017 section 2.9.1.1, Command Search and Execution, invoked here through `sh -c` rather than by name), present wherever a POSIX-conforming shell is -- unlike `which`, an EXTERNAL BINARY (part of debianutils on Debian/Ubuntu, not part of POSIX or a base busybox install) that may be absent on stripped appliance images, BMC firmware shells (Dropbear), or minimal containers. This operation's own assumption is not unconditional either: it still requires a POSIX shell at `sh` on the target's PATH. gunbc.fleet_intent's srv1-4 ComputeHost, and gunbc.ci_fleet's own CI/falsifier runner, both carry an `OperatingSystemSurface \{ distro_or_product: none, ... \}` -- no distro or package-set authority anywhere in this fleet, our own runner included (crisp-wren-896 finding, PR #8590; the modeling gap is named at std.os.types operating_system_surface_package_authority_gap_note). This operation's assumption strictly dominates `which`'s -- every target that can run `which` can run `command -v`, and the converse fails on busybox and stripped images -- which is why the former shell.Which.Check operation was deleted rather than kept as a second, narrower-scope choice (crisp-wren-896, PR #8590): a selection rule between two operations is only worth its cost when each is right somewhere the other is wrong, and here nothing was. If even the POSIX-shell assumption cannot be established for a target, the honest realization is a typed refusal, not a silent fall-through inheriting this operation's result as ground truth." - -data posix_command_v_argv_construction_note: String = "The probed command name is carried in its OWN argv slot (`\{command\}` bound to the shell's trailing positional parameter), never concatenated into the `-c` script text -- the same `-c script scriptname arg1` positional-parameter binding shell.Find.FilesAndSymlinksWithMode already uses. The value never touches the string parsed as shell code, so there is no escaping table to maintain and no quoting authority this operation depends on." +// `command -v` is a POSIX shell builtin (IEEE 1003.1-2017 section 2.9.1.1, Command Search and +// Execution, invoked here through `sh -c` rather than by name), present wherever a POSIX-conforming +// shell is -- unlike `which`, an EXTERNAL BINARY (part of debianutils on Debian/Ubuntu, not part of +// POSIX or a base busybox install) that may be absent on stripped appliance images, BMC firmware +// shells (Dropbear), or minimal containers. This operation's own assumption is not unconditional +// either: it still requires a POSIX shell at `sh` on the target's PATH. gunbc.fleet_intent's srv1-4 +// ComputeHost, and gunbc.ci_fleet's own CI/falsifier runner, both carry an `OperatingSystemSurface +// { distro_or_product: none, ... }` -- no distro or package-set authority anywhere in this fleet, +// our own runner included (crisp-wren-896 finding, PR #8590; the modeling gap is named at +// std.os.types operating_system_surface_package_authority_gap_note). This operation's assumption +// strictly dominates `which`'s -- every target that can run `which` can run `command -v`, and the +// converse fails on busybox and stripped images -- which is why the former shell.Which.Check +// operation was deleted rather than kept as a second, narrower-scope choice (crisp-wren-896, PR +// #8590): a selection rule between two operations is only worth its cost when each is right +// somewhere the other is wrong, and here nothing was. If even the POSIX-shell assumption cannot be +// established for a target, the honest realization is a typed refusal, not a silent fall-through +// inheriting this operation's result as ground truth. + +// The probed command name is carried in its OWN argv slot (`{command}` bound to the shell's +// trailing positional parameter), never concatenated into the `-c` script text -- the same `-c +// script scriptname arg1` positional-parameter binding shell.Find.FilesAndSymlinksWithMode already +// uses. The value never touches the string parsed as shell code, so there is no escaping table to +// maintain and no quoting authority this operation depends on. service shell.PosixCommandV { operation Check { diff --git a/dag/extdeps/shell/exec.dag b/dag/extdeps/shell/exec.dag index f93031ba049..92788f83a24 100644 --- a/dag/extdeps/shell/exec.dag +++ b/dag/extdeps/shell/exec.dag @@ -45,11 +45,19 @@ fn transport_script_seal(body: String) -> TransportScript ] = TransportScript { body: body } -data shell_exec_run_stdin_route_note: String = "shell.Exec.Run transport: argv is fixed [bash, -s]; the script input is routed through the transport stdin field. Large payloads belong here — they never become a single argv token, so they do not encounter the Linux per-argument exec ceiling modeled in extdeps.exec.exec_arg_limit.host_exec_arg_max_strlen (deploy Part A / #6711)." +// shell.Exec.Run transport: argv is fixed [bash, -s]; the script input is routed through the +// transport stdin field. Large payloads belong here — they never become a single argv token, so +// they do not encounter the Linux per-argument exec ceiling modeled in +// extdeps.exec.exec_arg_limit.host_exec_arg_max_strlen (deploy Part A / #6711). data service_op_string_wire_projection_method_fork_note: String = "🟡 KNOWN CLASS (infer-layer, not runtime Value): extdeps service-operation output fields declared `T from \"wire_key\"` (e.g. stdout: String from \"stdout\") infer as FaithfulFreeMonoid / Coproduct carriers when the closure lacks v1-seed sources (v1.compiler.infer rust_corpus_repr), while ordinary String parameters and algebra ReceiverSelf method templates (trim, length, …) attach to the declared String type node. Method syntax on the wire-minted receiver therefore refuses (method trim not found on Coproduct(FreeMonoid)) even though the extdeps contract names String. Free-call trim(s: x) can still compile when explicit import lists std.algebra { trim } and the argument coerces to String — a binding bridge, not type-node unification (#8062 trim registry). Pinning witness: fixtures/class_b_trim/monoid_specimen.dag + v1-compiler-tests trim_method_form_fails_on_freemonoid_receiver; bold-eagle effect_plan wire projection confirmed same class. Every other String method on any service-op result field shares this latent fork until dissolved. DISSOLVE-ON: service-op wire projection unifies with the declared output type authority so ReceiverSelf algebra methods attach to the same node the extdeps contract names (FaithfulFreeMonoid mint ≡ String for String-declared wire fields). SUPERSEDED-MECHANISM (eager-deer-389, RustCorpusRepr cutover): the trigger this class names -- v1.compiler.infer rust_corpus_repr selecting a corpus-global FaithfulFreeMonoid arm when the closure lacks v1-seed sources -- NO LONGER EXISTS. RustCorpusRepr and its closure-provenance switch are deleted; primitive realization is keyed on each type's declaring module, and the Rust text carrier is now unconditionally host-native. So the CITATION is stale even if the CLASS is not: this note's owner must re-measure whether a wire-minted receiver still forks, rather than reading the old trigger as live. One data point in that direction: emitting std.algebra::trim previously produced v1_rt::from_code_point((1 / 0)) in both arms of a tautological if -- the declared .dag seam leaking instead of routing to host realization -- and now emits v1_rt::trim(s). That is the same carrier family this class is about, but it is one function in one closure and is NOT a claim that the class is closed." -data shell_exec_check_argv_embed_note: String = "shell.Exec.Check transport: argv is [sh, -c, command] — the entire command string is ONE argv element. On Linux hosts this is the surface that hits execve(2) MAX_ARG_STRLEN (extdeps.exec.exec_arg_limit.host_exec_arg_max_strlen): an oversized embed fails E2BIG (opaque os error 7) unless the interpreter refuses first at dispatch_shell (InterpError::ArgvExceedsHostArgMax). Prefer Run for payloads approaching the per-argument ceiling." +// shell.Exec.Check transport: argv is [sh, -c, command] — the entire command string is ONE argv +// element. On Linux hosts this is the surface that hits execve(2) MAX_ARG_STRLEN +// (extdeps.exec.exec_arg_limit.host_exec_arg_max_strlen): an oversized embed fails E2BIG (opaque os +// error 7) unless the interpreter refuses first at dispatch_shell +// (InterpError::ArgvExceedsHostArgMax). Prefer Run for payloads approaching the per-argument +// ceiling. data shell_exec_transport_ceiling_authority: ByteSize = host_exec_arg_max_strlen diff --git a/dag/extdeps/simulators/icarus_verilog/subject.dag b/dag/extdeps/simulators/icarus_verilog/subject.dag index 07ab40b65fb..056749f0e6f 100644 --- a/dag/extdeps/simulators/icarus_verilog/subject.dag +++ b/dag/extdeps/simulators/icarus_verilog/subject.dag @@ -12,7 +12,14 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data icarus_verilog_subject_note: String = "ICARUS VERILOG, THE SIMULATOR — a runtime, deliberately not a language target, for the same reason ngspice is not one: it EXECUTES the Verilog medium and is independently governed and versioned, so under external-upstream decomposition it gets its own module rather than becoming an arm of the language. The distinction is the one this repository already drew for SPICE: what we emit and what runs it are separate facts, joined by a runtime binding rather than fused into one subject. IT IS ALSO NOT THE ONLY ONE — Verilator is a second implementation of the same relationship, and adding it must not require editing this file, which is the test of whether the decomposition was real." +// ICARUS VERILOG, THE SIMULATOR — a runtime, deliberately not a language target, for the same +// reason ngspice is not one: it EXECUTES the Verilog medium and is independently governed and +// versioned, so under external-upstream decomposition it gets its own module rather than becoming +// an arm of the language. The distinction is the one this repository already drew for SPICE: what +// we emit and what runs it are separate facts, joined by a runtime binding rather than fused into +// one subject. IT IS ALSO NOT THE ONLY ONE — Verilator is a second implementation of the same +// relationship, and adding it must not require editing this file, which is the test of whether the +// decomposition was real. type IcarusVerilog = | IcarusVerilogSimulator diff --git a/dag/extdeps/simulators/ngspice/subject.dag b/dag/extdeps/simulators/ngspice/subject.dag index bfc0a277913..2c431e40011 100644 --- a/dag/extdeps/simulators/ngspice/subject.dag +++ b/dag/extdeps/simulators/ngspice/subject.dag @@ -12,7 +12,18 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data ngspice_simulator_subject_note: String = "NGSPICE, THE SIMULATOR — A RUNTIME, DELIBERATELY NOT A LANGUAGE TARGET (cursor review 48122, P0). Verified live 2026-08-04: the fetched page describes ngspice as 'the open source Spice circuit simulator', a 'mixed mode - mixed level circuit simulator - based on Berkeley's Spice3f5', with the current release manual at version 46. WHY THIS IS ITS OWN SUBJECT AND NOT AN ARM OF SPICE: ngspice is independently versioned and maintained, so under external-upstream decomposition it is its own governed entity — the same reason Chromium is not an arm of the HTML standard. WHY IT IS NOT REGISTERED AS A TARGET: a simulator is not a language. It EXECUTES the SPICE netlist medium, and that relationship is carried as a TargetRuntimeBinding in gunbc.language_target_registry, which is what keeps 'which medium do we emit' separable from 'which tool runs it'. Registering it as a target was the fusion this split repairs. PLACEMENT: homed under simulators/ rather than languages/ precisely so the folder stops implying it is a language; paths are discriminators, not gospel, but a misleading one is worth moving." +// NGSPICE, THE SIMULATOR — A RUNTIME, DELIBERATELY NOT A LANGUAGE TARGET (cursor review 48122, P0). +// Verified live 2026-08-04: the fetched page describes ngspice as 'the open source Spice circuit +// simulator', a 'mixed mode - mixed level circuit simulator - based on Berkeley's Spice3f5', with +// the current release manual at version 46. WHY THIS IS ITS OWN SUBJECT AND NOT AN ARM OF SPICE: +// ngspice is independently versioned and maintained, so under external-upstream decomposition it is +// its own governed entity — the same reason Chromium is not an arm of the HTML standard. WHY IT IS +// NOT REGISTERED AS A TARGET: a simulator is not a language. It EXECUTES the SPICE netlist medium, +// and that relationship is carried as a TargetRuntimeBinding in gunbc.language_target_registry, +// which is what keeps 'which medium do we emit' separable from 'which tool runs it'. Registering it +// as a target was the fusion this split repairs. PLACEMENT: homed under simulators/ rather than +// languages/ precisely so the folder stops implying it is a language; paths are discriminators, not +// gospel, but a misleading one is worth moving. type Ngspice = | NgspiceSimulator diff --git a/dag/extdeps/ssh/client_options.dag b/dag/extdeps/ssh/client_options.dag index 97ee080b498..a7c56066560 100644 --- a/dag/extdeps/ssh/client_options.dag +++ b/dag/extdeps/ssh/client_options.dag @@ -8,13 +8,12 @@ import extdeps.uri { Uri, Https } // The OpenSSH CLIENT option vocabulary, modeled once. // -// This is a SEPARATE authority from extdeps.ssh.session deliberately. That module anchors to -// RFC 4254, the SSH Connection Protocol, which defines the channel requests it declares (Exec, -// ExecArgv, ExecScript). It does NOT define -o, ssh_config keywords, or any of their value -// domains -- those are OpenSSH implementation surface, documented in ssh_config(5). session.dag -// already separates PROTOCOL from IMPLEMENTATION in its own notes; putting these keywords under -// its RFC anchor would ground them in a document that does not contain them, which is the -// citation defect DESIGN section 3 names. +// This is a SEPARATE authority from extdeps.ssh.session deliberately. That module anchors to RFC +// 4254, the SSH Connection Protocol, which defines the channel requests it declares (Exec, +// ExecArgv, ExecScript) but NOT -o, ssh_config keywords, or their value domains — those are OpenSSH +// implementation surface, documented in ssh_config(5). session.dag already separates PROTOCOL from +// IMPLEMENTATION; putting these keywords under its RFC anchor would ground them in a document that +// does not contain them, the citation defect DESIGN section 3 names. data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { uri: Uri { scheme: Https @@ -23,10 +22,9 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } // THE SUBJECT IS THE CLIENT OPTION VOCABULARY ITSELF -- SshClientOption: the keyword/value pairs -// ssh(1) accepts via -o, whose domains ssh_config(5) enumerates. It is NOT the connection protocol -// (extdeps.ssh.session, RFC 4254) and NOT any particular verification policy: which options a given -// invocation carries is a caller's fact, while WHICH OPTIONS EXIST and how each serializes is this -// upstream's. +// ssh(1) accepts via -o, whose domains ssh_config(5) enumerates. NOT the connection protocol +// (extdeps.ssh.session, RFC 4254) and NOT any verification policy: which options an invocation +// carries is a caller's fact; WHICH OPTIONS EXIST and how each serializes is this upstream's. data extdeps_model_scope: ExternalModelScope = ExternalModelScope { subject: ExternalSubjectRef { declaration: DeclarationRef { @@ -40,37 +38,34 @@ data extdeps_model_scope: ExternalModelScope = ExternalModelScope { } // WHY THIS MODULE EXISTS, measured rather than asserted. Before it, the ssh client option grammar -// was re-coined as free text at every site that needed one. In extdeps/ssh alone: -// -// counted by INVOCATION IDENTITY (files that actually run ssh/scp), not by token -- an earlier -// revision of this note said "56 raw -o strings corpus-wide", which counted curl -o, xorriso -o and -// http/tmux options and was therefore not an OpenSSH population at all: +// was re-coined as free text at every site needing one. In extdeps/ssh alone, counted by INVOCATION +// IDENTITY (files that actually run ssh/scp), not by token -- an earlier revision said "56 raw -o +// strings corpus-wide", which counted curl -o, xorriso -o and http/tmux options and was not an +// OpenSSH population at all: // // openbmc_password_ssh_transport.dag 40 pairs <- the bulk of the migration, unmentioned before // password_session.dag 6 pairs // exec/command.dag 2 pairs (StrictHostKeyChecking=no + UserKnownHostsFile=/dev/null) // session.dag 1 pair (Reachability: BatchMode + ConnectTimeout) // -// Three separate costs, none of them cosmetic: +// Three costs, none cosmetic: // -// ONE, the closed value domain was a string. ssh_config(5) gives StrictHostKeyChecking exactly -// five values (yes | no | ask | accept-new | off). Spelled as text, `yes` and `no` are the same -// KIND of thing to every tool in this repository -- so a site that disables host-key verification -// and a site that requires it are indistinguishable without reading prose. Both exist in the -// corpus today. As a closed coproduct the difference is a variant a lens can match on. +// ONE, the closed value domain was a string. ssh_config(5) gives StrictHostKeyChecking exactly five +// values (yes | no | ask | accept-new | off). As text, `yes` and `no` are the same KIND of thing to +// every tool here, so a site disabling host-key verification and one requiring it are +// indistinguishable without reading prose; both exist in the corpus today. As a closed coproduct +// the difference is a variant a lens can match on. // -// TWO, `-o` and `=` were structure built by concatenation. Key=Value is a serialization of a pair, -// and building it with string joins puts the pair's shape in every caller instead of in one -// serializer. That is the section 2 deep-decomposition failure exactly: a String leaf hiding -// named parts that the source already knows. +// TWO, `-o` and `=` were structure built by concatenation. Key=Value is a serialization of a pair; +// string joins put the pair's shape in every caller instead of one serializer -- the section 2 +// deep-decomposition failure: a String leaf hiding named parts the source already knows. // -// THREE, and the load-bearing one: options inside a `transport` literal are UNWITNESSABLE. The -// established corpus idiom for invocation structure is a `shape_*_argv` function -- see -// shape_scp_copy_to_argv, which has no service operation at all and is asserted directly by +// THREE, the load-bearing one: options inside a `transport` literal are UNWITNESSABLE. The corpus +// idiom for invocation structure is a `shape_*_argv` function -- see shape_scp_copy_to_argv, which +// has no service operation and is asserted directly by // roadmap_dashboard_instance_apply_witness_test, and git's shape_git_*_argv family. A function can -// be called by a witness and pinned; a literal buried in a service block cannot be reached by one. -// That is why the yes/no divergence above went unnoticed rather than being caught: no test could -// name the thing that disagreed. +// be called by a witness and pinned; a literal in a service block cannot. That is why the yes/no +// divergence above went unnoticed: no test could name the thing that disagreed. type StrictHostKeyCheckingValue = StrictYes | StrictNo @@ -78,21 +73,20 @@ type StrictHostKeyCheckingValue | StrictAcceptNew | StrictOff -// THE OTHER FOUR SOURCES OF HOST-KEY TRUST, modeled because a policy that names only -// UserKnownHostsFile does not exclude them. ssh_config(5) gives an OpenSSH client five independent -// ways to decide a host key is acceptable, and setting one says nothing about the other four: -// GlobalKnownHostsFile is a system-wide file (/etc/ssh/ssh_known_hosts by default) consulted -// ALONGSIDE the user file; KnownHostsCommand names a program whose stdout is treated as known_hosts -// lines; VerifyHostKeyDNS accepts a key on the strength of an SSHFP DNS record; and UpdateHostKeys -// lets the SERVER add keys to the client's known_hosts mid-session. On a host we did not build -- -// which is every host in this fleet, all six enrolled ScannedFirstContact -- any of the four can be -// preconfigured, and a trust policy that leaves them at their defaults is anchored to the registry -// plus whatever else the machine already believed. They are modeled here so a policy can set them -// to their disabling values EXPLICITLY, which is the difference between "we pinned the user file" -// and "the registry is the only thing ssh will accept". +// THE OTHER FOUR SOURCES OF HOST-KEY TRUST, modeled because a policy naming only UserKnownHostsFile +// does not exclude them. ssh_config(5) gives the client five independent ways to accept a host key, +// and setting one says nothing about the others: GlobalKnownHostsFile is a system-wide file +// (/etc/ssh/ssh_known_hosts by default) consulted ALONGSIDE the user file; KnownHostsCommand names +// a program whose stdout is treated as known_hosts lines; VerifyHostKeyDNS accepts a key on an +// SSHFP DNS record; UpdateHostKeys lets the SERVER add keys to the client's known_hosts +// mid-session. On a host we did not build -- every host in this fleet, all six enrolled +// ScannedFirstContact -- any of the four can be preconfigured, and a policy leaving them at +// defaults is anchored to the registry plus whatever the machine already believed. Modeling them +// lets a policy set them to their disabling values EXPLICITLY -- the difference between "we pinned +// the user file" and "the registry is the only thing ssh will accept". // -// VerifyHostKeyDNS is three-valued upstream rather than a Bool: `ask` is a real third state, not a -// spelling of yes or no, and collapsing it would be the state-space conflation the corpus names. +// VerifyHostKeyDNS is three-valued upstream, not a Bool: `ask` is a real third state, and +// collapsing it would be the state-space conflation the corpus names. type VerifyHostKeyDnsValue = VerifyHostKeyDnsYes | VerifyHostKeyDnsNo @@ -104,11 +98,10 @@ type UpdateHostKeysValue | UpdateHostKeysNo | UpdateHostKeysAsk -// One variant per keyword, each carrying the value domain THAT keyword actually admits, so a -// nonsensical pairing has no constructor. ConnectTimeout takes an Int because ssh_config(5) -// specifies seconds; BatchMode takes a Bool because its domain is yes|no and a Bool is that domain -// already grounded -- reusing it rather than minting a second two-valued enum (section 2: no net -// new concepts by re-invention). +// One variant per keyword, each carrying the value domain THAT keyword admits, so a nonsensical +// pairing has no constructor. ConnectTimeout takes an Int because ssh_config(5) specifies seconds; +// BatchMode takes a Bool because its domain is yes|no and Bool is that domain already grounded +// (section 2: no net new concepts by re-invention). type SshClientOption = StrictHostKeyChecking { value: StrictHostKeyCheckingValue } | UserKnownHostsFile { path: NonEmptyStr } @@ -210,20 +203,19 @@ fn serialize_ssh_client_options(options: List) -> List flat_map(options, o => serialize_ssh_client_option(option: o)) } -// THE COMPLETE EXCLUSIVE POSTURE: every one of OpenSSH's five host-key trust sources set, four of -// them to their disabling values, so the named anchor is the ONLY thing that can make a host key -// acceptable. An earlier revision of this function returned just the StrictHostKeyChecking and -// UserKnownHostsFile pair, which pins the file ssh reads without excluding the four other routes -// beside it -- so a host carrying a preconfigured /etc/ssh/ssh_known_hosts, a KnownHostsCommand, or -// an SSHFP record would still have verified against something outside the registry, and -// UpdateHostKeys would have let the far end ADD keys to what we trust mid-session. +// THE COMPLETE EXCLUSIVE POSTURE: all five OpenSSH host-key trust sources set, four to their +// disabling values, so the named anchor is the ONLY thing that can make a host key acceptable. An +// earlier revision returned just the StrictHostKeyChecking and UserKnownHostsFile pair, which pins +// the file ssh reads without excluding the other four routes -- a host with a preconfigured +// /etc/ssh/ssh_known_hosts, a KnownHostsCommand, or an SSHFP record would still verify against +// something outside the registry, and UpdateHostKeys would let the far end ADD keys mid-session. // -// This function is still only a builder, not the wall: a caller may prepend its own option and -// OpenSSH takes the FIRST obtained value for each keyword, so an earlier StrictHostKeyChecking=no -// silently outranks everything here. The wall is gunbc.fleet_known_hosts_anchor -// FleetOpenSshTrustPolicy, which is sealed, is constructible only from a successful materialization, -// and is what consumers accept. This builder exists separately because it is PURE and therefore -// directly assertable by a witness, while the sealed policy requires an executed materialization. +// Still only a builder, not the wall: a caller may prepend its own option and OpenSSH takes the +// FIRST obtained value per keyword, so an earlier StrictHostKeyChecking=no silently outranks +// everything here. The wall is gunbc.fleet_known_hosts_anchor FleetOpenSshTrustPolicy — sealed, +// constructible only from a successful materialization, and what consumers accept. This builder +// exists separately because it is PURE and directly assertable by a witness; the sealed policy +// requires an executed materialization. fn openssh_exclusive_known_hosts_trust_options(known_hosts: NonEmptyStr) -> List { [ StrictHostKeyChecking { value: StrictYes }, @@ -237,16 +229,14 @@ fn openssh_exclusive_known_hosts_trust_options(known_hosts: NonEmptyStr) -> List // ── The invocation shaper ───────────────────────────────────────────────────────────────────── -// THE ONE PLACE "ssh" AND "--" EXIST. This is a function rather than a transport literal for the -// reason the whole module exists: a literal inside a `service` block cannot be called by a witness, -// so its argv order, its option placement and its separator are unassertable. shape_scp_copy_to_argv -// has no service operation at all for exactly this reason, and git's shape_git_*_argv family is the -// same idiom. +// THE ONE PLACE "ssh" AND "--" EXIST. A function rather than a transport literal for the reason the +// module exists: a literal inside a `service` block cannot be called by a witness, so its argv +// order, option placement and separator are unassertable. shape_scp_copy_to_argv has no service +// operation for exactly this reason; git's shape_git_*_argv family is the same idiom. // // Option words precede the target because OpenSSH takes the FIRST specified value for most -// directives -- so an option appended after the target, or after a caller-supplied word, would be -// silently outranked by anything earlier. Order here is load-bearing, which is why it is asserted by -// execution rather than described. +// directives -- an option after the target, or after a caller-supplied word, is silently outranked +// by anything earlier. Order is load-bearing, so it is asserted by execution rather than described. fn shape_openssh_exec_portable_words_argv( target: NonEmptyStr, options: List, @@ -261,32 +251,32 @@ fn shape_openssh_exec_portable_words_argv( ) } -// AMBIENT CONFIGURATION IS THE OTHER HALF OF THE PROBLEM, and the host-key options do not touch it. -// Even with all five trust sources pinned, ordinary ssh_config can still decide the username, resolve -// the hostname to a different address through a Host alias, insert a ProxyJump, choose which identity -// file or agent authenticates, and reuse an existing multiplexed connection whose authentication -// happened under conditions we never observed. Every one of those changes WHO and WHAT was actually -// contacted, which is precisely what a principal-identity probe is trying to establish. +// AMBIENT CONFIGURATION IS THE OTHER HALF OF THE PROBLEM, untouched by the host-key options. Even +// with all five trust sources pinned, ordinary ssh_config can still decide the username, resolve +// the hostname to a different address through a Host alias, insert a ProxyJump, choose the identity +// file or agent, and reuse a multiplexed connection authenticated under conditions we never +// observed. Each changes WHO and WHAT was contacted, precisely what a principal-identity probe +// establishes. // -// `-o ConfigFile=none` is not the spelling OpenSSH accepts for this; the documented control is the -// -F flag, which takes a path and suppresses both user and system config when given `none`. It is -// modeled as ConfigFile here so it serializes through the same one serializer as everything else and -// is projected to -F by the shaper -- the alternative was a second ad-hoc word-building path beside -// the option serializer, which is the fork this module exists to prevent. +// `-o ConfigFile=none` is not the spelling OpenSSH accepts; the documented control is the -F flag, +// which takes a path and suppresses user and system config when given `none`. It is modeled as +// ConfigFile so it serializes through the one serializer and is projected to -F by the shaper -- +// the alternative was a second ad-hoc word-building path beside the option serializer, the fork +// this module exists to prevent. fn openssh_suppress_ambient_configuration_options() -> List { [ConfigFile { path: "none" as NonEmptyStr }] } // THE CREDENTIAL HALF. A host-key posture can be exact while authentication succeeds through the // WRONG LOCAL KEY: an agent holding several identities, an ambient IdentityFile, or a password -// fallback can all make a command succeed without the modeled credential having caused it. For a -// probe whose entire product is "which account accepts which exact key", that is not a detail -- it -// is the difference between an observation and a coincidence. +// fallback can each make a command succeed without the modeled credential causing it. For a probe +// whose product is "which account accepts which exact key", that is the difference between an +// observation and a coincidence. // -// IdentitiesOnly is the load-bearing one: without it OpenSSH offers agent keys BEFORE the file named -// by IdentityFile, so naming the file does not mean the file authenticated. IdentityAgent=none -// removes the agent entirely, and the three authentication-method options remove the fallbacks that -// would otherwise let a success mean something other than publickey. +// IdentitiesOnly is load-bearing: without it OpenSSH offers agent keys BEFORE the file named by +// IdentityFile, so naming the file does not mean the file authenticated. IdentityAgent=none removes +// the agent entirely, and the three authentication-method options remove the fallbacks that would +// let a success mean something other than publickey. fn openssh_exact_credential_options(identity_file: NonEmptyStr) -> List { [ IdentityFile { path: identity_file }, @@ -298,20 +288,18 @@ fn openssh_exact_credential_options(identity_file: NonEmptyStr) -> List List { match option { ConfigFile { path: p } => ["-F", p as String] diff --git a/dag/extdeps/ssh/keys.dag b/dag/extdeps/ssh/keys.dag index 819b358249e..f00454c4240 100644 --- a/dag/extdeps/ssh/keys.dag +++ b/dag/extdeps/ssh/keys.dag @@ -23,7 +23,10 @@ data ssh_key_algorithm_token_registry_authority: ExternalAuthority = ExternalAut } } -// ssh_key_algorithm_token emits registered wire tokens: the token SET is owned by the IANA SSH Protocol Parameters public-key-algorithm-names registry (anchor above); each variant's cryptosystem and token carry their own upstream authorities in the per-variant rows below — a referenced entity carries ITS authority, never its consumer's (the Vendor precedent). +// ssh_key_algorithm_token emits registered wire tokens: the token SET is owned by the IANA SSH +// Protocol Parameters public-key-algorithm-names registry (anchor above); each variant's +// cryptosystem and token carry their own upstream authorities in the per-variant rows below — a +// referenced entity carries ITS authority, never its consumer's (the Vendor precedent). data ed25519_cryptosystem_authority: ExternalAuthority = ExternalAuthority { uri: Uri { scheme: Https @@ -31,7 +34,8 @@ data ed25519_cryptosystem_authority: ExternalAuthority = ExternalAuthority { } } -data ed25519_authority_note: String = "Ed25519: design authority ed25519.cr.yp.to (Bernstein et al.); EdDSA specified in RFC 8032; the ssh-ed25519 wire token is registered by RFC 8709." +// Ed25519: design authority ed25519.cr.yp.to (Bernstein et al.); EdDSA specified in RFC 8032; the +// ssh-ed25519 wire token is registered by RFC 8709. data rsa_cryptosystem_authority: ExternalAuthority = ExternalAuthority { uri: Uri { @@ -40,7 +44,10 @@ data rsa_cryptosystem_authority: ExternalAuthority = ExternalAuthority { } } -data rsa_authority_note: String = "Rsa: PKCS #1 v2.2 (RFC 8017); the ssh-rsa wire token is defined in RFC 4253 section 6.6. POSTURE: ssh-rsa SIGNATURES (SHA-1) are deprecated by RFC 8332 in favor of rsa-sha2-256/rsa-sha2-512 — an authorized_keys ssh-rsa KEY line remains valid, but selecting Rsa for new fleet keys is a deprecated posture and the fleet's declared keys are Ed25519." +// Rsa: PKCS #1 v2.2 (RFC 8017); the ssh-rsa wire token is defined in RFC 4253 section 6.6. POSTURE: +// ssh-rsa SIGNATURES (SHA-1) are deprecated by RFC 8332 in favor of rsa-sha2-256/rsa-sha2-512 — an +// authorized_keys ssh-rsa KEY line remains valid, but selecting Rsa for new fleet keys is a +// deprecated posture and the fleet's declared keys are Ed25519. data ecdsa_p256_cryptosystem_authority: ExternalAuthority = ExternalAuthority { uri: Uri { diff --git a/dag/extdeps/ssh/password_session.dag b/dag/extdeps/ssh/password_session.dag index 25748e01377..95afd4e4620 100644 --- a/dag/extdeps/ssh/password_session.dag +++ b/dag/extdeps/ssh/password_session.dag @@ -14,7 +14,13 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { data sshpass_password_fd_authority: ExternalAuthority = sshpass_authority -data password_ssh_transport_security_note: String = "PasswordSsh.CopyFile feeds the password to sshpass through inherited file descriptor 0 (-d 0), never argv, environment, remote command text, or transferred content. Remote execution is intentionally absent from this generic session module: each dependency owns a closed operation algebra whose transport exposes only fixed operation-specific argv, so callers cannot submit arbitrary argv or authored shell. CopyFile carries local and remote paths as distinct transport arguments and requires strict verification against the caller's target-specific known-hosts file. Password auth itself dissolves to a provisioned automation key or controller-native OAuth transport." +// PasswordSsh.CopyFile feeds the password to sshpass through inherited fd 0 (-d 0), never argv, +// environment, remote command text, or transferred content. Remote execution is deliberately absent +// here: each dependency owns a closed operation algebra whose transport exposes only fixed +// operation-specific argv, so callers cannot submit arbitrary argv or authored shell. CopyFile +// carries local and remote paths as distinct transport arguments and requires strict verification +// against the caller's target-specific known-hosts file. Password auth itself dissolves to a +// provisioned automation key or controller-native OAuth transport. type PasswordSshExecResult { exit_code: Int diff --git a/dag/extdeps/ssh/session.dag b/dag/extdeps/ssh/session.dag index 2501cc9ea93..68173696091 100644 --- a/dag/extdeps/ssh/session.dag +++ b/dag/extdeps/ssh/session.dag @@ -1,6 +1,5 @@ module extdeps.ssh.session - import extdeps.external_authority { ExternalAuthority } import extdeps.uri { Uri, Https } import std.types { Bool, FilePath, Int, List, NonEmptyStr } @@ -11,13 +10,64 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data session_authority_is_the_connection_protocol_note: String = "This anchor moved from RFC 4253 to RFC 4254 (review 45901). What this module declares is Exec, ExecArgv, ExecScript and Reachability - channel requests, which RFC 4254 (SSH Connection Protocol) defines. RFC 4253 is the SSH Transport Layer Protocol: key exchange, encryption, MAC. It underlies every SSH session but defines none of the operations here, so it was the authority for a layer this module does not model, and a note grounding an exec fact in it would have been citing the wrong document.\\n\\nThe sibling modules keep their own anchors and are untouched; this is a fidelity correction for this file's declarations, not a fleet-wide repoint." +// This anchor moved from RFC 4253 to RFC 4254 (review 45901). What this module declares is Exec, +// ExecArgv, ExecScript and Reachability - channel requests, which RFC 4254 (SSH Connection +// Protocol) defines. RFC 4253 is the SSH Transport Layer Protocol: key exchange, encryption, MAC. +// It underlies every SSH session but defines none of the operations here, so it was the authority +// for a layer this module does not model, and a note grounding an exec fact in it would have been +// citing the wrong document.\n\nThe sibling modules keep their own anchors and are untouched; this +// is a fidelity correction for this file's declarations, not a fleet-wide repoint. -data ssh_exec_portable_words_executable_position_note: String = "These operations take CLIENT_ARGS -- everything after argv[0] -- and the transport supplies the literal `ssh` itself. An earlier revision spliced the whole argv from the input, which the operation-argv executable-position wall refused, correctly: argv[0] decided by a bound input means a CALLER decides which program runs, and std.operation_argv states that a generic binder over the executable position is strictly worse than the hardcoded list it replaces. The shaper's own prepending of `ssh` was no defense -- the OPERATION could not know that, and a second caller could have passed anything. The wall found this within one CI run of the operations landing." +// These operations take CLIENT_ARGS -- everything after argv[0] -- and the transport supplies the +// literal `ssh` itself. An earlier revision spliced the whole argv from the input, which the +// operation-argv executable-position wall refused, correctly: argv[0] decided by a bound input +// means a CALLER decides which program runs, and std.operation_argv states that a generic binder +// over the executable position is strictly worse than the hardcoded list it replaces. The shaper's +// own prepending of `ssh` was no defense -- the OPERATION could not know that, and a second caller +// could have passed anything. The wall found this within one CI run of the operations landing. -data ssh_exec_portable_words_supersedes_exec_argv_note: String = "ExecPortableWords is the successor to ExecArgv and lands BESIDE it rather than replacing it, so the migration is a counted population rather than one edit that reds twenty call sites at once. Two differences, both corrections. ONE: it takes an argv already shaped by gunbc.fleet_known_hosts_anchor shape_fleet_ssh_exec instead of building [ssh, host, --, argv] inside a transport literal. A literal in a service block cannot be called by a witness, which is how this corpus came to hold one site requiring host-key verification and another disabling it, spelled as indistinguishable strings with no test able to name the disagreement. TWO: the NAME. ExecArgv promised a remote argv vector, and RFC 4254 section 6.5 gives the exec channel request a single string-typed command field, so no SSH client carries argv structure across that boundary -- a caller reading ExecArgv reasonably concludes its words survive as words, and they do not. What holds is the LOCAL half plus the portable-word allowlist, and ExecPortableWords names exactly that. DISSOLVE-ON: ExecArgv and this note are deleted together when the remaining consumers (host_effect_realize, live_deploy, systemctl/hostname reads, roadmap actuators) route through a FleetSshExecutionContext." +// ExecPortableWords is the successor to ExecArgv and lands BESIDE it rather than replacing it, so +// the migration is a counted population rather than one edit that reds twenty call sites at once. +// Two differences, both corrections. ONE: it takes an argv already shaped by +// gunbc.fleet_known_hosts_anchor shape_fleet_ssh_exec instead of building [ssh, host, --, argv] +// inside a transport literal. A literal in a service block cannot be called by a witness, which is +// how this corpus came to hold one site requiring host-key verification and another disabling it, +// spelled as indistinguishable strings with no test able to name the disagreement. TWO: the NAME. +// ExecArgv promised a remote argv vector, and RFC 4254 section 6.5 gives the exec channel request a +// single string-typed command field, so no SSH client carries argv structure across that boundary +// -- a caller reading ExecArgv reasonably concludes its words survive as words, and they do not. +// What holds is the LOCAL half plus the portable-word allowlist, and ExecPortableWords names +// exactly that. DISSOLVE-ON: ExecArgv and this note are deleted together when the remaining +// consumers (host_effect_realize, live_deploy, systemctl/hostname reads, roadmap actuators) route +// through a FleetSshExecutionContext. -data ssh_remote_exec_carries_a_command_string_note: String = "What ExecArgv does and does not carry, separated into what the PROTOCOL fixes and what an IMPLEMENTATION chooses, because those carry different weight and the earlier revision of this note fused them (review 45901).\\n\\nLOCAL, from this operation's own declaration: the transport argv is [ssh, host, --, argv], so the words reach the ssh client as distinct argv members and no local shell sees them. The local invocation is genuinely structured.\\n\\nPROTOCOL, from the anchored authority: RFC 4254 section 6.5 defines the exec channel request as carrying a single `command` field of type string. There is no argv vector in the request for structure to travel in. This is the universal half - it holds for every SSH client and server, so no ExecArgv-shaped operation over SSH can carry argv structure across the boundary AS STRUCTURE, and an operation wanting that promise needs a receiver on the far side accepting a structured invocation and calling exec itself. ssh.Session does not implement that and must not be read as implementing it.\\n\\nIMPLEMENTATION, which the protocol does NOT mandate and which this note therefore scopes rather than asserts: the OpenSSH client (ssh(1)) joins the command words into that single string, and OpenSSH's server (sshd(8)) executes it through the user's login shell, which re-parses it. RFC 4254 says the server starts execution of the given command; it does not say the command is joined a particular way, nor that a shell interprets it. A different server could exec the string directly.\\n\\nWHICH HALF CARRIES WHICH CONCLUSION, since they are not interchangeable: loss of argv structure follows from the PROTOCOL and is unavoidable. Re-splitting on spaces and interpretation of quotes, redirects and metacharacters follows from the OpenSSH client and server behavior above, and that is what the portable-word allowlist at gunbc.typed_argv_exec exists to bound. A note claiming the second as protocol-mandated would be overstating an extdeps authority, which is the defect this revision fixes.\\n\\nCorrection receipt: before this PR the note read `ExecArgv splices a List remote argv without embedding a shell command string`, which is false for the remote half and contradicted the note at the enforcing edge, which said the opposite in the same corpus. Nothing depended on the false half - the allowlist was already fail-closed - so the defect was that the corpus asserted both readings and a reader could reach either." +// What ExecArgv does and does not carry, separated into what the PROTOCOL fixes and what an +// IMPLEMENTATION chooses, because those carry different weight and the earlier revision of this +// note fused them (review 45901).\n\nLOCAL, from this operation's own declaration: the transport +// argv is [ssh, host, --, argv], so the words reach the ssh client as distinct argv members and no +// local shell sees them. The local invocation is genuinely structured.\n\nPROTOCOL, from the +// anchored authority: RFC 4254 section 6.5 defines the exec channel request as carrying a single +// `command` field of type string. There is no argv vector in the request for structure to travel +// in. This is the universal half - it holds for every SSH client and server, so no ExecArgv-shaped +// operation over SSH can carry argv structure across the boundary AS STRUCTURE, and an operation +// wanting that promise needs a receiver on the far side accepting a structured invocation and +// calling exec itself. ssh.Session does not implement that and must not be read as implementing +// it.\n\nIMPLEMENTATION, which the protocol does NOT mandate and which this note therefore scopes +// rather than asserts: the OpenSSH client (ssh(1)) joins the command words into that single string, +// and OpenSSH's server (sshd(8)) executes it through the user's login shell, which re-parses it. +// RFC 4254 says the server starts execution of the given command; it does not say the command is +// joined a particular way, nor that a shell interprets it. A different server could exec the string +// directly.\n\nWHICH HALF CARRIES WHICH CONCLUSION, since they are not interchangeable: loss of +// argv structure follows from the PROTOCOL and is unavoidable. Re-splitting on spaces and +// interpretation of quotes, redirects and metacharacters follows from the OpenSSH client and server +// behavior above, and that is what the portable-word allowlist at gunbc.typed_argv_exec exists to +// bound. A note claiming the second as protocol-mandated would be overstating an extdeps authority, +// which is the defect this revision fixes.\n\nCorrection receipt: before this PR the note read +// `ExecArgv splices a List remote argv without embedding a shell command string`, which is +// false for the remote half and contradicted the note at the enforcing edge, which said the +// opposite in the same corpus. Nothing depended on the false half - the allowlist was already +// fail-closed - so the defect was that the corpus asserted both readings and a reader could reach +// either. fn shape_scp_copy_to_argv( host: NonEmptyStr, diff --git a/dag/extdeps/standards/ieee_754_2019.dag b/dag/extdeps/standards/ieee_754_2019.dag index 23e2f42b40f..3b145cb0440 100644 --- a/dag/extdeps/standards/ieee_754_2019.dag +++ b/dag/extdeps/standards/ieee_754_2019.dag @@ -16,6 +16,10 @@ type Ieee754Standard data ieee_754_2019: Ieee754Standard = Ieee7542019 {} +// Cited upstream: edition-specific anchor above (IEEE Std 754-2019 document id 6210). This module +// owns standard-governed interchange binary format and default rounding facts only — not gunbc +// execution receipts. + data extdeps_model_scope: ExternalModelScope = ExternalModelScope { subject: ExternalSubjectRef { declaration: DeclarationRef { @@ -28,10 +32,12 @@ data extdeps_model_scope: ExternalModelScope = ExternalModelScope { further_citations: [] } -data ieee_754_2019_note: String = "Cited upstream: edition-specific anchor above (IEEE Std 754-2019 document id 6210). This module owns standard-governed interchange binary format and default rounding facts only — not gunbc execution receipts." - -data ieee_754_binary64_interchange_note: String = "IEEE 754 binary64 interchange format: 64-bit double-precision radix-2 floating-point as cited at the anchor." +// IEEE 754 binary64 interchange format: 64-bit double-precision radix-2 floating-point as cited at +// the anchor. -data ieee_754_round_ties_to_even_note: String = "Default rounding attribute roundTiesToEven (round to nearest, ties to even) for binary floating-point arithmetic as cited at the anchor." +// Default rounding attribute roundTiesToEven (round to nearest, ties to even) for binary +// floating-point arithmetic as cited at the anchor. -data ieee_754_2008_2019_binary_add_equivalence_note: String = "For binary64 roundTiesToEven interchange addition, triple (1e16, 1.0, 1.0) yields the same left fold 1e16 and right fold 1e16+2 under both the 2008 and 2019 revisions at this locator family — interchange binary addition semantics did not change between them." +// For binary64 roundTiesToEven interchange addition, triple (1e16, 1.0, 1.0) yields the same left +// fold 1e16 and right fold 1e16+2 under both the 2008 and 2019 revisions at this locator family — +// interchange binary addition semantics did not change between them. diff --git a/dag/extdeps/standards/web_annotation.dag b/dag/extdeps/standards/web_annotation.dag index 079e52d9668..26b5bb49b3c 100644 --- a/dag/extdeps/standards/web_annotation.dag +++ b/dag/extdeps/standards/web_annotation.dag @@ -14,7 +14,12 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { data web_annotation_authority: ExternalAuthority = extdeps_external_authority_anchor -data web_annotation_selector_grounding_note: String = "Cited upstream: W3C Web Annotation Data Model (retrieved 2026-08-04). std.citation FragmentSelector, TextQuoteSelector, and TextPositionSelector ground on this specification's selector vocabulary (FragmentSelector with conformsTo, TextQuoteSelector, TextPositionSelector). TRAP: the Recommendation's example table still points PDF fragment selectors at RFC 3778; application/pdf fragments cite extdeps.standards.rfc_8118 instead, with explicit supersession evidence — do not copy the stale table." +// Cited upstream: W3C Web Annotation Data Model (retrieved 2026-08-04). std.citation +// FragmentSelector, TextQuoteSelector, and TextPositionSelector ground on this specification's +// selector vocabulary (FragmentSelector with conformsTo, TextQuoteSelector, TextPositionSelector). +// TRAP: the Recommendation's example table still points PDF fragment selectors at RFC 3778; +// application/pdf fragments cite extdeps.standards.rfc_8118 instead, with explicit supersession +// evidence — do not copy the stale table. data web_annotation_data_model: NonEmptyStr = "Web Annotation Data Model" diff --git a/dag/extdeps/storage/amphenol_mdt420m01001.dag b/dag/extdeps/storage/amphenol_mdt420m01001.dag index 85ba677c969..f94dff0c2b5 100644 --- a/dag/extdeps/storage/amphenol_mdt420m01001.dag +++ b/dag/extdeps/storage/amphenol_mdt420m01001.dag @@ -16,7 +16,18 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data mdt420m01001_note: String = "THE CONTACT COUNT IS DELIBERATELY ABSENT AND IS AN OPEN QUESTION. Ampere's Mt. Jade reference describes a 75-pin M.2 NGFF connector; distributor listings for this part number describe 67 contacts. Those are very likely one connector counted two ways — M.2 numbers pin positions to 75 while the connector carries 67 physical contacts, the difference being the key — but a contact population is load-bearing, and reconciling it by reasoning is exactly how a plausible wrong number enters a repository. Neither number is recorded until a drawing states one. The sibling row extdeps.storage.te_m2_ngff_connector carries 67 positions from a specification that was read, so a disagreement between the two would be a real finding rather than a rounding. Distributor listings also describe key M, 0.50 mm pitch and 4.2 mm height; none of that is recorded, for the same reason. WHY THIS ROW IS NOT A SELECTION: that Mt. Jade names this connector is a reference-design precedent, which is a relation between a part and a particular board and therefore a product-layer fact, not a property of the part." +// THE CONTACT COUNT IS DELIBERATELY ABSENT AND IS AN OPEN QUESTION. Ampere's Mt. Jade reference +// describes a 75-pin M.2 NGFF connector; distributor listings for this part number describe 67 +// contacts. Those are very likely one connector counted two ways — M.2 numbers pin positions to 75 +// while the connector carries 67 physical contacts, the difference being the key — but a contact +// population is load-bearing, and reconciling it by reasoning is exactly how a plausible wrong +// number enters a repository. Neither number is recorded until a drawing states one. The sibling +// row extdeps.storage.te_m2_ngff_connector carries 67 positions from a specification that was read, +// so a disagreement between the two would be a real finding rather than a rounding. Distributor +// listings also describe key M, 0.50 mm pitch and 4.2 mm height; none of that is recorded, for the +// same reason. WHY THIS ROW IS NOT A SELECTION: that Mt. Jade names this connector is a +// reference-design precedent, which is a relation between a part and a particular board and +// therefore a product-layer fact, not a property of the part. data mdt420m01001: CatalogPartIdentity = CatalogPartIdentity { ordering_part_number: "MDT420M01001", diff --git a/dag/extdeps/storage/te_m2_ngff_connector.dag b/dag/extdeps/storage/te_m2_ngff_connector.dag index cc405ec57c9..73ac298cc08 100644 --- a/dag/extdeps/storage/te_m2_ngff_connector.dag +++ b/dag/extdeps/storage/te_m2_ngff_connector.dag @@ -26,7 +26,19 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data te_m2_ngff_note: String = "THIS ROW IS A CONNECTOR FAMILY, NOT AN ORDERING SELECTION, AND THE DIFFERENCE IS WHAT THE READ DOCUMENT SUPPORTS. The specification establishes 67 positions on 0.50 mm centres, surface-mount solder tails onto pads, and the stack-height and mid-plane variants it governs. It does NOT say which ordering part number is M-keyed: the key is a property of the individual part, carried on the per-part customer drawing, and this row carries no such drawing. SO THERE IS NO KEY FIELD HERE. An M2Key field would have had to be filled, and filling it from the design intent rather than from a document is precisely how an intent becomes indistinguishable from a fact. The M-key decision is real and was made at the product layer; what is missing is the part number that satisfies it. THE BOARD PATTERN IS OUTSTANDING, AND WHAT IS RECORDED ABOUT IT IS A FACT ABOUT THE DOCUMENT RATHER THAN ABOUT ANY READER: the specification publishes a recommended PCB layout as Figures 2 and 3, and defers to the per-part customer drawing where the two conflict, so the pattern exists and is published and is simply not transcribed here. Which fetches on which day did or did not yield text is an observation this repository made about its own tooling, and it belongs in the observing layer downstream rather than inside an upstream module." +// THIS ROW IS A CONNECTOR FAMILY, NOT AN ORDERING SELECTION, AND THE DIFFERENCE IS WHAT THE READ +// DOCUMENT SUPPORTS. The specification establishes 67 positions on 0.50 mm centres, surface-mount +// solder tails onto pads, and the stack-height and mid-plane variants it governs. It does NOT say +// which ordering part number is M-keyed: the key is a property of the individual part, carried on +// the per-part customer drawing, which this row does not carry. SO THERE IS NO KEY FIELD HERE: an +// M2Key field filled from design intent rather than a document is how an intent becomes +// indistinguishable from a fact. The M-key decision is real and was made at the product layer; +// missing is the part number that satisfies it. THE BOARD PATTERN IS OUTSTANDING, AND WHAT IS +// RECORDED ABOUT IT IS A FACT ABOUT THE DOCUMENT, NOT ANY READER: the specification publishes a +// recommended PCB layout as Figures 2 and 3, deferring to the per-part customer drawing where they +// conflict, so the pattern exists and is published and is simply not transcribed here. Which +// fetches on which day yielded text is an observation about this repository's own tooling and +// belongs in the observing layer downstream, not inside an upstream module. type M2ConnectorCatalogRow { specification_number: NonEmptyStr diff --git a/dag/extdeps/sudo/nopasswd_execute_probe.dag b/dag/extdeps/sudo/nopasswd_execute_probe.dag index ecc6890e289..83f8a94a661 100644 --- a/dag/extdeps/sudo/nopasswd_execute_probe.dag +++ b/dag/extdeps/sudo/nopasswd_execute_probe.dag @@ -11,9 +11,20 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data sudo_nopasswd_execute_probe_note: String = "Non-interactive sudo execute probe: sudo -n . Interface shape only — typed argv transport lives in extdeps.sudo.nopasswd_execute_probe_check_op at the realization edge. check_argv is one argv token for command_path (not a shell fragment): fleet roster rows use single-token probes only (--version, --help, -V, version, probe). Multi-token probes require a List shape change, not space-embedding in NonEmptyStr — emit concat and typed argv agree only when check_argv is single-token." +// Non-interactive sudo execute probe: sudo -n . Interface shape only — +// typed argv transport lives in extdeps.sudo.nopasswd_execute_probe_check_op at the realization +// edge. check_argv is one argv token for command_path (not a shell fragment): fleet roster rows use +// single-token probes only (--version, --help, -V, version, probe). Multi-token probes require a +// List shape change, not space-embedding in NonEmptyStr — emit concat and typed argv agree +// only when check_argv is single-token. -data sudo_nopasswd_grant_list_probe_note: String = "Non-interactive sudoers grant probe: read sudo -n -l (no target binary execution), then require an exact NOPASSWD row for command_path. Used when the binary may be absent before package ensure — exec failure would mis-diagnose absence as a grant miss (review 41039). Exit zero from the list query is not authorization evidence by itself because sudoers listpw may admit listing through a different NOPASSWD entry; extdeps.sudo.nopasswd_execute_probe_check_op owns both the typed read and exact-row parser. Its one-field probe shape is the typed command path itself; the parser, host effect, and callers consume that single authority directly." +// Non-interactive sudoers grant probe: read sudo -n -l (no target binary execution), then require +// an exact NOPASSWD row for command_path. Used when the binary may be absent before package ensure +// — exec failure would mis-diagnose absence as a grant miss (review 41039). Exit zero from the list +// query is not authorization evidence by itself because sudoers listpw may admit listing through a +// different NOPASSWD entry; extdeps.sudo.nopasswd_execute_probe_check_op owns both the typed read +// and exact-row parser. Its one-field probe shape is the typed command path itself; the parser, +// host effect, and callers consume that single authority directly. type SudoNopasswdExecuteProbeShape { command_path: NonEmptyStr diff --git a/dag/extdeps/sudo/nopasswd_execute_probe_check_op.dag b/dag/extdeps/sudo/nopasswd_execute_probe_check_op.dag index c67b8f7c156..86207443ee2 100644 --- a/dag/extdeps/sudo/nopasswd_execute_probe_check_op.dag +++ b/dag/extdeps/sudo/nopasswd_execute_probe_check_op.dag @@ -16,7 +16,11 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data sudo_nopasswd_execute_probe_check_op_note: String = "Typed argv transport for sudo.NopasswdExecuteProbe.Check and sudo.NopasswdGrantList.Read — program+args dispatch at the realization edge without shell.Exec.Run (Wave C5 / bash-emit #5828 slice). Grant-list authorization is not inferred from exit zero: sudoers listpw may permit a noninteractive list query because some other entry is NOPASSWD. The typed observation must also contain an exact NOPASSWD row for the requested command path." +// Typed argv transport for sudo.NopasswdExecuteProbe.Check and sudo.NopasswdGrantList.Read — +// program+args dispatch at the realization edge without shell.Exec.Run (Wave C5 / bash-emit #5828 +// slice). Grant-list authorization is not inferred from exit zero: sudoers listpw may permit a +// noninteractive list query because some other entry is NOPASSWD. The typed observation must also +// contain an exact NOPASSWD row for the requested command path. fn sudo_nopasswd_execute_probe_argv(probe: SudoNopasswdExecuteProbeShape) -> List { ["sudo", "-n", probe.command_path as String, probe.check_argv as String] diff --git a/dag/extdeps/systemd/loginctl.dag b/dag/extdeps/systemd/loginctl.dag index 9702d95047a..c9d200077e1 100644 --- a/dag/extdeps/systemd/loginctl.dag +++ b/dag/extdeps/systemd/loginctl.dag @@ -38,11 +38,10 @@ fn loginctl_binary_path() -> NonEmptyStr { } // enable-linger TAKES ITS SUBJECT, and the argument is not optional in any use this repository has. -// Upstream defaults the subject to the calling user when it is omitted, which is exactly why the -// omission is dangerous here rather than convenient: a sudoers grant written without the subject -// authorizes `loginctl enable-linger` for the grantee to run as root, and root's own linger is not -// what the grant was for. The parameter is required so that the subject cannot be dropped by -// forgetting it. +// Upstream defaults the subject to the calling user when omitted, which is exactly why omission is +// dangerous here: a sudoers grant written without the subject authorizes `loginctl enable-linger` +// for the grantee to run as root, and root's own linger is not what the grant was for. The +// parameter is required so the subject cannot be dropped by forgetting it. data loginctl_enable_linger_verb: NonEmptyStr = "enable-linger" as NonEmptyStr fn loginctl_enable_linger_argv(subject: NonEmptyStr) -> List { diff --git a/dag/extdeps/tailscale/serve.dag b/dag/extdeps/tailscale/serve.dag index 925fe470531..0efd41775da 100644 --- a/dag/extdeps/tailscale/serve.dag +++ b/dag/extdeps/tailscale/serve.dag @@ -132,7 +132,20 @@ fn tailscale_status_web_proxies(web: JsonValue, mount: TailscaleServeMount) -> L } } -data serve_status_json_projection_note: String = "Tailscale documents `serve status --json` as the machine-readable status surface. For the legacy node Serve configuration used by srv1, Web..Handlers..Proxy names the currently routed backend. DNS is intentionally not treated as identity here: it is a map key derived by tailscaled and the owned endpoint is listener x mount. Duplicate or wrong-shaped members yield no proxy.\n\nThat sentence used to end 'which the blue/green selector refuses before mutation; they never select a plausible slot', citing srv1_blue_green_slot_selection. That selector was deleted with the blue/green pair on 2026-08-19, so the citation named a symbol that no longer resolves -- the DESIGN section 3 stale-citation class, in an extdeps module. The projection's own behaviour is unchanged: a wrong-shaped member still yields no proxy. What is gone is the consumer that acted on it, and this module states the upstream fact rather than who reads it. tailscale_serve_status_proxy_targets and tailscale_serve_status_json_argv now have no production consumer at all and are held open only by test.claim.tailscale_serve_status." +// Tailscale documents `serve status --json` as the machine-readable status surface. For the legacy +// node Serve configuration used by srv1, Web..Handlers..Proxy names the currently +// routed backend. DNS is intentionally not treated as identity here: it is a map key derived by +// tailscaled and the owned endpoint is listener x mount. Duplicate or wrong-shaped members yield no +// proxy. +// +// That sentence used to end 'which the blue/green selector refuses before mutation; they never +// select a plausible slot', citing srv1_blue_green_slot_selection. That selector was deleted with +// the blue/green pair on 2026-08-19, so the citation named a symbol that no longer resolves -- the +// DESIGN section 3 stale-citation class, in an extdeps module. The projection's own behaviour is +// unchanged: a wrong-shaped member still yields no proxy. What is gone is the consumer that acted +// on it, and this module states the upstream fact rather than who reads it. +// tailscale_serve_status_proxy_targets and tailscale_serve_status_json_argv now have no production +// consumer at all and are held open only by test.claim.tailscale_serve_status. // AN UNREADABLE STATUS IS NOT AN EMPTY ONE. This projection previously answered [] when // parse_json refused the document, which is the empty-observation narrow DESIGN names: "I could not @@ -165,7 +178,17 @@ fn tailscale_serve_status_proxy_targets(status_json: String, mount: TailscaleSer } } -data serve_scoped_removal_capability_note: String = "SCOPED REMOVAL IS A VERSION-OBSERVED CAPABILITY, NOT A CONSTANT — and the constant it replaces was wrong. This module's consumer previously asserted that tailscale serve has no per-port removal and only a node-global reset, built a refusal arm on that premise, and cited nothing. The upstream documents scoped removal via `off`, and separately notes that the Serve CLI CHANGED in client 1.52 (review, 2026-07-30; tailscale.com/kb/1242).\\n\\nSo the capability is a fact about the client installed on a particular host at a particular time, which is why it is a function of an observed version rather than a row. The verdict is three-valued because an unobserved version is neither capable nor incapable, and collapsing that to either pole is what produced the false claim above. A consumer must refuse BEFORE mutating rather than after, because the failure this guards is a teardown that silently removes nothing and reports success.\\n\\nThe boundary is stated as the version the upstream itself names, not as a version this repo picked." +// SCOPED REMOVAL IS A VERSION-OBSERVED CAPABILITY, NOT A CONSTANT — and the constant it replaces +// was wrong. This module's consumer previously asserted that tailscale serve has no per-port +// removal and only a node-global reset, built a refusal arm on that premise, and cited nothing. The +// upstream documents scoped removal via `off`, and separately notes that the Serve CLI CHANGED in +// client 1.52 (review, 2026-07-30; tailscale.com/kb/1242).\n\nSo the capability is a fact about the +// client installed on a particular host at a particular time, which is why it is a function of an +// observed version rather than a row. The verdict is three-valued because an unobserved version is +// neither capable nor incapable, and collapsing that to either pole is what produced the false +// claim above. A consumer must refuse BEFORE mutating rather than after, because the failure this +// guards is a teardown that silently removes nothing and reports success.\n\nThe boundary is stated +// as the version the upstream itself names, not as a version this repo picked. data tailscale_scoped_serve_off_minimum: SemVerVersion = SemVerVersion { major: 1, diff --git a/dag/extdeps/tmux/tmux.dag b/dag/extdeps/tmux/tmux.dag index 8ec7f6f807a..73250a299d4 100644 --- a/dag/extdeps/tmux/tmux.dag +++ b/dag/extdeps/tmux/tmux.dag @@ -21,7 +21,19 @@ data tmux_client_connect_error_authority: ExternalAuthority = ExternalAuthority } } -data tmux_session_list_outcome_note: String = "THE ONE CLASSIFIER for tmux list-sessions (`tmux ls`) and any other tmux client command that shares client_connect's stderr vocabulary. Authority: tmux_client_connect_error_authority (upstream client.c client_connect); tmux.Session.List exit contract (`0 => Unit`, `1 => Unit \"No sessions\"`). On exit success the stdout grammar is parsed into TmuxLsEntry rows (`session_name: windows ...` per line) by parse_tmux_ls_output, which is total and fail-closed: the first malformed non-empty line refuses the whole observation with its 1-based line and raw evidence as TmuxListStdoutRefused — a truncated or corrupted successful response must never widen to an empty or partial session set. On exit failure only three documented no-server spellings classify TmuxNoServer: stderr containing `no server running`, `no sessions`, or the conjunctive ENOENT spelling (`error connecting to ` AND `(No such file or directory)`). `(Connection refused)` is deliberately distinct from ENOENT and classifies TmuxListUnknowable, as do permission errors and every other nonzero exit. TmuxListUnknowable and TmuxListStdoutRefused must never widen to an empty session set." +// THE ONE CLASSIFIER for tmux list-sessions (`tmux ls`) and any other tmux client command that +// shares client_connect's stderr vocabulary. Authority: tmux_client_connect_error_authority +// (upstream client.c client_connect); tmux.Session.List exit contract (`0 => Unit`, `1 => Unit "No +// sessions"`). On exit success the stdout grammar is parsed into TmuxLsEntry rows (`session_name: +// windows ...` per line) by parse_tmux_ls_output, which is total and fail-closed: the +// first malformed non-empty line refuses the whole observation with its 1-based line and raw +// evidence as TmuxListStdoutRefused — a truncated or corrupted successful response must never widen +// to an empty or partial session set. On exit failure only three documented no-server spellings +// classify TmuxNoServer: stderr containing `no server running`, `no sessions`, or the conjunctive +// ENOENT spelling (`error connecting to ` AND `(No such file or directory)`). `(Connection +// refused)` is deliberately distinct from ENOENT and classifies TmuxListUnknowable, as do +// permission errors and every other nonzero exit. TmuxListUnknowable and TmuxListStdoutRefused must +// never widen to an empty session set. type TmuxLsEntry { session_name: String @@ -376,7 +388,15 @@ type TmuxPipeCommand { body: NonEmptyStr } -data tmux_staged_provider_start_note: String = "Provider attempts use a staged pane rather than asking tmux to start the provider in the same call that creates the session: create an idle shell pane; select its exact default shell; enable remain-on-exit; attach the event pipe; then respawn the pane with typed provider argv. That ordering makes event capture and terminal exit observation active before the provider can emit or exit. tmux pipe-pane is itself a foreign executor whose API requires one shell-command string, so TmuxPipeCommand is an explicit carrier for that bounded residue. Callers must construct dynamic argv tokens with the shared POSIX shell_quote authority; tmux set-environment is not a transport for pipe commands (live readback showed pipe processes do not inherit session environment updates)." +// Provider attempts use a staged pane rather than asking tmux to start the provider in the same +// call that creates the session: create an idle shell pane; select its exact default shell; enable +// remain-on-exit; attach the event pipe; then respawn the pane with typed provider argv. That +// ordering makes event capture and terminal exit observation active before the provider can emit or +// exit. tmux pipe-pane is itself a foreign executor whose API requires one shell-command string, so +// TmuxPipeCommand is an explicit carrier for that bounded residue. Callers must construct dynamic +// argv tokens with the shared POSIX shell_quote authority; tmux set-environment is not a transport +// for pipe commands (live readback showed pipe processes do not inherit session environment +// updates). fn shape_tmux_new_idle_session_argv_for( scope: TmuxServerScope, @@ -435,7 +455,21 @@ fn shape_tmux_respawn_pane_argv_for( ) } -data tmux_capture_fidelity_note: String = "WHETHER A CAPTURE CARRIES ESCAPES IS A tmux FACT, and it is a fact about capture-pane specifically rather than about whatever program happened to write into the pane. tmux keeps a RENDERED grid: the pane's contents are the characters currently displayed, with the control sequences that positioned and coloured them already consumed by the renderer. So `capture-pane -p` emits plain text, and `-e` re-serializes escape sequences for the attributes it stored. Cited against tmux's own manual, which documents -e as 'the output includes escape sequences for text and background attributes' — the flag ADDS them back; their absence is the default, not an accident.\\n\\nThe distinction is typed rather than left as a bare flag because the two outputs are different kinds of thing and only one of them is safe to parse. A caller that wants values out of a pane wants CapturePlainText; a caller reproducing a terminal's appearance wants CaptureWithEscapes; and a Bool named include_escapes at the call site would let the wrong one through with no signal. Verified on srv2: a plain capture of a colour-emitting CLI contains zero escape bytes under cat -v.\\n\\nThis module says nothing about which program is being captured. That is the point — the fidelity is tmux's, the format inside the pane belongs to whoever wrote it, and a consumer composes the two." +// WHETHER A CAPTURE CARRIES ESCAPES IS A tmux FACT, and it is a fact about capture-pane +// specifically rather than about whatever program happened to write into the pane. tmux keeps a +// RENDERED grid: the pane's contents are the characters currently displayed, with the control +// sequences that positioned and coloured them already consumed by the renderer. So `capture-pane +// -p` emits plain text, and `-e` re-serializes escape sequences for the attributes it stored. Cited +// against tmux's own manual, which documents -e as 'the output includes escape sequences for text +// and background attributes' — the flag ADDS them back; their absence is the default, not an +// accident.\n\nThe distinction is typed rather than left as a bare flag because the two outputs are +// different kinds of thing and only one of them is safe to parse. A caller that wants values out of +// a pane wants CapturePlainText; a caller reproducing a terminal's appearance wants +// CaptureWithEscapes; and a Bool named include_escapes at the call site would let the wrong one +// through with no signal. Verified on srv2: a plain capture of a colour-emitting CLI contains zero +// escape bytes under cat -v.\n\nThis module says nothing about which program is being captured. +// That is the point — the fidelity is tmux's, the format inside the pane belongs to whoever wrote +// it, and a consumer composes the two. type TmuxCaptureFidelity = CapturePlainText @@ -462,7 +496,21 @@ fn shape_tmux_capture_pane_argv_for( ) } -data tmux_capture_rendering_note: String = "THE PRODUCER SIDE OF THE SEAM, without which the brand was a claim nothing could satisfy (review, 2026-07-30). std.types.RenderedTerminalText says a capture surface PRODUCES it and a parser CONSUMES it, and the composition is admissible because the types line up — but the argv shaper above returns argv, so nothing in the tree inhabited the type. A consumer with no producer is a described surface never checked against the surface; the types could not line up because only one side existed.\\n\\nThis is the fn that makes the claim true, and it is a tmux fact rather than a caller's convenience: the fidelity that was REQUESTED determines whether the bytes that came back are rendered text. A plain capture inhabits the brand because tmux's renderer already consumed the control sequences; a -e capture re-serialized them, so it does not, and it says so with a typed arm rather than branding the bytes anyway.\\n\\nThat is the whole construction. A caller cannot reach RenderedTerminalText from an escape-carrying capture at all, so the failure the brand exists to prevent — a one-time code extracted with escape bytes still inside it, pasted, and rejected at the far end with no sign the dashboard mangled it — is unwritable rather than merely discouraged. The captured bytes are taken as a parameter because performing the capture is a transport question this module does not answer." +// THE PRODUCER SIDE OF THE SEAM, without which the brand was a claim nothing could satisfy (review, +// 2026-07-30). std.types.RenderedTerminalText says a capture surface PRODUCES it and a parser +// CONSUMES it, and the composition is admissible because the types line up — but the argv shaper +// above returns argv, so nothing in the tree inhabited the type. A consumer with no producer is a +// described surface never checked against the surface; the types could not line up because only one +// side existed.\n\nThis is the fn that makes the claim true, and it is a tmux fact rather than a +// caller's convenience: the fidelity that was REQUESTED determines whether the bytes that came back +// are rendered text. A plain capture inhabits the brand because tmux's renderer already consumed +// the control sequences; a -e capture re-serialized them, so it does not, and it says so with a +// typed arm rather than branding the bytes anyway.\n\nThat is the whole construction. A caller +// cannot reach RenderedTerminalText from an escape-carrying capture at all, so the failure the +// brand exists to prevent — a one-time code extracted with escape bytes still inside it, pasted, +// and rejected at the far end with no sign the dashboard mangled it — is unwritable rather than +// merely discouraged. The captured bytes are taken as a parameter because performing the capture is +// a transport question this module does not answer. type TmuxCaptureRendering = TmuxPaneRendered { text: RenderedTerminalText } @@ -511,7 +559,13 @@ fn shape_tmux_has_session_argv(session_name: String) -> List { ) } -data tmux_list_panes_format_note: String = "The pane-fingerprint read (upstream: tmux FORMATS, man tmux — session_name and pane_current_command are cited format variables; pane_current_command is the name of the process running IN the pane, which is exactly the ownership evidence the dispatch lease classification needs). -a lists panes across all sessions in one read. Single-space separator: tmux session names in this system are single tokens (gunbc-dispatch-) and pane_current_command is a process name — the parse splits on the FIRST space, so even a hypothetical spaced command survives as the fingerprint's head token, which is the token the classification compares." +// The pane-fingerprint read (upstream: tmux FORMATS, man tmux — session_name and +// pane_current_command are cited format variables; pane_current_command is the name of the process +// running IN the pane, which is exactly the ownership evidence the dispatch lease classification +// needs). -a lists panes across all sessions in one read. Single-space separator: tmux session +// names in this system are single tokens (gunbc-dispatch-) and pane_current_command is a +// process name — the parse splits on the FIRST space, so even a hypothetical spaced command +// survives as the fingerprint's head token, which is the token the classification compares. data tmux_list_panes_format: String = "#\{session_name\} #\{pane_current_command\}" diff --git a/dag/extdeps/toolchain/icestorm/subject.dag b/dag/extdeps/toolchain/icestorm/subject.dag index 72b1f170bd5..48215339ad5 100644 --- a/dag/extdeps/toolchain/icestorm/subject.dag +++ b/dag/extdeps/toolchain/icestorm/subject.dag @@ -13,7 +13,20 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data icestorm_subject_note: String = "PROJECT ICESTORM, AND WHY IT IS A SEPARATE AUTHORITY FROM LATTICE. IceStorm is an independently governed open-source project (ISC licensed, per its own copyright file, read from the installed package 2026-08-13) that documented the iCE40 bitstream format and pin placement by reverse engineering rather than by vendor specification. It is what nextpnr actually consults to decide whether a pin constraint is legal, so it is the authority a generated constraint file must satisfy — and it is NOT the vendor's pin assignment table. Modelling one as the other would fuse a specification with a reverse-engineering of it, which is the browser-engine-versus-standard split this repository already draws elsewhere. THE PIN POPULATION BELOW is the package position roster IceStorm carries for the HX1K in TQ144: which numbered package pins are usable I/O. It carries the DESIGNATORS only. The tile coordinates beside them in IceStorm's database are the toolchain's placement internals, not facts a board consults, so they are deliberately not copied — a board needs to know that pin 47 exists and is an I/O, not which tile it lands on. THE CROSS-CHECK THAT MAKES THIS MORE THAN A COPIED LIST: Lattice's own per-bank table for this device and package sums to ninety-two general-purpose plus four configuration pins, and this roster has ninety-six entries. Two authorities produced independently, agreeing at a count. A witness executes that agreement rather than a reader noticing it." +// PROJECT ICESTORM, AND WHY IT IS A SEPARATE AUTHORITY FROM LATTICE. IceStorm is an independently +// governed open-source project (ISC licensed per its copyright file, read from the installed +// package 2026-08-13) that documented the iCE40 bitstream format and pin placement by reverse +// engineering, not vendor specification. nextpnr consults it to decide whether a pin constraint is +// legal, so it is the authority a generated constraint file must satisfy — NOT the vendor's pin +// assignment table. Fusing the two would fuse a specification with its reverse-engineering, the +// browser-engine-versus-standard split drawn elsewhere. THE PIN POPULATION BELOW is IceStorm's +// package position roster for the HX1K in TQ144: which numbered pins are usable I/O, DESIGNATORS +// only. The tile coordinates beside them in IceStorm's database are placement internals, not facts +// a board consults, so they are not copied — a board needs to know pin 47 exists and is an I/O, not +// which tile it lands on. THE CROSS-CHECK THAT MAKES THIS MORE THAN A COPIED LIST: Lattice's own +// per-bank table for this device and package sums to ninety-two general-purpose plus four +// configuration pins; this roster has ninety-six entries. Two independent authorities agree at a +// count, and a witness executes that agreement rather than a reader noticing it. type IcestormPackagePinRoster { device: NonEmptyStr @@ -29,11 +42,10 @@ fn roster_admits_pin(r: IcestormPackagePinRoster, pin: NonEmptyStr) -> Bool { fold(r.pins, init: false, f: fn(acc, p) { acc || (p as String) == (pin as String) }) } -// A DESIGNATOR APPEARING TWICE WOULD MAKE THE ROSTER'S COUNT A LIE, and the count is one half of -// the two-authority agreement this package rests on: Lattice's per-bank table says 96 and the -// roster says 96, which is evidence only if the roster's 96 are 96 DISTINCT positions. A duplicate -// would hold the count while shrinking the real population, so the agreement would still read -// green against a roster that had quietly lost a pin. +// A DESIGNATOR APPEARING TWICE WOULD MAKE THE ROSTER'S COUNT A LIE, and the count is half of the +// two-authority agreement: Lattice's per-bank table says 96 and the roster says 96, evidence only +// if those are 96 DISTINCT positions. A duplicate holds the count while shrinking the population, +// so the agreement would read green against a roster that quietly lost a pin. fn roster_pin_occurrences(r: IcestormPackagePinRoster, pin: NonEmptyStr) -> Nat { fold(r.pins, init: 0, f: fn(acc, p) { if (p as String) == (pin as String) { acc + 1 } else { acc } }) } diff --git a/dag/extdeps/tools.dag b/dag/extdeps/tools.dag index fb4d014f62d..a4da19909d5 100644 --- a/dag/extdeps/tools.dag +++ b/dag/extdeps/tools.dag @@ -64,7 +64,22 @@ fn install_hint(sources: List) -> String { sources |> map(s => one_hint(source: s)) |> join(separator: " | ") } -data resolve_posix_command_v_migration_note: String = "Migrated off shell.Which.Check to shell.PosixCommandV.Check (crisp-wren-896, PR #8590): no fleet host's OperatingSystemSurface carries a package-set authority, our own CI/falsifier runner included, so `which`'s external-binary presence was never an established fact here either, and command -v's weaker, dominant POSIX-shell assumption holds everywhere `which` did. UNLIKE the other four migrated call sites, this function reads .path as a resolved filesystem path rather than keying on .exists alone, so TWO hazards genuinely apply here rather than being ruled out: (1) command -v prints the bare NAME rather than a resolved path when the probed command is a shell builtin or alias, not an external binary -- every tool.name this resolves is expected to be an external binary (coreutils, rustup, apt, brew, or a GitHub-release install), so this is not believed to bite in practice, but it is not structurally excluded the way it is for the four .exists-only sites; (2) under WET execution PosixCommandV.Check.path comes back as a bare stdout String rather than a real Optional, so the Present/Absent match below relies on the same Value::Null / Optional realization straddle (DESIGN open thread) that gunbc.host_cli_dependency's which_path_realization_limit_note and gunbc.roadmap_belt_actuate's belt_program_available_note document -- this function has no current live caller in the tree (verified by grep, PR #8590), so the straddle is undischarged debt rather than an observed WET failure." +// Migrated off shell.Which.Check to shell.PosixCommandV.Check (crisp-wren-896, PR #8590): no fleet +// host's OperatingSystemSurface carries a package-set authority, our own CI/falsifier runner +// included, so `which`'s external-binary presence was never an established fact here either, and +// command -v's weaker, dominant POSIX-shell assumption holds everywhere `which` did. UNLIKE the +// other four migrated call sites, this function reads .path as a resolved filesystem path rather +// than keying on .exists alone, so TWO hazards genuinely apply here rather than being ruled out: +// (1) command -v prints the bare NAME rather than a resolved path when the probed command is a +// shell builtin or alias, not an external binary -- every tool.name this resolves is expected to be +// an external binary (coreutils, rustup, apt, brew, or a GitHub-release install), so this is not +// believed to bite in practice, but it is not structurally excluded the way it is for the four +// .exists-only sites; (2) under WET execution PosixCommandV.Check.path comes back as a bare stdout +// String rather than a real Optional, so the Present/Absent match below relies on the same +// Value::Null / Optional realization straddle (DESIGN open thread) that gunbc.host_cli_dependency's +// which_path_realization_limit_note and gunbc.roadmap_belt_actuate's belt_program_available_note +// document -- this function has no current live caller in the tree (verified by grep, PR #8590), so +// the straddle is undischarged debt rather than an observed WET failure. func resolve(tool: CliTool) -> ResolveResult { let check = shell.PosixCommandV.Check(command: tool.name) diff --git a/dag/extdeps/tools/diffutils.dag b/dag/extdeps/tools/diffutils.dag index b98ddf2b8e5..6d83ed26e0b 100644 --- a/dag/extdeps/tools/diffutils.dag +++ b/dag/extdeps/tools/diffutils.dag @@ -11,7 +11,10 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data diffutils_module_scope_doc: String = "GNU diffutils interface shapes, one file per upstream (DESIGN 3: cite the real upstream - diff is diffutils, not coreutils or the shell). Homed in its own module rather than extdeps.shell so that consumers of the diff operation do not couple to the whole shell-utilities surface, and a change here does not put every extdeps.shell importer in the affected set." +// GNU diffutils interface shapes, one file per upstream (DESIGN 3: cite the real upstream - diff is +// diffutils, not coreutils or the shell). Homed in its own module rather than extdeps.shell so that +// consumers of the diff operation do not couple to the whole shell-utilities surface, and a change +// here does not put every extdeps.shell importer in the affected set. service shell.Diff { operation Recursive { diff --git a/dag/extdeps/tools/jq.dag b/dag/extdeps/tools/jq.dag index 778b9f1dd74..eedeab833b5 100644 --- a/dag/extdeps/tools/jq.dag +++ b/dag/extdeps/tools/jq.dag @@ -35,7 +35,11 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { data jq_authority: ExternalAuthority = extdeps_external_authority_anchor -data jq_authority_anchor_scaffold_note: String = "SCAFFOLD (mandatory-tag corpus cadence) - the host scanner reads an aliased anchor as absent, so the literal anchor grounds its projection while jq_authority remains the semantic authority name. This copy dissolves with the host seam under the corpus_scan_scaffold_note in v2.lens.mandatory_tag.corpus_scan when native witness realization runs the parse-grain corpus witness." +// SCAFFOLD (mandatory-tag corpus cadence) - the host scanner reads an aliased anchor as absent, so +// the literal anchor grounds its projection while jq_authority remains the semantic authority name. +// This copy dissolves with the host seam under the corpus_scan_scaffold_note in +// v2.lens.mandatory_tag.corpus_scan when native witness realization runs the parse-grain corpus +// witness. data jq_cli_tool: CliTool = CliTool { name: "jq", @@ -450,7 +454,6 @@ fn jq_lowering_refused_diagnostic(cause: JqLoweringRefusal) -> Diagnostic { } } - // EXECUTION. The one function a domain module calls to actually run jq. // // It takes the SEALED plan, so the arguments reaching the process are the ones the row-driven diff --git a/dag/extdeps/tools/node.dag b/dag/extdeps/tools/node.dag index e5e0eb78fc2..ea568c58674 100644 --- a/dag/extdeps/tools/node.dag +++ b/dag/extdeps/tools/node.dag @@ -20,7 +20,10 @@ data node_cli_tool: CliTool = CliTool { installable_via: [SourceApt { package: package_nodejs }], } -data node_cli_version_observation_note: String = "Observed node --version is recorded on CodexBuildToolchainReceipt beside npm.Cli.Version so the ambient toolchain that performed offline ci is attributable. Service is named nodejs.Cli (not node.Cli) because bare `node` is the substrate Node primitive — a §3 nickname collision that made Version unresolvable (Primitive() receiver). min_version = none is not a materialization receipt." +// Observed node --version is recorded on CodexBuildToolchainReceipt beside npm.Cli.Version so the +// ambient toolchain that performed offline ci is attributable. Service is named nodejs.Cli (not +// node.Cli) because bare `node` is the substrate Node primitive — a §3 nickname collision that made +// Version unresolvable (Primitive() receiver). min_version = none is not a materialization receipt. service nodejs.Cli { operation Version { diff --git a/dag/extdeps/tools/npm.dag b/dag/extdeps/tools/npm.dag index 0c6816a0df4..5a1c8c0803a 100644 --- a/dag/extdeps/tools/npm.dag +++ b/dag/extdeps/tools/npm.dag @@ -20,7 +20,8 @@ data npm_cli_tool: CliTool = CliTool { installable_via: [SourceApt { package: package_npm }], } -data npm_cli_version_observation_note: String = "min_version = none is NOT a materialization receipt. Observed npm --version is recorded on CodexRuntimeBundleReceipt so the toolchain that performed offline ci is attributable." +// min_version = none is NOT a materialization receipt. Observed npm --version is recorded on +// CodexRuntimeBundleReceipt so the toolchain that performed offline ci is attributable. service npm.Cli { operation Version { @@ -51,9 +52,14 @@ data extdeps_model_scope: ExternalModelScope = ExternalModelScope { further_citations: [] } -data npm_ci_ignore_scripts_note: String = "npm ci --ignore-scripts is the FIRST Codex materialization realization: exact lock install with lifecycle scripts forbidden. workdir via env -C (cargo.BuildInheritEnv precedent). gunbc.package_delivery owns product policy (when to call); this module owns the npm CLI shape." +// npm ci --ignore-scripts is the FIRST Codex materialization realization: exact lock install with +// lifecycle scripts forbidden. workdir via env -C (cargo.BuildInheritEnv precedent). +// gunbc.package_delivery owns product policy (when to call); this module owns the npm CLI shape. -data npm_offline_cache_note: String = "Controlled cache realization: npm.Cache.Add seeds an isolated --cache from gunbc-admitted local tarball bytes; npm.Ci.IgnoreScriptsOffline then runs with --offline so no network package acquisition remains after admission. gunbc.package_delivery owns which archives populate the cache." +// Controlled cache realization: npm.Cache.Add seeds an isolated --cache from gunbc-admitted local +// tarball bytes; npm.Ci.IgnoreScriptsOffline then runs with --offline so no network package +// acquisition remains after admission. gunbc.package_delivery owns which archives populate the +// cache. service npm.Cache { operation Add { diff --git a/dag/extdeps/tools/pin.dag b/dag/extdeps/tools/pin.dag index 1b554e0105b..3040724b1e1 100644 --- a/dag/extdeps/tools/pin.dag +++ b/dag/extdeps/tools/pin.dag @@ -13,21 +13,82 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data cli_tool_pin_citation_note: String = "Cites the GNU Coding Standards' command-line interface conventions, which is what makes a CliTool a distinct pin SUBJECT rather than a generic artifact: a command-line program is identified by an invocable name and reports its own version through a conventional --version surface, and those two facts are exactly what a pin over this subject has to project. The pinning DISCIPLINE itself is cited once, subject-agnostically, at extdeps.pin — this module deliberately does not re-spell that anchor (DESIGN section 3: one fact, one authority)." +// Cites the GNU Coding Standards' command-line interface conventions, which is what makes a CliTool +// a distinct pin SUBJECT rather than a generic artifact: a command-line program is identified by an +// invocable name and reports its own version through a conventional --version surface, and those +// two facts are exactly what a pin over this subject has to project. The pinning DISCIPLINE itself +// is cited once, subject-agnostically, at extdeps.pin — this module deliberately does not re-spell +// that anchor (DESIGN section 3: one fact, one authority). -data cli_tool_pin_instantiation_note: String = "THE INSTANTIATION, and the whole point of the split. extdeps.pin owns the pinning DIMENSION as Pin; this module binds that dimension to ONE subject, CliTool, and owns only what is genuinely subject-specific: how to project an identity out of a CliTool, and what ingesting the existing roster means. Adding a second pinnable subject — SccacheBinaryArtifact, ActionRef, a container image, a GitHub release — is a new sibling instantiation, NOT an edit to extdeps.pin and NOT a second pin type (DESIGN section 2 horizontal: one concept, every breadth; the Vendor shape). If a future change to extdeps.pin is needed to admit a new subject, that is the signal the dimension was modeled too narrowly, and the fix belongs there rather than in a fork here." +// THE INSTANTIATION, and the whole point of the split. extdeps.pin owns the pinning DIMENSION as +// Pin; this module binds that dimension to ONE subject, CliTool, and owns only what is +// genuinely subject-specific: how to project an identity out of a CliTool, and what ingesting the +// existing roster means. Adding a second pinnable subject — SccacheBinaryArtifact, ActionRef, a +// container image, a GitHub release — is a new sibling instantiation, NOT an edit to extdeps.pin +// and NOT a second pin type (DESIGN section 2 horizontal: one concept, every breadth; the +// Vendor shape). If a future change to extdeps.pin is needed to admit a new subject, that +// is the signal the dimension was modeled too narrowly, and the fix belongs there rather than in a +// fork here. -data cli_tool_pin_subject_by_value_note: String = "The subject is carried BY VALUE, which is the defect the operator caught in the first shape (2026-07-29). That version declared tool_name: NonEmptyStr and joined to the tool through string equality — the single site in extdeps.tools doing so, against that module's own established convention: resolve(tool: CliTool), ResolvedTool { tool: CliTool }, NotFound { tool: CliTool } all pass the modeled row. A string foreign key into a type that is sitting right there is the anemic-leaf failure of DESIGN section 2, and it put tool identity in two places. Pin carries the CliTool itself, so cli_tool_pin_key_of PROJECTS the name from the row rather than trusting a copy of it." +// The subject is carried BY VALUE, which is the defect the operator caught in the first shape +// (2026-07-29). That version declared tool_name: NonEmptyStr and joined to the tool through string +// equality — the single site in extdeps.tools doing so, against that module's own established +// convention: resolve(tool: CliTool), ResolvedTool { tool: CliTool }, NotFound { tool: CliTool } +// all pass the modeled row. A string foreign key into a type that is sitting right there is the +// anemic-leaf failure of DESIGN section 2, and it put tool identity in two places. Pin +// carries the CliTool itself, so cli_tool_pin_key_of PROJECTS the name from the row rather than +// trusting a copy of it. fn cli_tool_pin_key_of(pin: Pin) -> NonEmptyStr { pin.subject.name } -data cli_tool_min_version_relation_note: String = "CORRECTING MY OWN OVERCLAIM, stated so it is not silently carried forward. When proposing this re-model I told the operator that CliTool.min_version and the pin's version were 'one concept forked by rigor' — the Vendor pattern — and that A0 would dissolve them onto one authority. That was WRONG, and re-reading the two types shows why. CliTool.min_version is extdeps.version.VersionConstraint, a RANGE, and it states a REQUIREMENT: the lowest version this tool's consumers can accept. Pin.version is extdeps.version.VersionIdentity, an EXACT version, and it states a SELECTION: the one we actually pinned. A requirement and a selection satisfying it are two different facts about version, not two spellings of one fact, so dissolving them would DESTROY information — a tool may require >= 1.6 while we pin exactly 1.7.1, and both halves are load-bearing. No dissolution is performed here and none is owed. What the two facts do have is a checkable RELATION — a pin ought to satisfy its subject's constraint — which is a real future consumer (a pin selecting 1.5 under a >= 1.6 requirement is a typed, located refusal), not a redundancy. It is deliberately NOT implemented in this pass: extdeps.version has no constraint-satisfaction fn today, and writing one here would fork version semantics into the tools namespace. dissolve-on: feature:version-constraint-satisfaction — extdeps.version gains satisfies(identity, constraint), at which point admission over a Pin can check it." +// CORRECTING MY OWN OVERCLAIM, stated so it is not silently carried forward. When proposing this +// re-model I told the operator that CliTool.min_version and the pin's version were 'one concept +// forked by rigor' — the Vendor pattern — and that A0 would dissolve them onto one authority. That +// was WRONG, and re-reading the two types shows why. CliTool.min_version is +// extdeps.version.VersionConstraint, a RANGE, and it states a REQUIREMENT: the lowest version this +// tool's consumers can accept. Pin.version is extdeps.version.VersionIdentity, an EXACT version, +// and it states a SELECTION: the one we actually pinned. A requirement and a selection satisfying +// it are two different facts about version, not two spellings of one fact, so dissolving them would +// DESTROY information — a tool may require >= 1.6 while we pin exactly 1.7.1, and both halves are +// load-bearing. No dissolution is performed here and none is owed. What the two facts do have is a +// checkable RELATION — a pin ought to satisfy its subject's constraint — which is a real future +// consumer (a pin selecting 1.5 under a >= 1.6 requirement is a typed, located refusal), not a +// redundancy. It is deliberately NOT implemented in this pass: extdeps.version has no +// constraint-satisfaction fn today, and writing one here would fork version semantics into the +// tools namespace. dissolve-on: feature:version-constraint-satisfaction — extdeps.version gains +// satisfies(identity, constraint), at which point admission over a Pin can check it. -data legacy_ingest_none_spelling_blocker_note: String = "DISCOVERED BY EXECUTION while witnessing admit_legacy_cli_tool, and declared rather than dodged (DESIGN section 5: noticing a workaround IS the line-stop signal). The Absent arm below does NOT match a field written with the `none` literal: a `none`-valued optional evaluates to Value::Null and the match reports `non-exhaustive pattern match on: null`, while a field written `Absent` matches fine. This is a live instance of the documented Value::Null open thread — Value::Null is the overloaded None/Absent/miss sentinel — surfaced here by a new consumer. It MATTERS because the corpus overwhelmingly uses the `none` spelling: roughly 568 `: none,` fields against 98 `: Absent,` in dag/ at authoring, and every shipped CliTool row with no version (extdeps.tools.jq, .grep, .sed, .sha256sum, .sleep) is spelled `none`. So this fn is correct on the model and CANNOT yet be run against the real rows: the witness beside it exercises the Absent spelling only, and that limit is stated rather than papered over — a green witness here does not claim corpus coverage. BLOCKER for roster ingest, not debt on this shape: it needs the Value::Null carrier split (docs/plans/value-null-split.md) or an equivalent grounding of the two spellings onto one representation. Dissolve-on: that split lands, at which point the witness extends to a `none`-spelled row and this note deletes." +// DISCOVERED BY EXECUTION while witnessing admit_legacy_cli_tool, and declared rather than dodged +// (DESIGN section 5: noticing a workaround IS the line-stop signal). The Absent arm below does NOT +// match a field written with the `none` literal: a `none`-valued optional evaluates to Value::Null +// and the match reports `non-exhaustive pattern match on: null`, while a field written `Absent` +// matches fine. This is a live instance of the documented Value::Null open thread — Value::Null is +// the overloaded None/Absent/miss sentinel — surfaced here by a new consumer. It MATTERS because +// the corpus overwhelmingly uses the `none` spelling: roughly 568 `: none,` fields against 98 `: +// Absent,` in dag/ at authoring, and every shipped CliTool row with no version (extdeps.tools.jq, +// .grep, .sed, .sha256sum, .sleep) is spelled `none`. So this fn is correct on the model and CANNOT +// yet be run against the real rows: the witness beside it exercises the Absent spelling only, and +// that limit is stated rather than papered over — a green witness here does not claim corpus +// coverage. BLOCKER for roster ingest, not debt on this shape: it needs the Value::Null carrier +// split (docs/plans/value-null-split.md) or an equivalent grounding of the two spellings onto one +// representation. Dissolve-on: that split lands, at which point the witness extends to a +// `none`-spelled row and this note deletes. -data legacy_cli_tool_ingest_note: String = "The discriminating RED, and the migration frontier made countable. EVERY CliTool row in the corpus today refuses conversion to a pin, and that is the correct answer rather than a gap: min_version is a RANGE brand ('>= 7.68') and a range is not an identity. Two hosts satisfying one range run two different binaries, which is precisely the variance a pin exists to remove, so widening the range into a fabricated identity here would be the absorbing fallback DESIGN section 5 forbids. Absent refuses for the same reason with a DISTINCT cause: no version stated at all is a different deficit from a stated range, and collapsing the two would be a state-space conflation. Each refusal names its subject and is countable, so the migration burns down visibly instead of being silently assumed complete. Note what this fn does NOT do: it does not invent a digest. A pin also requires expected_identity, and there is no real upstream digest anywhere in the corpus today, so ingest cannot be completed by this fn alone even for a row that stated an exact version — sourcing genuine upstream checksums is separate, deliberate work, and fabricating them would make a fail-closed carrier assert something false." +// The discriminating RED, and the migration frontier made countable. EVERY CliTool row in the +// corpus today refuses conversion to a pin, and that is the correct answer rather than a gap: +// min_version is a RANGE brand ('>= 7.68') and a range is not an identity. Two hosts satisfying one +// range run two different binaries, which is precisely the variance a pin exists to remove, so +// widening the range into a fabricated identity here would be the absorbing fallback DESIGN section +// 5 forbids. Absent refuses for the same reason with a DISTINCT cause: no version stated at all is +// a different deficit from a stated range, and collapsing the two would be a state-space +// conflation. Each refusal names its subject and is countable, so the migration burns down visibly +// instead of being silently assumed complete. Note what this fn does NOT do: it does not invent a +// digest. A pin also requires expected_identity, and there is no real upstream digest anywhere in +// the corpus today, so ingest cannot be completed by this fn alone even for a row that stated an +// exact version — sourcing genuine upstream checksums is separate, deliberate work, and fabricating +// them would make a fail-closed carrier assert something false. fn admit_legacy_cli_tool(tool: CliTool) -> PinAdmission { match tool.min_version { diff --git a/dag/extdeps/tools/sha256sum.dag b/dag/extdeps/tools/sha256sum.dag index 98dac9a68bd..da42fba5bb9 100644 --- a/dag/extdeps/tools/sha256sum.dag +++ b/dag/extdeps/tools/sha256sum.dag @@ -73,14 +73,23 @@ service sha256sum.Sha256 { } } -data sha256_verify_shape_doc: String = "DESIGN §3 shape/transport/policy split: Sha256Verify(path, expected_digest) is the interface shape, cited to extdeps.crypto.hash extdeps_external_authority_anchor (FIPS 180-4 — the SHA-256 algorithm itself, not a particular CLI, and consumed from the module that owns that citation rather than re-minted here). sha256sum_verify_via_shell is ONE transport handler realizing that shape via the coreutils sha256sum CLI (sha256sum_cli_tool / CheckFile above); a future native/library digest realization is another handler bound to the same shape, not a rewrite." +// DESIGN §3 shape/transport/policy split: Sha256Verify(path, expected_digest) is the interface +// shape, cited to extdeps.crypto.hash extdeps_external_authority_anchor (FIPS 180-4 — the SHA-256 +// algorithm itself, not a particular CLI, and consumed from the module that owns that citation +// rather than re-minted here). sha256sum_verify_via_shell is ONE transport handler realizing that +// shape via the coreutils sha256sum CLI (sha256sum_cli_tool / CheckFile above); a future +// native/library digest realization is another handler bound to the same shape, not a rewrite. type Sha256VerifyVerdict = Sha256Matches | Sha256Mismatch | Sha256VerifyInfraFailure { reason: NonEmptyStr } -data sha256_verify_infra_failure_doc: String = "Sha256VerifyInfraFailure is DESIGN §5 state-space de-conflation (cursor review, PR #6596): mktemp/checksum-file-write failures are environment/tooling failures, not evidence about path's digest, so they must not collapse into Sha256Mismatch alongside a genuine sha256sum mismatch - a caller that surfaces Sha256Mismatch as HashMismatchRefusal would otherwise mislabel a broken mktemp as a corrupted download." +// Sha256VerifyInfraFailure is DESIGN §5 state-space de-conflation (cursor review, PR #6596): +// mktemp/checksum-file-write failures are environment/tooling failures, not evidence about path's +// digest, so they must not collapse into Sha256Mismatch alongside a genuine sha256sum mismatch - a +// caller that surfaces Sha256Mismatch as HashMismatchRefusal would otherwise mislabel a broken +// mktemp as a corrupted download. data sha256_verify_native_transport_frontier_rows: List = [ frontier_row_decl( diff --git a/dag/extdeps/tools/sha512sum.dag b/dag/extdeps/tools/sha512sum.dag index 1772d6956db..a48e8c7a90c 100644 --- a/dag/extdeps/tools/sha512sum.dag +++ b/dag/extdeps/tools/sha512sum.dag @@ -34,9 +34,20 @@ data extdeps_model_scope: ExternalModelScope = ExternalModelScope { further_citations: [extdeps.crypto.hash.extdeps_external_authority_anchor] } -data sha512_algorithm_authority_consumed_note: String = "The FIPS 180-4 algorithm citation is CONSUMED from extdeps.crypto.hash, never re-minted here (DESIGN §3 single authority; review 50411 caught the fork). This module's own extdeps_external_authority_anchor is the coreutils manual — the tool that prints the digest — which is a different upstream subject from the algorithm it implements, so the two rows are distinct facts rather than a nickname pair. The anchor keeps a literal ExternalAuthority body deliberately: mandatory-tag enrollment reads the value subtree structurally and a datum alias reds as missing (extdeps.cache.pin sccache_pin_instantiation_note, review 44866). A further_citation carries no such constraint, so it references the owning module's row directly." +// The FIPS 180-4 algorithm citation is CONSUMED from extdeps.crypto.hash, never re-minted here +// (DESIGN §3 single authority; review 50411 caught the fork). This module's own +// extdeps_external_authority_anchor is the coreutils manual — the tool that prints the digest — +// which is a different upstream subject from the algorithm it implements, so the two rows are +// distinct facts rather than a nickname pair. The anchor keeps a literal ExternalAuthority body +// deliberately: mandatory-tag enrollment reads the value subtree structurally and a datum alias +// reds as missing (extdeps.cache.pin sccache_pin_instantiation_note, review 44866). A +// further_citation carries no such constraint, so it references the owning module's row directly. -data sha512_observe_shape_note: String = "INTERFACE: observe file bytes → Sha512Digest. Transport: coreutils sha512sum printing lowercase hex. This is an OBSERVATION, not digest_shell_verify_line (which assembles a check script from an expected digest). gunbc.package_delivery compares the observed digest to the lock expectation in admit_observed_archive_digest. dissolve-on: a native/library Sha512Digest realization as a second handler." +// INTERFACE: observe file bytes → Sha512Digest. Transport: coreutils sha512sum printing lowercase +// hex. This is an OBSERVATION, not digest_shell_verify_line (which assembles a check script from an +// expected digest). gunbc.package_delivery compares the observed digest to the lock expectation in +// admit_observed_archive_digest. dissolve-on: a native/library Sha512Digest realization as a second +// handler. service sha512sum.Sha512 { operation DigestFile { diff --git a/dag/extdeps/transports/rest.dag b/dag/extdeps/transports/rest.dag index 991022b7b0b..80365319a5c 100644 --- a/dag/extdeps/transports/rest.dag +++ b/dag/extdeps/transports/rest.dag @@ -32,14 +32,18 @@ type TlsPosture = VerifyPeer | InsecureAcceptAnyCert -data tls_posture_default_note: String = "Absent tls posture = VerifyPeer (fail-closed). InsecureAcceptAnyCert is an explicit per-transport-row opt-in for self-signed endpoints (BMCs); a realization must refuse an unrecognized posture value, never widen to a header or to VerifyPeer-silently." +// Absent tls posture = VerifyPeer (fail-closed). InsecureAcceptAnyCert is an explicit +// per-transport-row opt-in for self-signed endpoints (BMCs); a realization must refuse an +// unrecognized posture value, never widen to a header or to VerifyPeer-silently. type RestAuthScheme = BearerToken | HeaderToken { header_name: NonEmptyStr } | BasicCredentials -data rest_auth_value_single_authority_note: String = "The full header value (Bearer / Basic / raw token for HeaderToken) is derived in ONE place per realization from RestAuthScheme - a dispatch site re-deriving 'Bearer ' from a header name is the §3 fork this row forbids." +// The full header value (Bearer / Basic / raw token for HeaderToken) is +// derived in ONE place per realization from RestAuthScheme - a dispatch site re-deriving 'Bearer ' +// from a header name is the §3 fork this row forbids. data rest_auth_scheme_dissolution_note: String = "RestAuthScheme is forward-looking scaffolding: today the realizations read auth by shape (config-level Bearer/Header via svc_auth, transport-level BasicCredentials via the inline auth_basic username/password record) rather than matching this type. Dissolves when the auth axis is threaded as a first-class RestAuthScheme through parse/interp/emit - the redfish defork (PR B, extdeps.bmc.http redfish_http_hardwired_transport_dissolution_trigger) is its first BasicCredentials consumer; land the type-level consumption with that lane, or delete this row if the inline-record spelling stays the authority." @@ -47,7 +51,10 @@ type RestTargetForm = OriginFormOverBase | AbsoluteFormTarget -data rest_target_form_note: String = "OriginFormOverBase: url = config endpoint + path template. AbsoluteFormTarget: the path template carries the full scheme://host/... target (RFC 9112 §3.2.2 absolute-form) and interpolates per-call inputs (e.g. a BMC host); a non-empty base alongside an absolute-form path is a typed refusal, never a silent concat." +// OriginFormOverBase: url = config endpoint + path template. AbsoluteFormTarget: the path template +// carries the full scheme://host/... target (RFC 9112 §3.2.2 absolute-form) and interpolates +// per-call inputs (e.g. a BMC host); a non-empty base alongside an absolute-form path is a typed +// refusal, never a silent concat. data rest_target_form_dissolution_note: String = "RestTargetForm is forward-looking scaffolding: absolute-form targeting works today via an empty config base + a full-URL path template (the interp/emit concat `base_url + path`), so the realizations do not yet match this type or enforce the empty-base refusal it names. Dissolves when the empty-base-vs-absolute-path check is realized as a construction wall in parse (the redfish per-call bmc_host ops, PR B, are its first consumer); land it with that lane, or delete this row if the concat spelling stays the authority." @@ -60,7 +67,37 @@ type RestTransportConfig { tls: TlsPosture? } -data rest_outcome_note: String = "A RESPONSE THAT IS NOT 2xx IS AN ANSWER, NOT AN ABSENCE OF ONE, and until this type existed the realization threw it away. Operations already declare what a status yields - github.Pulls.List declares 200/401/404/422/5xx and twenty-two other extdeps modules declare 204 such arms between them - and the v1 seed parsed every one of those declarations onto the operation node as a response_ property and then never read it. dispatch_rest raised InterpError::TypeError with a rendered 'HTTP \{status\}: \{body\}' string instead, so a caller could not reach the status, could not reach the body, and could not persist either without parsing a diagnostic meant for a human. That is specification-without-execution across the whole REST surface, and it is what made a live 401 unrepresentable to the Publication observer that provoked this type.\\n\\nFOUR STATES, DELIBERATELY NOT THREE. RestOk means the operation's declared output fields carry the decoded answer. RestStatusRefused means the request was transported and a remote authority answered with a status this operation does not treat as success - the status and the body it came with are both retained, because the body is usually the only place the remote says WHY. RestTransportRefused means no status exists at all: nothing was answered, so nothing may be reported as an answer. RestBodyUndecodable means a status arrived and the payload beneath it could not be read - the status is known and is carried, and the body is not fabricated as empty. Collapsing the last two would report 'the remote said nothing' for 'we never reached the remote', which are different facts with different remedies; collapsing an unreadable body into an empty one would report silence the remote never chose.\\n\\nWHY status IS HttpStatus AND NOT Int: std.types already carries HttpStatus with its 100-599 range, so a second unconstrained integer here would be a second name for one concept, and a transport-refusal arm would then be able to hold a status that no response can have. There is deliberately no status on RestTransportRefused for the same reason in the other direction - a sentinel zero would be a plausible-looking value standing where the honest answer is that the question does not apply.\\n\\nSCOPE, STATED SO IT IS NOT MISREAD AS COMPLETE: this outcome is carried by an operation that DECLARES an outcome field, and an operation that declares none keeps raising exactly as before. That opt-in is the narrow half of the repair, not its end state - the refusal body also stays a String here rather than being decoded into the shape the response block already names for it. Both dissolve together when the response block becomes the single authority for a result and output derives from its 2xx arm, at which point every operation carries this outcome without declaring it and this field deletes as redundant." +// A RESPONSE THAT IS NOT 2xx IS AN ANSWER, NOT AN ABSENCE OF ONE, and until this type existed the +// realization threw it away. Operations already declare what a status yields - github.Pulls.List +// declares 200/401/404/422/5xx and twenty-two other extdeps modules declare 204 such arms between +// them - and the v1 seed parsed every one of those declarations onto the operation node as a +// response_ property and then never read it. dispatch_rest raised InterpError::TypeError +// with a rendered 'HTTP {status}: {body}' string instead, so a caller could not reach the status, +// could not reach the body, and could not persist either without parsing a diagnostic meant for a +// human. That is specification-without-execution across the whole REST surface, and it is what made +// a live 401 unrepresentable to the Publication observer that provoked this type.\n\nFOUR STATES, +// DELIBERATELY NOT THREE. RestOk means the operation's declared output fields carry the decoded +// answer. RestStatusRefused means the request was transported and a remote authority answered with +// a status this operation does not treat as success - the status and the body it came with are both +// retained, because the body is usually the only place the remote says WHY. RestTransportRefused +// means no status exists at all: nothing was answered, so nothing may be reported as an answer. +// RestBodyUndecodable means a status arrived and the payload beneath it could not be read - the +// status is known and is carried, and the body is not fabricated as empty. Collapsing the last two +// would report 'the remote said nothing' for 'we never reached the remote', which are different +// facts with different remedies; collapsing an unreadable body into an empty one would report +// silence the remote never chose.\n\nWHY status IS HttpStatus AND NOT Int: std.types already +// carries HttpStatus with its 100-599 range, so a second unconstrained integer here would be a +// second name for one concept, and a transport-refusal arm would then be able to hold a status that +// no response can have. There is deliberately no status on RestTransportRefused for the same reason +// in the other direction - a sentinel zero would be a plausible-looking value standing where the +// honest answer is that the question does not apply.\n\nSCOPE, STATED SO IT IS NOT MISREAD AS +// COMPLETE: this outcome is carried by an operation that DECLARES an outcome field, and an +// operation that declares none keeps raising exactly as before. That opt-in is the narrow half of +// the repair, not its end state - the refusal body also stays a String here rather than being +// decoded into the shape the response block already names for it. Both dissolve together when the +// response block becomes the single authority for a result and output derives from its 2xx arm, at +// which point every operation carries this outcome without declaring it and this field deletes as +// redundant. type RestOutcome = RestOk @@ -68,7 +105,13 @@ type RestOutcome | RestTransportRefused { cause: NonEmptyStr } | RestBodyUndecodable { status: HttpStatus, cause: NonEmptyStr } -data rest_exchange_observation_note: String = "THE HTTP CLIENT BOUNDARY REPORTS FACTS AND DECIDES NO OPERATION POLICY. RestResponseObserved says a status arrived and then says separately whether its body was readable; RestExchangeRefused says no response arrived, so no status can be fabricated. The operation-facing RestOutcome above is downstream: the same dispatch_rest fold consumes this carrier whether a real HTTP handler or an exact fixture replay produced it. A replay arm that returned the operation's final output would sit one layer too high and could green while dispatch_rest was broken - the defect in mock_response and the retired recorded-value fixture seam." +// THE HTTP CLIENT BOUNDARY REPORTS FACTS AND DECIDES NO OPERATION POLICY. RestResponseObserved says +// a status arrived and then says separately whether its body was readable; RestExchangeRefused says +// no response arrived, so no status can be fabricated. The operation-facing RestOutcome above is +// downstream: the same dispatch_rest fold consumes this carrier whether a real HTTP handler or an +// exact fixture replay produced it. A replay arm that returned the operation's final output would +// sit one layer too high and could green while dispatch_rest was broken - the defect in +// mock_response and the retired recorded-value fixture seam. type RestBodyObservation = RestBodyRead { body: String } @@ -82,9 +125,64 @@ type RestAuthSensitiveIdentity = RestUnauthenticated | RestAuthenticated { scheme: NonEmptyStr, digest: Fnv1a64Structural } -data rest_authenticated_digest_witness_note: String = "AUTHENTICATED ARM: PIN WITHDRAWN 2026-08-11 (gunbc#8146) - HEADER CORRECTED, was EXECUTED PIN; REPLAY-PATH DEFERRAL TYPED (review 47041, gunbc#7648). RestAuthenticated.digest is Fnv1a64Structural and the seed mint rest_auth_identity_value produces it via atom_identity_hash over '\\0' - the SAME fnv1a64 primitive std.content_hash.content_hash_atom realizes, so an authored fixture reproduces the digest through the modeled surface (content_hash_atom(value: \"\\0\")); an earlier spelling hashed with the host DefaultHasher, a value from outside the family this carrier names, which no dag fixture could ever author. WITHDRAWN EVIDENCE (2026-08-11, gunbc#8146, review 51198) - was EXECUTED EVIDENCE: rest_authenticated_identity_matches_dag_constructed_value (src/v1/tests/src/cross_representation_equality_test.rs, DELETED by the v1 Rust test-suite cutover; the assertion lived only there). It had not executed since the suite went --no-run, so this row was already nominal rather than newly broken. The seed mint itself is untouched. DISSOLVE-ON: re-establish the identity assertion as a floor witness. It previously pins the seed mint ==-equal to the dag-authored identity - RED under a bare-text digest, RED under a non-fnv1a64 digest - plus a secret-inequality control. WHAT REMAINS DEFERRED, AND WHY, TYPED: a full replay-path authenticated witness (fixture matched against a seed-produced invocation through the service dispatch) additionally requires the invocation input_digest, whose per-param limbs content_hash_service_inputs still derives via the host value_hash - not reproducible from .dag - so authoring the matching fixture is blocked on that limb pipeline migrating to the fnv1a64 primitives; that migration also moves the recorded-fixture store keys, so it is its own lane, not a fixture edit here. RUNG, CORRECTED FOR CI ENROLLMENT (calm-badger-682, verified against .github/workflows/ci.yml and gunbc.ci_layer_roots witness_layer_roots): CORRECTED AGAIN 2026-08-11 (gunbc#8146, review 51205) - this paragraph described a CI arrangement that no longer exists, and described it BACKWARDS relative to the change that removed it. It read that CI's only contact with the crate was 'cargo check -p v1-compiler-tests --tests (compile gate; no test execution)'. The build job now runs 'cargo test -p v1-compiler-tests --release' - the retained kernel EXECUTES in required CI, --no-run is gone from both authorities (v2.workflow.ci_v1_compiler_tests_compile_gate_emit and gunbc.ci_workflow) and from the emitted ci.yml. Two separate facts, deliberately not fused: the crate now executes in CI, AND this particular pin is no longer in it, because the module carrying it was deleted by that same cutover. So the reason this note names no executing consumer is deletion, not the old compile-only gate. witness_layer_roots remains dag plus src/v2, so floor discovery still never reaches src/v1/tests; measured, the pin passes locally in 1.66s and reds nothing anywhere. DESIGN 4b defines mechanically preventable as a mechanism that reliably exposes AND blocks, with safety depending on it executing and staying enrolled - so the mint seam sits BELOW that rung, and calling it mechanically prevented is rung inflation, which 4b names as worse than sitting low because an inflated class never ranks for climbing. Deleting the seam guard would leave this control present, compiling, and mute. NOT NEW DEBT FROM THIS PR: content_hash_cross_family_eq_v1_seed_bridge_note cites the same unexecuted crate for the cross-family backstop, so the whole family of seed-bridge controls shares this gap and it is recorded here rather than fixed. The end-to-end authenticated replay path remains unexecuted and must not be reported above either. HAND-RUST RECEIPT (review 47065): the ROADMAP row 'Work through the remaining hand-written files the products need' (authority dag/gunbc/v1/v1_deletion_plan.dag, v1-hand-queue-drain lane) owns the interim seed bridge in rest_auth_identity_value, fnv1a64_structural_value, and rest_authenticated_identity_for_witness. DISSOLVE-ON: that row migrates REST invocation construction out of v1_interpreter; the checkable receipt is deletion of those three hand-Rust symbols while this discriminating equality/inequality witness remains enrolled against the replacement realization. Independently, the input-digest limb migration lands and an authenticated replay witness executes against a seed-produced invocation; this note then deletes." - -data rest_bound_invocation_note: String = "REST needs a specialization of v2.std.operation_argv.BoundOperationInvocation rather than pretending its shell-only binding list is enough. OperationRef remains the declaration identity authority. The specialization adds the HTTP method, fully realized URI target, the digest of every declared input, and an auth-sensitive digest whose preimage never enters the fixture. Together those facts make a replay exact: a fixture for another operation, path/query/body input, method, target, or credential identity cannot satisfy this invocation. Query and body values are already in the declared-input digest; constants are fixed by OperationRef." +// AUTHENTICATED ARM: PIN WITHDRAWN 2026-08-11 (gunbc#8146) - HEADER CORRECTED, was EXECUTED PIN; +// REPLAY-PATH DEFERRAL TYPED (review 47041, gunbc#7648). RestAuthenticated.digest is +// Fnv1a64Structural and the seed mint rest_auth_identity_value produces it via atom_identity_hash +// over '\0' - the SAME fnv1a64 primitive std.content_hash.content_hash_atom +// realizes, so an authored fixture reproduces the digest through the modeled surface +// (content_hash_atom(value: "\0")); an earlier spelling hashed with the host +// DefaultHasher, a value from outside the family this carrier names, which no dag fixture could +// ever author. WITHDRAWN EVIDENCE (2026-08-11, gunbc#8146, review 51198) - was EXECUTED EVIDENCE: +// rest_authenticated_identity_matches_dag_constructed_value +// (src/v1/tests/src/cross_representation_equality_test.rs, DELETED by the v1 Rust test-suite +// cutover; the assertion lived only there). It had not executed since the suite went --no-run, so +// this row was already nominal rather than newly broken. The seed mint itself is untouched. +// DISSOLVE-ON: re-establish the identity assertion as a floor witness. It previously pins the seed +// mint ==-equal to the dag-authored identity - RED under a bare-text digest, RED under a +// non-fnv1a64 digest - plus a secret-inequality control. WHAT REMAINS DEFERRED, AND WHY, TYPED: a +// full replay-path authenticated witness (fixture matched against a seed-produced invocation +// through the service dispatch) additionally requires the invocation input_digest, whose per-param +// limbs content_hash_service_inputs still derives via the host value_hash - not reproducible from +// .dag - so authoring the matching fixture is blocked on that limb pipeline migrating to the +// fnv1a64 primitives; that migration also moves the recorded-fixture store keys, so it is its own +// lane, not a fixture edit here. RUNG, CORRECTED FOR CI ENROLLMENT (calm-badger-682, verified +// against .github/workflows/ci.yml and gunbc.ci_layer_roots witness_layer_roots): CORRECTED AGAIN +// 2026-08-11 (gunbc#8146, review 51205) - this paragraph described a CI arrangement that no longer +// exists, and described it BACKWARDS relative to the change that removed it. It read that CI's only +// contact with the crate was 'cargo check -p v1-compiler-tests --tests (compile gate; no test +// execution)'. The build job now runs 'cargo test -p v1-compiler-tests --release' - the retained +// kernel EXECUTES in required CI, --no-run is gone from both authorities +// (v2.workflow.ci_v1_compiler_tests_compile_gate_emit and gunbc.ci_workflow) and from the emitted +// ci.yml. Two separate facts, deliberately not fused: the crate now executes in CI, AND this +// particular pin is no longer in it, because the module carrying it was deleted by that same +// cutover. So the reason this note names no executing consumer is deletion, not the old +// compile-only gate. witness_layer_roots remains dag plus src/v2, so floor discovery still never +// reaches src/v1/tests; measured, the pin passes locally in 1.66s and reds nothing anywhere. DESIGN +// 4b defines mechanically preventable as a mechanism that reliably exposes AND blocks, with safety +// depending on it executing and staying enrolled - so the mint seam sits BELOW that rung, and +// calling it mechanically prevented is rung inflation, which 4b names as worse than sitting low +// because an inflated class never ranks for climbing. Deleting the seam guard would leave this +// control present, compiling, and mute. NOT NEW DEBT FROM THIS PR: +// content_hash_cross_family_eq_v1_seed_bridge_note cites the same unexecuted crate for the +// cross-family backstop, so the whole family of seed-bridge controls shares this gap and it is +// recorded here rather than fixed. The end-to-end authenticated replay path remains unexecuted and +// must not be reported above either. HAND-RUST RECEIPT (review 47065): the ROADMAP row 'Work +// through the remaining hand-written files the products need' (authority +// dag/gunbc/v1/v1_deletion_plan.dag, v1-hand-queue-drain lane) owns the interim seed bridge in +// rest_auth_identity_value, fnv1a64_structural_value, and rest_authenticated_identity_for_witness. +// DISSOLVE-ON: that row migrates REST invocation construction out of v1_interpreter; the checkable +// receipt is deletion of those three hand-Rust symbols while this discriminating +// equality/inequality witness remains enrolled against the replacement realization. Independently, +// the input-digest limb migration lands and an authenticated replay witness executes against a +// seed-produced invocation; this note then deletes. + +// REST needs a specialization of v2.std.operation_argv.BoundOperationInvocation rather than +// pretending its shell-only binding list is enough. OperationRef remains the declaration identity +// authority. The specialization adds the HTTP method, fully realized URI target, the digest of +// every declared input, and an auth-sensitive digest whose preimage never enters the fixture. +// Together those facts make a replay exact: a fixture for another operation, path/query/body input, +// method, target, or credential identity cannot satisfy this invocation. Query and body values are +// already in the declared-input digest; constants are fixed by OperationRef. type RestBoundOperationInvocation { at: OperationRef @@ -110,7 +208,20 @@ type RestFixtureLookup | RestFixtureAbsent { invocation: RestBoundOperationInvocation } | RestFixtureAmbiguous { invocation: RestBoundOperationInvocation, count: Int } -data invocation_identity_equality_note: String = "IDENTITY CONSUMES CANONICAL EQUALITY; IT DOES NOT RE-DERIVE IT. An earlier cut carried five hand-rolled predicates - rest_http_method_eq, rest_uri_scheme_eq, rest_uri_eq, rest_operation_ref_eq, rest_auth_identity_eq - each matching every variant of a closed coproduct to decide whether two of them were the same. That is a second representation of equality standing beside the one the substrate already provides, and it is the §2/§3 failure in its most literal form: adding a variant to HttpMethod or UriScheme would leave the predicate silently answering false for the new one, so the duplicate was not merely redundant but decayed differently from the type it claimed to compare (review 46769).\n\nWHAT IS DELIBERATELY NOT COLLAPSED: the field list below, rather than a whole-record a == b. Which facts constitute an exact invocation identity is a modeled decision - operation, method, fully realized target, input digest, and auth identity - and spelling them keeps that decision reviewable at the point it is made. The equality of each named fact is canonical; the choice of which facts to compare is not equality is business." +// IDENTITY CONSUMES CANONICAL EQUALITY; IT DOES NOT RE-DERIVE IT. An earlier cut carried five +// hand-rolled predicates - rest_http_method_eq, rest_uri_scheme_eq, rest_uri_eq, +// rest_operation_ref_eq, rest_auth_identity_eq - each matching every variant of a closed coproduct +// to decide whether two of them were the same. That is a second representation of equality standing +// beside the one the substrate already provides, and it is the §2/§3 failure in its most literal +// form: adding a variant to HttpMethod or UriScheme would leave the predicate silently answering +// false for the new one, so the duplicate was not merely redundant but decayed differently from the +// type it claimed to compare (review 46769). +// +// WHAT IS DELIBERATELY NOT COLLAPSED: the field list below, rather than a whole-record a == b. +// Which facts constitute an exact invocation identity is a modeled decision - operation, method, +// fully realized target, input digest, and auth identity - and spelling them keeps that decision +// reviewable at the point it is made. The equality of each named fact is canonical; the choice of +// which facts to compare is not equality is business. fn rest_bound_invocation_eq(a: RestBoundOperationInvocation, b: RestBoundOperationInvocation) -> Bool { a.at == b.at @@ -213,7 +324,11 @@ type RestExchangeResolution | RestReplayExchangeAmbiguous { invocation: RestBoundOperationInvocation, count: Int } | RestExchangeHandlerUncovered { invocation: RestBoundOperationInvocation } -data rest_exchange_resolution_note: String = "THE SINGLE HANDLER-BINDING AND LOOKUP DECISION. Runtime code asks this function what the frame's existing effect grant selected; it does not re-read Hermetic/Wet and does not implement its own first-match. RealTransport asks the host HTTP client for a RestExchangeObservation. Replay names a fixture store and reaches the three-valued exact-invocation lookup above. Missing, ambiguous and uncovered are distinct refusals, and none has a real-transport fallback arm." +// THE SINGLE HANDLER-BINDING AND LOOKUP DECISION. Runtime code asks this function what the frame's +// existing effect grant selected; it does not re-read Hermetic/Wet and does not implement its own +// first-match. RealTransport asks the host HTTP client for a RestExchangeObservation. Replay names +// a fixture store and reaches the three-valued exact-invocation lookup above. Missing, ambiguous +// and uncovered are distinct refusals, and none has a real-transport fallback arm. fn rest_exchange_resolution( env: Envelope, diff --git a/dag/extdeps/ucamco/gerber.dag b/dag/extdeps/ucamco/gerber.dag index 27d815ebf05..814556764c3 100644 --- a/dag/extdeps/ucamco/gerber.dag +++ b/dag/extdeps/ucamco/gerber.dag @@ -28,13 +28,24 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { // VERIFIED LIVE 2026-08-11 by fetching the cited locator. Facts taken from the response, none // recalled: Ucamco publishes the specification; the format is described there as 'the de facto -// standard for printed circuit board (PCB) design data transfer'; the newest revision named on -// the page is Gerber X3, with Gerber X2 (also called 'Gerber version 2') beside it and Standard -// Gerber declared OBSOLETE as of May 2014; the most recent specification update named in the -// news section is 'Gerber Layer Format Specification, revision 2024.05'. The page states that -// attributes carry meta-information about graphics objects and connections, and that the Gerber -// job file transfers fabrication characteristics such as thickness and finish. -data gerber_subject_note: String = "THE GERBER FORMAT, cited to Ucamco, its publisher. A CORRECTION THIS ROW EXISTS TO RECORD: the scoping proposal that motivated this module named Gerber X2 as the current revision. It is not — the fetched page names X3 as the newest, X2 beside it, and Standard Gerber obsolete since May 2014, so a compatibility projection that hardcoded 'X2' would have pinned the wrong revision of a live upstream on the strength of recollection. WHY THIS IS A SEPARATE SUBJECT FROM IPC-DPMX: Ucamco and the IPC-2581 Consortium are independently governed, so a single 'PCB output format' enum spanning both would fuse two upstreams into one nickname (DESIGN §3). THE LOAD-BEARING FACT FOR A MANUFACTURING HANDOFF is the carriage roster below, not the revision: layer imagery travels in the layer format, fabrication characteristics travel in a SEPARATE job file, and connectivity/BOM/placement do not travel in the image files at all. That asymmetry is precisely why 'we emitted Gerbers' is not 'we described the board' — the semantics that did not survive are the ones a fabricator then reconstructs by hand or guesses." +// standard for printed circuit board (PCB) design data transfer'; the newest revision named on the +// page is Gerber X3, with Gerber X2 (also called 'Gerber version 2') beside it and Standard Gerber +// declared OBSOLETE as of May 2014; the most recent specification update named in the news section +// is 'Gerber Layer Format Specification, revision 2024.05'. The page states that attributes carry +// meta-information about graphics objects and connections, and that the Gerber job file transfers +// fabrication characteristics such as thickness and finish. +// THE GERBER FORMAT, cited to Ucamco, its publisher. A CORRECTION THIS ROW EXISTS TO RECORD: the +// scoping proposal that motivated this module named Gerber X2 as the current revision. It is not — +// the fetched page names X3 newest, X2 beside it, and Standard Gerber obsolete since May 2014, so a +// compatibility projection hardcoding 'X2' would have pinned the wrong revision of a live upstream +// on the strength of recollection. WHY THIS IS A SEPARATE SUBJECT FROM IPC-DPMX: Ucamco and the +// IPC-2581 Consortium are independently governed, so a single 'PCB output format' enum spanning +// both would fuse two upstreams into one nickname (DESIGN §3). THE LOAD-BEARING FACT FOR A +// MANUFACTURING HANDOFF is the carriage roster below, not the revision: layer imagery travels in +// the layer format, fabrication characteristics in a SEPARATE job file, and connectivity/BOM/ +// placement not in the image files at all. That asymmetry is why 'we emitted Gerbers' is not 'we +// described the board' — the semantics that did not survive are the ones a fabricator then +// reconstructs by hand or guesses. type GerberFormat = | GerberFormatTarget diff --git a/dag/extdeps/unicode/blocks.dag b/dag/extdeps/unicode/blocks.dag index 430da1ae584..ddac0afe63d 100644 --- a/dag/extdeps/unicode/blocks.dag +++ b/dag/extdeps/unicode/blocks.dag @@ -13,7 +13,8 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data unicode_version_note: String = "East Asian Width block ranges transcribed from Unicode Standard UAX #11 (Unicode 15.1 UCD block data)." +// East Asian Width block ranges transcribed from Unicode Standard UAX #11 (Unicode 15.1 UCD block +// data). data zero_width_blocks: List = [ { name: "Combining Diacritical Marks", start: 768, end_inclusive: 879, default_width: ZeroWidth }, diff --git a/dag/extdeps/units/dimensionless.dag b/dag/extdeps/units/dimensionless.dag index 6908d3d2063..cef19f34064 100644 --- a/dag/extdeps/units/dimensionless.dag +++ b/dag/extdeps/units/dimensionless.dag @@ -12,9 +12,12 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data basis_point_authority_note: String = "Permyriad / basis-point unity: one part per ten thousand (1 bp = 0.01 percentage points = 1/10000 of unity). ISO 80000-1 quantity 'factor, unitless' applied to the ten-thousandth submultiple (permyriad, ‱); financial reporting convention names the same scale 'basis point'." +// Permyriad / basis-point unity: one part per ten thousand (1 bp = 0.01 percentage points = 1/10000 +// of unity). ISO 80000-1 quantity 'factor, unitless' applied to the ten-thousandth submultiple +// (permyriad, ‱); financial reporting convention names the same scale 'basis point'. -data percent_unity_note: String = "Percent unity: parts per hundred (1% = 1/100 of unity). ISO 80000-1 quantity 'factor, unitless' applied to the hundredth submultiple." +// Percent unity: parts per hundred (1% = 1/100 of unity). ISO 80000-1 quantity 'factor, unitless' +// applied to the hundredth submultiple. fn parts_per_ten_thousand_unity_count() -> Nat { 10000 diff --git a/dag/extdeps/units/iec_80000_13.dag b/dag/extdeps/units/iec_80000_13.dag index 07ba476d37d..603a67021fb 100644 --- a/dag/extdeps/units/iec_80000_13.dag +++ b/dag/extdeps/units/iec_80000_13.dag @@ -12,7 +12,8 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data iec_80000_13_version_note: String = "IEC 80000-13:2008 (Quantities and units — Part 13: Information science and technology): binary prefixes (kibi = 2^10) and the octet (8 bits) as the information-unit byte." +// IEC 80000-13:2008 (Quantities and units — Part 13: Information science and technology): binary +// prefixes (kibi = 2^10) and the octet (8 bits) as the information-unit byte. fn iec_kibi_factor() -> Nat { 1024 diff --git a/dag/extdeps/units/iso8601.dag b/dag/extdeps/units/iso8601.dag index 7320d350713..7a7fbb29951 100644 --- a/dag/extdeps/units/iso8601.dag +++ b/dag/extdeps/units/iso8601.dag @@ -12,7 +12,8 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data iso8601_duration_units_note: String = "ISO 8601-1:2019 duration and time-unit vocabulary: 60 seconds per minute, 60 minutes per hour, 24 hours per day." +// ISO 8601-1:2019 duration and time-unit vocabulary: 60 seconds per minute, 60 minutes per hour, 24 +// hours per day. fn iso8601_seconds_per_minute() -> Nat { 60 diff --git a/dag/extdeps/units/iso_80000_3.dag b/dag/extdeps/units/iso_80000_3.dag index 374521ec99d..68b800dad39 100644 --- a/dag/extdeps/units/iso_80000_3.dag +++ b/dag/extdeps/units/iso_80000_3.dag @@ -44,9 +44,35 @@ data iso_80000_3_model_scope: ExternalModelScope = ExternalModelScope { data extdeps_model_scope: ExternalModelScope = iso_80000_3_model_scope -data iso_80000_3_version_note: String = "ISO 80000-3 (Quantities and units - Part 3: Space and time): the quantity definitions for length, area, volume, plane angle and solid angle. This module grounds ONLY the exact unit relations the spatial substrate needs; it is not a general physics authority, and no generic extdeps.physics hub is created for it. THE PIN, WITH ITS EVIDENCE STANDING STATED: base edition 2, ISO 80000-3:2019, published October 2019 (catalogue 64974), amended by ISO 80000-3:2019/Amd 1:2025 (catalogue 87100), which is why the amendment is a SECOND citation of this one subject rather than a second subject. HOW THAT WAS ESTABLISHED, and how it was not: iso.org returns HTTP 403 to every automated fetch from this environment, so the NORMATIVE TEXT HAS NOT BEEN READ HERE and no claim in this repository may rest on its exact wording. What was verified is the designation-to-catalogue mapping, from the iso.org page titles as indexed by search plus independent resellers agreeing on both ids. An earlier revision carried www.iso.org/standard/31887.html, which review reported is ISO 80000-2 (mathematical signs and symbols); a later one removed the locator entirely and left the edition unstated, on the ground that an unverifiable numeric id is worse than none. The correction to THAT is the distinction between a paywall and an unknown: a paywall lowers evidence standing, and a mutable search-results URL is not a terminal pin. The relations carried below are in any case not edition-sensitive - they are definitional and independently checkable (a turn is 360 degrees by definition of the degree; a square metre is the area of a square of side one metre)." - -data iso_80000_3_plane_angle_note: String = "Plane angle is dimensionless in the SI sense - it is a ratio of two lengths - but it is NOT interchangeable with an ordinary dimensionless count, and the substrate keeps it a distinct Quantity so that adding an angle to a length, or reading an angle as a bare ratio, has no operation. ISO 80000-3 defines the radian as the coherent unit. The degree, arcminute and arcsecond are non-SI units accepted for use with the SI - a status stated by the BIPM SI Brochure, which is a SEPARATE independently governed subject and therefore neither a citation of this scope nor modeled here (the extdeps.languages.bash.subject / POSIX distinction: multiple citations may ground one subject, but a second governing body's document is never a citation of the first). Nothing below depends on that status; the subdivisions are carried as exact rational fractions of the turn." +// ISO 80000-3 (Quantities and units - Part 3: Space and time): the quantity definitions for length, +// area, volume, plane angle and solid angle. This module grounds ONLY the exact unit relations the +// spatial substrate needs; it is not a general physics authority, and no generic extdeps.physics +// hub is created for it. THE PIN, WITH ITS EVIDENCE STANDING STATED: base edition 2, ISO +// 80000-3:2019, published October 2019 (catalogue 64974), amended by ISO 80000-3:2019/Amd 1:2025 +// (catalogue 87100), which is why the amendment is a SECOND citation of this one subject rather +// than a second subject. HOW THAT WAS ESTABLISHED, and how it was not: iso.org returns HTTP 403 to +// every automated fetch from this environment, so the NORMATIVE TEXT HAS NOT BEEN READ HERE and no +// claim in this repository may rest on its exact wording. What was verified is the +// designation-to-catalogue mapping, from the iso.org page titles as indexed by search plus +// independent resellers agreeing on both ids. An earlier revision carried +// www.iso.org/standard/31887.html, which review reported is ISO 80000-2 (mathematical signs and +// symbols); a later one removed the locator entirely and left the edition unstated, on the ground +// that an unverifiable numeric id is worse than none. The correction to THAT is the distinction +// between a paywall and an unknown: a paywall lowers evidence standing, and a mutable +// search-results URL is not a terminal pin. The relations carried below are in any case not +// edition-sensitive - they are definitional and independently checkable (a turn is 360 degrees by +// definition of the degree; a square metre is the area of a square of side one metre). + +// Plane angle is dimensionless in the SI sense - it is a ratio of two lengths - but it is NOT +// interchangeable with an ordinary dimensionless count, and the substrate keeps it a distinct +// Quantity so that adding an angle to a length, or reading an angle as a bare ratio, has no +// operation. ISO 80000-3 defines the radian as the coherent unit. The degree, arcminute and +// arcsecond are non-SI units accepted for use with the SI - a status stated by the BIPM SI +// Brochure, which is a SEPARATE independently governed subject and therefore neither a citation of +// this scope nor modeled here (the extdeps.languages.bash.subject / POSIX distinction: multiple +// citations may ground one subject, but a second governing body's document is never a citation of +// the first). Nothing below depends on that status; the subdivisions are carried as exact rational +// fractions of the turn. fn degrees_per_turn() -> Nat { 360 diff --git a/dag/extdeps/uri.dag b/dag/extdeps/uri.dag index 71f26ead1b6..b8268626332 100644 --- a/dag/extdeps/uri.dag +++ b/dag/extdeps/uri.dag @@ -118,7 +118,13 @@ fn parse_href_scheme(url: String) -> ParsedHrefScheme { } } -data uri_component_encode_authority_note: String = "RFC 3986 section 2.1 percent-encoding (https://www.rfc-editor.org/rfc/rfc3986#section-2.1) realized by extdeps.uri uri_percent_encode_* over UriUnicodeScalar / UriUtf8Octet. RFC 3986 section 3.3 path grammar is cited at extdeps.uri_path extdeps_external_authority_anchor — same specification, distinct section facts, no parallel section module. UriUnicodeScalar { cp } is the raw carrier; UriValidatedScalar is sole_constructor with admitted_cp: Int and is minted only by uri_validated_scalar_construction (scalar-range and surrogate checks live in that constructor). uri_percent_encode_admitted_scalar_wire accepts UriValidatedScalar only." +// RFC 3986 section 2.1 percent-encoding (https://www.rfc-editor.org/rfc/rfc3986#section-2.1) +// realized by extdeps.uri uri_percent_encode_* over UriUnicodeScalar / UriUtf8Octet. RFC 3986 +// section 3.3 path grammar is cited at extdeps.uri_path extdeps_external_authority_anchor — same +// specification, distinct section facts, no parallel section module. UriUnicodeScalar { cp } is the +// raw carrier; UriValidatedScalar is sole_constructor with admitted_cp: Int and is minted only by +// uri_validated_scalar_construction (scalar-range and surrogate checks live in that constructor). +// uri_percent_encode_admitted_scalar_wire accepts UriValidatedScalar only. type UriHexNibble = UriNibble0 diff --git a/dag/extdeps/uri_path.dag b/dag/extdeps/uri_path.dag index 560a1b5398c..984d73d9a10 100644 --- a/dag/extdeps/uri_path.dag +++ b/dag/extdeps/uri_path.dag @@ -130,7 +130,19 @@ type PathTemplateMatch = PathMatched { params: List } | PathNotMatched -data match_path_template_note: String = "The FORWARD reading of the same PathTemplate rows gunbc.node_http_server_emit.emit_path_template_regex_source reads BACKWARD into a JS regex, and extdeps.uri_path.render_path_template reads backward into a rendered path — three directions, one row set, never parallel authorities (DESIGN 4); the binding record is extdeps.uri_path.PathParamBinding, the single binding authority (a local duplicate here briefly shadowed it and broke no-import name resolution for test.claim.uri_path_parse_witness — the §3 nickname caught by execution). Parity contract with the emitted regex, kept strict so the two realizations agree on every input: one token = one path segment joined by '/' (LiteralToken == segment text; ParamToken binds any NON-EMPTY segment, the [^/]+ class); the empty template matches exactly the root path '/'; a trailing slash is a mismatch (the regex anchors with $); an interior empty segment ('//') never matches any token (a Literal is never empty by parse construction, a Param requires non-empty). Query strings are stripped before matching, mirroring parse_path_template's own '?' strip." +// The FORWARD reading of the same PathTemplate rows +// gunbc.node_http_server_emit.emit_path_template_regex_source reads BACKWARD into a JS regex, and +// extdeps.uri_path.render_path_template reads backward into a rendered path — three directions, one +// row set, never parallel authorities (DESIGN 4); the binding record is +// extdeps.uri_path.PathParamBinding, the single binding authority (a local duplicate here briefly +// shadowed it and broke no-import name resolution for test.claim.uri_path_parse_witness — the §3 +// nickname caught by execution). Parity contract with the emitted regex, kept strict so the two +// realizations agree on every input: one token = one path segment joined by '/' (LiteralToken == +// segment text; ParamToken binds any NON-EMPTY segment, the [^/]+ class); the empty template +// matches exactly the root path '/'; a trailing slash is a mismatch (the regex anchors with $); an +// interior empty segment ('//') never matches any token (a Literal is never empty by parse +// construction, a Param requires non-empty). Query strings are stripped before matching, mirroring +// parse_path_template's own '?' strip. fn match_path_segments(path_only: String) -> List { if path_only == "/" || path_only == "" { @@ -216,7 +228,12 @@ fn parse_path_template(raw: String) -> PathTemplateParseResult { } } -data uri_query_param_note: String = "Query-string parsing, the URI concern's single home (§3) — mirror of the '?' strip match_path_template/parse_path_template already do. uri_query_string returns everything after the first '?' (empty if none); uri_query_param returns the value for a key over the '&'-separated 'k=v' pairs (empty if the key is absent). Kept minimal: no percent-decoding yet (the one consumer, the /sandbox/echo status label, is an ASCII enum), so a decode pass is a named follow-on when a consumer needs it." +// Query-string parsing, the URI concern's single home (§3) — mirror of the '?' strip +// match_path_template/parse_path_template already do. uri_query_string returns everything after the +// first '?' (empty if none); uri_query_param returns the value for a key over the '&'-separated +// 'k=v' pairs (empty if the key is absent). Kept minimal: no percent-decoding yet (the one +// consumer, the /sandbox/echo status label, is an ASCII enum), so a decode pass is a named +// follow-on when a consumer needs it. fn uri_query_string(path: String) -> String { match path |> split(delimiter: "?") |> skip(1) |> first { diff --git a/dag/extdeps/uuid/uuid.dag b/dag/extdeps/uuid/uuid.dag index 8e2aa618f1a..c19466484b9 100644 --- a/dag/extdeps/uuid/uuid.dag +++ b/dag/extdeps/uuid/uuid.dag @@ -11,7 +11,20 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data uuid_note: String = "RFC 9562 (which obsoletes RFC 4122) defines the UUID and its canonical textual form: 32 hexadecimal digits in five hyphen-separated groups of 8-4-4-4-12, thirty-six characters total. It is an IETF concept, so a vendor field that happens to hold one is not thereby a vendor type — several unrelated dependencies in this tree carry account and session identifiers in exactly this shape, and each re-typing it as its own NonEmptyStr would be the same concept coined once per consumer.\\n\\nThe brand is what makes the shape claim checkable at all. A 36-character string is not a UUID; a 36-character string matching the canonical layout is. Section 4 also fixes the reading order — the textual form is big-endian, and the version and variant live at fixed positions within it — which is why a UUID must never be compared case-sensitively or reordered as bytes without consulting that section.\\n\\nDeliberately NOT modeled here: version extraction, variant bits, generation. This module exists so consumers can say a field IS a UUID rather than a string that looks like one; minting and inspecting are separate concerns that land with the consumer that needs them." +// RFC 9562 (obsoletes RFC 4122) defines the UUID and its canonical textual form: 32 hexadecimal +// digits in five hyphen-separated groups of 8-4-4-4-12, thirty-six characters. It is an IETF +// concept, so a vendor field holding one is not thereby a vendor type — several unrelated +// dependencies here carry account and session identifiers in exactly this shape, and re-typing each +// as its own NonEmptyStr would coin the concept once per consumer. +// +// The brand makes the shape claim checkable: a 36-character string is not a UUID; one matching the +// canonical layout is. Section 4 also fixes the reading order — big-endian textual form, version +// and variant at fixed positions — so a UUID must never be compared case-sensitively or reordered +// as bytes without consulting that section. +// +// Deliberately NOT modeled: version extraction, variant bits, generation. This module lets +// consumers say a field IS a UUID rather than a string that looks like one; minting and inspecting +// land with the consumer that needs them. type Uuid = NonEmptyStr where brand("Uuid") diff --git a/dag/extdeps/vendor/amphenol.dag b/dag/extdeps/vendor/amphenol.dag index bf2bbdb5e85..77a4ddd75cf 100644 --- a/dag/extdeps/vendor/amphenol.dag +++ b/dag/extdeps/vendor/amphenol.dag @@ -16,7 +16,18 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data amphenol_registrant_note: String = "ONE ENTITY, SEVERAL BRANDS, AND THE BRANDS DO NOT GET THEIR OWN ROWS. Distributors list the M.2 connector under Amphenol ICC, Amphenol FCI and Amphenol Communications Solutions, and the lane that dispatched this work named the vendor as Amphenol Communications Solutions. Those are business-unit brands rather than independently governed entities, and the read authority describes one corporation. So this row is the corporation and the brands are not modelled - which is the MIRROR of the Tyco decision in this same tree, where two rows were minted BECAUSE two documents named two entities with two registered addresses. The rule is the same in both directions: the entity count follows what the documents establish, not what the branding suggests. WHAT IS NOT ESTABLISHED: whether Amphenol Communications Solutions is a distinct legal entity from Amphenol Corporation. No document read here says so, and asserting either way would invent a corporate structure. A filing that establishes it would split this row exactly as Tyco split." +// ONE ENTITY, SEVERAL BRANDS, AND THE BRANDS DO NOT GET THEIR OWN ROWS. Distributors list the M.2 +// connector under Amphenol ICC, Amphenol FCI and Amphenol Communications Solutions, and the lane +// that dispatched this work named the vendor as Amphenol Communications Solutions. Those are +// business-unit brands rather than independently governed entities, and the read authority +// describes one corporation. So this row is the corporation and the brands are not modelled - which +// is the MIRROR of the Tyco decision in this same tree, where two rows were minted BECAUSE two +// documents named two entities with two registered addresses. The rule is the same in both +// directions: the entity count follows what the documents establish, not what the branding +// suggests. WHAT IS NOT ESTABLISHED: whether Amphenol Communications Solutions is a distinct legal +// entity from Amphenol Corporation. No document read here says so, and asserting either way would +// invent a corporate structure. A filing that establishes it would split this row exactly as Tyco +// split. data amphenol: Vendor = Vendor { legal_name: "Amphenol Corporation" } diff --git a/dag/extdeps/vendor/aspeed.dag b/dag/extdeps/vendor/aspeed.dag index 20dfa330cf9..2ffc09ad4da 100644 --- a/dag/extdeps/vendor/aspeed.dag +++ b/dag/extdeps/vendor/aspeed.dag @@ -17,7 +17,10 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data aspeed_registrant_note: String = "ASPEED Technology Inc. is cited from its corporate About page as the manufacturer of the AST2500 integrated remote-management processor family. The chip's mechanical and electrical facts live in extdeps.bmc.aspeed_ast2500 under the datasheet citation there; this module owns only the company entity." +// ASPEED Technology Inc. is cited from its corporate About page as the manufacturer of the AST2500 +// integrated remote-management processor family. The chip's mechanical and electrical facts live in +// extdeps.bmc.aspeed_ast2500 under the datasheet citation there; this module owns only the company +// entity. data aspeed: Vendor = Vendor { legal_name: "ASPEED Technology Inc." } diff --git a/dag/extdeps/vendor/lattice_semiconductor.dag b/dag/extdeps/vendor/lattice_semiconductor.dag index 030ee2d594d..5022c710ecc 100644 --- a/dag/extdeps/vendor/lattice_semiconductor.dag +++ b/dag/extdeps/vendor/lattice_semiconductor.dag @@ -30,7 +30,10 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { // on the filing cover page. Copying the normalised spelling would have made the citation look // tighter while making the fact less accurate, so the cited endpoint establishes WHICH registrant // this is and the cover-page spelling is what the name says. -data lattice_registrant_note: String = "extdeps.vendor.lattice_semiconductor cites EDGAR submissions for CIK 0000855658 to establish the registrant. The endpoint's own name field reads LATTICE SEMICONDUCTOR CORP, an index normalisation to upper case with a truncated suffix; legal_name carries the cover-page spelling instead, because the normalised form is a property of that index rather than of the company." +// extdeps.vendor.lattice_semiconductor cites EDGAR submissions for CIK 0000855658 to establish the +// registrant. The endpoint's own name field reads LATTICE SEMICONDUCTOR CORP, an index +// normalisation to upper case with a truncated suffix; legal_name carries the cover-page spelling +// instead, because the normalised form is a property of that index rather than of the company. data lattice_semiconductor_vendor: Vendor = Vendor { legal_name: "Lattice Semiconductor Corporation" } diff --git a/dag/extdeps/vendor/lotes.dag b/dag/extdeps/vendor/lotes.dag index 414c7cd9bfa..4940d4b7b89 100644 --- a/dag/extdeps/vendor/lotes.dag +++ b/dag/extdeps/vendor/lotes.dag @@ -24,7 +24,9 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data lotes_registrant_note: String = "Lotes Co., Ltd is cited from its corporate site as a manufacturer of sockets, connectors and loading mechanisms. This module owns only the company entity. The LGA4926 attachment part numbers attributed to it, and the strength of that attribution, live in extdeps.cpu_attachment.ilm4926." +// Lotes Co., Ltd is cited from its corporate site as a manufacturer of sockets, connectors and +// loading mechanisms. This module owns only the company entity. The LGA4926 attachment part numbers +// attributed to it, and the strength of that attribution, live in extdeps.cpu_attachment.ilm4926. data lotes: Vendor = Vendor { legal_name: "Lotes Co., Ltd" } diff --git a/dag/extdeps/vendor/macronix.dag b/dag/extdeps/vendor/macronix.dag index c789b402aa0..6eeacd47604 100644 --- a/dag/extdeps/vendor/macronix.dag +++ b/dag/extdeps/vendor/macronix.dag @@ -15,7 +15,10 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data macronix_registrant_note: String = "Read 2026-08-14: an integrated device manufacturer of non-volatile memory, headquartered in Taiwan. The entry also gives the name in Chinese characters, which is not carried here because this row's legal_name is a single field and choosing which script it holds would be a decision no document read here makes; a later reader adding a script-aware name carrier is grounding something this row leaves open rather than correcting it." +// Read 2026-08-14: an integrated device manufacturer of non-volatile memory, headquartered in +// Taiwan. The entry also gives the name in Chinese characters, not carried because legal_name is a +// single field and choosing its script is a decision no document read here makes; a later +// script-aware name carrier grounds what this row leaves open rather than correcting it. data macronix: Vendor = Vendor { legal_name: "Macronix International Co., Ltd." } diff --git a/dag/extdeps/vendor/microchip_technology.dag b/dag/extdeps/vendor/microchip_technology.dag index f266b449503..1b218870a49 100644 --- a/dag/extdeps/vendor/microchip_technology.dag +++ b/dag/extdeps/vendor/microchip_technology.dag @@ -15,7 +15,11 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data microchip_technology_registrant_note: String = "Read 2026-08-14: an American publicly traded semiconductor corporation headquartered in Chandler, Arizona. The stronger authority for a registrant name is its own filing and this corpus reaches EDGAR directly for the SCM issuer rows; data.sec.gov refused every fetch performed from this session with HTTP 403, and microchip.com's own legal-information page returned HTTP 403 as well, so the weaker-but-read authority is cited rather than a stronger one asserted unread." +// Read 2026-08-14: an American publicly traded semiconductor corporation headquartered in Chandler, +// Arizona. The stronger authority for a registrant name is its own filing and this corpus reaches +// EDGAR directly for the SCM issuer rows; data.sec.gov refused every fetch performed from this +// session with HTTP 403, and microchip.com's own legal-information page returned HTTP 403 as well, +// so the weaker-but-read authority is cited rather than a stronger one asserted unread. data microchip_technology: Vendor = Vendor { legal_name: "Microchip Technology Incorporated" } diff --git a/dag/extdeps/vendor/samtec.dag b/dag/extdeps/vendor/samtec.dag index 215a13917a9..30e4f9fb144 100644 --- a/dag/extdeps/vendor/samtec.dag +++ b/dag/extdeps/vendor/samtec.dag @@ -16,7 +16,15 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data samtec_registrant_note: String = "THE REGISTRY RENDERS IT SAMTEC INC AND THAT SPELLING IS NOT WHAT IS RECORDED. Read 2026-08-14: a corporate registry aggregator's entry for the Oregon Secretary of State registration, giving entity name SAMTEC INC, type Foreign Business Corporation, home jurisdiction Indiana, registered address 520 Park East Blvd, New Albany, IN 47150. Registry indexes normalise to upper case and strip punctuation - the same artefact this corpus already recorded for the EDGAR submissions endpoint on the Lattice row - so legal_name carries the conventional spelling with its comma. WHAT IS NOT ESTABLISHED: the punctuated form was not read from a filing cover page or from the company's own materials, both of which refused this session's fetches, so the comma is convention rather than citation. This is a weaker grounding than the rest of the vendor rows and is recorded as such rather than smoothed over." +// THE REGISTRY RENDERS IT SAMTEC INC AND THAT SPELLING IS NOT WHAT IS RECORDED. Read 2026-08-14: a +// corporate registry aggregator's entry for the Oregon Secretary of State registration gives entity +// name SAMTEC INC, type Foreign Business Corporation, home jurisdiction Indiana, registered address +// 520 Park East Blvd, New Albany, IN 47150. Registry indexes normalise to upper case and strip +// punctuation - the artefact this corpus already recorded for the EDGAR submissions endpoint on the +// Lattice row - so legal_name carries the conventional spelling with its comma. WHAT IS NOT +// ESTABLISHED: the punctuated form was not read from a filing cover page or the company's own +// materials, both of which refused this session's fetches, so the comma is convention rather than +// citation — weaker grounding than the rest of the vendor rows, recorded as such. data samtec: Vendor = Vendor { legal_name: "Samtec, Inc." } diff --git a/dag/extdeps/vendor/texas_instruments.dag b/dag/extdeps/vendor/texas_instruments.dag index 4ae924e92a5..d3d7362827c 100644 --- a/dag/extdeps/vendor/texas_instruments.dag +++ b/dag/extdeps/vendor/texas_instruments.dag @@ -20,7 +20,12 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data texas_instruments_registrant_note: String = "extdeps.vendor.texas_instruments cites the vendor's own terms of use, whose scope clause reads 'Texas Instruments Incorporated, 12500 TI Boulevard, Dallas, Texas 75243, USA and its subsidiaries'. The stronger authority for a registrant's legal name is its own filing, and this repository already reaches EDGAR directly for the SCM issuer rows; both data.sec.gov and the EDGAR browse endpoint refused the fetch performed while authoring this module with HTTP 403, so the weaker-but-read authority is cited rather than a stronger one asserted unread." +// extdeps.vendor.texas_instruments cites the vendor's own terms of use, whose scope clause reads +// 'Texas Instruments Incorporated, 12500 TI Boulevard, Dallas, Texas 75243, USA and its +// subsidiaries'. The stronger authority for a registrant's legal name is its own filing, and this +// repository already reaches EDGAR directly for the SCM issuer rows; both data.sec.gov and the +// EDGAR browse endpoint refused the fetch performed while authoring this module with HTTP 403, so +// the weaker-but-read authority is cited rather than a stronger one asserted unread. data texas_instruments: Vendor = Vendor { legal_name: "Texas Instruments Incorporated" } diff --git a/dag/extdeps/vendor/the_wand_company.dag b/dag/extdeps/vendor/the_wand_company.dag index f5885a683be..1ea3eb518ff 100644 --- a/dag/extdeps/vendor/the_wand_company.dag +++ b/dag/extdeps/vendor/the_wand_company.dag @@ -22,7 +22,11 @@ data issuing_party_compliance_authority: ExternalAuthority = ExternalAuthority { data the_wand_company: Vendor = Vendor { legal_name: "The Wand Company Limited" } -// The registered form is grounded, and the grounding arrived by fixing a read path rather than by finding a new source. An earlier revision carried the bare trading name and declared that no source stated a suffix — true only because the manuals were unreadable to a refused fetcher. Both product manuals carry the same compliance block: 'Issuing Party: The Wand Company Limited, 3 Laundress Lane, Cambridge CB2 1SD, UK'. The address is deliberately not modeled: no consumer needs it, and a registered office is a fact with its own lifecycle. +// The registered form is grounded by fixing a read path, not a new source: an earlier revision +// carried the bare trading name and declared no source stated a suffix — true only because the +// manuals were unreadable to a refused fetcher. Both product manuals carry the compliance block +// 'Issuing Party: The Wand Company Limited, 3 Laundress Lane, Cambridge CB2 1SD, UK'. The address is +// deliberately not modeled: no consumer needs it, and a registered office has its own lifecycle. data extdeps_model_scope: ExternalModelScope = ExternalModelScope { subject: ExternalSubjectRef { declaration: DeclarationRef { diff --git a/dag/extdeps/vendor/tyco_electronics.dag b/dag/extdeps/vendor/tyco_electronics.dag index 7661ef079f1..542a09f3d18 100644 --- a/dag/extdeps/vendor/tyco_electronics.dag +++ b/dag/extdeps/vendor/tyco_electronics.dag @@ -16,7 +16,15 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data tyco_electronics_registrant_note: String = "THE NAME RECORDED IS THE ONE THE READ DOCUMENT CARRIES, AND THE UNCERTAINTY IS RECORDED WITH IT. The application specification's copyright line reads 'Tyco Electronics Corporation, Harrisburg, PA', while the same page states that the TE logo and TE (Tyco Electronics) are trademarks — so the document distinguishes an entity from a brand and this row takes the entity. What is NOT established is whether that corporation is the registrant a filing would name for the group trading as TE Connectivity; both the SEC submissions endpoint and the vendor's own terms-of-use page refused the fetches performed while authoring this module, with HTTP 403 in each case. A later reader establishing the group-level registrant is grounding a fact this row leaves open, not correcting one it asserted." +// THE NAME RECORDED IS THE ONE THE READ DOCUMENT CARRIES, AND THE UNCERTAINTY IS RECORDED WITH IT. +// The application specification's copyright line reads 'Tyco Electronics Corporation, Harrisburg, +// PA', while the same page states that the TE logo and TE (Tyco Electronics) are trademarks — so +// the document distinguishes an entity from a brand and this row takes the entity. What is NOT +// established is whether that corporation is the registrant a filing would name for the group +// trading as TE Connectivity; both the SEC submissions endpoint and the vendor's own terms-of-use +// page refused the fetches performed while authoring this module, with HTTP 403 in each case. A +// later reader establishing the group-level registrant is grounding a fact this row leaves open, +// not correcting one it asserted. data tyco_electronics: Vendor = Vendor { legal_name: "Tyco Electronics Corporation" } diff --git a/dag/extdeps/vendor/tyco_electronics_amp_kk.dag b/dag/extdeps/vendor/tyco_electronics_amp_kk.dag index 09ea56b4bac..cf625477c2a 100644 --- a/dag/extdeps/vendor/tyco_electronics_amp_kk.dag +++ b/dag/extdeps/vendor/tyco_electronics_amp_kk.dag @@ -16,7 +16,14 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } -data tyco_electronics_amp_kk_note: String = "A SEPARATE ROW FROM extdeps.vendor.tyco_electronics, AND DELIBERATELY SO. Two application specifications read on the same day carry two different entities in their headers: the DDR4 socket document is issued by Tyco Electronics Corporation of Harrisburg, Pennsylvania, and the M.2 document by Tyco Electronics AMP K.K. of Kawasaki, Japan, with its own registered address. They trade under one brand and it would have been easy to fold them into one vendor row, which is exactly what DESIGN section 3 forbids: independently governed entities inhabiting a shared brand are not one authority, and merging them would make a correction to either one edit the other. What is NOT established here is the relationship between them - parent, subsidiary, or successor - because no document read establishes it, and asserting one would be inventing a corporate structure to tidy a roster." +// A SEPARATE ROW FROM extdeps.vendor.tyco_electronics, DELIBERATELY. Two application +// specifications read the same day carry different header entities: the DDR4 socket document is +// issued by Tyco Electronics Corporation of Harrisburg, Pennsylvania, the M.2 document by Tyco +// Electronics AMP K.K. of Kawasaki, Japan, with its own registered address. One brand, but folding +// them into one vendor row is what DESIGN section 3 forbids: independently governed entities under +// a shared brand are not one authority, and a correction to either would edit the other. NOT +// established: their relationship - parent, subsidiary, or successor - because no document read +// establishes it; asserting one would invent corporate structure to tidy a roster. data tyco_electronics_amp_kk: Vendor = Vendor { legal_name: "Tyco Electronics AMP K.K." } diff --git a/dag/extdeps/w3c/accelerometer.dag b/dag/extdeps/w3c/accelerometer.dag index 527c67999f1..8529ce4d3c6 100644 --- a/dag/extdeps/w3c/accelerometer.dag +++ b/dag/extdeps/w3c/accelerometer.dag @@ -37,7 +37,11 @@ That is the upstream telling us the cross-engine route is Device Orientation and data maintained_for_existing_deployments: Bool = true data upstream_recommends_device_orientation_for_new_projects: Bool = true -data interface_family_note: String = "Three interfaces, and the inheritance is load-bearing rather than incidental: LinearAccelerationSensor and GravitySensor both EXTEND Accelerometer in the IDL, so all three expose the same three nullable axes and differ only in what the reading means. That difference is entirely about gravity, which is why a consumer choosing among them is really declaring how it wants gravity handled." +// Three interfaces, and the inheritance is load-bearing rather than incidental: +// LinearAccelerationSensor and GravitySensor both EXTEND Accelerometer in the IDL, so all three +// expose the same three nullable axes and differ only in what the reading means. That difference is +// entirely about gravity, which is why a consumer choosing among them is really declaring how it +// wants gravity handled. type AccelerometerInterface = AccelerometerSensor @@ -77,7 +81,10 @@ fn interface_suits_a_knock_detector(i: AccelerometerInterface) -> Bool { } } -data axes_are_nullable_note: String = "All three interfaces declare 'readonly attribute double? x' and likewise y and z — nullable, exactly as the Device Orientation and Motion event fields are. So the event-versus-value distinction survives on this route too: a reading can arrive with every axis null, and a consumer counting readings is counting something weaker than values." +// All three interfaces declare 'readonly attribute double? x' and likewise y and z — nullable, +// exactly as the Device Orientation and Motion event fields are. So the event-versus-value +// distinction survives on this route too: a reading can arrive with every axis null, and a consumer +// counting readings is counting something weaker than values. data axis_x_property: String = "x" data axis_y_property: String = "y" @@ -85,7 +92,10 @@ data axis_z_property: String = "z" data axis_count: Int = 3 data axes_are_nullable: Bool = true -data reference_frame_note: String = "The spec's AccelerometerLocalCoordinateSystem enum, defaulting to device. It matters for a chassis knock because the device frame is fixed to the hardware while the screen frame rotates with the display, so a knock's axis decomposition differs between them; the default is the one that describes the physical object being struck." +// The spec's AccelerometerLocalCoordinateSystem enum, defaulting to device. It matters for a +// chassis knock because the device frame is fixed to the hardware while the screen frame rotates +// with the display, so a knock's axis decomposition differs between them; the default is the one +// that describes the physical object being struck. type AccelerometerReferenceFrame = DeviceFrame @@ -100,7 +110,11 @@ fn reference_frame_wire(f: AccelerometerReferenceFrame) -> String { data default_reference_frame: String = "device" -data policy_token_note: String = "THE TOKEN IS CITED, AND ITS HOME IS NOT THIS SPECIFICATION. The sensor permission name and sensor feature name are both 'accelerometer', and this spec takes that token as defined in Device Orientation and Motion. So the same string grounds both web routes from a single upstream home, which is why extdeps.w3c.device_orientation_and_motion carries it and this module references the relationship rather than minting a second copy (DESIGN §3)." +// THE TOKEN IS CITED, AND ITS HOME IS NOT THIS SPECIFICATION. The sensor permission name and sensor +// feature name are both 'accelerometer', and this spec takes that token as defined in Device +// Orientation and Motion. So the same string grounds both web routes from a single upstream home, +// which is why extdeps.w3c.device_orientation_and_motion carries it and this module references the +// relationship rather than minting a second copy (DESIGN §3). data accelerometer_permission_name: String = "accelerometer" diff --git a/dag/extdeps/w3c/device_orientation_and_motion.dag b/dag/extdeps/w3c/device_orientation_and_motion.dag index 6ea680b2f8f..c171eafba9a 100644 --- a/dag/extdeps/w3c/device_orientation_and_motion.dag +++ b/dag/extdeps/w3c/device_orientation_and_motion.dag @@ -24,7 +24,14 @@ data extdeps_model_scope: ExternalModelScope = ExternalModelScope { further_citations: [] } -data device_orientation_and_motion_note: String = "Cited upstream: W3C Device Orientation and Motion, Candidate Recommendation Draft, 12 February 2025 (Devices and Sensors WG + Web Applications WG). This models the BOUNDED grain a motion consumer needs — the event type strings, the interface names, the nullability the WebIDL actually declares, the dispatch precondition, the permission surface, and the units — keeping the upstream spellings so a consumer projects a modeled fact rather than a hand-spelled string (DESIGN §3). It contains NO browser and NO platform behavior: what Chrome or Safari or macOS does with this contract is a separate execution subject and belongs downstream, never here (DESIGN §3 external upstream decomposition — a Chrome change must not require editing this file)." +// Cited upstream: W3C Device Orientation and Motion, Candidate Recommendation Draft, 12 February +// 2025 (Devices and Sensors WG + Web Applications WG). This models the BOUNDED grain a motion +// consumer needs — the event type strings, the interface names, the nullability the WebIDL actually +// declares, the dispatch precondition, the permission surface, and the units — keeping the upstream +// spellings so a consumer projects a modeled fact rather than a hand-spelled string (DESIGN §3). It +// contains NO browser and NO platform behavior: what Chrome or Safari or macOS does with this +// contract is a separate execution subject and belongs downstream, never here (DESIGN §3 external +// upstream decomposition — a Chrome change must not require editing this file). data spec_edition: String = "Candidate Recommendation Draft, 12 February 2025" @@ -39,7 +46,13 @@ data device_motion_event_rotation_rate_interface: String = "DeviceMotionEventRot data device_motion_acceleration_property: String = "acceleration" data device_motion_acceleration_including_gravity_property: String = "accelerationIncludingGravity" -data acceleration_axis_property_note: String = "The three axis attribute names DeviceMotionEventAcceleration declares, carried as data because a consumer reading these fields off a live event needs the names and would otherwise hard-code them at the reading site. They are cited from this specification's own WebIDL, quoted in nullable_motion_fields_note above; the identically spelled attributes on the Accelerometer extension's interfaces are that specification's facts and live in extdeps.w3c.accelerometer, because two independently versioned upstreams that happen to agree on a letter are not one authority." +// The three axis attribute names DeviceMotionEventAcceleration declares, carried as data because a +// consumer reading these fields off a live event needs the names and would otherwise hard-code them +// at the reading site. They are cited from this specification's own WebIDL, quoted in +// nullable_motion_fields_note above; the identically spelled attributes on the Accelerometer +// extension's interfaces are that specification's facts and live in extdeps.w3c.accelerometer, +// because two independently versioned upstreams that happen to agree on a letter are not one +// authority. data device_motion_axis_x_property: String = "x" data device_motion_axis_y_property: String = "y" @@ -49,17 +62,33 @@ data device_motion_rotation_rate_property: String = "rotationRate" data device_motion_interval_property: String = "interval" data request_permission_method: String = "requestPermission" -data secure_context_only_note: String = "The spec marks the interfaces [SecureContext]: DeviceMotionEvent, DeviceMotionEventAcceleration and DeviceMotionEventRotationRate, and the Window event handlers, are available only in a secure context. So an insecure origin is a distinct reason for absence from an unsupported engine, and a consumer that does not record which one it was has not observed the difference." +// The spec marks the interfaces [SecureContext]: DeviceMotionEvent, DeviceMotionEventAcceleration +// and DeviceMotionEventRotationRate, and the Window event handlers, are available only in a secure +// context. So an insecure origin is a distinct reason for absence from an unsupported engine, and a +// consumer that does not record which one it was has not observed the difference. data interfaces_are_secure_context_only: Bool = true -data nullable_motion_fields_note: String = "THE NULLABILITY IS THE SPEC'S, NOT OUR MODELING CHOICE, AND IT IS WHY A FIRED EVENT PROVES NOTHING ON ITS OWN. The WebIDL declares DeviceMotionEventAcceleration as `readonly attribute double? x` (likewise y, z) and DeviceMotionEventRotationRate as `double? alpha, beta, gamma`; DeviceMotionEvent itself declares `DeviceMotionEventAcceleration? acceleration` — nullable at BOTH grains, the struct and each axis. Only `double interval` is non-nullable. A consumer that counts events therefore counts something weaker than readings: an arriving event may carry no value at all, so events-seen and values-seen are two different observations and collapsing them loses the distinction the IDL went to the trouble of expressing." +// THE NULLABILITY IS THE SPEC'S, NOT OUR MODELING CHOICE, AND IT IS WHY A FIRED EVENT PROVES +// NOTHING ON ITS OWN. The WebIDL declares DeviceMotionEventAcceleration as `readonly attribute +// double? x` (likewise y, z) and DeviceMotionEventRotationRate as `double? alpha, beta, gamma`; +// DeviceMotionEvent itself declares `DeviceMotionEventAcceleration? acceleration` — nullable at +// BOTH grains, the struct and each axis. Only `double interval` is non-nullable. A consumer that +// counts events therefore counts something weaker than readings: an arriving event may carry no +// value at all, so events-seen and values-seen are two different observations and collapsing them +// loses the distinction the IDL went to the trouble of expressing. type MotionAxisReading = AxisNull | AxisValue { milli_units: Int } -data axis_reading_carrier_note: String = "The spec's double is carried as integral milli-units because the substrate's arithmetic here is integral and a fractional literal would be a second representation of one magnitude — the extdeps.web_audio micros precedent. The unit is the spec's own per field (m/s^2 for acceleration axes, deg/s for rotation-rate axes), named by the reading's position rather than restated per value. AxisNull is the IDL's null and is NOT zero: a null axis means no value was supplied, a zero axis means the device measured zero, and a carrier that rendered both as 0 would erase exactly the distinction this module exists to preserve." +// The spec's double is carried as integral milli-units because the substrate's arithmetic here is +// integral and a fractional literal would be a second representation of one magnitude — the +// extdeps.web_audio micros precedent. The unit is the spec's own per field (m/s^2 for acceleration +// axes, deg/s for rotation-rate axes), named by the reading's position rather than restated per +// value. AxisNull is the IDL's null and is NOT zero: a null axis means no value was supplied, a +// zero axis means the device measured zero, and a carrier that rendered both as 0 would erase +// exactly the distinction this module exists to preserve. fn axis_has_value(r: MotionAxisReading) -> Bool { match r { @@ -78,11 +107,28 @@ fn acceleration_carries_any_value(a: DeviceMotionAcceleration) -> Bool { axis_has_value(r: a.x) || axis_has_value(r: a.y) || axis_has_value(r: a.z) } -data hollow_event_emission_is_advisory_note: String = "SHOULD, NOT MUST — AND THIS IS THE SENTENCE A CAPABILITY PROBE MOST EASILY MISREADS. The spec says: 'If an implementation can never provide motion information, the event should be fired with the acceleration, accelerationIncludingGravity and rotationRate attributes set to null.' That is advisory. An implementation that can never provide motion information is NOT required to fire hollow events, and nothing in the spec requires it to keep firing them. Therefore observing ZERO events does not establish that hardware is absent, and observing zero events does not distinguish among: the frame was blocked by Permissions Policy, permission was denied, the document was not eligible for readings, the engine's backend was unavailable, the hardware was unavailable, the implementation simply declined to emit hollow events, or the observation window was too short. A consumer that derives 'no sensor' from 'no events' has converted could-not-observe into nothing-is-there — the empty-observation narrow — and this module refuses to supply that inference." +// SHOULD, NOT MUST — AND THIS IS THE SENTENCE A CAPABILITY PROBE MOST EASILY MISREADS. The spec +// says: 'If an implementation can never provide motion information, the event should be fired with +// the acceleration, accelerationIncludingGravity and rotationRate attributes set to null.' That is +// advisory. An implementation that can never provide motion information is NOT required to fire +// hollow events, and nothing in the spec requires it to keep firing them. Therefore observing ZERO +// events does not establish that hardware is absent, and observing zero events does not distinguish +// among: the frame was blocked by Permissions Policy, permission was denied, the document was not +// eligible for readings, the engine's backend was unavailable, the hardware was unavailable, the +// implementation simply declined to emit hollow events, or the observation window was too short. A +// consumer that derives 'no sensor' from 'no events' has converted could-not-observe into +// nothing-is-there — the empty-observation narrow — and this module refuses to supply that +// inference. data hollow_event_emission_is_required: Bool = false -data dispatch_precondition_note: String = "The precondition that makes silence ambiguous by construction. The spec states: 'The devicemotion event is only dispatched if the accelerometer and gyroscope permissions are granted.' BOTH are required, so a granted accelerometer alone dispatches nothing, and the absence of events is jointly explained by two independent permission facts before hardware is even reached. These two token names are cited HERE, from this specification's own text, rather than from the Generic Sensor base spec — which defines no feature tokens of its own (see extdeps.w3c.generic_sensor policy_tokens_are_extension_defined_note)." +// The precondition that makes silence ambiguous by construction. The spec states: 'The devicemotion +// event is only dispatched if the accelerometer and gyroscope permissions are granted.' BOTH are +// required, so a granted accelerometer alone dispatches nothing, and the absence of events is +// jointly explained by two independent permission facts before hardware is even reached. These two +// token names are cited HERE, from this specification's own text, rather than from the Generic +// Sensor base spec — which defines no feature tokens of its own (see extdeps.w3c.generic_sensor +// policy_tokens_are_extension_defined_note). type DeviceMotionDispatchPrecondition { accelerometer_permission_granted: Bool @@ -96,7 +142,12 @@ fn devicemotion_may_be_dispatched(p: DeviceMotionDispatchPrecondition) -> Bool { data device_motion_accelerometer_permission_name: String = "accelerometer" data gyroscope_permission_name: String = "gyroscope" -data permission_state_note: String = "The spec's own PermissionState enum, returned by the static requestPermission() declared on BOTH DeviceOrientationEvent and DeviceMotionEvent as `static Promise requestPermission()`. Recorded because a consumer must distinguish a denied answer from an unasked question, and because an engine that does not expose the method has not thereby made permission unnecessary — it has only made the spec's asking route unavailable, which is a fact about that engine and belongs in its own module." +// The spec's own PermissionState enum, returned by the static requestPermission() declared on BOTH +// DeviceOrientationEvent and DeviceMotionEvent as `static Promise +// requestPermission()`. Recorded because a consumer must distinguish a denied answer from an +// unasked question, and because an engine that does not expose the method has not thereby made +// permission unnecessary — it has only made the spec's asking route unavailable, which is a fact +// about that engine and belongs in its own module. type PermissionState = PermissionGranted diff --git a/dag/extdeps/w3c/generic_sensor.dag b/dag/extdeps/w3c/generic_sensor.dag index f6f82dcd3a0..1a44851517b 100644 --- a/dag/extdeps/w3c/generic_sensor.dag +++ b/dag/extdeps/w3c/generic_sensor.dag @@ -24,7 +24,13 @@ data extdeps_model_scope: ExternalModelScope = ExternalModelScope { further_citations: [] } -data generic_sensor_note: String = "Cited upstream: W3C Generic Sensor API, Candidate Recommendation Draft, 14 May 2026. This models the BOUNDED grain a motion consumer needs — the Sensor interface surface, the three lifecycle states, the exact error taxonomy with each error's own triggering condition, and the specification's own warning about feature detection. It contains NO browser and NO platform behavior: whether a particular engine can connect to a particular machine's hardware is an execution fact about that engine and that host, and it belongs downstream (DESIGN §3 external upstream decomposition)." +// Cited upstream: W3C Generic Sensor API, Candidate Recommendation Draft, 14 May 2026. This models +// the BOUNDED grain a motion consumer needs — the Sensor interface surface, the three lifecycle +// states, the exact error taxonomy with each error's own triggering condition, and the +// specification's own warning about feature detection. It contains NO browser and NO platform +// behavior: whether a particular engine can connect to a particular machine's hardware is an +// execution fact about that engine and that host, and it belongs downstream (DESIGN §3 external +// upstream decomposition). data spec_edition: String = "Candidate Recommendation Draft, 14 May 2026" @@ -44,11 +50,25 @@ data generic_sensor_interfaces_are_secure_context_only: Bool = true data secure_context_note: String = "The IDL declares [SecureContext, Exposed=(DedicatedWorker, Window)] and the spec states that all interfaces defined by it or by its extension specifications are only available within a secure context." -data construction_is_not_availability_note: String = "THE SPECIFICATION ITSELF WARNS AGAINST THE INFERENCE, WHICH IS WHY THIS MODULE REFUSES TO SUPPORT IT. Quoting the spec on feature detection: 'They do not tell you anything, however, about whether that API is actually connected to a real hardware sensor, whether that sensor works, if it is still connected, or even whether the user is going to allow you to access it.' So the presence of a constructor on the global object is exposure of an interface and NOTHING MORE — not hardware, not permission, not connectivity. The spec's recommended posture is the one this module encodes: combine feature detection with construction, with start(), and with handling every error the taxonomy below can produce. A probe that enumerates constructor names and stops has performed the exact non-observation the upstream calls out; reaching any conclusion about a machine requires actually running the lifecycle." +// THE SPECIFICATION ITSELF WARNS AGAINST THE INFERENCE, WHICH IS WHY THIS MODULE REFUSES TO SUPPORT +// IT. Quoting the spec on feature detection: 'They do not tell you anything, however, about whether +// that API is actually connected to a real hardware sensor, whether that sensor works, if it is +// still connected, or even whether the user is going to allow you to access it.' So the presence of +// a constructor on the global object is exposure of an interface and NOTHING MORE — not hardware, +// not permission, not connectivity. The spec's recommended posture is the one this module encodes: +// combine feature detection with construction, with start(), and with handling every error the +// taxonomy below can produce. A probe that enumerates constructor names and stops has performed the +// exact non-observation the upstream calls out; reaching any conclusion about a machine requires +// actually running the lifecycle. data feature_detection_alone_establishes_availability: Bool = false -data lifecycle_note: String = "The three states the spec defines, and the transition start() drives: idle -> (start()) -> activating -> activated, and stop() returns to idle. Activation is asynchronous and can fail after the state has already left idle, which is why activating is a state a consumer can genuinely observe and report rather than an instant that can be assumed away. A sensor that reaches activated has connected; it has not necessarily produced a reading, which is a further, separate fact carried by hasReading." +// The three states the spec defines, and the transition start() drives: idle -> (start()) -> +// activating -> activated, and stop() returns to idle. Activation is asynchronous and can fail +// after the state has already left idle, which is why activating is a state a consumer can +// genuinely observe and report rather than an instant that can be assumed away. A sensor that +// reaches activated has connected; it has not necessarily produced a reading, which is a further, +// separate fact carried by hasReading. type SensorLifecycleState = SensorIdle @@ -71,9 +91,20 @@ fn sensor_is_connected(s: SensorLifecycleState) -> Bool { } } -data activated_is_not_reading_note: String = "activated and hasReading are separate attributes in the IDL and a consumer must keep them separate. A sensor can be connected and never deliver a value within the observation window; reporting that as either a failure or a success is a fabrication in one direction or the other, so it gets its own outcome." - -data sensor_error_taxonomy_note: String = "THE ERROR NAMES CARRY THE DIAGNOSIS, AND THIS IS THE WHOLE REASON TO RUN start(). Each name in the spec has its own triggering condition, so an executed lifecycle answers WHICH obstacle stood in the way rather than returning a bare failure: SecurityError means sensor construction was blocked by the Permissions Policy; NotAllowedError means permission was denied via the Permissions API during start(); NotReadableError means the implementation could not connect to the sensor hardware during start(); ReferenceError means the sensor is not supported by the user agent; NotSupportedError means unsupported sensor options were passed to the constructor. A consumer that never starts the sensor learns none of these and can only report that it did not look." +// activated and hasReading are separate attributes in the IDL and a consumer must keep them +// separate. A sensor can be connected and never deliver a value within the observation window; +// reporting that as either a failure or a success is a fabrication in one direction or the other, +// so it gets its own outcome. + +// THE ERROR NAMES CARRY THE DIAGNOSIS, AND THIS IS THE WHOLE REASON TO RUN start(). Each name in +// the spec has its own triggering condition, so an executed lifecycle answers WHICH obstacle stood +// in the way rather than returning a bare failure: SecurityError means sensor construction was +// blocked by the Permissions Policy; NotAllowedError means permission was denied via the +// Permissions API during start(); NotReadableError means the implementation could not connect to +// the sensor hardware during start(); ReferenceError means the sensor is not supported by the user +// agent; NotSupportedError means unsupported sensor options were passed to the constructor. A +// consumer that never starts the sensor learns none of these and can only report that it did not +// look. type SensorAccessOutcome = SensorConstructionBlockedByPolicy @@ -96,7 +127,11 @@ fn sensor_access_outcome_key(o: SensorAccessOutcome) -> String { } } -data sensor_access_outcome_exception_note: String = "The upstream exception name each outcome corresponds to, kept as the spec spells it so a consumer surfacing a caught DOMException maps it rather than re-coining it. SensorActivatedWithoutReading and SensorReadingDelivered correspond to no exception: they are the two ways a lifecycle that threw nothing can still end differently, and having them in the same coproduct as the failures is what stops a consumer from treating did-not-throw as success." +// The upstream exception name each outcome corresponds to, kept as the spec spells it so a consumer +// surfacing a caught DOMException maps it rather than re-coining it. SensorActivatedWithoutReading +// and SensorReadingDelivered correspond to no exception: they are the two ways a lifecycle that +// threw nothing can still end differently, and having them in the same coproduct as the failures is +// what stops a consumer from treating did-not-throw as success. fn sensor_access_outcome_exception_name(o: SensorAccessOutcome) -> String { match o { @@ -122,6 +157,15 @@ fn sensor_access_outcome_delivered_a_value(o: SensorAccessOutcome) -> Bool { } } -data policy_tokens_are_extension_defined_note: String = "A HONEST ABSENCE, RECORDED SO NOBODY FILLS IT IN FROM MEMORY. The base Generic Sensor specification defines NO concrete policy-controlled feature tokens. It requires each extension specification to define 'a non-empty ordered set of associated policy-controlled feature tokens referred to as sensor feature names', and states only that 'sensor readings are only available for the documents which are allowed to use the policy-controlled features for the given sensor type'. So the token strings accelerometer and gyroscope are NOT cited from this module — they are cited from extdeps.w3c.device_orientation_and_motion, whose own text names them for the devicemotion dispatch precondition. Anything that needs the Accelerometer or Gyroscope extension specifications as an authority must cite those specifications, in their own module, rather than attributing their tokens to this one." +// A HONEST ABSENCE, RECORDED SO NOBODY FILLS IT IN FROM MEMORY. The base Generic Sensor +// specification defines NO concrete policy-controlled feature tokens. It requires each extension +// specification to define 'a non-empty ordered set of associated policy-controlled feature tokens +// referred to as sensor feature names', and states only that 'sensor readings are only available +// for the documents which are allowed to use the policy-controlled features for the given sensor +// type'. So the token strings accelerometer and gyroscope are NOT cited from this module — they are +// cited from extdeps.w3c.device_orientation_and_motion, whose own text names them for the +// devicemotion dispatch precondition. Anything that needs the Accelerometer or Gyroscope extension +// specifications as an authority must cite those specifications, in their own module, rather than +// attributing their tokens to this one. data base_spec_defines_feature_tokens: Bool = false diff --git a/dag/gunbc/apply.dag b/dag/gunbc/apply.dag index bd7e231aa0b..70f6214ac32 100644 --- a/dag/gunbc/apply.dag +++ b/dag/gunbc/apply.dag @@ -13,7 +13,10 @@ import gunbc.runner_capacity_plan { import gunbc.fleet_runner_connectivity { organization_runner_roster_evidence } import gunbc.runner_capacity_realize { runner_capacity_apply_wet_exit } -data apply_dispatcher_note: String = "TOP-LEVEL gunbc.apply dispatcher. gunbc.live_deploy.apply remains the srv1 dashboard child workflow only. RunnerCapacity plan half is PR A (#7939 runner_capacity_plan); ADD execution routes through gunbc.runner_capacity_realize (PR C) into shared HostEffect arms from PR B (#7946)." +// TOP-LEVEL gunbc.apply dispatcher. gunbc.live_deploy.apply remains the srv1 dashboard child +// workflow only. RunnerCapacity plan half is PR A (#7939 runner_capacity_plan); ADD execution +// routes through gunbc.runner_capacity_realize (PR C) into shared HostEffect arms from PR B +// (#7946). type ApplyWorkflow = DashboardSrv1 diff --git a/dag/gunbc/assimilate/bmc_bootstrap_provision.dag b/dag/gunbc/assimilate/bmc_bootstrap_provision.dag index 28cc8f2296f..d1593f56c09 100644 --- a/dag/gunbc/assimilate/bmc_bootstrap_provision.dag +++ b/dag/gunbc/assimilate/bmc_bootstrap_provision.dag @@ -9,7 +9,6 @@ import extdeps.cloud.gcp.iam { reconcile_gcp_policy, gcp_binding_cells, no_gcp_cells } - data bmc_assimilate_service_deps: List = [ GcpServiceUsage, GcpIam, @@ -33,7 +32,10 @@ fn bmc_secret_iam_bindings() -> List { ] } -data bmc_bootstrap_provision_eager_auth_print_access_token_fork: String = "🟡 dissolve-on: node auth calls shell.GCloud.AuthPrintAccessToken() directly instead of gunbc.auth.access_token_source.ensure_access_token(source: GcloudPrintToken). DISSOLVES WHEN this pattern routes token acquisition through ensure_access_token and deletes the shell.GCloud node. Authority: gunbc.auth.access_token_source ensure_access_token." +// 🟡 dissolve-on: node auth calls shell.GCloud.AuthPrintAccessToken() directly instead of +// gunbc.auth.access_token_source.ensure_access_token(source: GcloudPrintToken). DISSOLVES WHEN this +// pattern routes token acquisition through ensure_access_token and deletes the shell.GCloud node. +// Authority: gunbc.auth.access_token_source ensure_access_token. pattern bmc_bootstrap_provision_srv3() -> { sa_email: ServiceAccountEmail, diff --git a/dag/gunbc/assimilate/bmc_token_federation.dag b/dag/gunbc/assimilate/bmc_token_federation.dag index 73328170078..ac35784f3ad 100644 --- a/dag/gunbc/assimilate/bmc_token_federation.dag +++ b/dag/gunbc/assimilate/bmc_token_federation.dag @@ -231,8 +231,17 @@ data bmc_token_smoke_workflow: Workflow = { jobs: [bmc_token_smoke_job()] } +// dissolve-on: the repository-level federation VALUES now have one authority in +// gunbc.auth.github_gcp_federation, but seven bmc_-prefixed names in this module still stand for +// concepts that are not BMC facts (the fleet secrets project and number, the pool, provider, +// issuer, attribute mapping, federated repo). Those names are a residual second naming scheme for +// one concept -- weaker than the value fork just removed, since no row can now disagree, but still +// a nickname. DISSOLVES WHEN consumers (bmc_bootstrap_provision, bmc_wif_delegation_chain, +// bmc_wif_canary_bootstrap and their witnesses) reference the github_gcp_federation symbols +// directly and these pass-through rows are deleted. Deferred rather than done here because the +// rename touches BMC production files and their witnesses, and it is separable from the value +// consolidation this change lands. + fn emit_bmc_token_smoke_workflow_yaml() -> String { serialize_yaml(v: project_workflow_to_yaml(workflow: bmc_token_smoke_workflow)) } - -data bmc_federation_naming_residue_dissolve_on: String = "dissolve-on: the repository-level federation VALUES now have one authority in gunbc.auth.github_gcp_federation, but seven bmc_-prefixed names in this module still stand for concepts that are not BMC facts (the fleet secrets project and number, the pool, provider, issuer, attribute mapping, federated repo). Those names are a residual second naming scheme for one concept -- weaker than the value fork just removed, since no row can now disagree, but still a nickname. DISSOLVES WHEN consumers (bmc_bootstrap_provision, bmc_wif_delegation_chain, bmc_wif_canary_bootstrap and their witnesses) reference the github_gcp_federation symbols directly and these pass-through rows are deleted. Deferred rather than done here because the rename touches BMC production files and their witnesses, and it is separable from the value consolidation this change lands." diff --git a/dag/gunbc/auth/access_token_source.dag b/dag/gunbc/auth/access_token_source.dag index fb8103e096c..37d5a26e02b 100644 --- a/dag/gunbc/auth/access_token_source.dag +++ b/dag/gunbc/auth/access_token_source.dag @@ -31,7 +31,16 @@ type AccessTokenResolution | AccessTokenUpsertRequired { plan: AccessEnsurePlan } | AccessTokenEnsureRefused { reason: NonEmptyStr } -data gcloud_human_auth_pause_note: String = "Human gcloud authentication is the current REALIZER of an access ENSURE upsert, never a fleet phase or unconditional preflight. A selected operation first has to require a Secret Manager read/write; that effect requires an access token; only then does GcloudPrintToken OBSERVE the access capability. A successful print classifies Converged/Noop and continues without exposing the token. A missing or expired session classifies Absent/Apply(GoogleCloudInteractiveAuthenticationRequired) only when stderr carries an observed authentication deficit; missing binary, network, or other infrastructure failures classify Refuse with the stderr detail preserved. Re-driving after the upsert resumes the same operation. OperatorSuppliedToken remains an explicit compatibility handler. A future workload-identity/OAuth handler can realize the same ensure without changing BMC convergence." +// Human gcloud authentication is the current REALIZER of an access ENSURE upsert, never a fleet +// phase or unconditional preflight. A selected operation first has to require a Secret Manager +// read/write; that effect requires an access token; only then does GcloudPrintToken OBSERVE the +// access capability. A successful print classifies Converged/Noop and continues without exposing +// the token. A missing or expired session classifies +// Absent/Apply(GoogleCloudInteractiveAuthenticationRequired) only when stderr carries an observed +// authentication deficit; missing binary, network, or other infrastructure failures classify Refuse +// with the stderr detail preserved. Re-driving after the upsert resumes the same operation. +// OperatorSuppliedToken remains an explicit compatibility handler. A future workload-identity/OAuth +// handler can realize the same ensure without changing BMC convergence. fn gcloud_access_ensure_refusal_reason(stderr: String) -> NonEmptyStr { if string_length(s: stderr) > 0 { diff --git a/dag/gunbc/auth/github_gcp_federation.dag b/dag/gunbc/auth/github_gcp_federation.dag index 99611e348ab..6addffa0988 100644 --- a/dag/gunbc/auth/github_gcp_federation.dag +++ b/dag/gunbc/auth/github_gcp_federation.dag @@ -18,7 +18,12 @@ import extdeps.cloud.gcp.gcp { // BMC symbol for a non-BMC fact or copy the literal. The second option is what makes a fork, and // the Spark lane was about to take it. -data federation_authority_note: String = "One federation, many consuming domains. A domain that federates supplies only what is genuinely its own -- its service account, its secret population, its role set -- and reads the pool, provider, issuer, attribute mapping, project and principal-set construction from here. The prior arrangement forced a second domain to choose between importing a bmc_ symbol for a repository-level fact and copying the literal; copying is how the second authority gets minted, and the copy is invisible afterwards because both sides read correct." +// One federation, many consuming domains. A domain that federates supplies only what is genuinely +// its own -- its service account, its secret population, its role set -- and reads the pool, +// provider, issuer, attribute mapping, project and principal-set construction from here. The prior +// arrangement forced a second domain to choose between importing a bmc_ symbol for a +// repository-level fact and copying the literal; copying is how the second authority gets minted, +// and the copy is invisible afterwards because both sides read correct. // ── The GCP project the fleet's secrets live in ─────────────────────────────────────────── data fleet_secrets_project_id: GcpProjectId = "gunbai-secrets" diff --git a/dag/gunbc/auth/materialized_secret.dag b/dag/gunbc/auth/materialized_secret.dag index c4f26e26647..468a794ccb8 100644 --- a/dag/gunbc/auth/materialized_secret.dag +++ b/dag/gunbc/auth/materialized_secret.dag @@ -17,9 +17,31 @@ import gunbc.readback_independence { } import gunbc.host_phase_status { observation_verdict_eq, host_phase_verdict_from } -data materialized_secret_authority_note: String = "A1's authority — the lifecycle that turns a SecretRef into a usable credential and takes it away again. What the model carried before this was the two ENDS and not the middle: extdeps.cloud.gcp.secret_ref.SecretRef names a secret, gunbc.auth.secret_ref_credential.fetch_secret_ref_credential fetches its bytes, and everything between those two — bind the bytes to the transport that needs them (an ssh -i keyfile, an env var, a password on stdin), keep them 0600 and scoped, and destroy them afterwards — was performed by hand for every credential in the srv4 subsumption session. Hand-performed steps are where the fleet's credential incidents come from, and the shape of the fix is not a helper that does those steps: it is making the UNSCOPED state unwritable. So the only constructor of a MaterializedSecret is with_materialized_secret, which takes the use as a FUNCTION and returns that function's result — never the handle. A caller cannot hold a materialized secret after the bracket returns, because the bracket never yields one; the shred is the bracket's exit rather than a step someone must remember, which is the .dag reading of the plan's 'shred is by construction (the handle drop), not a remembered step'. Note what the handle deliberately does NOT carry: a path field. A transport that could read a temp path off the handle would make the raw path reachable again, and the plan's own RED is exactly 'a transport handed a raw temp path (not a MaterializedSecret handle) reds'." +// A1's authority — the lifecycle that turns a SecretRef into a usable credential and takes it away +// again. What the model carried before this was the two ENDS and not the middle: +// extdeps.cloud.gcp.secret_ref.SecretRef names a secret, +// gunbc.auth.secret_ref_credential.fetch_secret_ref_credential fetches its bytes, and everything +// between those two — bind the bytes to the transport that needs them (an ssh -i keyfile, an env +// var, a password on stdin), keep them 0600 and scoped, and destroy them afterwards — was performed +// by hand for every credential in the srv4 subsumption session. Hand-performed steps are where the +// fleet's credential incidents come from, and the shape of the fix is not a helper that does those +// steps: it is making the UNSCOPED state unwritable. So the only constructor of a +// MaterializedSecret is with_materialized_secret, which takes the use as a FUNCTION and returns +// that function's result — never the handle. A caller cannot hold a materialized secret after the +// bracket returns, because the bracket never yields one; the shred is the bracket's exit rather +// than a step someone must remember, which is the .dag reading of the plan's 'shred is by +// construction (the handle drop), not a remembered step'. Note what the handle deliberately does +// NOT carry: a path field. A transport that could read a temp path off the handle would make the +// raw path reachable again, and the plan's own RED is exactly 'a transport handed a raw temp path +// (not a MaterializedSecret handle) reds'. -data materialized_secret_binding_note: String = "The three bindings are the three the srv4 session actually performed by hand, taken from the A1 gap row verbatim rather than generalized ahead of a consumer. They are separate variants and not one 'path' because the transport contract differs in kind: SshKeyFile must be a 0600 file on disk (ssh refuses a loose-permissioned key, so the mode is part of the binding's meaning), EnvVar never touches disk and carries a name the child process reads, StdinPassword never persists at all. Collapsing them to a path would force the two non-file bindings to invent a file, which is how a secret ends up on disk that never needed to be there." +// The three bindings are the three the srv4 session actually performed by hand, taken from the A1 +// gap row verbatim rather than generalized ahead of a consumer. They are separate variants and not +// one 'path' because the transport contract differs in kind: SshKeyFile must be a 0600 file on disk +// (ssh refuses a loose-permissioned key, so the mode is part of the binding's meaning), EnvVar +// never touches disk and carries a name the child process reads, StdinPassword never persists at +// all. Collapsing them to a path would force the two non-file bindings to invent a file, which is +// how a secret ends up on disk that never needed to be there. type SecretBinding = SshKeyFile @@ -40,7 +62,17 @@ type MaterializationOutcome = MaterializationCompleted { value: R } | MaterializationRefused { cause: MaterializationRefusal } -data secret_existence_is_not_validity_note: String = "A3, and it is A1's first slice rather than a separate probe module because liveness is a fact about a materialization, not about a secret sitting still. The incident it comes from: Secret Manager held ci-github-app-private-key at SmEnabled while the key it contained (fingerprint 0c7a15...) had been DELETED from the GitHub App, and the live key (64a8af...) existed only on the hosts. Every existence check the model could perform said yes. srv4's runner registration then 401'd, and the divergence was diagnosed and reconciled by hand. SmEnabled answers 'this version exists and is not disabled' — a fact about the STORE — and the question that matters is whether the upstream that consumes the credential still accepts it. Those are different questions and the first was standing in for the second, which is the same presupposition failure gunbc.host_swap_backing found in the swap cap: a value reported green because the condition under which it would MEAN something was assumed rather than observed." +// A3, and it is A1's first slice rather than a separate probe module because liveness is a fact +// about a materialization, not about a secret sitting still. The incident it comes from: Secret +// Manager held ci-github-app-private-key at SmEnabled while the key it contained (fingerprint +// 0c7a15...) had been DELETED from the GitHub App, and the live key (64a8af...) existed only on the +// hosts. Every existence check the model could perform said yes. srv4's runner registration then +// 401'd, and the divergence was diagnosed and reconciled by hand. SmEnabled answers 'this version +// exists and is not disabled' — a fact about the STORE — and the question that matters is whether +// the upstream that consumes the credential still accepts it. Those are different questions and the +// first was standing in for the second, which is the same presupposition failure +// gunbc.host_swap_backing found in the swap cap: a value reported green because the condition under +// which it would MEAN something was assumed rather than observed. fn secret_existence_verdict(state: SmVersionState) -> ObservationVerdict { match state { @@ -50,7 +82,14 @@ fn secret_existence_verdict(state: SmVersionState) -> ObservationVerdict { } } -data secret_liveness_qualification_note: String = "Liveness DOMINATES existence, asymmetrically, exactly as observed swap backing dominates a MemorySwapMax cap. An enabled version whose key upstream rejects is not a usable credential, and an enabled version nobody has probed is not KNOWN to be one — so the qualified verdict is Converged only when both agree, and every other liveness state passes through in place of the existence answer. The arm that matters most is the unprobed one: with no liveness observation the qualified verdict is UnknownRefused, never Converged. That is the arm that would have stopped the srv4 401 before any host was touched, because the model's existence check was green throughout the incident and would have stayed green under any amount of care." +// Liveness DOMINATES existence, asymmetrically, exactly as observed swap backing dominates a +// MemorySwapMax cap. An enabled version whose key upstream rejects is not a usable credential, and +// an enabled version nobody has probed is not KNOWN to be one — so the qualified verdict is +// Converged only when both agree, and every other liveness state passes through in place of the +// existence answer. The arm that matters most is the unprobed one: with no liveness observation the +// qualified verdict is UnknownRefused, never Converged. That is the arm that would have stopped the +// srv4 401 before any host was touched, because the model's existence check was green throughout +// the incident and would have stayed green under any amount of care. fn secret_liveness_qualified_verdict( existence: ObservationVerdict, @@ -66,7 +105,15 @@ fn secret_liveness_qualified_verdict( } } -data github_app_jwt_mint_probe_note: String = "The liveness probe, classified under the read-back independence criterion rather than trusted. Minting a GitHub App installation token with the key is read-only against the subject: it asks GitHub whether this key is accepted and cannot MAKE a revoked key accepted, so it is ProbeInert and its Converged survives. The classification is doing real work even though it passes today, because the tempting shape for a liveness check is exactly the one that would fail it — a probe that repairs on miss (rotate the key, add a new version, re-register the app) reports success it caused, and the hand reconciliation this gap row describes was precisely that repair performed by a human. A probe that both diagnoses and repairs cannot be the thing that certifies the diagnosis." +// The liveness probe, classified under the read-back independence criterion rather than trusted. +// Minting a GitHub App installation token with the key is read-only against the subject: it asks +// GitHub whether this key is accepted and cannot MAKE a revoked key accepted, so it is ProbeInert +// and its Converged survives. The classification is doing real work even though it passes today, +// because the tempting shape for a liveness check is exactly the one that would fail it — a probe +// that repairs on miss (rotate the key, add a new version, re-register the app) reports success it +// caused, and the hand reconciliation this gap row describes was precisely that repair performed by +// a human. A probe that both diagnoses and repairs cannot be the thing that certifies the +// diagnosis. data github_app_jwt_mint_probe: ReadBackProbe = ReadBackProbe { label: "mint a GitHub App installation token with the SecretRef's private key", @@ -89,7 +136,19 @@ fn secret_ref_liveness_verdict( readback_evidence_verdict(p: probe, observed: host_phase_verdict_from(observation: observed)) } -data materialization_bracket_note: String = "THE construction wall. with_materialized_secret is the only place a MaterializedSecret value is built, and it hands that value to `use` rather than returning it, so the handle's lifetime is the bracket's body by construction — there is no expression a caller can write that holds it afterwards. The refusal arm carries no handle at all, which is the same EffectsOrRefusal shape the runner-host enrollment wall uses: a refused materialization cannot be accidentally treated as a successful one, because it has nothing to treat. The three refusal causes stay separate because their remedies are different work: SecretNotLive is a credential to reconcile with its upstream, SecretVersionUnusable is a store state to fix, LivenessProbeNotIndependent is a probe to replace. HONEST RESIDUAL, the same one the enrollment wall carries: .dag has no visibility modifiers today, so a caller could hand-write MaterializedSecret { ref: .., binding: .. } and bypass this entirely. That is an explicit lie rather than an accident — it requires naming the type and building it deliberately — and it closes when node/subtree visibility grants land and this constructor becomes private to the module." +// THE construction wall. with_materialized_secret is the only place a MaterializedSecret value is +// built, and it hands that value to `use` rather than returning it, so the handle's lifetime is the +// bracket's body by construction — there is no expression a caller can write that holds it +// afterwards. The refusal arm carries no handle at all, which is the same EffectsOrRefusal shape +// the runner-host enrollment wall uses: a refused materialization cannot be accidentally treated as +// a successful one, because it has nothing to treat. The three refusal causes stay separate because +// their remedies are different work: SecretNotLive is a credential to reconcile with its upstream, +// SecretVersionUnusable is a store state to fix, LivenessProbeNotIndependent is a probe to replace. +// HONEST RESIDUAL, the same one the enrollment wall carries: .dag has no visibility modifiers +// today, so a caller could hand-write MaterializedSecret { ref: .., binding: .. } and bypass this +// entirely. That is an explicit lie rather than an accident — it requires naming the type and +// building it deliberately — and it closes when node/subtree visibility grants land and this +// constructor becomes private to the module. fn with_materialized_secret( ref: SecretRef, @@ -119,9 +178,26 @@ fn with_materialized_secret( } } -data refusal_axis_dispatch_note: String = "CORRECTION, found in review (review 42890) and it is an instance of this module's own subject matter turned on itself. The bracket originally folded existence and liveness into ONE qualified verdict and then refused with SecretNotLive whatever came out — so SecretVersionUnusable was declared and unreachable on the authority path, and two refusals with different remedies collapsed onto one shape: a destroyed store version and an upstream-revoked key both emerged as SecretNotLive { verdict: Absent }. The note two declarations above claimed the three causes stay separate BECAUSE their remedies are different work, and the code did not do it — the claim was true about the type and false about the fold. The destroyed-version witness did not catch it because it only asserted that materialization refused, never which cause it named, which is the same weak assertion this lane keeps warning about in other people's tests. The axes are now dispatched in order: the STORE axis first, because a version that is disabled or destroyed is unusable whatever any probe says about the key it used to hold, and only then the UPSTREAM axis. secret_liveness_qualified_verdict survives as the combined-STATUS authority for cell consumers that want one verdict; it is no longer what decides a materialization, because a status verdict and a refusal cause answer different questions." +// CORRECTION, found in review (review 42890) and it is an instance of this module's own subject +// matter turned on itself. The bracket originally folded existence and liveness into ONE qualified +// verdict and then refused with SecretNotLive whatever came out — so SecretVersionUnusable was +// declared and unreachable on the authority path, and two refusals with different remedies +// collapsed onto one shape: a destroyed store version and an upstream-revoked key both emerged as +// SecretNotLive { verdict: Absent }. The note two declarations above claimed the three causes stay +// separate BECAUSE their remedies are different work, and the code did not do it — the claim was +// true about the type and false about the fold. The destroyed-version witness did not catch it +// because it only asserted that materialization refused, never which cause it named, which is the +// same weak assertion this lane keeps warning about in other people's tests. The axes are now +// dispatched in order: the STORE axis first, because a version that is disabled or destroyed is +// unusable whatever any probe says about the key it used to hold, and only then the UPSTREAM axis. +// secret_liveness_qualified_verdict survives as the combined-STATUS authority for cell consumers +// that want one verdict; it is no longer what decides a materialization, because a status verdict +// and a refusal cause answer different questions. -data materialize_widen_red_control_note: String = "The forbidden arm as an executable declaration: materialize whenever the store says the version exists, which is what every hand-run credential step in the srv4 session effectively did. It is not a strawman — it is the behaviour that produced the 401, written down so a gate can reject it and so the authority's refusal is proven to be a decision rather than a coincidence." +// The forbidden arm as an executable declaration: materialize whenever the store says the version +// exists, which is what every hand-run credential step in the srv4 session effectively did. It is +// not a strawman — it is the behaviour that produced the 401, written down so a gate can reject it +// and so the authority's refusal is proven to be a decision rather than a coincidence. fn materialize_existence_only_widen_sketch( ref: SecretRef, @@ -241,6 +317,14 @@ fn with_materialized_ssh_key_file( } } +// A1 lands on top of a declared scaffold and says so: authenticating to Secret Manager at all +// currently depends on an access token the operator pastes by hand roughly hourly, because no +// modeled workflow identity can reach the store. That is A2 (reach-secrets ADC), already a +// DeclaredGap in host_standup P0:reach-secrets-network and in fleet_ssh_access. This lane does not +// smuggle a fix for it — the fetch leg simply inherits whatever credential is live — and A1's +// dependence on it is recorded here so that the day A2 lands, the thing to delete is a named row +// rather than an assumption nobody wrote down. + // What is modeled in this module is the LIFECYCLE and its refusals. The // fetch leg already exists as // gunbc.auth.secret_ref_credential.fetch_secret_ref_credential over the @@ -294,5 +378,3 @@ data materialized_secret_binding_realization_scaffold: Disposition = Scaffold { field: WholeDeclaration, } } - -data materialized_secret_adc_scaffold_note: String = "A1 lands on top of a declared scaffold and says so: authenticating to Secret Manager at all currently depends on an access token the operator pastes by hand roughly hourly, because no modeled workflow identity can reach the store. That is A2 (reach-secrets ADC), already a DeclaredGap in host_standup P0:reach-secrets-network and in fleet_ssh_access. This lane does not smuggle a fix for it — the fetch leg simply inherits whatever credential is live — and A1's dependence on it is recorded here so that the day A2 lands, the thing to delete is a named row rather than an assumption nobody wrote down." diff --git a/dag/gunbc/bach_double_slit_observation.dag b/dag/gunbc/bach_double_slit_observation.dag index 57a2b64b377..a469cc7c093 100644 --- a/dag/gunbc/bach_double_slit_observation.dag +++ b/dag/gunbc/bach_double_slit_observation.dag @@ -26,17 +26,25 @@ import extdeps.physics.bach_et_al_2013_double_slit { BothSlitsDoubleSlitDiffractionObserved, } -data bach_observation_layer_note: String = "Observing-layer receipts for Bach et al. 2013. Buildup progression derives structural properties from extdeps.physics.bach_et_al_2013_double_slit.bach_buildup_observations — sole population authority, no parallel copy." +// Observing-layer receipts for Bach et al. 2013. Buildup progression derives structural properties +// from extdeps.physics.bach_et_al_2013_double_slit.bach_buildup_observations — sole population +// authority, no parallel copy. -data bach_distribution_canonical_sequence_note: String = "Distribution gate checks that observations match the cited canonical sequence (slit-one, slit-two, both-open) via total bach_distribution_observation_rank — not completeness over the BachDistributionObservation coproduct. A fourth variant left unobserved still passes; the literal length 3 tracks the three known variants, not a derived coproduct arity." +// Distribution gate checks that observations match the cited canonical sequence (slit-one, +// slit-two, both-open) via total bach_distribution_observation_rank — not completeness over the +// BachDistributionObservation coproduct. A fourth variant left unobserved still passes; the literal +// length 3 tracks the three known variants, not a derived coproduct arity. -data bach_raw_count_additivity_not_evaluable_note: String = "Derived from cited Bach figures 1–2 (normalized distributions only): raw matched-exposure per-band arrival triplet N_both = N_slit_one + N_slit_two is not evaluable from this source." +// Derived from cited Bach figures 1–2 (normalized distributions only): raw matched-exposure +// per-band arrival triplet N_both = N_slit_one + N_slit_two is not evaluable from this source. data bach_which_path_evidence_requirement_unsatisfied_note: String = "Derived from cited Bach slit-mask control (not a which-path readout): which-path distinguishability evidence requirement is unsatisfied for this source." -data which_path_coverage_obligation_note: String = "Which-path distinguishability evidence is absent from cited Bach et al. 2013; obligation recorded here per DESIGN section 3." +// Which-path distinguishability evidence is absent from cited Bach et al. 2013; obligation recorded +// here per DESIGN section 3. -data bach_quantitative_path_additivity_gap_note: String = "Authored coverage gap (explicit gap rung, not a derived readiness verdict): bach_raw_count_additivity_not_evaluable_note grounds why no raw triplet exists to evaluate." +// Authored coverage gap (explicit gap rung, not a derived readiness verdict): +// bach_raw_count_additivity_not_evaluable_note grounds why no raw triplet exists to evaluate. // Authored coverage gap (explicit gap rung, not a derived readiness verdict): bach_which_path_evidence_requirement_unsatisfied_note grounds why which-path evidence is absent. type BachRawCountPathAdditivityGap diff --git a/dag/gunbc/bach_double_slit_path_additivity_synthetic_fixture.dag b/dag/gunbc/bach_double_slit_path_additivity_synthetic_fixture.dag index a4f941b03a5..f823645d854 100644 --- a/dag/gunbc/bach_double_slit_path_additivity_synthetic_fixture.dag +++ b/dag/gunbc/bach_double_slit_path_additivity_synthetic_fixture.dag @@ -51,7 +51,9 @@ import gunbc.physics.bach_double_slit_path_additivity { } import std.algebra { Greater } -data synthetic_fixture_note: String = "SYNTHETIC MECHANISM FIXTURE — NOT Bach et al. 2013 provenance. Rows exercise gunbc.physics.bach_double_slit_path_additivity with equal declared exposure identity; classical mechanism adds raw counts." +// SYNTHETIC MECHANISM FIXTURE — NOT Bach et al. 2013 provenance. Rows exercise +// gunbc.physics.bach_double_slit_path_additivity with equal declared exposure identity; classical +// mechanism adds raw counts. data synthetic_double_slit_fixture: SyntheticDoubleSlitFixture = SyntheticDoubleSlitFixtureOne {} diff --git a/dag/gunbc/bare_reference_scanner_admission.dag b/dag/gunbc/bare_reference_scanner_admission.dag index 8b35ceac2b1..61f29c16d40 100644 --- a/dag/gunbc/bare_reference_scanner_admission.dag +++ b/dag/gunbc/bare_reference_scanner_admission.dag @@ -11,11 +11,82 @@ data bare_reference_scanner_receipt_disposition: Disposition = Terminal { reason: "Single authority for the SEED-GROWTH RECEIPT owed by the bare-reference scanner change (gunbc#9102). It declares WHICH hand-authored Rust declarations that change adds to the seed, why they are there, and what retires them. It does NOT admit them: the realization disposition for the scanner itself is an operator judgment (ScaffoldAdmitted / TerminalOrRetainedKernel / RejectedForFinalConstruction) and is deliberately NOT asserted here. A receipt that also granted its own admission would be the self-authorized dissolution failure DESIGN names — an author who can write a scaffold can equally write a row claiming it was approved. The row is Terminal because the RECEIPT is permanent: it survives the scanner it describes, recording what was owed and on what trigger it was discharged." } -data bare_reference_scanner_operator_admission_note: String = "OPERATOR DISPOSITION: ScaffoldAdmitted, 2026-08-24, bound to gunbc#9102 at 7bc60669132bee8601410b82e493fdd707d68cf9. Relayed by deep-ant-102; this session cannot read the operator thread, so the relay is the carrier and is recorded as such rather than as a first-hand reading.\n\nTHE VERDICT: the raw-text bare-reference scanner is admitted as a BOUNDED BOOTSTRAP REALIZATION serving the v2 self-host namespace cut. It is NOT a retained kernel. It dissolves when parser/ingestion output exposes the binder and declaration facts it currently re-derives, at which point the textual scanner DELETES rather than surviving as a fallback.\n\nADMITTED SCOPE, exactly: the production fn module_self_declared_names; the corrections inside bare_identifier_candidates and bare_reference_pull_paths_for_source; bare_reference_scanner_tests and its seven test fns.\n\nNOT ADMITTED, each requiring a NEW disposition: additional source-text recognizers; new callers of the scanner; public-surface growth; the whole-pool fallback; nearest-ancestor ambiguity selection; a general hand-written namespace parser.\n\nONE ENUMERATION DISCREPANCY, RECONCILED HERE BECAUSE THE BOUNDARY IS WHAT FUTURE SCOPE QUESTIONS ARE JUDGED AGAINST. The verdict prose says THREE separately recorded modifications and the boundary block then names TWO. The third is project_roadmap_acceptance_event_history_from_authority_text_builtin, recorded in bare_reference_scanner_not_growth_note. The COUNT agrees and the ENUMERATION does not, which reads as an omission in the boundary rather than a narrowing of it — the verdict's own count includes it. It is covered by the same admission. This is stated rather than silently assumed, because a future scope question resolved against a two-item boundary would reach a different answer than one resolved against the three the verdict counted.\n\nWHY THE HEAD MOVED AND WHY THAT DOES NOT REQUIRE RENEWED ADMISSION: the disposition names 7bc6606 and the branch has since merged main (d6a4bf409336). Measured against current main the delta is TWO .dag FILES, +74/-2, with ZERO Rust files differing and exactly one definition of module_self_declared_names in the merged tree. The change adds no Rust, no declaration and no scope, so it falls under the queue's next-fully-reviewed-head clause rather than the renewed-admission clause, which governs SCOPE growth." +// OPERATOR DISPOSITION: ScaffoldAdmitted, 2026-08-24, bound to gunbc#9102 at +// 7bc60669132bee8601410b82e493fdd707d68cf9. Relayed by deep-ant-102; this session cannot read the +// operator thread, so the relay is the carrier and is recorded as such rather than as a first-hand +// reading. +// +// THE VERDICT: the raw-text bare-reference scanner is admitted as a BOUNDED BOOTSTRAP REALIZATION +// serving the v2 self-host namespace cut. It is NOT a retained kernel. It dissolves when +// parser/ingestion output exposes the binder and declaration facts it currently re-derives, at +// which point the textual scanner DELETES rather than surviving as a fallback. +// +// ADMITTED SCOPE, exactly: the production fn module_self_declared_names; the corrections inside +// bare_identifier_candidates and bare_reference_pull_paths_for_source; bare_reference_scanner_tests +// and its seven test fns. +// +// NOT ADMITTED, each requiring a NEW disposition: additional source-text recognizers; new callers +// of the scanner; public-surface growth; the whole-pool fallback; nearest-ancestor ambiguity +// selection; a general hand-written namespace parser. +// +// ONE ENUMERATION DISCREPANCY, RECONCILED HERE BECAUSE THE BOUNDARY IS WHAT FUTURE SCOPE QUESTIONS +// ARE JUDGED AGAINST. The verdict prose says THREE separately recorded modifications and the +// boundary block then names TWO. The third is +// project_roadmap_acceptance_event_history_from_authority_text_builtin, recorded in +// bare_reference_scanner_not_growth_note. The COUNT agrees and the ENUMERATION does not, which +// reads as an omission in the boundary rather than a narrowing of it — the verdict's own count +// includes it. It is covered by the same admission. This is stated rather than silently assumed, +// because a future scope question resolved against a two-item boundary would reach a different +// answer than one resolved against the three the verdict counted. +// +// WHY THE HEAD MOVED AND WHY THAT DOES NOT REQUIRE RENEWED ADMISSION: the disposition names 7bc6606 +// and the branch has since merged main (d6a4bf409336). Measured against current main the delta is +// TWO .dag FILES, +74/-2, with ZERO Rust files differing and exactly one definition of +// module_self_declared_names in the merged tree. The change adds no Rust, no declaration and no +// scope, so it falls under the queue's next-fully-reviewed-head clause rather than the +// renewed-admission clause, which governs SCOPE growth. data bare_reference_scanner_presumption_note: String = "THE SCAFFOLD PRESUMPTION STANDS AND IS NOT ARGUED AWAY HERE. module_self_declared_names re-parses language structure from raw source text; the facts it reconstructs — declarations, coproduct variants, type parameters, lambda binders — belong to parsing or ingestion, which already own them; and it sits in cli_run.rs. None of those three grounds scales with size, so the measured 115 lines (116 by the census's inclusive span, which counts the signature line) rather than a circulated ~603 changes HOW MUCH debt is on the table, never WHETHER the presumption applies. A 115-line raw-text scanner re-derives the same parser-owned facts as a 603-line one.\n\nWHAT THE CORRECT FIGURE DOES CHANGE: the operator is being asked to dispose of ONE production item of 115 lines, not nine items of 603.\n\nWHY THE SCANNER EXISTS AT ALL, stated as the cost of NOT having it rather than as a justification: the bare-reference census resolves a name against the whole pool. Without knowing which names a module BINDS or DECLARES, a lambda parameter, a type parameter and a module's own coproduct variant are each scanned as references out and pulled a module that happened to declare that spelling. Measured on the import-free corpus: 172 sources / 65 blocking without the guards, 94 / 2 with them, main byte-identical at 99 / 0 in every arm.\n\nTHE TERMINAL CONSTRUCTION IS NOT THIS. It is the parse phase publishing the binder and declaration facts it already computes, so no consumer re-derives them from text. Until that surface exists the scanner is the only thing standing between an import-free corpus and a whole-pool over-pull, and that is an argument for a bounded exception, never for keeping it." -data bare_reference_scanner_not_growth_note: String = "WHAT IS NOT GROWTH AND IS DELIBERATELY NOT LISTED, so a modification is never netted into an addition census. THREE existing items are MODIFIED rather than added — bare_identifier_candidates (a new arm inside its existing scan loop), bare_reference_pull_paths_for_source (two new skip conditions in its existing resolve loop, plus provenance carried on the existing trace line), and project_roadmap_acceptance_event_history_from_authority_text_builtin. Their disposition is ExistingSeedItemModified. Listing them above would inflate the addition count by three and understate nothing, which is exactly the direction that makes a census look conservative while being wrong.\n\nTHE AUTHORITATIVE G0 CENSUS OVER-REPORTS THIS DIFF BY 100 PERCENT, AND THE RECONCILIATION IS STATED HERE SO THE NEXT AUTHOR DOES NOT CONCLUDE THIS RECEIPT IS WRONG. scripts/rust_item_census.py --diff origin/main reports EIGHTEEN added hand items. NINE are the real declarations enumerated above. The other NINE are fixture .dag source authored inside Rust raw string literals as test input — type VisibilityScope, type FrameExtent, type CostBasis, type CostAccount, type Result, fn trim_one, type Dimension, type Scale, fn g. They are data, not declarations, and counting them is the same shape as counting a script's own echo as its output.\n\nTHE DENOMINATORS ARE DIFFERENT AND MUST BE NAMED, because three plausible counts circulate. EIGHTEEN is the census ITEM total. NINE is the deduplicated phantom ITEM count. TWELVE is the count of fixture LINES in the diff, which is larger because CostAccount is authored twice (bare and parameterized) and fn f is authored twice (over List and List) — the census enrols an item once and the line scan sees each spelling. A reader who adds an item count to a line count gets a number that reconciles with nothing.\n\nTHE CENSUS DEFECT IS PREDICTED BY THE CENSUS ITSELF: gunbc.seed_growth_admission seed_growth_g0_census_host_scaffold_note records that its item extraction is regex, parallel to but outside the dag/extdeps/languages/ rust grammar authority. This is that prediction firing. It is also the same CLASS as the artifact this receipt accounts for — a raw-text recognizer misreading language structure because it reads source as text rather than consulting the authority that owns it. BUT THEY DO NOT SHARE ONE TERMINAL CONSTRUCTION, and an earlier revision of this clause said they did, which would discharge a second defect by implication rather than by an owner. The scanner retires when the .dag front end publishes its binder and declaration facts; the census retires when a Rust grammar authority publishes real Rust item identities. A .dag binder surface does not retire a regex-based Rust-item census. What they share is the ARCHITECTURAL RULE — language structure must come from the authority for that language, never from a parallel raw-text recognizer — with two language-specific consumers and two independent retirement triggers. The Rust census defect needs its own owner and trigger; it does not block this receipt, because the nine real declarations are enumerated by hand above and the reported eighteen is explicitly rejected." +// WHAT IS NOT GROWTH AND IS DELIBERATELY NOT LISTED, so a modification is never netted into an +// addition census. THREE existing items are MODIFIED rather than added — bare_identifier_candidates +// (a new arm inside its existing scan loop), bare_reference_pull_paths_for_source (two new skip +// conditions in its existing resolve loop, plus provenance carried on the existing trace line), and +// project_roadmap_acceptance_event_history_from_authority_text_builtin. Their disposition is +// ExistingSeedItemModified. Listing them above would inflate the addition count by three and +// understate nothing, which is exactly the direction that makes a census look conservative while +// being wrong. +// +// THE AUTHORITATIVE G0 CENSUS OVER-REPORTS THIS DIFF BY 100 PERCENT, AND THE RECONCILIATION IS +// STATED HERE SO THE NEXT AUTHOR DOES NOT CONCLUDE THIS RECEIPT IS WRONG. +// scripts/rust_item_census.py --diff origin/main reports EIGHTEEN added hand items. NINE are the +// real declarations enumerated above. The other NINE are fixture .dag source authored inside Rust +// raw string literals as test input — type VisibilityScope, type FrameExtent, type CostBasis, type +// CostAccount, type Result, fn trim_one, type Dimension, type Scale, fn g. They are data, not +// declarations, and counting them is the same shape as counting a script's own echo as its output. +// +// THE DENOMINATORS ARE DIFFERENT AND MUST BE NAMED, because three plausible counts circulate. +// EIGHTEEN is the census ITEM total. NINE is the deduplicated phantom ITEM count. TWELVE is the +// count of fixture LINES in the diff, which is larger because CostAccount is authored twice (bare +// and parameterized) and fn f is authored twice (over List and List) — the census enrols +// an item once and the line scan sees each spelling. A reader who adds an item count to a line +// count gets a number that reconciles with nothing. +// +// THE CENSUS DEFECT IS PREDICTED BY THE CENSUS ITSELF: gunbc.seed_growth_admission +// seed_growth_g0_census_host_scaffold_note records that its item extraction is regex, parallel to +// but outside the dag/extdeps/languages/ rust grammar authority. This is that prediction firing. It +// is also the same CLASS as the artifact this receipt accounts for — a raw-text recognizer +// misreading language structure because it reads source as text rather than consulting the +// authority that owns it. BUT THEY DO NOT SHARE ONE TERMINAL CONSTRUCTION, and an earlier revision +// of this clause said they did, which would discharge a second defect by implication rather than by +// an owner. The scanner retires when the .dag front end publishes its binder and declaration facts; +// the census retires when a Rust grammar authority publishes real Rust item identities. A .dag +// binder surface does not retire a regex-based Rust-item census. What they share is the +// ARCHITECTURAL RULE — language structure must come from the authority for that language, never +// from a parallel raw-text recognizer — with two language-specific consumers and two independent +// retirement triggers. The Rust census defect needs its own owner and trigger; it does not block +// this receipt, because the nine real declarations are enumerated by hand above and the reported +// eighteen is explicitly rejected. data bare_reference_scanner_seed_growth_justification: SeedGrowthJustification = SeedGrowthJustification { hand_authored_declarations: [ diff --git a/dag/gunbc/bmc/bmc_converge.dag b/dag/gunbc/bmc/bmc_converge.dag index 043b465c972..e71ef0d95f5 100644 --- a/dag/gunbc/bmc/bmc_converge.dag +++ b/dag/gunbc/bmc/bmc_converge.dag @@ -220,7 +220,11 @@ fn bmc_fan_policy_suitability_refusal_reason( } } -// BMC and Ubuntu convergence share std.upsert_decision for observe/classify/plan and std.realization_reconcile for apply/read-back. Fan-policy reconciliation is three separable questions: apply suitability, post-apply durability qualification, and ordinary convergence after qualification. Apply may proceed without prior durability evidence; Noop requires qualification plus a live canonical digest equal to the admitted digest. +// BMC and Ubuntu convergence share std.upsert_decision for observe/classify/plan and +// std.realization_reconcile for apply/read-back. Fan-policy reconciliation is three separable +// questions: apply suitability, post-apply durability qualification, and ordinary convergence after +// qualification. Apply may proceed without prior durability evidence; Noop requires qualification +// plus a live canonical digest equal to the admitted digest. fn bmc_firmware_intent_mismatch_reason( required: BmcFirmwareTrack, observed: BmcFirmwareVersion, @@ -791,7 +795,13 @@ fn bmc_fan_policy_durability_gated_convergence_from_inputs( ) } -data bmc_fan_firmware_unpinned_realizer_note: String = "The fan realizer needs a firmware-matched projection (the projection's config bytes and digest are firmware-version-specific). It does NOT require the fleet to pin a single firmware track: an unpinned firmware intent is admitted, and each host qualifies against a projection whose firmware matches that host's observed firmware (bmc_fan_policy_apply_suitability rejects a firmware mismatch). What is refused is a PINNED track that the host has not yet converged to (FanPolicyFirmwareMustConvergeBeforeRealizer) -- a firmware update would replace the flash and wipe the fan config, so fans must not be realized until that update lands. Unpinned means no update is intended, so realization is safe." +// The fan realizer needs a firmware-matched projection (its config bytes and digest are +// firmware-version-specific). It does NOT require the fleet to pin a single firmware track: an +// unpinned firmware intent is admitted, and each host qualifies against a projection matching its +// observed firmware (bmc_fan_policy_apply_suitability rejects a mismatch). What is refused is a +// PINNED track the host has not yet converged to (FanPolicyFirmwareMustConvergeBeforeRealizer) -- a +// firmware update would replace the flash and wipe the fan config, so fans must not be realized +// until it lands. Unpinned means no update is intended, so realization is safe. fn bmc_fan_firmware_blocks_realizer( intent_firmware: BmcFirmwareIntent, @@ -909,17 +919,16 @@ fn bmc_fan_policy_qualify_with_projection( } } -// THE ROSTER IS A PARAMETER HERE AND CLOSED AT THE WET EDGE, which is a correction to where I -// first put the closure. Closing it inside the projection seam meant every fixture exercising a -// DOWNSTREAM distinction -- durability linkage, topology drift, live digest -- was answered by -// the alignment gate instead, because srv4's real standing refuses. Nine such tests went red, and -// they were not wrong: the pipeline's order had changed under them and they could no longer reach -// the subject they were written for. +// THE ROSTER IS A PARAMETER HERE AND CLOSED AT THE WET EDGE, a correction to where the closure was +// first put. Closing it inside the projection seam meant every fixture exercising a DOWNSTREAM +// distinction -- durability linkage, topology drift, live digest -- was answered by the alignment +// gate instead, because srv4's real standing refuses. Nine such tests went red, and rightly: the +// pipeline's order had changed under them and they could no longer reach their subject. // -// A confinement belongs at the boundary where production actually enters, not mid-pipeline where -// it also excludes every fixture. bmc_fan_converge_with_credential and -// bmc_fan_observe_only_with_credential pass the fleet roster and take no roster argument -// themselves; a fixture supplies its own and reaches the arm it is testing. +// A confinement belongs at the boundary where production enters, not mid-pipeline where it also +// excludes every fixture. bmc_fan_converge_with_credential and bmc_fan_observe_only_with_credential +// pass the fleet roster and take no roster argument; a fixture supplies its own and reaches the arm +// it is testing. fn bmc_fan_policy_classification( standings: List, host: HostIdentity, diff --git a/dag/gunbc/bmc/bmc_fan_converge.dag b/dag/gunbc/bmc/bmc_fan_converge.dag index eac432801b8..7e67be23091 100644 --- a/dag/gunbc/bmc/bmc_fan_converge.dag +++ b/dag/gunbc/bmc/bmc_fan_converge.dag @@ -1780,6 +1780,16 @@ func srv4_bmc_fan_converge() -> ProcessExit ) } +// The BMC fan entrypoint performs exact-target identity observation, typed policy/config +// observation with a live digest, policy classification, and live thermal/tach safety readback. A +// Noop is accepted only when the live canonical digest equals the admitted digest and every safety +// observation holds. Apply and rollback remain phase-partial refusals: the rejected two-file +// backup/mutation realizer is deleted, and no effect can run until one content-addressed manifest +// binds the exact target, stable BMC identity, firmware, config path, before/backup digests and +// generation, followed by exact readback and a bounded monitoring receipt. GCP credential access +// remains demand-derived, and password SSH pins the target-specific known-hosts file with the +// password on inherited fd 0. + func srv4_bmc_fan_rollback_previous() -> ProcessExit uses net: Network { @@ -1788,5 +1798,3 @@ func srv4_bmc_fan_rollback_previous() -> ProcessExit action: BmcFanRollbackPrevious, ) } - -data bmc_fan_converge_subsumption_note: String = "The BMC fan entrypoint performs exact-target identity observation, typed policy/config observation with a live digest, policy classification, and live thermal/tach safety readback. A Noop is accepted only when the live canonical digest equals the admitted digest and every safety observation holds. Apply and rollback remain phase-partial refusals: the rejected two-file backup/mutation realizer is deleted, and no effect can run until one content-addressed manifest binds the exact target, stable BMC identity, firmware, config path, before/backup digests and generation, followed by exact readback and a bounded monitoring receipt. GCP credential access remains demand-derived, and password SSH pins the target-specific known-hosts file with the password on inherited fd 0." diff --git a/dag/gunbc/bmc/bmc_fan_projection.dag b/dag/gunbc/bmc/bmc_fan_projection.dag index addfba9a005..c7de0cff72b 100644 --- a/dag/gunbc/bmc/bmc_fan_projection.dag +++ b/dag/gunbc/bmc/bmc_fan_projection.dag @@ -177,7 +177,20 @@ data altrad8ud_openbmc_322_fleet_quiet_projected_config_content_identity: BmcPro projected_config_sha256_hex: altrad8ud_openbmc_322_fleet_quiet_projected_config_sha256_hex, } -data altrad8ud_openbmc_322_fleet_quiet_projected_config_probe_note: String = "SHA-256 of /usr/share/swampd/config-asrr.json after applying altrad8ud_openbmc_322_fleet_quiet_projected_config_curve (the srv3 2.07.00 vendor stock aggressive curve) through the altrad8ud-openbmc-3.22.00-swampd-asrr-temp-soc-jffs2-v1 realization, measured on srv4 by authenticated BMC SSH 2026-07-30: the jq realization was run on srv4's pre-onboarding base config with the stock aggressive desired params, the resulting canonical bytes were written to srv4 by staged same-filesystem atomic rename, and the live config-asrr.json digest read back equal to this hex. Unlike the earlier lazy row (whose hex was the raw onboarding file, idempotent only in structure), this hex IS the realization's own output — the realization reproduces the cited digest byte-for-byte. altrad8ud_openbmc_322_fleet_quiet_projected_config_content_identity is the immutable content identity binding realization, config path, probed curve bytes, and cited sha256 hex; bmc_fan_projected_config_digest admits that hex only when every input matches that row exactly. Live intent curves must be witnessed against the frozen probed curve before projection can qualify new bytes." +// SHA-256 of /usr/share/swampd/config-asrr.json after applying +// altrad8ud_openbmc_322_fleet_quiet_projected_config_curve (the srv3 2.07.00 vendor stock +// aggressive curve) through the altrad8ud-openbmc-3.22.00-swampd-asrr-temp-soc-jffs2-v1 +// realization, measured on srv4 by authenticated BMC SSH 2026-07-30: the jq realization was run on +// srv4's pre-onboarding base config with the stock aggressive desired params, the resulting +// canonical bytes were written to srv4 by staged same-filesystem atomic rename, and the live +// config-asrr.json digest read back equal to this hex. Unlike the earlier lazy row (whose hex was +// the raw onboarding file, idempotent only in structure), this hex IS the realization's own output +// — the realization reproduces the cited digest byte-for-byte. +// altrad8ud_openbmc_322_fleet_quiet_projected_config_content_identity is the immutable content +// identity binding realization, config path, probed curve bytes, and cited sha256 hex; +// bmc_fan_projected_config_digest admits that hex only when every input matches that row exactly. +// Live intent curves must be witnessed against the frozen probed curve before projection can +// qualify new bytes. data altrad8ud_openbmc_322_fleet_quiet_temp_soc_projected_config_digest: Digest = sha256_digest( hex: altrad8ud_openbmc_322_fleet_quiet_projected_config_sha256_hex, @@ -451,6 +464,24 @@ fn bmc_fan_policy_projection_durability_capability( ) } +// A fleet firmware track says which states are valid; it does not select a JSON realizer. A +// projection capability is admitted only for an exact board, firmware version, desired semantic +// curve, a BmcProjectedConfigContentIdentity row binding probed curve bytes to cited sha256 hex, +// config path, controller service, zone control path, aggregate fan PID entry, and installed +// tach-input topology witnessed together. bmc_fan_projected_config_digest derives the digest only +// when curve, intent curve, realization, config path, and content_identity match exactly; it never +// reads observation. bmc_fan_admitted_config_digest takes the intent curve and an optional +// caller-supplied projection-derived digest at the reconcile site; it returns +// AdmittedConfigDigestUnavailable when the curve is not the modeled intent curve or the digest is +// absent. bmc_fan_witness_digest_mismatch_sentinel is witness-only observation input for +// live-digest mismatch controls, not an admitted digest authority. Its durability capability and +// admission are derived from the same realization content identity, capability version/hash, +// lifecycle boundaries, executable publication protocol, target host, readback subject, and +// declared perturbation generation; there is no separately writable string ID that can let an old +// receipt qualify changed policy bytes. Ordinary convergence requires the live canonical config +// digest to equal the projection-derived admitted digest; durability qualification is a separate +// post-apply gate and never uses converge-noop as an activation premise. + fn bmc_fan_policy_projection_durability_admission( host: HostIdentity, capability: BmcFanPolicyProjectionCapability, @@ -467,5 +498,3 @@ fn bmc_fan_policy_projection_durability_admission( }, } } - -data bmc_fan_projection_exact_version_note: String = "A fleet firmware track says which states are valid; it does not select a JSON realizer. A projection capability is admitted only for an exact board, firmware version, desired semantic curve, a BmcProjectedConfigContentIdentity row binding probed curve bytes to cited sha256 hex, config path, controller service, zone control path, aggregate fan PID entry, and installed tach-input topology witnessed together. bmc_fan_projected_config_digest derives the digest only when curve, intent curve, realization, config path, and content_identity match exactly; it never reads observation. bmc_fan_admitted_config_digest takes the intent curve and an optional caller-supplied projection-derived digest at the reconcile site; it returns AdmittedConfigDigestUnavailable when the curve is not the modeled intent curve or the digest is absent. bmc_fan_witness_digest_mismatch_sentinel is witness-only observation input for live-digest mismatch controls, not an admitted digest authority. Its durability capability and admission are derived from the same realization content identity, capability version/hash, lifecycle boundaries, executable publication protocol, target host, readback subject, and declared perturbation generation; there is no separately writable string ID that can let an old receipt qualify changed policy bytes. Ordinary convergence requires the live canonical config digest to equal the projection-derived admitted digest; durability qualification is a separate post-apply gate and never uses converge-noop as an activation premise." diff --git a/dag/gunbc/bmc/bmc_intent.dag b/dag/gunbc/bmc/bmc_intent.dag index 497ce08a658..1df49eba2de 100644 --- a/dag/gunbc/bmc/bmc_intent.dag +++ b/dag/gunbc/bmc/bmc_intent.dag @@ -37,7 +37,14 @@ type BmcFanAcousticPolicy { emergency_full_duty_at_or_below: Celsius } -data bmc_fan_acoustic_policy_reframe_note: String = "Acoustic safety here is engage-cooling-early, not minimize-duty-until-late. The empirical basis (fleet incident 2026-07-30): holding a low duty across a wide band and only ramping near the emergency zone lets the SoC climb hot, and the controller then panics to maximum duty — the loudest outcome. So the policy asserts the curve has engaged substantial cooling (cooling_engaged_duty_floor) by a moderate control temperature (cooling_engaged_by_temperature), which keeps the chip steady-cool and inaudible, and rejects the idle-until-panic shape by construction. It is NOT a low-duty ceiling: a curve that stays quiet by staying idle fails this policy." +// Acoustic safety here is engage-cooling-early, not minimize-duty-until-late. The empirical basis +// (fleet incident 2026-07-30): holding a low duty across a wide band and only ramping near the +// emergency zone lets the SoC climb hot, and the controller then panics to maximum duty — the +// loudest outcome. So the policy asserts the curve has engaged substantial cooling +// (cooling_engaged_duty_floor) by a moderate control temperature (cooling_engaged_by_temperature), +// which keeps the chip steady-cool and inaudible, and rejects the idle-until-panic shape by +// construction. It is NOT a low-duty ceiling: a curve that stays quiet by staying idle fails this +// policy. type BmcFanThermalGuard { processor_rollback_temperature: Celsius @@ -196,6 +203,34 @@ fn fleet_fan_policy_is_thermally_bounded() -> Bool { && (celsius_count(fleet_fan_acoustic_policy.emergency_full_duty_at_or_below) < celsius_count(ampere_altra_max_m12830_thermal_limits.thermal_throttle_temperature)) } +// The single fleet-wide BMC desired fan state for the ALTRAD8UD/M128-30 fleet: the vendor stock +// TEMP_SOC curve carried by srv3's OpenBMC 2.07.00 factory config. The curve engages cooling +// progressively — 30 percent at/below 40C, 40 at 55C, 50 at 65C, 75 at 75C, and full 100 percent at +// 85C — with the cited controller's 1C rising and falling hysteresis. It holds the SoC +// steady-moderate (about 4800 RPM at the 50 percent step, inaudible, roughly 65C under load) rather +// than idling until a late panic ramp: the empirically loud outcome (fleet incident 2026-07-30) +// came from a stripped onboarding overwrite that minimized duty — 30 percent held through 90C — on +// a hot chip, which then drove the controller to maximum. Sensor loss requests the stepwise +// controller's 75 percent; a zone-controller loss requests the vendor's 30 percent zone failsafe, +// with the independent thermal guard (host rollback at 100C junction / 90C component) as the +// backstop. Full cooling is effectively selected below the processor's cited 100C +// maximum-continuous junction temperature. This is desired policy, not a factory observation and +// not an acoustic measurement: inaudibility is a live acceptance criterion because different fan +// models produce different sound at one PWM duty. The same curve is validated on both the 2.07.00 +// stock realization (srv3) and the 3.22.00 realization (srv4), so a fan change never requires a BMC +// firmware track change. Firmware is therefore unpinned (BmcFirmwareUnpinned): srv3 is not forced +// off its known-good 2.07.00 and srv4 is not forced off 3.22.00 for fan reasons, and the +// heterogeneous fleet (2.07.00 + 3.22.00) both satisfy the intent. The fan realizer admits an +// unpinned intent and qualifies each host against a projection whose firmware matches that host +// (bmc_fan_firmware_blocks_realizer); it still refuses to realize fans on a host that has not yet +// converged to a PINNED track, because a firmware update would replace the flash and wipe the fan +// config. A specific track, if ever required, is a separate non-fan intent, and the +// firmware-classification machinery still detects drift and builds typed update plans for a pinned +// intent. Its declared lifetime currently requires preservation across a graceful BMC control-plane +// restart; power loss, forced restart, firmware replacement, and factory reset are different +// boundaries and remain unclaimed until separately qualified. Firmware application still requires +// an exact approved release artifact. + data fleet_bmc_valid_state: BmcControlPlaneIntent = BmcControlPlaneIntent { firmware: BmcFirmwareUnpinned, fan_policy: BmcFanPolicyIntent { @@ -206,5 +241,3 @@ data fleet_bmc_valid_state: BmcControlPlaneIntent = BmcControlPlaneIntent { processor_thermal_limits: ampere_altra_max_m12830_thermal_limits, }, } - -data fleet_bmc_valid_state_note: String = "The single fleet-wide BMC desired fan state for the ALTRAD8UD/M128-30 fleet: the vendor stock TEMP_SOC curve carried by srv3's OpenBMC 2.07.00 factory config. The curve engages cooling progressively — 30 percent at/below 40C, 40 at 55C, 50 at 65C, 75 at 75C, and full 100 percent at 85C — with the cited controller's 1C rising and falling hysteresis. It holds the SoC steady-moderate (about 4800 RPM at the 50 percent step, inaudible, roughly 65C under load) rather than idling until a late panic ramp: the empirically loud outcome (fleet incident 2026-07-30) came from a stripped onboarding overwrite that minimized duty — 30 percent held through 90C — on a hot chip, which then drove the controller to maximum. Sensor loss requests the stepwise controller's 75 percent; a zone-controller loss requests the vendor's 30 percent zone failsafe, with the independent thermal guard (host rollback at 100C junction / 90C component) as the backstop. Full cooling is effectively selected below the processor's cited 100C maximum-continuous junction temperature. This is desired policy, not a factory observation and not an acoustic measurement: inaudibility is a live acceptance criterion because different fan models produce different sound at one PWM duty. The same curve is validated on both the 2.07.00 stock realization (srv3) and the 3.22.00 realization (srv4), so a fan change never requires a BMC firmware track change. Firmware is therefore unpinned (BmcFirmwareUnpinned): srv3 is not forced off its known-good 2.07.00 and srv4 is not forced off 3.22.00 for fan reasons, and the heterogeneous fleet (2.07.00 + 3.22.00) both satisfy the intent. The fan realizer admits an unpinned intent and qualifies each host against a projection whose firmware matches that host (bmc_fan_firmware_blocks_realizer); it still refuses to realize fans on a host that has not yet converged to a PINNED track, because a firmware update would replace the flash and wipe the fan config. A specific track, if ever required, is a separate non-fan intent, and the firmware-classification machinery still detects drift and builds typed update plans for a pinned intent. Its declared lifetime currently requires preservation across a graceful BMC control-plane restart; power loss, forced restart, firmware replacement, and factory reset are different boundaries and remain unclaimed until separately qualified. Firmware application still requires an exact approved release artifact." diff --git a/dag/gunbc/bmc/bmc_netboot_serve.dag b/dag/gunbc/bmc/bmc_netboot_serve.dag index d1ee737cca7..ec0e889d16b 100644 --- a/dag/gunbc/bmc/bmc_netboot_serve.dag +++ b/dag/gunbc/bmc/bmc_netboot_serve.dag @@ -109,7 +109,10 @@ fn bmc_netboot_nocloud_user_data(plan: BmcNetbootServePlan) -> String { autoinstall_user_data(payload: plan.autoinstall) } -data bmc_transport_interpretation_note: String = "The BMC fleet this module targets ships OpenBMC/Dropbear (gunbc.remote_shell_command openbmc_dropbear_interpretation_note), so the interpretation is a determined fact of THIS deployment, not Unknown -- named explicitly on SshExec rather than left for command_over_transport to guess (DESIGN 5)." +// The BMC fleet this module targets ships OpenBMC/Dropbear (gunbc.remote_shell_command +// openbmc_dropbear_interpretation_note), so the interpretation is a determined fact of THIS +// deployment, not Unknown -- named explicitly on SshExec rather than left for +// command_over_transport to guess (DESIGN 5). fn bmc_transport(plan: BmcNetbootServePlan) -> CommandTransport { SshExec { @@ -149,7 +152,6 @@ fn bmc_ssh_target(plan: BmcNetbootServePlan) -> NonEmptyStr { join(["root@", plan.bmc_host as String], "") as NonEmptyStr } - data bmc_netboot_provision_scaffold: Disposition = Scaffold { dissolves_to: RealizationDispatch, bind: DeclarationRef { @@ -210,7 +212,6 @@ func bmc_netboot_stage_inline(plan: BmcNetbootServePlan, remote_path: NonEmptySt return bmc_netboot_put_path(plan: plan, local: local, remote_path: remote_path) } - data bmc_path_resolved_mkdir_applet: NonEmptyStr = "mkdir" // The BMC's mkdir is a PATH-resolved BusyBox applet, deliberately not an absolute path: this diff --git a/dag/gunbc/build_cache/build_cache_endpoint_observe.dag b/dag/gunbc/build_cache/build_cache_endpoint_observe.dag index ba53a9e00ed..9846c7fd713 100644 --- a/dag/gunbc/build_cache/build_cache_endpoint_observe.dag +++ b/dag/gunbc/build_cache/build_cache_endpoint_observe.dag @@ -65,13 +65,42 @@ import product.placement_supply { HostIdentity } data build_cache_endpoint_observe_scope_disposition: Disposition = SingleAuthority -data observe_is_the_missing_producer_note: String = "THIS MODULE IS THE PRODUCER TWO PROVEN-BUT-UNWIRED AUTHORITIES WERE WAITING ON. gunbc.build_cache_ensure admit_ensure_build_cache_instance takes a BuildCacheServerPlacement, an EndpointPathState and an ObservationCurrency; gunbc.build_cache_endpoint_observation could produce none of them and said so in its own body, returning ProcTableUnreadable with the detail 'shell transport available but /proc endpoint-owner walk has no host_effect producer yet'. So the admission was reachable only from tests, and gunbc.host_effect_realize realize_provision_build_cache rendered and restarted the unit unconditionally instead. That is the rung problem the shell-to-dag census C-note names: the refusal a live CI-capacity incident was fixed by is computed by a function no production caller called.\n\nWHAT IS HERE AND WHAT IS NOT. Everything below is a PURE DECODE of read text into the modeled states -- no transport, no effect, no host. The reads themselves are performed by gunbc.host_effect_realize, which is where a transport already exists, and are handed back as BuildCacheEndpointReads. Splitting it that way is not tidiness: it makes every arm of the decode reachable from a fixture, so the discriminating REDs run against specimen text rather than against a host, and it keeps this module free of the effect graph it would otherwise have to import.\n\nEVERY READ IS NON-MUTATING, AND THAT IS A CORRECTNESS PROPERTY RATHER THAN A COURTESY. The one probe that would be easiest to reach for -- sccache --show-stats -- is refused as evidence anywhere in this lane, because extdeps.cache.sccache sccache_stats_query_does_not_spawn_note records by measurement that it prints a full table whether or not any server exists. It therefore cannot establish absence, which is precisely the question the path axis asks. A real compile WOULD establish it and is a mutation. Neither is admissible." +// THIS MODULE IS THE PRODUCER TWO PROVEN-BUT-UNWIRED AUTHORITIES WERE WAITING ON. +// gunbc.build_cache_ensure admit_ensure_build_cache_instance takes a BuildCacheServerPlacement, an +// EndpointPathState and an ObservationCurrency; gunbc.build_cache_endpoint_observation could +// produce none of them and said so in its own body, returning ProcTableUnreadable with the detail +// 'shell transport available but /proc endpoint-owner walk has no host_effect producer yet'. So the +// admission was reachable only from tests, and gunbc.host_effect_realize +// realize_provision_build_cache rendered and restarted the unit unconditionally instead. That is +// the rung problem the shell-to-dag census C-note names: the refusal a live CI-capacity incident +// was fixed by is computed by a function no production caller called. +// +// WHAT IS HERE AND WHAT IS NOT. Everything below is a PURE DECODE of read text into the modeled +// states -- no transport, no effect, no host. The reads themselves are performed by +// gunbc.host_effect_realize, which is where a transport already exists, and are handed back as +// BuildCacheEndpointReads. Splitting it that way is not tidiness: it makes every arm of the decode +// reachable from a fixture, so the discriminating REDs run against specimen text rather than +// against a host, and it keeps this module free of the effect graph it would otherwise have to +// import. +// +// EVERY READ IS NON-MUTATING, AND THAT IS A CORRECTNESS PROPERTY RATHER THAN A COURTESY. The one +// probe that would be easiest to reach for -- sccache --show-stats -- is refused as evidence +// anywhere in this lane, because extdeps.cache.sccache sccache_stats_query_does_not_spawn_note +// records by measurement that it prints a full table whether or not any server exists. It therefore +// cannot establish absence, which is precisely the question the path axis asks. A real compile +// WOULD establish it and is a mutation. Neither is admissible. type HostRead = HostReadText { text: String } | HostReadFailed { detail: NonEmptyStr } -data host_read_carries_failure_not_emptiness_note: String = "A FAILED READ IS NOT AN EMPTY READ, and conflating them is the empty-observation narrow DESIGN names beside the absorbing fallback. find printing nothing means no process holds the socket; find not running, or being denied, also prints nothing on stdout. If both arrive as the same String the decoder answers 'nothing is listening' to a question it never got an answer to, and the caller then unlinks a socket or starts a unit on that basis. So the runner classifies exit status into these two arms before any text reaches a decoder, and every decode of HostReadFailed lands on an Unavailable/Undecided arm rather than on an absence." +// A FAILED READ IS NOT AN EMPTY READ, and conflating them is the empty-observation narrow DESIGN +// names beside the absorbing fallback. find printing nothing means no process holds the socket; +// find not running, or being denied, also prints nothing on stdout. If both arrive as the same +// String the decoder answers 'nothing is listening' to a question it never got an answer to, and +// the caller then unlinks a socket or starts a unit on that basis. So the runner classifies exit +// status into these two arms before any text reaches a decoder, and every decode of HostReadFailed +// lands on an Unavailable/Undecided arm rather than on an absence. type EndpointListenerRead = ListenerOwnerPid { pid: Nat } @@ -86,7 +115,13 @@ type EndpointPathKind | PathKindNoFilesystemPath | PathKindUnreadable { detail: NonEmptyStr } -data path_kind_no_filesystem_path_note: String = "A TCP ENDPOINT HAS NO PATH, AND THAT IS ITS OWN ARM RATHER THAN PathKindAbsent. Absent means we looked at a path and found nothing there, which licenses a bind; no-path means the question does not apply to this instance's endpoint shape at all. Collapsing them would make the two indistinguishable to a reader deciding whether an unlink could ever be relevant -- and gunbc.build_cache_endpoint_path already refuses a stale-socket unlink over a TCP endpoint positively for the same reason, so answering the path axis with a fabricated 'absent' here would put the contradiction one layer earlier where nothing refuses it." +// A TCP ENDPOINT HAS NO PATH, AND THAT IS ITS OWN ARM RATHER THAN PathKindAbsent. Absent means we +// looked at a path and found nothing there, which licenses a bind; no-path means the question does +// not apply to this instance's endpoint shape at all. Collapsing them would make the two +// indistinguishable to a reader deciding whether an unlink could ever be relevant -- and +// gunbc.build_cache_endpoint_path already refuses a stale-socket unlink over a TCP endpoint +// positively for the same reason, so answering the path axis with a fabricated 'absent' here would +// put the contradiction one layer earlier where nothing refuses it. type BuildCacheEndpointReads { boot_id: HostRead @@ -108,7 +143,27 @@ type BuildCacheEndpointSurvey { // --- argv construction ------------------------------------------------------ -data listener_chain_is_the_receipt_chain_note: String = "THIS IS THE CHAIN THE STEP-1 PLACEMENT RECEIPT EXECUTED BY HAND, and it is here rather than a tool that answers the same question in one call because the receipt and gunbc.build_cache_instance endpoint_ownership_note both specify it and both explicitly exclude ss, pgrep, head -1 and lsof. An earlier revision of this module DID use ss, on the stated ground that resolving a socket inode to its holder needs a glob over /proc and therefore a shell this lane may not emit. THAT GROUND WAS FALSE and was refuted by execution 2026-08-21, twice independently: find takes the whole walk as one argv with no shell (extdeps.shell shell.Find SocketInodeHolders), so the receipt's chain was reachable the whole time and the substitution bought nothing. The measurement that produced the error is worth as much as the one that corrected it -- the first probe used the bracketed pattern socket:[N], find read the brackets as a glob character class, it matched nothing, and 'found nothing' was accepted as confirmation that the walk could not be done. A zero with no nonzero beside it in the same run is not a measurement. It is recorded rather than quietly corrected because the false claim had already been written into three carrier notes and a PR body as a capability boundary, and a fabricated boundary is worse than a missing feature -- it tells the next person not to try.\n\nWHAT THE CORRECTION BUYS, concretely: the fd-holder step IS clause (3) of gunbc.build_cache_endpoint_path's stale-socket observer contract -- no open descriptor anywhere in /proc still resolves to the socket's inode. Under ss that clause was unreadable, so EndpointStaleSocket could never be produced and srv4's stranded socket refused forever. It is readable now, so the arm is produced and the admitted unlink is reachable." +// THIS IS THE CHAIN THE STEP-1 PLACEMENT RECEIPT EXECUTED BY HAND, and it is here rather than a +// tool that answers the same question in one call because the receipt and +// gunbc.build_cache_instance endpoint_ownership_note both specify it and both explicitly exclude +// ss, pgrep, head -1 and lsof. An earlier revision of this module DID use ss, on the stated ground +// that resolving a socket inode to its holder needs a glob over /proc and therefore a shell this +// lane may not emit. THAT GROUND WAS FALSE and was refuted by execution 2026-08-21, twice +// independently: find takes the whole walk as one argv with no shell (extdeps.shell shell.Find +// SocketInodeHolders), so the receipt's chain was reachable the whole time and the substitution +// bought nothing. The measurement that produced the error is worth as much as the one that +// corrected it -- the first probe used the bracketed pattern socket:[N], find read the brackets as +// a glob character class, it matched nothing, and 'found nothing' was accepted as confirmation that +// the walk could not be done. A zero with no nonzero beside it in the same run is not a +// measurement. It is recorded rather than quietly corrected because the false claim had already +// been written into three carrier notes and a PR body as a capability boundary, and a fabricated +// boundary is worse than a missing feature -- it tells the next person not to try. +// +// WHAT THE CORRECTION BUYS, concretely: the fd-holder step IS clause (3) of +// gunbc.build_cache_endpoint_path's stale-socket observer contract -- no open descriptor anywhere +// in /proc still resolves to the socket's inode. Under ss that clause was unreadable, so +// EndpointStaleSocket could never be produced and srv4's stranded socket refused forever. It is +// readable now, so the arm is produced and the admitted unlink is reachable. // THE READS ARE COMMANDS, NOT WORD LISTS. Each of these was a bare argv literal whose first element // happened to be a tool name; they now derive from the cited coreutils authorities, and the two @@ -190,7 +245,20 @@ fn nat_from_digits(digits: String, whence: String) -> EndpointListenerRead { } } -data proc_net_unix_columns_note: String = "/proc/net/unix IS COLUMN-STRUCTURED AND proc(5) FIXES THE COLUMNS, so this decode reads fields by their documented position rather than by scanning for a token: Num RefCount Protocol Flags Type St Inode Path, eight fields, with St the connection state and 01 meaning LISTEN. Reading a documented, versioned wire format positionally is not the positional citation DESIGN section 3 warns about -- that rule is about naming something the containment tree already names by where it sits, and a kernel-fixed column has no name to use instead. The PATH is matched exactly, never by prefix: /var/lib/ctrl/sccache-ci/server.sock and /var/lib/ctrl/sccache/server.sock are two different instances on the same host (gunbc.build_cache_instance ci_cache_instance and session_cache_instance), so a prefix match would classify one instance's server as the other's.\n\nA LINE WITH FEWER THAN EIGHT FIELDS IS UNNAMED, NOT UNMATCHED. An abstract-namespace or unnamed socket has no path column at all, which is ordinary and must not be read as a malformed table -- it simply is not our endpoint. What IS refused is a read that failed, because then the table was never seen." +// /proc/net/unix IS COLUMN-STRUCTURED AND proc(5) FIXES THE COLUMNS, so this decode reads fields by +// their documented position rather than by scanning for a token: Num RefCount Protocol Flags Type +// St Inode Path, eight fields, with St the connection state and 01 meaning LISTEN. Reading a +// documented, versioned wire format positionally is not the positional citation DESIGN section 3 +// warns about -- that rule is about naming something the containment tree already names by where it +// sits, and a kernel-fixed column has no name to use instead. The PATH is matched exactly, never by +// prefix: /var/lib/ctrl/sccache-ci/server.sock and /var/lib/ctrl/sccache/server.sock are two +// different instances on the same host (gunbc.build_cache_instance ci_cache_instance and +// session_cache_instance), so a prefix match would classify one instance's server as the other's. +// +// A LINE WITH FEWER THAN EIGHT FIELDS IS UNNAMED, NOT UNMATCHED. An abstract-namespace or unnamed +// socket has no path column at all, which is ordinary and must not be read as a malformed table -- +// it simply is not our endpoint. What IS refused is a read that failed, because then the table was +// never seen. data proc_net_unix_listen_state: String = "01" data proc_net_unix_path_field_index: Int = 7 @@ -264,7 +332,16 @@ fn listen_inode_at_path(read: HostRead, path: NonEmptyStr) -> EndpointInodeLooku } } -data fd_holder_is_clause_three_note: String = "THE FD-HOLDER READ ANSWERS TWO QUESTIONS AT ONCE, AND THAT IS WHY IT IS ONE READ. Asked of a LISTENING socket's inode it names the endpoint owner -- the pid whose cgroup bounds every cache-miss compile on the host, which is the whole placement axis. Asked of a socket that has NO listen entry it answers clause (3) of the stale-socket contract: whether any process still holds the socket open without listening on it. An empty result is therefore load-bearing evidence in one direction and a refusal in the other, which is exactly why the read's FAILURE must be distinguishable from its EMPTINESS -- see host_read_carries_failure_not_emptiness_note. find exits 0 with empty stdout when nothing matches, and the runner classifies a nonzero exit as HostReadFailed, so the two states arrive here as different values rather than as one empty string." +// THE FD-HOLDER READ ANSWERS TWO QUESTIONS AT ONCE, AND THAT IS WHY IT IS ONE READ. Asked of a +// LISTENING socket's inode it names the endpoint owner -- the pid whose cgroup bounds every +// cache-miss compile on the host, which is the whole placement axis. Asked of a socket that has NO +// listen entry it answers clause (3) of the stale-socket contract: whether any process still holds +// the socket open without listening on it. An empty result is therefore load-bearing evidence in +// one direction and a refusal in the other, which is exactly why the read's FAILURE must be +// distinguishable from its EMPTINESS -- see host_read_carries_failure_not_emptiness_note. find +// exits 0 with empty stdout when nothing matches, and the runner classifies a nonzero exit as +// HostReadFailed, so the two states arrive here as different values rather than as one empty +// string. fn fd_holder_of_find_output(read: HostRead) -> EndpointListenerRead { match read { @@ -290,7 +367,10 @@ fn fd_holder_of_find_output(read: HostRead) -> EndpointListenerRead { } } -data pid_is_the_second_segment_note: String = "find prints the matching link's PATH -- /proc//fd/ -- so the pid is the second segment, read by splitting on / rather than by a character offset. Splitting is total: a line that does not have the shape yields no digits and refuses, rather than producing a pid from whatever happened to be there." +// find prints the matching link's PATH -- /proc//fd/ -- so the pid is the second segment, +// read by splitting on / rather than by a character offset. Splitting is total: a line that does +// not have the shape yields no digits and refuses, rather than producing a pid from whatever +// happened to be there. fn pid_of_proc_fd_path(fd_path: String) -> EndpointListenerRead { match fd_path.split("/").skip(2).first() { @@ -303,7 +383,13 @@ fn pid_of_proc_fd_path(fd_path: String) -> EndpointListenerRead { } } -data stat_format_is_the_type_word_note: String = "`stat -c %F` PRINTS THE OBJECT TYPE AS A WORD -- `socket`, `regular file`, `directory`, `symbolic link` -- so the path axis is decided by reading it rather than by a chain of test -e/-S calls. One argv, one round trip, and the non-socket answer arrives NAMED: EndpointPathConflict quotes the word, so an operator sees that the endpoint path is a directory rather than merely that it is not a socket. A nonzero exit is absence, which is the one place in this module where a failed command is a positive reading rather than an unavailability -- and it is safe exactly because stat's failure to stat a path it was asked for by name means no object is there." +// `stat -c %F` PRINTS THE OBJECT TYPE AS A WORD -- `socket`, `regular file`, `directory`, `symbolic +// link` -- so the path axis is decided by reading it rather than by a chain of test -e/-S calls. +// One argv, one round trip, and the non-socket answer arrives NAMED: EndpointPathConflict quotes +// the word, so an operator sees that the endpoint path is a directory rather than merely that it is +// not a socket. A nonzero exit is absence, which is the one place in this module where a failed +// command is a positive reading rather than an unavailability -- and it is safe exactly because +// stat's failure to stat a path it was asked for by name means no object is there. data stat_socket_type_word: String = "socket" @@ -348,7 +434,13 @@ fn currency_of_boot_id_read(read: HostRead) -> ObservationCurrency { } } -data currency_is_read_first_and_never_derived_note: String = "CURRENCY IS THE BOOT ID, READ FROM THE HOST, AND THERE IS NO ObservationStale ARM PRODUCED HERE. This observer performs one read pass, so every fact it returns is as current as that pass; staleness is a property of a CACHED observation replayed later, and nothing in this producer caches. The arm exists on the type for the consumer that will replay one, and producing it here would be inventing an age this pass cannot have. What this producer CAN fail at is knowing the boot id at all, and that is ObservationCurrencyUnknown -- which the unlink admission refuses exactly as it refuses Stale, so an unreadable boot id costs a refusal rather than a race." +// CURRENCY IS THE BOOT ID, READ FROM THE HOST, AND THERE IS NO ObservationStale ARM PRODUCED HERE. +// This observer performs one read pass, so every fact it returns is as current as that pass; +// staleness is a property of a CACHED observation replayed later, and nothing in this producer +// caches. The arm exists on the type for the consumer that will replay one, and producing it here +// would be inventing an age this pass cannot have. What this producer CAN fail at is knowing the +// boot id at all, and that is ObservationCurrencyUnknown -- which the unlink admission refuses +// exactly as it refuses Stale, so an unreadable boot id costs a refusal rather than a race. // --- cgroup classification -------------------------------------------------- @@ -375,7 +467,14 @@ fn unit_name_of_cgroup_path(cgroup_path: NonEmptyStr) -> String { if leaf.ends_with(systemd_unit_suffix) { leaf } else { "" } } -data runner_slot_index_is_decided_against_current_intent_note: String = "THE LIFECYCLE IS A QUESTION ABOUT TODAY'S FLEET, ASKED AT CLASSIFICATION TIME. gunbc.build_cache_instance runner_slot_lifecycle_note fixes the rule -- a slot is retired exactly when it does not appear in the fleet's desired slots now, not when someone remembers it being decommissioned -- so the index parsed out of the cgroup is compared against gunbc_runner_slots_per_host for the host the instance names. That comparison is why the coproduct can carry a slot and a lifecycle together and never half-filled: finding the slot and deciding its lifecycle are the same act (review 46217). A cgroup whose unit looks like a runner slot but whose index does not parse is OwnerContextUndecided, never a fabricated index." +// THE LIFECYCLE IS A QUESTION ABOUT TODAY'S FLEET, ASKED AT CLASSIFICATION TIME. +// gunbc.build_cache_instance runner_slot_lifecycle_note fixes the rule -- a slot is retired exactly +// when it does not appear in the fleet's desired slots now, not when someone remembers it being +// decommissioned -- so the index parsed out of the cgroup is compared against +// gunbc_runner_slots_per_host for the host the instance names. That comparison is why the coproduct +// can carry a slot and a lifecycle together and never half-filled: finding the slot and deciding +// its lifecycle are the same act (review 46217). A cgroup whose unit looks like a runner slot but +// whose index does not parse is OwnerContextUndecided, never a fabricated index. fn runner_slot_index_text(unit_name: String) -> String { match read_runner_unit_name(observed: unit_name as NonEmptyStr) { @@ -492,7 +591,13 @@ fn owner_context_of_cgroup_read( // --- process identity ------------------------------------------------------- -data proc_stat_starttime_note: String = "THE PROCESS START TIME IS FIELD 22 OF /proc/PID/stat, READ FROM AFTER THE LAST `)`. Field 2 is the executable name in parentheses and it may itself contain spaces and parentheses, so splitting the whole line on spaces is wrong for exactly the processes whose names are adversarial. proc(5) fixes the shape: everything after the FINAL `)` is fields 3 onward, separated by single spaces, so starttime is the 20th token of that tail. A tail too short to hold 20 tokens is a refusal, not a zero -- a fabricated start time would make two observations of DIFFERENT processes compare equal, which is the one thing ProcessIdentity exists to prevent." +// THE PROCESS START TIME IS FIELD 22 OF /proc/PID/stat, READ FROM AFTER THE LAST `)`. Field 2 is +// the executable name in parentheses and it may itself contain spaces and parentheses, so splitting +// the whole line on spaces is wrong for exactly the processes whose names are adversarial. proc(5) +// fixes the shape: everything after the FINAL `)` is fields 3 onward, separated by single spaces, +// so starttime is the 20th token of that tail. A tail too short to hold 20 tokens is a refusal, not +// a zero -- a fabricated start time would make two observations of DIFFERENT processes compare +// equal, which is the one thing ProcessIdentity exists to prevent. fn text_after_last(s: String, marker: String) -> String { match s.split(marker).last() { @@ -598,7 +703,27 @@ fn endpoint_owner_observation( } } -data path_and_owner_are_joined_not_duplicated_note: String = "THE PATH AXIS AND THE OWNER AXIS ARE DERIVED FROM ONE PAIR OF READS, AND THAT IS A JOIN RATHER THAN A SECOND OPINION. gunbc.build_cache_endpoint_path's authority note fixes why the axes are separate: EndpointStaleSocket and EndpointPathAbsent both project to EndpointAbsent on the ownership axis, so ownership cannot carry the distinction actuation needs. What they must never do is disagree about whether a listener exists, which is what would happen if each ran its own probe. So /proc/net/unix is read once and the fd-holder walk is run once, and both axes are derived from those -- the owner axis asks who, the path axis asks what is at the path.\n\nALL FOUR CLAUSES OF THE EndpointStaleSocket OBSERVER CONTRACT ARE READ. Clause (1), stat reports a socket. Clause (2), no LISTEN entry for the exact path in /proc/net/unix. Clause (3), no /proc/*/fd link resolves to the socket's inode -- the fd-holder walk returning empty, which is why its empty result had to be distinguishable from its failure. Clause (4), one boot id across the pass; an unreadable boot id degrades the survey to ObservationCurrencyUnknown, which the unlink admission refuses. So this producer DOES return EndpointStaleSocket, and srv4's stranded socket becomes an admitted unlink rather than a permanent refusal.\n\nAn earlier revision of this note declared clause (3) unreadable and called that a bounded residue. It was not bounded and it was not a residue -- it was a false capability claim, refuted by executing the find walk (listener_chain_is_the_receipt_chain_note carries the measurement). The correction is recorded here rather than the sentence quietly deleted, because that claim shipped in a PR body and two other carriers as a reason not to try." +// THE PATH AXIS AND THE OWNER AXIS ARE DERIVED FROM ONE PAIR OF READS, AND THAT IS A JOIN RATHER +// THAN A SECOND OPINION. gunbc.build_cache_endpoint_path's authority note fixes why the axes are +// separate: EndpointStaleSocket and EndpointPathAbsent both project to EndpointAbsent on the +// ownership axis, so ownership cannot carry the distinction actuation needs. What they must never +// do is disagree about whether a listener exists, which is what would happen if each ran its own +// probe. So /proc/net/unix is read once and the fd-holder walk is run once, and both axes are +// derived from those -- the owner axis asks who, the path axis asks what is at the path. +// +// ALL FOUR CLAUSES OF THE EndpointStaleSocket OBSERVER CONTRACT ARE READ. Clause (1), stat reports +// a socket. Clause (2), no LISTEN entry for the exact path in /proc/net/unix. Clause (3), no +// /proc/*/fd link resolves to the socket's inode -- the fd-holder walk returning empty, which is +// why its empty result had to be distinguishable from its failure. Clause (4), one boot id across +// the pass; an unreadable boot id degrades the survey to ObservationCurrencyUnknown, which the +// unlink admission refuses. So this producer DOES return EndpointStaleSocket, and srv4's stranded +// socket becomes an admitted unlink rather than a permanent refusal. +// +// An earlier revision of this note declared clause (3) unreadable and called that a bounded +// residue. It was not bounded and it was not a residue -- it was a false capability claim, refuted +// by executing the find walk (listener_chain_is_the_receipt_chain_note carries the measurement). +// The correction is recorded here rather than the sentence quietly deleted, because that claim +// shipped in a PR body and two other carriers as a reason not to try. data socket_held_open_without_listening_detail: NonEmptyStr = "the endpoint path holds a socket with no LISTEN entry, but a process still holds an open descriptor on its inode -- so it is NOT stale. This is exactly the state clause (3) of the stale-socket contract exists to catch: unlinking here severs a live process's socket and leaves it running and unreachable. Refusing, with the holder observable in the same walk" @@ -628,7 +753,22 @@ fn stale_or_held(reads: BuildCacheEndpointReads) -> EndpointPathState { } } -data holder_is_not_listener_note: String = "AN OBSERVED HOLDER IS NOT A LISTENER, AND THIS ARM IS WHERE THAT WAS CONFLATED. The owner observation is built from the fd-holder walk -- some process has an open descriptor on the socket's inode -- while LISTENING is a separate read, the St==01 row for this exact path in /proc/net/unix. An earlier revision of endpoint_path_state answered EndpointListening from the owner observation alone, so a socket held open by a process that is NOT accepting connections was reported as a healthy listening endpoint. Both states refuse, so nothing was destroyed by it, but they refuse for DIFFERENT reasons and only one of them is true: EnsureEndpointOccupied says 'a live server is serving here, leave it alone', which is the wrong sentence to put in front of an operator whose cache is unreachable precisely because nothing is serving.\n\nIt is the state-space conflation DESIGN names, on the two reads that must stay separate for the whole stale-socket contract to mean anything -- clause (2) is 'no LISTEN entry' and clause (3) is 'no fd holder', and a decoder that lets either one answer for the other has collapsed a four-clause contract into a two-clause one. It was found by a witness whose fixture pairs a no-listen /proc/net/unix text with a non-empty holder walk, which is exactly the combination neither clause alone can distinguish." +// AN OBSERVED HOLDER IS NOT A LISTENER, AND THIS ARM IS WHERE THAT WAS CONFLATED. The owner +// observation is built from the fd-holder walk -- some process has an open descriptor on the +// socket's inode -- while LISTENING is a separate read, the St==01 row for this exact path in +// /proc/net/unix. An earlier revision of endpoint_path_state answered EndpointListening from the +// owner observation alone, so a socket held open by a process that is NOT accepting connections was +// reported as a healthy listening endpoint. Both states refuse, so nothing was destroyed by it, but +// they refuse for DIFFERENT reasons and only one of them is true: EnsureEndpointOccupied says 'a +// live server is serving here, leave it alone', which is the wrong sentence to put in front of an +// operator whose cache is unreachable precisely because nothing is serving. +// +// It is the state-space conflation DESIGN names, on the two reads that must stay separate for the +// whole stale-socket contract to mean anything -- clause (2) is 'no LISTEN entry' and clause (3) is +// 'no fd holder', and a decoder that lets either one answer for the other has collapsed a +// four-clause contract into a two-clause one. It was found by a witness whose fixture pairs a +// no-listen /proc/net/unix text with a non-empty holder walk, which is exactly the combination +// neither clause alone can distinguish. fn socket_owner_path_state(reads: BuildCacheEndpointReads, owner: ProcessIdentity) -> EndpointPathState { match reads.listen_inode { @@ -726,6 +866,14 @@ fn survey_build_cache_endpoint( } } +// A HOST THAT COULD NOT BE REACHED PRODUCES A FULL READS RECORD OF FAILURES RATHER THAN AN EARLY +// RETURN. The temptation is to answer unreachability with one ServerPlacementUnknown at the top of +// the producer, which is the right verdict and the wrong construction: it puts a second path +// through the decode that no fixture exercises and that no future arm addition is forced to +// consider. Feeding the ordinary decode a record whose every field is HostReadFailed reaches the +// same verdict THROUGH the arms that a live refusal would take, so the unreachable case and the +// denied-read case are one code path and stay one forever. + fn unreachable_host_reads(detail: NonEmptyStr) -> BuildCacheEndpointReads { BuildCacheEndpointReads { boot_id: HostReadFailed { detail: detail }, @@ -738,5 +886,3 @@ fn unreachable_host_reads(detail: NonEmptyStr) -> BuildCacheEndpointReads { owner_executable: HostReadFailed { detail: detail }, } } - -data unreachable_is_a_read_state_not_a_short_circuit_note: String = "A HOST THAT COULD NOT BE REACHED PRODUCES A FULL READS RECORD OF FAILURES RATHER THAN AN EARLY RETURN. The temptation is to answer unreachability with one ServerPlacementUnknown at the top of the producer, which is the right verdict and the wrong construction: it puts a second path through the decode that no fixture exercises and that no future arm addition is forced to consider. Feeding the ordinary decode a record whose every field is HostReadFailed reaches the same verdict THROUGH the arms that a live refusal would take, so the unreachable case and the denied-read case are one code path and stay one forever." diff --git a/dag/gunbc/build_cache/build_cache_endpoint_path.dag b/dag/gunbc/build_cache/build_cache_endpoint_path.dag index 533739074dd..72c2ea22d8c 100644 --- a/dag/gunbc/build_cache/build_cache_endpoint_path.dag +++ b/dag/gunbc/build_cache/build_cache_endpoint_path.dag @@ -12,7 +12,26 @@ import extdeps.tools.gnu_coreutils { rm_force_command } data build_cache_endpoint_path_scope_disposition: Disposition = SingleAuthority -data endpoint_path_state_authority_note: String = "WHY THE ENDPOINT PATH NEEDS ITS OWN STATE MACHINE, AND WHY EndpointAbsent WAS NOT ENOUGH. gunbc.build_cache_instance's EndpointAbsent answers one question — is anybody listening — and that is the right question for PLACEMENT, because an endpoint with no listener has no owner whose cgroup could be classified. It is the wrong question for ACTUATION, and srv4 is the specimen that proves it. Observed 2026-08-01: no listener, no process, no unit, a server.sock file present on disk, and an error log whose entire contents are 'Address in use (os error 98)'. A server born inside a runner cgroup died with that cgroup without unlinking its socket, so every later server fails to bind and the whole cache tree holds one file. srv4 has been uncached since roughly 2026-07-26 for this reason alone.\n\nUnder EndpointAbsent, srv4 and a host that simply has no cache yet are the SAME state. They are not: one needs a unit started, the other needs a file removed FIRST and then a unit started, and starting the unit without removing the file reproduces error 98 forever. That is a state-space conflation of exactly the kind DESIGN's failure-mode list names, and its cost is measured rather than hypothetical — a host silently uncached for a week.\n\nSO THE PATH IS A SEPARATE AXIS FROM THE OWNER. EndpointPathState answers 'what is at this path', BuildCacheEndpointObservation answers 'who owns this endpoint'. They are not redundant readings of one fact: EndpointStaleSocket and EndpointPathAbsent both project to EndpointAbsent on the ownership axis, which is precisely the distinction the ownership axis is not built to carry." +// WHY THE ENDPOINT PATH NEEDS ITS OWN STATE MACHINE, AND WHY EndpointAbsent WAS NOT ENOUGH. +// gunbc.build_cache_instance's EndpointAbsent answers one question — is anybody listening — and +// that is the right question for PLACEMENT, because an endpoint with no listener has no owner whose +// cgroup could be classified. It is the wrong question for ACTUATION, and srv4 is the specimen that +// proves it. Observed 2026-08-01: no listener, no process, no unit, a server.sock file present on +// disk, and an error log whose entire contents are 'Address in use (os error 98)'. A server born +// inside a runner cgroup died with that cgroup without unlinking its socket, so every later server +// fails to bind and the whole cache tree holds one file. srv4 has been uncached since roughly +// 2026-07-26 for this reason alone. +// +// Under EndpointAbsent, srv4 and a host that simply has no cache yet are the SAME state. They are +// not: one needs a unit started, the other needs a file removed FIRST and then a unit started, and +// starting the unit without removing the file reproduces error 98 forever. That is a state-space +// conflation of exactly the kind DESIGN's failure-mode list names, and its cost is measured rather +// than hypothetical — a host silently uncached for a week. +// +// SO THE PATH IS A SEPARATE AXIS FROM THE OWNER. EndpointPathState answers 'what is at this path', +// BuildCacheEndpointObservation answers 'who owns this endpoint'. They are not redundant readings +// of one fact: EndpointStaleSocket and EndpointPathAbsent both project to EndpointAbsent on the +// ownership axis, which is precisely the distinction the ownership axis is not built to carry. type EndpointPathState = EndpointPathAbsent @@ -48,7 +67,24 @@ type StaleSocketUnlinkAdmission reason: StaleSocketUnlinkRefusal } -data unlink_admission_construction_note: String = "THE ADMISSION IS DERIVED FROM THE OBSERVED STATE, NOT CHECKED AGAINST FLAGS. An earlier shape for this carried a StaleSocketEvidence record of four Bools — path_is_socket, no_listener, no_fd_owner, observation_current — and admitted when all four were true. That is validation wearing construction's clothes: the four Bools are independently writable, an all-true record can be hand-authored with no observation behind it, and the check restates a constraint the observation already decided. It is the same defect the endpoint owner context carried before review 46217 collapsed three independent fields into one coproduct.\n\nSo admit_stale_socket_unlink takes the OBSERVED EndpointPathState and admits only from EndpointStaleSocket. The four facts are not inputs to be re-checked here; they are what the observer must establish in order to produce that arm at all, and they are stated on the observer's contract below rather than re-represented as data. Every other arm is a positively named refusal, so the refusal is typed, located and countable rather than a false Bool.\n\nWhat remains honestly open, and it is the same residual the activation receipt carries: .dag has no visibility modifiers, so a caller can hand-write EndpointStaleSocket with nothing behind it. That is an explicit lie in the source rather than a forgotten check. It closes when node/subtree visibility grants make this constructor private to its observer." +// THE ADMISSION IS DERIVED FROM THE OBSERVED STATE, NOT CHECKED AGAINST FLAGS. An earlier shape for +// this carried a StaleSocketEvidence record of four Bools — path_is_socket, no_listener, +// no_fd_owner, observation_current — and admitted when all four were true. That is validation +// wearing construction's clothes: the four Bools are independently writable, an all-true record can +// be hand-authored with no observation behind it, and the check restates a constraint the +// observation already decided. It is the same defect the endpoint owner context carried before +// review 46217 collapsed three independent fields into one coproduct. +// +// So admit_stale_socket_unlink takes the OBSERVED EndpointPathState and admits only from +// EndpointStaleSocket. The four facts are not inputs to be re-checked here; they are what the +// observer must establish in order to produce that arm at all, and they are stated on the +// observer's contract below rather than re-represented as data. Every other arm is a positively +// named refusal, so the refusal is typed, located and countable rather than a false Bool. +// +// What remains honestly open, and it is the same residual the activation receipt carries: .dag has +// no visibility modifiers, so a caller can hand-write EndpointStaleSocket with nothing behind it. +// That is an explicit lie in the source rather than a forgotten check. It closes when node/subtree +// visibility grants make this constructor private to its observer. // WHAT AN OBSERVER MUST ESTABLISH BEFORE IT MAY RETURN EndpointStaleSocket. All four, in this order, over a single non-mutating read: (1) the path exists and stat reports a SOCKET — anything else is EndpointPathConflict; (2) the exact path does not appear as a listening entry in /proc/net/unix with St==01 — if it does, the state is EndpointListening and the owner is resolvable; (3) no /proc/*/fd entry resolves to the path's inode, which catches a process holding the socket open without listening, the case a /proc/net/unix scan alone misses; (4) the whole read completed under one boot id with no intervening reboot. Failing to read any of them is EndpointPathObservationUnavailable, never a default to stale — an unreadable /proc is exactly when a bind race is most likely, not least. // @@ -137,7 +173,25 @@ fn stale_socket_unlink_command(admission: StaleSocketUnlinkAdmission) -> StaleSo } } -data no_unconditional_execstartpre_note: String = "WHY THE GENERATED UNIT MUST NOT CARRY ExecStartPre=/bin/rm -f server.sock. It is the obvious fix for srv4 and it is an absorbing fallback: it unlinks in EVERY path state rather than the one state where unlinking is correct. Under EndpointPathAbsent it is a harmless no-op, which is what makes it look safe in testing. Under EndpointPathConflict it destroys a file this lane did not create. Under EndpointListening — the state that occurs on every restart of a healthy unit, and on every double-start race — it severs a live server's socket, leaving a running process that no client can reach and that will not be replaced because it is still holding memory and still parenting compiles. The failure is silent in exactly the way the stale socket was silent, so the remedy would recreate the disease one layer up.\n\nThe deficit's frequency would also be zeroed by construction: an unconditional rm never reports that it removed something it should not have, so nobody would ever learn the unit was doing it. Under this admission the same situation produces a typed, located, countable refusal naming which state blocked it, so a host stuck behind a conflict is visible instead of quietly clobbered.\n\nThe unlink is therefore a SEPARATE admitted step that runs before the unit start and can refuse independently of it, not a line inside the unit. runner_activation's readiness receipt is the analogue: the expensive thing is not performing the action, it is performing it without the observation that licenses it." +// WHY THE GENERATED UNIT MUST NOT CARRY ExecStartPre=/bin/rm -f server.sock. It is the obvious fix +// for srv4 and it is an absorbing fallback: it unlinks in EVERY path state rather than the one +// state where unlinking is correct. Under EndpointPathAbsent it is a harmless no-op, which is what +// makes it look safe in testing. Under EndpointPathConflict it destroys a file this lane did not +// create. Under EndpointListening — the state that occurs on every restart of a healthy unit, and +// on every double-start race — it severs a live server's socket, leaving a running process that no +// client can reach and that will not be replaced because it is still holding memory and still +// parenting compiles. The failure is silent in exactly the way the stale socket was silent, so the +// remedy would recreate the disease one layer up. +// +// The deficit's frequency would also be zeroed by construction: an unconditional rm never reports +// that it removed something it should not have, so nobody would ever learn the unit was doing it. +// Under this admission the same situation produces a typed, located, countable refusal naming which +// state blocked it, so a host stuck behind a conflict is visible instead of quietly clobbered. +// +// The unlink is therefore a SEPARATE admitted step that runs before the unit start and can refuse +// independently of it, not a line inside the unit. runner_activation's readiness receipt is the +// analogue: the expensive thing is not performing the action, it is performing it without the +// observation that licenses it. fn unconditional_unlink_sketch(observed: EndpointPathState) -> Bool { match observed { @@ -149,7 +203,15 @@ fn unconditional_unlink_sketch(observed: EndpointPathState) -> Bool { } } -data unconditional_unlink_sketch_note: String = "THE RED CONTROL, KEPT LIVE RATHER THAN DESCRIBED. This is ExecStartPre=rm -f written as a predicate: it answers 'unlink?' with true in all five path states, because that is precisely what the shell line does. It exists so the witnesses can assert the DIFFERENCE between it and admit_stale_socket_unlink on the two states where the difference is destructive — EndpointListening and EndpointPathConflict — rather than asserting the admission's behaviour alone. An assertion that the admission refuses proves nothing about whether the refusal was needed; the contrast is what makes the wall discriminating. It is never called by production code and it is not a scaffold to dissolve: per DESIGN section 4b, a class's discriminating control stays enrolled as the executing evidence that the higher rung stays real." +// THE RED CONTROL, KEPT LIVE RATHER THAN DESCRIBED. This is ExecStartPre=rm -f written as a +// predicate: it answers 'unlink?' with true in all five path states, because that is precisely what +// the shell line does. It exists so the witnesses can assert the DIFFERENCE between it and +// admit_stale_socket_unlink on the two states where the difference is destructive — +// EndpointListening and EndpointPathConflict — rather than asserting the admission's behaviour +// alone. An assertion that the admission refuses proves nothing about whether the refusal was +// needed; the contrast is what makes the wall discriminating. It is never called by production code +// and it is not a scaffold to dissolve: per DESIGN section 4b, a class's discriminating control +// stays enrolled as the executing evidence that the higher rung stays real. type StaleSocketUnlinkOutcomeFold { ready: fn(ArgvCommand) -> R @@ -166,7 +228,11 @@ fn stale_socket_unlink_outcome_fold( } } -data unlink_outcome_fold_note: String = "One named elimination surface for the outcome rather than a match at each consumer (review 46784 / review 46655). The reason is not style: a fold record makes adding an arm a COMPILE ERROR at every consumer, because each literal must gain a field, while a scattered match keeps compiling and quietly stops covering. Earlier in this lane a missed ProbeEffectOnSubjectFold literal was caught by exactly that mechanism." +// One named elimination surface for the outcome rather than a match at each consumer (review 46784 +// / review 46655). The reason is not style: a fold record makes adding an arm a COMPILE ERROR at +// every consumer, because each literal must gain a field, while a scattered match keeps compiling +// and quietly stops covering. Earlier in this lane a missed ProbeEffectOnSubjectFold literal was +// caught by exactly that mechanism. fn admission_yields_unlink(admission: StaleSocketUnlinkAdmission) -> Bool { stale_socket_unlink_outcome_fold( diff --git a/dag/gunbc/build_cache/build_cache_ensure.dag b/dag/gunbc/build_cache/build_cache_ensure.dag index 1e545818670..e0e0b1850a2 100644 --- a/dag/gunbc/build_cache/build_cache_ensure.dag +++ b/dag/gunbc/build_cache/build_cache_ensure.dag @@ -33,7 +33,58 @@ type EnsureRefusal | EnsureUnlinkRefused { detail: NonEmptyStr } | EnsurePlacementUnknown { detail: NonEmptyStr } -data ensure_is_a_reconcile_not_an_install_note: String = "ENSURE MUST BE IDEMPOTENT, WHICH IS WHY EnsureAlreadyConverged IS ITS OWN ARM RATHER THAN A SUCCESSFUL RE-INSTALL. The membership-diff spine's reading applies unchanged here: desired versus observed, and Unchanged is the ABSENCE of a hunk, not a cheap version of Added. If a correctly-placed managed unit already owns the endpoint AND IS SERVING IT, writing the unit again and restarting it would tear down a healthy cache and re-cold every compile on the host to reach a state it was already in -- the most expensive possible no-op, and one that would fire on every convergence pass.\n\nCONVERGED MEANS SERVING, NOT MERELY PLACED, AND THAT IS A CORRECTION RATHER THAN A REFINEMENT (2026-08-22). An earlier revision of this note argued the opposite construction in this same paragraph: that placement is consulted FIRST and short-circuits before any path reasoning, because when the answer is nothing-to-do the path state is irrelevant and reading it could only produce a spurious refusal. That argument is wrong, and the tree had already measured why. gunbc.build_cache_endpoint_observe socket_owner_path_state produces EndpointPathConflict for a socket that a live process holds OPEN while nothing LISTENS on it, and placement is derived from the fd-holder walk rather than from the listen table -- so a process in our own intended unit's cgroup, wedged with its socket open and no listener, reads as ServerManagedUnitOwned. Under the placement-first short-circuit that host converged: no unit written, no restart issued, a receipt saying already_converged, and a cache no client can reach. The path state was not irrelevant; it carried the half of the question placement cannot answer.\n\nTHE DECISION, STATED AS THE CONTRACT: EnsureAlreadyConverged is minted only when BOTH axes agree -- the endpoint owner is our intended managed unit (placement) AND the endpoint is being LISTENED on (path). Placement answers WHO, the path answers WHETHER IT SERVES, and neither is derivable from the other. Every other combination under our own placement is a REFUSAL rather than a converge or a write, because a unit of ours that owns the endpoint without serving it is not a state ensure can fix by its own means: restarting it is an eviction of a live process that owns in-flight work, which the sibling occupied_endpoint_refuses_note already rules is a different operation from ensuring a unit exists. The refusal names the unit, so an operator sees exactly which of the host's servers is wedged.\n\nWHAT THIS DOES NOT ESTABLISH, NAMED SO THE RUNG IS HONEST (DESIGN section 4b). Serving here means a listener is bound on the configured endpoint by our unit. It does NOT establish that the daemon behind that listener RESPONDS, that it was started from the unit body this desired instance renders, or that its storage, capacity and principal still match the desired ones -- a unit file edited out of band keeps its name, and the name is all placement compares. Those are the postcondition axes gunbc.build_cache_provision_verdict already folds (DaemonUnresponsive, DaemonMisconfigured, DaemonPathShadowed), and the acted path consumes that fold after its ensure runs while this no-act path cannot: answering them needs a read this module may not emit, because sccache --show-stats is refused as evidence anywhere in this lane (it prints a full table whether or not a server exists) and the config read-back is behind the same typed-argv dissolve-on the rest of this lane's shell waits on (#5828). So the no-act path is strictly weaker than the acted path on those axes, and it is weaker LOUDLY rather than silently: it is named here, and it is the residue this contract change deliberately does not claim. NEXT-RUNG TRIGGER: a non-mutating readiness/identity read of the running server that the preflight can take, at which point the no-act arm routes through the same verdict authority the acted arm does and the two paths stop answering one question by two contracts.\n\nTHE ORDERING IS UNCHANGED FOR EVERY OTHER PLACEMENT. ServerPlacementUnknown still refuses before the path is consulted (placement_unknown_refuses_before_the_path_note), and the four positive non-ours placements still fall through to the path reasoning. What changed is only that being ours is no longer sufficient on its own." +// ENSURE MUST BE IDEMPOTENT, WHICH IS WHY EnsureAlreadyConverged IS ITS OWN ARM RATHER THAN A +// SUCCESSFUL RE-INSTALL. The membership-diff spine's reading applies unchanged here: desired versus +// observed, and Unchanged is the ABSENCE of a hunk, not a cheap version of Added. If a +// correctly-placed managed unit already owns the endpoint AND IS SERVING IT, writing the unit again +// and restarting it would tear down a healthy cache and re-cold every compile on the host to reach +// a state it was already in -- the most expensive possible no-op, and one that would fire on every +// convergence pass. +// +// CONVERGED MEANS SERVING, NOT MERELY PLACED, AND THAT IS A CORRECTION RATHER THAN A REFINEMENT +// (2026-08-22). An earlier revision of this note argued the opposite construction in this same +// paragraph: that placement is consulted FIRST and short-circuits before any path reasoning, +// because when the answer is nothing-to-do the path state is irrelevant and reading it could only +// produce a spurious refusal. That argument is wrong, and the tree had already measured why. +// gunbc.build_cache_endpoint_observe socket_owner_path_state produces EndpointPathConflict for a +// socket that a live process holds OPEN while nothing LISTENS on it, and placement is derived from +// the fd-holder walk rather than from the listen table -- so a process in our own intended unit's +// cgroup, wedged with its socket open and no listener, reads as ServerManagedUnitOwned. Under the +// placement-first short-circuit that host converged: no unit written, no restart issued, a receipt +// saying already_converged, and a cache no client can reach. The path state was not irrelevant; it +// carried the half of the question placement cannot answer. +// +// THE DECISION, STATED AS THE CONTRACT: EnsureAlreadyConverged is minted only when BOTH axes agree +// -- the endpoint owner is our intended managed unit (placement) AND the endpoint is being LISTENED +// on (path). Placement answers WHO, the path answers WHETHER IT SERVES, and neither is derivable +// from the other. Every other combination under our own placement is a REFUSAL rather than a +// converge or a write, because a unit of ours that owns the endpoint without serving it is not a +// state ensure can fix by its own means: restarting it is an eviction of a live process that owns +// in-flight work, which the sibling occupied_endpoint_refuses_note already rules is a different +// operation from ensuring a unit exists. The refusal names the unit, so an operator sees exactly +// which of the host's servers is wedged. +// +// WHAT THIS DOES NOT ESTABLISH, NAMED SO THE RUNG IS HONEST (DESIGN section 4b). Serving here means +// a listener is bound on the configured endpoint by our unit. It does NOT establish that the daemon +// behind that listener RESPONDS, that it was started from the unit body this desired instance +// renders, or that its storage, capacity and principal still match the desired ones -- a unit file +// edited out of band keeps its name, and the name is all placement compares. Those are the +// postcondition axes gunbc.build_cache_provision_verdict already folds (DaemonUnresponsive, +// DaemonMisconfigured, DaemonPathShadowed), and the acted path consumes that fold after its ensure +// runs while this no-act path cannot: answering them needs a read this module may not emit, because +// sccache --show-stats is refused as evidence anywhere in this lane (it prints a full table whether +// or not a server exists) and the config read-back is behind the same typed-argv dissolve-on the +// rest of this lane's shell waits on (#5828). So the no-act path is strictly weaker than the acted +// path on those axes, and it is weaker LOUDLY rather than silently: it is named here, and it is the +// residue this contract change deliberately does not claim. NEXT-RUNG TRIGGER: a non-mutating +// readiness/identity read of the running server that the preflight can take, at which point the +// no-act arm routes through the same verdict authority the acted arm does and the two paths stop +// answering one question by two contracts. +// +// THE ORDERING IS UNCHANGED FOR EVERY OTHER PLACEMENT. ServerPlacementUnknown still refuses before +// the path is consulted (placement_unknown_refuses_before_the_path_note), and the four positive +// non-ours placements still fall through to the path reasoning. What changed is only that being +// ours is no longer sufficient on its own. type EnsureBuildCacheAdmission = EnsureAlreadyConverged { desired: BuildCacheInstance, unit: NonEmptyStr } @@ -44,7 +95,26 @@ type EnsureBuildCacheAdmission } | EnsureBuildCacheRefused { instance: BuildCacheInstance, reason: EnsureRefusal } -data canonical_fold_note: String = "THE ELIMINATION SURFACE IS A FOLD, NOT A SCATTER OF MATCHES (review 46784, raised three times before it was taken). The predicates below previously each matched EnsureBuildCacheAdmission directly, which is legal — DESIGN section 4 names Match as one of the six closed behaviours — and that legality was the whole of my argument for keeping them. It was the wrong question. The right one is whether a coproduct with several consumers should have ONE named elimination surface, and it should: a fold record makes adding an arm a COMPILE ERROR at every consumer, because each literal must gain a field, whereas a scattered match silently keeps compiling and silently stops covering.\n\nThat is not theoretical here. Earlier in this same lane a fourth ProbeEffectOnSubjectFold literal in gunbc.claim_evidence_probe_rule was missed when an arm was added, and it was the FOLD RECORD that caught it — the compiler refused the incomplete literal. A hand-written match in that position would have gone on compiling against the old arm set. So the fold is the better construction for exactly the reason DESIGN prefers construction to validation, and the precedent to follow was already in the tree.\n\nBuildCacheServerPlacement is deliberately NOT folded here. It lives in gunbc.build_cache_instance with consumers already on main, so giving it a canonical fold is a broader refactor of a merged module rather than part of this change; placement_already_converged still matches it directly and is named as the residue that follow-up closes." +// THE ELIMINATION SURFACE IS A FOLD, NOT A SCATTER OF MATCHES (review 46784, raised three times +// before it was taken). The predicates below previously each matched EnsureBuildCacheAdmission +// directly, which is legal — DESIGN section 4 names Match as one of the six closed behaviours — and +// that legality was the whole of my argument for keeping them. It was the wrong question. The right +// one is whether a coproduct with several consumers should have ONE named elimination surface, and +// it should: a fold record makes adding an arm a COMPILE ERROR at every consumer, because each +// literal must gain a field, whereas a scattered match silently keeps compiling and silently stops +// covering. +// +// That is not theoretical here. Earlier in this same lane a fourth ProbeEffectOnSubjectFold literal +// in gunbc.claim_evidence_probe_rule was missed when an arm was added, and it was the FOLD RECORD +// that caught it — the compiler refused the incomplete literal. A hand-written match in that +// position would have gone on compiling against the old arm set. So the fold is the better +// construction for exactly the reason DESIGN prefers construction to validation, and the precedent +// to follow was already in the tree. +// +// BuildCacheServerPlacement is deliberately NOT folded here. It lives in gunbc.build_cache_instance +// with consumers already on main, so giving it a canonical fold is a broader refactor of a merged +// module rather than part of this change; placement_already_converged still matches it directly and +// is named as the residue that follow-up closes. type EndpointPreflightFold { clear_for_bind: R @@ -160,9 +230,43 @@ fn owned_but_not_serving( } } -data admission_carries_a_unit_not_its_text_note: String = "THE ADMISSION DECIDES; IT DOES NOT SPELL. This arm carried unit_lines: List -- the rendered systemd unit text -- so a value whose whole job is to answer 'may this ensure proceed, and under what preflight' also carried a serialized artifact of one particular realization. That is transport leaking into a semantic request: the same admission is the right answer whether the daemon is supervised by systemd, by a container runtime, or by something not yet written, and none of that changes whether the endpoint is clear to bind. Carrying the text made the decision unusable by any realization that does not spell systemd INI, and it put a second copy of the unit's bytes inside a decision type where nothing would ever compare it against the one the realizer installs.\n\nIt now carries a SystemdUnitFile: still systemd-specific, and deliberately so -- BuildCacheInstance names a .service unit and a .slice compile pool, so this vertical has exactly one realization family today and pretending otherwise would be modeling a choice nobody has. What changed is the GRAIN: a modeled unit is projectable, diffable and refusable, and its bytes are produced by extdeps.systemd.unit_file's single serializer at the moment of writing rather than frozen into the decision. When a second family exists, this field is what moves behind it; the rendered text could not have.\n\nStated plainly so the boundary is not overread: this change narrows what the REQUEST CARRIES and nothing else. It does not decide, and does not claim, anything about whether the realize path consumes this admission -- that is a separate lane's question, its answer moves independently of this row, and the authority for it is the next-consumer paragraph of the migration note above rather than this one. This row deliberately does not restate that status, because a second place asserting it is a second place for it to go stale." +// THE ADMISSION DECIDES; IT DOES NOT SPELL. This arm carried unit_lines: List -- the +// rendered systemd unit text -- so a value whose whole job is to answer 'may this ensure proceed, +// and under what preflight' also carried a serialized artifact of one particular realization. That +// is transport leaking into a semantic request: the same admission is the right answer whether the +// daemon is supervised by systemd, by a container runtime, or by something not yet written, and +// none of that changes whether the endpoint is clear to bind. Carrying the text made the decision +// unusable by any realization that does not spell systemd INI, and it put a second copy of the +// unit's bytes inside a decision type where nothing would ever compare it against the one the +// realizer installs. +// +// It now carries a SystemdUnitFile: still systemd-specific, and deliberately so -- +// BuildCacheInstance names a .service unit and a .slice compile pool, so this vertical has exactly +// one realization family today and pretending otherwise would be modeling a choice nobody has. What +// changed is the GRAIN: a modeled unit is projectable, diffable and refusable, and its bytes are +// produced by extdeps.systemd.unit_file's single serializer at the moment of writing rather than +// frozen into the decision. When a second family exists, this field is what moves behind it; the +// rendered text could not have. +// +// Stated plainly so the boundary is not overread: this change narrows what the REQUEST CARRIES and +// nothing else. It does not decide, and does not claim, anything about whether the realize path +// consumes this admission -- that is a separate lane's question, its answer moves independently of +// this row, and the authority for it is the next-consumer paragraph of the migration note above +// rather than this one. This row deliberately does not restate that status, because a second place +// asserting it is a second place for it to go stale. -data placement_unknown_refuses_before_the_path_note: String = "ServerPlacementUnknown REFUSES RATHER THAN FALLING THROUGH TO THE PATH. The other four non-converged placements are all POSITIVE readings — a runner owns it, something unsupervised owns it, nothing owns it, or a managed unit that is not ours owns it — and in each of those the path state is the right next question, because we know we must act and only need to know whether the endpoint is clear. Unknown is not a reading at all: the observer could not classify the owner's cgroup. Proceeding on it would mean deciding to write and restart a unit on a host whose current state we failed to read, which is the absorbing fallback in its most expensive form — the widen here is not a rerun, it is a host mutation.\n\nThe distinction is worth the extra arm precisely because Unknown and Absent are easy to conflate: both mean 'we found no managed owner', but Absent means we LOOKED and there is none, while Unknown means the looking failed." +// ServerPlacementUnknown REFUSES RATHER THAN FALLING THROUGH TO THE PATH. The other four +// non-converged placements are all POSITIVE readings — a runner owns it, something unsupervised +// owns it, nothing owns it, or a managed unit that is not ours owns it — and in each of those the +// path state is the right next question, because we know we must act and only need to know whether +// the endpoint is clear. Unknown is not a reading at all: the observer could not classify the +// owner's cgroup. Proceeding on it would mean deciding to write and restart a unit on a host whose +// current state we failed to read, which is the absorbing fallback in its most expensive form — the +// widen here is not a rerun, it is a host mutation. +// +// The distinction is worth the extra arm precisely because Unknown and Absent are easy to conflate: +// both mean 'we found no managed owner', but Absent means we LOOKED and there is none, while +// Unknown means the looking failed. fn ensure_from_path( desired: BuildCacheInstance, @@ -204,7 +308,19 @@ fn ensure_from_path( } } -data occupied_endpoint_refuses_note: String = "AN OCCUPIED ENDPOINT REFUSES EVEN THOUGH WE KNOW THE OWNER IS WRONG, and that is deliberate rather than timid. Reaching this arm means placement already said the owner is not our intended unit — a runner-owned server, an unsupervised one, or another unit's — so it is tempting to treat 'wrong owner holding the socket' as licence to take it. It is not, for the same reason the unlink admission refuses a listener: the holder is a LIVE process with in-flight compiles as its children, and severing or replacing its socket strands those compiles and leaves a server running that nothing can reach.\n\nEvicting a live wrong owner is a genuinely different operation from ensuring a unit exists — it requires draining the runners whose compiles are that server's children, and it can fail halfway. Folding it into ensure would make an idempotent converge step conditionally destructive. It gets its own admission when the quiesce path is modeled; until then this refusal is the honest boundary and it names the owner so the operator can see exactly which process is in the way." +// AN OCCUPIED ENDPOINT REFUSES EVEN THOUGH WE KNOW THE OWNER IS WRONG, and that is deliberate +// rather than timid. Reaching this arm means placement already said the owner is not our intended +// unit — a runner-owned server, an unsupervised one, or another unit's — so it is tempting to treat +// 'wrong owner holding the socket' as licence to take it. It is not, for the same reason the unlink +// admission refuses a listener: the holder is a LIVE process with in-flight compiles as its +// children, and severing or replacing its socket strands those compiles and leaves a server running +// that nothing can reach. +// +// Evicting a live wrong owner is a genuinely different operation from ensuring a unit exists — it +// requires draining the runners whose compiles are that server's children, and it can fail halfway. +// Folding it into ensure would make an idempotent converge step conditionally destructive. It gets +// its own admission when the quiesce path is modeled; until then this refusal is the honest +// boundary and it names the owner so the operator can see exactly which process is in the way. fn ensure_yields_unit(admission: EnsureBuildCacheAdmission) -> Bool { ensure_build_cache_admission_fold( @@ -278,7 +394,13 @@ fn ensure_requires_unlink_first(admission: EnsureBuildCacheAdmission) -> Bool { ) } -data refusal_text_is_a_diagnostic_not_the_contract_note: String = "THE REFUSAL'S TEXT IS RENDERED HERE, BESIDE THE COPRODUCT IT RENDERS, so a consumer at a realization boundary reports the refusal without re-deciding what it means. gunbc.host_build_cache_provision provision_gate_consumes_the_fold_note already fixed the rule this follows: what a verdict CONTRACTUALLY says is its arm and its cause, and the located prose is a diagnostic payload. A realizer writing its own sentence per arm would be a second rendering of one fact that typechecks while it drifts, and would silently stop covering the day an arm is added -- so the rendering lives with the type and every consumer quotes it." +// THE REFUSAL'S TEXT IS RENDERED HERE, BESIDE THE COPRODUCT IT RENDERS, so a consumer at a +// realization boundary reports the refusal without re-deciding what it means. +// gunbc.host_build_cache_provision provision_gate_consumes_the_fold_note already fixed the rule +// this follows: what a verdict CONTRACTUALLY says is its arm and its cause, and the located prose +// is a diagnostic payload. A realizer writing its own sentence per arm would be a second rendering +// of one fact that typechecks while it drifts, and would silently stop covering the day an arm is +// added -- so the rendering lives with the type and every consumer quotes it. fn ensure_refusal_detail(reason: EnsureRefusal) -> NonEmptyStr { match reason { diff --git a/dag/gunbc/build_cache/build_cache_instance.dag b/dag/gunbc/build_cache/build_cache_instance.dag index 2f263ae08f4..c0190e3ae45 100644 --- a/dag/gunbc/build_cache/build_cache_instance.dag +++ b/dag/gunbc/build_cache/build_cache_instance.dag @@ -18,7 +18,19 @@ import gunbc.host_layout { data build_cache_instance_scope_disposition: Disposition = SingleAuthority -data build_cache_instance_grain_note: String = "CATALOG IDENTITY IS NOT DEPLOYED-INSTANCE IDENTITY, and conflating them is why a host carrying two differently-configured uses of one implementation could not be described. gunbc.host_build_cache_provision speaks of provisioning `the sccache_local catalog` on a host; extdeps.cache.sccache.sccache_local_id names sccache's cache SEMANTICS and its pinned binary. Neither identifies a running daemon. srv1 and srv2 each run TWO sccache servers from that one catalog row — a CI server (principal ghrunner, /var/lib/ctrl/sccache-ci, endpoint /var/lib/ctrl/sccache-ci/server.sock) and a session server (principal briansrls, /var/lib/ctrl/sccache, 50G, endpoint /var/lib/ctrl/sccache/server.sock) — so 'provision the catalog on this host' names neither of them and cannot express that one is correctly placed while the other is not. BuildCacheInstance is the deployed grain: one row per (host, consumer class, instance name), carrying the endpoint and the INTENDED unit, principal, storage, capacity and compile pool that an observation is checked against. One concept with multiple data rows, never a second provisioning mechanism per use (DESIGN section 2 horizontal)." +// CATALOG IDENTITY IS NOT DEPLOYED-INSTANCE IDENTITY, and conflating them is why a host carrying +// two differently-configured uses of one implementation could not be described. +// gunbc.host_build_cache_provision speaks of provisioning `the sccache_local catalog` on a host; +// extdeps.cache.sccache.sccache_local_id names sccache's cache SEMANTICS and its pinned binary. +// Neither identifies a running daemon. srv1 and srv2 each run TWO sccache servers from that one +// catalog row — a CI server (principal ghrunner, /var/lib/ctrl/sccache-ci, endpoint +// /var/lib/ctrl/sccache-ci/server.sock) and a session server (principal briansrls, +// /var/lib/ctrl/sccache, 50G, endpoint /var/lib/ctrl/sccache/server.sock) — so 'provision the +// catalog on this host' names neither of them and cannot express that one is correctly placed while +// the other is not. BuildCacheInstance is the deployed grain: one row per (host, consumer class, +// instance name), carrying the endpoint and the INTENDED unit, principal, storage, capacity and +// compile pool that an observation is checked against. One concept with multiple data rows, never a +// second provisioning mechanism per use (DESIGN section 2 horizontal). type BuildCacheConsumerClass = ContinuousIntegrationCompiles @@ -41,7 +53,34 @@ type BuildCacheInstance { intended_compile_pool: CompilePoolPlacement } -data build_cache_configuration_observation_note: String = "STORAGE ROOT AND CAPACITY ARE TWO OBSERVED CONFIGURATION AXES, and each observation is bound to both the exact deployed BuildCacheInstance and the ProcessIdentity whose environment was read. A path or byte count without both subjects could be replayed across hosts, across the CI and session instances that share one sccache implementation, or across two processes after pid reuse. The owner is the process already proven to hold the instance endpoint; a unit file would answer what systemd WOULD start, not how the live server is configured, and every host in the incident population currently runs a server systemd did not start.\n\nUNSET AND READ UNAVAILABLE ARE DIFFERENT OBSERVATIONS WITH DIFFERENT REMEDIES. Unset means the owner environment was read and carries no assignment; unavailable means the read did not answer, and must never narrow to unset. Endpoint or owner absence does not appear as a third arm here: without the prerequisite endpoint-owner observation there is no configuration subject, so these carriers are not constructed at all. That is not-applicability at the join rather than malformed configuration.\n\nCAPACITY ALSO RETAINS CONFIGURED TEXT THAT CANNOT YET BE GROUNDED AS BYTES. gunbc.build_cache_unit emits a plain decimal byte count, which has one exact decoding. sccache also accepts suffixed text such as its documented 10G default, but this repository has no cited rule deciding whether G denotes 10^9 or 2^30. Choosing either would fabricate a magnitude whose seven-percent difference could silently pass or refuse convergence. BuildCacheCapacityConfiguredUngrounded therefore carries the text verbatim; it narrows when extdeps.cache.sccache cites and models the accepted size grammar. The instance itself retains whether capacity intent is declared, so gunbc.build_cache_instance_readiness refuses intended_capacity Absent rather than freezing a vendor default as a decision.\n\nThese carriers are consumed by gunbc.build_cache_instance_readiness, whose producer preserves unavailable separately from observed mismatch and is the only function that mints the proof-shaped BuildCacheInstanceReady receipt." +// STORAGE ROOT AND CAPACITY ARE TWO OBSERVED CONFIGURATION AXES, and each observation is bound to +// both the exact deployed BuildCacheInstance and the ProcessIdentity whose environment was read. A +// path or byte count without both subjects could be replayed across hosts, across the CI and +// session instances that share one sccache implementation, or across two processes after pid reuse. +// The owner is the process already proven to hold the instance endpoint; a unit file would answer +// what systemd WOULD start, not how the live server is configured, and every host in the incident +// population currently runs a server systemd did not start. +// +// UNSET AND READ UNAVAILABLE ARE DIFFERENT OBSERVATIONS WITH DIFFERENT REMEDIES. Unset means the +// owner environment was read and carries no assignment; unavailable means the read did not answer, +// and must never narrow to unset. Endpoint or owner absence does not appear as a third arm here: +// without the prerequisite endpoint-owner observation there is no configuration subject, so these +// carriers are not constructed at all. That is not-applicability at the join rather than malformed +// configuration. +// +// CAPACITY ALSO RETAINS CONFIGURED TEXT THAT CANNOT YET BE GROUNDED AS BYTES. +// gunbc.build_cache_unit emits a plain decimal byte count, which has one exact decoding. sccache +// also accepts suffixed text such as its documented 10G default, but this repository has no cited +// rule deciding whether G denotes 10^9 or 2^30. Choosing either would fabricate a magnitude whose +// seven-percent difference could silently pass or refuse convergence. +// BuildCacheCapacityConfiguredUngrounded therefore carries the text verbatim; it narrows when +// extdeps.cache.sccache cites and models the accepted size grammar. The instance itself retains +// whether capacity intent is declared, so gunbc.build_cache_instance_readiness refuses +// intended_capacity Absent rather than freezing a vendor default as a decision. +// +// These carriers are consumed by gunbc.build_cache_instance_readiness, whose producer preserves +// unavailable separately from observed mismatch and is the only function that mints the +// proof-shaped BuildCacheInstanceReady receipt. type BuildCacheStorageConfiguration = BuildCacheStorageRootConfigured { root: NonEmptyStr } @@ -76,7 +115,12 @@ type BuildCacheCapacityObservation detail: NonEmptyStr } -data process_identity_note: String = "A BARE PID IS NOT AN IDENTITY: the kernel reuses pids, so a pid recorded in one observation and re-read in the next may name a different process, and a receipt that says 'pid 3975946 is the endpoint owner' is only true relative to a boot and a start time. boot_id distinguishes across reboots; start_time (the process's own start, monotonic within a boot) distinguishes across pid reuse within one. The triple is what makes two consecutive observations comparable, which is exactly what the non-mutation control asserts." +// A BARE PID IS NOT AN IDENTITY: the kernel reuses pids, so a pid recorded in one observation and +// re-read in the next may name a different process, and a receipt that says 'pid 3975946 is the +// endpoint owner' is only true relative to a boot and a start time. boot_id distinguishes across +// reboots; start_time (the process's own start, monotonic within a boot) distinguishes across pid +// reuse within one. The triple is what makes two consecutive observations comparable, which is +// exactly what the non-mutation control asserts. type ProcessIdentity { boot_id: NonEmptyStr @@ -84,7 +128,14 @@ type ProcessIdentity { start_time: NonEmptyStr } -data runner_slot_lifecycle_note: String = "A RETIRED SLOT IS A CURRENT-INTENT FACT, NOT AN ARCHAEOLOGICAL ONE. Whether srv1-10 is retired is decided by asking whether it appears in the fleet's desired runner slots today — not by explaining how the fleet once had ten. The lifecycle distinction is kept in the receipt because it names WHICH construction wall failed: an ACTIVE runner owning the endpoint means birth ordering failed (a runner ran before a managed cache existed); a RETIRED runner owning it means birth ordering AND retirement both failed (the slot's unit was disabled while its cgroup stayed computationally alive, still owning an endpoint and still parenting new compiler children under its ceiling). Both refuse." +// A RETIRED SLOT IS A CURRENT-INTENT FACT, NOT AN ARCHAEOLOGICAL ONE. Whether srv1-10 is retired is +// decided by asking whether it appears in the fleet's desired runner slots today — not by +// explaining how the fleet once had ten. The lifecycle distinction is kept in the receipt because +// it names WHICH construction wall failed: an ACTIVE runner owning the endpoint means birth +// ordering failed (a runner ran before a managed cache existed); a RETIRED runner owning it means +// birth ordering AND retirement both failed (the slot's unit was disabled while its cgroup stayed +// computationally alive, still owning an endpoint and still parenting new compiler children under +// its ceiling). Both refuse. type RunnerSlotIdentity { host: HostIdentity @@ -182,7 +233,30 @@ type EndpointObservationUnavailableReason | ProcTableUnreadable { detail: NonEmptyStr } | EndpointPathUnreadable { detail: NonEmptyStr } -data endpoint_owner_context_note: String = "WHAT THE OWNER'S CGROUP RESOLVES TO IS ONE FACT WITH THREE SHAPES, not three fields that happen to agree. The first version of this carrier spelled it as a bare `unit` beside `owning_runner_slot: RunnerSlotIdentity?` and `runner_slot_lifecycle: RunnerSlotLifecycle?`, which admitted states no observation can produce and no reader can interpret: a slot with no lifecycle, a lifecycle with no slot, or a unit contradicting both. The lifecycle-without-slot case was the dangerous one — the derivation matched on the slot first, so an Absent slot carrying a Present lifecycle fell straight through to the unit comparison and could return ServerManagedUnitOwned, converging on a runner-owned endpoint from inconsistent substrate data (review 46217). Guarding that with a check would have conceded the state is writable; the coproduct makes it unwritable instead (DESIGN section 5, construction over validation). A slot cannot be recorded without deciding its lifecycle against current fleet intent, because deciding it is the same act as finding it. When the chain reaches a cgroup it cannot classify — an unreadable unit, a slot index that does not parse, a desired-slot roster the observer could not read — that is its own positive arm carrying the located reason, never a half-filled record.\n\nSECOND REPAIR (operator review, post-merge): the raw cgroup path now lives INSIDE each arm as observed_cgroup rather than beside the context as its own field on EndpointOwnerObserved. Those two were independently writable and could disagree — a record could carry a cgroup naming a runner slice while its context said OwnerInSystemdUnit, and placement trusted the context, so the contradiction was silent. They are not two facts: the context IS the classification of that cgroup, so the string travels with the classification derived from it and the disagreement has nowhere to live. The path is retained because a refusal must be located — ServerUnsupervised quotes it so an operator sees WHICH cgroup was found — but it is diagnostic evidence carried by the decision, never a second input a reader could consult instead." +// WHAT THE OWNER'S CGROUP RESOLVES TO IS ONE FACT WITH THREE SHAPES, not three fields that happen +// to agree. The first version of this carrier spelled it as a bare `unit` beside +// `owning_runner_slot: RunnerSlotIdentity?` and `runner_slot_lifecycle: RunnerSlotLifecycle?`, +// which admitted states no observation can produce and no reader can interpret: a slot with no +// lifecycle, a lifecycle with no slot, or a unit contradicting both. The lifecycle-without-slot +// case was the dangerous one — the derivation matched on the slot first, so an Absent slot carrying +// a Present lifecycle fell straight through to the unit comparison and could return +// ServerManagedUnitOwned, converging on a runner-owned endpoint from inconsistent substrate data +// (review 46217). Guarding that with a check would have conceded the state is writable; the +// coproduct makes it unwritable instead (DESIGN section 5, construction over validation). A slot +// cannot be recorded without deciding its lifecycle against current fleet intent, because deciding +// it is the same act as finding it. When the chain reaches a cgroup it cannot classify — an +// unreadable unit, a slot index that does not parse, a desired-slot roster the observer could not +// read — that is its own positive arm carrying the located reason, never a half-filled record. +// +// SECOND REPAIR (operator review, post-merge): the raw cgroup path now lives INSIDE each arm as +// observed_cgroup rather than beside the context as its own field on EndpointOwnerObserved. Those +// two were independently writable and could disagree — a record could carry a cgroup naming a +// runner slice while its context said OwnerInSystemdUnit, and placement trusted the context, so the +// contradiction was silent. They are not two facts: the context IS the classification of that +// cgroup, so the string travels with the classification derived from it and the disagreement has +// nowhere to live. The path is retained because a refusal must be located — ServerUnsupervised +// quotes it so an operator sees WHICH cgroup was found — but it is diagnostic evidence carried by +// the decision, never a second input a reader could consult instead. type EndpointOwnerContext = OwnerInRunnerSlot { @@ -273,7 +347,38 @@ fn unavailable_reason_detail(reason: EndpointObservationUnavailableReason) -> No } } -data build_cache_desired_instances_note: String = "THE DESIRED DEPLOYED INSTANCES, one row per (host, consumer class). These state INTENT and are deliberately not a description of the live hosts: the whole point of the observer is that intent and observation are different things and their difference is refusable. As of the 2026-08-01 reads NO host satisfies intended_unit for its CI instance, and the four hosts fail in four DIFFERENT ways rather than along one ladder — srv1's endpoint is owned by a 31-day process in RETIRED slot srv1-10, srv2's by a process in an ACTIVE slot, srv3 had an owner in an active slot that later vanished with its runner cgroup, and srv4 has no owner at all because a stale socket makes every new server fail to bind. Every row below refused against those reads, which is the correct and intended reading rather than a defect in the rows.\n\nTHE FOUR-DIFFERENT-WAYS SENTENCE IS A RECEIPT OF 2026-08-01 AND THE FLEET HAS SINCE CONVERGED ON ONE WAY, which is worth stating because the variety was the argument for the observer. Measured 2026-08-25T18:43Z on all four hosts: /var/lib/ctrl/sccache-ci/server.sock EXISTS with ZERO listeners behind it, no sccache process holds it anywhere (srv3 runs one user-session sccache under user@1000.service that does not touch this path), and no host has a gunbc-build-cache-ci.service unit at all. So every row still refuses, and now for the same reason on every host -- the srv4 shape of 2026-08-01 generalised to the fleet.\n\nCI intended_capacity stays Absent on purpose and that is now the ONLY Absent here: the 10 GiB the CI servers ran under was sccache's built-in default rather than a chosen number, and choosing one is a later increment, so declaring it would fabricate a decision nobody has made.\n\nTHE SESSION ROWS' 50G IS FLAGGED RATHER THAN CORRECTED, BECAUSE ITS PREMISE IS GONE AND ONLY THE OPERATOR CAN REPLACE IT. This note used to record a correction: the session rows carried Absent while the note claimed 50G, the two disagreed, and the note was judged the true half on the strength of ctrl-sccache.service really setting 50G -- so the VALUE was changed to match the note. Measured 2026-08-25T18:43Z, ctrl-sccache.service DOES NOT EXIST on any of the four hosts: is-enabled returns not-found and no unit file matches. The evidence that decided that correction has no subject, so the 50G now stands on nothing, and re-deriving it from a unit nobody can read would be inventing the decision this paragraph refuses to invent for CI. An Absent capacity cannot be read back or converged against, which is why the CI Absent is a tracked gap rather than a resting state; the session 50G is now a tracked claim of the same kind, pointing the other way." +// THE DESIRED DEPLOYED INSTANCES, one row per (host, consumer class). These state INTENT and are +// deliberately not a description of the live hosts: the whole point of the observer is that intent +// and observation are different things and their difference is refusable. As of the 2026-08-01 +// reads NO host satisfies intended_unit for its CI instance, and the four hosts fail in four +// DIFFERENT ways rather than along one ladder — srv1's endpoint is owned by a 31-day process in +// RETIRED slot srv1-10, srv2's by a process in an ACTIVE slot, srv3 had an owner in an active slot +// that later vanished with its runner cgroup, and srv4 has no owner at all because a stale socket +// makes every new server fail to bind. Every row below refused against those reads, which is the +// correct and intended reading rather than a defect in the rows. +// +// THE FOUR-DIFFERENT-WAYS SENTENCE IS A RECEIPT OF 2026-08-01 AND THE FLEET HAS SINCE CONVERGED ON +// ONE WAY, which is worth stating because the variety was the argument for the observer. Measured +// 2026-08-25T18:43Z on all four hosts: /var/lib/ctrl/sccache-ci/server.sock EXISTS with ZERO +// listeners behind it, no sccache process holds it anywhere (srv3 runs one user-session sccache +// under user@1000.service that does not touch this path), and no host has a +// gunbc-build-cache-ci.service unit at all. So every row still refuses, and now for the same reason +// on every host -- the srv4 shape of 2026-08-01 generalised to the fleet. +// +// CI intended_capacity stays Absent on purpose and that is now the ONLY Absent here: the 10 GiB the +// CI servers ran under was sccache's built-in default rather than a chosen number, and choosing one +// is a later increment, so declaring it would fabricate a decision nobody has made. +// +// THE SESSION ROWS' 50G IS FLAGGED RATHER THAN CORRECTED, BECAUSE ITS PREMISE IS GONE AND ONLY THE +// OPERATOR CAN REPLACE IT. This note used to record a correction: the session rows carried Absent +// while the note claimed 50G, the two disagreed, and the note was judged the true half on the +// strength of ctrl-sccache.service really setting 50G -- so the VALUE was changed to match the +// note. Measured 2026-08-25T18:43Z, ctrl-sccache.service DOES NOT EXIST on any of the four hosts: +// is-enabled returns not-found and no unit file matches. The evidence that decided that correction +// has no subject, so the 50G now stands on nothing, and re-deriving it from a unit nobody can read +// would be inventing the decision this paragraph refuses to invent for CI. An Absent capacity +// cannot be read back or converged against, which is why the CI Absent is a tracked gap rather than +// a resting state; the session 50G is now a tracked claim of the same kind, pointing the other way. fn ci_cache_instance(host: HostIdentity) -> BuildCacheInstance { BuildCacheInstance { @@ -309,6 +414,35 @@ fn session_cache_instance(host: HostIdentity) -> BuildCacheInstance { } } +// srv4 JOINED THE DESIRED ROSTER ON AN OBSERVATION, and the sequence is the argument for the row. +// When this authority first landed, srv4 was unreachable (ssh returned Permission denied +// (publickey)) and it appeared in this module only as diagnostic prose — so the fleet's intent was +// silently four-fifths stated, and a typed unavailability had no SUBJECT to attach to. That is the +// state-space conflation one level up: a host absent from the desired roster is indistinguishable +// from a host with nothing desired on it, and only one of those was true. +// +// The reachability was never the blocker it looked like. srv4 carries exactly the two declared +// fleet keys and deliberately NOT the session-bootstrap keys other hosts were hand-keyed with, so +// it is the CONVERGED control of gunbc.host_authorized_keys_reconcile, not a mis-keyed host; the +// missing piece was materializing the fleet-automation private key for a session, which is the A1 +// gap in gunbc.plans.fleet_subsumption_manual_gaps. Once the operator supplied it the read took +// under a minute over the ordinary route. +// +// WHAT THAT READ FOUND ON 2026-08-01, recorded because it is the reason this row is not a +// formality: srv4 held NO listener on its configured endpoint, ran no sccache process and had no +// unit, yet a STALE server.sock remained on disk and its error log's entire contents were one line, +// Address in use (os error 98). A server born in a runner cgroup died with that cgroup without +// unlinking its socket, so every later server failed to bind and compiles ran uncached. Its +// placement therefore derives ServerPlacementAbsent and refuses, which is correct, but the CAUSE +// was a distinct defect from srv1's stranded owner and the managed unit must clear a stale endpoint +// on start or it will inherit the same trap. +// +// THAT SHAPE IS NO LONGER srv4'S ALONE, WHICH IS WHY THE PARAGRAPH IS DATED RATHER THAN LEFT IN THE +// PRESENT TENSE. Measured 2026-08-25T18:43Z, all four hosts carry the socket with zero listeners +// and no sccache process holding it, and srv1's stranded owner -- the contrast this paragraph drew +// -- is gone. The distinctness of the CAUSES stands as history and no longer distinguishes the +// hosts. + data build_cache_desired_instances: List = [ ci_cache_instance(host: operator_host_srv1), session_cache_instance(host: operator_host_srv1), @@ -317,5 +451,3 @@ data build_cache_desired_instances: List = [ ci_cache_instance(host: operator_host_srv3), ci_cache_instance(host: operator_host_srv4), ] - -data srv4_desired_instance_admission_note: String = "srv4 JOINED THE DESIRED ROSTER ON AN OBSERVATION, and the sequence is the argument for the row. When this authority first landed, srv4 was unreachable (ssh returned Permission denied (publickey)) and it appeared in this module only as diagnostic prose — so the fleet's intent was silently four-fifths stated, and a typed unavailability had no SUBJECT to attach to. That is the state-space conflation one level up: a host absent from the desired roster is indistinguishable from a host with nothing desired on it, and only one of those was true.\n\nThe reachability was never the blocker it looked like. srv4 carries exactly the two declared fleet keys and deliberately NOT the session-bootstrap keys other hosts were hand-keyed with, so it is the CONVERGED control of gunbc.host_authorized_keys_reconcile, not a mis-keyed host; the missing piece was materializing the fleet-automation private key for a session, which is the A1 gap in gunbc.plans.fleet_subsumption_manual_gaps. Once the operator supplied it the read took under a minute over the ordinary route.\n\nWHAT THAT READ FOUND ON 2026-08-01, recorded because it is the reason this row is not a formality: srv4 held NO listener on its configured endpoint, ran no sccache process and had no unit, yet a STALE server.sock remained on disk and its error log's entire contents were one line, Address in use (os error 98). A server born in a runner cgroup died with that cgroup without unlinking its socket, so every later server failed to bind and compiles ran uncached. Its placement therefore derives ServerPlacementAbsent and refuses, which is correct, but the CAUSE was a distinct defect from srv1's stranded owner and the managed unit must clear a stale endpoint on start or it will inherit the same trap.\n\nTHAT SHAPE IS NO LONGER srv4'S ALONE, WHICH IS WHY THE PARAGRAPH IS DATED RATHER THAN LEFT IN THE PRESENT TENSE. Measured 2026-08-25T18:43Z, all four hosts carry the socket with zero listeners and no sccache process holding it, and srv1's stranded owner -- the contrast this paragraph drew -- is gone. The distinctness of the CAUSES stands as history and no longer distinguishes the hosts." diff --git a/dag/gunbc/build_cache/build_cache_instance_readiness.dag b/dag/gunbc/build_cache/build_cache_instance_readiness.dag index fb4a9309a69..7e349729e87 100644 --- a/dag/gunbc/build_cache/build_cache_instance_readiness.dag +++ b/dag/gunbc/build_cache/build_cache_instance_readiness.dag @@ -21,7 +21,27 @@ import gunbc.runner_activation { BuildCacheInstanceReady, HostCompilePoolReady } data build_cache_instance_readiness_scope_disposition: Disposition = SingleAuthority -data build_cache_instance_readiness_producer_note: String = "THE PRODUCER PRESERVES THREE STATES, NOT TWO. BuildCacheInstanceReady used to carry six Bool flags, but false could mean either an observation positively disagreed with intent OR the observation never answered. Those states have different remedies and different authority: drift is a host fact; unavailable is a failure of the observation path. Collapsing them is DESIGN's empty-observation narrow. The proof receipt therefore carries no flags. This producer returns Ready only after every exact-subject comparison passes, NotReady only from positive observations that disagree, and ObservationUnavailable whenever an exact-subject input read did not answer.\n\nTHE COMPILE-POOL CHECK IS ABOUT THIS SERVER, NOT MERELY THIS HOST. HostCompilePoolReady proves the declared slice exists and is sized on the host. It does not prove the endpoint owner lives beneath that slice. The producer separately checks the OwnerInSystemdUnit cgroup path contains the declared slice as one complete path segment, so a healthy slice beside a server still running in a runner cgroup cannot mint readiness. CompilePoolInRunnerSlots has no slice name and refuses as PoolNotDeclared; the global managed-slice constant is deliberately not consulted.\n\nTHE JOIN BINDS EVERY INPUT TWICE: the full BuildCacheInstance value and the ProcessIdentity. A storage read from the session server cannot verify the CI server on the same host, and a read from a recycled pid cannot verify its predecessor. An unavailable storage or capacity observation dominates mismatches on other axes only after that observation is bound to the endpoint's instance and owner. A foreign unavailable observation is refused as a subject mismatch before its unavailable result is accepted, so it cannot classify this instance's availability." +// THE PRODUCER PRESERVES THREE STATES, NOT TWO. BuildCacheInstanceReady used to carry six Bool +// flags, where false meant either an observation positively disagreed with intent OR never +// answered. Different remedies, different authority: drift is a host fact, unavailable is a failure +// of the observation path; collapsing them is DESIGN's empty-observation narrow. So the receipt +// carries no flags: Ready only after every exact-subject comparison passes, NotReady only from +// positive disagreeing observations, ObservationUnavailable whenever an exact-subject read did not +// answer. +// +// THE COMPILE-POOL CHECK IS ABOUT THIS SERVER, NOT MERELY THIS HOST. HostCompilePoolReady proves the +// declared slice exists and is sized; not that the endpoint owner lives beneath it. The producer +// separately checks the OwnerInSystemdUnit cgroup path contains the declared slice as one complete +// path segment, so a healthy slice beside a server still in a runner cgroup cannot mint readiness. +// CompilePoolInRunnerSlots has no slice name and refuses as PoolNotDeclared; the global +// managed-slice constant is deliberately not consulted. +// +// THE JOIN BINDS EVERY INPUT TWICE: the full BuildCacheInstance value and the ProcessIdentity. A +// storage read from the session server cannot verify the CI server on the same host, nor a read +// from a recycled pid its predecessor. An unavailable storage or capacity observation dominates +// mismatches on other axes only once bound to the endpoint's instance and owner; a foreign +// unavailable observation is refused as a subject mismatch first, so it cannot classify this +// instance's availability. type BuildCacheInstanceNotReadyReason = CacheEndpointAbsent diff --git a/dag/gunbc/build_cache/build_cache_latency_probe.dag b/dag/gunbc/build_cache/build_cache_latency_probe.dag index 6cfc3bb4b59..6d5e4eabab2 100644 --- a/dag/gunbc/build_cache/build_cache_latency_probe.dag +++ b/dag/gunbc/build_cache/build_cache_latency_probe.dag @@ -12,9 +12,39 @@ data build_cache_latency_probe_disposition: Disposition = Terminal { reason: "The acceptance metric for the srvN build-cache placement lane, and the falsifier for any fix to it. It measures ONE thing — how long the CI `build` job takes, per host — because that duration is dominated by whether cache-miss compiles are served by a healthy sccache server, and because it is derivable from GitHub Actions job timings that already exist. No host access, no new instrumentation, no agent on the box." } -data why_this_metric_note: String = "THE DEFECT IS INVISIBLE ON THE AXIS EVERYONE WAS WATCHING. srv1's build jobs on 2026-08-05 ran a median of 607000ms against a peer median of 238000ms — 2.55x — while its job FAILURE rate was 26.2%, the second LOWEST of the four hosts (srv2 28.9%, srv4 31.4%). Counting conclusion=failure finds nothing here; counting cancellations finds nothing either. The harm is latency, and it is large: about 6.2 minutes of excess per build, 44 builds in the window, roughly 4.5 hours of wall-clock per day on the build job alone — which gates everything downstream through the release-bins artifact.\n\nThat is why the metric is worth a carrier rather than a shell command. A defect that costs hours a day while every conclusion-shaped signal reads normal will be rediscovered by whoever next happens to look, in exactly the way this one was." - -data bimodality_is_the_mechanism_note: String = "SORTED, srv1's 44 samples fall into two clusters with an EMPTY GAP between 112000ms and 505000ms — nothing in between, on either side of a four-minute void. Ten samples sit at 78000-112000ms; thirty-four sit at 505000-1640000ms. That gap is the mechanism made visible: there is no partially-warm state, because a compile is either served from the cache or it is not.\n\nA CORRECTION THE FULL DISTRIBUTION FORCED, and it sharpens the diagnosis rather than softening it. The fast cluster was first read as 'faster than peers', comparing srv1's 78000ms floor to the peer MEDIAN of 238000ms. That comparison is wrong: the peer hosts' own minima are 74000-81000ms, indistinguishable from srv1's. Every host gets full cache hits at about the same speed.\n\nThe difference is entirely at the top. Peer builds cap at 342000-372000ms — that is what a cache MISS costs on a healthy host. srv1's misses run 505000-1640000ms, between 1.4x and 4.4x a peer's worst case. So srv1 is not merely running cold; its misses are individually more expensive than a peer's misses, which a cold cache alone does not explain. It is consistent with the server being memory-starved while compiling — the same server whose cgroup carries 229780 memory.max breach events and 12 oom_kills (gunbc.fleet_host_budget gunbc_compile_pool_doc).\n\nCONSEQUENCE FOR THE FIX, which is why the correction matters: respawning the server into a healthier cgroup addresses the placement, but the durable repair is the one that gives it a memory budget of its own — the managed unit plus the declared compile-pool slice. A server that is merely relocated is still unbudgeted." +// THE DEFECT IS INVISIBLE ON THE AXIS EVERYONE WAS WATCHING. srv1's build jobs on 2026-08-05 ran a +// median of 607000ms against a peer median of 238000ms — 2.55x — while its job FAILURE rate was +// 26.2%, the second LOWEST of the four hosts (srv2 28.9%, srv4 31.4%). Counting conclusion=failure +// finds nothing here; counting cancellations finds nothing either. The harm is latency, and it is +// large: about 6.2 minutes of excess per build, 44 builds in the window, roughly 4.5 hours of +// wall-clock per day on the build job alone — which gates everything downstream through the +// release-bins artifact. +// +// That is why the metric is worth a carrier rather than a shell command. A defect that costs hours +// a day while every conclusion-shaped signal reads normal will be rediscovered by whoever next +// happens to look, in exactly the way this one was. + +// SORTED, srv1's 44 samples fall into two clusters with an EMPTY GAP between 112000ms and 505000ms +// — nothing in between, on either side of a four-minute void. Ten samples sit at 78000-112000ms; +// thirty-four sit at 505000-1640000ms. That gap is the mechanism made visible: there is no +// partially-warm state, because a compile is either served from the cache or it is not. +// +// A CORRECTION THE FULL DISTRIBUTION FORCED, and it sharpens the diagnosis rather than softening +// it. The fast cluster was first read as 'faster than peers', comparing srv1's 78000ms floor to the +// peer MEDIAN of 238000ms. That comparison is wrong: the peer hosts' own minima are 74000-81000ms, +// indistinguishable from srv1's. Every host gets full cache hits at about the same speed. +// +// The difference is entirely at the top. Peer builds cap at 342000-372000ms — that is what a cache +// MISS costs on a healthy host. srv1's misses run 505000-1640000ms, between 1.4x and 4.4x a peer's +// worst case. So srv1 is not merely running cold; its misses are individually more expensive than a +// peer's misses, which a cold cache alone does not explain. It is consistent with the server being +// memory-starved while compiling — the same server whose cgroup carries 229780 memory.max breach +// events and 12 oom_kills (gunbc.fleet_host_budget gunbc_compile_pool_doc). +// +// CONSEQUENCE FOR THE FIX, which is why the correction matters: respawning the server into a +// healthier cgroup addresses the placement, but the durable repair is the one that gives it a +// memory budget of its own — the managed unit plus the declared compile-pool slice. A server that +// is merely relocated is still unbudgeted. type BuildLatencySample { host: HostIdentity @@ -91,7 +121,23 @@ type MedianScan { picked: Millisecond? } -data median_selection_note: String = "Selection is a single linear fold over the sorted list because this substrate exposes only first/last/length on List — there is no index accessor, and the alternatives are a quadratic rank-count or a hand-rolled recursion. One fold carrying (index, picked) is O(n) after the sort, which keeps the whole derivation at sort cost and clear of DESIGN section 6's standing rule that a quadratic fold is always fixed regardless of the realized n.\n\nEVEN-SIZED POPULATIONS take the LOWER median rather than averaging the middle pair. Declared rather than incidental: averaging would mint a duration that no build actually took, and every consumer here compares a real observation against a policy ratio, so a synthetic midpoint would be the only unmeasured number in the chain.\n\nRECEIPT FOR THE RULE, from this file: a sibling note first quoted srv1's median as 609000ms, taken from an averaging median computed in a shell during collection. The derivation returns 607000ms — a real sample — and 609000 is the mean of the two middle values, a duration no build on srv1 ever took. The witness comparing the recorded verdict to its re-derivation caught the discrepancy immediately. The convention was already declared here and violated two declarations away, which is the ordinary way a stated convention fails." +// Selection is a single linear fold over the sorted list because this substrate exposes only +// first/last/length on List — there is no index accessor, and the alternatives are a quadratic +// rank-count or a hand-rolled recursion. One fold carrying (index, picked) is O(n) after the sort, +// which keeps the whole derivation at sort cost and clear of DESIGN section 6's standing rule that +// a quadratic fold is always fixed regardless of the realized n. +// +// EVEN-SIZED POPULATIONS take the LOWER median rather than averaging the middle pair. Declared +// rather than incidental: averaging would mint a duration that no build actually took, and every +// consumer here compares a real observation against a policy ratio, so a synthetic midpoint would +// be the only unmeasured number in the chain. +// +// RECEIPT FOR THE RULE, from this file: a sibling note first quoted srv1's median as 609000ms, +// taken from an averaging median computed in a shell during collection. The derivation returns +// 607000ms — a real sample — and 609000 is the mean of the two middle values, a duration no build +// on srv1 ever took. The witness comparing the recorded verdict to its re-derivation caught the +// discrepancy immediately. The convention was already declared here and violated two declarations +// away, which is the ordinary way a stated convention fails. fn population_median(p: BuildLatencyPopulation) -> Millisecond? { let n = p.durations |> length @@ -113,7 +159,17 @@ fn population_median(p: BuildLatencyPopulation) -> Millisecond? { } } -data divergence_policy_note: String = "THE RATIO IS AN OPERATOR POLICY BUDGET, not a threshold fitted to the observation — which matters because the 2026-08-01 test-oracle ruling forbids a merge-blocking comparison against a literal read off the current tree. Its ground is that all four hosts are the SAME BOARD with the same CPU and the same declared per-slot caps (gunbc.fleet_intent: ASRock ALTRAD8UD-1L2T, Ampere Altra Max M128-30). Identical hardware compiling identical inputs cannot sustain a 2x median divergence for any hardware reason, so a host that does is telling you something about software placement.\n\nTwo is chosen as the smallest ratio that no plausible hardware or scheduling variance explains, not as the smallest ratio that flags srv1 — srv1 sits at 2.55x and would still be caught at 2.5x. A tighter ratio would begin to catch ordinary cache-warmth luck; a looser one would have let srv1 pass at some point during its degradation." +// THE RATIO IS AN OPERATOR POLICY BUDGET, not a threshold fitted to the observation — which matters +// because the 2026-08-01 test-oracle ruling forbids a merge-blocking comparison against a literal +// read off the current tree. Its ground is that all four hosts are the SAME BOARD with the same CPU +// and the same declared per-slot caps (gunbc.fleet_intent: ASRock ALTRAD8UD-1L2T, Ampere Altra Max +// M128-30). Identical hardware compiling identical inputs cannot sustain a 2x median divergence for +// any hardware reason, so a host that does is telling you something about software placement. +// +// Two is chosen as the smallest ratio that no plausible hardware or scheduling variance explains, +// not as the smallest ratio that flags srv1 — srv1 sits at 2.55x and would still be caught at 2.5x. +// A tighter ratio would begin to catch ordinary cache-warmth luck; a looser one would have let srv1 +// pass at some point during its degradation. type BuildLatencyDivergencePolicy { ratio_numerator: Int @@ -164,9 +220,29 @@ data srv1_verdict_2026_08_05: BuildLatencyVerdict = build_latency_verdict( policy: gunbc_build_latency_divergence_policy ) -data acceptance_criterion_note: String = "THE FALSIFIER FOR THE FIX, declared BEFORE the fix so it cannot be fitted afterwards. Whatever is done about srv1 — killing the stranded server, relocating it, or the durable managed-unit-plus-compile-pool-slice repair — succeeds exactly when srv1's build median re-derives to Converged against the peer reference on a fresh window, and fails otherwise. Nothing else counts: not a healthy-looking stats table (extdeps.cache.sccache sccache_stats_query_does_not_spawn_note records that --show-stats answers plausibly with no server in existence), not an individual fast build (23% of srv1's builds were already fast, which is what made the defect look like luck), and not a green CI run.\n\nIt is also the EARLY WARNING for the rest of the fleet, at no extra cost. The birth defect is fleet-wide: srv2's and srv3's cache servers live in ACTIVE runner slots and self-limit only because ephemeral runners recycle them, so each is one slot-retirement from srv1's state. A build-median divergence on either is that transition happening, and this same derivation sees it within a window." - -data live_producer_frontier_note: String = "THE LIVE PRODUCER IS MODELED BUT NOT YET SCHEDULED. build_latency_population_from_actions_api folds a list of ActionsBuildJobObservation rows — the fields the GitHub Actions jobs API returns for each job — into per-host BuildLatencyPopulation values filtered to the CI build job with conclusion=success. A thin collector (gh api / gh run list + jobs) is the only missing seam; it needs no host access and no fleet SSH key. The 2026-08-05 hand-entered populations remain the pre-actuation baseline until a fresh window is collected through this fold and written back. WET ACTUATION of the srv1 repair is a separate gate: this instrument only measures whether the repair worked once builds run again." +// THE FALSIFIER FOR THE FIX, declared BEFORE the fix so it cannot be fitted afterwards. Whatever is +// done about srv1 — killing the stranded server, relocating it, or the durable +// managed-unit-plus-compile-pool-slice repair — succeeds exactly when srv1's build median +// re-derives to Converged against the peer reference on a fresh window, and fails otherwise. +// Nothing else counts: not a healthy-looking stats table (extdeps.cache.sccache +// sccache_stats_query_does_not_spawn_note records that --show-stats answers plausibly with no +// server in existence), not an individual fast build (23% of srv1's builds were already fast, which +// is what made the defect look like luck), and not a green CI run. +// +// It is also the EARLY WARNING for the rest of the fleet, at no extra cost. The birth defect is +// fleet-wide: srv2's and srv3's cache servers live in ACTIVE runner slots and self-limit only +// because ephemeral runners recycle them, so each is one slot-retirement from srv1's state. A +// build-median divergence on either is that transition happening, and this same derivation sees it +// within a window. + +// THE LIVE PRODUCER IS MODELED BUT NOT YET SCHEDULED. build_latency_population_from_actions_api +// folds a list of ActionsBuildJobObservation rows — the fields the GitHub Actions jobs API returns +// for each job — into per-host BuildLatencyPopulation values filtered to the CI build job with +// conclusion=success. A thin collector (gh api / gh run list + jobs) is the only missing seam; it +// needs no host access and no fleet SSH key. The 2026-08-05 hand-entered populations remain the +// pre-actuation baseline until a fresh window is collected through this fold and written back. WET +// ACTUATION of the srv1 repair is a separate gate: this instrument only measures whether the repair +// worked once builds run again. data build_cache_latency_producer_disposition: Disposition = SingleAuthority diff --git a/dag/gunbc/build_cache/build_cache_provision_verdict.dag b/dag/gunbc/build_cache/build_cache_provision_verdict.dag index d4f0ebae856..a7274350cc2 100644 --- a/dag/gunbc/build_cache/build_cache_provision_verdict.dag +++ b/dag/gunbc/build_cache/build_cache_provision_verdict.dag @@ -25,17 +25,98 @@ import gunbc.build_cache_instance { data build_cache_provision_verdict_scope_disposition: Disposition = SingleAuthority -data verdict_variant_rename_note: String = "THE ARMS ARE BuildCacheProvisionConverged / BuildCacheProvisionRefused BECAUSE THE BARE SPELLING WAS A HOMONYM, and it refused the moment the fold entered a closure that already held the other one. gunbc.host_codex_runtime_provision_verdict's CodexRuntimeProvisionVerdict declares ProvisionConverged and ProvisionRefused; this fold declared the same two names over a different type, which was invisible while nothing imported both and became four refusals in gunbc.host_effect_realize the moment the wiring made it import both. Neither pair is generic across the two verdicts -- one carries an executable and a version line, the other a catalog id and a stats line -- so the shared spelling was never carrying a shared concept (DESIGN section 3). gunbc.build_cache_ensure build_cache_ensure_is_converged carries the same repair for the same reason and records the larger defect it exposes: reference binding that changes as a function of which unrelated modules a build happens to load. Renaming the declarations removes the class where disambiguating each reference would only treat the occurrence." +// THE ARMS ARE BuildCacheProvisionConverged / BuildCacheProvisionRefused BECAUSE THE BARE SPELLING +// WAS A HOMONYM, and it refused the moment the fold entered a closure that already held the other +// one. gunbc.host_codex_runtime_provision_verdict's CodexRuntimeProvisionVerdict declares +// ProvisionConverged and ProvisionRefused; this fold declared the same two names over a different +// type, which was invisible while nothing imported both and became four refusals in +// gunbc.host_effect_realize the moment the wiring made it import both. Neither pair is generic +// across the two verdicts -- one carries an executable and a version line, the other a catalog id +// and a stats line -- so the shared spelling was never carrying a shared concept (DESIGN section +// 3). gunbc.build_cache_ensure build_cache_ensure_is_converged carries the same repair for the same +// reason and records the larger defect it exposes: reference binding that changes as a function of +// which unrelated modules a build happens to load. Renaming the declarations removes the class +// where disambiguating each reference would only treat the occurrence. -data verdict_fold_home_note: String = "THE FOLD MOVED HERE OUT OF gunbc.host_build_cache_provision, AND THE REASON IS ACYCLICITY RATHER THAN TASTE (2026-08-21). Wiring the fold as the provisioning postcondition means gunbc.host_effect_realize must call it; host_build_cache_provision imports gunbc.host_toolchain_ensure, which imports host_effect_realize, so the import would have closed a cycle -- and acyclicity is the import graph's one structural law (DESIGN section 3). Splitting the fold from the effect-constructor module removes the cycle without weakening either: what stayed behind is the effect construction and the legacy provision entry point, and what came here is the judgement, which depends only on the catalog gate and the placement authority.\n\nIT ALSO PUTS THE JUDGEMENT WHERE ITS OWN NOTE SAID IT BELONGED. build_cache_placement_authority_is_elsewhere_note, below, already records that BuildCacheServerPlacement is derived from an observation of a deployed instance and that this fold is a CONSUMER of it. A consumer sitting inside a module that also constructs host effects was carrying two unrelated jobs; the split is the one the note argues for, arrived at from the other direction." +// THE FOLD MOVED HERE OUT OF gunbc.host_build_cache_provision, AND THE REASON IS ACYCLICITY RATHER +// THAN TASTE (2026-08-21). Wiring the fold as the provisioning postcondition means +// gunbc.host_effect_realize must call it; host_build_cache_provision imports +// gunbc.host_toolchain_ensure, which imports host_effect_realize, so the import would have closed a +// cycle -- and acyclicity is the import graph's one structural law (DESIGN section 3). Splitting +// the fold from the effect-constructor module removes the cycle without weakening either: what +// stayed behind is the effect construction and the legacy provision entry point, and what came here +// is the judgement, which depends only on the catalog gate and the placement authority. +// +// IT ALSO PUTS THE JUDGEMENT WHERE ITS OWN NOTE SAID IT BELONGED. +// build_cache_placement_authority_is_elsewhere_note, below, already records that +// BuildCacheServerPlacement is derived from an observation of a deployed instance and that this +// fold is a CONSUMER of it. A consumer sitting inside a module that also constructs host effects +// was carrying two unrelated jobs; the split is the one the note argues for, arrived at from the +// other direction. -data build_cache_path_authority_observation_note: String = "CI and ctrl-build sessions consume the cache as bare `sccache` on PATH (RUSTC_WRAPPER=sccache), so provisioning the modeled binary is NOT sufficient — the PATH-visible sccache must BE the modeled one. A second sccache earlier in PATH (a distro package, a stale copy) is precisely the two-authority state this lane exists to remove, and it is invisible to a read-back that probes the absolute path: that read-back greens on the binary we provisioned while the compiler calls a different one. DaemonPathShadowed carries BOTH paths so the refusal is located (which binary won, which one was expected) rather than merely typed. This judgement is modeled HERE, in the verdict fold (proven by design_witness_path_shadow_refuses_fail_closed against a synthetic DaemonPathShadowed observation), not decided by the provisioning shell. Its LIVE observation is STAGED, not emitted: per the shell→dag census this module's script side is an A5 `decompose` row that must not grow its emitted shell surface, so the `command -v sccache` resolve waits for the typed-argv dissolve-on (#5828) rather than landing as hand-shell now (review 42343). The model carries the state and its refusal today; only the live producer is deferred." +// CI and ctrl-build sessions consume the cache as bare `sccache` on PATH (RUSTC_WRAPPER=sccache), +// so provisioning the modeled binary is NOT sufficient — the PATH-visible sccache must BE the +// modeled one. A second sccache earlier in PATH (a distro package, a stale copy) is precisely the +// two-authority state this lane exists to remove, and it is invisible to a read-back that probes +// the absolute path: that read-back greens on the binary we provisioned while the compiler calls a +// different one. DaemonPathShadowed carries BOTH paths so the refusal is located (which binary won, +// which one was expected) rather than merely typed. This judgement is modeled HERE, in the verdict +// fold (proven by design_witness_path_shadow_refuses_fail_closed against a synthetic +// DaemonPathShadowed observation), not decided by the provisioning shell. Its LIVE observation is +// STAGED, not emitted: per the shell→dag census this module's script side is an A5 `decompose` row +// that must not grow its emitted shell surface, so the `command -v sccache` resolve waits for the +// typed-argv dissolve-on (#5828) rather than landing as hand-shell now (review 42343). The model +// carries the state and its refusal today; only the live producer is deferred. -data build_cache_supervision_observation_note: String = "A stats read-back CANNOT see whether the daemon it just talked to is supervised — and this is not a gap in the probe, it is the probe's semantics: `--show-stats` answers with a fully-formed table WHETHER OR NOT any server exists, so it reports health it has not established. CORRECTED 2026-08-01, and the correction matters because the original sentence named the wrong mechanism: this note previously said the call AUTO-STARTS a server and therefore manufactures the evidence it reports. Measured false for sccache 0.15.0 — two private-endpoint controls (UDS and TCP, each with a private cache dir) took the listener count 0 -> 0 across `--show-stats` while it printed all zeroes and returned rc=0; a third control showed that a real COMPILE is what takes the count 0 -> 1, with the new server inheriting the invoking process's cgroup. See extdeps.cache.sccache sccache_stats_query_does_not_spawn_note. The conclusion is unchanged and if anything sharper: stats establishes nothing about supervision OR existence, so it may only ever be supplemental data read AFTER ownership is independently grounded. On srv3 2026-07-25 it printed a full stats table while the modeled unit had been `inactive (dead)` for six hours and no sccache process existed beforehand. Read through the verdict fold as it stood, that host observed DaemonStatsOk and therefore converged: the provisioning act would have reported success over a host with NO durable cache — a fabricated plausible output (§5), and the same shape as DaemonPathShadowed (a read-back that greens on evidence it produced rather than on the state the lane exists to establish). DaemonUnsupervised splits that conflation: 'a daemon answered' and 'the modeled supervisor is running it' are different states with different remedies, so they get different names rather than one Ok. The LIVE observation is STAGED exactly as the PATH-shadow one is — the supervisor read (`systemctl --user is-active`) is not emitted as hand-shell here, because this module's script side is an A5 `decompose` census row that may shrink but must not grow (review 42343); it lands with the typed-argv dissolve-on (#5828). The model carries the state and its refusal today; only the live producer is deferred. Construction covers the case this observation would catch on a host provisioned BY THIS LANE — build_cache_unit_daemon_runs_until_stopped makes the self-exiting unit unwritable — so the observation's standing job is the residue construction cannot reach: a host whose unit was stopped, masked, or replaced out of band." +// A stats read-back CANNOT see whether the daemon it just talked to is supervised — and this is not +// a gap in the probe, it is the probe's semantics: `--show-stats` answers with a fully-formed table +// WHETHER OR NOT any server exists, so it reports health it has not established. CORRECTED +// 2026-08-01, and the correction matters because the original sentence named the wrong mechanism: +// this note previously said the call AUTO-STARTS a server and therefore manufactures the evidence +// it reports. Measured false for sccache 0.15.0 — two private-endpoint controls (UDS and TCP, each +// with a private cache dir) took the listener count 0 -> 0 across `--show-stats` while it printed +// all zeroes and returned rc=0; a third control showed that a real COMPILE is what takes the count +// 0 -> 1, with the new server inheriting the invoking process's cgroup. See extdeps.cache.sccache +// sccache_stats_query_does_not_spawn_note. The conclusion is unchanged and if anything sharper: +// stats establishes nothing about supervision OR existence, so it may only ever be supplemental +// data read AFTER ownership is independently grounded. On srv3 2026-07-25 it printed a full stats +// table while the modeled unit had been `inactive (dead)` for six hours and no sccache process +// existed beforehand. Read through the verdict fold as it stood, that host observed DaemonStatsOk +// and therefore converged: the provisioning act would have reported success over a host with NO +// durable cache — a fabricated plausible output (§5), and the same shape as DaemonPathShadowed (a +// read-back that greens on evidence it produced rather than on the state the lane exists to +// establish). DaemonUnsupervised splits that conflation: 'a daemon answered' and 'the modeled +// supervisor is running it' are different states with different remedies, so they get different +// names rather than one Ok. The LIVE observation is STAGED exactly as the PATH-shadow one is — the +// supervisor read (`systemctl --user is-active`) is not emitted as hand-shell here, because this +// module's script side is an A5 `decompose` census row that may shrink but must not grow (review +// 42343); it lands with the typed-argv dissolve-on (#5828). The model carries the state and its +// refusal today; only the live producer is deferred. Construction covers the case this observation +// would catch on a host provisioned BY THIS LANE — build_cache_unit_daemon_runs_until_stopped makes +// the self-exiting unit unwritable — so the observation's standing job is the residue construction +// cannot reach: a host whose unit was stopped, masked, or replaced out of band. -data build_cache_server_placement_doc: String = "WHY PLACEMENT IS A VERDICT AXIS AND NOT A DETAIL: extdeps.cache.sccache.sccache_server_process_facts cites that cache-miss compiles execute as CHILDREN OF THE SERVER PROCESS. So the server's cgroup is the cgroup every compile on the host runs in, and the server's caps bound every compile on the host — not just the caps of the job that issued them. A lazily auto-spawned server therefore silently exports one arbitrary job's memory caps onto the whole host's compile traffic, and SCCACHE_IDLE_TIMEOUT=0 keeps it there indefinitely. Answering --show-stats proves the server is RESPONSIVE; it proves nothing about WHERE it runs. Collapsing those two into one DaemonStatsOk arm is the DESIGN.md §5 state-space conflation, and it fabricates a plausible BuildCacheProvisionConverged over a host whose compiles are being throttled and killed." +// WHY PLACEMENT IS A VERDICT AXIS AND NOT A DETAIL: +// extdeps.cache.sccache.sccache_server_process_facts cites that cache-miss compiles execute as +// CHILDREN OF THE SERVER PROCESS. So the server's cgroup is the cgroup every compile on the host +// runs in, and the server's caps bound every compile on the host — not just the caps of the job +// that issued them. A lazily auto-spawned server therefore silently exports one arbitrary job's +// memory caps onto the whole host's compile traffic, and SCCACHE_IDLE_TIMEOUT=0 keeps it there +// indefinitely. Answering --show-stats proves the server is RESPONSIVE; it proves nothing about +// WHERE it runs. Collapsing those two into one DaemonStatsOk arm is the DESIGN.md §5 state-space +// conflation, and it fabricates a plausible BuildCacheProvisionConverged over a host whose compiles +// are being throttled and killed. -data build_cache_placement_authority_is_elsewhere_note: String = "BuildCacheServerPlacement is IMPORTED from gunbc.build_cache_instance, not declared here. It used to be declared in this module with three arms (ServerUnitOwned | ServerLazySpawned | ServerPlacementUnknown). It moved because placement is derived from an observation of a DEPLOYED INSTANCE and this fold is a consumer of it; leaving a copy here would be two definitions of 'correctly placed' that can drift (DESIGN section 3). The arms also changed shape with the move, and the renames are honesty rather than churn: ServerLazySpawned named an INFERRED CAUSE (that the server was lazily auto-spawned) while what an observer can actually see is the current owner, so it is now ServerRunnerOwned carrying the slot and its lifecycle; and ServerUnitOwned became ServerManagedUnitOwned to say WHICH unit, since 'a unit owns it' is satisfied by any unit including the wrong one." +// BuildCacheServerPlacement is IMPORTED from gunbc.build_cache_instance, not declared here. It used +// to be declared in this module with three arms (ServerUnitOwned | ServerLazySpawned | +// ServerPlacementUnknown). It moved because placement is derived from an observation of a DEPLOYED +// INSTANCE and this fold is a consumer of it; leaving a copy here would be two definitions of +// 'correctly placed' that can drift (DESIGN section 3). The arms also changed shape with the move, +// and the renames are honesty rather than churn: ServerLazySpawned named an INFERRED CAUSE (that +// the server was lazily auto-spawned) while what an observer can actually see is the current owner, +// so it is now ServerRunnerOwned carrying the slot and its lifecycle; and ServerUnitOwned became +// ServerManagedUnitOwned to say WHICH unit, since 'a unit owns it' is satisfied by any unit +// including the wrong one. type BuildCacheDaemonObservation = DaemonStatsOk { stats_line: String, placement: BuildCacheServerPlacement } @@ -92,7 +173,19 @@ fn provision_build_cache_absorbing_fallback_widen_sketch( } } -data provision_gate_consumes_the_fold_note: String = "THE GATE ASKS THE AUTHORITY; IT DOES NOT HOLD A SECOND OPINION. It used to be a full hand-written traversal of BuildCacheDaemonObservation — and, inside the DaemonStatsOk arm, of BuildCacheServerPlacement — mapping each arm to the verdict shape it expected. That mapping is exactly what provision_build_cache_verdict and placement_verdict already compute, so the same fact was written twice and either copy could drift while both typechecked (review 46217; DESIGN sections 2 and 3). The drift is not hypothetical for a gate whose whole job is to reject widens: a placement arm added to the coproduct and handled in the fold but forgotten here would silently keep accepting whatever the gate's stale arm said. Consuming the fold means a new arm is impossible to forget, because there is nowhere left to forget it. What the gate still owns is the one judgement the fold cannot make for itself: WHICH PART of a verdict is contractual. Two verdicts agree when they agree on converged-versus-refused and, when refused, on the CAUSE — the located reason string is a diagnostic payload, not part of the contract, so comparing it would make every wording edit a gate failure while catching nothing a cause comparison misses." +// THE GATE ASKS THE AUTHORITY; IT DOES NOT HOLD A SECOND OPINION. It used to be a full hand-written +// traversal of BuildCacheDaemonObservation — and, inside the DaemonStatsOk arm, of +// BuildCacheServerPlacement — mapping each arm to the verdict shape it expected. That mapping is +// exactly what provision_build_cache_verdict and placement_verdict already compute, so the same +// fact was written twice and either copy could drift while both typechecked (review 46217; DESIGN +// sections 2 and 3). The drift is not hypothetical for a gate whose whole job is to reject widens: +// a placement arm added to the coproduct and handled in the fold but forgotten here would silently +// keep accepting whatever the gate's stale arm said. Consuming the fold means a new arm is +// impossible to forget, because there is nowhere left to forget it. What the gate still owns is the +// one judgement the fold cannot make for itself: WHICH PART of a verdict is contractual. Two +// verdicts agree when they agree on converged-versus-refused and, when refused, on the CAUSE — the +// located reason string is a diagnostic payload, not part of the contract, so comparing it would +// make every wording edit a gate failure while catching nothing a cause comparison misses. fn verdict_shape_matches( actual: BuildCacheProvisionVerdict, @@ -269,6 +362,33 @@ fn misconfigured_matches_cited_fragment() -> Bool { ) } +// RED-control fixture modeling TWO forbidden widens; it must never ship as the authority verdict +// fold. (1) The absorbing fallback (the -u RUSTC_WRAPPER cache-drop): the live instance +// ci_retry_escalation_level2 was REMOVED 2026-07-23 (STEP 2) — the eagain retry now escalates to +// CARGO_BUILD_JOBS=1 (keep sccache) only, then fails loud via verify-build-artifacts, so no +// cache-drop widen remains and this fixture guards the fold against its re-introduction. (2) The +// PLACEMENT widen: its DaemonStatsOk arm discards the placement field, i.e. 'responsive implies +// converged'. That is precisely the behavior that shipped on srv1 — a lazily auto-spawned server in +// the cgroup of srv1-10, then an inactive and disabled slot, accumulated 229780 memory.max breach +// events and 12 oom_kills while every provision read reported converged. Re-confirmed live +// 2026-08-01: pid 3975946, age 31.2d, cgroup actions-runner@srv1-10.service (unit +// inactive+disabled, slots 01-05 are the live ones), holding LISTEN on +// /var/lib/ctrl/sccache-ci/server.sock, resident beside an active runner's rustc. INCIDENT CLEARED +// 2026-08-07 (lane_e_srv1_ci_noncompletion_root_cause_receipt): pid 3975946 no longer exists and +// srv1-10 was then still inactive+disabled; the stranded-server symptom was gone, and srv1 carried +// a young runner-owned server on ACTIVE slot srv1-02 at that date -- the ServerRunnerOwned arm this +// fold must refuse BuildCacheProvisionConverged on until ServerManagedUnitOwned lands. +// +// BOTH OF THOSE HOST SENTENCES WERE WRITTEN IN THE PRESENT TENSE AND HAVE EXPIRED; the fixture they +// justify has not. Measured 2026-08-25T18:43Z: srv1-10 is ACTIVE and ENABLED, one of 21 contiguous +// slots srv1 serves, so it is an ordinary serving slot and not a dead one; and srv1 runs NO sccache +// process, so there is no runner-owned server on srv1-02 or anywhere else. What every host does +// carry is the OTHER shape -- a CI socket present at /var/lib/ctrl/sccache-ci/server.sock with zero +// listeners behind it, on all four. The defect class this RED control guards against is therefore +// not currently instantiated by a live owner, which changes nothing about why the widen must never +// ship: a fold that reads `responsive implies converged` is wrong whether or not a wrongly-parented +// server happens to exist this week. + fn daemon_absent_refuses_fail_closed() -> Bool { match provision_build_cache_verdict( catalog_id: sccache_local_id, @@ -278,5 +398,3 @@ fn daemon_absent_refuses_fail_closed() -> Bool { BuildCacheProvisionConverged { catalog_id: _, stats_line: _ } => false } } - -data provision_build_cache_widen_sketch_note: String = "RED-control fixture modeling TWO forbidden widens; it must never ship as the authority verdict fold. (1) The absorbing fallback (the -u RUSTC_WRAPPER cache-drop): the live instance ci_retry_escalation_level2 was REMOVED 2026-07-23 (STEP 2) — the eagain retry now escalates to CARGO_BUILD_JOBS=1 (keep sccache) only, then fails loud via verify-build-artifacts, so no cache-drop widen remains and this fixture guards the fold against its re-introduction. (2) The PLACEMENT widen: its DaemonStatsOk arm discards the placement field, i.e. 'responsive implies converged'. That is precisely the behavior that shipped on srv1 — a lazily auto-spawned server in the cgroup of srv1-10, then an inactive and disabled slot, accumulated 229780 memory.max breach events and 12 oom_kills while every provision read reported converged. Re-confirmed live 2026-08-01: pid 3975946, age 31.2d, cgroup actions-runner@srv1-10.service (unit inactive+disabled, slots 01-05 are the live ones), holding LISTEN on /var/lib/ctrl/sccache-ci/server.sock, resident beside an active runner's rustc. INCIDENT CLEARED 2026-08-07 (lane_e_srv1_ci_noncompletion_root_cause_receipt): pid 3975946 no longer exists and srv1-10 was then still inactive+disabled; the stranded-server symptom was gone, and srv1 carried a young runner-owned server on ACTIVE slot srv1-02 at that date -- the ServerRunnerOwned arm this fold must refuse BuildCacheProvisionConverged on until ServerManagedUnitOwned lands.\n\nBOTH OF THOSE HOST SENTENCES WERE WRITTEN IN THE PRESENT TENSE AND HAVE EXPIRED; the fixture they justify has not. Measured 2026-08-25T18:43Z: srv1-10 is ACTIVE and ENABLED, one of 21 contiguous slots srv1 serves, so it is an ordinary serving slot and not a dead one; and srv1 runs NO sccache process, so there is no runner-owned server on srv1-02 or anywhere else. What every host does carry is the OTHER shape -- a CI socket present at /var/lib/ctrl/sccache-ci/server.sock with zero listeners behind it, on all four. The defect class this RED control guards against is therefore not currently instantiated by a live owner, which changes nothing about why the widen must never ship: a fold that reads `responsive implies converged` is wrong whether or not a wrongly-parented server happens to exist this week." diff --git a/dag/gunbc/build_cache/build_cache_unit.dag b/dag/gunbc/build_cache/build_cache_unit.dag index c8b23728c6b..6af74b6e38e 100644 --- a/dag/gunbc/build_cache/build_cache_unit.dag +++ b/dag/gunbc/build_cache/build_cache_unit.dag @@ -24,11 +24,91 @@ import extdeps.cache.sccache { data build_cache_unit_scope_disposition: Disposition = SingleAuthority -data build_cache_unit_authority_note: String = "THE UNIT IS DERIVED FROM THE INSTANCE, WHICH IS THE WHOLE POINT. A hand-written unit beside a declared BuildCacheInstance is two representations of one deployment, and they drift in the direction that is hardest to notice: the declaration says what we meant and the unit says what runs, so an observation checked against the declaration passes while the host does something else. Rendering the unit FROM the instance means a disagreement has nowhere to live (DESIGN section 5, construction over validation) — the same move placement made when observed_cgroup went inside EndpointOwnerContext.\n\nWHAT THE RENDER IS GROUNDED ON. Not invention: a live sudo capture of srv1's running CI server on 2026-08-01, and the ctrl-sccache.service unit that already manages the SESSION instance on srv1 and srv2. Every line below either comes from a field of the instance or is a constant that capture showed present. The captured CI environment was exactly SCCACHE_DIR, SCCACHE_ERROR_LOG, SCCACHE_IDLE_TIMEOUT=0, SCCACHE_SERVER_UDS, SCCACHE_START_SERVER=1 and a SCCACHE_STARTUP_NOTIFY temp path — and that last one is the tell that no unit exists: it is created by connect_or_start on a first cache-miss compile, so the server was born from a runner's compile rather than started by systemd.\n\nTHE UID IS LOAD-BEARING AND IS NOT A HOST CONSTANT. ctrl-sccache.service's own header records why: the UDS is created srwxr-xr-x, so connect() requires write and only the owning uid may connect; a cross-uid client fails SILENTLY and falls back to a per-container server, which is a cache miss that looks like a cache. The captured CI server runs as ghrunner while the session server on the same host runs as briansrls, so the principal genuinely differs per INSTANCE on one host and must stay a field rather than a per-host constant." +// THE UNIT IS DERIVED FROM THE INSTANCE, WHICH IS THE WHOLE POINT. A hand-written unit beside a +// declared BuildCacheInstance is two representations of one deployment, and they drift in the +// direction that is hardest to notice: the declaration says what we meant and the unit says what +// runs, so an observation checked against the declaration passes while the host does something +// else. Rendering the unit FROM the instance means a disagreement has nowhere to live (DESIGN +// section 5, construction over validation) — the same move placement made when observed_cgroup went +// inside EndpointOwnerContext. +// +// WHAT THE RENDER IS GROUNDED ON. Not invention: a live sudo capture of srv1's running CI server on +// 2026-08-01, and the ctrl-sccache.service unit that already manages the SESSION instance on srv1 +// and srv2. Every line below either comes from a field of the instance or is a constant that +// capture showed present. The captured CI environment was exactly SCCACHE_DIR, SCCACHE_ERROR_LOG, +// SCCACHE_IDLE_TIMEOUT=0, SCCACHE_SERVER_UDS, SCCACHE_START_SERVER=1 and a SCCACHE_STARTUP_NOTIFY +// temp path — and that last one is the tell that no unit exists: it is created by connect_or_start +// on a first cache-miss compile, so the server was born from a runner's compile rather than started +// by systemd. +// +// THE UID IS LOAD-BEARING AND IS NOT A HOST CONSTANT. ctrl-sccache.service's own header records +// why: the UDS is created srwxr-xr-x, so connect() requires write and only the owning uid may +// connect; a cross-uid client fails SILENTLY and falls back to a per-container server, which is a +// cache miss that looks like a cache. The captured CI server runs as ghrunner while the session +// server on the same host runs as briansrls, so the principal genuinely differs per INSTANCE on one +// host and must stay a field rather than a per-host constant. -data systemd_medium_is_modeled_note: String = "THE UNIT IS A MODELED SystemdUnitFile, NOT CONCATENATED INI TEXT, AND THIS ROW RECORDS THE TRIGGER THAT FIRED. Until this change every line below was a string literal spelled here -- \"[Unit]\", \"[Service]\", \"Environment=SCCACHE_DIR=...\", \"WantedBy=multi-user.target\" -- which made gunbc a SECOND AUTHORITY for systemd's unit-file syntax beside extdeps.systemd.unit_file, the exact section 3 nicknaming that module's own header says it exists to prevent. Three facts were fused into each opaque line: a section, a directive name, and a value, none of them projectable, diffable, or structurally refusable.\n\nTHE PRIOR ROW HERE DECLARED THAT DEBT AND NAMED THE WRONG EXIT. It said the dissolve-on was extdeps.languages gaining a systemd unit grammar. The grammar landed at extdeps.systemd.unit_file instead -- cited to systemd.unit(5), systemd.service(5), systemd.exec(5) and now systemd.resource-control(5), with byte-exact witnesses including one pinning a live fleet timer's deployed bytes -- so the condition had effectively been met and nothing noticed, because the note pointed at a module that was never going to be the home. That is the declared-and-left-standing class rather than an open debt, and it is why the trigger is recorded here as fired rather than quietly deleted.\n\nWHAT THE MIGRATION BOUGHT BEYOND TIDINESS. A directive in the wrong section now has no representation: [Unit], [Service], [Slice] and [Install] each have their own directive type, so Slice= cannot be written into [Unit] and WantedBy= cannot be written into [Service]. That is a construction wall where there was previously nothing at all -- the host's systemd was the only thing that would ever have refused a misplaced directive, long after delivery. Section order and section membership likewise stop being things this module can get wrong.\n\nWHAT DID NOT CHANGE, and it is the point of keeping the render at all: every directive value is still a field of the BuildCacheInstance or a row from extdeps.cache.sccache. This module maps a typed instance onto a typed unit file; it decides nothing and defaults nothing.\n\nTHE INSTALLED BYTES DO NOT CHANGE, AND THAT WAS MEASURED RATHER THAN REASONED. An earlier revision of this row declared a one-byte behavioural change -- that the shared serializer terminates the file with a newline where the hand renderer did not, so units on hosts would gain a trailing newline. That claim was WRONG, and it was wrong in the direction that would have shipped a declared behaviour change that never happens. The hand renderer's output was joined into a heredoc, and the heredoc supplies the terminating newline itself, so the delivered file already ended in exactly one newline; the serializer's terminator merely moves that byte's origin from the heredoc into the render. Where the two would have compounded -- the terminator joined as a sibling line, yielding a trailing BLANK line -- the write site concatenates rather than joins, which is why the bytes hold.\n\nThe receipt: gunbc.host_effect_realize build_cache_ensure_daemon_body was rendered on this tree and on origin/main with one compiler, for the CI instance (absent capacity) and the session instance (present capacity), and the two bodies are byte-identical -- with a control confirming the two trees genuinely differ, because two runs that silently measure the same tree agree for a reason that has nothing to do with the code. What DOES change is the line-view projection: render_build_cache_unit and render_compile_pool_slice now carry a trailing empty element, the faithful decomposition of a file that ends in a newline. That view reaches tests and gunbc.compile_pool_ensure's admission, and reaches no host." +// THE UNIT IS A MODELED SystemdUnitFile, NOT CONCATENATED INI TEXT, AND THIS ROW RECORDS THE +// TRIGGER THAT FIRED. Until this change every line below was a string literal spelled here -- +// "[Unit]", "[Service]", "Environment=SCCACHE_DIR=...", "WantedBy=multi-user.target" -- which made +// gunbc a SECOND AUTHORITY for systemd's unit-file syntax beside extdeps.systemd.unit_file, the +// exact section 3 nicknaming that module's own header says it exists to prevent. Three facts were +// fused into each opaque line: a section, a directive name, and a value, none of them projectable, +// diffable, or structurally refusable. +// +// THE PRIOR ROW HERE DECLARED THAT DEBT AND NAMED THE WRONG EXIT. It said the dissolve-on was +// extdeps.languages gaining a systemd unit grammar. The grammar landed at extdeps.systemd.unit_file +// instead -- cited to systemd.unit(5), systemd.service(5), systemd.exec(5) and now +// systemd.resource-control(5), with byte-exact witnesses including one pinning a live fleet timer's +// deployed bytes -- so the condition had effectively been met and nothing noticed, because the note +// pointed at a module that was never going to be the home. That is the declared-and-left-standing +// class rather than an open debt, and it is why the trigger is recorded here as fired rather than +// quietly deleted. +// +// WHAT THE MIGRATION BOUGHT BEYOND TIDINESS. A directive in the wrong section now has no +// representation: [Unit], [Service], [Slice] and [Install] each have their own directive type, so +// Slice= cannot be written into [Unit] and WantedBy= cannot be written into [Service]. That is a +// construction wall where there was previously nothing at all -- the host's systemd was the only +// thing that would ever have refused a misplaced directive, long after delivery. Section order and +// section membership likewise stop being things this module can get wrong. +// +// WHAT DID NOT CHANGE, and it is the point of keeping the render at all: every directive value is +// still a field of the BuildCacheInstance or a row from extdeps.cache.sccache. This module maps a +// typed instance onto a typed unit file; it decides nothing and defaults nothing. +// +// THE INSTALLED BYTES DO NOT CHANGE, AND THAT WAS MEASURED RATHER THAN REASONED. An earlier +// revision of this row declared a one-byte behavioural change -- that the shared serializer +// terminates the file with a newline where the hand renderer did not, so units on hosts would gain +// a trailing newline. That claim was WRONG, and it was wrong in the direction that would have +// shipped a declared behaviour change that never happens. The hand renderer's output was joined +// into a heredoc, and the heredoc supplies the terminating newline itself, so the delivered file +// already ended in exactly one newline; the serializer's terminator merely moves that byte's origin +// from the heredoc into the render. Where the two would have compounded -- the terminator joined as +// a sibling line, yielding a trailing BLANK line -- the write site concatenates rather than joins, +// which is why the bytes hold. +// +// The receipt: gunbc.host_effect_realize build_cache_ensure_daemon_body was rendered on this tree +// and on origin/main with one compiler, for the CI instance (absent capacity) and the session +// instance (present capacity), and the two bodies are byte-identical -- with a control confirming +// the two trees genuinely differ, because two runs that silently measure the same tree agree for a +// reason that has nothing to do with the code. What DOES change is the line-view projection: +// render_build_cache_unit and render_compile_pool_slice now carry a trailing empty element, the +// faithful decomposition of a file that ends in a newline. That view reaches tests and +// gunbc.compile_pool_ensure's admission, and reaches no host. -data absent_capacity_omits_the_line_note: String = "AN ABSENT CAPACITY OMITS THE LINE; IT DOES NOT RENDER A DEFAULT. BuildCacheInstance.intended_capacity is optional, and CI's is deliberately Absent because the live 10 GiB is sccache's own built-in default rather than a number anybody chose. The live capture SETTLED that this is the right reading: srv1's running CI server carries NO SCCACHE_CACHE_SIZE in its environment at all, so 10 GiB is genuinely a default and not a decision recorded elsewhere.\n\nRendering `SCCACHE_CACHE_SIZE=10G` for an Absent capacity would therefore fabricate a decision nobody made and, worse, FREEZE it: once the number is written into a generated unit it becomes indistinguishable from an intended one, and the tracked gap silently closes without anyone choosing a value. Omitting the line preserves the same behaviour the host has today — sccache applies its own default — while leaving the absence visible in the instance where it can still be counted. This is the one place in the render where doing less is the fail-closed choice." +// AN ABSENT CAPACITY OMITS THE LINE; IT DOES NOT RENDER A DEFAULT. +// BuildCacheInstance.intended_capacity is optional, and CI's is deliberately Absent because the +// live 10 GiB is sccache's own built-in default rather than a number anybody chose. The live +// capture SETTLED that this is the right reading: srv1's running CI server carries NO +// SCCACHE_CACHE_SIZE in its environment at all, so 10 GiB is genuinely a default and not a decision +// recorded elsewhere. +// +// Rendering `SCCACHE_CACHE_SIZE=10G` for an Absent capacity would therefore fabricate a decision +// nobody made and, worse, FREEZE it: once the number is written into a generated unit it becomes +// indistinguishable from an intended one, and the tracked gap silently closes without anyone +// choosing a value. Omitting the line preserves the same behaviour the host has today — sccache +// applies its own default — while leaving the absence visible in the instance where it can still be +// counted. This is the one place in the render where doing less is the fail-closed choice. fn sccache_endpoint_env_assignment(instance: BuildCacheInstance) -> NonEmptyStr { match instance.endpoint { @@ -54,7 +134,22 @@ fn build_cache_unit_name(instance: BuildCacheInstance) -> NonEmptyStr { instance.intended_unit } -data unit_ordering_note: String = "THE ORDERING LINES ARE THE PHASE-3 REBIRTH GUARD, AND THEY ARE AN INTERIM RATHER THAN A WALL. A runner that starts before a managed cache exists will connect-or-start on its first cache-miss compile and adopt whatever cgroup it is in — which is exactly how srv1's server came to live in slot srv1-10 and how srv2's and srv3's came to live in active ones. Requires= plus After= on the runner template makes systemd refuse to start a runner whose cache unit failed, and orders it after the cache on boot.\n\nWHAT IT DOES NOT DO, stated so it is not read as the fix: systemd ordering constrains the BOOT and START path only. It does not prevent a runner already running from outliving a stopped cache unit, and it does not make a bad birth impossible — a runner started by hand with systemctl --no-block, or a cache unit that starts and then dies, both leave the window open. The construction-side answer is the activation admission in gunbc.runner_activation, which refuses to yield an enable command at all without a bound readiness receipt. These lines are defence in depth beneath that, with a bounded runway: they retire when activation withdrawal lands, so that a cache unit going away withdraws the admission that licensed the runners depending on it rather than merely failing to order them." +// THE ORDERING LINES ARE THE PHASE-3 REBIRTH GUARD, AND THEY ARE AN INTERIM RATHER THAN A WALL. A +// runner that starts before a managed cache exists will connect-or-start on its first cache-miss +// compile and adopt whatever cgroup it is in — which is exactly how srv1's server came to live in +// slot srv1-10 and how srv2's and srv3's came to live in active ones. Requires= plus After= on the +// runner template makes systemd refuse to start a runner whose cache unit failed, and orders it +// after the cache on boot. +// +// WHAT IT DOES NOT DO, stated so it is not read as the fix: systemd ordering constrains the BOOT +// and START path only. It does not prevent a runner already running from outliving a stopped cache +// unit, and it does not make a bad birth impossible — a runner started by hand with systemctl +// --no-block, or a cache unit that starts and then dies, both leave the window open. The +// construction-side answer is the activation admission in gunbc.runner_activation, which refuses to +// yield an enable command at all without a bound readiness receipt. These lines are defence in +// depth beneath that, with a bounded runway: they retire when activation withdrawal lands, so that +// a cache unit going away withdraws the admission that licensed the runners depending on it rather +// than merely failing to order them. fn runner_ordering_drop_in(instance: BuildCacheInstance) -> SystemdDropIn { SystemdDropIn { @@ -140,7 +235,35 @@ fn render_build_cache_unit(instance: BuildCacheInstance) -> List { systemd_unit_file_lines(file: build_cache_unit_file(instance: instance)) } -data foreground_supervision_note: String = "ExecStart IS BARE, AND THE ENV ASSIGNMENTS COME FROM extdeps ROWS. An earlier draft of this render wrote `ExecStart=/usr/local/bin/sccache --start-server` with hand-spelled Environment literals. Both were wrong, and the tree already knew why.\n\n--start-server DAEMONIZES and returns 0, so under Type=simple systemd sees ExecStart exit cleanly and marks the unit inactive (dead) the instant it starts; the backgrounded server then idle-times-out unsupervised and Restart=on-failure never fires, because a clean exit is not a failure. Proven live on srv3 2026-07-25 -- the unit sat inactive with no sccache process, surviving only through sccache's own per-invocation auto-start, i.e. the unit was a no-op. That is the specification-without-execution trap in its purest form: a string witness asserting ExecStart matched the install path was GREEN while the unit did nothing.\n\nSo the form is bare ExecStart plus SCCACHE_START_SERVER=1 and SCCACHE_NO_DAEMON=1, matching the production ctrl-sccache.service invocation verified running for a month on srv1 and srv2 -- converging on the existing authority rather than inventing a third shape. SCCACHE_IDLE_TIMEOUT=0 is required for the same reason one level deeper: the foreground server otherwise exits status 0 after ten quiet minutes (measured on srv3: Duration 10min 8.215s, status=0/SUCCESS) and the unit dies clean. Restart=always would be the wrong repair -- it would relaunch THROUGH the clean exit and make the defect invisible, an absorbing fallback whose deficit frequency drops to zero by construction.\n\nAll three values come from extdeps.cache.sccache rows rather than literals spelled here, so gunbc is not a second authority for sccache env var names.\n\nHOW THIS WAS FOUND, because it is the point: the defect was caught by gunbc.host_build_cache_provision_script's existing construction checks -- build_cache_unit_is_foreground_supervised refuses a body containing --start-server -- when the A5 swap pointed them at this render. The wall built for the hand-written unit caught the generated one on its first contact." +// ExecStart IS BARE, AND THE ENV ASSIGNMENTS COME FROM extdeps ROWS. An earlier draft of this +// render wrote `ExecStart=/usr/local/bin/sccache --start-server` with hand-spelled Environment +// literals. Both were wrong, and the tree already knew why. +// +// --start-server DAEMONIZES and returns 0, so under Type=simple systemd sees ExecStart exit cleanly +// and marks the unit inactive (dead) the instant it starts; the backgrounded server then +// idle-times-out unsupervised and Restart=on-failure never fires, because a clean exit is not a +// failure. Proven live on srv3 2026-07-25 -- the unit sat inactive with no sccache process, +// surviving only through sccache's own per-invocation auto-start, i.e. the unit was a no-op. That +// is the specification-without-execution trap in its purest form: a string witness asserting +// ExecStart matched the install path was GREEN while the unit did nothing. +// +// So the form is bare ExecStart plus SCCACHE_START_SERVER=1 and SCCACHE_NO_DAEMON=1, matching the +// production ctrl-sccache.service invocation verified running for a month on srv1 and srv2 -- +// converging on the existing authority rather than inventing a third shape. SCCACHE_IDLE_TIMEOUT=0 +// is required for the same reason one level deeper: the foreground server otherwise exits status 0 +// after ten quiet minutes (measured on srv3: Duration 10min 8.215s, status=0/SUCCESS) and the unit +// dies clean. Restart=always would be the wrong repair -- it would relaunch THROUGH the clean exit +// and make the defect invisible, an absorbing fallback whose deficit frequency drops to zero by +// construction. +// +// All three values come from extdeps.cache.sccache rows rather than literals spelled here, so gunbc +// is not a second authority for sccache env var names. +// +// HOW THIS WAS FOUND, because it is the point: the defect was caught by +// gunbc.host_build_cache_provision_script's existing construction checks -- +// build_cache_unit_is_foreground_supervised refuses a body containing --start-server -- when the A5 +// swap pointed them at this render. The wall built for the hand-written unit caught the generated +// one on its first contact. // THE COMPILE-POOL SLICE, RENDERED FROM THE SAME AUTHORITY THAT SIZES IT. The slice's memory ceiling is not a number this module chooses: gunbc.fleet_host_budget derives it through compile_pool_bytes_for from extdeps.jobserver's jobserver_build_pool_bytes, the same cited knob that bounds build concurrency, so the pool holds exactly the compiles the jobserver admits. Taking the size as a parameter here rather than computing one keeps that single authority intact — this module renders, it does not decide. // diff --git a/dag/gunbc/capability_binding.dag b/dag/gunbc/capability_binding.dag index 7b8a92ce8c1..189e1d75e2e 100644 --- a/dag/gunbc/capability_binding.dag +++ b/dag/gunbc/capability_binding.dag @@ -12,7 +12,13 @@ data capability_binding_note: String = "SUPPORT IS NEVER AN OPERATING-SYSTEM BOO So the support matrix is a PROJECTION over executed observations, never an authored table, and this module carries the join that makes that projection possible: a host, a requirement, a provider, and what actually happened when they met." -data source_provider_naming_note: String = "THE FIELD NAMES A PROVIDER, SO IT IS CALLED ONE (review finding, 2026-08-07). It was source_binding, and what it actually carries is a reference to a producer — the synthetic tap's own function, or an upstream interface declaration. A CapabilityBinding is the join of a host, a requirement and a provider; neither of those references is one, and calling them bindings made a three-way join look present where a single provider identity stood. The rename is the whole repair at this rung: an impulse does not need a binding to be a valid impulse, and inventing one to satisfy a field name would have been worse than the name." +// THE FIELD NAMES A PROVIDER, SO IT IS CALLED ONE (review finding, 2026-08-07). It was +// source_binding, and what it actually carries is a reference to a producer — the synthetic tap's +// own function, or an upstream interface declaration. A CapabilityBinding is the join of a host, a +// requirement and a provider; neither of those references is one, and calling them bindings made a +// three-way join look present where a single provider identity stood. The rename is the whole +// repair at this rung: an impulse does not need a binding to be a valid impulse, and inventing one +// to satisfy a field name would have been worse than the name. data physical_impulse_note: String = "THE ONE FACT EVERY BINDING PRODUCES, AND THE REASON THE BOBBER PHYSICS NEVER LEARNS WHERE MOTION CAME FROM. A web DeviceMotion event, a web LinearAccelerationSensor reading, a Windows sensor sample, an Apple Core Motion update, an Android SensorManager callback, a Linux IIO read and the deterministic synthetic tap all converge here. The display consumes PhysicalImpulse and nothing else, so a new platform binding is a new producer and changes no physics, no projection and no roadmap semantics (DESIGN §2 Realization: one kernel, N handlers). @@ -40,7 +46,16 @@ type PhysicalImpulse { observed_at_ms: Milliseconds } -data requirement_standing_note: String = "THESE THRESHOLDS ARE AUDITION CANDIDATES, NOT A SIGNED CONTRACT (review finding, 2026-08-06). The cited W3C sources ground the units, the interface behaviour, the nullability and the quantisation floor; they do NOT ground a product's choice of three axes, fifty hertz, a hundred and twenty milliseconds and two thousand milli-m/s^2. An earlier revision of this module wrote justifications for each number that READ as derivations and were in fact hypotheses — the most dangerous shape a constant can take, because it looks settled. The standing is therefore carried in the type, and the requirement cannot be described as signed until three auditions have actually run: synthetic bob testing establishing acceptable response latency, idle and typing negative controls establishing a magnitude threshold that discriminates, and at least one real stream establishing attainable sampling behaviour." +// THESE THRESHOLDS ARE AUDITION CANDIDATES, NOT A SIGNED CONTRACT (review finding, 2026-08-06). The +// cited W3C sources ground the units, the interface behaviour, the nullability and the quantisation +// floor; they do NOT ground a product's choice of three axes, fifty hertz, a hundred and twenty +// milliseconds and two thousand milli-m/s^2. An earlier revision of this module wrote +// justifications for each number that READ as derivations and were in fact hypotheses — the most +// dangerous shape a constant can take, because it looks settled. The standing is therefore carried +// in the type, and the requirement cannot be described as signed until three auditions have +// actually run: synthetic bob testing establishing acceptable response latency, idle and typing +// negative controls establishing a magnitude threshold that discriminates, and at least one real +// stream establishing attainable sampling behaviour. type RequirementStanding = AuditionProvisional { pending: String } @@ -53,7 +68,13 @@ fn requirement_is_signed(s: RequirementStanding) -> Bool { } } -data impulse_requirement_note: String = "A READABLE SENSOR IS NOT A FIT SENSOR, AND THIS IS THE DISTINCTION A SUPPORT MATRIX MOST OFTEN LOSES. The bob does not require 'an accelerometer'; it requires an interaction-quality contract — enough axes, a sample rate fast enough that a knock is not aliased away, a detection latency short enough that the response still feels caused, a declared answer to whether gravity is included in the signal, and a discriminating magnitude that separates a deliberate knock from someone typing. A sensor that streams beautifully and cannot tell a tap from a keystroke must REFUSE fitness for this use rather than passing because it read something." +// A READABLE SENSOR IS NOT A FIT SENSOR, AND THIS IS THE DISTINCTION A SUPPORT MATRIX MOST OFTEN +// LOSES. The bob does not require 'an accelerometer'; it requires an interaction-quality contract — +// enough axes, a sample rate fast enough that a knock is not aliased away, a detection latency +// short enough that the response still feels caused, a declared answer to whether gravity is +// included in the signal, and a discriminating magnitude that separates a deliberate knock from +// someone typing. A sensor that streams beautifully and cannot tell a tap from a keystroke must +// REFUSE fitness for this use rather than passing because it read something. type GravityHandling = GravityIncluded @@ -77,7 +98,14 @@ type PhysicalImpulseRequirement { standing: RequirementStanding } -data bob_requirement_candidate_note: String = "The candidate values and the reasoning that produced them, labelled as reasoning rather than as measurement. Three axes, because a knock on a chassis arrives on whichever face was struck. Fifty samples a second, because a knock's useful signal is a few tens of milliseconds long and a slower stream would alias it. A hundred and twenty milliseconds of detection latency, because past roughly that a response stops reading as caused. Gravity excluded, because a constant one-gravity bias would swamp the discriminator. Two thousand milli-m/s^2 as the discriminating floor, which is above the cited standard's own 0.1 m/s^2 precision limit and is BELIEVED to be above the incidental motion of typing — the belief the negative control exists to test." +// The candidate values and the reasoning that produced them, labelled as reasoning rather than as +// measurement. Three axes, because a knock on a chassis arrives on whichever face was struck. Fifty +// samples a second, because a knock's useful signal is a few tens of milliseconds long and a slower +// stream would alias it. A hundred and twenty milliseconds of detection latency, because past +// roughly that a response stops reading as caused. Gravity excluded, because a constant one-gravity +// bias would swamp the discriminator. Two thousand milli-m/s^2 as the discriminating floor, which +// is above the cited standard's own 0.1 m/s^2 precision limit and is BELIEVED to be above the +// incidental motion of typing — the belief the negative control exists to test. data bob_impulse_requirement_candidate: PhysicalImpulseRequirement = PhysicalImpulseRequirement { axis_count: 3, @@ -90,7 +118,13 @@ data bob_impulse_requirement_candidate: PhysicalImpulseRequirement = PhysicalImp }, } -data observed_quantity_note: String = "AN UNOBSERVED QUANTITY IS NOT ZERO (review finding, 2026-08-06). An earlier revision projected a web reading into a sample receipt carrying frequency_hz: 0 and noise_floor_milli: 0 because the web observation supplied neither — and a zero noise floor is not an absence, it is a claim of a perfect signal, which is the strongest possible statement about a quantity nobody measured. Each measured axis therefore carries its own observed-or-not coproduct, and a qualification that needs a dimension nobody observed REFUSES on that dimension rather than reading the fabricated zero as a pass." +// AN UNOBSERVED QUANTITY IS NOT ZERO (review finding, 2026-08-06). An earlier revision projected a +// web reading into a sample receipt carrying frequency_hz: 0 and noise_floor_milli: 0 because the +// web observation supplied neither — and a zero noise floor is not an absence, it is a claim of a +// perfect signal, which is the strongest possible statement about a quantity nobody measured. Each +// measured axis therefore carries its own observed-or-not coproduct, and a qualification that needs +// a dimension nobody observed REFUSES on that dimension rather than reading the fabricated zero as +// a pass. type FrequencyObservation = FrequencyObserved { hz: Nat } @@ -241,7 +275,13 @@ fn qualify_tap(r: PhysicalImpulseRequirement, s: SampleReceipt) -> TapQualificat } } -data capability_binding_join_note: String = "A binding is the join of a host, a requirement, and a provider — and the provider is a symbolic reference to ITS own authority, never an arm of an enumeration here. The W3C Device Orientation and Motion contract, the W3C Accelerometer extension's concrete interfaces, the Windows sensor namespace, Apple Core Motion, Android SensorManager, the Linux IIO subsystem and the synthetic source each belong in their own module, and combining the standards with the operating-system frameworks in a single motion-support file would fuse independently governed upstreams into one authority (DESIGN §3)." +// A binding is the join of a host, a requirement, and a provider — and the provider is a symbolic +// reference to ITS own authority, never an arm of an enumeration here. The W3C Device Orientation +// and Motion contract, the W3C Accelerometer extension's concrete interfaces, the Windows sensor +// namespace, Apple Core Motion, Android SensorManager, the Linux IIO subsystem and the synthetic +// source each belong in their own module, and combining the standards with the operating-system +// frameworks in a single motion-support file would fuse independently governed upstreams into one +// authority (DESIGN §3). data requirement_identity_and_snapshot_note: String = "ONE REQUIREMENT PER OBSERVATION, OWNED ONCE (review finding, 2026-08-07). The previous shape had the binding carry a requirement IDENTITY as a DeclarationRef while the stream independently carried a requirement VALUE — two writable authorities for one fact, and the verdict read the second while the row reported the first. The witness demonstrated the hazard without meaning to: it bound every observation to the provisional candidate and passed the signed twin through the stream, so a row said it was measured against unsigned thresholds while its supported verdict came from signed ones. On an exact host that disagreement reaches the durable-claim gate. @@ -310,7 +350,12 @@ type CapabilityBindingObservation { observation_window_ms: Milliseconds } -data typed_cause_note: String = "Causes that participate in a decision are typed rather than prose (review finding, 2026-08-06). A free String cause cannot be matched on, counted, or compared across rows, so a support matrix built from prose causes can only be read by a human — which is the same class as an inert lens one layer out. The degradation's alternative is likewise a reference to the declared binding that provides it, so 'there is an alternative' is a resolvable fact rather than a sentence asserting one." +// Causes that participate in a decision are typed rather than prose (review finding, 2026-08-06). A +// free String cause cannot be matched on, counted, or compared across rows, so a support matrix +// built from prose causes can only be read by a human — which is the same class as an inert lens +// one layer out. The degradation's alternative is likewise a reference to the declared binding that +// provides it, so 'there is an alternative' is a resolvable fact rather than a sentence asserting +// one. type CapabilityObstacle = ObstacleInterfaceUnavailable @@ -434,7 +479,11 @@ fn verdict_rows(rows: List) -> List Bool { capability_verdict_key(v: r.verdict) == "supported" && host_identity_is_exact(h: r.binding.host) @@ -457,7 +506,10 @@ fn synthetic_tap_strength_key(t: SyntheticTapStrength) -> String { } } -data synthetic_tap_strength_note: String = "Three strengths rather than a slider, because the point of the control is to demonstrate the response AND its bound: a light tap that barely moves the plate and sits below the discriminating floor, a firm one that clears it, and a full knock that reaches the clamp. A continuous input lets a viewer wander the range without ever seeing the two ends that carry the argument." +// Three strengths rather than a slider, because the point of the control is to demonstrate the +// response AND its bound: a light tap that barely moves the plate and sits below the discriminating +// floor, a firm one that clears it, and a full knock that reaches the clamp. A continuous input +// lets a viewer wander the range without ever seeing the two ends that carry the argument. fn synthetic_tap_magnitude_milli(t: SyntheticTapStrength) -> Nat { match t { @@ -476,7 +528,9 @@ fn synthetic_tap_impulse(t: SyntheticTapStrength, at_ms: Milliseconds) -> Physic } } -data synthetic_is_never_a_hardware_claim_note: String = "The predicate a receipt uses to label an impulse honestly, derived from which producer made it rather than from a flag whoever renders it could set. A hardware reading routed through a synthetic label, or the reverse, is not expressible." +// The predicate a receipt uses to label an impulse honestly, derived from which producer made it +// rather than from a flag whoever renders it could set. A hardware reading routed through a +// synthetic label, or the reverse, is not expressible. fn impulse_is_synthetic(i: PhysicalImpulse) -> Bool { i.source_provider.module_path == synthetic_tap_provider.module_path && @@ -487,7 +541,12 @@ fn synthetic_tap_clears_requirement(t: SyntheticTapStrength, r: PhysicalImpulseR synthetic_tap_magnitude_milli(t: t) >= r.minimum_discriminating_magnitude_milli_m_per_s2 } -data coverage_frontier_note: String = "COVERAGE OBLIGATIONS, NOT SUPPORT CLAIMS. Every platform this product could eventually reach is a target with a typed disposition and, where deferred, an explicit trigger. There is no WindowsSupported here, no MacUnsupported, and no placeholder observation standing in for a run that never happened: a target nobody has probed is ProbeRequired or Deferred, both of which say plainly that nothing is known. The page renders this so the gap between what is implemented and what is merely intended is visible rather than inferred." +// COVERAGE OBLIGATIONS, NOT SUPPORT CLAIMS. Every platform this product could eventually reach is a +// target with a typed disposition and, where deferred, an explicit trigger. There is no +// WindowsSupported here, no MacUnsupported, and no placeholder observation standing in for a run +// that never happened: a target nobody has probed is ProbeRequired or Deferred, both of which say +// plainly that nothing is known. The page renders this so the gap between what is implemented and +// what is merely intended is visible rather than inferred. data coverage_is_two_axes_note: String = "IMPLEMENTED AND UNPROBED IS A REAL STATE, AND ONE COPRODUCT COULD NOT SAY IT (review finding, 2026-08-07). Whether a binding EXISTS and whether anyone has RUN it are independent facts that coexist — which is precisely the state of both web probes in this change — and a single mutually exclusive enumeration forced a row to pick one and drop the other. Worse, its implemented arm carried a field named receipt whose value was a producer FUNCTION reference: not an executed receipt, not evidence of anything having run, but the strongest-sounding word available for a declaration that proves only that someone wrote code. @@ -542,9 +601,20 @@ fn probe_state_is_a_support_claim(s: BindingProbeState) -> Bool { } } -data coverage_is_never_support_note: String = "Total and constantly false, and that IS the claim: no coverage state is a support statement, not even Implemented — implemented means the binding exists and can be probed, while support is a verdict derived from an executed observation on an exact host. If a fifth state is ever added that DOES assert support, this match forces the author to write the arm and a reviewer sees a true in a function whose name says there should not be one." +// Total and constantly false, and that IS the claim: no coverage state is a support statement, not +// even Implemented — implemented means the binding exists and can be probed, while support is a +// verdict derived from an executed observation on an exact host. If a fifth state is ever added +// that DOES assert support, this match forces the author to write the arm and a reviewer sees a +// true in a function whose name says there should not be one. -data coverage_provider_note: String = "A DEFERRED TARGET CANNOT CITE A MODULE THAT DOES NOT EXIST, AND POINTING AT ONE ANYWAY WOULD BE A FABRICATED SYMBOL (DESIGN §3 cite-the-symbol). There is no Core Motion module, no Windows sensor module and no Linux IIO module in this tree, because nothing has consumed them yet — so a coverage row for those targets names the upstream it INTENDS to cite as prose, and says plainly that it is not yet modelled. When the module lands, the row flips to the modelled arm and the reference becomes resolvable, which makes the migration decidable by symbol resolution rather than by someone remembering. The prose arm is therefore the honest shape for exactly as long as the absence is real, and no longer." +// A DEFERRED TARGET CANNOT CITE A MODULE THAT DOES NOT EXIST, AND POINTING AT ONE ANYWAY WOULD BE A +// FABRICATED SYMBOL (DESIGN §3 cite-the-symbol). There is no Core Motion module, no Windows sensor +// module and no Linux IIO module in this tree, because nothing has consumed them yet — so a +// coverage row for those targets names the upstream it INTENDS to cite as prose, and says plainly +// that it is not yet modelled. When the module lands, the row flips to the modelled arm and the +// reference becomes resolvable, which makes the migration decidable by symbol resolution rather +// than by someone remembering. The prose arm is therefore the honest shape for exactly as long as +// the absence is real, and no longer. type CoverageProvider = ProviderModelled { declaration: DeclarationRef } @@ -564,7 +634,11 @@ type BindingCoverageTarget { probe: BindingProbeState } -data coverage_targets_note: String = "Seeded with the targets that have a real near-term reason and nothing else. Three are implemented in this lane; one is probed immediately after it; four are deferred behind a named trigger that is a CONSUMER appearing, not a date. No row asserts support, no row carries a placeholder observation, and there is deliberately no row for a platform nobody has a reason to reach — an empty space is more honest than a Deferred row invented to look thorough." +// Seeded with the targets that have a real near-term reason and nothing else. Three are implemented +// in this lane; one is probed immediately after it; four are deferred behind a named trigger that +// is a CONSUMER appearing, not a date. No row asserts support, no row carries a placeholder +// observation, and there is deliberately no row for a platform nobody has a reason to reach — an +// empty space is more honest than a Deferred row invented to look thorough. data coverage_targets: List = [ BindingCoverageTarget { diff --git a/dag/gunbc/capability_origin.dag b/dag/gunbc/capability_origin.dag index 63a4144f2ce..16358480c6a 100644 --- a/dag/gunbc/capability_origin.dag +++ b/dag/gunbc/capability_origin.dag @@ -17,7 +17,20 @@ import v2.std.algebra { HeadAbsent, HeadFound, filter, fold_list, list_append, l // // So this module carries the ORIGIN relation, and it is a second test beside the leaf test rather // than a replacement for it. -data capability_origin_note: String = "ORIGIN RELATION for hand-authored Rust growth, the second test beside gunbc.seed_growth_admission's leaf test. THE LEAF TEST asks whether Rust owns a semantic decision; THE ORIGIN TEST asks where the semantics came from, and a retro-described implementation passes the leaf test perfectly. THE FIELD THIS REPLACES: CapabilityAlreadyModeled was payload-free, so it could be satisfied by inspecting the final candidate -- which cannot distinguish a model that existed before its realization from a model added beside the realization that inspired it, and that distinction is the whole question. THERE IS DELIBERATELY NO ARM MEANING 'the Rust behaves this way, therefore this is the intended model': that state is unrepresentable here rather than checked (DESIGN section 5, construction over validation). REJECTED AS EVIDENCE, each because it is satisfiable by a retro-described implementation: that the .dag was committed first (sufficient but neither necessary nor conclusive, and erased by squash merge); that the model carries refusal arms the realization does not implement (establishes an implementation gap, not model independence); final-tree agreement; the full candidate compiling; and a comment in the Rust naming the model." +// ORIGIN RELATION for hand-authored Rust growth, the second test beside +// gunbc.seed_growth_admission's leaf test. THE LEAF TEST asks whether Rust owns a semantic +// decision; THE ORIGIN TEST asks where the semantics came from, and a retro-described +// implementation passes the leaf test perfectly. THE FIELD THIS REPLACES: CapabilityAlreadyModeled +// was payload-free, so it could be satisfied by inspecting the final candidate -- which cannot +// distinguish a model that existed before its realization from a model added beside the realization +// that inspired it, and that distinction is the whole question. THERE IS DELIBERATELY NO ARM +// MEANING 'the Rust behaves this way, therefore this is the intended model': that state is +// unrepresentable here rather than checked (DESIGN section 5, construction over validation). +// REJECTED AS EVIDENCE, each because it is satisfiable by a retro-described implementation: that +// the .dag was committed first (sufficient but neither necessary nor conclusive, and erased by +// squash merge); that the model carries refusal arms the realization does not implement +// (establishes an implementation gap, not model independence); final-tree agreement; the full +// candidate compiling; and a comment in the Rust naming the model. // THE GROUND IS WHAT MAKES A MODEL INTRODUCED IN THIS CHANGE INDEPENDENT OF THE RUST BESIDE IT. // @@ -243,7 +256,32 @@ fn seed_bridge_well_formed(bridge: AuthorizedTemporarySeedBridge) -> Bool { && bridge.current_boundary != "" } -data capability_origin_rung_note: String = "RUNG, STATED AT THE GRAIN THAT IS HONEST RATHER THAN THE ONE THAT READS BEST. THE VOCABULARY IS STRUCTURAL: no arm of CapabilityGround or ModeledCapabilityOrigin means 'the Rust behaves this way, therefore this is the intended model', so that claim is unwritable here rather than refused by a check; origin_test_verdict has no path from ProbeNotExecuted to an admitting verdict, conformance_verdict has none from ConformanceProbeNotExecuted, and an added item whose bridge is absent refuses rather than falling through -- so an unperformed test cannot render as a passed one anywhere in this carrier. THREE TESTS, NOT ONE, AND THEY ESTABLISH DIFFERENT THINGS: the LEAF test (gunbc.seed_growth_admission) proves Rust owns no semantic decision; the ORIGIN test proves the semantics predate the Rust; the CONFORMANCE test proves the model CONSTRAINS the Rust rather than merely agreeing with it. A retro-described implementation passes the first two and fails the third, because its fixtures were derived from the behavior they check -- which is why FixtureProvenance refuses GeneratedByRealization even when the mutation went red. THE TESTS THEMSELVES ARE NOT EXECUTED BY ANYTHING IN THIS REPOSITORY YET, and calling this carrier a wall would be exactly the inflation DESIGN section 4b(1) names as worse than sitting low. ModelEpochProbeResult and ConformanceProbeResult are the shapes an executing host must supply; no host supplies them, so every real call today lands on the not-executed arms and REFUSES, which is the correct fail-closed reading and not a passing one. NEXT-RUNG TRIGGER, NAMING THE CAPABILITY RATHER THAN AN ARTIFACT THAT WOULD CONTRIBUTE TO ONE: an executable route that (1) materializes the E1 subject from a candidate against its ACTUAL proposed base -- base plus the .dag delta with every added and modified hand-Rust item removed -- (2) resolves and compiles the cited capability authority inside it and runs that authority's semantic controls, and (3) mutates the Rust realization alone and observes the predicted conformance evidence go red while E1 stays green, returning all of it from real builds rather than from an author's assertion. A route that constructs the subject without building it does not retire this row; neither does one that builds a single entry when the authority's controls span several; and neither does one that derives its base from whatever ref the caller happened to have." +// RUNG, STATED AT THE GRAIN THAT IS HONEST RATHER THAN THE ONE THAT READS BEST. THE VOCABULARY IS +// STRUCTURAL: no arm of CapabilityGround or ModeledCapabilityOrigin means 'the Rust behaves this +// way, therefore this is the intended model', so that claim is unwritable here rather than refused +// by a check; origin_test_verdict has no path from ProbeNotExecuted to an admitting verdict, +// conformance_verdict has none from ConformanceProbeNotExecuted, and an added item whose bridge is +// absent refuses rather than falling through -- so an unperformed test cannot render as a passed +// one anywhere in this carrier. THREE TESTS, NOT ONE, AND THEY ESTABLISH DIFFERENT THINGS: the LEAF +// test (gunbc.seed_growth_admission) proves Rust owns no semantic decision; the ORIGIN test proves +// the semantics predate the Rust; the CONFORMANCE test proves the model CONSTRAINS the Rust rather +// than merely agreeing with it. A retro-described implementation passes the first two and fails the +// third, because its fixtures were derived from the behavior they check -- which is why +// FixtureProvenance refuses GeneratedByRealization even when the mutation went red. THE TESTS +// THEMSELVES ARE NOT EXECUTED BY ANYTHING IN THIS REPOSITORY YET, and calling this carrier a wall +// would be exactly the inflation DESIGN section 4b(1) names as worse than sitting low. +// ModelEpochProbeResult and ConformanceProbeResult are the shapes an executing host must supply; no +// host supplies them, so every real call today lands on the not-executed arms and REFUSES, which is +// the correct fail-closed reading and not a passing one. NEXT-RUNG TRIGGER, NAMING THE CAPABILITY +// RATHER THAN AN ARTIFACT THAT WOULD CONTRIBUTE TO ONE: an executable route that (1) materializes +// the E1 subject from a candidate against its ACTUAL proposed base -- base plus the .dag delta with +// every added and modified hand-Rust item removed -- (2) resolves and compiles the cited capability +// authority inside it and runs that authority's semantic controls, and (3) mutates the Rust +// realization alone and observes the predicted conformance evidence go red while E1 stays green, +// returning all of it from real builds rather than from an author's assertion. A route that +// constructs the subject without building it does not retire this row; neither does one that builds +// a single entry when the authority's controls span several; and neither does one that derives its +// base from whatever ref the caller happened to have. // THE PROBE IS LOOKED UP, NEVER SUPPLIED AS A VERDICT, AND THE DIFFERENCE IS THE WHOLE WALL. // @@ -348,7 +386,6 @@ fn origin_refusals(bridges: List, evidence: Admis ) } - // THE THIRD TEST: DOES THE MODEL CONSTRAIN THE RUST, OR MERELY AGREE WITH IT? // // The leaf test proves Rust owns no decision. The origin test proves the semantics predate the diff --git a/dag/gunbc/change_realization.dag b/dag/gunbc/change_realization.dag index 0590a5ff240..020f2f4cfd7 100644 --- a/dag/gunbc/change_realization.dag +++ b/dag/gunbc/change_realization.dag @@ -2,16 +2,33 @@ module gunbc.change_realization import std.types { String, List, Bool, Int, NonEmptyStr } -data change_realization_authority_note: String = "THE classification a MemberChanged\{from,to} carries into apply: given that an occurrence survived and only its value moved, HOW is that move realized? This is the table that makes MemberChanged useful rather than merely more faithful — without it, every change is an undifferentiated overwrite and the only available realization is the most dangerous one. The four rows are not four conveniences: they are four DIFFERENT atomicity guarantees, and picking the wrong row is how a safe transformation acquires an unsafe realization." - -data scm_versus_host_conflation_note: String = "THE conflation this table exists to kill: a .dag module re-home is an SCM TREE TRANSFORMATION, not a host migration. Moving a declaration between modules changes no runtime address, unlinks no socket, and restarts no unit — it is atomic in the SCM and needs no intermediates at all. Treating it as a host migration is what made an ordinary source cleanup look like it required an operational migration plan. The converse error is the dangerous one: renaming a systemd unit or moving a FIFO IS a host replacement, its intermediate states are real and observable, and calling it a mere update is how a live endpoint gets stranded." +// THE classification a MemberChanged{from,to} carries into apply: given that an occurrence survived +// and only its value moved, HOW is that move realized? This is the table that makes MemberChanged +// useful rather than merely more faithful — without it, every change is an undifferentiated +// overwrite and the only available realization is the most dangerous one. The four rows are not +// four conveniences: they are four DIFFERENT atomicity guarantees, and picking the wrong row is how +// a safe transformation acquires an unsafe realization. + +// THE conflation this table exists to kill: a .dag module re-home is an SCM TREE TRANSFORMATION, +// not a host migration. Moving a declaration between modules changes no runtime address, unlinks no +// socket, and restarts no unit — it is atomic in the SCM and needs no intermediates at all. +// Treating it as a host migration is what made an ordinary source cleanup look like it required an +// operational migration plan. The converse error is the dangerous one: renaming a systemd unit or +// moving a FIFO IS a host replacement, its intermediate states are real and observable, and calling +// it a mere update is how a live endpoint gets stranded. type ChangeSubject = ScmRefSubject | SourceDeclarationSubject | HostResourceSubject -data address_continuity_note: String = "For a host resource the deciding fact is whether the RUNTIME ADDRESS survived the change — the unit name, socket path, FIFO, endpoint. Same address means the running thing can absorb a new value in place; a changed address means two addresses exist during the transition and the old one must be retired, which is a staged operation with observable intermediate states. Continuity is DERIVED by comparing the address projection of from and to (address_continuity), never asserted by a caller-supplied Bool: an address that is claimed preserved while actually moving is exactly the silent wrong answer DESIGN §5 forbids." +// For a host resource the deciding fact is whether the RUNTIME ADDRESS survived the change — the +// unit name, socket path, FIFO, endpoint. Same address means the running thing can absorb a new +// value in place; a changed address means two addresses exist during the transition and the old one +// must be retired, which is a staged operation with observable intermediate states. Continuity is +// DERIVED by comparing the address projection of from and to (address_continuity), never asserted +// by a caller-supplied Bool: an address that is claimed preserved while actually moving is exactly +// the silent wrong answer DESIGN §5 forbids. type AddressContinuity = AddressPreserved | AddressChanged @@ -19,7 +36,15 @@ fn address_continuity(from: A, to: A, address_eq: fn(A, A) -> Bool) -> Addres if address_eq(from, to) { AddressPreserved } else { AddressChanged } } -data staged_intermediate_srv4_specimen_note: String = "The LIVE specimen that makes declared intermediates a requirement rather than a nicety: srv4's build-cache server died with its cgroup, never unlinked its socket, and ran uncached for weeks because nobody modeled the intermediate state. gunbc.build_cache_instance already says IN PROSE that a managed unit must clear a stale endpoint on start — and a sentence in prose is the one place no machine can read it (DESIGN §4c: any machine-consumed fact belongs in a typed carrier). StagedIntermediates is that sentence given a carrier. Emptiness is unrepresentable by construction (head + tail, not a List that could be []), because a staged replacement whose intermediate list is empty is precisely the srv4 state: a transition that believed it had nothing to retire." +// The LIVE specimen that makes declared intermediates a requirement rather than a nicety: srv4's +// build-cache server died with its cgroup, never unlinked its socket, and ran uncached for weeks +// because nobody modeled the intermediate state. gunbc.build_cache_instance already says IN PROSE +// that a managed unit must clear a stale endpoint on start — and a sentence in prose is the one +// place no machine can read it (DESIGN §4c: any machine-consumed fact belongs in a typed carrier). +// StagedIntermediates is that sentence given a carrier. Emptiness is unrepresentable by +// construction (head + tail, not a List that could be []), because a staged replacement whose +// intermediate list is empty is precisely the srv4 state: a transition that believed it had nothing +// to retire. type StagedIntermediate = RetirePriorEndpoint { endpoint: NonEmptyStr } @@ -46,7 +71,13 @@ type ChangeRealization | InPlaceUpdate | StagedReplacement { intermediates: StagedIntermediates } -data change_classification_refusal_note: String = "A staged replacement whose intermediates were never declared REFUSES; it does not fall back to an in-place update and it does not proceed with an empty plan. Both fallbacks are the absorbing fallback DESIGN §5 forbids, and the second is the srv4 outcome exactly. The refusal is typed and LOCATED — it carries subject_label, the identity of the resource whose intermediates are missing, so a refusal points at a thing rather than merely reporting that something somewhere is undeclared so the deficit is countable and ranks for fixing, rather than having its frequency zeroed by a silent widen." +// A staged replacement whose intermediates were never declared REFUSES; it does not fall back to an +// in-place update and it does not proceed with an empty plan. Both fallbacks are the absorbing +// fallback DESIGN §5 forbids, and the second is the srv4 outcome exactly. The refusal is typed and +// LOCATED — it carries subject_label, the identity of the resource whose intermediates are missing, +// so a refusal points at a thing rather than merely reporting that something somewhere is +// undeclared so the deficit is countable and ranks for fixing, rather than having its frequency +// zeroed by a silent widen. type ChangeRefusalCause = StagedReplacementIntermediatesUndeclared { subject_label: NonEmptyStr } @@ -55,6 +86,14 @@ type ChangeRealizationClassification = ChangeRealized { realization: ChangeRealization } | ChangeRefused { cause: ChangeRefusalCause } +// An SCM ref update is a COMPARE-AND-SWAP, not a write: git update-ref names the +// expected prior value, so a concurrent update loses rather than silently overwriting. That +// expected-prior is the from side of MemberChanged — which is another reason the pre-MemberChanged +// collapse was lossy in a load-bearing way, not merely untidy: with only the to side in hand, the +// sole available realization is the unconditional write, and the atomicity the underlying +// capability already offers cannot be reached. AtomicCompareAndSwap is therefore a realization the +// from side UNLOCKS, not merely one it documents. + fn classify_change( subject: ChangeSubject, subject_label: NonEmptyStr, @@ -77,5 +116,3 @@ fn classify_change( } } } - -data scm_ref_compare_and_swap_note: String = "An SCM ref update is a COMPARE-AND-SWAP, not a write: git update-ref names the expected prior value, so a concurrent update loses rather than silently overwriting. That expected-prior is the from side of MemberChanged — which is another reason the pre-MemberChanged collapse was lossy in a load-bearing way, not merely untidy: with only the to side in hand, the sole available realization is the unconditional write, and the atomicity the underlying capability already offers cannot be reached. AtomicCompareAndSwap is therefore a realization the from side UNLOCKS, not merely one it documents." diff --git a/dag/gunbc/ci/ci_budget_tree.dag b/dag/gunbc/ci/ci_budget_tree.dag index 93d37fe4ef2..5737c9746ce 100644 --- a/dag/gunbc/ci/ci_budget_tree.dag +++ b/dag/gunbc/ci/ci_budget_tree.dag @@ -22,7 +22,18 @@ fn host_overhead_bytes(offer: ComputeOffer) -> ByteSize { } } -data host_ram_ceiling_direction_note: String = "THIS READS OS-USABLE RAM PER HOST, NOT NOMINAL DIMM CAPACITY, and the change matters because this module and gunbc.ci_runner_placement were answering the same question with different ceilings. compute_host_ram_bytes_total sums the populated modules; the OS sees less after firmware and kernel reservations. While placement checked usable and these budget trees checked nominal, both could report conservation green while disagreeing about how much memory exists — and the sibling tests here call themselves live, so agreement was never being tested.\n\nWHY ZERO IS THE FAIL-CLOSED ARM HERE, which is the opposite of the direction it means one function above. host_overhead_bytes maps an unmeasured overhead to zero and that IS a fail-open — an unknown CHARGE read as no charge lets a host look emptier than it is. This value is a CEILING, so an unknown ceiling read as zero admits nothing: every pool overcommits and conservation refuses. Same literal, opposite polarity, because one is a debit and the other is the account." +// THIS READS OS-USABLE RAM PER HOST, NOT NOMINAL DIMM CAPACITY, and the change matters because this +// module and gunbc.ci_runner_placement were answering the same question with different ceilings. +// compute_host_ram_bytes_total sums the populated modules; the OS sees less after firmware and +// kernel reservations. While placement checked usable and these budget trees checked nominal, both +// could report conservation green while disagreeing about how much memory exists — and the sibling +// tests here call themselves live, so agreement was never being tested. +// +// WHY ZERO IS THE FAIL-CLOSED ARM HERE, which is the opposite of the direction it means one +// function above. host_overhead_bytes maps an unmeasured overhead to zero and that IS a fail-open — +// an unknown CHARGE read as no charge lets a host look emptier than it is. This value is a CEILING, +// so an unknown ceiling read as zero admits nothing: every pool overcommits and conservation +// refuses. Same literal, opposite polarity, because one is a debit and the other is the account. fn host_ram_of(offer: ComputeOffer) -> ByteSize { host_usable_ram_ceiling_bytes(host: offer.supply.physical.identity) @@ -131,7 +142,6 @@ fn srv1_ci_run_container_hard_cap() -> ByteSize { gunbc_ci_runner_cgroup_memory_cap } - fn witness_floor_budget_equals_selected_target_budget() -> Bool { let budget = byte_size_count(b: srv1_floor_memory_budget()) budget == byte_size_count(b: ci_runner_target_ram_speed_budget(target: selected_ci_runner_target())) diff --git a/dag/gunbc/ci/ci_build_job_v1_compiler_unit_receipt.dag b/dag/gunbc/ci/ci_build_job_v1_compiler_unit_receipt.dag index 5dfe2b84690..bb881a5cf48 100644 --- a/dag/gunbc/ci/ci_build_job_v1_compiler_unit_receipt.dag +++ b/dag/gunbc/ci/ci_build_job_v1_compiler_unit_receipt.dag @@ -2,7 +2,13 @@ module gunbc.ci_build_job_v1_compiler_unit_receipt import std.types { String } -data ci_build_job_v1_compiler_unit_receipt_note: String = "CI-performance authority for the A2 build-job lane (operator ruling smart-badger-549, 2026-08-06). Corrects rung inflation from #7868: ordering landed but the one-compiler-unit claim was not fleet-delivered (run 31062435170 showed a second Compiling v1-compiler on the gate step). Status lines below are the honest program reading; execution evidence for the fix lives in PR #7910 and its fleet run. Wall-clock benefit is NOT claimed until a controlled measurement lands; unit-reuse evidence is a cargo fingerprint fact (second invocation omits Compiling for a fresh unit)." +// CI-performance authority for the A2 build-job lane (operator ruling smart-badger-549, +// 2026-08-06). Corrects rung inflation from #7868: ordering landed but the one-compiler-unit claim +// was not fleet-delivered (run 31062435170 showed a second Compiling v1-compiler on the gate step). +// Status lines below are the honest program reading; execution evidence for the fix lives in PR +// #7910 and its fleet run. Wall-clock benefit is NOT claimed until a controlled measurement lands; +// unit-reuse evidence is a cargo fingerprint fact (second invocation omits Compiling for a fresh +// unit). data one_profile_ordering_status: String = "landed" data one_compiler_unit_status: String = "delivered" @@ -12,9 +18,30 @@ data pr7868_landed_pull_request: String = "7868" data pr7868_fleet_counterexample_run: String = "31062435170" data pr7910_fix_pull_request: String = "7910" -data machine_unit_identity_note: String = "Machine-level acceptance (local controlled repro, RUSTC_WRAPPER unset, CARGO_INCREMENTAL=0, CI-shaped release bin set + cargo test -p v1-compiler-tests --release --no-run). The shared release lib unit identity across build and gate is ONE artifact: cargo fingerprint dir v1-compiler-591811ee6f9846da / lib-v1_compiler.json with profile=2040997289075261528, config=2069994364910194474, compile_kind=0, features=[default, interp_test_witness, test_hooks, text_lookup_work_counter]. BEFORE fix (broken package-relative .git rerun-if-changed restored): after build the lib unit is 591811ee6f9846da; the second cargo invocation logs fingerprint dirty for v1-compiler lib with StaleDepFingerprint\{build_script_build\} and StaleItem(MissingFile\{src/v1/stage0/.git/HEAD\}) — rustc re-executes on the SAME unit identity. AFTER fix (#7910): after build lib unit 591811ee6f9846da; second invocation produces no dirty line for v1-compiler lib and reuses target/release/deps/libv1_compiler-591811ee6f9846da.rlib (gate Finished in 23s warm). Log line count is weaker evidence; the fingerprint identity + dirty-or-fresh disposition is the acceptance bar." +// Machine-level acceptance (local controlled repro, RUSTC_WRAPPER unset, CARGO_INCREMENTAL=0, +// CI-shaped release bin set + cargo test -p v1-compiler-tests --release --no-run). The shared +// release lib unit identity across build and gate is ONE artifact: cargo fingerprint dir +// v1-compiler-591811ee6f9846da / lib-v1_compiler.json with profile=2040997289075261528, +// config=2069994364910194474, compile_kind=0, features=[default, interp_test_witness, test_hooks, +// text_lookup_work_counter]. BEFORE fix (broken package-relative .git rerun-if-changed restored): +// after build the lib unit is 591811ee6f9846da; the second cargo invocation logs fingerprint dirty +// for v1-compiler lib with StaleDepFingerprint{build_script_build} and +// StaleItem(MissingFile{src/v1/stage0/.git/HEAD}) — rustc re-executes on the SAME unit identity. +// AFTER fix (#7910): after build lib unit 591811ee6f9846da; second invocation produces no dirty +// line for v1-compiler lib and reuses target/release/deps/libv1_compiler-591811ee6f9846da.rlib +// (gate Finished in 23s warm). Log line count is weaker evidence; the fingerprint identity + +// dirty-or-fresh disposition is the acceptance bar. data fleet_build_job_log_evidence_run: String = "31077456733" data fleet_build_job_log_evidence_job: String = "92538509081" +// Fleet build job run 31077456733 job 92538509081 (srv1-04, head 5a0414cf32). TWO cargo invocations +// in one job: (1) release build prints Compiling v1-compiler then Finished release in 21.13s; (2) +// compile-contract gate prints Compiling v1-compiler-tests then Finished release in 17.61s with NO +// Compiling v1-compiler line — the dependent invocation reused the first invocation's v1-compiler +// unit (cargo omits Compiling for a fresh unit). Quoted: 2026-08-06T06:36:48.5552454Z Compiling +// v1-compiler v0.1.0 (.../src/v1/stage0); 2026-08-06T06:37:08.6981353Z Finished release in 21.13s; +// 2026-08-06T06:37:08.8943777Z Compiling v1-compiler-tests v0.1.0 (.../src/v1/tests); +// 2026-08-06T06:37:26.4211587Z Finished release in 17.61s. Counterexample run 31062435170 printed +// Compiling v1-compiler on the gate step. Wall-clock benefit not claimed from this receipt. + data fleet_build_job_log_evidence_status: String = "delivered" -data fleet_build_job_log_evidence_note: String = "Fleet build job run 31077456733 job 92538509081 (srv1-04, head 5a0414cf32). TWO cargo invocations in one job: (1) release build prints Compiling v1-compiler then Finished release in 21.13s; (2) compile-contract gate prints Compiling v1-compiler-tests then Finished release in 17.61s with NO Compiling v1-compiler line — the dependent invocation reused the first invocation's v1-compiler unit (cargo omits Compiling for a fresh unit). Quoted: 2026-08-06T06:36:48.5552454Z Compiling v1-compiler v0.1.0 (.../src/v1/stage0); 2026-08-06T06:37:08.6981353Z Finished release in 21.13s; 2026-08-06T06:37:08.8943777Z Compiling v1-compiler-tests v0.1.0 (.../src/v1/tests); 2026-08-06T06:37:26.4211587Z Finished release in 17.61s. Counterexample run 31062435170 printed Compiling v1-compiler on the gate step. Wall-clock benefit not claimed from this receipt." diff --git a/dag/gunbc/ci/ci_deploy_access.dag b/dag/gunbc/ci/ci_deploy_access.dag index cdbaa0a8b2f..e8464c1f067 100644 --- a/dag/gunbc/ci/ci_deploy_access.dag +++ b/dag/gunbc/ci/ci_deploy_access.dag @@ -79,7 +79,17 @@ type DeployAuthorizationQuery { data deploy_access_frame: Frame = Frame { name: "deploy", kind: SharedStateFrame } -data deploy_access_authorization_query_note: String = "Wave C (shell-intent-emit-realization-design.md): deploy-preflight is an AUTHORIZATION QUERY — the deploy frame requires the current principal to hold Execute grants over privileged command paths and to match the expected job principal (DeployRunnerPrincipal.job_user, never unit_user). Grants project from GroundedPosixPrincipal.sudo_grants via posix_sudo_grant_to_effect_grant (never a parallel grant vocabulary). Observation and typed-effect execution live in gunbc.ci_deploy_access_observe / gunbc.host_effect_realize at the runtime-present realization edge: present binaries use sudo.NopasswdExecuteProbe.Check; package-managed binaries that may not exist yet use sudo.NopasswdGrantList.Read plus an exact NOPASSWD-row check for the roster path. A successful list query alone is not authorization evidence. The duplicate in-script gunbc.ci_deploy_access_emit path is deleted; gunbc.ci_deploy_sudoers remains the roster-derived sudoers materialization." +// Wave C (shell-intent-emit-realization-design.md): deploy-preflight is an AUTHORIZATION QUERY — +// the deploy frame requires the current principal to hold Execute grants over privileged command +// paths and to match the expected job principal (DeployRunnerPrincipal.job_user, never unit_user). +// Grants project from GroundedPosixPrincipal.sudo_grants via posix_sudo_grant_to_effect_grant +// (never a parallel grant vocabulary). Observation and typed-effect execution live in +// gunbc.ci_deploy_access_observe / gunbc.host_effect_realize at the runtime-present realization +// edge: present binaries use sudo.NopasswdExecuteProbe.Check; package-managed binaries that may not +// exist yet use sudo.NopasswdGrantList.Read plus an exact NOPASSWD-row check for the roster path. A +// successful list query alone is not authorization evidence. The duplicate in-script +// gunbc.ci_deploy_access_emit path is deleted; gunbc.ci_deploy_sudoers remains the roster-derived +// sudoers materialization. data deploy_access_refusal_prefix: String = "deploy_access: typed refusal before mutation" diff --git a/dag/gunbc/ci/ci_deploy_sudoers.dag b/dag/gunbc/ci/ci_deploy_sudoers.dag index 5b570749372..a782a0f12e8 100644 --- a/dag/gunbc/ci/ci_deploy_sudoers.dag +++ b/dag/gunbc/ci/ci_deploy_sudoers.dag @@ -18,7 +18,11 @@ import gunbc.fleet_posix_accounts { fleet_visudo_binary_path, } -data deploy_sudoers_note: String = "Wave C envelope: /etc/sudoers.d/gunbc-deploy is EMITTED from the roster grants (never hand-managed). visudo -c gates install. Generic movers (rsync/chown) are refused in the roster — #7011 realization routes tree sync through gunbc-tree-sync.service and ownership through install -o/-g. Sudo-grant lint allow-list is DERIVED from fleet_posix_ci_runner_sudo_grants() command_path rows — never a parallel path list." +// Wave C envelope: /etc/sudoers.d/gunbc-deploy is EMITTED from the roster grants (never +// hand-managed). visudo -c gates install. Generic movers (rsync/chown) are refused in the roster — +// #7011 realization routes tree sync through gunbc-tree-sync.service and ownership through install +// -o/-g. Sudo-grant lint allow-list is DERIVED from fleet_posix_ci_runner_sudo_grants() +// command_path rows — never a parallel path list. data deploy_sudoers_shell_emit_dissolution_trigger: DissolutionCondition = unbound_dissolution(description: "🟡 dissolve-on: gunbc.ci_deploy_sudoers — concat-built bash for sudoers dropin install and visudo gates; DISSOLVES WHEN live_deploy_emit_shell_dissolution_trigger retires (bash-emit #5828 / shell-intent Phase 2 realizes deploy shell through orchestration emit without medium-as-string here-docs).") diff --git a/dag/gunbc/ci/ci_failure_class.dag b/dag/gunbc/ci/ci_failure_class.dag index 94fb580a1e7..779f4175169 100644 --- a/dag/gunbc/ci/ci_failure_class.dag +++ b/dag/gunbc/ci/ci_failure_class.dag @@ -101,7 +101,30 @@ fn reason_matches_infra_signature(reason: String, sig: InfraSignature) -> Bool { string_contains(s: reason, pattern: infra_signature_match_substring(sig: sig)) } -data ci_failure_class_seed_fork_note: String = "PURE BOOTSTRAP RECEIPT and a recorded SINGLE-AUTHORITY FORK (review 45261 asked for the receipt; verifying it surfaced the fork). THIS module is the authority: InfraSignature is a closed sum whose match text is DERIVED from cited upstream models — errno_strerror(EAGAIN), spawn_outcome_log_fragment(SpawnFailed), the sccache fragments, runner_lifecycle_log_fragment(ShutdownReceived) — each carrying an infra_signature_origin naming where the text comes from, and an infra_signature_site saying whether it is build-log or job-signal text. THE FORK: claim_executor.rs falsifier_failure_mode carries a hand-typed substring list covering the same concept, and THAT is the one that runs. classify_failure_reason below has no production consumer at all — only witness tests reach it — so the grounded authority is inert while the ungrounded copy is live, which is the coverage-by-illusion tier DESIGN section 6 names. WHAT THE SEED ADDS in the change carrying this note: an InfraFault sum recording a fault the walk OBSERVES structurally (a claim thread panic, from handle.join() returning Err) plus the field carrying it, replacing a format!-then-contains round-trip through the walk's own rendered line; that fault is the walk's own observation and is NOT a signature this module models, so it is not a second fork of this roster. WHAT IT DOES NOT ADD: no new substring authority — an earlier revision deleted the failed-to-spawn arm from the seed list on surface-local evidence and was retracted, because editing one side of a fork widens divergence instead of closing it. DEFERRED TO: the v1 exit finish line ZeroHandMaintainedRust, roadmap node v1-zero-hand-maintained-rust (gunbc.v1_deletion_plan v1_exit_finish_lines). CONCRETE DISSOLUTION, and it is the fork closure rather than a generic seed deletion: falsifier_failure_mode consumes this module — resolving it and calling classify_failure_reason the way claim_executor already resolves gunbc.floor_component_receipt — at which point the hand-typed list is deleted, classify_failure_reason gains its first production consumer, and the site axis (build-log versus job-signal text) becomes decidable at the call site instead of being flattened away." +// PURE BOOTSTRAP RECEIPT and a recorded SINGLE-AUTHORITY FORK (review 45261 asked for the receipt; +// verifying it surfaced the fork). THIS module is the authority: InfraSignature is a closed sum +// whose match text is DERIVED from cited upstream models — errno_strerror(EAGAIN), +// spawn_outcome_log_fragment(SpawnFailed), the sccache fragments, +// runner_lifecycle_log_fragment(ShutdownReceived) — each carrying an infra_signature_origin naming +// where the text comes from, and an infra_signature_site saying whether it is build-log or +// job-signal text. THE FORK: claim_executor.rs falsifier_failure_mode carries a hand-typed +// substring list covering the same concept, and THAT is the one that runs. classify_failure_reason +// below has no production consumer at all — only witness tests reach it — so the grounded authority +// is inert while the ungrounded copy is live, which is the coverage-by-illusion tier DESIGN section +// 6 names. WHAT THE SEED ADDS in the change carrying this note: an InfraFault sum recording a fault +// the walk OBSERVES structurally (a claim thread panic, from handle.join() returning Err) plus the +// field carrying it, replacing a format!-then-contains round-trip through the walk's own rendered +// line; that fault is the walk's own observation and is NOT a signature this module models, so it +// is not a second fork of this roster. WHAT IT DOES NOT ADD: no new substring authority — an +// earlier revision deleted the failed-to-spawn arm from the seed list on surface-local evidence and +// was retracted, because editing one side of a fork widens divergence instead of closing it. +// DEFERRED TO: the v1 exit finish line ZeroHandMaintainedRust, roadmap node +// v1-zero-hand-maintained-rust (gunbc.v1_deletion_plan v1_exit_finish_lines). CONCRETE DISSOLUTION, +// and it is the fork closure rather than a generic seed deletion: falsifier_failure_mode consumes +// this module — resolving it and calling classify_failure_reason the way claim_executor already +// resolves gunbc.floor_component_receipt — at which point the hand-typed list is deleted, +// classify_failure_reason gains its first production consumer, and the site axis (build-log versus +// job-signal text) becomes decidable at the call site instead of being flattened away. fn classify_failure_reason(reason: String) -> FloorFailureClass { fold(verdict_infra_signatures, init: Structural { reason: reason }, f: fn(acc, sig) { @@ -152,7 +175,6 @@ type MergeGatingStatus data merge_freshness_gating_status: MergeGatingStatus = GatingComputedDeferred - data merge_admission_enforcement_gating_scaffold: Disposition = Scaffold { dissolves_to: SingleAuthority, bind: DeclarationRef { diff --git a/dag/gunbc/ci/ci_floor_measurement.dag b/dag/gunbc/ci/ci_floor_measurement.dag index f24bb4cc13f..d7750159155 100644 --- a/dag/gunbc/ci/ci_floor_measurement.dag +++ b/dag/gunbc/ci/ci_floor_measurement.dag @@ -17,8 +17,6 @@ data gunbc_ci_width_constants_retired_note: String = "WITHIN-run width constants data gunbc_ci_runner_cgroup_memory_cap: ByteSize = gunbc_runner_slot_desired().memory_max - - data gunbc_ci_runner_slot_memory_max_live: ByteSize = gunbc_runner_slot_desired().memory_max data gunbc_ci_runner_slot_memory_high_live: ByteSize = gunbc_runner_slot_desired().memory_high @@ -39,7 +37,24 @@ data gunbc_ci_runner_slot_memory_max_live_source: Disposition = Terminal { reason: "Operator ruling 2026-07-14: gunbc_ci_runner_slot_memory_max_live derives from gunbc.runner_slot_allocation (no duplicate literal). DECLARED ROW is 14GiB MemoryMax / 13GiB MemoryHigh (2026-08-05 allocation ruling). HONEST RESIDUAL (operator observation 2026-08-13, srv1): deploy drop-ins on disk may still read 16GiB/15GiB while system.control set-property residue pins EFFECTIVE caps at 14GiB/13GiB until live_deploy clears it — gunbc.host_axis_caps control_shadow_reconcile + deploy apply (author derived drop-ins then systemctl revert under ghrunner sudo; converge on briansrls does not run this pipeline). This carrier names the DECLARED allocation row, not the kernel-effective cap; floor width sizing must not assume deploy drop-in authority is in force while shadow residue remains. Post-apply throttle-line evidence is the four floor_peak_post receipts (gunbc_ci_floor_run_peak_receipts). Dissolve-on: direct post-apply memory.max readback on a COMPLETED fleet floor lands as a FloorRunCgroupPeakReceipt with memory_max_observed, then the live row can retire in favor of the receipt-fed authority." } -data gunbc_ci_floor_run_peak_receipt_carries_its_own_line_note: String = "AN OBSERVATION IS EVIDENCE ONLY TOGETHER WITH THE CONDITIONS IT WAS TAKEN UNDER, and these four receipts did not carry theirs. Each is a 2026-07-11 floor_peak_post readback pinned at 16106127360 — the throttle line THEN IN FORCE — and the witness over them compared each peak to gunbc_ci_runner_slot_memory_high_live, whichever value that happens to hold today. That coupling made a historical measurement silently re-interpret itself against a later declaration: the 2026-08-05 ruling moved MemoryHigh to 13GiB and four untouched observations from July began asserting something false about a line that did not exist when they were recorded.\n\nThe fix is to record the line beside the peak. armed_memory_high is what the slot's memory.high WAS for that run, so the throttle-line claim stays decidable forever and stays about the right number — the receipts continue to prove exactly what they always proved (the 2026-07-11 caps were applied and the reclaim clamp held within 0.65MiB, zero oom_kills) and they stop being hostage to the current row.\n\nThis is the same shape as the srv2 slice-cap fixture in fleet_show_effective_read_witness_test: a real readback whose relationship to the declaration changed because the declaration moved, where the wrong repairs are editing the observation or reconciling the declaration toward it. Here neither was needed — the receipt was simply missing a field it always implicitly had." +// AN OBSERVATION IS EVIDENCE ONLY TOGETHER WITH THE CONDITIONS IT WAS TAKEN UNDER, and these four +// receipts did not carry theirs. Each is a 2026-07-11 floor_peak_post readback pinned at +// 16106127360 — the throttle line THEN IN FORCE — and the witness over them compared each peak to +// gunbc_ci_runner_slot_memory_high_live, whichever value that happens to hold today. That coupling +// made a historical measurement silently re-interpret itself against a later declaration: the +// 2026-08-05 ruling moved MemoryHigh to 13GiB and four untouched observations from July began +// asserting something false about a line that did not exist when they were recorded. +// +// The fix is to record the line beside the peak. armed_memory_high is what the slot's memory.high +// WAS for that run, so the throttle-line claim stays decidable forever and stays about the right +// number — the receipts continue to prove exactly what they always proved (the 2026-07-11 caps were +// applied and the reclaim clamp held within 0.65MiB, zero oom_kills) and they stop being hostage to +// the current row. +// +// This is the same shape as the srv2 slice-cap fixture in fleet_show_effective_read_witness_test: a +// real readback whose relationship to the declaration changed because the declaration moved, where +// the wrong repairs are editing the observation or reconciling the declaration toward it. Here +// neither was needed — the receipt was simply missing a field it always implicitly had. type FloorRunCgroupPeakReceipt = FloorRunCgroupPeakMeasured { @@ -113,7 +128,6 @@ fn witness_floor_run_peak_receipts_at_throttle_line() -> Bool { && all(gunbc_ci_floor_run_peak_receipts, r => floor_run_peak_receipt_at_throttle_line(receipt: r)) } - type JobCgroupPeakMeasurement = JobCgroupPeakMeasured { cgroup_peak: ByteSize } | JobCgroupPeakUnmeasured @@ -128,14 +142,11 @@ data gunbc_ci_job_peaks: List = [ CiJobPeak { job: "rust_tests", peak: JobCgroupPeakMeasured { cgroup_peak: byte_size(39728447488) } }, ] - data gunbc_ci_run_count_divisor: ByteSize = byte_size(9985798963) - data gunbc_ci_runner_unit_cap_safety_margin_num: Int = 5 data gunbc_ci_runner_unit_cap_safety_margin_den: Int = 4 - data gunbc_ci_per_session_peak_samples: List = [ byte_size(8879844884), byte_size(4584877588), @@ -152,7 +163,6 @@ data gunbc_ci_per_session_peak_samples: List = [ byte_size(300647711), ] - type SessionOperatingPoint = SessionOperatingPointSet { per_session_cap: ByteSize, session_concurrency: Int } | SessionOperatingPointUnset @@ -161,7 +171,6 @@ type OomdEnforcementEvidence = OomdEnforced | OomdUnverified - type RunnerSliceCapEffectiveness = CapEffective { bytes: ByteSize } | CapUnbounded @@ -180,9 +189,6 @@ type SessionReservationMode = Regime1WorstCase { point: SessionOperatingPoint } | SessionReservationModeUnset - - - data gunbc_ci_footprint_granularity_disposition: Disposition = Terminal { reason: "DESIGN §3 deliberate two-concerns split, revised 2026-07-12: INTER-run whole-run cgroup peak (gunbc_ci_job_peaks) governs co-residence - how many runs share a host, feeding the conserving budget tree; WITHIN-run parallelism is governed at run time by the memory governor against the slot's own cgroup budget (the former within-run per-shard-peak carrier and its width fold are retired). Different granularity, different scheduling decision, different consumer. Do not re-fuse them into one authority." } data gunbc_ci_footprint_bridge_disposition: Disposition = Terminal { reason: "Deferred unification path (acknowledged, not attempted): INTER-run whole-run peak is plausibly governor-width x measured per-worker residency + base, so the INTER-run footprint could eventually derive from within-run measured receipts (the executor's [measurement]/[governor] lines) - the real single authority. Held back deliberately: derive only from measured receipts, never from authored predictions (the retired width-fold constants are the cautionary receipt)." } @@ -192,7 +198,28 @@ type HostFixedOverhead | HostFixedOverheadAllowance { bytes: ByteSize, reason: NonEmptyStr } | HostFixedOverheadUnmeasured -data gunbc_ci_host_fixed_overhead_kind_note: String = "THREE ARMS BECAUSE THERE ARE THREE FACTS, and the row that stood here fused two of them under the name of the third. gunbc_ci_host_fixed_overhead was a single fleet-wide HostFixedOverheadMeasured carrying exactly 17179869184 bytes — 2^34, a round power of two — and its Scaffold bound to gunbc_ci_per_service_overhead, a declaration that HAS NEVER EXISTED anywhere in the corpus. So the constructor asserted a measurement, the provenance pointed at a row nobody wrote, and no reader could tell the difference between the two. That is rung inflation (DESIGN 4b) in the budget's own input: a carrier named for the top rung while occupying the bottom, and an inflated class never ranks for climbing.\n\nMEASURED means an observation produced it and the row says which one — the provenance field is required, not optional, for exactly the reason the SEC citation rule is: for a measured fact the provenance IS the grounding, so an arm that could omit it is a fail-open invitation. ALLOWANCE means an operator granted headroom for a deployment the model does not describe, which is a DIFFERENT KIND OF FACT, not a weaker measurement — it is honest, it is load-bearing, and it must never be reported as measured. UNMEASURED stays the fail-closed floor.\n\nWhy per-host rather than one fleet number: srv1 and srv2 carry hand-built deployments this corpus never modeled (the session dashboard instances of gunbc.build_cache_instance among them), so their allowance is a real operator judgement about unmodeled residents. srv3 and srv4 were stood up through the modeled path and have an observation. One number could only have been the maximum of the two, which silently taxes the managed hosts for the legacy ones' mess — 10.4 GiB per host of memory the budget refused to spend because two other machines are untidy." +// THREE ARMS BECAUSE THERE ARE THREE FACTS, and the row that stood here fused two of them under the +// name of the third. gunbc_ci_host_fixed_overhead was a single fleet-wide HostFixedOverheadMeasured +// carrying exactly 17179869184 bytes — 2^34, a round power of two — and its Scaffold bound to +// gunbc_ci_per_service_overhead, a declaration that HAS NEVER EXISTED anywhere in the corpus. So +// the constructor asserted a measurement, the provenance pointed at a row nobody wrote, and no +// reader could tell the difference between the two. That is rung inflation (DESIGN 4b) in the +// budget's own input: a carrier named for the top rung while occupying the bottom, and an inflated +// class never ranks for climbing. +// +// MEASURED means an observation produced it and the row says which one — the provenance field is +// required, not optional, for exactly the reason the SEC citation rule is: for a measured fact the +// provenance IS the grounding, so an arm that could omit it is a fail-open invitation. ALLOWANCE +// means an operator granted headroom for a deployment the model does not describe, which is a +// DIFFERENT KIND OF FACT, not a weaker measurement — it is honest, it is load-bearing, and it must +// never be reported as measured. UNMEASURED stays the fail-closed floor. +// +// Why per-host rather than one fleet number: srv1 and srv2 carry hand-built deployments this corpus +// never modeled (the session dashboard instances of gunbc.build_cache_instance among them), so +// their allowance is a real operator judgement about unmodeled residents. srv3 and srv4 were stood +// up through the modeled path and have an observation. One number could only have been the maximum +// of the two, which silently taxes the managed hosts for the legacy ones' mess — 10.4 GiB per host +// of memory the budget refused to spend because two other machines are untidy. data gunbc_ci_host_memavailable_observation_note: String = "THE OBSERVATION THAT BOUNDS THE MANAGED-HOST OVERHEAD, and it is a found receipt rather than a fresh probe — worth saying plainly because it changes what the bound is allowed to claim. Before the srv3 cap converge (gunbc.runner_slot_allocation, 2026-07-21) srv3's slots were uncapped, so v1_compiler::memory_governor had no cgroup memory.high to arm against and fell back to /proc/meminfo MemAvailable, printing the host-level figure into every floor job's log. Seventeen such arm-time lines survive from 2026-07-16 across srv3-02/05/08, spanning 104.8 to 119.4 GiB; srv1 and srv2 on the same day printed only their 15 GiB cgroup budget, so the series is srv3 and nothing else.\n\nTHE DIRECTION OF THE BOUND IS THE WHOLE POINT. MemAvailable counts free memory plus reclaimable page cache and slab, so what it EXCLUDES is non-reclaimable anonymous RSS — services, daemons, and whatever runner work was resident at that instant. The MAXIMUM observation therefore corresponds to the QUIETEST moment seen, and host_total - max(MemAvailable) is an UPPER bound on fixed service overhead: at that instant everything non-reclaimable on the box, including any concurrent job, fit inside it. A smaller true overhead is consistent with this receipt; a larger one is not.\n\nWHAT IT IS NOT: a dedicated measurement. Nobody read /proc/meminfo on a quiescent srv3, and this row must not be cited as though someone had. It is admissible because it is an upper bound and the budget spends against it conservatively — the bound refuses to justify more slots than it can carry, which is the direction a fail-closed budget wants. The dissolution trigger is an ordinary quiescent read, which is also a prerequisite of the systemd-oomd verification that gunbc.oomd_install still lacks, so one reach closes both." @@ -226,7 +253,19 @@ data gunbc_ci_legacy_host_fixed_overhead: HostFixedOverhead = HostFixedOverheadA reason: "srv1 and srv2 carry hand-built deployments the corpus does not model — the session dashboard instance (gunbc.build_cache_instance session_cache_instance, srv1 and srv2 only), its sccache server, and the residue of the pre-subsumption install path. 16 GiB is the operator's standing allowance for those unmodeled residents (ruling 2026-08-05), carried forward unchanged from the fleet-wide row it replaces so this change moves no number on these two hosts. It is an ALLOWANCE and not a measurement: no observation produced it, and the same MemAvailable receipt that bounds srv3 does not exist for srv1/srv2 because their slots were already capped when the governor stopped reading meminfo. Dissolves when those deployments are modeled — at which point their residents become budget rows and the allowance becomes whatever is left over, if anything.", } -data gunbc_ci_host_fixed_overhead_unknown_host_note: String = "THE UNKNOWN ARM REFUSES, and the first draft of this function did not — it ended in a bare else returning the managed measurement, so a host nobody had classified would silently inherit srv3's 5.6 GiB bound and the budget would hand it two extra slots on the strength of another machine's receipt. That is the absorbing fallback (DESIGN 5) sitting in the input to a conservation wall, which is the worst place for one: the wall's arithmetic would be correct and its premise fabricated, exactly the shape gunbc.plans.fleet_subsumption_manual_gaps section C prices — srv4 took fleet work as a member of GitHub's fleet and a ghost of the model, and OOM-killed live CI the next day.\n\nSo an unclassified host derives HostFixedOverheadUnmeasured, which host_fixed_overhead_bytes cannot turn into a number and gunbc.ci_runner_placement refuses on. Adding srv5 therefore fails closed with a located reason rather than quietly succeeding with a borrowed one — the enrollment wall's own shape, applied to the budget's inputs instead of its installer." +// THE UNKNOWN ARM REFUSES, and the first draft of this function did not — it ended in a bare else +// returning the managed measurement, so a host nobody had classified would silently inherit srv3's +// 5.6 GiB bound and the budget would hand it two extra slots on the strength of another machine's +// receipt. That is the absorbing fallback (DESIGN 5) sitting in the input to a conservation wall, +// which is the worst place for one: the wall's arithmetic would be correct and its premise +// fabricated, exactly the shape gunbc.plans.fleet_subsumption_manual_gaps section C prices — srv4 +// took fleet work as a member of GitHub's fleet and a ghost of the model, and OOM-killed live CI +// the next day. +// +// So an unclassified host derives HostFixedOverheadUnmeasured, which host_fixed_overhead_bytes +// cannot turn into a number and gunbc.ci_runner_placement refuses on. Adding srv5 therefore fails +// closed with a located reason rather than quietly succeeding with a borrowed one — the enrollment +// wall's own shape, applied to the budget's inputs instead of its installer. fn gunbc_ci_host_fixed_overhead_for(host: HostIdentity) -> HostFixedOverhead { if (host as String) == (operator_host_srv1 as String) { @@ -242,7 +281,28 @@ fn gunbc_ci_host_fixed_overhead_for(host: HostIdentity) -> HostFixedOverhead { } } -data gunbc_ci_host_fixed_overhead_disposition_split_note: String = "ONE SCAFFOLD BECOMES TWO BECAUSE THE TWO OVERHEADS DISSOLVE ON DIFFERENT EVENTS, and each still binds a successor that DOES NOT YET EXIST — which is what a Scaffold bind is for. v2.lens.disposition_redundancy reads these marks and counts a violation when a bind target is PRESENT: a scaffold whose successor has been written is redundant and must be dissolved. So an absent target is the normal, correct state of outstanding debt, and the lens is the thing that notices when the debt is actually payable.\n\nCORRECTION, recorded because an earlier revision of this change got it backwards and would have shipped the inversion. The row this replaces bound to gunbc_ci_per_service_overhead, and that bind was read here as a defect — 'a dissolve-on pointing at nothing' — on the reasoning that a marker naming a nonexistent declaration can never fire. The reasoning inverts the mechanism. It fires precisely BY that declaration coming into existence, and the first draft's repair (re-binding both scaffolds to rows this module already declares) would have made both of them immediate violations of the redundancy lens, converting two pieces of honest outstanding debt into two false claims that the debt was already paid. The original bind was right; only its single-row grain was wrong.\n\nWhat legitimately changed is the grain. gunbc_ci_per_service_overhead was one successor for one fleet-wide number, and there are now two facts with two different closure conditions: the managed hosts need an observation, the legacy pair need their deployments modeled. Neither successor exists, both name what would replace them, and writing either one fires the lens for that arm alone." +// ONE SCAFFOLD BECOMES TWO BECAUSE THE TWO OVERHEADS DISSOLVE ON DIFFERENT EVENTS, and each still +// binds a successor that DOES NOT YET EXIST — which is what a Scaffold bind is for. +// v2.lens.disposition_redundancy reads these marks and counts a violation when a bind target is +// PRESENT: a scaffold whose successor has been written is redundant and must be dissolved. So an +// absent target is the normal, correct state of outstanding debt, and the lens is the thing that +// notices when the debt is actually payable. +// +// CORRECTION, recorded because an earlier revision of this change got it backwards and would have +// shipped the inversion. The row this replaces bound to gunbc_ci_per_service_overhead, and that +// bind was read here as a defect — 'a dissolve-on pointing at nothing' — on the reasoning that a +// marker naming a nonexistent declaration can never fire. The reasoning inverts the mechanism. It +// fires precisely BY that declaration coming into existence, and the first draft's repair +// (re-binding both scaffolds to rows this module already declares) would have made both of them +// immediate violations of the redundancy lens, converting two pieces of honest outstanding debt +// into two false claims that the debt was already paid. The original bind was right; only its +// single-row grain was wrong. +// +// What legitimately changed is the grain. gunbc_ci_per_service_overhead was one successor for one +// fleet-wide number, and there are now two facts with two different closure conditions: the managed +// hosts need an observation, the legacy pair need their deployments modeled. Neither successor +// exists, both name what would replace them, and writing either one fires the lens for that arm +// alone. data gunbc_ci_managed_host_fixed_overhead_disposition: Disposition = Scaffold { dissolves_to: SingleAuthority, @@ -271,7 +331,6 @@ data gunbc_ci_host_usable_ram_kind_note: String = "OS-USABLE RAM IS A PER-HOST F data gunbc_ci_conservative_lower_bound_retired_note: String = "THE FLEET-WIDE CONSERVATIVE LOWER BOUND IS DELETED, ITS OBLIGATION DISCHARGED ON EVERY HOST. It held byte_size(134217728000) -- the MemTotal-shaped figure captured on srv3 on 2026-07-16 -- and lent it to whichever hosts had no read of their own, which was at first all four and latterly srv1 and srv2. All four now carry their own /proc/meminfo MemTotal, so it had no consumer left and is removed rather than kept as a value nothing reads.\n\nIT IS DELETED, NOT MARKED HISTORICAL, and the distinction matters because the observation it borrowed still exists: gunbc_ci_srv3_memavailable_high_water is untouched and is still the receipt the managed-host overhead bound rests on. What is gone is the act of LENDING that number to other machines. The row's own text predicted this end -- it said the bound survives a host growing because a lower bound only loosens, and that each host replaces it independently by its own MemTotal read -- and that is what happened, on the last of the four, today.\n\nWHAT SURVIVES AND SHOULD NOT BE MISTAKEN FOR DEAD: the HostUsableRamConservativeLowerBound VARIANT stays in the type and host_usable_ram_or_refusal still handles it. Nothing constructs it today, and an arm that nothing currently occupies is a quiet guard rather than a dead one -- a fifth host enrolled tomorrow with no read of its own is exactly the state it exists to represent, and deleting the arm because its population is momentarily zero would remove the representation along with the occupant." - data gunbc_ci_host_usable_ram_disposition_note: String = "REGISTERED AS SCAFFOLD DEBT BECAUSE ITS TWIN IS, and because the debt is PRE-EXISTING rather than introduced here: the fleet-wide constant this carrier replaces was already an unmeasured stand-in, and modeling it is what makes it countable. DESIGN 5 refuses NEW dissolution obligations without an operator verdict; it separately exists so that documenting previously hidden debt never reads as creating it, which is this row.\n\nTHE BIND TARGET IS DELIBERATELY NOT THE OVERHEAD ROW'S. gunbc_ci_managed_host_fixed_overhead_disposition binds a QUIESCENT read, because overhead is estimated from MemAvailable and MemAvailable moves with load. MemTotal does not — the kernel reports total usable RAM after firmware and kernel reservations — so an ordinary read taken while the host serves work discharges this and not that. Two obligations, two observations, two independently removable units, which is the grain DESIGN 5 requires: sharing one trigger is admissible only when neither artifact can survive the other, and these plainly can." data gunbc_ci_host_usable_ram_disposition: Disposition = Terminal { @@ -334,7 +393,17 @@ fn gunbc_ci_host_usable_ram_for(host: HostIdentity) -> HostUsableRam { } } -data host_usable_ram_ceiling_note: String = "THE CEILING PROJECTION LIVES ONCE, BESIDE THE CARRIER. gunbc.ci_budget_tree and gunbc.fleet_host_budget each grew a byte-identical fold over HostUsableRam when they migrated off nominal DIMM capacity, which is the duplication DESIGN 2 forbids and exactly the shape that lets two budget models drift apart again — the defect the migration existed to close.\n\nIt does NOT absorb gunbc.ci_runner_placement host_usable_ram_or_refusal, which folds the same coproduct to a different answer on purpose. That one is LOUD: an unmeasured host yields a typed, located refusal that becomes PlanUnsound. This one is a total ceiling for budget arithmetic, where zero is the fail-closed reading because an unknown ceiling admits nothing. Same input, two honest consumers, two results — collapsing them would force one caller to accept the other's failure semantics." +// THE CEILING PROJECTION LIVES ONCE, BESIDE THE CARRIER. gunbc.ci_budget_tree and +// gunbc.fleet_host_budget each grew a byte-identical fold over HostUsableRam when they migrated off +// nominal DIMM capacity, which is the duplication DESIGN 2 forbids and exactly the shape that lets +// two budget models drift apart again — the defect the migration existed to close. +// +// It does NOT absorb gunbc.ci_runner_placement host_usable_ram_or_refusal, which folds the same +// coproduct to a different answer on purpose. That one is LOUD: an unmeasured host yields a typed, +// located refusal that becomes PlanUnsound. This one is a total ceiling for budget arithmetic, +// where zero is the fail-closed reading because an unknown ceiling admits nothing. Same input, two +// honest consumers, two results — collapsing them would force one caller to accept the other's +// failure semantics. fn host_usable_ram_ceiling_bytes(host: HostIdentity) -> ByteSize { match gunbc_ci_host_usable_ram_for(host: host) { @@ -344,7 +413,6 @@ fn host_usable_ram_ceiling_bytes(host: HostIdentity) -> ByteSize { } } - data gunbc_ci_concurrent_runs_conservative_high_disposition: Disposition = Scaffold { dissolves_to: SingleAuthority, bind: DeclarationRef { diff --git a/dag/gunbc/ci/ci_gate.dag b/dag/gunbc/ci/ci_gate.dag index 0cb38a65185..f762792c87b 100644 --- a/dag/gunbc/ci/ci_gate.dag +++ b/dag/gunbc/ci/ci_gate.dag @@ -13,7 +13,31 @@ type Gate | ProseRowIntroductionGate | SourceRootIngestGate -data gate_receipt_state_split_note: String = "WHY A GATE ANSWERS A RECEIPT AND NOT A Bool. The two host gate consumers behind DagCompileCleanGate and GeneratedArtifactDriftGate used to answer through a process-global read collapsed to Bool, with a second String builtin beside it carrying the located detail. consume_floor_compile_clean_gate_verdict folded FIVE distinct states into false -- receipt lock poisoned, receipt install failed, no in-run receipt at all, the receipt's own typed Refused arm, and a genuine Compiled ok:false -- and folded the scope disposition's Skipped into true. So a gate that never ran in this process was indistinguishable from a compile that found hard diagnostics, and a scope disposition selecting nothing was indistinguishable from a clean tree. That is DESIGN's execution-provenance-loss row (a masked run and a clean run rendered identically) sitting underneath not-applicable-rendered-as-malformed, in one Bool. The detail companion did not repair it: it was a SECOND builtin over the SAME global, so a caller had to correlate two reads to recover one fact, and the generated-artifact one FABRICATED prose ('gate body did not run in this process') for the very state the Bool had already erased. THE THREE ARMS ARE THE THREE OWNERS, which is why this is a construction and not a wider enum. GateObserved means the gate reached its subject and decided; its GateOutcome carries the located detail on the failing side, so the detail can no longer be read without the verdict that produced it. GateNotApplicable means the gate's own scope disposition selected nothing to check, an answer whose owner is the disposition and whose remedy is never 'fix the tree'. GateNotRun means could-not-measure: the instrument did not arrive at its subject, whose owner is the instrument and whose remedy is never 'fix the tree' either. The line still stops on GateNotRun (gate_receipt_exit refuses it), so this is not a relaxation: what changes is that the stop is typed and located, and analysis can precede restart. MODELLED ON gunbc.compile_diagnostic_census, deliberately and not coincidentally: that carrier splits CensusObserved from CensusNotRunnable for exactly this reason, and its census_projection_note records that exporting a reader which answers empty on the not-runnable arm was the fail-open it existed to prevent. The same rule holds here, and it is why no accessor hands back a Bool from a GateReceipt: the only way to a verdict is to match the coproduct." +// WHY A GATE ANSWERS A RECEIPT AND NOT A Bool. The two host gate consumers behind +// DagCompileCleanGate and GeneratedArtifactDriftGate used to answer through a process-global read +// collapsed to Bool, with a second String builtin beside it carrying the located detail. +// consume_floor_compile_clean_gate_verdict folded FIVE distinct states into false -- receipt lock +// poisoned, receipt install failed, no in-run receipt at all, the receipt's own typed Refused arm, +// and a genuine Compiled ok:false -- and folded the scope disposition's Skipped into true. So a +// gate that never ran in this process was indistinguishable from a compile that found hard +// diagnostics, and a scope disposition selecting nothing from a clean tree: DESIGN's +// execution-provenance-loss row (a masked run and a clean run rendered identically) underneath +// not-applicable-rendered-as-malformed, in one Bool. The detail companion did not repair it: a +// SECOND builtin over the SAME global, so a caller had to correlate two reads to recover one fact, +// and the generated-artifact one FABRICATED prose ('gate body did not run in this process') for +// the very state the Bool had erased. THE THREE ARMS ARE THE THREE OWNERS, which is why this is a +// construction and not a wider enum. GateObserved means the gate reached its subject and decided; +// its GateOutcome carries the located detail on the failing side, so the detail can no longer be +// read without the verdict that produced it. GateNotApplicable means the gate's own scope +// disposition selected nothing to check -- owner the disposition, remedy never 'fix the tree'. +// GateNotRun means could-not-measure: the instrument did not arrive at its subject -- owner the +// instrument, remedy never 'fix the tree' either. The line still stops on GateNotRun +// (gate_receipt_exit refuses it), so this is not a relaxation: the stop is now typed and located, +// and analysis can precede restart. MODELLED ON gunbc.compile_diagnostic_census, deliberately: +// that carrier splits CensusObserved from CensusNotRunnable for exactly this reason, and its +// census_projection_note records that exporting a reader answering empty on the not-runnable arm +// was the fail-open it existed to prevent. The same rule holds here, and it is why no accessor +// hands back a Bool from a GateReceipt: the only way to a verdict is to match the coproduct. type GateOutcome = GateClean @@ -24,7 +48,13 @@ type GateReceipt | GateNotApplicable { reason: NonEmptyStr } | GateNotRun { cause: NonEmptyStr } -data gate_receipt_exit_note: String = "The one place a receipt becomes a line-stop, so the three arms cannot drift apart across gates. GateNotRun exits FAILURE and says so in its OWN vocabulary rather than borrowing the subject's: a reader of the exit reason can tell 'the gate did not run' from 'the gate ran and refused the tree', which is the whole distinction the Bool destroyed. GateNotApplicable exits SUCCESS and that is not a widen: the scope disposition is the authority for what this gate covers, so a disposition selecting nothing is an answer, not an absence of one, and its reason travels in the receipt for any consumer that wants to count it." +// The one place a receipt becomes a line-stop, so the three arms cannot drift apart across gates. +// GateNotRun exits FAILURE in its OWN vocabulary rather than the subject's: a reader of the exit +// reason can tell 'the gate did not run' from 'the gate ran and refused the tree', the distinction +// the Bool destroyed. GateNotApplicable exits SUCCESS and that is not a widen: the scope +// disposition is the authority for what this gate covers, so a disposition selecting nothing is +// an answer, not an absence of one, and its reason travels in the receipt for any consumer that +// wants to count it. fn gate_outcome_exit(outcome: GateOutcome) -> ProcessExit { match outcome { @@ -43,7 +73,16 @@ fn gate_receipt_exit(r: GateReceipt) -> ProcessExit { } } -data gate_receipt_failure_detail_note: String = "THE COMPANION PROJECTION, and it is a String only because its consumer is. claim_executor derives a _failure_receipt companion from a Bool witness's own name and evaluates it to explain a red (tools.floor_effect_gate_witness floor_gate_failure_receipt_note is that mechanism's authority), so the two CI gates must still be able to hand back a located line. What changes is where the line comes from: it is now PROJECTED from the same receipt that produced the verdict, instead of read out of a second builtin over the same process global. That is the §3 half of this retype -- one authority, two projections -- and it is why no arm here answers a detail the verdict did not produce. Each arm keeps its OWN vocabulary: a gate that did not run says so, and never borrows the subject's ('compile-clean failed'), which is precisely what the two-builtin form could not avoid, because the detail read had no idea which state the verdict read had been in." +// THE COMPANION PROJECTION, a String only because its consumer is. claim_executor derives a +// _failure_receipt companion from a Bool witness's own name and evaluates it to explain a +// red (tools.floor_effect_gate_witness floor_gate_failure_receipt_note is that mechanism's +// authority), so the two CI gates must still hand back a located line. What changes is its source: +// PROJECTED from the same receipt that produced the verdict, instead of read out of a second +// builtin over the same process global. That is the §3 half of this retype -- one authority, two +// projections -- and why no arm here answers a detail the verdict did not produce. Each arm keeps +// its OWN vocabulary: a gate that did not run says so, never borrowing the subject's +// ('compile-clean failed'), which the two-builtin form could not avoid because the detail read had +// no idea which state the verdict read had been in. fn gate_outcome_failure_detail(outcome: GateOutcome) -> String { match outcome { diff --git a/dag/gunbc/ci/ci_layer_roots.dag b/dag/gunbc/ci/ci_layer_roots.dag index 9498cbc34b5..9cdbf697cdc 100644 --- a/dag/gunbc/ci/ci_layer_roots.dag +++ b/dag/gunbc/ci/ci_layer_roots.dag @@ -236,7 +236,15 @@ data required_emit_compile_entries: List = [ "dag/std/logic.dag" ] -data frontier_probe_witness_measure_receipt_note: String = "Measured durations cited on commit_gate SpanEnrolled cost_basis rows are WallClock: claim_batch performance receipts use wall_nanos (performance_receipt_from_witness), not the thread-CPU nanos that budget_completion_outcome gates on (operator msg_e24f4cab). Do not compare these figures to the per-witness eval CPU budget without stating the clock. The arm is named in std.measure ClockBasis, which is the single clock-basis authority; this sentence said WitnessCostWallEval until 2026-08-05, naming a second carrier that std.realization_schedule declared for one day and that witness_cost_clock_note records the dissolution of. The advice the sentence gives is exactly what the surviving carrier now enforces rather than advises: witness_row_cost_verdict answers BasisClockMismatch instead of comparing across clocks." +// Measured durations cited on commit_gate SpanEnrolled cost_basis rows are WallClock: claim_batch +// performance receipts use wall_nanos (performance_receipt_from_witness), not the thread-CPU nanos +// that budget_completion_outcome gates on (operator msg_e24f4cab). Do not compare these figures to +// the per-witness eval CPU budget without stating the clock. The arm is named in std.measure +// ClockBasis, which is the single clock-basis authority; this sentence said WitnessCostWallEval +// until 2026-08-05, naming a second carrier that std.realization_schedule declared for one day and +// that witness_cost_clock_note records the dissolution of. The advice the sentence gives is exactly +// what the surviving carrier now enforces rather than advises: witness_row_cost_verdict answers +// BasisClockMismatch instead of comparing across clocks. data frontier_probe_witness_measure_receipt_read_failure: String = "target/frontier-probe-survey/matrix_readthrough_claim_batch_receipt.log" @@ -269,7 +277,15 @@ data excl_run_verdict_exit_status_reason: String = "SUBSTANTIATED per-row (found data excl_run_verdict_exit_status_dissolve: DissolutionCondition = unbound_dissolution(description: "the driver's exit-status seam becomes observable without a subprocess — i.e. the verdict boundary migrates under the execution-corpus scope prefix and enrolls as ExecutionWitnessKind rows in gunbc.commit_workflow, the same terminal excl_bin_wet_dissolve names; then this row and the three roster entries delete together. Publishing a mock_response for Run/IsExecutable does NOT discharge it: a mocked status is authored data, so the assertions would be re-checked against the fixture rather than against the seam, which is the vacuous pass this file's refusal-polarity note already records once.") -data excl_rehomed_bin_wet_reason: String = "bin-execution witness class re-homed to the falsifier wet cadence (CI floor endgame D6): the file's enrolled fn(s) exceed bin_witness_wet_per_row_wall_budget_seconds, so they are CLASSIFIED as falsifier batch 5 via falsifier_rehomed_bin_wet_entries (typed frontier rows in falsifier_rehomed_bin_wet_rows). CADENCE STANDING (2026-08-19, dashboard node adhoc-1eb1a9f9-ad9): falsifier.yml, the workflow that scheduled every falsifier-family cadence including this one, was deleted at 611fd02770/#8283 (2026-08-15); classification into FalsifierRehomedBinWet still routes the row off per-PR discovery but no longer implies anything executes it — witness_execution_standing_for_row now derives DeclaredCadenceUnrealized here, same as FalsifierSubstrateLongLane (see falsifier_substrate_long_lane_note)." +// bin-execution witness class re-homed to the falsifier wet cadence (CI floor endgame D6): the +// file's enrolled fn(s) exceed bin_witness_wet_per_row_wall_budget_seconds, so they are CLASSIFIED +// as falsifier batch 5 via falsifier_rehomed_bin_wet_entries (typed frontier rows in +// falsifier_rehomed_bin_wet_rows). CADENCE STANDING (2026-08-19, dashboard node +// adhoc-1eb1a9f9-ad9): falsifier.yml, the workflow that scheduled every falsifier-family cadence +// including this one, was deleted at 611fd02770/#8283 (2026-08-15); classification into +// FalsifierRehomedBinWet still routes the row off per-PR discovery but no longer implies anything +// executes it — witness_execution_standing_for_row now derives DeclaredCadenceUnrealized here, same +// as FalsifierSubstrateLongLane (see falsifier_substrate_long_lane_note). data excl_rehomed_bin_wet_dissolve: DissolutionCondition = unbound_dissolution(description: "per falsifier_rehomed_bin_wet_rows dissolve_on: the row's realization lands under the per-row budget on a measured run and re-enrolls per-PR (or deletes as a duplicate); then this classification flips back to BinWitnessWet") @@ -1044,9 +1060,6 @@ fn witness_admission_fixture_exclusion_substrings() -> List { ) } - - - data witness_exclusion_single_authority_reconciliation_note: String = "Single-authority reconciliation (module-identity-storage-binding Phase 0(b) follow-on; the prior note's dissolve-on FIRED 2026-07-22 via the group-of-units ruling): witness_exclusion_frontier is the ONE typed discovery-exclusion roster — every row carries pattern, consumer classification (the std.witness_admission WitnessConsumerCadence authority, no minted twin), reason, and dissolve_on. The former parallel lists witness_admission_offline_exclusion_substrings / witness_admission_fixture_exclusion_substrings are now DERIVED projections (classification filters) — the hand-kept containment alignment they needed is unwritable, so the old one-way-containment witness dissolved with them; v2.test.claim.witness_exclusion_reconciliation_test now witnesses row well-formedness (no DiscoverySelection row; NoConsumer only for the synthetic admission RED-control path; nonempty reason and dissolve_on per row) and explicit-roster consistency (BinWitnessWet / rehomed / substrate-long-lane rows name files the corresponding rosters actually run). As of 2026-08-03 it is a PATH POLICY roster: exact witness admission moved to gunbc.explicit_witness_admission at function grain and a QuarantineProbeExpectRed row here is refused outright, because a substring pattern can only be checked for containment against a schedule row and containment is not identity. whole_tree_resolve_exclusion_substrings stays the ONE concat(witness_exclusion_substrings(), whole_tree_strict_resolve_exclusion_substrings) pattern authority in .dag; the host projection in cli_run.rs parses the frontier rows with the real front-end and must stay list-identical (Rust unit test witness_whole_tree_resolve_exclusion_matches_concat_authority). Strict-walk probe bins consume whole_tree_probe_exclusion_substrings() — pattern rows ∪ host-derived module-path closure (census_exclude_derive) with LiveImporterExclusion receipts. The roster is enrolled in gunbc.roster_registry as a DeclaredFrontier group. Dissolves when the enforcement-intent registry absorbs the roster or floor admission consumes per-row cost and consumer declarations directly." data witness_admission_offline_note: String = "Phase 0(b) admission invariant (module-identity-storage-binding): operator-ruled OFFLINE exclusion substrings whose witness rows carry OfflineLocalRecipe consumers — documented local recipes in the paired exclusion notes (wet_integration_offline_note, enforcement_coverage_exclusion_note, accumulator_copy_roster_gate_exclusion_note, lever_a_local_receipt_witness_exclusion_note, long_lane_exclusion_note). Dissolves when each row graduates to a scheduled CI consumer or deletes. STANDING, 2026-08-25: the recipes are now typed rows in gunbc.offline_local_recipe (five of the six policies; identity_captured_navigation deliberately owns none, its exclusion being deleted rather than repaired by gunbc#9206). The GRADUATION arm of the dissolution above currently names no destination -- no scheduled cadence has existed since the floor cut -- so the arm actioned per policy is either delete, or the typed carrier plus its declared defect roster, never a graduation nobody can perform." @@ -1061,9 +1074,38 @@ Local recipe: gunbc run --source-root src/v2 --source-root dag --entry src/v2/wo Dissolve-on: the crate-layout artifact moves to host-driven generation with the index supplied at the boundary — then the hand rows, the parity witness, and this exclusion delete together." -data witness_admission_fixture_note: String = "THE 2026-07-11 FIXTURE-HOME RULING, KEPT AFTER ITS LAST POPULATION WENT AWAY: a control fixture whose consumer is an explicit roster is excluded from tree-wide discovery at DIR grain, never at file grain — a fixture that WANTS witness enrollment is mis-homed by definition, and a file-grain exclusion invites the next control fixture to repeat the mistake. BOTH homes the ruling was written for are now gone: floor_skip died with affected-set selection, and walk_plan_stage died with the claim_executor plan/walk surface it was driven by (2026-08-26). This row therefore carries a RULING and no rows, which is deliberate — the ruling is what the next control fixture needs, and deleting it with its last specimen would make the next author rediscover it by repeating the file-grain mistake. THE SUCCESSOR FIXTURE HOME DOES NOT NEED IT: src/v2/test/fixture/compile_clean_scope/ (tools.dag_compile_clean_scope scope_disposition_witness_roster_note) authors no test fn at all, so discovery has nothing to admit and no exclusion row exists for it — which is the stronger construction, since an exclusion is a policy someone must maintain and an absent test fn is not writable into the corpus by accident. Dissolves when a control fixture home authoring test fns exists again and needs a row, or never." - -data known_red_probe_note: String = "Phase 0(b) quarantine probe cadence (module-identity-storage-binding): known-red frontier witnesses that EXPECT RED on main — the falsifier probe batch's designed shape runs them SelectionApplied; greening is a counted un-quarantine event (per-row dissolve_on on the authority). Distinct from falsifier_self_host_wet (green-only SelfEmitted receipts). SINGLE AUTHORITY (2026-08-03): the rows live at function grain in gunbc.explicit_witness_admission explicit_witness_admissions and BOTH consumers derive from them — this roster is the falsifier batch's projection, and v2.workflow.floor_discovery_producer drops exactly the same (entry, function) pairs from per-PR discovery. The former shape was a dual representation: a QuarantineProbeExpectRed WitnessExclusionRow removing the whole FILE plus a probe_red(entry, f) row enrolling one FUNCTION, reconciled by a check instead of by construction. Those pattern rows are deleted; a pattern row that re-states a quarantine is now refused by witness_exclusion_row_well_formed. CADENCE STANDING (2026-08-19, dashboard node adhoc-1eb1a9f9-ad9): 'runs them' describes the batch's designed ordering, not a currently running schedule — falsifier.yml was deleted at 611fd02770/#8283 (2026-08-15), so nothing currently runs this probe batch and witness_execution_standing_for_row derives DeclaredCadenceUnrealized for QuarantineProbeExpectRed rows too (see falsifier_substrate_long_lane_note)." +// THE 2026-07-11 FIXTURE-HOME RULING, KEPT AFTER ITS LAST POPULATION WENT AWAY: a control fixture +// whose consumer is an explicit roster is excluded from tree-wide discovery at DIR grain, never at +// file grain — a fixture that WANTS witness enrollment is mis-homed by definition, and a file-grain +// exclusion invites the next control fixture to repeat the mistake. BOTH homes the ruling was +// written for are now gone: floor_skip died with affected-set selection, and walk_plan_stage died +// with the claim_executor plan/walk surface it was driven by (2026-08-26). This row therefore +// carries a RULING and no rows, which is deliberate — the ruling is what the next control fixture +// needs, and deleting it with its last specimen would make the next author rediscover it by +// repeating the file-grain mistake. THE SUCCESSOR FIXTURE HOME DOES NOT NEED IT: +// src/v2/test/fixture/compile_clean_scope/ (tools.dag_compile_clean_scope +// scope_disposition_witness_roster_note) authors no test fn at all, so discovery has nothing to +// admit and no exclusion row exists for it — which is the stronger construction, since an exclusion +// is a policy someone must maintain and an absent test fn is not writable into the corpus by +// accident. Dissolves when a control fixture home authoring test fns exists again and needs a row, +// or never. + +// Phase 0(b) quarantine probe cadence (module-identity-storage-binding): known-red frontier +// witnesses that EXPECT RED on main — the falsifier probe batch's designed shape runs them +// SelectionApplied; greening is a counted un-quarantine event (per-row dissolve_on on the +// authority). Distinct from falsifier_self_host_wet (green-only SelfEmitted receipts). SINGLE +// AUTHORITY (2026-08-03): the rows live at function grain in gunbc.explicit_witness_admission +// explicit_witness_admissions and BOTH consumers derive from them — this roster is the falsifier +// batch's projection, and v2.workflow.floor_discovery_producer drops exactly the same (entry, +// function) pairs from per-PR discovery. The former shape was a dual representation: a +// QuarantineProbeExpectRed WitnessExclusionRow removing the whole FILE plus a probe_red(entry, f) +// row enrolling one FUNCTION, reconciled by a check instead of by construction. Those pattern rows +// are deleted; a pattern row that re-states a quarantine is now refused by +// witness_exclusion_row_well_formed. CADENCE STANDING (2026-08-19, dashboard node +// adhoc-1eb1a9f9-ad9): 'runs them' describes the batch's designed ordering, not a currently running +// schedule — falsifier.yml was deleted at 611fd02770/#8283 (2026-08-15), so nothing currently runs +// this probe batch and witness_execution_standing_for_row derives DeclaredCadenceUnrealized for +// QuarantineProbeExpectRed rows too (see falsifier_substrate_long_lane_note). fn known_red_probe_roster() -> List { concat( @@ -1074,7 +1116,6 @@ fn known_red_probe_roster() -> List { data long_lane_exclusion_note: String = "test/claim/long/ is the LONG-test home (operator 5-second rule, 2026-07-12; authority + enforcement: gunbc_ci_fast_lane_rule_note / gunbc_ci_fast_lane_witness_eval_budget in v2.workflow.ci_floor_plan): a witness whose own eval reaches 5s does not run in per-PR discovery — the executor's cooperative eval deadline REDs it by name (EvalBudgetExceeded), and the fix is moving its file here (dag/test/claim/long/, src/v2/test/claim/long/), where discovery excludes it at dir grain and it runs via the local recipe (claim_batch --source-root dag --source-root src/v2 --entry --functions ) -- carried as a typed home row in gunbc.offline_local_recipe, which renders a resident's argv rather than leaving the command in prose -- or the scheduled lane's future enrollment. First residents (receipted): s1_closure_receipt_test (whole-closure re-parse through the interpreted v2 frontend, 90+min eval, the run-29183446733 wedge class) and self_host_module_emit_derisk_test (~31s/witness). Membership here was called a HOME rather than a per-file ruling on the ground that the 5s deadline makes an over-budget fast-lane witness unwritable-in-place, so the roster could not silently grow stale. THAT SENTENCE WAS FALSE AND IS RETRACTED (2026-08-04). The deadline is evaluated only while a witness RUNS; relocating its file under this prefix is precisely what stops it running, so the claimed construction backstop is disarmed by the exact move it was supposed to govern, and the home grew by whatever anyone moved into it. BudgetExceeded is an interruption plus a measured lower bound on cost — never a semantic verdict on a witness, and never a discharge of one by causing its relocation. Receipt: gunbc#7762 commit 0cfd199f8, thirteen witnesses relocated here under the title fix CI fast-lane budget failures with a claim_batch command line as their only stated consumer and no cadence row. WHAT NOW HOLDS THE LINE: this prefix still organizes the files, but it no longer answers the admission question. gunbc.witness_deferral_freeze enumerates the exact legacy identities path policy had already admitted, and any offline-classified witness outside that frozen set and outside gunbc.explicit_witness_admission refuses as UnclassifiedPathDeferral, while a frozen row the tree no longer carries refuses as StaleFrozenPathDeferral. Dissolve-on: the freeze reaching empty — every resident carrying an exact admission with a declared purpose, cost and executing cadence — at which point the pass-through arm and this dir row delete together." - data wet_integration_offline_note: String = "Four wet-integration entries above (host_effect_apply, deploy_access_privilege, ci_deploy_observed_wet, random_bytes_csprng) are OFFLINE per the hermetic-floor posture (operator 2026-07-11: integration-style witnesses must not run regularly; CI runs hermetic/mocked behavior checks). Each performs genuinely live host effects the hermetic envelope refuses by construction: host_effect_apply drives real LocalShell/SSH host effects (the 133s row atop the slow-witness census), deploy_access_privilege applies privileged shell mutations through fixture actors, ci_deploy_observed_wet shells whoami + sudo -n, random_bytes_csprng reads host entropy. Hermetic duals stay discovered where they exist (the ci_deploy split keeps the pure half; deploy_access_check(access, actor) is the mock-shaped dual). Local recipe: claim_batch --wet --source-root dag --source-root src/v2 --entry per file. THE RECIPE ITSELF IS NO LONGER PROSE: gunbc.offline_local_recipe carries it as typed rows (entry, function selection, execution mode) and renders the argv from them, so this note keeps the RATIONALE and that carrier owns the COMMAND. The sentence above is retained as the historical form; it is not the authority, and it is measured unrunnable as written -- claim_batch refuses an invocation naming no function (offline_recipe_consumer_defects). Dissolve-on: the scheduled (nightly) lane un-darkens and admits wet integration rows under its own budget — then these four enroll there as declared execution rows and the exclusion rows delete." data known_red_frontier_note: String = "HOW THE KNOWN-RED FRONTIER WORKS, and the receipts it has accumulated. The rows themselves live at function grain on gunbc.explicit_witness_admission explicit_witness_admissions, each carrying its own diagnosis and dissolve_on; this note deliberately does NOT restate them (that would be the parallel-ledger duplication the rows exist to prevent) and carries only what is not a property of a live row. WHAT THEY ARE: witnesses red on main for feature gaps owned by named lanes. They are RED CONTROLS for their lanes, not broken tests - deleting one would discard the discriminating input, while leaving it discovered would red every wide PR on a debt that PR did not create. So each sits as a declared, countable row (the typed self-host-frontier pattern applied to witnesses). Local recipe per row: claim_batch --source-root dag --source-root src/v2 --entry --functions -- AND FOR AN ExecutionWitnessKind ROW, --wet AS WELL, WITHOUT WHICH THE RECIPE CANNOT DO WHAT THE ROW ASKS OF IT. claim_batch is hermetic by default, so a wet row invoked by the recipe as written above never reaches its subject: measured 2026-08-26 on self_host_logic_behavioral_receipt_holds, it returns FAIL in 87 seconds with 'hermetic route has no arm for Dir: the claim never reached its subject, so this is a route gap and not a verdict', naming three undeclared dispatches (Filesystem.Read, git.Inspect.Toplevel, shell.Mktemp.Dir). The refusal is well-modeled and says plainly that it is not a verdict; the defect was in the recipe, which every wet dissolution condition below cites as the way its row greens. A row whose only recorded closing move cannot close it is the shape DESIGN records for a gate whose sole green is the forbidden action -- here arrived at from the other side, with no green reachable at all. THE ROSTER MUST NOT GROW SILENTLY: a new admission requires a per-row reason naming the diagnosis and owner plus a dissolve_on, and a row whose witness runs green locally is stale and deletes. Greening is the counted un-quarantine event of std.witness_admission: the row deletes in the SAME change that lands the fix, which promotes the unchanged witness to ordinary DiscoverySelection as a permanent regression control (DESIGN 4b(4) - the climb deletes the quarantine machinery, never the evidence). UN-QUARANTINE RECEIPTS (history, not live rows). emit_host_fold_closure_equals_eval_test (2026-07-20, smart-hawk-154 / witness-realization lane 3): three rows deleted - the eval walk now materializes RuntimeClosure for an Arrow TypeNode in runtime-argument position (eval_fold_is_closure_argument_edge / eval_fold_closure_argument_child_state in 05_eval.dag), so the closure at transform child 3 of fold_fixture_param_call_body evaluates to a real closure value instead of falling through eval_type_node_atom's non-Atom arm into allocate_literal and refusing. The interpreted leg reads the closure from the EVALUATED argument list rather than re-walking the node AST, so eval is genuinely exercised, and no literal-atom roster was extended (the PR 6879 diagnosis-table tripwire). emit_on_demand_field_access_family_witness_test + emit_on_demand_complement_family_witness_test (2026-07-20, witty-owl-841 / eval-repair arc): ten rows deleted - dag_node_is_eval_literal_atom plus eval_type_node_atom bind-miss and non-Atom TypeNode arms routing to allocate_literal repaired the lane-2 equals_eval BuildFailed roots. emit_host_meet_join_equals_eval_test and emit_host_variant_construct_equals_eval_test greened as byproducts of the same consolidation and never held rows of their own. WET ASSEMBLE-RED SelfEmitted MODULE RECEIPTS (admitted 2026-07-25, review #7199) are the same frontier at a different WitnessKind: six SelfEmitted wet behavioral receipts red on assemble (unresolved std_algebra/std_types/std_nat/List and target_carriers type-shape, cadence run 30126573464), which run Wet expect_red on the falsifier quarantine batch because Phase-0 forbids SelfEmitted-with-zero-executing-consumer. As of 2026-08-03 they are ExecutionWitnessKind rows on the one admission authority beside the hermetic ones, and v2.compiler.self_host.seed_emitter_behavioral_wet_known_red_entries derives from it rather than holding its own copy. Owner: #7199 falsifier lane / self-host assemble." @@ -1106,10 +1147,8 @@ fn bin_wet(entry: String, f: String) -> ScheduleWitnessEntry { data v1_claim_scoped_witness_batch_deleted_note: String = "THE SCOPED WITNESS BATCH IS DELETED (2026-08-15), and with it the only ScopedWitnessBatch instance that ever existed. It ran five file-grain v1 claim entries under a dag plus src/v1 source-root envelope, in a sequential child worker, so the v1 compiler closure could be resolved without widening witness_layer_roots for everything else.\n\nWHY IT COULD NOT SURVIVE AFFECTED-SET SELECTION. It was the last SelectionApplied consumer on the required path, and its 720-second clamp was derived (PR #8259) from a measured 565-second wall on the assumption that unrelated PRs skip it. With selection deleted the two available readings were both wrong: run it every PR and the required floor grows by roughly nine minutes under a clamp basis that no longer describes it, or keep a selection mechanism alive for one batch. Its cost was also never really the witnesses — the same receipt measured about 390 seconds of closure materialization before the first witness line and about 30 seconds of witness evaluation, so it was paying a whole second subject preparation to check thirty rows.\n\nWHAT THIS NARROWS, stated rather than implied: the five entries under src/v1/tests/claim have NO executing consumer. They are not covered by ordinary discovery, whose roots are dag plus src/v2. Their generated stage0 emissions still compile into the seed crate, which is not execution. This is a real loss of coverage over v1 parser and namespace-binding behavior, taken deliberately as part of deleting the floor rather than discovered later, and it is NOT backstopped by any cadence — the cadence lane those rows might have moved to was refused for the same reason the per-PR slot was: a second subject envelope is the defect, not the schedule it runs on.\n\nRESTORATION: one prepared subject spanning dag, src/v1 and src/v2, at which point a separate envelope, a separate worker, a separate clamp and a separate receipt family all stop existing and these rows are ordinary discovery." - data witness_fold_src_v1_coverage_gap_note: String = "DECLARED COVERAGE GAP (operator ruling relayed 2026-08-15, at the re-add #1 boundary). The required witness fold runs `claim_executor --required-ci --source-root dag --source-root src/v2` (the flag was `--required-floor` until the 2026-08-20 consolidation folded parse, regen and the floor into one invocation; the source roots are unchanged, so this gap is unchanged too), so every witness under src/v1 is OUTSIDE it. Measured at declaration: 116 `test fn` declarations across 15 files under src/v1/tests/claim. Their previous consumer was the scoped witness batch deleted above; nothing has replaced it, and no cadence backstops them.\n\nWHY THE ONE-TOKEN FIX WAS REFUSED. Adding --source-root src/v1 would also admit the 52 non-test .dag modules under src/v1 into the ONE shared prepared subject the fold depends on. That subject is a single flat namespace per scope, and the v1 tree declares its own module population; poisoning the fold's first census with collisions to chase coverage costs more than the gap, and a first measurement taken against a subject nobody has run before is not a measurement worth having.\n\nWHY IT IS DECLARED RATHER THAN QUIET. run_required_floor refuses internally when planned, executed and terminal identity counts disagree, precisely so a narrowed roster cannot present as a roster. That wall operates INSIDE the subject; it cannot see a population the subject never admitted. So the same honesty has to be carried here, in prose, at the boundary: the fold runs every witness IN ITS SUBJECT, and its subject is two roots, not three.\n\nPOPULATION LOCATION IS BRANCH-DEPENDENT (relayed 2026-08-15, after this row was authored). The 15 files are present on main and DELETED on integration/v1-cut, where a src/v1/**/*.dag root deletion swept 16 witness .dag files under src/v1/tests/claim — this population — as collateral. So a reader checking main and a reader checking that cut branch will disagree about whether the gap has a subject at all, and the dissolve-on below is only satisfiable on a branch where the files still exist. A per-file disposition proposal (re-home under a surviving subject, or retire with a receipt naming the rejected behavior) is routed elsewhere and is deliberately NOT acted on here; several of the 16 witness LANGUAGE behavior rather than v1-compiler behavior (§4c annotation capture and erasure, the line-continuation operator-table derivation), so a blanket retire would drop evidence for obligations the replacement still claims. This row stays a declared gap until that proposal lands, at which point it either dissolves for real (files re-homed and inside the fold) or converts to a retirement receipt.\n\nDISSOLVE-ON: the boundary decision on these 15 files. The recommendation carried into that decision is RELOCATION into the dag test root rather than widening the root set — one tree, one namespace, consistent with every cut currently consolidating away from src/v1 — with the caveat that three sibling cuts are reshaping src/v1 concurrently and some of these 116 may die with them, so the relocation census is taken at the boundary and not before it." - data v1_dead_witness_tree_triage_receipt: String = "DISCHARGES witness_fold_src_v1_coverage_gap_note's routed-elsewhere per-file disposition proposal (dashboard node adhoc-9b80ec49-d63, session zesty-newt-828, 2026-08-18/19). COUNT CORRECTION: that note measured 116 test fn declarations across 15 files at 2026-08-15 declaration time; by the time this triage read the tree the population was 17 files (12 disposed directly in this receipt, plus 5 files — checkpoint_identity_keying_witness_test.dag, occurrence_binding_parser_walk_witness_test.dag, occurrence_identity_debt_receipt_test.dag, pattern_binder_declaration_node_test.dag, v1_match_pattern_identity_test.dag — dispositioned by a concurrent batch and not re-narrated here; ATTRIBUTION, confirmed against git show --stat 11113ac9e9: all 17 files, including these 5, were deleted in that ONE commit, one author (Brian Searls), on this same branch session/zesty-newt-828 — there is no separate sibling dashboard session to attribute for PR purposes, 'concurrent' here means a parallel work stream inside this session, not a different session's contribution; checkpoint_identity_keying_witness_test.dag alone is carved out to stern-fox-619's own PR per the binding scope exclusion, unaffected by this attribution). The 15-vs-17 gap is branch drift between 2026-08-15 and the triage read, not a miscount of either measurement.\n\nDISPOSITION, MIGRATED (relocated to dag/test/claim, rewritten onto compile_dag_rust_emit_check, running under ordinary discovery — source roots dag + src/v2, no v1 import): dag_parse_continuation_operator_witness_test.dag -> dag/test/claim/dag_parse_continuation_operator_witness_test.dag (8 of 9 tests migrated black-box; w_dual_role_exclusion_intersection_is_singleton_minus retired in-file, pure operator-table introspection with no compile-observable surface, its protected family covered black-box by the comparison-family test beside it). required_expr_newline_continuation_test.dag -> dag/test/claim/required_expr_newline_continuation_witness_test.dag (all 8 tests migrated 1:1, 5 parse-clean + 3 refuses-to-compile each paired against a proven-clean sibling). ordinary_frontend_observation_test.dag -> dag/test/claim/frontend_literal_and_compile_witness_test.dag (2 pure interpreter-literal tests moved unchanged, no v1 import ever needed; 2 compile-observation tests converted to compile_dag_rust_emit_check clean/broken-subject pairs). caret_parse_smoke_test.dag -> dag/test/claim/caret_syntax_witness_test.dag (3 black-box compile/emit-substring tests migrated; 7 white-box tokenizer/parser-internals tests retired in-file, no compile-observable equivalent). v1_annotation_target_emission_test.dag -> dag/test/claim/annotation_erasure_emission_witness_test.dag (the D-C property 4 erasure claim plus a non-vacuity control, both via compile_dag_rust_emit_check includes/excludes on emitted Rust text; not a byte-identical replay of the 2026-08-05 host-execution diff receipt, which stands as separate historical evidence).\n\nDISPOSITION, RETIRED (white-box, no compile-observable proxy for the same claim; deleted with this receipt as the record): v1_annotation_binding_test.dag, v1_annotation_capture_test.dag, v1_annotation_round_trip_test.dag, v1_annotation_erasure_test.dag — all four inspect v1-internal occurrence-identity/annotation-graph/span-provenance/node-fingerprint state directly (std.occurrence_identity, v1.compiler.annotation_bind, dag_node_surface_fingerprint) that has no representation in emitted Rust text; per DESIGN §4c, annotation capture/erasure is disjoint from semantic occurrence identity by construction, so these are facts about the FRONTEND's internal bookkeeping, not about a compiled program, and cannot be reduced to a compile_dag_rust_emit_check assertion without inventing a second v1-internal-state-reading surface this triage was not chartered to build. v1_annotation_erasure_test.dag's own erasure claim (prose does not leak into emitted output) is separately and already covered black-box by the migrated annotation_erasure_emission_witness_test.dag above — that file migrates v1_annotation_target_emission_test.dag, a distinct source, not this one; naming this precisely to avoid the false impression that this exact file was migrated rather than retired. v1_complexity_eviction_hazard_test.dag — retired with a live-defect note, not a clean retirement, structured as an explicit DESIGN §4b(3) declared bounded rung-drop rather than left as prose implying it: it documents a real fail-open in v1.compiler.complexity's evict_summary/lookup_summary (a §5 absorbing-fallback shape) that is orthogonal to emitted-Rust text and therefore not compile-observable, and the defect itself remains live and unfixed by this retirement. PREVIOUS RUNG: mechanically preventable — before v1_claim_scoped_witness_batch was deleted (2026-08-15), this file's assertions executed and would have caught a regression in the eviction fail-open shape. TEMPORARY RUNG: mitigatable — the fail-open itself is unrepaired, so this class sits at the floor rung DESIGN §5 names for an absorbing fallback (a typed, located, counted diagnostic is owed but not yet built); this retirement removes only the dead witness, it does not climb or lower the underlying defect's own rung. REASON: the witness's sole execution path (v1_claim_scoped_witness_batch) is gone and RESTORE (widening the required floor to admit src/v1) was measured and ruled out repo-wide in this same triage. RE-MEASURED 2026-08-19 (gunbc#8520, CI run 32219326621): the stall does NOT reproduce -- preparation completes in ~9 minutes and refuses with 532 typed located diagnostics, none of them in src/v1. The ruling stands on a different cause (twelve last-segment module collisions between src/v1 and src/v2 breaking bare cross-module references that resolve only by pool coincidence), and the trigger is namespace-only resolution rather than any floor speedup. The single corrected measurement lives in v1_dead_witness_tree_triage_receipt_remainder; this row cites it rather than restating it. RESTORATION TRIGGER: a v1-compiler-local cargo test (v1 is semantics-frozen with active maintenance per gunbc.v1_maintenance_standing, and a local Rust-side regression test is an admitted maintenance-class change under that standing, unlike widening the required floor's source roots) — or, separately and preferably, root-causing the fail-open itself into a typed refusal per §5, which would obsolete this witness's assertions rather than merely restore them. namespace_reference_derived_closure_production_admissions_witness_test.dag — retired: white-box, matched ReferenceBindingObservation variant shapes directly rather than compile outcomes; its own fixtures were already documented in-file as illustrative-only and not satisfying the namespace-structural-observations closing contract (see below).\n\nOUT OF SCOPE, NOT A WITNESS, LEFT IN PLACE: src/v1/gunbc/namespace_reference_derived_closure_production_observations.dag is a v1 PRODUCTION module (four ReferenceBindingObservation producers for the namespace-reference-derived-closure clauses a-d), not a test file, so it is outside this triage's 17-file population. Its only historical test consumer was the retired namespace_reference_derived_closure_production_admissions_witness_test.dag above, so it currently has zero executing consumers and zero test coverage — but it is not dead code: gunbc.roadmap_authority's namespace_reference_derived_closure_execution_contract (an open WorkItemExecutionContract, enrolled QuarantineProbeExpectRed) names 'the gated node' supplying production observations for clauses a-d as the precondition for that contract to green, and this module's namespace_structural_binding_observations_a_through_d export is the designated future producer for exactly that seam. Retiring it would delete a still-claimed future authority, not dead scaffolding; it is left in place with this receipt recording its coverage gap rather than silently carrying it.\n\nRECEIPT COMPLETENESS: undispositioned files = 0 among the 12 handled directly here; retired files' evidence is deleted with them (no discriminating claim survives that a compile-observable check could re-prove); migrated files' evidence is the new dag/test/claim files enumerated above, executing under ordinary per-PR discovery. witness_fold_src_v1_coverage_gap_note's dissolve-on ('the boundary decision on these 15 files... RELOCATION into the dag test root rather than widening the root set') is satisfied by this receipt for the population it could reach; that note is retained rather than deleted because it is still the correct historical account of why the gap existed and how the fold's subject is bounded, and because the 5-file concurrent-batch population is disposed under a separate session-local record this row does not restate." data v1_dead_witness_tree_triage_receipt_remainder: String = "DISCHARGES the remainder of v1_dead_witness_tree_triage_receipt's routed-elsewhere concurrent-batch population (dashboard node adhoc-9b80ec49-d63, session zesty-newt-828, 2026-08-19) for 4 of the 5 files that receipt named but declined to narrate: occurrence_binding_parser_walk_witness_test.dag, occurrence_identity_debt_receipt_test.dag, pattern_binder_declaration_node_test.dag, v1_match_pattern_identity_test.dag. The 5th named file, checkpoint_identity_keying_witness_test.dag, is explicitly OUT OF SCOPE for this row — a sibling session (stern-fox-619) migrates it atomically in its own PR and this receipt does not touch it. All 4 files here share the same root cause as the 12-file receipt above: their only historical consumer, v1_claim_scoped_witness_batch, was deleted 2026-08-15, so none of their assertions had executed since. RESTORE (widening the required floor's source roots to include src/v1) is RULED OUT, and the REASON WAS RE-MEASURED 2026-08-19 because the original one no longer holds. ORIGINAL RECEIPT, retained as the record of what was true then: claim_executor --required-floor with --source-root src/v1 added stalled 8+ minutes in strict-preparation, RSS plateaued ~7.2GB, zero phase progress, killed. THAT NO LONGER REPRODUCES. Re-run on gunbc#8520 (CI run 32219326621, srv1-04, floor step 05:27:35Z-05:36:41Z): preparation ran to COMPLETION in ~9 minutes, no stall, no kill, and refused with 532 typed located diagnostics across 120 files. The performance objection is stale -- preparation cost fell out from under it the night before, when gunbc#8465 unblocked the prepare_grammar memo for every language and gunbc#8487 replaced 38 left-corner closures with one converging relation (measured: first row of a parse-bearing module 10890ms -> 1106ms). THE REAL BLOCKER IS NAME RESOLUTION, NOT COST, and it is sharper than the old receipt could see. ZERO of the 532 diagnostics are in src/v1: 525 are in src/v2 and 7 in dag/test. src/v1 and src/v2 share NO exact module path, but their module paths collide on the LAST SEGMENT in twelve places -- artifact, coercion, common, compile, complexity, emit, infer, normalize, ownership, parse, resolve, tokenize. Files carrying BARE cross-module references (v2.compiler.program_assembly calls tokenize at line 28 with no import for it) resolve today only by pool-membership coincidence; adding src/v1 supplies a second module whose last segment matches and the reference stops resolving. That is the Class B failure DESIGN's import-strip cascade thread already names, reached from the opposite direction. DISSOLVE-ON: this ruling is reconsidered when bare cross-module references bind by containment rather than by pool coincidence -- the namespace-only resolution lane -- NOT when the floor gets faster. Consequence to state plainly rather than leave implicit, CORRECTED 2026-08-22 and narrowed to what is still true: src/v1's .dag sources have NO per-PR TYPECHECK gate, since compile-clean was deleted with the floor cut and regen is red by construction, so a type or resolution defect inside src/v1 is invisible to CI until the namespace-only resolution lane lands. They DO have a per-PR PARSE gate, and have had one since 2026-08-19: the required run executes a src/v1 .dag parse sweep on every pull request and every push. The mechanism CHANGED on 2026-08-20 and the current one is the only one to cite -- the sweep was folded into claim_executor's --required-ci mode as an IN-PROCESS PHASE calling the same cli_run walk, so there is no separate step and no standalone binary in the job. One step, gunbc.witness_floor_workflow's emitted 'Required CI: parse, regen, witness floor', runs claim_executor --required-ci over source roots dag and src/v2, and its parse phase reports in the form 'parse OK N file(s) parse-clean'. WIDENED 2026-08-23: that phase swept src/v1 ALONE until then, so the 3831 authored .dag files under dag/ and src/v2 had no parse-grain gate cheaper than the floor itself, and it now sweeps all three roots from one roster (v1_compiler.cli_run DAG_PARSE_SWEEP_ROOTS) with per-file source-annotation admission, in about ten seconds. THIS DOES NOT TOUCH THE RULING ABOVE and does not weaken it: RESTORE is about the floor's SOURCE ROOTS, whose blocker is name resolution across a shared pool, and the sweep resolves nothing -- each file is tokenized, parsed and annotation-admitted in isolation, so no module of one root is ever visible to another. The count in the reported form is left as N rather than transcribed, because a pinned count here is a positional citation of a run's output and rots without anyone touching it. gunbc.witness_floor_workflow's own annotation on witness_floor_required_bins records the removal and why, and test.claim.witness_floor_workflow_consolidation_witness_test asserts by execution that the emitted yml contains NEITHER the retired step name NOR the retired binary -- so a citation naming either is refused by a live witness, not merely stale. This clause previously read that src/v1 had NO per-PR gate at all and that a PARSE ERROR there was invisible to CI. That was true when written and the 2026-08-19 sweep falsified it, so it is rewritten here rather than annotated -- a second sentence beside it would be two accounts of one fact, which is the defect this document exists to refuse. The distinction the corrected form turns on is PARSE versus TYPECHECK: the gap is real and the DISSOLVE-ON above is unaffected, but it is one phase narrower than this row claimed. Recorded because the overstatement had already been relayed onward as fleet guidance before it was caught, which is the cost of a stale carrier -- it is read and believed by people with no reason to re-derive it. And recorded twice over, because the FIRST attempt at this very correction cited the 2026-08-19 step and binary as though they still existed: that evidence was read out of a worktree 146 commits behind main, and the retired mechanism was reported as the live one. A correction is a high-risk edit -- it carries the authority of having just checked -- and this one reproduced its own subject's defect at one remove. Caught by review, not by me, the same way the consolidation annotation two files over records ITS author being caught. The general form: when a mechanism has CHANGED rather than merely aged, a stale reader sees a working mechanism and has no signal that it is the wrong one.\n\nDISPOSITION, MIGRATED (partial — see dag/test/claim/match_arm_pattern_identity_emission_witness_test.dag's own migration note for the full accounting): v1_match_pattern_identity_test.dag. 2 of its 4 assertions (distinct-literal-pattern and distinct-variant-name fingerprint distinctness) migrate onto compile_dag_rust_emit_check as a black-box 'every arm's marker string survives, distinctly, to emitted Rust text' check. This is a genuine emitter-behaviour defect class, not merely v1-internal bookkeeping: the file's own note documents that Node.match_pattern was once missing from both v1 identity readers (dag_collect_support.dag_node_surface_fingerprint_rec, the content fingerprint used by compile.dag's DAG artifact node table, and dag_collect.dag_collect_node_tree, the DAG artifact collector), so two match arms differing only by pattern could silently alias to one cache identity and the second arm's body would be dropped or duplicated in emitted output — exactly what the migrated black-box check would catch. Grep confirmed dag_node_surface_fingerprint has zero consumers anywhere outside src/v1, so no wider corpus-side coverage already existed for this defect class; DESIGN.md's own still-open '§3 convergence thread — fnv1a64 dual surface' note tracks the underlying dual-representation defect (dag_node_surface_fingerprint_rec calling raw v1 intrinsics rather than std.content_hash) as unresolved, so this migration is consistent with, not superseding, that open thread. The remaining 2 assertions (w_variant_pattern_field_bindings_are_registered_as_dag_artifacts, w_unrelated_field_binding_is_not_registered) retire in-file within the new witness's own migration note: they test dag_collect_insert_slots/dag_node_key directly against a hand-built Node, asserting DAG-artifact-TABLE registration/membership — bookkeeping with no representation in a single compiled program's emitted text, since compile_dag_rust_emit_check observes one compile's output rather than the incremental artifact table's contents. Restoration path if that registration surface needs live regression coverage again: a v1-compiler-local cargo test, an admitted maintenance-class change under gunbc.v1_maintenance_standing's active-maintenance arm.\n\nDISPOSITION, RETIRED (white-box, no compile-observable proxy for the same claim; deleted with this receipt as the record): occurrence_binding_parser_walk_witness_test.dag — its own in-file governed-placement note already states dag/test/claim is structurally invalid as this file's home ('ordinary discovery resolves against witness_layer_roots = [dag, src/v2], where v1.compiler imports cannot resolve'), because its whole subject is proving the REAL v1 parser (v1.compiler.parse via v1.gunbc.occurrence_binding_parser_walk) feeds correctly into std.occurrence_binding_candidates's structural-candidate resolution — an internal OccurrenceTransport/candidate-index fact, not an emitted-text fact. That underlying resolution logic (resolve_reference_binding_via_structural_candidates, occurrence_candidate_index_build, structural_binding_walk) already has independent black-box coverage via hand-built transports in dag/test/claim/occurrence_binding_candidates_witness_test.dag and dag/test/claim/occurrence_binding_resolve_witness_test.dag — confirmed by grep, both files exist and both exercise the same core functions — so retiring this file loses only the narrower 'fed by the real production parser rather than a hand-built transport' fidelity, not the underlying resolution claim. The file's own dissolve-on ('witness_layer_roots admits src/v1 for parser-transport witnesses without Node ambiguity, or execution corpus carries per-entry source roots') cannot fire given RESTORE's repo-wide non-viability, so it is retired rather than left standing per DESIGN §4b(4). occurrence_identity_debt_receipt_test.dag — same reasoning: its own in-file note (parser_reaching_witness_governed_placement_note) states plainly that dag/test/claim is invalid for it, since ordinary discovery cannot resolve its v1.compiler.tokenize / v1.compiler.parse.parse_with_table_in_occurrence_scope imports. Its four assertions (rebuilt reference identity preservation across a parse, structurally-equal-but-distinct OccurrenceIds from two parses sharing an allocator, pattern-binder reachability across 5 binder shapes, same-spelling sibling-scope isolation) are all facts about the parser's internal allocator/containment bookkeeping — frontend internal state, not compiled-program output — with no compile_dag_rust_emit_check translation without inventing a second v1-internal-state-reading surface this triage was not chartered to build, the identical reasoning the 12-file receipt above already applied to the four v1_annotation_*_test.dag retirements. pattern_binder_declaration_node_test.dag — retired whole rather than split: its own defunct-enrollment note (pattern_binder_declaration_node_parser_transport_enrollment) already names the same deleted v1_claim_scoped_witness_batch/scoped_witness_batches/ci_floor_plan machinery as its only historical execution path. Its 13 tests split into two subjects, both white-box: (a) 4 tests calling v1.compiler.parse.parse_pattern/parse_with_table directly on raw fragments to check dedicated Bind-declaration-node materialization, shorthand/declaration-role distinctness, let-initializer lexical-reference-category retention, and sequential-parse ordinal-space disjointness/preservation-under-parse-error — parser-internal AST-shape and allocator facts, not emitted-text facts; and (b) 9 w_production_frontend_* tests hand-constructing synthetic OccurrenceTransport/OccurrenceIndexEntry/DeclarationOccurrence/ReferenceOccurrence fixtures with hand-set OccurrenceIds to assert v1.compiler.compile.resolve_frontend_occurrence_transport accepts a valid transport and refuses 6 exact malformed shapes with exact typed diagnostic variants and spans — a construction-wall correctness test over a purely internal validation function's input shape, with no parser and no emission involved at all. Neither subject reduces to 'this source compiles' / 'this substring is in the emitted text'; restoration path for either, if needed again, is the same v1-compiler-local cargo test route named above.\n\nRECEIPT COMPLETENESS: undispositioned files = 0 among these 4; migrated file's evidence is dag/test/claim/match_arm_pattern_identity_emission_witness_test.dag, executing under ordinary per-PR discovery; retired files' evidence is deleted with them, since no discriminating claim among them survives that a compile-observable check could re-prove without inventing new v1-internal-state-reading machinery this triage was not chartered to build. Together with v1_dead_witness_tree_triage_receipt above (12 files) and stern-fox-619's separate T7 PR (checkpoint_identity_keying_witness_test.dag), this receipt closes out the full 17-file population witness_fold_src_v1_coverage_gap_note originally measured (adjusted for branch drift, per that receipt's own count correction)." @@ -1224,7 +1263,20 @@ data bin_witness_wet_entries: List = [ data bin_witness_wet_per_row_wall_budget_seconds: Int = 60 -data bin_witness_wet_per_row_budget_note: String = "Short/quick-witness discipline for the per-PR wet batch (CI floor endgame D6, operator ruling 2026-07-23): per-PR CI is short/quick witnesses, and a bin-execution row's subprocess wall is invisible to the fast-lane 5-second EVAL deadline by construction (the deadline counts interpreter CPU; child-process wall is host-effect time), so the bin lane carries its own per-row wall budget — this datum. A row whose MEASURED wall exceeds it re-homes to the falsifier wet cadence as a typed frontier row (falsifier_rehomed_bin_wet_rows below, reason + dissolve_on per row), keeping the remaining rows as the per-PR smoke subset so plumbing-diff PRs retain a discriminating signal. Classification basis: run 30009199696's per-row cost table (the 54-row pool's 561s eval was 94 percent three rows: floor_skip_discovery 289s, cross_shard_seam live-tree 183s, dag_compile_clean_perturb 53s — the third is under budget and stays). Enforcement layering: this datum is the classification rule applied at review; the EXECUTABLE wall against silent regrowth is the batch clamp (gunbc.ci_spec gunbc_ci_floor_batch_clamp_params — a new heavy row grows the wet batch's unit count and can blow its derived clamp, reding with a typed refusal), so an over-budget enrollment cannot ride silently even if review misses it." +// Short/quick-witness discipline for the per-PR wet batch (CI floor endgame D6, operator ruling +// 2026-07-23): per-PR CI is short/quick witnesses, and a bin-execution row's subprocess wall is +// invisible to the fast-lane 5-second EVAL deadline by construction (the deadline counts +// interpreter CPU; child-process wall is host-effect time), so the bin lane carries its own per-row +// wall budget — this datum. A row whose MEASURED wall exceeds it re-homes to the falsifier wet +// cadence as a typed frontier row (falsifier_rehomed_bin_wet_rows below, reason + dissolve_on per +// row), keeping the remaining rows as the per-PR smoke subset so plumbing-diff PRs retain a +// discriminating signal. Classification basis: run 30009199696's per-row cost table (the 54-row +// pool's 561s eval was 94 percent three rows: floor_skip_discovery 289s, cross_shard_seam live-tree +// 183s, dag_compile_clean_perturb 53s — the third is under budget and stays). Enforcement layering: +// this datum is the classification rule applied at review; the EXECUTABLE wall against silent +// regrowth is the batch clamp (gunbc.ci_spec gunbc_ci_floor_batch_clamp_params — a new heavy row +// grows the wet batch's unit count and can blow its derived clamp, reding with a typed refusal), so +// an over-budget enrollment cannot ride silently even if review misses it. type RehomedBinWetRow { entry: String @@ -1752,7 +1804,15 @@ fn falsifier_substrate_long_lane_entries() -> List { }) } -data falsifier_substrate_long_lane_batch_schedule_note: String = "Batch 6 roster derivation: legacy falsifier_substrate_long_lane_rows plus explicit_witness_admission_substrate_long_lane_roster, identity-joined (witness_long_eval_budget_union_in) — this is the exact-identity bounded population falsifier_substrate_long_lane_note's CADENCE STANDING paragraph cites, not a currently-executing schedule (falsifier.yml, batch 6's sole invoker, was deleted at 611fd02770/#8283). Declared-long-budget admission rows join here without joining falsifier_substrate_long_lane_rows — falsifier_substrate_long_lane_entries stays the legacy roster only so declared_long_budget_reaches_the_ceiling_roster_without_the_batch_roster can refuse dual enrollment." +// Batch 6 roster derivation: legacy falsifier_substrate_long_lane_rows plus +// explicit_witness_admission_substrate_long_lane_roster, identity-joined +// (witness_long_eval_budget_union_in) — this is the exact-identity bounded population +// falsifier_substrate_long_lane_note's CADENCE STANDING paragraph cites, not a currently-executing +// schedule (falsifier.yml, batch 6's sole invoker, was deleted at 611fd02770/#8283). +// Declared-long-budget admission rows join here without joining falsifier_substrate_long_lane_rows +// — falsifier_substrate_long_lane_entries stays the legacy roster only so +// declared_long_budget_reaches_the_ceiling_roster_without_the_batch_roster can refuse dual +// enrollment. fn falsifier_substrate_long_lane_batch_schedule_entries() -> List { witness_long_eval_budget_union_in( @@ -1761,7 +1821,39 @@ fn falsifier_substrate_long_lane_batch_schedule_entries() -> List, diff --git a/dag/gunbc/ci/ci_materialization.dag b/dag/gunbc/ci/ci_materialization.dag index 4655fd8a10d..0345100ca61 100644 --- a/dag/gunbc/ci/ci_materialization.dag +++ b/dag/gunbc/ci/ci_materialization.dag @@ -1,6 +1,5 @@ module gunbc.ci_materialization - import std.types { Bool, Int, List, String } import std.nat { Nat } import std.disposition { Disposition, Scaffold, RealizationDispatch, SingleAuthority } @@ -24,10 +23,34 @@ import gunbc.floor_materialization { floor_entry_walk_memo_provider_id } import v2.std.algebra { any, for_all } import std.measure { Nanosecond, nanosecond, nanosecond_count } - -data ci_materialization_note: String = "The worked example the ladder was built for (operator, 2026-07-08): CI emits shell which runs the gunbc binary, and the runs in shell are one frame above the runs in the binary — not fundamentally different, since both are emitted by the same program. This module models the emitted GitHub Actions workflow as a frame tree (workflow = isolated children; each job = a shared-state frame of steps), extracts every RunStep script as a FrameDemand at its site, and declares the cache providers that discharge the obligations the ladder finds. The flagship: the release build script is demanded by THREE sibling jobs (ci, ci_regen, rust_tests warm) whose LCA is the workflow isolation boundary — unrewireable redundancy, so it MUST be discharged by a covering store-tier provider; that provider is the sccache row below, and the shell fragment that realizes it in the emitted ci.yml is DERIVED from this module (ci_sccache_provider_shell_injection), so deleting the provider row would orphan the injection, and deleting the injection while the row stands is drift the witness reds. Grain honesty: identity is the step's run-script string — deploy scripts EMBED the release build script as a superset string and do not group with it at this grain; sub-step decomposition arrives when demands derive from the DependencyView rather than emitted argv (the forward-wiring thread). UsesStep actions are foreign computations and carry no demand row yet; their caching (actions/cache) is itself a provider to model when uses-grain demands land." - -data ci_fleet_frame_note: String = "The fleet frame (recurring CI invocations over time) is DEFINED but not yet in live sites: UnboundedSiblingsFrame is declared-emergent, so enrolling it obligates a cross-run provider for EVERY pure step up front — exactly right, and exactly what the floor does not have yet (the affected-set / floor-memoization thread; the only cross-run providers live today are sccache and actions/cache, which cover the toolchain, not the floor). Enrolling ci_fleet_frame into ci_step_site is the ratchet flip that FORCES the cross-run floor provider — the typed dissolve-on trigger for the C2 census rows in docs/plans/duplicate-work-graph-lens-design.md section 10a. Until then the cross-run redundancy is state-4 unexpected-emergent only in the formal sense that the model has not yet declared it; the design doc names it, so it is prepared-for in prose and unprepared-for in the graph — the gap this note keeps countable." +// The worked example the ladder was built for (operator, 2026-07-08): CI emits shell which runs the +// gunbc binary, and the runs in shell are one frame above the runs in the binary — not +// fundamentally different, since both are emitted by the same program. This module models the +// emitted GitHub Actions workflow as a frame tree (workflow = isolated children; each job = a +// shared-state frame of steps), extracts every RunStep script as a FrameDemand at its site, and +// declares the cache providers that discharge the obligations the ladder finds. The flagship: the +// release build script is demanded by THREE sibling jobs (ci, ci_regen, rust_tests warm) whose LCA +// is the workflow isolation boundary — unrewireable redundancy, so it MUST be discharged by a +// covering store-tier provider; that provider is the sccache row below, and the shell fragment that +// realizes it in the emitted ci.yml is DERIVED from this module +// (ci_sccache_provider_shell_injection), so deleting the provider row would orphan the injection, +// and deleting the injection while the row stands is drift the witness reds. Grain honesty: +// identity is the step's run-script string — deploy scripts EMBED the release build script as a +// superset string and do not group with it at this grain; sub-step decomposition arrives when +// demands derive from the DependencyView rather than emitted argv (the forward-wiring thread). +// UsesStep actions are foreign computations and carry no demand row yet; their caching +// (actions/cache) is itself a provider to model when uses-grain demands land. + +// The fleet frame (recurring CI invocations over time) is DEFINED but not yet in live sites: +// UnboundedSiblingsFrame is declared-emergent, so enrolling it obligates a cross-run provider for +// EVERY pure step up front — exactly right, and exactly what the floor does not have yet (the +// affected-set / floor-memoization thread; the only cross-run providers live today are sccache and +// actions/cache, which cover the toolchain, not the floor). Enrolling ci_fleet_frame into +// ci_step_site is the ratchet flip that FORCES the cross-run floor provider — the typed dissolve-on +// trigger for the C2 census rows in docs/plans/duplicate-work-graph-lens-design.md section 10a. +// Until then the cross-run redundancy is state-4 unexpected-emergent only in the formal sense that +// the model has not yet declared it; the design doc names it, so it is prepared-for in prose and +// unprepared-for in the graph — the gap this note keeps countable. fn ci_fleet_frame() -> Frame { Frame { name: "github-actions-ci-runs", kind: UnboundedSiblingsFrame } @@ -115,7 +138,16 @@ fn workflow_scripts_named(w: Workflow, names: List) -> List { ) } -data ci_uses_step_silence_note: String = "Denominator, not silence (the complexity-lens false-clean lesson, operator 2026-07-10): UsesStep actions are foreign computations this gate does NOT analyze — checkout, toolchain setup, cargo cache restore. Producing no demand row for them is a KNOWN blind spot, so it is COUNTED and pinned by the enrolled witness: a new foreign action entering the workflow must consciously bump the pinned count, never slide in unexamined. Dissolve-on: uses-grain demands (each action's cited cache behavior modeled, actions/cache as a provider row), at which point the count returns to zero and the pin becomes a wall. Nature default is the WEAKEST claim: an unrostered run-step is IdempotentEffect (never PureComputation — purity is not provable from an argv string; overclaiming it would let a misdeclared WorldRead be legally memoized). Verdict-identical under the ladder, honest under section 5." +// Denominator, not silence (the complexity-lens false-clean lesson, operator 2026-07-10): UsesStep +// actions are foreign computations this gate does NOT analyze — checkout, toolchain setup, cargo +// cache restore. Producing no demand row for them is a KNOWN blind spot, so it is COUNTED and +// pinned by the enrolled witness: a new foreign action entering the workflow must consciously bump +// the pinned count, never slide in unexamined. Dissolve-on: uses-grain demands (each action's cited +// cache behavior modeled, actions/cache as a provider row), at which point the count returns to +// zero and the pin becomes a wall. Nature default is the WEAKEST claim: an unrostered run-step is +// IdempotentEffect (never PureComputation — purity is not provable from an argv string; +// overclaiming it would let a misdeclared WorldRead be legally memoized). Verdict-identical under +// the ladder, honest under section 5. fn job_uses_steps_count(job: Job) -> Int { job.steps |> filter(s => !step_is_run(s: s)) |> count() @@ -125,7 +157,19 @@ fn workflow_uses_steps_count(w: Workflow) -> Int { w.jobs |> fold(init: 0, f: (acc, j) => acc + job_uses_steps_count(job: j)) } -data ci_run_step_nature_claim_note: String = "Run-step natures are CLAIMS, not derivations — counted (operator ruling, 2026-07-10: hardcoded shell scripts get no affordances; run-steps are supposed to be modeled as typed intents and EMITTED — docs/plans/shell-emission-model.md, where the ci_workflow RunSteps are slice 4 of the pre-runtime census). Until that slice lands, every run-step script in this workflow is an authored bash string, so NO nature in job_run_demands is read off a modeled effect row: FreshEffect is a rostered claim (resolved by projection at gate time) and IdempotentEffect is the weakest default. This count pins the whole claimed-nature surface as residue: a NEW raw-script step must consciously bump the pin — it cannot slide in with a silently defaulted nature — and the slice-4 migration becomes a countable ratchet. Dissolve-on: run-steps authored as typed intents whose effect row supplies the nature; the count then returns to zero and the pin becomes a wall against new raw scripts. Rejected alternative (same ruling): a per-step declared-natures roster — an affordance that makes the raw-string state comfortable, a second parallel ledger over scripts the shell-emission plan already governs." +// Run-step natures are CLAIMS, not derivations — counted (operator ruling, 2026-07-10: hardcoded +// shell scripts get no affordances; run-steps are supposed to be modeled as typed intents and +// EMITTED — docs/plans/shell-emission-model.md, where the ci_workflow RunSteps are slice 4 of the +// pre-runtime census). Until that slice lands, every run-step script in this workflow is an +// authored bash string, so NO nature in job_run_demands is read off a modeled effect row: +// FreshEffect is a rostered claim (resolved by projection at gate time) and IdempotentEffect is the +// weakest default. This count pins the whole claimed-nature surface as residue: a NEW raw-script +// step must consciously bump the pin — it cannot slide in with a silently defaulted nature — and +// the slice-4 migration becomes a countable ratchet. Dissolve-on: run-steps authored as typed +// intents whose effect row supplies the nature; the count then returns to zero and the pin becomes +// a wall against new raw scripts. Rejected alternative (same ruling): a per-step declared-natures +// roster — an affordance that makes the raw-string state comfortable, a second parallel ledger over +// scripts the shell-emission plan already governs. fn job_run_steps_count(job: Job) -> Int { job.steps |> filter(s => step_is_run(s: s)) |> count() @@ -146,7 +190,24 @@ data ci_cost_floor_exempt_step_names: List = [ data ci_native_cache_root_exempt_note: String = "Fourth exempt entry (CI2-0 capsule 1, 2026-08-08): the GUNBC_NATIVE_CACHE_ROOT derivation now runs in build, regen, AND ci — GITHUB_ENV is job-scoped, so each job that spawns native emit-host transports must derive its own root, and the three sites' LCA is the workflow isolation boundary. The script is one rustc -V plus a mkdir and a GITHUB_ENV append — a sub-second environment write whose store-tier discharge would cost more than the recompute it saves, the exact class the first two roster entries name. Dissolve-on: the same typed-toolchain-probe trigger the step's own 🟡 marker carries (#5828 / shell→intent Phase 2)." -data ci_sccache_provider_note: String = "C0 (operator ruling, 2026-07-09): this provider row is DERIVED from the cited catalog, never hand-typed. extdeps.cache.sccache owns what sccache IS (NativeInternal content hashing over RustcInvocation+ToolchainSpec, PerHostFilesystem locality, and a cited retention: a finite on-disk ceiling with least-recently-used replacement); this module owns only the ROLE: scope (whole tree, covers every LCA including the fleet frame - why the release build is already cross-run warm while the floor is not) and coverage (the release build script). WHAT THIS ROW USED TO REST ON, and no longer does (2026-08-06): the sentence here read Lru => SpacePacked, sound to drop, costs one recompile - an inference from a replacement ORDER to a space GUARANTEE, which the old fused eviction axis made silently for every Lru row. A least-recently-used order says which entry goes first when the store is full; it does not establish that the store is ever full rather than growing without limit. The boundedness now comes from the fact that actually carries it: sccache v0.15.0 Configuration.md documents SCCACHE_CACHE_SIZE as the maximum size of the local on-disk cache (default 10G), pinned at the modeled binary version rather than main, so the row projects through CapacityBounded with LeastRecentlyUsed replacement. The effective ceiling stays LimitUnobserved because a runner may override it and none was read. A catalog correction still flows into this gate with zero edits here, but eligibility is now ASKED rather than minted by the projection, and a row that stopped establishing a ceiling would refuse here, counted, never a silent shrink of the provider list." +// C0 (operator ruling, 2026-07-09): this provider row is DERIVED from the cited catalog, never +// hand-typed. extdeps.cache.sccache owns what sccache IS (NativeInternal content hashing over +// RustcInvocation+ToolchainSpec, PerHostFilesystem locality, and a cited retention: a finite +// on-disk ceiling with least-recently-used replacement); this module owns only the ROLE: scope +// (whole tree, covers every LCA including the fleet frame - why the release build is already +// cross-run warm while the floor is not) and coverage (the release build script). WHAT THIS ROW +// USED TO REST ON, and no longer does (2026-08-06): the sentence here read Lru => SpacePacked, +// sound to drop, costs one recompile - an inference from a replacement ORDER to a space GUARANTEE, +// which the old fused eviction axis made silently for every Lru row. A least-recently-used order +// says which entry goes first when the store is full; it does not establish that the store is ever +// full rather than growing without limit. The boundedness now comes from the fact that actually +// carries it: sccache v0.15.0 Configuration.md documents SCCACHE_CACHE_SIZE as the maximum size of +// the local on-disk cache (default 10G), pinned at the modeled binary version rather than main, so +// the row projects through CapacityBounded with LeastRecentlyUsed replacement. The effective +// ceiling stays LimitUnobserved because a runner may override it and none was read. A catalog +// correction still flows into this gate with zero edits here, but eligibility is now ASKED rather +// than minted by the projection, and a row that stopped establishing a ceiling would refuse here, +// counted, never a silent shrink of the provider list. fn ci_sccache_eligibility() -> CatalogProviderEligibility { provider_for_capacity_bounded_role( @@ -181,11 +242,35 @@ data ci_floor_resolve_receipt_note: String = "The counted cold-resolve receipt o data ci_floor_resolve_receipt_path: String = "target/floor-resolve-receipt.txt" -data ci_floor_admission_capture_resolve_receipt_note: String = "RECEIPT 7 — CONSCIOUS RAISING 1 -> 2 (2026-07-31, first capture placement): the production WalkPlan run with the old declaration refused exactly `floor resolve count 2 differs from declared 1` and wrote target/floor-resolve-receipt.txt with `resolves_total=2`, `resolve_ms_total=64486`. The then-ordinary first-batch capture_tested_subject claim was one new entry class whose closure escaped the compile anchor. The two non-heavy success stages use a separate entry and may each resolve it; those resolves belong to the attempt-scoped on-success receipt, not the ordinary-floor count. This was the empirical law above operating as designed, not a guessed increment. The same red run proved the safety boundary: finalization refusal left `0 of 2` success stages run. Receipt 8 records why the capture placement and this temporary raise were later retired." - -data ci_floor_declared_resolve_count_obligation_note: String = "SEMANTICS (shared CI floor item 0B, operator ruling 2026-08-04): this row names the CLOSED UNIVERSE of semantic resolve OBLIGATIONS the floor roster structurally owes each run — compile-anchor-whole-tree plus native-bundle-escaping-entry — NOT the count of physical cold resolves (resolve_nanos > 0). THE LAW (single authority here, observed by claim_executor, transported by ci_floor_plan only): expected obligation identities == observed realization obligation identities. Warm shared-pool satisfaction is SatisfiedFromSharedPool with provider + receipt, never absence. cold_resolves_total in target/floor-resolve-receipt.txt is observational only. STANDING CONSTRAINT: stays at 2 until identity-based finalization REPLACES it via a conscious roster change in the PR that adds or removes an escaping entry class." - -data ci_floor_resolve_obligation_authority_note: String = "AUTHORITY SPLIT (0B): gunbc.ci_materialization owns ci_floor_expected_resolve_obligations and resolve_realization_finalization_holds. claim_executor records ResolveRealization observations at reuse sites using the same variant names as ResolveRealization (ColdResolvePerformed | SatisfiedFromSharedPool) and joins observed rows to the obligation population transported in WalkPlan.finalization.expected_resolve_obligations — no HAND-Rust subject literals, no timing-derived disposition. ci_floor_plan transports ci_floor_expected_resolve_obligations() unchanged into FloorFinalization only. Dissolve-on: witness-realization P4 decodes modeled .dag observations instead of seed re-authoring." +// RECEIPT 7 — CONSCIOUS RAISING 1 -> 2 (2026-07-31, first capture placement): the production +// WalkPlan run with the old declaration refused exactly `floor resolve count 2 differs from +// declared 1` and wrote target/floor-resolve-receipt.txt with `resolves_total=2`, +// `resolve_ms_total=64486`. The then-ordinary first-batch capture_tested_subject claim was one new +// entry class whose closure escaped the compile anchor. The two non-heavy success stages use a +// separate entry and may each resolve it; those resolves belong to the attempt-scoped on-success +// receipt, not the ordinary-floor count. This was the empirical law above operating as designed, +// not a guessed increment. The same red run proved the safety boundary: finalization refusal left +// `0 of 2` success stages run. Receipt 8 records why the capture placement and this temporary raise +// were later retired. + +// SEMANTICS (shared CI floor item 0B, operator ruling 2026-08-04): this row names the CLOSED +// UNIVERSE of semantic resolve OBLIGATIONS the floor roster structurally owes each run — +// compile-anchor-whole-tree plus native-bundle-escaping-entry — NOT the count of physical cold +// resolves (resolve_nanos > 0). THE LAW (single authority here, observed by claim_executor, +// transported by ci_floor_plan only): expected obligation identities == observed realization +// obligation identities. Warm shared-pool satisfaction is SatisfiedFromSharedPool with provider + +// receipt, never absence. cold_resolves_total in target/floor-resolve-receipt.txt is observational +// only. STANDING CONSTRAINT: stays at 2 until identity-based finalization REPLACES it via a +// conscious roster change in the PR that adds or removes an escaping entry class. + +// AUTHORITY SPLIT (0B): gunbc.ci_materialization owns ci_floor_expected_resolve_obligations and +// resolve_realization_finalization_holds. claim_executor records ResolveRealization observations at +// reuse sites using the same variant names as ResolveRealization (ColdResolvePerformed | +// SatisfiedFromSharedPool) and joins observed rows to the obligation population transported in +// WalkPlan.finalization.expected_resolve_obligations — no HAND-Rust subject literals, no +// timing-derived disposition. ci_floor_plan transports ci_floor_expected_resolve_obligations() +// unchanged into FloorFinalization only. Dissolve-on: witness-realization P4 decodes modeled .dag +// observations instead of seed re-authoring. type ResolveObligationIdentity = CompileAnchorWholeTree @@ -377,9 +462,29 @@ fn cold_resolves_performed(realizations: List) -> Nat { }) } -data ci_floor_native_batch_resolve_receipt_note: String = "RECEIPT 9 — CONSCIOUS RAISING 1 -> 2 (2026-08-02, native-bundle production enrollment): CI run 30767841790 refused exactly `floor resolve count 2 differs from declared 1` and wrote target/floor-resolve-receipt.txt with resolves_total=2, resolve_ms_total=79064. The cause is the empirical law above operating as designed: gunbc_pr_native_batch enrolls as a RunnableSingleClaim on its own entry file (src/v2/test/claim/pr_native_batch_test.dag), an entry class whose closure escapes the batch-1 whole-tree materialization, so it pays its own cold resolve — 1 anchor + 1 escaping entry = 2. Same run: the batch itself PASSED via the counted outage fallback (verdict fallback:native_realization_refused, transport causes located), so the resolve count is the sole finalization refusal. Ratchet direction unchanged: the persistent content-keyed store still owes the permanent pin at 1, and de-enrolling the native batch (or pooling its closure warm) lowers this row consciously in the PR that does it. Merge note (2026-08-03, #7522 x #7671): the warm merge-admission capture stays OUTSIDE this count — its claim_batch child owns the capture resolve per Receipt 8 — so the two lanes compose at 2, not 3." - -data ci_floor_capture_subprocess_resolve_receipt_note: String = "RECEIPT 8 — CONSCIOUS LOWERING 2 -> 1 (2026-08-01, typed pre-walk capture subprocess): production run 30702499883 proved the ordinary first-batch capture resolve left about one governor worker-share of evaluator arena resident after its thread joined: discovery handed off at 7.4 GiB versus the 5.2-6.1 GiB control and then swapped to the 60-minute wrapper. The capture is now outside the ordinary population as a TypedClaimSubprocess; its claim_batch child owns the capture resolve and its address space dies before batch 1. Therefore the floor receipt returns to the one ordinary entry class measured before capture population. This is a topology/accounting change, not hiding a resolve: the pre-walk child has its own located wall receipt in the executor log and the floor count names only ordinary in-process resolves." +// RECEIPT 9 — CONSCIOUS RAISING 1 -> 2 (2026-08-02, native-bundle production enrollment): CI run +// 30767841790 refused exactly `floor resolve count 2 differs from declared 1` and wrote +// target/floor-resolve-receipt.txt with resolves_total=2, resolve_ms_total=79064. The cause is the +// empirical law above operating as designed: gunbc_pr_native_batch enrolls as a RunnableSingleClaim +// on its own entry file (src/v2/test/claim/pr_native_batch_test.dag), an entry class whose closure +// escapes the batch-1 whole-tree materialization, so it pays its own cold resolve — 1 anchor + 1 +// escaping entry = 2. Same run: the batch itself PASSED via the counted outage fallback (verdict +// fallback:native_realization_refused, transport causes located), so the resolve count is the sole +// finalization refusal. Ratchet direction unchanged: the persistent content-keyed store still owes +// the permanent pin at 1, and de-enrolling the native batch (or pooling its closure warm) lowers +// this row consciously in the PR that does it. Merge note (2026-08-03, #7522 x #7671): the warm +// merge-admission capture stays OUTSIDE this count — its claim_batch child owns the capture resolve +// per Receipt 8 — so the two lanes compose at 2, not 3. + +// RECEIPT 8 — CONSCIOUS LOWERING 2 -> 1 (2026-08-01, typed pre-walk capture subprocess): production +// run 30702499883 proved the ordinary first-batch capture resolve left about one governor +// worker-share of evaluator arena resident after its thread joined: discovery handed off at 7.4 GiB +// versus the 5.2-6.1 GiB control and then swapped to the 60-minute wrapper. The capture is now +// outside the ordinary population as a TypedClaimSubprocess; its claim_batch child owns the capture +// resolve and its address space dies before batch 1. Therefore the floor receipt returns to the one +// ordinary entry class measured before capture population. This is a topology/accounting change, +// not hiding a resolve: the pre-walk child has its own located wall receipt in the executor log and +// the floor count names only ordinary in-process resolves. data floor_finalization_note: String = "The CI floor's post-batch contract, and it lives HERE rather than in std.realization_schedule because it is a fact about THIS floor's duplicate-computation budget, not about walks in general (review 2026-07-30): the generic carrier takes it as a type parameter, so gunbc_ci_floor_plan returns WalkPlan and every other plan returns WalkPlan. WHAT THAT BUYS, narrowly: the signature DECLARES which finalization family each plan intends, and the std-level coproduct fork is gone. It is NOT a construction wall today — probed by execution, the typechecker does not check a declared return type against the body, so a plan can still return the wrong family and typecheck. Until return-position and annotated-data checking are grounded, the enrolled value witnesses and the executor's parser are the wall; the witnesses dissolve when that checking lands (std.realization_schedule walk_finalization_note carries the probe and the dissolve-on). @@ -406,7 +511,18 @@ data ci_floor_materialization_receipt_path: String = "target/floor-materializati data ci_floor_on_success_materialization_receipt_path: String = "target/floor-attempt-/floor-on-success-materialization-receipt.txt" -data ci_floor_on_success_materialization_receipt_claim_executor_seed_note: String = "§7 seed-retained HAND-RUST bridge (#7499 blocker 6, src/v1/stage0/src/bin/claim_executor.rs): the ordinary materialization receipt is harvested before on_success_stages run; a second receipt at ci_floor_on_success_materialization_receipt_path (attempt-scoped like the other on-success receipts, with attempt_id= and plan_site= in the payload) captures the stage population after stage_memo drops, with the same keyed/unkeyed/duplicated/memo fields. Reason: the process-wide accumulator and fs write at the walk boundary are executor realization — not yet expressible as substrate behavior inside claim_executor. Checkable receipt: ci_floor_on_success_materialization_receipt_claim_executor_seed_deferral + receipt_plan_anchor + roadmap_lane_anchor (witness ci_floor_on_success_materialization_receipt_hand_rust_witness_test.dag); behavioral cross-check: on_success_materialization_receipt_separates_from_ordinary_floor in claim_executor.rs (PROCESS_EVAL_RECOMPUTE_TEST_LOCK)." +// §7 seed-retained HAND-RUST bridge (#7499 blocker 6, src/v1/stage0/src/bin/claim_executor.rs): the +// ordinary materialization receipt is harvested before on_success_stages run; a second receipt at +// ci_floor_on_success_materialization_receipt_path (attempt-scoped like the other on-success +// receipts, with attempt_id= and plan_site= in the payload) captures the stage population after +// stage_memo drops, with the same keyed/unkeyed/duplicated/memo fields. Reason: the process-wide +// accumulator and fs write at the walk boundary are executor realization — not yet expressible as +// substrate behavior inside claim_executor. Checkable receipt: +// ci_floor_on_success_materialization_receipt_claim_executor_seed_deferral + receipt_plan_anchor + +// roadmap_lane_anchor (witness +// ci_floor_on_success_materialization_receipt_hand_rust_witness_test.dag); behavioral cross-check: +// on_success_materialization_receipt_separates_from_ordinary_floor in claim_executor.rs +// (PROCESS_EVAL_RECOMPUTE_TEST_LOCK). data ci_floor_on_success_materialization_receipt_claim_executor_seed_deferral: String = "🟡 dissolve-on: write_on_success_materialization_receipt / materialization_receipt_body / write_materialization_receipt_at harvest arms in src/v1/stage0/src/bin/claim_executor.rs — process-wide accumulator take plus fs write at the run_walk boundary, bundled with the other on-success receipt writers. Counted interim HAND-Rust growth (#7499 blocker 6), NOT a census shrink (review 45752). DISSOLVES WHEN claim_executor walk scheduling and receipt emission are expressed as modeled .dag effects rather than std::thread plus std::fs — the same executor-cutover seam named by walk_plan_run_stage_claim_executor_seed_deferral (std.realization_schedule). Gated on witness-realization P4 executor cutover; not an indefinite seed exemption." diff --git a/dag/gunbc/ci/ci_runner_placement.dag b/dag/gunbc/ci/ci_runner_placement.dag index f431b696a05..081a8cb5065 100644 --- a/dag/gunbc/ci/ci_runner_placement.dag +++ b/dag/gunbc/ci/ci_runner_placement.dag @@ -236,9 +236,50 @@ fn conservation_host_deployment(offer: ComputeOffer, nproc: HardwareThreadCount) } } -data host_session_slice_source_note: String = "WHICH HOSTS OWE A SESSIONS RESERVATION comes from gunbc.session_residency, the authority for where interactive session containers actually live. It does NOT come from the build-cache roster, and the correction is recorded because the proxy came within one cleanup of causing the bug this charge exists to prevent.\n\nThe original derivation asked whether gunbc.build_cache_instance listed an InteractiveSessionCompiles instance for the host. That correlated with session residency and nothing more. When ctrl-sccache.service was deleted from srv1 and srv2 on 2026-08-06 as provably dead — 29MB in 45 days, zero connections, zero writes in a week, because containers cache to their own in-container path — the obvious follow-up of dropping those roster rows would have stopped charging both hosts their 20GiB reservation, and the conservation wall would then have admitted 7 slots each and overcommitted them by ~17GiB while returning true.\n\nSessions consume host RAM whether or not a shared build cache exists for them; srv1 and srv2 were each running 19 session containers at the moment their session cache was found dead. The charge follows residency.\n\nTHE RESERVATION SIZE IS NOW A PER-HOST QUESTION WITH A PER-HOST ANSWER, and the paragraph this replaces described the fleet-wide row that answered it before. That row -- gunbc_ci_session_reservation_mode, a Regime-2 aggregate of exactly 21474836480 bytes, a round power of two with no derivation from the thirteen measured per-session samples beside it -- is DELETED. gunbc.session_reservation_policy answers per host from observed evidence, and the sentence is rewritten rather than annotated because a note describing a deleted authority in the present tense is premise contamination whichever direction it points." - -data host_session_slice_refuses_rather_than_charging_zero_note: String = "AN UNSIZED SESSION SLICE ON A SESSION HOST REFUSES, because the alternative is the bug this change exists to remove, re-entering through a different door. The first draft of this function returned byte_size(0) whenever the reservation mode could not produce a number — SessionAggregateUnmeasured, SessionOperatingPointUnset, SessionReservationModeUnset — which reads as 'sessions cost nothing' and is exactly the unconditional zero that let srv1 and srv2 score as though 20GiB of reservation were free. Fixing the WHERE (a host carries sessions) while leaving the HOW MUCH silently zero would have moved the defect rather than closed it.\n\nThe distinction the arms preserve: a host with NO session instance genuinely owes nothing, and SessionSliceNotApplicable says so. A host that DOES carry one and cannot size it is unknown, not free, and SessionSliceUnsized refuses the whole plan with a located reason. Those are different states and one of them is safe.\n\nTHE REFUSAL IS NO LONGER DORMANT, and that is the change the host-keyed policy made rather than a defect it introduced. The fleet-wide row resolved for every host by construction, so SessionSliceUnsized was unreachable; srv2's reservation is now UNESTABLISHED on measured evidence -- its session denominator is incomplete -- so this arm fires on a real host today. A conservation plan that includes srv2 refuses, which is the correct reading of not knowing the largest non-runner claim on this machine, and is exactly what the arm was written for." +// WHICH HOSTS OWE A SESSIONS RESERVATION comes from gunbc.session_residency, the authority for +// where interactive session containers actually live. It does NOT come from the build-cache roster, +// and the correction is recorded because the proxy came within one cleanup of causing the bug this +// charge exists to prevent. +// +// The original derivation asked whether gunbc.build_cache_instance listed an +// InteractiveSessionCompiles instance for the host. That correlated with session residency and +// nothing more. When ctrl-sccache.service was deleted from srv1 and srv2 on 2026-08-06 as provably +// dead — 29MB in 45 days, zero connections, zero writes in a week, because containers cache to +// their own in-container path — the obvious follow-up of dropping those roster rows would have +// stopped charging both hosts their 20GiB reservation, and the conservation wall would then have +// admitted 7 slots each and overcommitted them by ~17GiB while returning true. +// +// Sessions consume host RAM whether or not a shared build cache exists for them; srv1 and srv2 were +// each running 19 session containers at the moment their session cache was found dead. The charge +// follows residency. +// +// THE RESERVATION SIZE IS NOW A PER-HOST QUESTION WITH A PER-HOST ANSWER, and the paragraph this +// replaces described the fleet-wide row that answered it before. That row -- +// gunbc_ci_session_reservation_mode, a Regime-2 aggregate of exactly 21474836480 bytes, a round +// power of two with no derivation from the thirteen measured per-session samples beside it -- is +// DELETED. gunbc.session_reservation_policy answers per host from observed evidence, and the +// sentence is rewritten rather than annotated because a note describing a deleted authority in the +// present tense is premise contamination whichever direction it points. + +// AN UNSIZED SESSION SLICE ON A SESSION HOST REFUSES, because the alternative is the bug this +// change exists to remove, re-entering through a different door. The first draft of this function +// returned byte_size(0) whenever the reservation mode could not produce a number — +// SessionAggregateUnmeasured, SessionOperatingPointUnset, SessionReservationModeUnset — which reads +// as 'sessions cost nothing' and is exactly the unconditional zero that let srv1 and srv2 score as +// though 20GiB of reservation were free. Fixing the WHERE (a host carries sessions) while leaving +// the HOW MUCH silently zero would have moved the defect rather than closed it. +// +// The distinction the arms preserve: a host with NO session instance genuinely owes nothing, and +// SessionSliceNotApplicable says so. A host that DOES carry one and cannot size it is unknown, not +// free, and SessionSliceUnsized refuses the whole plan with a located reason. Those are different +// states and one of them is safe. +// +// THE REFUSAL IS NO LONGER DORMANT, and that is the change the host-keyed policy made rather than a +// defect it introduced. The fleet-wide row resolved for every host by construction, so +// SessionSliceUnsized was unreachable; srv2's reservation is now UNESTABLISHED on measured evidence +// -- its session denominator is incomplete -- so this arm fires on a real host today. A +// conservation plan that includes srv2 refuses, which is the correct reading of not knowing the +// largest non-runner claim on this machine, and is exactly what the arm was written for. type HostSessionSliceResolution = SessionSliceCharged { bytes: ByteSize } @@ -278,7 +319,52 @@ fn host_session_slice_resolution(host: HostIdentity) -> HostSessionSliceResoluti } } -data host_usable_ram_note: String = "CONSERVATION IS CHECKED AGAINST OS-USABLE RAM, NOT THE DIMM TOTAL. compute_host_ram_bytes_total sums the populated modules -- nominal capacity -- while the OS sees less after firmware and kernel reservations, and gunbc.ci_fleet fleet_host_memory_nominal_vs_usable_note exists specifically to say these are two facts that must not be conflated. The margin decides admissions rather than being academic: at the 128 GiB nominal / 125 GiB usable population this fleet carried until 2026-08-22, an eighth 14 GiB slot summed to 125.6 GiB, which PASSED against nominal and FAILED against usable -- so checking the nominal figure would admit a width the machine cannot carry, by spending memory that does not exist.\n\nTHE UNIFORM-POPULATION PREMISE HAS NOW ACTUALLY FALSIFIED, AND THIS PARAGRAPH IS THE THIRD REVISION OF THAT CLAIM -- which is itself the finding. The premise was: every host on the same board at the same population, so one figure stands in for all four. It was declared falsified once on an in-flight report of a DIMM replacement, then RESTORED when that replacement was reported failed (operator, 2026-08-12), with a note saying that marking a true statement historical is the same defect as leaving a false one live. Both of those were correct at the time. Both are now superseded by measurement: srv3 and srv4 were each read at 512 GiB over BMC Redfish on 2026-08-22 (8 x Samsung M393A8G40D40-CRB, one per channel), srv1 was upgraded to the same population on operator report, and srv2's identical attempt failed memory training and was reverted to 8 x 16 GiB Hynix. The fleet is HETEROGENEOUS as of that date and the premise is dead for the first time in this note's history.\n\nWHAT THAT DOES NOT LICENSE, because it is the move this note has always existed to forbid: the 512 GiB figure is NOMINAL DIMM CAPACITY read from a management controller. It is NOT OS-usable RAM and must not be written into gunbc_ci_host_usable_ram_for. The gap was closed on two hosts the only way it can be: srv3 and srv4 now carry MEASURED usable RAM from /proc/meminfo reads taken over SSH on 2026-08-22, about 502.25 GiB each -- roughly 10 GiB BELOW their nominal 512, which is exactly the firmware-and-kernel reservation this note exists to preserve. THAT GAP IS NOW CLOSED ON ALL FOUR HOSTS AND THIS PARAGRAPH'S PREVIOUS REVISION WAS STALE RATHER THAN WRONG-AT-THE-TIME. It read that srv1 and srv2 had taken no such read and that their rows remained conservative lower bounds; both took ordinary /proc/meminfo reads over SSH on 2026-08-23, so gunbc.ci_floor_measurement now carries HostUsableRamMeasured for every host and gunbc_ci_conservative_lower_bound_retired_note records the borrowed bound's deletion. The correction matters beyond tidiness: a reader sizing srv1 against the retired 125 GiB figure would under-admit a host with about four times that much, and a reader who believed the bound was still live would look for a measurement obligation that no longer exists. srv2's read is the sharper receipt -- it landed about 96 MiB ABOVE the bound it replaced, so that bound was not merely loose but on the wrong side, and a lower bound that is too high is not conservative at all. Neither read promotes a Redfish nominal number, which stays inadmissible here for the reason fleet_host_memory_nominal_vs_usable_note gives.\n\nWHAT THE PER-HOST GRAIN RESTS ON is therefore still not this trigger, and the heterogeneity does not retroactively justify the grain. It is that this wall folded over per-host offers and then read RAM from a synthetic machine outside that fold, and that an unclassified host borrowed rather than refused -- see gunbc.ci_floor_measurement gunbc_ci_host_usable_ram_kind_note. A per-host fact belonged at per-host grain when the hosts were identical; it merely became visible when they stopped being." +// CONSERVATION IS CHECKED AGAINST OS-USABLE RAM, NOT THE DIMM TOTAL. compute_host_ram_bytes_total +// sums the populated modules -- nominal capacity -- while the OS sees less after firmware and +// kernel reservations, and gunbc.ci_fleet fleet_host_memory_nominal_vs_usable_note exists +// specifically to say these are two facts that must not be conflated. The margin decides admissions +// rather than being academic: at the 128 GiB nominal / 125 GiB usable population this fleet carried +// until 2026-08-22, an eighth 14 GiB slot summed to 125.6 GiB, which PASSED against nominal and +// FAILED against usable -- so checking the nominal figure would admit a width the machine cannot +// carry, by spending memory that does not exist. +// +// THE UNIFORM-POPULATION PREMISE HAS NOW ACTUALLY FALSIFIED, AND THIS PARAGRAPH IS THE THIRD +// REVISION OF THAT CLAIM -- which is itself the finding. The premise was: every host on the same +// board at the same population, so one figure stands in for all four. It was declared falsified +// once on an in-flight report of a DIMM replacement, then RESTORED when that replacement was +// reported failed (operator, 2026-08-12), with a note saying that marking a true statement +// historical is the same defect as leaving a false one live. Both of those were correct at the +// time. Both are now superseded by measurement: srv3 and srv4 were each read at 512 GiB over BMC +// Redfish on 2026-08-22 (8 x Samsung M393A8G40D40-CRB, one per channel), srv1 was upgraded to the +// same population on operator report, and srv2's identical attempt failed memory training and was +// reverted to 8 x 16 GiB Hynix. The fleet is HETEROGENEOUS as of that date and the premise is dead +// for the first time in this note's history. +// +// WHAT THAT DOES NOT LICENSE, because it is the move this note has always existed to forbid: the +// 512 GiB figure is NOMINAL DIMM CAPACITY read from a management controller. It is NOT OS-usable +// RAM and must not be written into gunbc_ci_host_usable_ram_for. The gap was closed on two hosts +// the only way it can be: srv3 and srv4 now carry MEASURED usable RAM from /proc/meminfo reads +// taken over SSH on 2026-08-22, about 502.25 GiB each -- roughly 10 GiB BELOW their nominal 512, +// which is exactly the firmware-and-kernel reservation this note exists to preserve. THAT GAP IS +// NOW CLOSED ON ALL FOUR HOSTS AND THIS PARAGRAPH'S PREVIOUS REVISION WAS STALE RATHER THAN +// WRONG-AT-THE-TIME. It read that srv1 and srv2 had taken no such read and that their rows remained +// conservative lower bounds; both took ordinary /proc/meminfo reads over SSH on 2026-08-23, so +// gunbc.ci_floor_measurement now carries HostUsableRamMeasured for every host and +// gunbc_ci_conservative_lower_bound_retired_note records the borrowed bound's deletion. The +// correction matters beyond tidiness: a reader sizing srv1 against the retired 125 GiB figure would +// under-admit a host with about four times that much, and a reader who believed the bound was still +// live would look for a measurement obligation that no longer exists. srv2's read is the sharper +// receipt -- it landed about 96 MiB ABOVE the bound it replaced, so that bound was not merely loose +// but on the wrong side, and a lower bound that is too high is not conservative at all. Neither +// read promotes a Redfish nominal number, which stays inadmissible here for the reason +// fleet_host_memory_nominal_vs_usable_note gives. +// +// WHAT THE PER-HOST GRAIN RESTS ON is therefore still not this trigger, and the heterogeneity does +// not retroactively justify the grain. It is that this wall folded over per-host offers and then +// read RAM from a synthetic machine outside that fold, and that an unclassified host borrowed +// rather than refused -- see gunbc.ci_floor_measurement gunbc_ci_host_usable_ram_kind_note. A +// per-host fact belonged at per-host grain when the hosts were identical; it merely became visible +// when they stopped being. // The grain here is per host, and the reason is not that the hosts differ. This wall folds over // per-host offers and charges each host its own overhead and session slice, then read RAM from a @@ -293,7 +379,6 @@ type HostUsableRamResolution = HostUsableRamResolved { bytes: ByteSize } | HostUsableRamUnresolved { reason: String } - fn host_usable_ram_or_refusal(host: HostIdentity, r: HostUsableRam) -> HostUsableRamResolution { match r { HostUsableRamMeasured { bytes: b, provenance: _ } => HostUsableRamResolved { bytes: b } @@ -564,7 +649,6 @@ data gunbc_ci_count_field_measure_followup: Disposition = Scaffold { } } - type OperatingPointRow { per_session_cap: ByteSize session_concurrency: Int diff --git a/dag/gunbc/ci/ci_runner_target.dag b/dag/gunbc/ci/ci_runner_target.dag index 69462c03ec4..60f032f7941 100644 --- a/dag/gunbc/ci/ci_runner_target.dag +++ b/dag/gunbc/ci/ci_runner_target.dag @@ -28,7 +28,14 @@ fn ci_runner_target_spec(target: CiRunnerTarget) -> RunnerSpec { } } -data ci_runner_target_spec_form_note: String = "Two serialization classes, per the actions grammar (extdeps.languages.yaml.gha_workflow runner_yaml): GitHub-HOSTED labels are the scalar runs-on form and only the HostedRunner variant serializes that way, so the hosted target projects HostedRunner with the row's RunnerLabel (a labels-list here would emit the label-matching form GitHub reserves for the self-hosted class - the 2026-07-11 cursor catch). Ubicloud tokens are NOT GitHub hosted vocabulary: ubicloud routes queued jobs by label match exactly like the self-hosted class, so its rows stay a provider-cited token in the SelfHosted list form (proven live: the 2026-07-11 receipts ran on runs-on: [ubicloud-standard-16-arm])." +// Two serialization classes, per the actions grammar (extdeps.languages.yaml.gha_workflow +// runner_yaml): GitHub-HOSTED labels are the scalar runs-on form and only the HostedRunner variant +// serializes that way, so the hosted target projects HostedRunner with the row's RunnerLabel (a +// labels-list here would emit the label-matching form GitHub reserves for the self-hosted class - +// the 2026-07-11 cursor catch). Ubicloud tokens are NOT GitHub hosted vocabulary: ubicloud routes +// queued jobs by label match exactly like the self-hosted class, so its rows stay a provider-cited +// token in the SelfHosted list form (proven live: the 2026-07-11 receipts ran on runs-on: +// [ubicloud-standard-16-arm]). fn ci_runner_target_memory_regime(target: CiRunnerTarget) -> RunnerMemoryRegime { match target { @@ -45,7 +52,15 @@ fn ci_runner_target_ram_speed_budget(target: CiRunnerTarget) -> ByteSize { } } -data selected_ci_runner_target_note: String = "Operator routing 2026-07-12: back to FleetSelfHosted - the ubicloud window (routed 2026-07-11 while the fleet was slot-contended) was intended for local debugging only, and the return happens THROUGH this selection row exactly as its design promised (the #6489 iteration window's override forked the runner authority and needed a paired revert; this row IS the sanctioned switch point). The projections carry the whole flip: runs-on returns to the fleet-offer labels, the memory regime returns to SlotCarved (memory.max carves and the floor's swap-throttle class exist again on this shape), and the RAM-speed budget returns to the fleet slot ceiling for CARGO_BUILD_JOBS and floor width. The ubicloud catalog row and its derivations stay live and witnessed for any future cloud window: that selection is one row here + regen; nothing else moves." +// Operator routing 2026-07-12: back to FleetSelfHosted - the ubicloud window (routed 2026-07-11 +// while the fleet was slot-contended) was for local debugging only, and the return happens THROUGH +// this selection row as designed (the #6489 iteration window's override forked the runner authority +// and needed a paired revert; this row IS the sanctioned switch point). The projections carry the +// whole flip: runs-on returns to the fleet-offer labels, the memory regime returns to SlotCarved +// (memory.max carves and the floor's swap-throttle class exist again on this shape), and the +// RAM-speed budget returns to the fleet slot ceiling for CARGO_BUILD_JOBS and floor width. The +// ubicloud catalog row and its derivations stay live and witnessed for any future cloud window: +// that selection is one row here + regen; nothing else moves. fn selected_ci_runner_target() -> CiRunnerTarget { FleetSelfHosted diff --git a/dag/gunbc/ci/ci_spec.dag b/dag/gunbc/ci/ci_spec.dag index 4894f7bd901..47f696a26ae 100644 --- a/dag/gunbc/ci/ci_spec.dag +++ b/dag/gunbc/ci/ci_spec.dag @@ -83,9 +83,23 @@ type FloorBatchStopPolicy = StopBeforeDependents | FullLedger type RuntimeUnitCount = RuntimeUnitCountObserved { units: Nat } | RuntimeUnitCountUnavailable { cause: String } -data gunbc_ci_floor_batch_runtime_unit_count_note: String = "RUNTIME UNIT COUNT for the derived batch clamp (gunbc_ci_floor_batch_clamp_note): claim_executor alone knows the affected-set-selected unit count at enforcement time. RuntimeUnitCountObserved carries the count used in clamp_ms = overhead_seconds*1000 + units * per_unit_ms. RuntimeUnitCountUnavailable means the count was lost — typically a hard discovery error discarded DiscoverySummary at the Result boundary so corpus_witnesses reads 0 while the batch is a discovery aggregate, not a single-claim gate row. When unavailable the clamp comparison is REFUSED (verdict ClampRefused): a third state, not WithinBudget and not OverBudget — never map zero to one and render a corpus failure as FLOOR-BATCH-OVER-BUDGET. Authority type here; observation and enforcement live in claim_executor.rs (see gunbc_ci_floor_batch_runtime_unit_count_claim_executor_seed_note)." - -data gunbc_ci_floor_batch_runtime_unit_count_claim_executor_seed_note: String = "§7 seed-retained HAND-RUST bridge (src/v1/stage0/src/bin/claim_executor.rs): FloorRuntimeUnitCount enum mirrors RuntimeUnitCount; each ClaimResult carries the observation; batch aggregation refuses the clamp when any row is Unavailable. Reason: unit-count availability is executor realization over batch results, not yet expressible as substrate behavior inside claim_executor. Dissolve-on: v2 self-emitted claim_executor when the executor bin crosses the self-host frontier." +// RUNTIME UNIT COUNT for the derived batch clamp (gunbc_ci_floor_batch_clamp_note): only +// claim_executor knows the affected-set-selected unit count at enforcement time. +// RuntimeUnitCountObserved carries the count used in clamp_ms = overhead_seconds*1000 + units * +// per_unit_ms. RuntimeUnitCountUnavailable means the count was lost — typically a hard discovery +// error discarded DiscoverySummary at the Result boundary, so corpus_witnesses reads 0 while the +// batch is a discovery aggregate, not a single-claim gate row. When unavailable the clamp +// comparison is REFUSED (verdict ClampRefused): a third state, neither WithinBudget nor OverBudget +// — never map zero to one and render a corpus failure as FLOOR-BATCH-OVER-BUDGET. Authority type +// here; observation and enforcement live in claim_executor.rs (see +// gunbc_ci_floor_batch_runtime_unit_count_claim_executor_seed_note). + +// §7 seed-retained HAND-RUST bridge (src/v1/stage0/src/bin/claim_executor.rs): +// FloorRuntimeUnitCount enum mirrors RuntimeUnitCount; each ClaimResult carries the observation; +// batch aggregation refuses the clamp when any row is Unavailable. Reason: unit-count availability +// is executor realization over batch results, not yet expressible as substrate behavior inside +// claim_executor. Dissolve-on: v2 self-emitted claim_executor when the executor bin crosses the +// self-host frontier. data gunbc_ci_floor_batch_runtime_unit_count_claim_executor_seed_disposition: Disposition = Scaffold { dissolves_to: SingleAuthority, @@ -106,11 +120,65 @@ fn gunbc_ci_workflow_wrapper_budget_minutes() -> Minute { minute(count: minute_count(m: gunbc_ci_ordinary_floor_budget_minutes) + minute_count(m: gunbc_ci_admission_postcondition_budget_minutes) + minute_count(m: gunbc_ci_walk_overhead_allowance_minutes)) } -data gunbc_ci_two_population_budget_note: String = "The CI walk owns two disjoint wall allowances: 55 minutes for ordinary batches and 5 minutes for green-only admission postconditions. WalkPlan carries both so claim_executor, not the outer runner, prevents ordinary work from consuming the admission allowance. The wall spent OUTSIDE both populations — the pre-walk capture subprocess, plan resolve, finalization and receipt writes, phase-journal flushes — is neither ordinary nor postcondition work, so the GitHub step wrapper reserves the named gunbc_ci_walk_overhead_allowance_minutes term for it rather than silently letting a slow pre-walk eat the ordinary allowance from the outside (review 47517; the two watchdogs bound their own populations, and the wrapper is strictly the three-term sum, 75 minutes today). A budget refusal is located to its population inside the executor; a wrapper kill with both populations under budget is located to the overhead term. The overhead term was raised 5→15 minutes (2026-08-05): measured CI floor pre-walk plus eager compile-clean install routinely exceeds 10 minutes before the ordinary population clock starts, so a 5-minute allowance let the GitHub step wrapper kill the job while ordinary budget remained — silent wrapper kill, not ordinary exhaustion. OPERATOR-SIGNED RAISE (briansrls, 2026-08-11): ordinary floor 55 -> 80 minutes, +45.5 percent; the wrapper sum follows derivedly, 75 -> 100. RUN ID: 31467399193 job 93706807817 @ cdabd41 on PR #8091, ORDINARY-FLOOR-OVER-BUDGET at population_index=4 (discovery batch), elapsed_ms=3300000 == budget_ms=3300000. THE ENROLLMENT THAT GREW THE BATCH: none, and as with all three batch-3 raises before it that mismatch with the template IS the receipt - the ordinary floor is affected-set-denominated, and this diff touches std.measure and std.induction and adds a std module beneath both, so the import closure selected 892 of 1074 entry groups (0.83) and discovered 8681 witnesses. SIZING, and its honesty class: 55 minutes is a CENSORED LOWER BOUND, not a measurement - the walk was refused while still running, so the true demand was never observed and cannot be quoted. The number is therefore an EXTRAPOLATION: 3300s at 892/1074 groups extrapolates linearly to ~3973s (66.2min) for the full selection, times the 1.2 measured fleet spread this family already funds elsewhere (the batch-4 raise cites 7.5-20 percent across srv2-02/srv3-01 on identical content) = ~4768s, rounded to 80 at whole-minute grain. THE ASSUMPTION THAT COULD BE WRONG: per-group cost is taken as uniform across the 182 unexecuted groups; if the tail is dearer than the head, 80 still reds and the remedy stays diagnose-or-signed-raise. WHAT THE RUN ACTUALLY SPENT IT ON, because the raise should not be mistaken for the fix: eval - running witnesses - was 122s, 3.7 percent of the budget. resolve was 2070s, and inside the assembly split rewire_import_str ALONE was 1189s, 36 percent of the entire ordinary allowance, with services 212s, symbol_index 192s and emit_info 181s behind it. This raise buys time for a cost shape that belongs to the per-entry assembly decomposition lane (#7597) and the five-minute-CI-gate program; it does not improve it, and it should TIGHTEN by ordinary receipt note when that lane lands. A SECOND FINDING, independent of this PR and larger than it: gunbc_ci_floor_batch_clamp_note sizes the discovery clamp against \"a full corpus of ~2316 witnesses clamps at ~44min\". This run discovered 8681. The clamp's stated sizing basis is ~3.7x stale against the corpus it governs, so a deep-std diff is simply the first shape to walk into a wall every broad diff now approaches; re-denominating that basis is owed and is not done here." - -data gunbc_ci_floor_batch_stop_policy_note: String = "Floor batch stop policy (operator ruling 2026-07-22, fail-fast lane): claim_executor's walk halt is event-scoped so cheap-gate-early reorder does not truncate main's ledger. pull_request → StopBeforeDependents (fail-fast: a cheap-gate red stops before compile/corpus). push (main) and schedule (falsifier) → FullLedger (record batch failure and continue; exit non-zero at end). Unknown/unrecognized events default to FullLedger — never silently stop-early and lose ledger (the absorbing fallback). Authority: gunbc_ci_floor_batch_stop_policy_for_github_event in v2.workflow.ci_floor_plan; consumer: claim_executor.rs run_walk (see gunbc_ci_floor_batch_stop_policy_claim_executor_seed_note)." - -data gunbc_ci_floor_batch_stop_policy_claim_executor_seed_note: String = "§7 seed-retained HAND-RUST bridge (src/v1/stage0/src/bin/claim_executor.rs): FloorBatchStopPolicy enum mirrors this type; run_walk halts or continues based on the variant returned by gunbc_ci_floor_batch_stop_policy_for_github_event after gunbc_ci_floor_plan_uses_batch_stop_policy gates enrollment. Reason: batch-walk halt is executor realization — host-process control flow over an already-evaluated .dag policy, not yet expressible as substrate behavior inside claim_executor. Checkable receipts: witness_floor_stop_policy_* + witness_floor_batch_stop_policy_plan_roster_enrolled in ci_floor_plan_witness_test; acceptance measurement (PR fail-fast ~2min; main push prints complete gate+corpus ledger under FullLedger). Dissolve-on: v2 self-emitted claim_executor run_walk when the executor bin crosses the self-host frontier (DESIGN §7 terminal collapse of stage0 host bins)." +// The CI walk owns two disjoint wall allowances: 55 minutes for ordinary batches, 5 for green-only +// admission postconditions. WalkPlan carries both so claim_executor, not the outer runner, keeps +// ordinary work from consuming the admission allowance. Wall spent OUTSIDE both populations — +// pre-walk capture subprocess, plan resolve, finalization and receipt writes, phase-journal flushes +// — is neither ordinary nor postcondition work, so the GitHub step wrapper reserves the named +// gunbc_ci_walk_overhead_allowance_minutes term for it rather than letting a slow pre-walk silently +// eat the ordinary allowance (review 47517; the two watchdogs bound their own populations, and the +// wrapper is strictly the three-term sum, 75 minutes today). A budget refusal is located to its +// population inside the executor; a wrapper kill with both populations under budget is located to +// the overhead term. The overhead term was raised 5→15 minutes (2026-08-05): measured CI floor +// pre-walk plus eager compile-clean install routinely exceeds 10 minutes before the ordinary clock +// starts, so 5 minutes let the wrapper kill the job with ordinary budget remaining — silent wrapper +// kill, not ordinary exhaustion. +// +// OPERATOR-SIGNED RAISE (briansrls, 2026-08-11): ordinary floor 55 -> 80 minutes, +45.5 percent; +// the wrapper sum follows derivedly, 75 -> 100. RUN ID: 31467399193 job 93706807817 @ cdabd41 on PR +// #8091, ORDINARY-FLOOR-OVER-BUDGET at population_index=4 (discovery batch), elapsed_ms=3300000 == +// budget_ms=3300000. THE ENROLLMENT THAT GREW THE BATCH: none — as with all three batch-3 raises +// before it, that mismatch with the template IS the receipt: the ordinary floor is +// affected-set-denominated, and this diff touches std.measure and std.induction and adds a std +// module beneath both, so the import closure selected 892 of 1074 entry groups (0.83) and +// discovered 8681 witnesses. SIZING and its honesty class: 55 minutes is a CENSORED LOWER BOUND, +// not a measurement — the walk was refused while running, so true demand was never observed. The +// number is an EXTRAPOLATION: 3300s at 892/1074 groups extrapolates linearly to ~3973s (66.2min) +// for the full selection, times the 1.2 measured fleet spread this family already funds elsewhere +// (the batch-4 raise cites 7.5-20 percent across srv2-02/srv3-01 on identical content) = ~4768s, +// rounded to 80 at whole-minute grain. THE ASSUMPTION THAT COULD BE WRONG: per-group cost is taken +// as uniform across the 182 unexecuted groups; if the tail is dearer, 80 still reds and the remedy +// stays diagnose-or-signed-raise. WHAT THE RUN SPENT IT ON, so the raise is not mistaken for the +// fix: eval (running witnesses) was 122s, 3.7 percent of budget. resolve was 2070s, and inside the +// assembly split rewire_import_str ALONE was 1189s, 36 percent of the entire ordinary allowance, +// with services 212s, symbol_index 192s and emit_info 181s behind it. This raise buys time for a +// cost shape belonging to the per-entry assembly decomposition lane (#7597) and the +// five-minute-CI-gate program; it does not improve it, and should TIGHTEN by ordinary receipt note +// when that lane lands. A SECOND FINDING, independent of this PR and larger: +// gunbc_ci_floor_batch_clamp_note sizes the discovery clamp against "a full corpus of ~2316 +// witnesses clamps at ~44min". This run discovered 8681, so the clamp's stated basis is ~3.7x stale +// against the corpus it governs; a deep-std diff is simply the first shape to hit a wall every +// broad diff now approaches. Re-denominating that basis is owed and not done here. + +// Floor batch stop policy (operator ruling 2026-07-22, fail-fast lane): claim_executor's walk halt +// is event-scoped so cheap-gate-early reorder does not truncate main's ledger. pull_request → +// StopBeforeDependents (a cheap-gate red stops before compile/corpus). push (main) and schedule +// (falsifier) → FullLedger (record batch failure, continue, exit non-zero at end). Unknown events +// default to FullLedger — never silently stop early and lose ledger (the absorbing fallback). +// Authority: gunbc_ci_floor_batch_stop_policy_for_github_event in v2.workflow.ci_floor_plan; +// consumer: claim_executor.rs run_walk (see +// gunbc_ci_floor_batch_stop_policy_claim_executor_seed_note). + +// §7 seed-retained HAND-RUST bridge (src/v1/stage0/src/bin/claim_executor.rs): FloorBatchStopPolicy +// enum mirrors this type; run_walk halts or continues on the variant returned by +// gunbc_ci_floor_batch_stop_policy_for_github_event after +// gunbc_ci_floor_plan_uses_batch_stop_policy gates enrollment. Reason: batch-walk halt is executor +// realization — host-process control flow over an already-evaluated .dag policy, not yet +// expressible inside claim_executor. Checkable receipts: witness_floor_stop_policy_* + +// witness_floor_batch_stop_policy_plan_roster_enrolled in ci_floor_plan_witness_test; acceptance +// measurement (PR fail-fast ~2min; main push prints the complete gate+corpus ledger under +// FullLedger). Dissolve-on: v2 self-emitted claim_executor run_walk when the executor bin crosses +// the self-host frontier (DESIGN §7 terminal collapse of stage0 host bins). data gunbc_ci_floor_batch_stop_policy_claim_executor_seed_disposition: Disposition = Scaffold { dissolves_to: SingleAuthority, @@ -137,7 +205,19 @@ type DeployStage { runner: RunnerSpec } -data ci_spec_witness_entries_note: String = "witness_entries is the opt-in CI witness roster (operator inversion 2026-07-04): declared CommitWitnessClaim rows on the GithubActionsCiJob surface (see ci_witness_optin_inversion in gunbc.commit_workflow), projected as explicit entries. Since the discovery flip (see ci_spec_discovery_flip_note) the roster is no longer the only witness source: discovery_scan_dirs re-admits the tree-wide corpus, WHOLE — the three ordinary corpus batches carry SelectionOff as of 2026-08-13 — that authority note lived at v2.workflow.ci_floor_plan corpus_selection_off_note and was DELETED with the floor cut (2026-08-15), so the citation is retained as history rather than as a live pointer; the property it recorded now holds BY CONSTRUCTION in the replacement, which has no selection flag to set (gunbc.witness_floor_workflow, and the CI rung drop in DESIGN). The roster remains the home for rows that must run as declared explicit entries (execution-kind rows with their own resource profile). Empty witness_entries AND empty discovery_scan_dirs means the plan carries no witness-corpus node at all (the regen spec's shape)." +// witness_entries is the opt-in CI witness roster (operator inversion 2026-07-04): declared +// CommitWitnessClaim rows on the GithubActionsCiJob surface (see ci_witness_optin_inversion in +// gunbc.commit_workflow), projected as explicit entries. Since the discovery flip +// (ci_spec_discovery_flip_note) the roster is no longer the only witness source: +// discovery_scan_dirs re-admits the tree-wide corpus, WHOLE — the three ordinary corpus batches +// carry SelectionOff as of 2026-08-13. That authority note lived at v2.workflow.ci_floor_plan +// corpus_selection_off_note and was DELETED with the floor cut (2026-08-15); the citation is +// retained as history, and the property it recorded now holds BY CONSTRUCTION in the replacement, +// which has no selection flag to set (gunbc.witness_floor_workflow, and the CI rung drop in +// DESIGN). The roster remains the home for rows that must run as declared explicit entries +// (execution-kind rows with their own resource profile). Empty witness_entries AND empty +// discovery_scan_dirs means the plan carries no witness-corpus node at all (the regen spec's +// shape). data ci_spec_discovery_flip_note: String = "SUPERSEDED IN ITS OPERATIVE HALF 2026-08-13, AND ITS AUTHORITY DELETED 2026-08-15: the corpus batch carried SelectionOff (v2.workflow.ci_floor_plan corpus_selection_off_note — that module is deleted by the floor cut, so this names history, not a live symbol), so a PR and a main push both ran the tree-wide witness corpus WHOLE; the replacement preserves that whole-roster property by construction rather than by a flag. The shrink this note was written to describe is gone; the enrollment half it also describes — discovery_scan_dirs being the roster source at all, rather than a fixed opt-in list — still stands and is why the row survives instead of being deleted. Read the rest as the history of how the roster came to be discovered, not as the live selection policy.\n\nAS ORIGINALLY WRITTEN (affected-set enrollment flip, operator acceptance 2026-07-09 on #6403; re-landed 2026-07-10 after the operator's direct go, merged onto the resolve-receipt-gate era): the ci floor's corpus batch scans these dirs (plus the source-root *_test.dag walk) with SelectionApplied, so a PR runs the tree-wide witness corpus SHRUNK to the diff's affected set instead of the fixed opt-in roster. Rationale accepted: at flip authoring per-PR coverage was 8 of ~1721 discoverable witnesses, so a selection miss (skipped-but-affected witness) is bounded by the status quo - the witness would not have run at all - while the nightly affected-set-falsifier keeps running the full corpus cold with predictions recorded, so any missing selection edge still surfaces as a counted divergence within one cadence window. Selection stays fail-closed per the PR-A rulings (a provenance gap refuses, never widens; host-scaffold/live-tree rows never predict-skip). The 8-clean-window falsifier criterion continues to govern the falsifier's own retirement, not this flip. Empty list = no discovery (the regen spec, and synthetic test specs exercising pure roster machinery)." type CiSpec { @@ -151,8 +231,17 @@ type CiSpec { data gunbc_ci_floor_gates: List = project_ci_floor_gates(roster: commit_gate_roster) - -data gunbc_ci_falsifier_gates_note: String = "The THIRD executing gate surface, projected here beside the floor and regen lists so the coproduct-totality witness can see it. It is not decoration: v2.test.claim.ci_spec_witness witness_gate_roster_matches_coproduct_arms asserts BAG-EQUALITY between the enrolled gates and every arm of the Gate coproduct, which is the construction-shaped form of 'no gate is unenrolled' — strictly stronger than the per-row detection wall in gunbc.commit_workflow, because it reds when a gate is enrolled NOWHERE even if no roster row was edited (a newly added Gate arm, for instance). When the cadence lane gained gates on 2026-07-26 that witness went red for exactly the right reason: its universe of executing surfaces was two, and there were now three. Adding this projection to the bag EXTENDS the totality check to the new surface — it does not relax it. The distinction matters because the tempting alternative, dropping the two flipped arms from the comparison or slackening bag-equality to subset, would have silenced the wall in the same motion that made it informative, which is the absorbing-fallback shape DESIGN §5 forbids." +// The THIRD executing gate surface, projected beside the floor and regen lists so the +// coproduct-totality witness can see it. v2.test.claim.ci_spec_witness +// witness_gate_roster_matches_coproduct_arms asserts BAG-EQUALITY between the enrolled gates and +// every arm of the Gate coproduct — the construction-shaped form of 'no gate is unenrolled', +// strictly stronger than the per-row detection wall in gunbc.commit_workflow because it reds when a +// gate is enrolled NOWHERE even if no roster row was edited (a newly added Gate arm, for instance). +// When the cadence lane gained gates on 2026-07-26 that witness went red for exactly the right +// reason: its universe of executing surfaces was two and there were now three. Adding this +// projection to the bag EXTENDS the totality check to the new surface; the tempting alternative — +// dropping the two flipped arms or slackening bag-equality to subset — would have silenced the wall +// in the same motion that made it informative, the absorbing-fallback shape DESIGN §5 forbids. data gunbc_ci_falsifier_gates: List = project_falsifier_gates(roster: commit_gate_roster) @@ -160,7 +249,6 @@ data gunbc_ci_floor_witness_entries: List = project_ci_flo data gunbc_ci_gates: List = gunbc_ci_floor_gates - data gunbc_ci_deploy_srv1_stage: DeployStage = { id: "deploy_dashboard_srv1", step_name: "Deploy dashboard to srv1 (live_deploy_apply_srv1_wet)", @@ -210,14 +298,164 @@ fn ci_spec_with_discovery_scan_dirs(spec: CiSpec, dirs: List) -> CiSpec data gunbc_ci_floor_batch_wall_budget_note: String = "SUPERSEDED by the derived clamp gunbc_ci_floor_batch_clamp_note (Piece 3, 2026-07-24): the hand-set gunbc_ci_floor_batch_wall_budget_seconds list this note governed is deleted, replaced by overhead + units*rate computed at run time; this note is kept for the operator-signed static-era history (including the two 1320->1440->1680 raises below) and the raise discipline, which carries forward to the clamp constants. THE COST WALL (CI floor endgame D5, operator brief 2026-07-23 — the resolve-regression journey's section-3 finding made mechanism: nothing redded a merge that added floor minutes, so every recovered minute was re-spent by the next lane's enrollment, and the step timeout's own bounce history 30 -> 60 -> 90 -> 120 -> 180 -> 270 -> 55 is the record of the budget being raised to fit). Per-BATCH wall budgets for gunbc_ci_floor_plan (named gunbc_ci_floor_batches through the static era this note records), enforced by claim_executor at walk time: each batch's measured wall is recorded as a typed receipt row (target/floor-batch-wall-receipt.txt), and a batch over its budget is a typed, located refusal — FLOOR-BATCH-OVER-BUDGET naming the batch index, measured wall, budget row, and this carrier — that reds the walk. A failure arm refuses, never widens: over-budget never triggers a rerun, a wider scope, or a silent cap raise. Denominated PER-BATCH, never per-run, because plumbing PRs have a structurally different cost profile (attribution doc section 9.2: a PR touching host_prelude/cli_run legitimately runs 46 of 55 wet rows — a per-run wall would red every such PR spuriously; per-batch budgets absorb the profile where it lands). RULING RECONCILIATION (the operator dispatching the 2026-07-23 endgame brief is the sign-off on this reading): the standing rule 'no wall-clock term in any verdict' (ci_floor_materialization_receipt_note, operator ruling 2026-07-10) governs WITNESS VERDICTS — a witness's pass/fail must never depend on how long it took. Batch budgets do not touch witness verdicts: they are admission/scheduling facts at the walk grain, the same split the 5-second fast-lane eval law already uses (gunbc_ci_fast_lane_rule_note: the deadline is lane admission, not a verdict term), so a batch refusal names the BATCH, and every witness verdict inside it stands as evaluated. RAISE DISCIPLINE: raising any budget requires appending a dated receipt note here naming the run id and the enrollment that grew the batch — the same discipline as the timeout-note bounce history — never a silent number edit. Budget basis (run 30009199696, post-#7122, with the endgame fixes priced in): batch 1 cheap gates 185s measured -> 125s post-pool, budget 240; batch 2 receipt consume ~0s, budget 60; batch 3 discovery 1024s measured (corpus-denominated resolve wall, lever-1 re-diagnosis pending), budget 1320; batch 4 wet corpora 649s plumbing-profile measured -> ~175s post-re-home, budget 420; batch 5 emit-host 7s, budget 120; batch 6 ingest 414s measured (4 composed overlay children — the genuine mktemp constraint), budget 600; batch 7 reads-real-bytes 206s, budget 420. Sum 3180s = 53min under the 55min step cap. OPERATOR SIGNATURE (briansrls, 2026-07-23): the admission/verdict reading above is affirmed — per-batch wall budgets are scheduling-admission facts, not wall-clock terms in witness verdicts; this line is the declared human-signed exemption row the 2026-07-10 ruling requires. Raise discipline amended: RAISING any budget row requires a new dated operator-signed line here naming the run id and the enrollment that grew the batch; TIGHTENING may land by ordinary receipt note. The on-call remedy for FLOOR-BATCH-OVER-BUDGET is diagnose-or-signed-raise, never rerun. FOLLOW-UP ROWS (operator rework push, 2026-07-23): (a) PRELUDE COVERAGE HOLE — the ~5min before batch-1 arms (naming-hygiene walk, policy install, plan resolve/eval, governor arm, eager compile-clean install) sits OUTSIDE every batch budget and can only red at the 55min step cap; a prelude budget row lands when the phase_mark walls get their own receipt keys. (b) IDENTITY-KEYED BUDGETS — rows are keyed by batch INDEX today (the coverage witness pins length, so a schedule change reds loudly rather than misassigning); keying by batch content-identity dissolves the index coupling and rides the ComputationIdentity lane. (c) K-CAP UNION-RSS RECEIPT — cheap_gate_pool_max_claims_per_child=16 is provisional until the pooled child's union RSS is receipted (claim_batch already prints per-shard-peak-rss; the first CI runs' logs back or re-size the cap; local proxy receipt: the 4-gate outer child peaked 1.82GB with the 12-claim pool nested). OPERATOR-SIGNED RAISE (briansrls, 2026-07-24): batch 3 (row index [2]) 1320 -> 1440 seconds, +9.1 percent (operator: 'lets raise the budget slightly more to give it some room over main' / 'like 10%'; 24min keeps the row's whole-minute grain). RUN IDS: 30063268739 @ e7a9006f wall_ms=1629287, 30058048605 wall_ms=1344115, 30055079462 wall_ms=1380561 — all batch=3 against budget_ms=1320000 on PR #7137. THE ENROLLMENT THAT GREW THE BATCH: none, and that mismatch with this discipline's template IS the receipt. Batch 3 is the affected-set-selected discovery corpus, so its wall is denominated in the DIFF's affected entry count, not in a roster edit: #7137 touches gunbc.ci_layer_roots (broadly imported) and src/v1/stage0/src/cli_run.rs, selecting 839-841 entries where a typical main push selects 600-601. Per-entry cost is at PARITY with green controls, so nothing regressed — #7137 1.602/1.609/1.646 s-per-entry vs main 1.574, clever-ram-110 1.756, clever-pike-49 1.504. The retired 1320 divided by 841 entries = 1.570 s-per-entry, BELOW main's own 1.574 measured rate: the row had zero headroom for any broad-touch diff and would red a no-op PR that merely touched a widely-imported carrier. 1440/841 = 1.712 s-per-entry, ~8.8 percent over main's rate — that is the 'room over main'. HONEST RESIDUAL: 1440 covers three of the four observed runs; the 1629287 outlier (run 30063268739, host peaked 9GB vs 16GB on the other three, 1.937 s-per-entry) still reds, and that is the wall working as designed — the remedy stays diagnose-or-signed-raise, never rerun. SUM ACCOUNTING (the one invariant this raise spends): the ceiling sum moves 3180 -> 3300 seconds = exactly 55min, so this note's original 'Sum 3180s = 53min under the 55min step cap' margin is now CONSUMED. Ceilings are per-batch maxima rather than a predicted run wall, but the sum was this note's stated safety argument and it no longer holds with margin, so it is recorded here rather than dropped. FOLLOW-UP (not landed here): restore margin by TIGHTENING the rows with the largest declared-vs-measured gap once post-merge receipts exist — batch 4 (budget 420, ~175s measured post-re-home) and batch 6 (budget 600, 414s measured) — which lands by ordinary receipt note under this same discipline. OPERATOR-SIGNED RAISE (briansrls, 2026-07-24, second): batch 3 (row index [2]) 1440 -> 1680 seconds, +16.7 percent (28min, whole-minute grain). Operator's word this raise: 'you may bump the budget to 1680 - we're redoing it completely anyway' — so 1680 is a STOPGAP to green this PR while the batch-budget mechanism itself is reworked (the FOLLOW-UP ROWS above: prelude coverage, identity-keyed budgets, k-cap union-RSS), not a claim that 1680 is the settled ceiling. WHY 1440 WAS UNDER-SIZED: the first raise sized off three observations (1344/1381/1629) where one landed on a fast host; five batch-3 walls are now observed on #7137 — 1344115, 1380561, 1558226 (run 30081341899 @ 06a1f2c, host srv4-03), 1581666 (run 30067149030 @ 93642bc, host srv4-03), 1629287 (run 30063268739) — and THREE of five exceed 1440, so 1440 reds the majority, not a lone outlier. This is host variance on the ~840-entry affected set, not a per-entry regression (per-entry stays at parity with green controls, ~1.6-1.9 s-per-entry across hosts). THE ENROLLMENT THAT GREW THE BATCH: still none — batch 3 is affected-set-denominated and #7137 touches broadly-imported carriers (gunbc.ci_layer_roots, cli_run.rs), the same profile the FLOOR-BATCH-OVER-BUDGET template's 'enrollment' field does not capture. SIZING: 1680 clears all five observed walls (max 1629287) with ~3 percent margin. SUM ACCOUNTING CORRECTED: the prior line's 'sum = 55min, margin consumed' was over-conservative — per-batch maxima never co-occur, and run 30081341899's ACTUAL floor step wall was ~44min (b1 185s + b2 ~0s + b3 1558s + b4 175s + b5 7s + b6 414s + b7 206s), well under the 55min step timeout that is the real constraint; the ceiling-sum (now 3540s) is a paper figure nothing hits. The remedy stays diagnose-or-signed-raise, never rerun." -data gunbc_ci_floor_batch_clamp_note: String = "DERIVED per-batch wall clamp (Piece 3, ci-two-tier-placement-redesign.md §9.8, operator 2026-07-24). Supersedes the hand-set gunbc_ci_floor_batch_wall_budget_seconds list (deleted; its two operator-signed raises 1320->1440->1680 are the static-era history kept in gunbc_ci_floor_batch_wall_budget_note): a scalar wall budget conflated workload size (affected-set selection is diff-proportional BY DESIGN, ~5x swing), host speed (±20% fleet envelope), and the quantity actually worth bounding (per-unit cost creep). The clamp re-denominates — per batch, clamp_ms = overhead_seconds*1000 + runtime_unit_count * per_unit_ms — computed by claim_executor from THIS authority plus the affected-set-selected unit count it alone knows (the schedule holds one opaque discovery runnable; the witness count is runtime, not schedule data). Rows are index-aligned to the batch list gunbc_ci_floor_ordinary_batches, minus the trailing scoped-witness batch which owns its own clamp (the length-match witness witness_scoped_batch_is_singleton_and_outside_positional_clamps pins the alignment, mirroring the deleted list's discipline). The load-bearing row is the discovery witness batch (index 2): overhead 300s + 1000ms/witness, so a full corpus of ~2316 witnesses clamps at ~44min (under the 55-min step cap, ~1.6x the ~25-min healthy wall) while every legitimate observed full-corpus wall (1344-1629s) passes, and a runaway reds proportionally instead of at the fixed 1680s that the two hand-raises had to keep chasing. Fixed-count gate batches carry per_unit_ms 0 (their count does not vary, so overhead IS the clamp) at their measured basis. Index 3 (wet corpora) stays a fixed overhead pending a wet-per-witness rate from the D2 probe — a declared calibration gap, not a hidden default. SIGNED CONSTANTS (operator, 2026-07-24): the witness aggregate coefficient 1000ms and the discovery overhead 300s; the per-WITNESS hard max is NOT redefined here — it stays the single fast-lane authority gunbc_ci_fast_lane_eval_budget_ms (5s). BASIS OF THE 1000ms COEFFICIENT (operator, 2026-07-24): the aggregate coefficient is denominated against the observed 0.58-0.70 s/witness (the 1344-1629s full-corpus fleet envelope over ~2316 witnesses) on the srv fleet host class (arm64 self-hosted, capped runners) at the adaptive governor's realized worker width. Naming host-class-x-worker-width as the basis makes a future width or fleet change a DELIBERATE re-sign of this constant (s/witness re-denominates when the fleet or width moves), never a rediscovered fleet-wide red; the ~1.4-1.7x headroom the 1000ms average carries over the observed top rate IS the >=~1.6x runaway the clamp is sized to catch, and sub-threshold creep below that ratio is owned by the gauntlet's per-cadence s/unit receipt (not this clamp). RAISE DISCIPLINE (carried from gunbc_ci_floor_batch_wall_budget_note): raising any overhead or rate requires an appended dated operator-signed line naming the run id and the enrollment that grew the batch; tightening may land by ordinary receipt note; unit counts need no signature (the schedule computes them). The clamp is interim mechanics — the structural wall is the complexity lens (§8, cost <= a + b*n asserted at compile time), and the clamp demotes to host-pathology backstop when that lens goes Blocking. The 55-min step cap stays the absolute backstop for the total floor wall; GUNBC_FLOOR_BATCH_BUDGET_TIGHTEN_MS lowers the COMPUTED clamp (min), never raises — the RED-control hook, never an escape hatch.OPERATOR-SIGNED RAISE (briansrls, 2026-07-25): batch 4 (row index [3]) 420 -> 540 seconds, +28.6 percent. THE MARGIN RULING THIS INSTANTIATES, signed once for the budget FAMILY rather than re-litigated per incident: a clamp was doing double duty as merge-refusal threshold ('this must not merge') and growth detector ('something got slower - look'). Tight margins serve the second job and demonstrably worked - the perturb row's 53 -> 141s growth was caught precisely because the clamp was tight - but they make the FIRST job fire on host roulette, and a breach that means 'you landed on srv2-02' trains the on-call to rerun, which is the crying-wolf failure mode wearing budget clothes. POLICY: clamps are sized to cover MEASURED fleet spread, so a breach means content grew, never which host answered. The growth-detector job moves to per-row trend receipts on the falsifier cadence (row grew >2x against its dated basis = a counted drift receipt), which is what makes a larger clamp margin safe - sensitivity is preserved at row grain while merge-refusal stops firing on variance. BASIS OF 540: post-re-home projection 377s (the worst of the two observed walls, 613864ms on srv2-02, minus the ~237s of eval the six re-homed rows carried) x 1.2 worst observed fleet spread = ~452s, + ~20 percent policy margin = 540 (whole-minute grain, 9min). RUN IDS: 30148859947 @ 619bba5 wall_ms=613864 units=74 host srv2-02; 30163496549 @ 9f87967 wall_ms=571556 units=74 host srv3-01 - identical content, 7.5 percent apart, which IS the spread this raise funds. THE ENROLLMENT THAT GREW THE BATCH: none - and as with the batch-3 raises, that mismatch with this discipline's template IS the receipt. Batch 4 SHRANK this cycle (six rows re-homed to FalsifierCadenceJob, units 74 -> 68); the raise buys spread coverage on a batch that got smaller, not headroom for growth. WHY THIS IS NOT THE FORBIDDEN WIDEN: batch 4's per_unit_ms is 0, which this note ALREADY declares 'a fixed overhead pending a wet-per-witness rate from the D2 probe - a declared calibration gap, not a hidden default'; 540 funds that gap with an honest, dated, dissolving interim instead of leaving the flat rate to fire on host variance. It refuses exactly as before - only the threshold moved, and it moved on a stated measurement, not to make a red go away. HOST SPREAD IS NOT WEATHER and has a named owner: srv1/srv2 still run the pre-#7213 sccache units and are typed expected-latent-defective until re-provisioned (the A1/reach gap), so part of the 7.5-20 percent collapses when the fleet lane re-converges them; this margin covers the genuine hardware-heterogeneity residue, eventually the machine-shape lane's. DISSOLVE-ON: the dag_compile_clean_perturb_receipts_holds diagnosis lands (141s, 38 percent of the remaining batch; if the growth is the cold-spawn / per-entry-reconcile class the wasted_ms=166525 reading supports, returning it toward its 53s basis puts worst case near 350s - under even the retired 420) AND the per-unit rate replaces the flat clamp from the D2 probe; then this row recalibrates by ordinary receipt note under the tightening rule. SAME DISEASE, NOT YET DOSED: gunbc_falsifier_self_host_wet_receipt_wall_budget (600s, the 707s falsifier red) rests on a ten-day-stale basis with zero spread allowance. The margin POLICY above governs it too, but the NUMBER is deliberately not moved here - it is gated on its own sccache-vs-growth attribution, so that dosing lands with that measurement rather than by analogy. WORKER COUNTER-SIGNATURE (claude, 2026-07-25, per this discipline's dated-line requirement): I ran the dissolve-on diagnosis and it REFUTES the mechanism this note hypothesised. The growth is NOT the cold-spawn / per-entry-reconcile class the wasted_ms=166525 reading suggested. Measured by execution on the perturb spawn shape (trivial 1-module compile, 4 source roots) there are TWO independent corpus-denominated costs, in different places. (A) THE CENSUS, inside reconcile: main.rs:491-514 reads every indexed module outside the closure off disk into census_only_sources, and v1_compiler_compile.rs:2580 parse_census_fill_sources parses all ~2543 of them BETWEEN compile.normalize.done and compile.reconcile.done - so it books to the reconcile bin and reads as 'reconcile is slow' when it is a whole-tree parse wearing reconcile's label. ~23s local, ~12-13s on fleet. (B) THE POLICY TAIL, after the compile: cli_run.rs:2105 compile_clean_unlisted_import_use_blocks_from_policy calls default_source_roots() (WHOLE TREE) + resolve_entry_graph_shared to evaluate ONE nullary Bool fn. ~34-42s, once per process, and INDEPENDENT of what was compiled - proven by running the same module with only its own source root: zero pool, all phases 0ms, still 42.4s wall. THE ASYMMETRY that closes the arithmetic: main.rs:566/598 exit(1) on hard diagnostics, so a RED compile never reaches the gate (measured 27.4s total, ~2s tail) while a GREEN compile pays it in full (57.8s total, ~34s tail). The perturb row makes 4 spawns, 2 red and 2 green: ~2x14.5s + ~2x60s = ~149s against the measured 148495.7ms. Pre-#7179 that same row is 4 spawns x census-only = ~52s, which IS the 53s basis. So cost B is the ENTIRE 53->148s regression (~94s of the 148s) and cost A was always present. CONSEQUENCE FOR THIS ROW: the note's projected 'near 350s' still holds directionally but by a different mechanism - fixing B alone returns the row to ~55s, so worst case lands well under even the retired 420 and the 540 becomes pure spread coverage, which is what this note already says it is for. NO BISECT WAS RUN AND NONE IS NEEDED: the three-run measurement attributes the split directly, so the bisect across #7178/#7179 named in the probe plan is superseded work (DESIGN 2 - redundant work is not free just because it would confirm). DISSOLVE-ON, unchanged in shape but now half-discharged: the diagnosis half is DONE (this line); recalibration still waits on the cost-B fix landing (scope the policy resolve to the policy module's own import closure) plus the D2 wet-per-witness rate replacing per_unit_ms 0 on this row. COST-B FIX LANDED (claude, 2026-08-23, lively-bat-222): the repair this line names is done, and the dissolve-on is discharged to its remaining half -- only the D2 wet-per-witness rate is still outstanding. compile_clean_unlisted_import_use_blocks_from_policy no longer resolves default_source_roots(); it assembles the policy entry's OWN import closure (three imports) and resolves that explicit source set, with every unresolvable-import and unreadable-file arm REFUSING by name rather than widening back to the whole tree -- the absorbing fallback is what would have restored the cost while zeroing its frequency. HOW IT RESURFACED, which is the part worth carrying: the cost was re-found from the other end as a floor line reading COMPLETED-OVER-COST-REQUIREMENT qualified_spelling_takes_the_shared_layer wall_ms=57337, against the 57.8s this note measured for exactly this tail. It presented as a qualified-name RESOLUTION defect because only a qualified spelling triggers it -- a qualified type annotation's authored name misses env.source_visible_names, which carries the BARE imported names, so the masked type-ref arm emits an advisory UnlistedImportUse and classifying that one diagnostic is what reaches this policy read; a bare annotation emits nothing and skips it. Qualified resolution itself is a map_get and costs nothing, and a qualified PATTERN HEAD never enters that arm at all. MEASURED, post-fix, same probe and process, only the caller roots varied: the cold arm 44886ms to 109ms, and the qualified/bare asymmetry is gone rather than reduced. NOT CLAIMED: that the floor's 1.22GB RSS is entirely this term -- the wall figures agree three ways but the memory line was never attributed by execution, and if it survives the fix that is a second defect to find rather than one to absorb into this row. Full method and the four orderings: authored in #9044 and NEVER LANDED -- that PR squashed to an empty commit because the code had already landed via #9025 -- so the figures above are the surviving in-tree record and the document is at refs/pull/9044/head. Not restored: docs/probes was bankrupted 2026-08-24.ROW REMOVAL (claude, 2026-07-26, D3b gate flips): two rows DELETED from the tail, 7 -> 5, because their batches no longer exist — SourceRootIngestGate and SelfHostReadsRealBytesGate moved to the falsifier cadence (gunbc.commit_workflow gate_gauntlet_flip_basis_note), and each occupied a batch of its own. This is not a budget change: no surviving row's numbers moved, so no re-signature is owed under the raise discipline above. WHICH TWO, and why the obvious answer was wrong: the removed rows are the LAST two (600s and 420s), NOT the 120s/600s pair a reading of the schedule order suggests. Verified by execution rather than inferred — emit_host_gate_passes sits at batch index 4 BOTH before and after the flip, which is only possible if the two vanished batches were at indices 5 and 6, after it. The 120s row at index 4 is emit_host's and survives. RECORDED BECAUSE THE ALIGNMENT IS POSITIONAL AND HAND-MAINTAINED: an index-aligned list carries no evidence of which row belongs to which batch, so a plausible-but-wrong deletion here is silent — it mis-clamps a surviving batch and reads as a passing length check. The length witness (witness_floor_batch_clamp_params_cover_schedule) proves the COUNT and cannot prove the MAPPING; that gap is the standing argument for deriving these rows from the schedule rather than pairing them by position." - -data gunbc_ci_native_bundle_batch_clamp_basis_note: String = "NATIVE BUNDLE ROW (index 5, operator ruling via Dispatch A->C, 2026-08-02): the production selector enrollment adds one isolated fixed-count batch, so the positional clamp table must add exactly one companion row; co-locating it with an unrelated batch is rejected because that would obscure co-residency and the transition memory receipt. Measured acceptance basis: batch wall 77.020s for three selected members; cold compile 367209379ns, warm artifact lookup 8040ns, native execution 39332338ns, and interpreter oracle 17407839ns. The fixed 125s overhead is the observed whole-batch wall times the family's 1.6x fleet-spread factor, rounded up to a whole five seconds (1.62x measured), and per_unit_ms stays 0 because slice 1 has a fixed three-member population with an explicit next-tranche scaling trigger. Memory basis: RSS peak 4801241088 bytes and cgroup peak 11940978688 bytes. The Runnable remains Substantial and isolated, preserving the measured 7.14GB gap between process RSS and cgroup peak rather than disguising that footprint through co-residence. The clamp bounds wall admission; the memory governor remains the fail-closed capacity authority." - -// The authority is filled ONCE here rather than repeated per row: every row in this list is -// declared by this list, so restating it six times would be six chances for one of them to name -// something else. The reader supplies the index — an offset is the one part of the citation no -// symbol can carry. +// DERIVED per-batch wall clamp (Piece 3, ci-two-tier-placement-redesign.md §9.8, operator +// 2026-07-24). Supersedes the hand-set gunbc_ci_floor_batch_wall_budget_seconds list (deleted; its +// two operator-signed raises 1320->1440->1680 are the static-era history kept in +// gunbc_ci_floor_batch_wall_budget_note): a scalar wall budget conflated workload size +// (affected-set selection is diff-proportional BY DESIGN, ~5x swing), host speed (±20% fleet +// envelope), and the quantity worth bounding (per-unit cost creep). The clamp re-denominates — per +// batch, clamp_ms = overhead_seconds*1000 + runtime_unit_count * per_unit_ms — computed by +// claim_executor from THIS authority plus the affected-set-selected unit count it alone knows (the +// schedule holds one opaque discovery runnable; the witness count is runtime, not schedule data). +// Rows are index-aligned to gunbc_ci_floor_ordinary_batches, minus the trailing scoped-witness +// batch which owns its own clamp (witness_scoped_batch_is_singleton_and_outside_positional_clamps +// pins the alignment, mirroring the deleted list's discipline). The load-bearing row is the +// discovery witness batch (index 2): overhead 300s + 1000ms/witness, so a full corpus of ~2316 +// witnesses clamps at ~44min (under the 55-min step cap, ~1.6x the ~25-min healthy wall) while +// every legitimate observed full-corpus wall (1344-1629s) passes, and a runaway reds proportionally +// instead of at the fixed 1680s the two hand-raises kept chasing. Fixed-count gate batches carry +// per_unit_ms 0 (overhead IS the clamp) at their measured basis. Index 3 (wet corpora) stays a +// fixed overhead pending a wet-per-witness rate from the D2 probe — a declared calibration gap, not +// a hidden default. SIGNED CONSTANTS (operator, 2026-07-24): witness aggregate coefficient 1000ms +// and discovery overhead 300s; the per-WITNESS hard max is NOT redefined here — it stays the single +// fast-lane authority gunbc_ci_fast_lane_eval_budget_ms (5s). BASIS OF THE 1000ms COEFFICIENT +// (operator, 2026-07-24): denominated against the observed 0.58-0.70 s/witness (the 1344-1629s +// full-corpus fleet envelope over ~2316 witnesses) on the srv fleet host class (arm64 self-hosted, +// capped runners) at the adaptive governor's realized worker width. Naming +// host-class-x-worker-width as the basis makes a future width or fleet change a DELIBERATE re-sign +// of this constant, never a rediscovered fleet-wide red; the ~1.4-1.7x headroom over the observed +// top rate IS the >=~1.6x runaway the clamp is sized to catch, and sub-threshold creep is owned by +// the gauntlet's per-cadence s/unit receipt, not this clamp. RAISE DISCIPLINE (carried from +// gunbc_ci_floor_batch_wall_budget_note): raising any overhead or rate requires an appended dated +// operator-signed line naming the run id and the enrollment that grew the batch; tightening may +// land by ordinary receipt note; unit counts need no signature (the schedule computes them). The +// clamp is interim mechanics — the structural wall is the complexity lens (§8, cost <= a + b*n +// asserted at compile time), and the clamp demotes to host-pathology backstop when that lens goes +// Blocking. The 55-min step cap stays the absolute backstop for the total floor wall; +// GUNBC_FLOOR_BATCH_BUDGET_TIGHTEN_MS lowers the COMPUTED clamp (min), never raises — the +// RED-control hook, never an escape hatch. +// +// OPERATOR-SIGNED RAISE (briansrls, 2026-07-25): batch 4 (row index [3]) 420 -> 540 seconds, +28.6 +// percent. THE MARGIN RULING THIS INSTANTIATES, signed once for the budget FAMILY: a clamp was +// doing double duty as merge-refusal threshold ('this must not merge') and growth detector +// ('something got slower - look'). Tight margins serve the second job and worked — the perturb +// row's 53 -> 141s growth was caught because the clamp was tight — but they make the FIRST job fire +// on host roulette, and a breach meaning 'you landed on srv2-02' trains the on-call to rerun: +// crying wolf in budget clothes. POLICY: clamps are sized to cover MEASURED fleet spread, so a +// breach means content grew, never which host answered. The growth-detector job moves to per-row +// trend receipts on the falsifier cadence (row grew >2x against its dated basis = a counted drift +// receipt), which is what makes a larger clamp margin safe — sensitivity is preserved at row grain +// while merge-refusal stops firing on variance. BASIS OF 540: post-re-home projection 377s (the +// worse of two observed walls, 613864ms on srv2-02, minus ~237s of eval the six re-homed rows +// carried) x 1.2 worst observed fleet spread = ~452s, + ~20 percent policy margin = 540 +// (whole-minute grain, 9min). RUN IDS: 30148859947 @ 619bba5 wall_ms=613864 units=74 host srv2-02; +// 30163496549 @ 9f87967 wall_ms=571556 units=74 host srv3-01 — identical content, 7.5 percent +// apart, which IS the spread this raise funds. THE ENROLLMENT THAT GREW THE BATCH: none — as with +// the batch-3 raises, that mismatch with the template IS the receipt. Batch 4 SHRANK this cycle +// (six rows re-homed to FalsifierCadenceJob, units 74 -> 68); the raise buys spread coverage on a +// smaller batch, not growth headroom. WHY THIS IS NOT THE FORBIDDEN WIDEN: batch 4's per_unit_ms is +// 0, which this note ALREADY declares 'a fixed overhead pending a wet-per-witness rate from the D2 +// probe - a declared calibration gap, not a hidden default'; 540 funds that gap with an honest, +// dated, dissolving interim instead of leaving the flat rate to fire on host variance. It refuses +// exactly as before — only the threshold moved, on a stated measurement, not to make a red go away. +// HOST SPREAD IS NOT WEATHER and has a named owner: srv1/srv2 still run the pre-#7213 sccache units +// and are typed expected-latent-defective until re-provisioned (the A1/reach gap), so part of the +// 7.5-20 percent collapses when the fleet lane re-converges them; this margin covers the genuine +// hardware-heterogeneity residue, eventually the machine-shape lane's. DISSOLVE-ON: the +// dag_compile_clean_perturb_receipts_holds diagnosis lands (141s, 38 percent of the remaining +// batch; if the growth is the cold-spawn / per-entry-reconcile class the wasted_ms=166525 reading +// supports, returning it toward its 53s basis puts worst case near 350s — under even the retired +// 420) AND the per-unit rate replaces the flat clamp from the D2 probe; then this row recalibrates +// by ordinary receipt note under the tightening rule. SAME DISEASE, NOT YET DOSED: +// gunbc_falsifier_self_host_wet_receipt_wall_budget (600s, the 707s falsifier red) rests on a +// ten-day-stale basis with zero spread allowance. The margin POLICY governs it too, but the NUMBER +// is deliberately not moved here — it is gated on its own sccache-vs-growth attribution, so that +// dosing lands with that measurement rather than by analogy. +// +// WORKER COUNTER-SIGNATURE (claude, 2026-07-25, per the dated-line requirement): I ran the +// dissolve-on diagnosis and it REFUTES the hypothesised mechanism. The growth is NOT the cold-spawn +// / per-entry-reconcile class the wasted_ms=166525 reading suggested. Measured by execution on the +// perturb spawn shape (trivial 1-module compile, 4 source roots) there are TWO independent +// corpus-denominated costs in different places. (A) THE CENSUS, inside reconcile: main.rs:491-514 +// reads every indexed module outside the closure off disk into census_only_sources, and +// v1_compiler_compile.rs:2580 parse_census_fill_sources parses all ~2543 of them BETWEEN +// compile.normalize.done and compile.reconcile.done — so it books to the reconcile bin and reads as +// 'reconcile is slow' when it is a whole-tree parse wearing reconcile's label. ~23s local, ~12-13s +// on fleet. (B) THE POLICY TAIL, after the compile: cli_run.rs:2105 +// compile_clean_unlisted_import_use_blocks_from_policy calls default_source_roots() (WHOLE TREE) + +// resolve_entry_graph_shared to evaluate ONE nullary Bool fn. ~34-42s, once per process, +// INDEPENDENT of what was compiled — proven by running the same module with only its own source +// root: zero pool, all phases 0ms, still 42.4s wall. THE ASYMMETRY that closes the arithmetic: +// main.rs:566/598 exit(1) on hard diagnostics, so a RED compile never reaches the gate (measured +// 27.4s total, ~2s tail) while a GREEN compile pays it in full (57.8s total, ~34s tail). The +// perturb row makes 4 spawns, 2 red and 2 green: ~2x14.5s + ~2x60s = ~149s against the measured +// 148495.7ms. Pre-#7179 the same row is 4 spawns x census-only = ~52s, which IS the 53s basis. So +// cost B is the ENTIRE 53->148s regression (~94s of 148s) and cost A was always present. +// CONSEQUENCE FOR THIS ROW: the projected 'near 350s' still holds directionally but by a different +// mechanism — fixing B alone returns the row to ~55s, so worst case lands well under even the +// retired 420 and the 540 becomes pure spread coverage, which is what this note already says it is +// for. NO BISECT WAS RUN AND NONE IS NEEDED: the three-run measurement attributes the split +// directly, so the bisect across #7178/#7179 named in the probe plan is superseded work (DESIGN 2 — +// redundant work is not free just because it would confirm). DISSOLVE-ON, unchanged in shape but +// half-discharged: the diagnosis half is DONE (this line); recalibration still waits on the cost-B +// fix landing (scope the policy resolve to the policy module's own import closure) plus the D2 +// wet-per-witness rate replacing per_unit_ms 0 on this row. +// +// COST-B FIX LANDED (claude, 2026-08-23, lively-bat-222): the repair above is done and the +// dissolve-on is discharged to its remaining half — only the D2 wet-per-witness rate is +// outstanding. compile_clean_unlisted_import_use_blocks_from_policy no longer resolves +// default_source_roots(); it assembles the policy entry's OWN import closure (three imports) and +// resolves that explicit source set, with every unresolvable-import and unreadable-file arm +// REFUSING by name rather than widening back to the whole tree — the absorbing fallback that would +// have restored the cost while zeroing its frequency. HOW IT RESURFACED: the cost was re-found from +// the other end as a floor line reading COMPLETED-OVER-COST-REQUIREMENT +// qualified_spelling_takes_the_shared_layer wall_ms=57337, against the 57.8s this note measured for +// exactly this tail. It presented as a qualified-name RESOLUTION defect because only a qualified +// spelling triggers it — a qualified type annotation's authored name misses +// env.source_visible_names, which carries the BARE imported names, so the masked type-ref arm emits +// an advisory UnlistedImportUse and classifying that one diagnostic is what reaches this policy +// read; a bare annotation emits nothing and skips it. Qualified resolution itself is a map_get and +// costs nothing, and a qualified PATTERN HEAD never enters that arm. MEASURED, post-fix, same probe +// and process, only the caller roots varied: the cold arm 44886ms to 109ms, and the qualified/bare +// asymmetry is gone rather than reduced. NOT CLAIMED: that the floor's 1.22GB RSS is entirely this +// term — the wall figures agree three ways but the memory line was never attributed by execution; +// if it survives the fix that is a second defect to find, not one to absorb into this row. Full +// method and the four orderings: authored in #9044 and NEVER LANDED — that PR squashed to an empty +// commit because the code had already landed via #9025 — so the figures above are the surviving +// in-tree record and the document is at refs/pull/9044/head. Not restored: docs/probes was +// bankrupted 2026-08-24. +// +// ROW REMOVAL (claude, 2026-07-26, D3b gate flips): two rows DELETED from the tail, 7 -> 5, because +// their batches no longer exist — SourceRootIngestGate and SelfHostReadsRealBytesGate moved to the +// falsifier cadence (gunbc.commit_workflow gate_gauntlet_flip_basis_note), each having occupied a +// batch of its own. Not a budget change: no surviving row's numbers moved, so no re-signature is +// owed. WHICH TWO, and why the obvious answer was wrong: the removed rows are the LAST two (600s +// and 420s), NOT the 120s/600s pair the schedule order suggests. Verified by execution — +// emit_host_gate_passes sits at batch index 4 BOTH before and after the flip, only possible if the +// two vanished batches were at indices 5 and 6, after it. The 120s row at index 4 is emit_host's +// and survives. RECORDED BECAUSE THE ALIGNMENT IS POSITIONAL AND HAND-MAINTAINED: an index-aligned +// list carries no evidence of which row belongs to which batch, so a plausible-but-wrong deletion +// here is silent — it mis-clamps a surviving batch and passes the length check. The length witness +// (witness_floor_batch_clamp_params_cover_schedule) proves the COUNT and cannot prove the MAPPING; +// that gap is the standing argument for deriving these rows from the schedule rather than pairing +// them by position. + +// NATIVE BUNDLE ROW (index 5, operator ruling via Dispatch A->C, 2026-08-02): the production +// selector enrollment adds one isolated fixed-count batch, so the positional clamp table adds +// exactly one companion row; co-locating it with an unrelated batch is rejected because that would +// obscure co-residency and the transition memory receipt. Measured acceptance basis: batch wall +// 77.020s for three selected members; cold compile 367209379ns, warm artifact lookup 8040ns, native +// execution 39332338ns, interpreter oracle 17407839ns. The fixed 125s overhead is the observed +// whole-batch wall times the family's 1.6x fleet-spread factor, rounded up to a whole five seconds +// (1.62x measured); per_unit_ms stays 0 because slice 1 has a fixed three-member population with an +// explicit next-tranche scaling trigger. Memory basis: RSS peak 4801241088 bytes, cgroup peak +// 11940978688 bytes. The Runnable remains Substantial and isolated, preserving the measured 7.14GB +// gap between process RSS and cgroup peak rather than disguising it through co-residence. The clamp +// bounds wall admission; the memory governor remains the fail-closed capacity authority. + +// The authority is filled ONCE here rather than per row: every row in this list is declared by this +// list, and restating it six times would be six chances for one to name something else. The reader +// supplies the index — an offset is the one part of the citation no symbol can carry. fn gunbc_ci_floor_positional_clamp(overhead_seconds: Int, per_unit_ms: Int) -> RunnableBatchClamp { RunnableBatchClamp { overhead: second(count: overhead_seconds), @@ -311,7 +549,15 @@ fn ci_cargo_eagain_retry_intent(command: String) -> PipelineStep { } } -data ci_floor_required_artifact_names_note: String = "DERIVED pack membership — authority is gunbc.ci_release_bins (ci_floor_invoked_bins ∪ witness_declared_release_bins). The hand List that lived here was deleted 2026-07-26: fan-out (build --bin flags / pack / verify) still folds ci_floor_required_artifact_names(), but MEMBERSHIP is no longer a free-floating hand list in this module. See gunbc.ci_release_bins.ci_release_bins_authority_note for the construction wall (WitnessBinNotEnrolled) and the dissolve-on for witness_declared_release_bins. Operator job-split ruling 2026-07-11 still applies to the handoff shape. The intentionally-absent negative control (no_such_witness_bin_zzz) is excluded. The build-if-absent stale-binary class (#6352) is out of scope: a same-run artifact is fresh by construction." +// DERIVED pack membership — authority is gunbc.ci_release_bins (ci_floor_invoked_bins ∪ +// witness_declared_release_bins). The hand List that lived here was deleted 2026-07-26: fan-out +// (build --bin flags / pack / verify) still folds ci_floor_required_artifact_names(), but +// MEMBERSHIP is no longer a free-floating hand list here. See +// gunbc.ci_release_bins.ci_release_bins_authority_note for the construction wall +// (WitnessBinNotEnrolled) and the dissolve-on for witness_declared_release_bins. Operator job-split +// ruling 2026-07-11 still applies to the handoff shape. The intentionally-absent negative control +// (no_such_witness_bin_zzz) is excluded. The build-if-absent stale-binary class (#6352) is out of +// scope: a same-run artifact is fresh by construction. fn artifact_shell_path(name: String) -> String { concat(concat("\"$ROOT/target/release/", name), "\"") @@ -396,65 +642,57 @@ fn ci_merge_base_diff_range(policy: DiffPolicy) -> String { } } -// Read-only srv1 tasks pre-apply observation (operator ruling 2026-08-09 on PR 8045; -// manager approval msg_03ec1f78): produces the ONE exact pre-apply receipt (effective -// TasksMax, pids.max, pids.current, ancestor limit, observer slot membership) via the -// typed read seams only. workflow_dispatch-gated at the job, so it never rides PR or -// push events; the printed return value IS the typed receipt. - -// Main→desired-ref advancement (CONVERGE-0 loop leg, operator directive 2026-08-09): -// on a floor-green main push (needs: [ci] IS the floor-green admission — this job exists -// only downstream of the required check succeeding in the same run), advance -// refs/fleet/desired to the merged revision by CAS: --force-with-lease names the exact -// observed prior (gunbc.fleet_main_revision decide_fleet_desired_advance's from side), so -// a race with a concurrent advance refuses rather than overwriting; ref creation is the -// from=none arm. Hosts follow the ref; no host is contacted here. +// Read-only srv1 tasks pre-apply observation (operator ruling 2026-08-09 on PR 8045; manager +// approval msg_03ec1f78): produces the ONE exact pre-apply receipt (effective TasksMax, pids.max, +// pids.current, ancestor limit, observer slot membership) via the typed read seams only. +// workflow_dispatch-gated at the job, so it never rides PR or push events; the printed return value +// IS the typed receipt. +// Main→desired-ref advancement (CONVERGE-0 loop leg, operator directive 2026-08-09): on a +// floor-green main push (needs: [ci] IS the floor-green admission — this job exists only downstream +// of the required check succeeding in the same run), advance refs/fleet/desired to the merged +// revision by CAS: --force-with-lease names the exact observed prior (gunbc.fleet_main_revision +// decide_fleet_desired_advance's from side), so a race with a concurrent advance refuses rather +// than overwriting; ref creation is the from=none arm. Hosts follow the ref; no host is contacted +// here. // EVERY `gunbc run` STEP THIS SPEC EMITS NAMES ITS TARGET HERE, AND NOWHERE ELSE. // -// The pair was previously two string literals at each call site, and that shape cost a real -// defect: a merge conflict resolution in gunbc#8377 re-authored two of them by hand, guessing -// the paths from the module names, and produced `dag/gunbc/spark/grant_install.dag` (a file that -// has never existed at any commit) and `dag/gunbc/spark/serving_durability.dag` (a file that -// exists but does not define the named function, and does not import the module that does). -// Nothing refused, because a path in a string is not a symbol -- the DESIGN section 3 stale-citation -// class, landing in an emitted argv rather than in prose. Both steps were unrunnable on main from -// the day they landed. -// -// Hoisting the pairs into one roster is what makes the class checkable at all: one population to -// join against, instead of pairs scattered across call sites where two strings could disagree with -// nothing in a position to notice. +// The pair was previously two string literals at each call site, and that cost a real defect: a +// merge conflict resolution in gunbc#8377 re-authored two of them by hand, guessing paths from +// module names, and produced `dag/gunbc/spark/grant_install.dag` (never existed at any commit) and +// `dag/gunbc/spark/serving_durability.dag` (exists but does not define the named function, nor +// import the module that does). Nothing refused, because a path in a string is not a symbol — the +// DESIGN section 3 stale-citation class, landing in an emitted argv rather than prose. Both steps +// were unrunnable on main from the day they landed. One roster gives one population to join +// against, instead of pairs scattered across call sites where two strings could disagree unnoticed. // -// NO CHECK ENFORCES THIS ROSTER TODAY, and that is stated plainly because an earlier revision of -// this comment named one. It promised that `test.claim.ci_run_step_target_resolution` joined every -// row against decl_facts -- a witness that was written, measured, and then REMOVED from this PR -// along with a second attempt at an emit-time wall. Leaving that sentence would have been the -// DESIGN section 3 stale-citation defect committed in the very comment that diagnoses it two -// paragraphs above, and it had already misled one reviewer into reading coverage that does not -// exist. What the roster buys today is single authority, not enforcement. +// NO CHECK ENFORCES THIS ROSTER TODAY, stated plainly because an earlier revision named one: it +// promised `test.claim.ci_run_step_target_resolution` joined every row against decl_facts — a +// witness written, measured, then REMOVED from this PR along with a second attempt at an emit-time +// wall. Leaving that sentence would have been the DESIGN section 3 stale-citation defect in the +// very comment diagnosing it, and it had already misled one reviewer into reading coverage that +// does not exist. The roster buys single authority, not enforcement. // -// WHY BOTH ATTEMPTS WERE WITHDRAWN, so the next person does not rebuild them: the question "does -// file F declare symbol G" is answered by decl_facts, which walks POOL ROOTS -- directories, -// recursively, ~3833 modules, ~8s. Measured by controlled A/B, an emit-time wall cost 180s across -// 22 call sites and took SEVEN pre-existing witness claims below verdict, because two witness -// modules render workflow steps and inherited the walk. The fast-lane ceiling is 5000ms, so the -// binding constraint is not how many walks happen but that NO CORPUS WALK MAY BE REACHABLE FROM A -// FAST-LANE CLAIM AT ALL -- one walk breaches it. +// WHY BOTH ATTEMPTS WERE WITHDRAWN, so nobody rebuilds them: "does file F declare symbol G" is +// answered by decl_facts, which walks POOL ROOTS — directories, recursively, ~3833 modules, ~8s. By +// controlled A/B, an emit-time wall cost 180s across 22 call sites and took SEVEN pre-existing +// witness claims below verdict, because two witness modules render workflow steps and inherited the +// walk. The fast-lane ceiling is 5000ms, so the binding constraint is that NO CORPUS WALK MAY BE +// REACHABLE FROM A FAST-LANE CLAIM AT ALL — one walk breaches it. // -// THE WALL IS OWED, at the generated-artifact gate rather than at the argv crossing. That is a -// re-scoping of the hazard, not a weaker wall: a bad argv built in memory by a witness harms -// nothing, while a bad argv reaching a COMMITTED workflow is what CI then runs, and it is the route -// both defects above actually took. The gate sits upstream of every emitter that produces a -// committed artifact and already returns ArtifactGenerationRefused { reason }. +// THE WALL IS OWED at the generated-artifact gate rather than the argv crossing — a re-scoping of +// the hazard, not a weaker wall: a bad argv built in memory by a witness harms nothing, while a bad +// argv reaching a COMMITTED workflow is what CI runs, and is the route both defects above took. The +// gate sits upstream of every emitter producing a committed artifact and already returns +// ArtifactGenerationRefused { reason }. // // SO THE RUNG THIS ROSTER REACHES IS MITIGATABLE, NOT MECHANICALLY PREVENTABLE. An earlier revision -// claimed the higher rung on the strength of the witness that is no longer here; with nothing -// enforcing it, the honest rung is one step lower and the consolidation is a cost-of-error -// reduction rather than a wall. NEXT-RUNG TRIGGER: the artifact-gate refusal above. The rung ABOVE -// that -- structural -- is a resolved symbol reference (a DeclarationRef the substrate resolves) -// with the entry path derived from the module index rather than authored beside it, at which point -// a wrong target cannot be written at all. +// claimed the higher rung on the strength of the witness no longer here; with nothing enforcing it, +// the consolidation is a cost-of-error reduction, not a wall. NEXT-RUNG TRIGGER: the artifact-gate +// refusal above. The rung ABOVE that — structural — is a resolved symbol reference (a +// DeclarationRef the substrate resolves) with the entry path derived from the module index rather +// than authored beside it, at which point a wrong target cannot be written. type GunbcRunStepTarget { entry: String function: String @@ -556,31 +794,31 @@ data gunbc_ci_heal_dispatch_target: GunbcRunStepTarget = GunbcRunStepTarget { } // The roster the witness folds. A step whose target is not in this list is not covered, which is -// why the targets are declared here rather than inline: a new call site that re-inlines a literal -// pair is outside the join and the wall would not see it. +// why targets are declared here rather than inline: a new call site that re-inlines a literal pair +// is outside the join. // // THIS LIST IS ITSELF A HAND-MAINTAINED PARALLEL ENUMERATION, and naming only the inline hole -// above understated the denominator problem. There are TWO ways a target escapes this roster, -// and they need different mechanisms: +// understated the denominator problem. A target escapes this roster TWO ways, needing different +// mechanisms: // // 1. a `gunbc run` step spelling a literal entry/function pair inline, never declared as a -// target at all -- invisible to any enumeration of declarations, because the pair is not a +// target -- invisible to any enumeration of declarations, because the pair is not a // declaration. Only a check over the emitted steps can see it. // 2. a twentieth `data ..._target: GunbcRunStepTarget` declared correctly and simply NOT ADDED -// HERE. It looks completely correct at its declaration site and silently under-reports on -// anything that folds this list. +// HERE. It looks correct at its declaration site and silently under-reports on anything +// that folds this list. // -// The second is the open-denominator class this repository has already named and priced: -// `std.primitives` `primitive_contract_roster_note` records the identical shape over -// *_contract rows, its silent under-reporting, and its dissolution -- derive the roster by -// enumerating the declarations once declaration introspection lands. +// The second is the open-denominator class already named and priced: `std.primitives` +// `primitive_contract_roster_note` records the identical shape over *_contract rows, its silent +// under-reporting, and its dissolution — derive the roster by enumerating the declarations once +// declaration introspection lands. // // That dissolution condition is SATISFIED for this roster: `decl_facts` is a registered builtin -// callable from ordinary `.dag`, so the nineteen can be derived by enumerating every -// module-scope `data` of type `GunbcRunStepTarget` in this module instead of being retyped -// here. Doing so costs nothing extra to any consumer that already walks decl_facts, and it -// turns a fold over "the rows someone remembered to list" into a fold over "the rows that -// exist". Until that lands, hole 2 is open and this list is diligence, not a denominator. +// callable from ordinary `.dag`, so the nineteen can be derived by enumerating every module-scope +// `data` of type `GunbcRunStepTarget` in this module instead of being retyped here — at no extra +// cost to any consumer that already walks decl_facts, turning a fold over "the rows someone +// remembered to list" into a fold over "the rows that exist". Until that lands, hole 2 is open and +// this list is diligence, not a denominator. // // Counted at authoring: 19 declarations, 19 entries. That equality is the thing nothing checks. fn gunbc_run_step_targets() -> List { @@ -610,8 +848,8 @@ fn gunbc_run_step_targets() -> List { // The dashboard deploy, invoked as a fleet-converge mode rather than from a workflow of its own. // The operation is unchanged and already correct: live_deploy_apply_srv1_wet observes the candidate // release BEFORE any mutation and refuses when it cannot name the release it would install, so a -// deploy that cannot identify itself stops the line with nothing touched. What was missing was only -// a caller -- gunbc#8676 deleted the invocation, never the operation. +// deploy that cannot identify itself stops the line with nothing touched. Only a caller was missing +// — gunbc#8676 deleted the invocation, never the operation. fn gunbc_ci_live_deploy_apply_srv1_invoke() -> String { gunbc_run_step_script( source_roots: witness_layer_roots, @@ -632,12 +870,12 @@ fn gunbc_ci_fleet_reach_probe_invoke() -> String { ) } -// Read-only multi-principal fleet-key probe (parent msg_e767a783): measures whether the -// in-run fleet key authenticates as any PRE-EXISTING account on srv3/srv4 (ubuntu, -// briansrls, ghrunner, root) — hostname + sudo -n -l, no writes. Rides the same probe -// job so it reuses the one WIF/agent materialization (gunbc_ci_fleet_key_agent_script); -// fleet_multi_principal_probe_wet uses typed_argv_probe_over_ssh over SSH_AUTH_SOCK, not -// a second Secret Manager fetch from gunbc run. Its receipt is a separate file. +// Read-only multi-principal fleet-key probe (parent msg_e767a783): measures whether the in-run +// fleet key authenticates as any PRE-EXISTING account on srv3/srv4 (ubuntu, briansrls, ghrunner, +// root) — hostname + sudo -n -l, no writes. Rides the same probe job to reuse the one WIF/agent +// materialization (gunbc_ci_fleet_key_agent_script); fleet_multi_principal_probe_wet uses +// typed_argv_probe_over_ssh over SSH_AUTH_SOCK, not a second Secret Manager fetch from gunbc run. +// Its receipt is a separate file. fn gunbc_ci_multi_principal_probe_invoke() -> String { gunbc_run_step_script( @@ -649,114 +887,106 @@ fn gunbc_ci_multi_principal_probe_invoke() -> String { ) } -// A2 in-run key materialization (parent msg_72e2dcee): foreign-executor shell that -// exchanges nothing itself — the WIF access token arrives from the google_auth_action -// step via env — and turns Secret Manager versions/1 (PINNED, never `latest`) into a -// loaded ssh-agent identity. SECRET HYGIENE, load-bearing: the payload flows -// curl -> python3 decode -> file mode 0600 -> ssh-add -> rm; it is never echoed, never -// written under a durable HOME, and no digest of it is printed. The only stdout is the -// identity NAME line. The agent socket/pid go to GITHUB_ENV so the probe step's ssh -// children inherit them; the job's always() cleanup step kills the agent. -// THE MARKER'S ORIGINAL TRIGGER WAS UNFALSIFIABLE, AND THIS ROW RECORDS WHY BEFORE -// DELETING IT (parent-session finding, 2026-08-21). It read "DISSOLVES WHEN the typed -// secret-materialization effect... lands on host_effect_apply" — but host_effect_apply's -// HostEffectIntent.target is a NodeControlPlane. Fleet effects use arms carrying ComputeHost; -// explicitly runner-local effects use RunnerLocalHost without fabricating a fleet identity. -// An ephemeral GitHub Actions runner is not a ComputeHost the fleet models; there was no -// host_effect_apply arm this step could ever land on, so the condition named could never -// occur. That is the same defect class as the trigger corrected alongside it this session -// (gunbc_ci_spark_admin_credential_shell_emit_scaffold, PR #8777) — a marker naming a -// condition that sounds like progress but is not reachable from where the code actually -// runs. +// A2 in-run key materialization (parent msg_72e2dcee): foreign-executor shell that exchanges +// nothing itself — the WIF access token arrives from the google_auth_action step via env — and +// turns Secret Manager versions/1 (PINNED, never `latest`) into a loaded ssh-agent identity. SECRET +// HYGIENE, load-bearing: the payload flows curl -> python3 decode -> file mode 0600 -> ssh-add -> +// rm; never echoed, never written under a durable HOME, no digest printed. The only stdout is the +// identity NAME line. The agent socket/pid go to GITHUB_ENV so the probe step's ssh children +// inherit them; the job's always() cleanup step kills the agent. +// +// THE MARKER'S ORIGINAL TRIGGER WAS UNFALSIFIABLE, RECORDED BEFORE DELETING IT (parent-session +// finding, 2026-08-21). It read "DISSOLVES WHEN the typed secret-materialization effect... lands on +// host_effect_apply" — but host_effect_apply's HostEffectIntent.target is a NodeControlPlane. Fleet +// effects use arms carrying ComputeHost; explicitly runner-local effects use RunnerLocalHost +// without fabricating a fleet identity. An ephemeral GitHub Actions runner is not a ComputeHost the +// fleet models, so no host_effect_apply arm could ever land this step and the named condition could +// never occur — the same defect class as the trigger corrected alongside it +// (gunbc_ci_spark_admin_credential_shell_emit_scaffold, PR #8777): a marker naming a condition that +// sounds like progress but is unreachable from where the code runs. // -// THE ACTUAL DEBT WAS NEVER ABOUT host_effect_apply: it was a hand-concatenated String -// where typed orchestration emit belongs (DESIGN section 4c: prose the substrate can see, -// not program data hidden in a joined literal), the same debt PR #8777 discharged for the -// spark credential. Repairs #3 and #4 below (operator scan of #8083, 2026-08-09) are -// preserved verbatim in the Do/Run steps that replace the join: -// #3 FAILURE-SAFE CLEANUP — a `trap cleanup EXIT` is armed BEFORE the key touches disk, -// so any failure in curl/base64/chmod/keygen/agent/add removes the key file and kills -// the agent regardless of where set -e aborts. The outer always() step stays as -// defense in depth. The old ordering (rm only on the happy path, PID exported last) -// could leave the key on disk with no exported PID for the outer cleanup to find. -// #4 SECRET-IDENTITY VERIFICATION — before ssh-add, derive the public key from the fetched -// private material (ssh-keygen -y) and its fingerprint (ssh-keygen -lf) and compare to -// the modeled fleet_automation_ssh_key_fingerprint. A wrong secret VERSION is then a -// located ProbeCredentialIdentityMismatch that CONTACTS NO HOST, never a downstream -// host-auth failure. The comparison is against the repo's single fingerprint authority. +// THE ACTUAL DEBT WAS NEVER ABOUT host_effect_apply: it was a hand-concatenated String where typed +// orchestration emit belongs (DESIGN section 4c: prose the substrate can see, not program data +// hidden in a joined literal), the same debt PR #8777 discharged for the spark credential. Repairs +// #3 and #4 (operator scan of #8083, 2026-08-09) are preserved verbatim in the Do/Run steps that +// replace the join: // -// UNLIKE THE SPARK CREDENTIAL, THIS STEP DOES NOT COLLAPSE INTO A SINGLE CONSUMER. The -// agent this step starts is read by five mutually-exclusive mode-gated steps -// (spark_grants/observe/linger/reboot/durability, all via fleet_multi_principal_probe), -// selected only at workflow-dispatch runtime by github.event.inputs.mode; plan/apply touch -// no SSH at all. Folding this script into all five would author the same script five times -// (DESIGN section 2). What genuinely crosses the step boundary is the AGENT — a background -// daemon plus its socket path and pid, neither of which is secret material — via -// SSH_AUTH_SOCK/SSH_AGENT_PID over GITHUB_ENV, exactly as before. The private key file -// itself already never crossed a step boundary (ssh-add then rm, in that order, before the -// trap disarms): that was measured directly against this function's body and found -// correct, not a defect this change is fixing. +// #3 FAILURE-SAFE CLEANUP -- a `trap cleanup EXIT` is armed BEFORE the key touches disk, so any +// failure in curl/base64/chmod/keygen/agent/add removes the key file and kills the agent +// regardless of where set -e aborts. The outer always() step stays as defense in depth. The +// old ordering (rm only on the happy path, PID exported last) could leave the key on disk +// with no exported PID for the outer cleanup to find. +// #4 SECRET-IDENTITY VERIFICATION -- before ssh-add, derive the public key from the fetched +// private material (ssh-keygen -y) and its fingerprint (ssh-keygen -lf) and compare to the +// modeled fleet_automation_ssh_key_fingerprint. A wrong secret VERSION is then a located +// ProbeCredentialIdentityMismatch that CONTACTS NO HOST, never a downstream host-auth +// failure. The comparison is against the repo's single fingerprint authority. // -// The Scaffold and DissolutionCondition rows that stood here are deleted rather than -// corrected-in-place: the typed-orchestration-emit condition they should have named is -// fulfilled by this same change (gunbc_ci_fleet_key_agent_prelude below, emitted through -// v2.workflow.gunbc_invoke_step_emit gunbc_invoke_step_emit_pipeline), so a corrected row -// would be created and discharged in the same diff — ceremony, not a standing obligation. -// THE TOKEN IS WRITTEN BY A SHELL BUILTIN, AND THAT IS THE WHOLE MECHANISM. Both preludes below used -// `curl -H "Authorization: Bearer $WIF_ACCESS_TOKEN"`, and the shell expands that BEFORE exec, so the -// GCP access token became a word in curl's argv -- readable by any unprivileged `ps` for the life of -// the request. On these runners that is not a hypothetical audience: /proc carries no hidepid, -// /proc/PID/cmdline is mode 0444, and the runner slots are ordinary host systemd services in the host -// pid namespace, so a job payload sees every process on the machine. +// UNLIKE THE SPARK CREDENTIAL, THIS STEP DOES NOT COLLAPSE INTO A SINGLE CONSUMER. The agent is +// read by five mutually-exclusive mode-gated steps (spark_grants/observe/linger/reboot/durability, +// all via fleet_multi_principal_probe), selected at workflow-dispatch runtime by +// github.event.inputs.mode; plan/apply touch no SSH. Folding this script into all five would author +// it five times (DESIGN section 2). What crosses the step boundary is the AGENT — a background +// daemon plus socket path and pid, neither secret — via SSH_AUTH_SOCK/SSH_AGENT_PID over +// GITHUB_ENV, as before. The private key file never crossed a step boundary (ssh-add then rm, +// before the trap disarms): measured against this function's body and found correct, not a defect +// this change fixes. // -// curl reads a header from a file with `-H @FILE` (>=7.55; the runners carry 7.88), which moves the -// value out of curl's argv. That alone would not be enough: writing the file with any EXTERNAL -// command would simply relocate the token from curl's argv into that command's argv. `printf` is a -// bash BUILTIN, so it forks no process and creates no command line -- the token exists in the shell's -// own memory and in the file, and never in any process table. +// The Scaffold and DissolutionCondition rows that stood here are deleted rather than corrected in +// place: the typed-orchestration-emit condition they should have named is fulfilled by this same +// change (gunbc_ci_fleet_key_agent_prelude below, emitted through +// v2.workflow.gunbc_invoke_step_emit gunbc_invoke_step_emit_pipeline), so a corrected row would be +// created and discharged in one diff — ceremony, not a standing obligation. // -// The mode is set BEFORE the content exists. Both preludes already run `umask 077`, so the redirect -// creates the file 0600 at creation rather than writing world-readable bytes and narrowing them -// afterwards; there is no window where the token sits at a wider mode. +// THE TOKEN IS WRITTEN BY A SHELL BUILTIN, AND THAT IS THE WHOLE MECHANISM. Both preludes below +// used `curl -H "Authorization: Bearer $WIF_ACCESS_TOKEN"`, which the shell expands BEFORE exec, so +// the GCP access token became a word in curl's argv — readable by any unprivileged `ps` for the +// life of the request. On these runners that audience is real: /proc carries no hidepid, +// /proc/PID/cmdline is mode 0444, and runner slots are ordinary host systemd services in the host +// pid namespace, so a job payload sees every process on the machine. curl reads a header from a +// file with `-H @FILE` (>=7.55; the runners carry 7.88), moving the value out of curl's argv. +// Writing the file with any EXTERNAL command would merely relocate the token into that command's +// argv; `printf` is a bash BUILTIN, forks nothing and creates no command line, so the token exists +// only in the shell's own memory and the file, never in any process table. The mode is set BEFORE +// the content exists: both preludes run `umask 077`, so the redirect creates the file 0600 at +// creation, with no window at a wider mode. // -// THE TRAP IS NOT WHAT RETIRES THE TOKEN IN THE FLEET PRELUDE, and an earlier revision of this -// annotation said it was: "Each prelude's existing trap removes HDR_FILE on exit alongside the -// other credential material it already cleans up." That holds for the spark prelude, whose trap -// stays armed through step exit. It is FALSE for the fleet prelude, which DISARMS its trap -// (`trap - EXIT`) so the ssh-agent survives into later steps -- and disarms it after removing only -// KEY_FILE, so the bearer token was left sitting in RUNNER_TEMP for the remainder of the job. -// Moving the token out of argv and then leaving it on disk is not a repair, it is the same -// exposure at a different address; found in review of gunbc#9230 (review 56110). +// THE TRAP IS NOT WHAT RETIRES THE TOKEN IN THE FLEET PRELUDE, and an earlier revision said it was +// ("Each prelude's existing trap removes HDR_FILE on exit alongside the other credential material +// it already cleans up"). That holds for the spark prelude, whose trap stays armed through step +// exit. It is FALSE for the fleet prelude, which DISARMS its trap (`trap - EXIT`) so the ssh-agent +// survives into later steps — after removing only KEY_FILE, so the bearer token was left in +// RUNNER_TEMP for the rest of the job. Moving the token out of argv and leaving it on disk is the +// same exposure at a different address; found in review of gunbc#9230 (review 56110). // // THE FIX IS LIFETIME, NOT CLEANUP. HDR_FILE is consumed by exactly one curl, so it is removed on -// the line after that curl rather than added to a later rm -- the credential ceases to exist at -// the moment its only consumer is done with it, which is a shorter window than any exit path can -// offer and does not depend on which trap is armed when the step ends. The trap still covers the -// failure arm between creation and that removal, where the file genuinely can outlive the shell. +// the line after that curl rather than added to a later rm — the credential ceases to exist the +// moment its only consumer is done, a shorter window than any exit path offers and independent of +// which trap is armed at step end. The trap still covers the failure arm between creation and that +// removal, where the file can outlive the shell. // // WHAT IS NOT CLAIMED: this is a repair at two call sites, not a wall. Nothing refuses the next -// prelude that interpolates a credential into a command string -- these bodies are hand-authored -// shell text, not declared operation argv, so v2.std.operation_argv's binding wall cannot see them -// at all. The class stays mitigatable here and the trigger is the same one extdeps.bmc.http names: -// credentials modeled as declared inputs whose type the binder can refuse. +// prelude that interpolates a credential into a command string — these bodies are hand-authored +// shell text, not declared operation argv, so v2.std.operation_argv's binding wall cannot see them. +// The class stays mitigatable here; the trigger is the one extdeps.bmc.http names: credentials +// modeled as declared inputs whose type the binder can refuse. +// // WHY THIS PAYLOAD IS SHELL TEXT AND WHAT WOULD CHANGE THAT (review 55857 asked for a bash-emit // scaffold marker here; recorded rather than marked, because the marker would assert the wrong -// thing). This prelude is NOT the unmigrated medium-as-string form that the two scaffolds in this -// module carry a dissolution trigger for -- those mark *_script functions that concat-build a whole -// script, and their trigger text says so. gunbc_ci_fleet_key_agent_script builds a typed Pipeline of +// thing). This prelude is NOT the unmigrated medium-as-string form the two scaffolds in this module +// carry a dissolution trigger for — those mark *_script functions that concat-build a whole script, +// and their trigger text says so. gunbc_ci_fleet_key_agent_script builds a typed Pipeline of // PipelineStep and emits it through v2.workflow.gunbc_invoke_step_emit, which IS the orchestration // emit those triggers name as their terminal state; gunbc.fleet_ssh_credential_verify // fleet_ssh_credential_shell_verification_supersession_note records that migration. Marking one // command payload as a scaffold while its twenty peers in the same function carry none would claim -// this line is unmigrated debt when it sits in the migrated construction. -// -// The residue the review is right about: a Run's `command` is still shell text, corpus-wide, tracked -// by bash-emit rather than per line. And the reason no modeled actuator was available HERE is a -// boundary rather than a preference -- this prelude executes as raw shell in a FOREIGN EXECUTOR -// (GitHub Actions `run:`) before gunbc runs at all, so Filesystem.WriteOwnerOnly, which this same -// change uses for the netrc on the BMC path, is unreachable at this point in a job. Choosing printf -// is not declining a modeled route; there is no modeled route on that side of the boundary yet, -// which is exactly what bash-emit would change. +// this line is unmigrated debt when it sits in the migrated construction. The residue the review is +// right about: a Run's `command` is still shell text, corpus-wide, tracked by bash-emit rather than +// per line. No modeled actuator was available HERE because of a boundary, not a preference — this +// prelude executes as raw shell in a FOREIGN EXECUTOR (GitHub Actions `run:`) before gunbc runs at +// all, so Filesystem.WriteOwnerOnly, which this same change uses for the netrc on the BMC path, is +// unreachable at this point in a job. Choosing printf is not declining a modeled route; there is +// none on that side of the boundary yet, which is exactly what bash-emit would change. fn gunbc_ci_auth_header_file_write_command() -> String { "printf 'Authorization: Bearer %s\\n' \"$WIF_ACCESS_TOKEN\" > \"$HDR_FILE\"" } @@ -831,39 +1061,39 @@ fn gunbc_ci_fleet_key_agent_script() -> String { // one PINNED secret version, a 0600 file under RUNNER_TEMP, and a trap armed BEFORE the credential // touches disk so any failure removes it regardless of where set -e aborts. // -// WHAT GOES TO GITHUB_ENV IS THE PATH, NEVER THE CREDENTIAL. An environment variable holding the -// password would be readable by every child process the job spawns and printed by any diagnostic -// that dumps the environment — and this repository's own receipts print argv. A path discloses -// nothing to anyone who cannot already read the file. +// WHAT GOES TO GITHUB_ENV IS THE PATH, NEVER THE CREDENTIAL. An env var holding the password would +// be readable by every child process and printed by any diagnostic that dumps the environment — and +// this repository's own receipts print argv. A path discloses nothing to anyone who cannot already +// read the file. +// +// The URL is derived from the modeled SecretRef rather than spelled, so this step and the lease +// that authorizes it cannot name different versions. There is no fingerprint check as for the key: +// a password has no derivable public half, so the identity evidence is sudo's own accept-or-reject, +// which the privileged step classifies as its own outcome. // -// The URL is derived from the modeled SecretRef rather than spelled, so this step and the lease that -// authorizes it cannot name different versions. There is no fingerprint check as there is for the -// key: a password has no derivable public half to compare against, so the identity evidence is -// sudo's own accept-or-reject, which the privileged step classifies as its own outcome. +// THE HTTP STATUS IS CLASSIFIED, NOT COLLAPSED; the first cut got that wrong at the cost of a run. +// It piped `curl -sSf` straight into a decoder and checked the file size afterwards, so a 403 +// surfaced as a JSON decode traceback followed by "produced no payload" — true of the file, false +// about the world. Unreadable and enrolled-but-blank have different remedies, and neither is +// discoverable from an empty file. // -// THE HTTP STATUS IS CLASSIFIED, NOT COLLAPSED, and the first cut of this step got that wrong in a -// way that cost a run to discover. It piped `curl -sSf` straight into a decoder and checked the file -// size afterwards, so a 403 surfaced as a JSON decode traceback followed by "produced no payload" -- -// a sentence true of the file and false about the world. Unreadable and enrolled-but-blank have -// different remedies, and neither is discoverable from an empty file. +// A 403 DOES NOT ESTABLISH THAT THE SECRET EXISTS, and the second cut claimed it did ("an access +// grant and not a missing secret"). Secret Manager answers 403 both for a version that exists and +// is unreadable and for one that does not exist — it hides existence from principals who may not +// read it. gunbc.spark.bootstrap_provision records exactly this ambiguity about exactly this API, +// having read an empty testIamPermissions response as proof of denial rather than of nothing. So +// the refusal states the ambiguity and gives the remedy covering both arms, rather than picking the +// arm that fits the current guess. // -// A 403 DOES NOT ESTABLISH THAT THE SECRET EXISTS, and the second cut of this step claimed it did: -// "an access grant and not a missing secret". Secret Manager answers 403 both for a version that -// exists and is not readable and for one that does not exist -- it hides existence from principals -// who may not read it. gunbc.spark.bootstrap_provision already records exactly this ambiguity about -// exactly this API, having been burned by reading an empty testIamPermissions response as proof of -// denial rather than of nothing. So the refusal states the ambiguity and gives the remedy that -// covers both arms, rather than picking the arm that happens to fit the current guess. -// THE SPARK ADMINISTRATOR CREDENTIAL PRELUDE, folded into the ONE step that consumes it. The -// former two-step shape (a separate "materialize" step exporting a path via GITHUB_ENV, plus an -// always()-gated cleanup step) is gone: the five steps that once sat between them all carry -// mutually exclusive mode == '...' guards against this step's own mode == 'spark_grants' guard, -// so on the only path this credential is ever fetched, nothing genuinely ran between fetch and -// consumption -- the two-step shape was YAML-adjacent, not runtime-adjacent. One shell script now -// fetches, decodes, and hands the credential to the grant-install invocation entirely within its -// own process tree: one trap (armed for the whole script, never disarmed, because nothing needs -// the file to survive past this script's own exit), one CRED_FILE, a shell-local export instead -// of GITHUB_ENV so the path never crosses a step boundary. +// THE SPARK ADMINISTRATOR CREDENTIAL PRELUDE, folded into the ONE step that consumes it. The former +// two-step shape (a separate "materialize" step exporting a path via GITHUB_ENV, plus an +// always()-gated cleanup step) is gone: the five steps between them all carry mutually exclusive +// mode == '...' guards against this step's own mode == 'spark_grants' guard, so on the only path +// this credential is fetched nothing ran between fetch and consumption — the two-step shape was +// YAML-adjacent, not runtime-adjacent. One shell script now fetches, decodes, and hands the +// credential to the grant-install invocation within its own process tree: one trap (armed for the +// whole script, never disarmed, since nothing needs the file past this script's exit), one +// CRED_FILE, a shell-local export instead of GITHUB_ENV so the path never crosses a step boundary. fn gunbc_ci_spark_grant_install_credential_prelude() -> List { [ Do { run: ci_retry_body_run(command: "set -euo pipefail") }, @@ -939,33 +1169,29 @@ fn gunbc_ci_spark_grant_install_credential_prelude() -> List { ] } -// The in-run ssh-agent teardown — a FRESH per-job effect (each isolated job starts and -// must kill its own agent; nothing is memoizable across jobs), named so both the workflow -// step and the materialization gate's fresh-effect list reference ONE authority (§3), -// rather than an inline literal duplicated per job that the ladder would read as authored -// cross-job duplication. +// The in-run ssh-agent teardown — a FRESH per-job effect (each isolated job starts and must kill +// its own agent; nothing is memoizable across jobs), named so the workflow step and the +// materialization gate's fresh-effect list reference ONE authority (§3), rather than an inline +// literal per job that the ladder would read as authored cross-job duplication. fn gunbc_ci_fleet_key_agent_cleanup_script() -> String { "if [ -n \"$\{SSH_AGENT_PID:-}\" ]; then ssh-agent -k || true; fi\n" } -// THE FLEET-TIMER SELF-INSTALL WIRE IS DELETED (2026-08-18). It emitted a GitHub Actions -// `run:` step invoking `gunbc.fleet_converge_timer_install fleet_timer_self_install_wet`, -// and its own comment recorded that the step existed because the self-install route -// otherwise "had zero callers, a disconnected mechanism". That step lived in `ci.yml`, -// which #8283 deleted, so the wire returned to being exactly the disconnected mechanism it -// was added to prevent: measured at deletion, `gunbc_ci_fleet_timer_install_invoke` had no -// consumer but the `DeclarationRef` inside its own `Scaffold` row, and -// `fleet_timer_self_install` appears in neither surviving workflow. +// THE FLEET-TIMER SELF-INSTALL WIRE IS DELETED (2026-08-18). It emitted a GitHub Actions `run:` +// step invoking `gunbc.fleet_converge_timer_install fleet_timer_self_install_wet`, and its own +// comment recorded that the step existed because the self-install route otherwise "had zero +// callers, a disconnected mechanism". That step lived in `ci.yml`, which #8283 deleted, so the wire +// returned to being exactly the disconnected mechanism it was added to prevent: at deletion, +// `gunbc_ci_fleet_timer_install_invoke` had no consumer but the `DeclarationRef` inside its own +// `Scaffold` row, and `fleet_timer_self_install` appears in neither surviving workflow. // // Its registered `Scaffold` and unbound `DissolutionCondition` go with it. Both described a -// concat-built foreign-executor shell awaiting bash-emit; deleting the emitter discharges -// the obligation instead of carrying it, and an unbound condition whose subject no longer -// exists can never report itself fired. +// concat-built foreign-executor shell awaiting bash-emit; deleting the emitter discharges the +// obligation instead of carrying it, and an unbound condition whose subject no longer exists can +// never report itself fired. data gunbc_fleet_converge_plan_artifact_name: String = "fleet-converge-plan" - - fn gunbc_ci_fleet_converge_plan_invoke() -> String { gunbc_run_step_script( source_roots: witness_layer_roots, @@ -978,9 +1204,22 @@ fn gunbc_ci_fleet_converge_plan_invoke() -> String { data gunbc_ci_fleet_converge_plan_artifact_hash_env_name: String = "EXPECTED_HASH" - - -data gunbc_ci_fleet_converge_apply_plan_hash_predicate_note: String = "THE PLAN-HASH GUARD IS NOT TYPED, AND THIS ROW SAYS WHY RATHER THAN LETTING THE Do LINES READ AS AN OVERSIGHT. The guard is now three orchestration steps inside the same Pipeline as the invocation (it was a join of raw string literals beside it), so it is emitted rather than concatenated -- but its condition is still an opaque Run leaf rather than v2.std.orchestration Or/Not/StrEmpty/StrEq. THE BLOCKER, measured against the emitter rather than assumed: orch_emit_predicate refuses any test operand outside orch_shell_test_operand_alphabet (v2.compiler.emit_orchestration), whose members are the alphanumerics plus underscore, dot, slash, hyphen, at-sign, dollar, double-quote and the two brace characters. The operand this guard needs is the POSIX default-substitution form of EXPECTED_HASH -- which is exactly how the guard tolerates the variable being unset under set -u instead of aborting -- and the colon that form requires is not in that alphabet. Spelling the operand as a bare EXPECTED_HASH reference instead would make an unset EXPECTED_HASH abort under set -u BEFORE the guard could report PlanArtifactHashMismatch, which is a different failure with a different diagnostic, so that is a semantic loss and not a respelling. Widening the alphabet by one character was declined: the alphabet is the fail-closed wall against metacharacter injection in test operands, and a per-site widening to admit one construct is the workaround class DESIGN section 5 names -- the substrate answer is a typed parameter-expansion carrier, default-substitution as a variant rather than a character in a string, which makes the whole construct emittable without loosening the wall for anything else. Dissolve-on below." +// THE PLAN-HASH GUARD IS NOT TYPED, AND THIS ROW SAYS WHY so the Do lines do not read as an +// oversight. The guard is now three orchestration steps inside the same Pipeline as the invocation +// (it was a join of raw string literals beside it), so it is emitted rather than concatenated — but +// its condition is still an opaque Run leaf rather than v2.std.orchestration Or/Not/StrEmpty/StrEq. +// THE BLOCKER, measured against the emitter: orch_emit_predicate refuses any test operand outside +// orch_shell_test_operand_alphabet (v2.compiler.emit_orchestration) — alphanumerics plus +// underscore, dot, slash, hyphen, at-sign, dollar, double-quote and the two braces. The operand +// this guard needs is the POSIX default-substitution form of EXPECTED_HASH — exactly how the guard +// tolerates the variable being unset under set -u — and the colon that form requires is not in the +// alphabet. A bare EXPECTED_HASH reference would make an unset EXPECTED_HASH abort under set -u +// BEFORE the guard could report PlanArtifactHashMismatch — a different failure with a different +// diagnostic, so a semantic loss, not a respelling. Widening the alphabet by one character was +// declined: the alphabet is the fail-closed wall against metacharacter injection in test operands, +// and a per-site widening is the workaround class DESIGN section 5 names — the substrate answer is +// a typed parameter-expansion carrier, default-substitution as a variant rather than a character in +// a string, making the construct emittable without loosening the wall. Dissolve-on below. data gunbc_ci_fleet_converge_apply_plan_hash_predicate_dissolution_trigger: DissolutionCondition = unbound_dissolution(description: "🟡 dissolve-on: gunbc_ci_fleet_converge_apply_plan_hash_prelude — RUNG: mitigatable, and stated rather than left to be read off the Pipeline it now sits inside. The guard executes and its refusal is typed and located at RUNTIME (PlanArtifactHashMismatch naming both hashes, exit 1 before any actuation), but the condition itself is an opaque Run leaf, so nothing at emit time can tell a correct comparison from a lying one — a mistyped operand emits happily and fails open on the runner. CEILING: structurally guaranteed. The property is decidable and the emitter already refuses malformed operands for every predicate it can carry; this one is outside what the carrier can express, not outside what is decidable. NEXT-RUNG TRIGGER, the one named thing that moves it: v2.std.orchestration Predicate operands take a typed shell-word carrier with a default-substitution variant, so the POSIX default-substitution form this guard needs becomes a constructor rather than a character sequence that v2.compiler.emit_orchestration orch_shell_test_operand_alphabet must be widened to admit. At that point the guard becomes Or/Not/StrEmpty/StrEq and Exit, the alphabet check on this construct is unwritable-by-construction, and this row and the three Do lines delete together. NOT BLOCKED ON ANYTHING ELSE: this is can-climb-after-one-grounding, not cannot-climb.") @@ -1007,8 +1246,6 @@ fn gunbc_ci_fleet_converge_apply_invoke() -> String { ) } - - fn gunbc_ci_spark_serving_reboot_invoke() -> String { gunbc_run_step_script( source_roots: witness_layer_roots, @@ -1040,7 +1277,6 @@ fn gunbc_ci_spark_grant_install_invoke() -> String { ) } - fn gunbc_ci_spark_serving_observe_invoke() -> String { gunbc_run_step_script( source_roots: witness_layer_roots, @@ -1061,42 +1297,37 @@ fn gunbc_ci_spark_serving_converge_slice_invoke() -> String { ) } -// Fleet convergence collection (operator verdict join 5, parent verification -// msg_8d84ae4f). THIS WIRE IS DEAD AND THE SENTENCE THAT STOOD HERE SAID IT WAS LIVE. -// It read, present tense, that this is the LIVE invocation wire for -// collect_fleet_convergence_wet, that it runs on main pushes downstream of the required -// ci check from srv1 over the typed SSH probe seam, and that the collector-to-fold -// composition therefore executes on a real cadence. Every clause of that was true when -// written and none of it is true now: the step it builds was a step of ci.yml, and the -// floor cut deleted that workflow. Measured 2026-08-22 — this function has ZERO -// consumers, and no file under .github/workflows names collect_fleet_convergence_wet. -// The three surviving workflows are witnesses.yml, fleet-converge.yml and -// fleet-desired.yml, and none of them reaches it. +// Fleet convergence collection (operator verdict join 5, parent verification msg_8d84ae4f). THIS +// WIRE IS DEAD AND THE SENTENCE THAT STOOD HERE SAID IT WAS LIVE. It read, present tense, that this +// is the LIVE invocation wire for collect_fleet_convergence_wet, running on main pushes downstream +// of the required ci check from srv1 over the typed SSH probe seam, so the collector-to-fold +// composition executes on a real cadence. Every clause was true when written and none is now: the +// step it builds was a step of ci.yml, which the floor cut deleted. Measured 2026-08-22 — this +// function has ZERO consumers, and no file under .github/workflows names +// collect_fleet_convergence_wet. The three surviving workflows are witnesses.yml, +// fleet-converge.yml and fleet-desired.yml; none reaches it. // -// WHY THE FALSE SENTENCE SURVIVED THE CUT, which is the part worth keeping. Nothing -// refused when ci.yml was deleted, because no carrier ever had a dependency EDGE to a -// workflow — they had prose naming it. DESIGN's delete-first doctrine rests on the -// deletion BEING the census, every real dependent refusing loudly; a prose dependent -// cannot refuse at all, so this class is exactly the doctrine's blind spot. See -// docs/plans/replacement-migration-doctrine.md, which now names referents-named-rather- -// than-imported alongside the other things a deletion cannot discover -- and records that -// the path this comment spells is ALREADY a symbol: gunbc.generated_artifact -// WitnessFloorYamlArtifact. A carrier pointing at the variant instead of the filename -// gets its census from coproduct exhaustiveness for free. +// WHY THE FALSE SENTENCE SURVIVED THE CUT: nothing refused when ci.yml was deleted, because no +// carrier had a dependency EDGE to a workflow — they had prose naming it. DESIGN's delete-first +// doctrine rests on the deletion BEING the census, every real dependent refusing loudly; a prose +// dependent cannot refuse, so this class is exactly the doctrine's blind spot. See +// docs/plans/replacement-migration-doctrine.md, which now names +// referents-named-rather-than-imported alongside the other things a deletion cannot discover — and +// records that the path this comment spells is ALREADY a symbol: gunbc.generated_artifact +// WitnessFloorYamlArtifact. A carrier pointing at the variant instead of the filename gets its +// census from coproduct exhaustiveness for free. // -// THIS CORRECTION IS ONE SENTENCE AND NOT A SWEEP (operator ruling, 2026-08-22). -// ci.yml is still named 99 times across 39 .dag files, and correcting all of them would -// fix those sentences and leave the class as open as it was — the next workflow deleted -// decays the same way. The count is recorded rather than swept; the durable fix is the -// next-rung trigger in that document, not prose hygiene here. This one is corrected -// because it had already cost a session real time: read first, it agreed with what the -// code looked like, and it nearly produced a report of a live actuation defect against -// two hosts that do not exist. +// THIS CORRECTION IS ONE SENTENCE AND NOT A SWEEP (operator ruling, 2026-08-22). ci.yml is still +// named 99 times across 39 .dag files; correcting all of them would fix those sentences and leave +// the class as open as it was — the next deleted workflow decays the same way. The count is +// recorded rather than swept; the durable fix is the next-rung trigger in that document, not prose +// hygiene here. This one is corrected because it already cost a session real time: read first, it +// agreed with what the code looked like, and nearly produced a report of a live actuation defect +// against two hosts that do not exist. // -// WHAT THE FUNCTION STILL IS: an emitter for a step script, retained because the -// collector it names is not itself dead — collect_fleet_convergence_wet is intact and -// hand-callable. What ended is this wire's enrolment, not the capability. - +// WHAT THE FUNCTION STILL IS: an emitter for a step script, retained because +// collect_fleet_convergence_wet is intact and hand-callable. What ended is this wire's enrolment, +// not the capability. fn gunbc_ci_collect_fleet_convergence_invoke() -> String { gunbc_run_step_script( @@ -1111,7 +1342,6 @@ fn gunbc_ci_collect_fleet_convergence_invoke() -> String { // Host-key scan + enroll dispatch wires (CONVERGE acceptance item 4): scan observes, // enroll applies the committed registry; both srv1-dispatched, receipts catted. - fn gunbc_ci_fleet_host_key_scan_invoke() -> String { gunbc_run_step_script( source_roots: witness_layer_roots, @@ -1122,8 +1352,6 @@ fn gunbc_ci_fleet_host_key_scan_invoke() -> String { ) } - - fn gunbc_ci_fleet_host_key_enroll_invoke() -> String { gunbc_run_step_script( source_roots: witness_layer_roots, @@ -1134,8 +1362,6 @@ fn gunbc_ci_fleet_host_key_enroll_invoke() -> String { ) } - - fn gunbc_ci_probe_identity_observe_invoke() -> String { gunbc_run_step_script( source_roots: witness_layer_roots, @@ -1146,8 +1372,6 @@ fn gunbc_ci_probe_identity_observe_invoke() -> String { ) } - - fn gunbc_ci_authorized_key_enroll_invoke() -> String { gunbc_run_step_script( source_roots: witness_layer_roots, @@ -1158,41 +1382,38 @@ fn gunbc_ci_authorized_key_enroll_invoke() -> String { ) } -// THE UNGATED FLEET-DESIRED ADVANCE WAS DELETED HERE, and the deletion is the point rather than a -// tidy-up. What stood here was gunbc_ci_advance_fleet_desired_script, a concat-built shell step -// that pushed $GITHUB_SHA onto refs/fleet/desired -- with a compare-and-swap on the observed prior -// on the advance branch, and on the CREATE branch a bare `git push origin "$GITHUB_SHA:$DESIRED_REF"` -// carrying no lease at all, which is the branch that runs when the ref does not yet exist. +// THE UNGATED FLEET-DESIRED ADVANCE WAS DELETED HERE, and the deletion is the point. What stood +// here was gunbc_ci_advance_fleet_desired_script, a concat-built shell step that pushed $GITHUB_SHA +// onto refs/fleet/desired — with a compare-and-swap on the observed prior on the advance branch, +// and on the CREATE branch a bare `git push origin "$GITHUB_SHA:$DESIRED_REF"` carrying no lease at +// all, the branch that runs when the ref does not yet exist. // -// The CAS half was correct. The ADMISSION half did not exist: gunbc.fleet_main_revision requires the -// desired revision to derive from merged, FLOOR-GREEN main and models that gate as -// decide_fleet_desired_advance, and this script never called it -- so the gate's only caller was its +// The CAS half was correct. The ADMISSION half did not exist: gunbc.fleet_main_revision requires +// the desired revision to derive from merged, FLOOR-GREEN main and models that gate as +// decide_fleet_desired_advance, and this script never called it — so the gate's only caller was its // own witness test while the unproven path ran on every push to main. That is exactly the raw-HEAD // advance the authority names as its prevented class, and it reached further than the ref: // gunbc.roadmap_belt_actuate gates worker dispatch on revision standing, so a desired ref equal to -// whatever last landed made that gate agree unconditionally and pass in silence. +// whatever last landed made that gate agree unconditionally, in silence. // // The replacement is gunbc.fleet_desired_admission, whose decision consults the modeled gate with a -// floor conclusion observed from the floor run's own workflow_run payload, and which renders the -// authorized push itself so the surrounding shell carries no ref, revision or prior of its own. It -// is cut at the root rather than kept beside the replacement: two answers to how the fleet's -// admitted revision advances is the fork DESIGN section 3 forbids, and the weaker one was the live -// one. Keeping a function that can still render the raw form would leave the unsafe spelling one -// call site from returning. The script's only other consumers were gunbc.live_deploy_workflow and -// its witness file, both deleted with the deploy workflow authority. +// floor conclusion observed from the floor run's own workflow_run payload, and renders the +// authorized push itself so the surrounding shell carries no ref, revision or prior. It is cut at +// the root rather than kept beside the replacement: two answers to how the fleet's admitted +// revision advances is the fork DESIGN section 3 forbids, and the weaker one was live. A function +// that can still render the raw form leaves the unsafe spelling one call site from returning. The +// script's only other consumers were gunbc.live_deploy_workflow and its witness file, both deleted +// with the deploy workflow authority. // -// The Scaffold disposition and dissolution trigger that tracked this script's medium-as-string debt -// went with it -- the replacement's actuation is a model-rendered script, so that debt is re-declared -// where the remaining shell actually lives, not inherited here. +// The Scaffold disposition and dissolution trigger tracking this script's medium-as-string debt +// went with it — the replacement's actuation is a model-rendered script, so that debt is +// re-declared where the remaining shell lives, not inherited here. // // This annotation was itself a merge conflict: the root cut and the admission change each deleted -// this script and each wrote their own account of why. Two annotations for one deletion is the -// duplication section 3 forbids, so they are merged here rather than stacked, and the accompanying -// `data ..._deleted_note: String` row is dropped -- a String declaration whose sole purpose is -// commentary is the misplaced data section 4c names, and the annotation above already carries it. - - - +// this script and each wrote its own account. Two annotations for one deletion is the duplication +// section 3 forbids, so they are merged here, and the accompanying `data ..._deleted_note: String` +// row is dropped — a String declaration whose sole purpose is commentary is the misplaced data +// section 4c names. fn gunbc_ci_srv1_tasks_observation_invoke() -> String { gunbc_run_step_script( @@ -1214,8 +1435,6 @@ fn gunbc_ci_deploy_invoke(stage: DeployStage) -> String { ) } - - data ci_heal_regen_note: String = "The heal REGEN invoke: gunbc run on the generated-artifact drift gate's main_wet, which writes each committed_generated_artifacts() projection to its path (fail-closed fold — a write error stops the line, never a partial commit). Runs on the same witness_layer_roots and released gunbc binary the deploy job consumes. Idempotent by construction: on a tree already at the fixed point the writes are byte-identical, so the downstream commit-push step sees no git diff and pushes nothing — the loop terminates in one heal. That fixed point is the enforced contract of generated_artifact_drift_fixed_point_witness_test.witness_committed_is_fixed_point (committed bytes == artifact_generate), so a non-terminating heal loop is unwritable while that witness stays green." fn gunbc_ci_heal_regen_invoke() -> String { @@ -1228,10 +1447,15 @@ fn gunbc_ci_heal_regen_invoke() -> String { ) } - -data ci_heal_repo_local_git_config_note: String = "Immediately after the release gunbc binary is unpacked, converge repository-local Git integration bindings (merge.generated-artifact.driver and core.hooksPath) via the modeled repo_local_git_config actuator — BEFORE the skew-guard git merge, so merge=generated-artifact rows govern the remedy merge rather than dying as unresolved conflicts. Ordering is after unpack (converge is gunbc run) and before ci_heal_binary_source_skew_guard_step (that step's git merge is the first merge on this checkout). Heal push uses git -c core.hooksPath= so the post-converge hooksPath binding does not invoke pre-push during the unattended push (cargo is absent from the hook PATH on heal runners — a push refusal, not a formatting finding). Idempotent: already-converged checkouts are a no-op." - - +// Immediately after the release gunbc binary is unpacked, converge repository-local Git integration +// bindings (merge.generated-artifact.driver and core.hooksPath) via the modeled +// repo_local_git_config actuator — BEFORE the skew-guard git merge, so merge=generated-artifact +// rows govern the remedy merge rather than dying as unresolved conflicts. Ordering: after unpack +// (converge is gunbc run) and before ci_heal_binary_source_skew_guard_step (whose git merge is the +// first merge on this checkout). Heal push uses git -c core.hooksPath= so the post-converge +// hooksPath binding does not invoke pre-push during the unattended push (cargo is absent from the +// hook PATH on heal runners — a push refusal, not a formatting finding). Idempotent: +// already-converged checkouts are a no-op. fn gunbc_ci_heal_repo_local_git_config_invoke() -> String { gunbc_run_step_script( @@ -1243,7 +1467,6 @@ fn gunbc_ci_heal_repo_local_git_config_invoke() -> String { ) } - fn gunbc_ci_heal_dispatch_invoke() -> String { gunbc_run_invocation_script_with_env( env: [ @@ -1257,13 +1480,85 @@ fn gunbc_ci_heal_dispatch_invoke() -> String { ) } -data ci_heal_dispatch_invoke_note: String = "After a successful push the heal invokes tools.ci_heal_dispatch, which binds the modeled github.Workflows.CreateDispatch operation. The moving branch ref and immutable HEALED_HEAD are distinct inputs. The dispatched run binds the branch resolution recorded in github.sha and its checkout head to that immutable input before any witness can count. The workflow RunStep projects github.token into its GITHUB_TOKEN environment because GitHub Actions does not inject the installation token as a process variable by default; CreateDispatch auth_source reads that env var. A transport refusal makes the gunbc invocation nonzero; the surrounding heal script classifies HealDispatchRefused, keeps revalidation required, and fails loudly. There is deliberately no fallback to the ordinary push event and no silent retry." - -data ci_heal_binary_source_skew_note: String = "RETIRED 2026-08-18 WITH THE REGEN ROOT CUT: the guard this note describes is DELETED and no function implements it. Everything below is the incident record that produced it, kept because the receipt outlives the mechanism; read it in the past tense. THE PAIRING THE HEAL JOB NEVER MODELED (incident 2026-07-29). A generated artifact's identity is (authority source x compiler), and this job silently pairs TWO DIFFERENT REVISIONS of that product: the release binary comes from `build`, which uses the default pull_request checkout — the MERGE ref (main + PR) — while this job checks out `github.head_ref`, the BRANCH HEAD, because it pushes the regeneration back (ci_heal_branch_checkout_note reasons carefully about the push semantics and never about the binary's provenance; it calls build's output 'the release gunbc binary', treating a projection of one revision as a context-free tool). The skew is latent until a seed change adds a Rust->.dag call: then the merge-ref binary invokes a `.dag` function the branch-head tree does not contain, `lookup_fn` misses, and the run dies mid-regen. Live receipt: #7348 landed the scoped-run observation seed, whose Rust calls gunbc.observation_seed_render.scoped_run_begin_write_measured; every open PR whose branch head predated it went red at once (#7392, #7394, #7395 — identical failure, none of them at fault), while main stayed green because ci_regen_heal_if skips this job on push. Reproduced by execution: ONE binary built from the merge ref, run against a pre-#7348 tree -> the exact CI failure; run against the same tree with origin/main merged in -> ExitSuccess, zero drift. The guard below refuses the unsound pairing instead of discovering it as a mid-regen crash." - -data ci_heal_binary_source_skew_guard_note: String = "RETIRED 2026-08-18 WITH THE REGEN ROOT CUT: the guard is DELETED. The operator ruling of 2026-07-29 recorded below is kept as the ruling it was, not as a description of live behaviour. THE COMPARAND IS THE BUILT-FROM REVISION, NOT A BRANCH NAME (operator ruling 2026-07-29). The first cut of this guard compared against `origin/main`, and that was wrong on the operator's own objection: `origin/main` names several different things — the one on this runner, the one on a developer's machine, and the actual source of truth — so a guard keyed on it asserts a relationship between the tree and WHICHEVER main this box last fetched, and can even race an advance mid-run. There is no ambiguity to resolve because the question was never about main: it is 'was this binary compiled from a revision this tree can satisfy'. `github.sha` answers it exactly — on a pull_request it IS the merge commit the build job checked out, so the guard compares the tree against the binary's own provenance, in the same workflow run, with no floating ref anywhere. The guard fails closed if that revision cannot be resolved: an unverifiable provenance is a refusal, never an assumed-fine.\n\nPRECISION IS ALSO THE POINT (DESIGN §5 — refuse, never widen; and never manufacture friction the defect does not warrant). A blanket 'tree must equal the built-from revision' would red nearly every PR to catch a rare pairing — a wall priced in other people's time. The DECIDABLE condition that actually matters is narrower: the binary is unsound against this tree exactly when the built-from revision carries SEED commits (src/v1 — the Rust the binary is compiled from) this tree lacks, because only those can introduce a Rust->.dag expectation the tree cannot satisfy. `git rev-list HEAD..$BUILT_FROM -- src/v1` is exactly that set: empty on the overwhelming majority of PRs (a .dag-only advance cannot skew a compiled binary), non-empty precisely in the #7348 shape. VERIFIED BY EXECUTION on the incident itself: at the pre-#7348 branch point the set is two commits and one of them IS 445bac31c (#7348), so the guard names the responsible commit in its own refusal; at current main and at the same branch after the advance was merged in, the set is empty and the guard is silent — the heal that went green on #7395 is the live receipt for the passing arm. On non-empty the step REFUSES rather than regenerating with a compiler that disagrees with the tree, which is the fabricated-plausible-output arm §5 forbids: main_wet would write whatever artifacts that mismatched pair produces and the commit-push step would push them to the branch. NOT IN SCOPE: preventing a stale branch from MERGING at all. That is merge-admission policy, it needs a source-of-truth model this repo does not have yet (operator: the SCM lane after v1 deletion), and this guard deliberately does not pretend to supply one — it protects the one pairing this job has agency over." - -data ci_heal_skew_auto_remedy_note: String = "RETIRED 2026-08-18 WITH THE REGEN ROOT CUT: the guard and its remedy are DELETED. The operator ruling of 2026-07-29 recorded below is kept as the ruling it was, not as a description of live behaviour. THE GUARD APPLIES ITS OWN REMEDY (operator ruling 2026-07-29: 'we can also try to apply the remedy automatically'). The first cut printed 'merge the base branch, then re-run' and exited 1, which is a correct refusal and a wasted round trip: the job holds contents:write, already pushes commits to this branch, and the fix is a merge it is fully authorized to perform. Making a human perform a mechanical repair the machine can do is the kind of toil this pipeline exists to remove.\n\nThis is a REPAIR, not a widen, and the distinction is the whole reason it is admissible under DESIGN §5. The refused arm does not proceed despite the skew — it REMOVES the skew by bringing the tree up to the revision the binary was built from, then re-checks the same condition it just failed. Regeneration runs only if that second check passes, so the artifacts are still projected by a compiler the tree agrees with. Nothing is assumed; the invariant is re-established and re-verified.\n\nBoth failure arms stay closed. A merge that CONFLICTS aborts and refuses with the original diagnostic plus the conflicting paths — the machine will not resolve semantic conflicts on an author's behalf. A merge that succeeds but leaves the seed still ahead (possible if the base moved again mid-run) refuses too, rather than looping. The remedy is attempted exactly once." +// After a successful push the heal invokes tools.ci_heal_dispatch, which binds the modeled +// github.Workflows.CreateDispatch operation. The moving branch ref and immutable HEALED_HEAD are +// distinct inputs. The dispatched run binds the branch resolution recorded in github.sha and its +// checkout head to that immutable input before any witness can count. The workflow RunStep projects +// github.token into its GITHUB_TOKEN environment because GitHub Actions does not inject the +// installation token as a process variable by default; CreateDispatch auth_source reads that env +// var. A transport refusal makes the gunbc invocation nonzero; the heal script classifies +// HealDispatchRefused, keeps revalidation required, and fails loudly. Deliberately no fallback to +// the ordinary push event and no silent retry. + +// RETIRED 2026-08-18 WITH THE REGEN ROOT CUT: the guard this note describes is DELETED and no +// function implements it. What follows is the incident record that produced it, kept because the +// receipt outlives the mechanism; read it in the past tense. THE PAIRING THE HEAL JOB NEVER MODELED +// (incident 2026-07-29). A generated artifact's identity is (authority source x compiler), and this +// job silently paired TWO DIFFERENT REVISIONS of that product: the release binary came from +// `build`, which uses the default pull_request checkout — the MERGE ref (main + PR) — while this +// job checked out `github.head_ref`, the BRANCH HEAD, because it pushes the regeneration back +// (ci_heal_branch_checkout_note reasons about push semantics and never about the binary's +// provenance; it calls build's output 'the release gunbc binary', treating a projection of one +// revision as a context-free tool). The skew is latent until a seed change adds a Rust->.dag call: +// then the merge-ref binary invokes a `.dag` function the branch-head tree lacks, `lookup_fn` +// misses, and the run dies mid-regen. Live receipt: #7348 landed the scoped-run observation seed, +// whose Rust calls gunbc.observation_seed_render.scoped_run_begin_write_measured; every open PR +// whose branch head predated it went red at once (#7392, #7394, #7395 — identical failure, none at +// fault), while main stayed green because ci_regen_heal_if skips this job on push. Reproduced by +// execution: ONE binary built from the merge ref, run against a pre-#7348 tree -> the exact CI +// failure; against the same tree with origin/main merged in -> ExitSuccess, zero drift. The guard +// refused the unsound pairing instead of discovering it as a mid-regen crash. + +// RETIRED 2026-08-18 WITH THE REGEN ROOT CUT: the guard is DELETED. The operator ruling of +// 2026-07-29 below is kept as the ruling it was, not as live behaviour. THE COMPARAND IS THE +// BUILT-FROM REVISION, NOT A BRANCH NAME (operator ruling 2026-07-29). The first cut compared +// against `origin/main`, wrong on the operator's own objection: `origin/main` names several things +// — the one on this runner, the one on a developer's machine, the actual source of truth — so a +// guard keyed on it asserts a relationship between the tree and WHICHEVER main this box last +// fetched, and can race an advance mid-run. The question was never about main: it is 'was this +// binary compiled from a revision this tree can satisfy'. `github.sha` answers it exactly — on a +// pull_request it IS the merge commit the build job checked out, so the guard compares the tree +// against the binary's own provenance, in the same run, with no floating ref. The guard fails +// closed if that revision cannot be resolved: unverifiable provenance is a refusal, never assumed +// fine. +// +// PRECISION IS ALSO THE POINT (DESIGN §5 — refuse, never widen; never manufacture friction the +// defect does not warrant). A blanket 'tree must equal the built-from revision' would red nearly +// every PR to catch a rare pairing — a wall priced in other people's time. The DECIDABLE condition +// that matters is narrower: the binary is unsound against this tree exactly when the built-from +// revision carries SEED commits (src/v1 — the Rust the binary is compiled from) this tree lacks, +// since only those can introduce a Rust->.dag expectation the tree cannot satisfy. `git rev-list +// HEAD..$BUILT_FROM -- src/v1` is exactly that set: empty on the overwhelming majority of PRs (a +// .dag-only advance cannot skew a compiled binary), non-empty precisely in the #7348 shape. +// VERIFIED BY EXECUTION on the incident: at the pre-#7348 branch point the set is two commits and +// one IS 445bac31c (#7348), so the guard names the responsible commit in its own refusal; at +// current main and at the same branch after the advance was merged in, the set is empty and the +// guard is silent — the heal that went green on #7395 is the live receipt for the passing arm. On +// non-empty the step REFUSES rather than regenerating with a compiler that disagrees with the tree +// — the fabricated-plausible-output arm §5 forbids: main_wet would write whatever the mismatched +// pair produces and the commit-push step would push it. NOT IN SCOPE: preventing a stale branch +// from MERGING at all. That is merge-admission policy, needs a source-of-truth model this repo does +// not have yet (operator: the SCM lane after v1 deletion), and this guard does not pretend to +// supply one — it protects the one pairing this job has agency over. + +// RETIRED 2026-08-18 WITH THE REGEN ROOT CUT: the guard and its remedy are DELETED. The operator +// ruling of 2026-07-29 below is kept as the ruling it was, not as live behaviour. THE GUARD APPLIES +// ITS OWN REMEDY (operator ruling 2026-07-29: 'we can also try to apply the remedy automatically'). +// The first cut printed 'merge the base branch, then re-run' and exited 1 — a correct refusal and a +// wasted round trip: the job holds contents:write, already pushes commits to this branch, and the +// fix is a merge it is authorized to perform. Making a human do a mechanical repair the machine can +// do is the toil this pipeline exists to remove. +// +// This is a REPAIR, not a widen, which is why it is admissible under DESIGN §5. The refused arm +// does not proceed despite the skew — it REMOVES the skew by bringing the tree up to the revision +// the binary was built from, then re-checks the same condition. Regeneration runs only if that +// second check passes, so the artifacts are still projected by a compiler the tree agrees with; the +// invariant is re-established and re-verified. +// +// Both failure arms stay closed. A merge that CONFLICTS aborts and refuses with the original +// diagnostic plus the conflicting paths — the machine will not resolve semantic conflicts on an +// author's behalf. A merge that succeeds but leaves the seed still ahead (possible if the base +// moved again mid-run) refuses too, rather than looping. The remedy is attempted exactly once. data ci_heal_author_commit_artifact_dir: String = "heal-author-commit-required" diff --git a/dag/gunbc/ci/ci_workflow_expressions.dag b/dag/gunbc/ci/ci_workflow_expressions.dag index 8cc0b3283b9..3756099bc2a 100644 --- a/dag/gunbc/ci/ci_workflow_expressions.dag +++ b/dag/gunbc/ci/ci_workflow_expressions.dag @@ -131,13 +131,87 @@ data ci_fleet_job_if: String = "!cancelled() && ((github.ref == 'refs/heads/main data ci_floor_runs_unless_cancelled_if: String = "!cancelled()" -data ci_floor_upstream_red_refusal_script: String = "set -e\nBUILD='${{ needs.build.result }}'\nREGEN='${{ needs.regen.result }}'\nHEAL='${{ needs.heal_generated_artifacts.result }}'\nif [ \"$BUILD\" != \"success\" ] || [ \"$REGEN\" != \"success\" ] || [ \"$HEAL\" = \"failure\" ]; then\necho \"::error::FloorUpstreamAlreadyRed build=$BUILD regen=$REGEN heal=$HEAL — an upstream job already established this branch is red, so the floor refuses before spending its budget. The located cause is that job's own diagnostic; this step adds no verdict of its own.\"\nexit 1\nfi\necho \"[floor-preflight] upstream clean: build=$BUILD regen=$REGEN heal=$HEAL\"" - -data ci_floor_upstream_red_refusal_note: String = "THE FLOOR REFUSES ON AN ALREADY-RED BRANCH BY RUNNING AND FAILING FAST, NEVER BY BEING SKIPPED. Goal (operator analysis 2026-08-05): 'a branch can already be known to have ci.yml or generated-artifact drift while the 65-minute floor continues consuming a runner'. ci needs [build, regen, heal_generated_artifacts] so the floor is orderable after the cheap preflights, and this step is its first step.\n\nTHE FIRST IMPLEMENTATION OF THIS ROW WAS A FAIL-OPEN AND IS RETRACTED (review 49309, cursor/composer-2.5, 2026-08-06). It set the job condition to `!failure() && !cancelled()`, so an upstream failure SKIPPED ci. The retracted note asserted 'a skipped job does not satisfy a required check, so this makes drift BLOCK the merge'. That sentence was false, and it was written from reasoning rather than from a receipt — the §5 trap of fluent output that was never run. GitHub's own troubleshooting-required-status-checks page states that a job skipped by a conditional REPORTS SUCCESS, and that a job skipped because a needed job failed 'may not block merging'. The live default-branch ruleset (id 16178731) names exactly one required context, `ci`; heal_generated_artifacts is not required. Composing those two facts: drift would have left ci skipped-as-success while only the non-required heal job was red, so GeneratedArtifactDriftGate and the merge-admission receipt stamping could be bypassed entirely. The change intended to remove wasted computation would have removed the only merge wall.\n\nWHAT HOLDS NOW. The condition is `!cancelled()`, so ci RUNS whenever the run is live — including when an upstream job failed — and the required check therefore always produces a real verdict. The refusal moved into this step, which is the job's first step and reads needs..result directly: build and regen must be `success`, and heal must not be `failure`. A refusal is a typed, located, exit-1 RED on the required context, not an absence. The budget saving is preserved because the step is a runner spin-up plus a string compare, not a floor run.\n\nheal is allowed to be `skipped` because it is pull_request-only (ci_regen_heal_if); on push to main, merge_group and workflow_dispatch it does not run and the floor proceeds, preserving main's cold control. Only `failure` refuses, which covers both HealAuthorCommitRequired (drift the app cannot push) and the deliberate SupersededByHealedHead exit that fires after heal auto-pushes a repair — in the latter case the superseded head correctly stops and the healed commit gets its own run.\n\nA SIDE EFFECT WORTH NAMING: before this change ci carried no condition at all, so a failed build or regen SKIPPED it, and by the same GitHub rule that skip did not reliably block merging either. Making the job unconditional-on-failure closes that pre-existing hole in the same motion rather than leaving a second instance of the class behind." +// set -e +// BUILD='${{ needs.build.result }}' +// REGEN='${{ needs.regen.result }}' +// HEAL='${{ needs.heal_generated_artifacts.result }}' +// if [ "$BUILD" != "success" ] || [ "$REGEN" != "success" ] || [ "$HEAL" = "failure" ]; then +// echo "::error::FloorUpstreamAlreadyRed build=$BUILD regen=$REGEN heal=$HEAL — an upstream job +// already established this branch is red, so the floor refuses before spending its budget. The +// located cause is that job's own diagnostic; this step adds no verdict of its own." +// exit 1 +// fi +// echo "[floor-preflight] upstream clean: build=$BUILD regen=$REGEN heal=$HEAL" + +// THE FLOOR REFUSES ON AN ALREADY-RED BRANCH BY RUNNING AND FAILING FAST, NEVER BY BEING SKIPPED. +// Goal (operator analysis 2026-08-05): 'a branch can already be known to have ci.yml or +// generated-artifact drift while the 65-minute floor continues consuming a runner'. ci needs +// [build, regen, heal_generated_artifacts] so the floor is orderable after the cheap preflights, +// and this step is its first step. +// +// THE FIRST IMPLEMENTATION OF THIS ROW WAS A FAIL-OPEN AND IS RETRACTED (review 49309, +// cursor/composer-2.5, 2026-08-06). It set the job condition to `!failure() && !cancelled()`, so an +// upstream failure SKIPPED ci. The retracted note asserted 'a skipped job does not satisfy a +// required check, so this makes drift BLOCK the merge'. That sentence was false, and it was written +// from reasoning rather than from a receipt — the §5 trap of fluent output that was never run. +// GitHub's own troubleshooting-required-status-checks page states that a job skipped by a +// conditional REPORTS SUCCESS, and that a job skipped because a needed job failed 'may not block +// merging'. The live default-branch ruleset (id 16178731) names exactly one required context, `ci`; +// heal_generated_artifacts is not required. Composing those two facts: drift would have left ci +// skipped-as-success while only the non-required heal job was red, so GeneratedArtifactDriftGate +// and the merge-admission receipt stamping could be bypassed entirely. The change intended to +// remove wasted computation would have removed the only merge wall. +// +// WHAT HOLDS NOW. The condition is `!cancelled()`, so ci RUNS whenever the run is live — including +// when an upstream job failed — and the required check therefore always produces a real verdict. +// The refusal moved into this step, which is the job's first step and reads needs..result +// directly: build and regen must be `success`, and heal must not be `failure`. A refusal is a +// typed, located, exit-1 RED on the required context, not an absence. The budget saving is +// preserved because the step is a runner spin-up plus a string compare, not a floor run. +// +// heal is allowed to be `skipped` because it is pull_request-only (ci_regen_heal_if); on push to +// main, merge_group and workflow_dispatch it does not run and the floor proceeds, preserving main's +// cold control. Only `failure` refuses, which covers both HealAuthorCommitRequired (drift the app +// cannot push) and the deliberate SupersededByHealedHead exit that fires after heal auto-pushes a +// repair — in the latter case the superseded head correctly stops and the healed commit gets its +// own run. +// +// A SIDE EFFECT WORTH NAMING: before this change ci carried no condition at all, so a failed build +// or regen SKIPPED it, and by the same GitHub rule that skip did not reliably block merging either. +// Making the job unconditional-on-failure closes that pre-existing hole in the same motion rather +// than leaving a second instance of the class behind. data ci_concurrency_cancel_in_progress_if: String = "${{ github.event_name != 'merge_group' }}" -data ci_concurrency_event_scoped_stop_policy_note: String = "Event-scoped stop policy (operator ruling 2026-07-22; CI RESET lane 2026-08-10; main-push exemption 2026-08-13): cancel-in-progress is true for pull_request / push / workflow_dispatch and false for merge_group (the queue must not kill an in-flight check when another PR enters the queue — gunbc.ci_workflow ci_merge_group_trigger_note). SUPERSESSION REQUIRES BOTH ARMS: cancel-in-progress plus a shared group key, and main is now exempted through the GROUP arm rather than the cancel arm. ci_concurrency_discriminator groups PR events by pull_request.number, push-to-main by github.sha, and everything else by github.ref — never run_id, which is unique per run and would give every event its own group, making cancellation unreachable everywhere while this note still read as if it worked.\n\nWHY THE GROUP ARM AND NOT THE CANCEL ARM. Setting cancel-in-progress false for main would leave main pushes sharing one group key, and GitHub QUEUES same-group runs when cancellation is off. Measured 2026-08-13: the floor's merge-critical wall is 84.2 minutes (run 31672107941, the last main run to complete) against a 9.8-minute median gap between main pushes — 8.6x — so a queued policy accumulates an unbounded backlog and delivers each verdict hours after the commit it judges. A per-commit group key runs them concurrently instead, which is the only arm that yields a verdict per main commit at this arrival rate.\n\nWHAT THIS COSTS, stated because it is a real load increase and not a free win: a superseded main run previously died after roughly 10 minutes, so cancellation was also acting as an unmeasured load shedder. Retaining every main run raises average main CI cost from that partial run to the full 84 minutes, roughly 8x aggregate main load on a fixed self-hosted fleet. That is the price of observing main at all, and it falls as the floor shortens. If fleet saturation appears before the floor lands, the next move is batching main verdicts across commits — NOT restoring cancellation, which trades the saturation problem for a blindness problem.\n\nThe expression pair is the single authority; dissolve-on: typed EventStopPolicy + group-key rows per trigger replace the hand-spelled strings when the workflow model carries event-grain policy by construction." +// Event-scoped stop policy (operator ruling 2026-07-22; CI RESET lane 2026-08-10; main-push +// exemption 2026-08-13): cancel-in-progress is true for pull_request / push / workflow_dispatch and +// false for merge_group (the queue must not kill an in-flight check when another PR enters the +// queue — gunbc.ci_workflow ci_merge_group_trigger_note). SUPERSESSION REQUIRES BOTH ARMS: +// cancel-in-progress plus a shared group key, and main is now exempted through the GROUP arm rather +// than the cancel arm. ci_concurrency_discriminator groups PR events by pull_request.number, +// push-to-main by github.sha, and everything else by github.ref — never run_id, which is unique per +// run and would give every event its own group, making cancellation unreachable everywhere while +// this note still read as if it worked. +// +// WHY THE GROUP ARM AND NOT THE CANCEL ARM. Setting cancel-in-progress false for main would leave +// main pushes sharing one group key, and GitHub QUEUES same-group runs when cancellation is off. +// Measured 2026-08-13: the floor's merge-critical wall is 84.2 minutes (run 31672107941, the last +// main run to complete) against a 9.8-minute median gap between main pushes — 8.6x — so a queued +// policy accumulates an unbounded backlog and delivers each verdict hours after the commit it +// judges. A per-commit group key runs them concurrently instead, which is the only arm that yields +// a verdict per main commit at this arrival rate. +// +// WHAT THIS COSTS, stated because it is a real load increase and not a free win: a superseded main +// run previously died after roughly 10 minutes, so cancellation was also acting as an unmeasured +// load shedder. Retaining every main run raises average main CI cost from that partial run to the +// full 84 minutes, roughly 8x aggregate main load on a fixed self-hosted fleet. That is the price +// of observing main at all, and it falls as the floor shortens. If fleet saturation appears before +// the floor lands, the next move is batching main verdicts across commits — NOT restoring +// cancellation, which trades the saturation problem for a blindness problem. +// +// The expression pair is the single authority; dissolve-on: typed EventStopPolicy + group-key rows +// per trigger replace the hand-spelled strings when the workflow model carries event-grain policy +// by construction. data ci_concurrency_event_scoped_stop_policy_disposition: Disposition = Scaffold { dissolves_to: SingleAuthority, diff --git a/dag/gunbc/citation/pdf_safe_profile.dag b/dag/gunbc/citation/pdf_safe_profile.dag index dc6df603af5..17f1e23bba7 100644 --- a/dag/gunbc/citation/pdf_safe_profile.dag +++ b/dag/gunbc/citation/pdf_safe_profile.dag @@ -29,7 +29,9 @@ import extdeps.standards.rfc_8118 { project_navigable_uri, } -data pdf_safe_profile_note: String = "gunbc bounded admission policy for RFC 8118 PDF open parameters — NOT upstream semantics. extdeps.standards.rfc_8118 owns the full RFC vocabulary; this module declines parameters gunbc has not yet admitted for citation use." +// gunbc bounded admission policy for RFC 8118 PDF open parameters — NOT upstream semantics. +// extdeps.standards.rfc_8118 owns the full RFC vocabulary; this module declines parameters gunbc +// has not yet admitted for citation use. type PdfRefusedParameterKind = PdfParameterComment diff --git a/dag/gunbc/claim_unwind_seed_growth.dag b/dag/gunbc/claim_unwind_seed_growth.dag index d2dbc88143d..1b50cba00ef 100644 --- a/dag/gunbc/claim_unwind_seed_growth.dag +++ b/dag/gunbc/claim_unwind_seed_growth.dag @@ -4,32 +4,31 @@ import gunbc.roadmap_model { RoadmapNodeId } import gunbc.seed_growth { SeedGrowthJustification } import std.decl_ref { DeclarationRef, WholeDeclaration } -// FORWARD-FREEZE RECEIPT for the hand Rust added by the claim-unwind terminal repair. -// Authored because gunbc.seed_growth_admission seed_growth_forward_freeze_policy_note makes -// unenumerated hand growth in src/v1 a stop-line, and this change adds two hand items to -// v1_compiler.cli_run. That row also records, measured, that zero of the four PRs adding hand -// src/v1 Rust in the 48 hours to 2026-08-24 were caught by an unassisted reviewer -- so a row -// authored only when a review asks for it is a row the roster cannot rely on. +// FORWARD-FREEZE RECEIPT for the hand Rust added by the claim-unwind terminal repair. Authored +// because gunbc.seed_growth_admission seed_growth_forward_freeze_policy_note makes unenumerated +// hand growth in src/v1 a stop-line, and this change adds two hand items to v1_compiler.cli_run. +// That row also records, measured, that zero of the four PRs adding hand src/v1 Rust in the 48 +// hours to 2026-08-24 were caught by an unassisted reviewer -- so a row authored only when a +// review asks for it is a row the roster cannot rely on. // -// WHAT THE CHANGE IS. ClaimOutcome carries a typed arm for every non-verdict a claim can reach -// -- runtime error, not-Bool, budget interruption, unresolved host tool, route gap -- and had -// none for an UNWIND. Measured on this branch's baseline: a panic raised in witness evaluation -// outside compile_dag_diagnostic_census's catch_unwind ABORTED the process with exit 101, and -// the identity produced no PASS/FAIL line at all. The terminal ledger, the expected-red roster -// join and the disposition TSV are all written after the fold, so none of the three existed; -// every remaining planned claim went unmeasured and the artifact that would have said so was -// never written. +// WHAT THE CHANGE IS. ClaimOutcome carries a typed arm for every non-verdict a claim can reach -- +// runtime error, not-Bool, budget interruption, unresolved host tool, route gap -- and had none +// for an UNWIND. Measured on this branch's baseline: a panic raised in witness evaluation outside +// compile_dag_diagnostic_census's catch_unwind ABORTED the process with exit 101, and the identity +// produced no PASS/FAIL line at all. The terminal ledger, the expected-red roster join and the +// disposition TSV are all written after the fold, so none of the three existed; every remaining +// planned claim went unmeasured and the artifact that would have said so was never written. // -// WHY IT IS RUST AND NOT .dag, which is the question this receipt exists to answer honestly. -// The VOCABULARY is not modeled here: ClaimAttemptTerminal, ClaimDisposition and their wire -// tags are v2.workflow.floor_terminal_ledger and floor_terminal_ledger_wire, and the seed's -// arms are a MIRROR of that coproduct -- the same relation floor_terminal_ledger.dag's own -// header already declares for this file, under a production per-row comparator that requires -// the two mappings to agree. What cannot move is CATCHING THE UNWIND: std::panic::catch_unwind -// is a property of the host that runs the evaluator, and the substrate has no representation of -// a Rust stack unwinding through an interpreter frame. Modeling it would require the .dag side -// to name the seed's own unwind mechanism, which is a layer inversion and not a migration -- -// the same boundary gunbc.witness_runtime_cause_seed_growth records one seam over. +// WHY IT IS RUST AND NOT .dag, the question this receipt exists to answer honestly. The +// VOCABULARY is not modeled here: ClaimAttemptTerminal, ClaimDisposition and their wire tags are +// v2.workflow.floor_terminal_ledger and floor_terminal_ledger_wire, and the seed's arms are a +// MIRROR of that coproduct -- the relation floor_terminal_ledger.dag's own header declares for +// this file, under a production per-row comparator requiring the two mappings to agree. What +// cannot move is CATCHING THE UNWIND: std::panic::catch_unwind is a property of the host that runs +// the evaluator, and the substrate has no representation of a Rust stack unwinding through an +// interpreter frame. Modeling it would require the .dag side to name the seed's own unwind +// mechanism, a layer inversion and not a migration -- the boundary +// gunbc.witness_runtime_cause_seed_growth records one seam over. // // WHAT IS NOT NETTED. Several existing items were MODIFIED rather than added and are // ExistingSeedItemModified per gunbc.seed_growth_admission SeedGrowthChangeDisposition, so they @@ -41,11 +40,11 @@ import std.decl_ref { DeclarationRef, WholeDeclaration } // HAND-ITEM DELTA: +2 citable declarations, both in v1_compiler.cli_run. // HAND-LOC DELTA: +394 / -19 in src/v1/stage0/src/cli_run.rs, +19 / -0 in // src/v1/stage0/src/bin/claim_batch.rs, +2 / -1 in src/v1/stage0/src/bin/claim_executor.rs, -// measured by `git diff --numstat` against the merge base. The two bin files add no -// declarations -- their deltas are one arm each on an existing report match, plus one counter -// added to the floor's summary line. The cli_run figure is majority COMMENT: the two new items -// are 41 lines of code between them, and the rest is the fold's stop-the-line argument, the -// not-attempted publication, and the reverse-join suspension the halt makes necessary. +// measured by `git diff --numstat` against the merge base. The two bin files add no declarations +// -- one arm each on an existing report match, plus one counter on the floor's summary line. The +// cli_run figure is majority COMMENT: the two new items are 41 lines of code between them; the +// rest is the fold's stop-the-line argument, the not-attempted publication, and the reverse-join +// suspension the halt makes necessary. data claim_unwind_seed_growth_justification: SeedGrowthJustification = SeedGrowthJustification { hand_authored_declarations: [ DeclarationRef { module_path: "v1_compiler.cli_run", decl_name: "run_claim_evaluation", field: WholeDeclaration }, diff --git a/dag/gunbc/class_b_import_closure_overlay.dag b/dag/gunbc/class_b_import_closure_overlay.dag index f1ccedfce74..a6e717e727e 100644 --- a/dag/gunbc/class_b_import_closure_overlay.dag +++ b/dag/gunbc/class_b_import_closure_overlay.dag @@ -7,12 +7,21 @@ import std.decl_ref { DeclarationRef, WholeDeclaration } import gunbc.seed_growth { SeedGrowthJustification } import gunbc.roadmap_model { RoadmapNodeId } -data class_b_overlay_note: String = "Derives the Class B stripped entry overlay from the live rust_test_fixtures.dag at execution time (#7817 strip_import_block property, moved to the executing transport/witness path). No checked-in copy of the live corpus file — the overlay is read from disk, the declared rust import block removed under a typed strip receipt, and written under a caller-supplied overlay root so pool-membership coincidence cannot drift silently." +// Derives the Class B stripped entry overlay from the live rust_test_fixtures.dag at execution time +// (#7817 strip_import_block property, moved to the executing transport/witness path). No checked-in +// copy of the live corpus file — the overlay is read from disk, the declared rust import block +// removed under a typed strip receipt, and written under a caller-supplied overlay root so +// pool-membership coincidence cannot drift silently. data class_b_entry_rel: String = "src/v2/extdeps/languages/rust_test_fixtures.dag" data class_b_rust_import_prefix: String = "import v2.extdeps.languages.rust" -data class_b_declared_import_pool_roots_note: String = "Minimal explicit pool for Class B rows 1-2 (#7835): the declared-import-closure footprint for rust_test_fixtures — entry plus v2.extdeps.languages.rust and its transitive std/extdeps dependencies — NOT witness_layer_roots. Stronger control: binding is observed with no unrelated ambient modules that could accidentally supply rust symbols; row 2 prepends fixtures/class_b_import_closure/perturbation_overlay to this baseline only. Rows 3-4 on the transport gate keep witness_layer_roots for stripped-entry pool-coincidence pressure." +// Minimal explicit pool for Class B rows 1-2 (#7835): the declared-import-closure footprint for +// rust_test_fixtures — entry plus v2.extdeps.languages.rust and its transitive std/extdeps +// dependencies — NOT witness_layer_roots. Stronger control: binding is observed with no unrelated +// ambient modules that could accidentally supply rust symbols; row 2 prepends +// fixtures/class_b_import_closure/perturbation_overlay to this baseline only. Rows 3-4 on the +// transport gate keep witness_layer_roots for stripped-entry pool-coincidence pressure. data class_b_accidental_coverage_seed_growth_justification: SeedGrowthJustification = SeedGrowthJustification { hand_authored_declarations: [ diff --git a/dag/gunbc/claude_setup_token_enrollment.dag b/dag/gunbc/claude_setup_token_enrollment.dag index cca87754811..763a9394a73 100644 --- a/dag/gunbc/claude_setup_token_enrollment.dag +++ b/dag/gunbc/claude_setup_token_enrollment.dag @@ -55,13 +55,27 @@ import extdeps.llm.cli_lifecycle { } import std.claim_evidence { RecordedFactId } -data claude_setup_token_enrollment_authority_note: String = "Claude Code enrollment station workflow (operator direction 2026-08-07; revised msg_3b291a6a). The enrollment host is where the irreducible browser gesture happens — NOT the roadmap worker host. Enrollment INITIATES ONLY through the pinned Claude CLI setup-token path. This module is a STANDING PRODUCER: it emits ProviderStanding from managed enrollment; it does NOT wire OfferClaude into production dispatch inventory (WorkerTurn PR). NO WorkerTurn and NO UI workflow progress in this slice." - -data claude_enrollment_no_invented_binding_note: String = "HARD PROHIBITION (msg_3b291a6a): no exact enrollment host, GCP project, secret ID, or Unix service-principal binding is established anywhere in this module. Operator proposals (claude-personal-max, claude-code-oauth-personal-max, gunbai-secrets) are NOT selected values. Model the shape; leave values unbound; refuse rather than default." - -data claude_ambient_oauth_home_separate_lifecycle_note: String = "Ambient claudeAiOauth home (refresh/access state, finite refresh-token expiry) may mutate in normal operation — write-back behaviour for THAT realization must be MEASURED, not assumed. CLAUDE_CODE_OAUTH_TOKEN from setup-token is supplied through environment with NO evidence the runtime mutates or rotates it; setup-token standing uses ClaudeSetupTokenStanding, not the ambient OAuth write-back path." - -data claude_enrollment_no_provider_authenticated_literal_note: String = "Acceptance: no authored ProviderAuthenticated literal from extdeps.llm.cli_lifecycle exists anywhere in this workflow. Authentication standing is projected into gunbc.provider_standing ProviderAuthenticationStanding only." +// Claude Code enrollment station workflow (operator direction 2026-08-07; revised msg_3b291a6a). +// The enrollment host is where the irreducible browser gesture happens — NOT the roadmap worker +// host. Enrollment INITIATES ONLY through the pinned Claude CLI setup-token path. This module is a +// STANDING PRODUCER: it emits ProviderStanding from managed enrollment; it does NOT wire +// OfferClaude into production dispatch inventory (WorkerTurn PR). NO WorkerTurn and NO UI workflow +// progress in this slice. + +// HARD PROHIBITION (msg_3b291a6a): no exact enrollment host, GCP project, secret ID, or Unix +// service-principal binding is established anywhere in this module. Operator proposals +// (claude-personal-max, claude-code-oauth-personal-max, gunbai-secrets) are NOT selected values. +// Model the shape; leave values unbound; refuse rather than default. + +// Ambient claudeAiOauth home (refresh/access state, finite refresh-token expiry) may mutate in +// normal operation — write-back behaviour for THAT realization must be MEASURED, not assumed. +// CLAUDE_CODE_OAUTH_TOKEN from setup-token is supplied through environment with NO evidence the +// runtime mutates or rotates it; setup-token standing uses ClaudeSetupTokenStanding, not the +// ambient OAuth write-back path. + +// Acceptance: no authored ProviderAuthenticated literal from extdeps.llm.cli_lifecycle exists +// anywhere in this workflow. Authentication standing is projected into gunbc.provider_standing +// ProviderAuthenticationStanding only. type ClaudeSetupTokenStanding = SetupTokenUsable @@ -339,11 +353,27 @@ data claude_enrollment_managed_paired_probe_scaffold: Disposition = Scaffold { } } -data claude_enrollment_workflow_stage_scaffold_registry_note: String = "DECLARED SCAFFOLDS (wind-down landing state, msg_0ec68b71): each unimplemented host effect names a Scaffold disposition row — claude_enrollment_spawn_cli_private_pty_scaffold, claude_enrollment_observe_browser_challenge_scaffold, claude_enrollment_capture_setup_token_private_scaffold, claude_enrollment_secret_manager_add_version_scaffold, claude_enrollment_exact_version_read_back_scaffold, claude_enrollment_clean_context_materialization_scaffold, claude_enrollment_managed_paired_probe_scaffold — umbrella claude_enrollment_host_effect_realization_scaffold. ProviderStanding receipt projection is pure model (claude_enrollment_provider_standing_from_bundle); host probe that feeds it is scaffolded." +// DECLARED SCAFFOLDS (wind-down landing state, msg_0ec68b71): each unimplemented host effect names +// a Scaffold disposition row — claude_enrollment_spawn_cli_private_pty_scaffold, +// claude_enrollment_observe_browser_challenge_scaffold, +// claude_enrollment_capture_setup_token_private_scaffold, +// claude_enrollment_secret_manager_add_version_scaffold, +// claude_enrollment_exact_version_read_back_scaffold, +// claude_enrollment_clean_context_materialization_scaffold, +// claude_enrollment_managed_paired_probe_scaffold — umbrella +// claude_enrollment_host_effect_realization_scaffold. ProviderStanding receipt projection is pure +// model (claude_enrollment_provider_standing_from_bundle); host probe that feeds it is scaffolded. -data claude_enrollment_acceptance_control_note: String = "ACCEPTANCE CONTROL (msg_3b291a6a): with the enrollment host ambient credential HIDDEN OR ABSENT, the workflow initiates setup-token, the operator authorizes, an exact Secret Manager version is produced; then a CLEAN SECOND CONTEXT materializes only that exact version and reaches authenticated Claude subscription standing." +// ACCEPTANCE CONTROL (msg_3b291a6a): with the enrollment host ambient credential HIDDEN OR ABSENT, +// the workflow initiates setup-token, the operator authorizes, an exact Secret Manager version is +// produced; then a CLEAN SECOND CONTEXT materializes only that exact version and reaches +// authenticated Claude subscription standing. -data claude_enrollment_workflow_realization_note: String = "Required host effects (not yet realized): spawn exact Claude CLI in a private PTY; observe browser challenge and project ONLY url and code; capture token privately; Secret Manager AddVersion; exact-version independent read-back; clean-context materialization; managed paired probe; ProviderStanding receipt. Until realization lands this module remains an enrollment specification with honest scaffold refusal — not a silent no-op." +// Required host effects (not yet realized): spawn exact Claude CLI in a private PTY; observe +// browser challenge and project ONLY url and code; capture token privately; Secret Manager +// AddVersion; exact-version independent read-back; clean-context materialization; managed paired +// probe; ProviderStanding receipt. Until realization lands this module remains an enrollment +// specification with honest scaffold refusal — not a silent no-op. fn claude_enrollment_root_for_account(account_label: NonEmptyStr) -> FilePath { join( @@ -773,11 +803,17 @@ fn claude_enrollment_standing_bundle_before_probe() -> ProviderStandingBundle { } } -data claude_paired_probe_open_question_note: String = "Step 7 MEASURES an open question for the first time. The unauthenticated 7921 receipt leaves authenticated entitlement dissolution undecided. A negative authenticated probe result is a finding, not a harness bug." +// Step 7 MEASURES an open question for the first time. The unauthenticated 7921 receipt leaves +// authenticated entitlement dissolution undecided. A negative authenticated probe result is a +// finding, not a harness bug. -data claude_enrollment_writer_lease_version_order_note: String = "incumbent_version_is_newer is computed at the host boundary with numeric Secret Manager version ordering. The substrate does not compare version strings lexicographically here because GCP version ids are integers and lexicographic order disagrees on multi-digit ids." +// incumbent_version_is_newer is computed at the host boundary with numeric Secret Manager version +// ordering. The substrate does not compare version strings lexicographically here because GCP +// version ids are integers and lexicographic order disagrees on multi-digit ids. -data claude_enrollment_native_limit_encoding_note: String = "Limits axis encodes SDK rateLimitType tokens as ProviderNativeLimitBucket { native_key } — never ProviderModelLimit from token spelling (seven_day_opus is not a cited Opus join). Authority: extdeps.llm.claude_sdk claude_native_limit_token_to_model_gap_note." +// Limits axis encodes SDK rateLimitType tokens as ProviderNativeLimitBucket { native_key } — never +// ProviderModelLimit from token spelling (seven_day_opus is not a cited Opus join). Authority: +// extdeps.llm.claude_sdk claude_native_limit_token_to_model_gap_note. fn claude_enrollment_limit_subject( credential_label: NonEmptyStr, @@ -849,6 +885,13 @@ fn claude_enrollment_standing_bundle_from_paired_probe_verdict( } } +// Clean-second-context materialization: load CLAUDE_CODE_OAUTH_TOKEN from +// ClaudeExactSecretVersionBinding via gunbc.auth.materialized_secret EnvVar binding in a context +// that does NOT inherit the enrollment host ambient credential. Setup-token capture does NOT use +// admit_ambient_oauth_home_write_back — rotation semantics are +// SetupTokenRotationSemanticsUnobserved until measured. Host binding realization remains a declared +// scaffold until gunbc.auth.materialized_secret host effects land. + fn claude_enrollment_provider_standing_from_bundle( session: ClaudeEnrollmentSession, bundle: ProviderStandingBundle, @@ -878,6 +921,6 @@ fn claude_enrollment_provider_standing_from_bundle( ) } -data claude_enrollment_materialization_frontier_note: String = "Clean-second-context materialization: load CLAUDE_CODE_OAUTH_TOKEN from ClaudeExactSecretVersionBinding via gunbc.auth.materialized_secret EnvVar binding in a context that does NOT inherit the enrollment host ambient credential. Setup-token capture does NOT use admit_ambient_oauth_home_write_back — rotation semantics are SetupTokenRotationSemanticsUnobserved until measured. Host binding realization remains a declared scaffold until gunbc.auth.materialized_secret host effects land." - -data claude_enrollment_writer_lease_epoch_note: String = "One account credential has exactly one writer lease epoch. admit_enrollment_writer_lease_publish refuses when holder_epoch < current_epoch so a stale writer cannot publish after another lease epoch advanced. Concurrent holder and newer-binding refusals remain independent axes." +// One account credential has exactly one writer lease epoch. admit_enrollment_writer_lease_publish +// refuses when holder_epoch < current_epoch so a stale writer cannot publish after another lease +// epoch advanced. Concurrent holder and newer-binding refusals remain independent axes. diff --git a/dag/gunbc/cli_run_hand_rust_area_ledger.dag b/dag/gunbc/cli_run_hand_rust_area_ledger.dag index 42237115686..5e13e27c73e 100644 --- a/dag/gunbc/cli_run_hand_rust_area_ledger.dag +++ b/dag/gunbc/cli_run_hand_rust_area_ledger.dag @@ -5,11 +5,87 @@ import std.types { Int, List, NonEmptyStr, String } data cli_run_hand_rust_area_ledger_note: String = "LIVE ITEM-GRAIN ledger for cli_run.rs hand Rust. Supersedes cli_run_hollowing_plan section 0 stale prose. Area grain follows the 16 functional areas in gunbc.plans.cli_run_hollowing_plan sections 2.1 to 2.16. NOT a per-function ledger. NOT a deletion plan -- instrument only. This note does NOT pin numeric literals, per DESIGN section 3's stale-citation class." -data cli_run_ledger_measurement_route_missing: String = "THE LEDGER HAS NO EXECUTABLE MEASUREMENT ROUTE, AND THIS ROW SAYS SO INSTEAD OF PRETENDING OTHERWISE. Until 2026-08-24 the module note above named scripts/rust_item_census.py as the instrument that re-derived this ledger's live scale at execution -- item identity primary, LOC secondary. THAT FILE IS DELETED. It went with the measurement bankruptcy (gunbc#9132), which removed docs/probes/ whole and left no .sh or .py anywhere in the repository outside .githooks, under the ruling that an ad-hoc script is DESIGN section 6 unmodeled realization: if a measurement is worth re-deriving it is worth a .dag entry point, and if it is not worth an entry point it is not an instrument. So the ledger has been citing a dead instrument since that cut, which is the rot mode DESIGN section 3 predicts of a citation nothing checks. WHAT IS MISSING, precisely, because 'no instrument' is too coarse to act on. Two different measurements have no route, and they fail for different reasons. (1) LIVE SCALE -- how much hand Rust cli_run.rs still carries, at item grain. Nothing in the tree re-derives it. (2) ANCHOR LIVENESS -- whether a symbol named in anchor_symbols still names a declared Rust item, and where. No host builtin answers 'does this Rust symbol exist and in which file'; the module-fact builtins reach .dag modules, not .rs items, so this is not a matter of wiring an existing feed up. RESTORATION TRIGGER: a .dag entry point that walks the seed's Rust sources and returns item identities with their declaring file, cited by name from this module -- at which point both measurements become derivable and anchor_home below stops being an authored field. WHAT THIS ROW DOES NOT CLAIM: that the ledger is unusable meanwhile. The disposition carrier below is now a construction rather than a transcription, which is a real climb and is independent of this gap; what stays unmeasured is the SCALE the ledger reports on, not the READINESS it asserts." +// THE LEDGER HAS NO EXECUTABLE MEASUREMENT ROUTE, AND THIS ROW SAYS SO INSTEAD OF PRETENDING +// OTHERWISE. Until 2026-08-24 the module note above named scripts/rust_item_census.py as the +// instrument that re-derived this ledger's live scale at execution -- item identity primary, LOC +// secondary. THAT FILE IS DELETED. It went with the measurement bankruptcy (gunbc#9132), which +// removed docs/probes/ whole and left no .sh or .py anywhere in the repository outside .githooks, +// under the ruling that an ad-hoc script is DESIGN section 6 unmodeled realization: if a +// measurement is worth re-deriving it is worth a .dag entry point, and if it is not worth an entry +// point it is not an instrument. So the ledger has been citing a dead instrument since that cut, +// which is the rot mode DESIGN section 3 predicts of a citation nothing checks. WHAT IS MISSING, +// precisely, because 'no instrument' is too coarse to act on. Two different measurements have no +// route, and they fail for different reasons. (1) LIVE SCALE -- how much hand Rust cli_run.rs still +// carries, at item grain. Nothing in the tree re-derives it. (2) ANCHOR LIVENESS -- whether a +// symbol named in anchor_symbols still names a declared Rust item, and where. No host builtin +// answers 'does this Rust symbol exist and in which file'; the module-fact builtins reach .dag +// modules, not .rs items, so this is not a matter of wiring an existing feed up. RESTORATION +// TRIGGER: a .dag entry point that walks the seed's Rust sources and returns item identities with +// their declaring file, cited by name from this module -- at which point both measurements become +// derivable and anchor_home below stops being an authored field. WHAT THIS ROW DOES NOT CLAIM: that +// the ledger is unusable meanwhile. The disposition carrier below is now a construction rather than +// a transcription, which is a real climb and is independent of this gap; what stays unmeasured is +// the SCALE the ledger reports on, not the READINESS it asserts. -data cli_run_area_anchor_retirement_receipt: String = "FOUR ANCHOR SYMBOLS ARE RETIRED IN THIS EDIT, AND TWO MORE ARE RE-HOMED. The grain is the anchor symbol, not the row, and the reason is recorded rather than assumed: this ledger was once DELETED WHOLE by a cut and then restored, because deleting it unmarked 22 live areas in order to retire one symbol. Restoring it unedited would have been the mirror error -- an anchor pointing at a symbol that no longer exists is an obligation nobody can discharge, so it never retires and reads as live debt forever. Neither whole-file answer was available; both halves are answerable per symbol.\n\nRETIRED, each because the symbol names nothing in the seed: load_both_closure and run_witness_batch resolve nowhere in src/v1/stage0/src at all; merge_envs survives only inside a prose string in a generated file, its subject having been DELETED by operator ruling on 2026-07-06 (v1.compiler.infer_env visible_bindings_invariant records that deletion, and DESIGN section 6 carries the receipt for why the example that cited it was kept without it); regen_stage0 survives only in generated file headers, its binary having been deleted at the root by the regen cut. Their rows SURVIVE -- 2.4, 2.5, 2.10 and 2.12 each keep their other anchors and their dispositions -- because retiring a symbol is not retiring an area.\n\nRE-HOMED rather than retired: eval_builtin and floor_materialization are both LIVE and neither is in cli_run.rs. eval_builtin is a real function in v1_interpreter.rs, which is correct for area 2.13 (the interpreter seam) and merely not in this ledger's nominal subject file; floor_materialization is a .dag module read from claim_executor.rs. ONE ANCHOR IS ALSO RESPELLED, and it is a different defect from either of the two above: area 2.11 carried the anchor \"compile_clean\", which is a PREFIX rather than a symbol -- it matches compile_clean_scope_plan_for_ci, compile_clean_source_roots, compile_clean_diagnostic_is_hard and a dozen more, so it names no declaration and any liveness check over it answers yes forever regardless of what is actually there. It is replaced by compile_clean_scope_plan_for_ci, a real function in the subject and the symbol DESIGN's CI section already names when it reports that this area's selection capability has no caller. A prefix anchor is the positional-citation failure wearing a name: it looks symbolic and resolves to nothing in particular. That mix is what the anchor_home field below exists to make visible: the previous shape was a bare List, so a reader could not tell an anchor in the subject from an anchor somewhere else in the seed from an anchor naming nothing at all, and all three read identically." +// FOUR ANCHOR SYMBOLS ARE RETIRED IN THIS EDIT, AND TWO MORE ARE RE-HOMED. The grain is the anchor +// symbol, not the row, and the reason is recorded rather than assumed: this ledger was once DELETED +// WHOLE by a cut and then restored, because deleting it unmarked 22 live areas in order to retire +// one symbol. Restoring it unedited would have been the mirror error -- an anchor pointing at a +// symbol that no longer exists is an obligation nobody can discharge, so it never retires and reads +// as live debt forever. Neither whole-file answer was available; both halves are answerable per +// symbol. +// +// RETIRED, each because the symbol names nothing in the seed: load_both_closure and +// run_witness_batch resolve nowhere in src/v1/stage0/src at all; merge_envs survives only inside a +// prose string in a generated file, its subject having been DELETED by operator ruling on +// 2026-07-06 (v1.compiler.infer_env visible_bindings_invariant records that deletion, and DESIGN +// section 6 carries the receipt for why the example that cited it was kept without it); +// regen_stage0 survives only in generated file headers, its binary having been deleted at the root +// by the regen cut. Their rows SURVIVE -- 2.4, 2.5, 2.10 and 2.12 each keep their other anchors and +// their dispositions -- because retiring a symbol is not retiring an area. +// +// RE-HOMED rather than retired: eval_builtin and floor_materialization are both LIVE and neither is +// in cli_run.rs. eval_builtin is a real function in v1_interpreter.rs, which is correct for area +// 2.13 (the interpreter seam) and merely not in this ledger's nominal subject file; +// floor_materialization is a .dag module read from claim_executor.rs. ONE ANCHOR IS ALSO RESPELLED, +// and it is a different defect from either of the two above: area 2.11 carried the anchor +// "compile_clean", which is a PREFIX rather than a symbol -- it matches +// compile_clean_scope_plan_for_ci, compile_clean_source_roots, compile_clean_diagnostic_is_hard and +// a dozen more, so it names no declaration and any liveness check over it answers yes forever +// regardless of what is actually there. It is replaced by compile_clean_scope_plan_for_ci, a real +// function in the subject and the symbol DESIGN's CI section already names when it reports that +// this area's selection capability has no caller. A prefix anchor is the positional-citation +// failure wearing a name: it looks symbolic and resolves to nothing in particular. That mix is what +// the anchor_home field below exists to make visible: the previous shape was a bare List, +// so a reader could not tell an anchor in the subject from an anchor somewhere else in the seed +// from an anchor naming nothing at all, and all three read identically. -data cli_run_disposition_is_derived_not_transcribed: String = "THE CARRIER NOW REFUSES AN UNEARNED READINESS, WHICH IS THE POINT OF THIS EDIT. GenerateNow means 'the v2 replacement exists, generate it'. As a BARE VARIANT it was a transcription -- a claim about readiness that only a human's memory could check, and one that had gone false underneath eight rows without any of them changing. The 2026-08-15 floor cut DELETED the machinery two of those rows named as their replacement (gunbc.ci_floor_plan and src/v2/workflow/ci_floor_plan.dag are both absent from the tree) rather than replacing it, and for the other six nothing in the repository establishes that the named v2 authority carries the area -- the module existing is necessary and nowhere near sufficient. A row asserting a readiness with no referent is rung inflation in DESIGN section 4b's sense, and its cost is exactly the one that clause names: an inflated row never ranks for climbing, because it already reports the state it is supposed to reach.\n\nTHE FIX IS CONSTRUCTION, NOT A CORRECTED TRANSCRIPTION. GenerateNow now CARRIES the entry point that emits the area, so the claim cannot be written without naming the thing that would discharge it -- 'name the instrument, never transcribe its output', applied to a disposition. No such entry point can be named for any of the sixteen areas today, so no row is GenerateNow, and that is a fact about the tree rather than a judgement about it. Correspondingly UnclassifiedStopLine carries its cause, so a stop-line row says WHICH of the two situations it is in -- replacement deleted, or replacement never established -- which are different problems with different owners and were previously the same variant.\n\nWHAT THIS DOES NOT CLAIM: that the eight retyped areas are un-generatable, or that the six whose named authority still stands are as far from generation as the two whose authority was deleted. It claims only that nothing in the tree currently establishes readiness, which is what the row is now shaped to record. A row climbs back to GenerateNow the moment someone can name its emitting entry, and that is a one-field edit rather than an argument." +// THE CARRIER NOW REFUSES AN UNEARNED READINESS, WHICH IS THE POINT OF THIS EDIT. GenerateNow means +// 'the v2 replacement exists, generate it'. As a BARE VARIANT it was a transcription -- a claim +// about readiness that only a human's memory could check, and one that had gone false underneath +// eight rows without any of them changing. The 2026-08-15 floor cut DELETED the machinery two of +// those rows named as their replacement (gunbc.ci_floor_plan and src/v2/workflow/ci_floor_plan.dag +// are both absent from the tree) rather than replacing it, and for the other six nothing in the +// repository establishes that the named v2 authority carries the area -- the module existing is +// necessary and nowhere near sufficient. A row asserting a readiness with no referent is rung +// inflation in DESIGN section 4b's sense, and its cost is exactly the one that clause names: an +// inflated row never ranks for climbing, because it already reports the state it is supposed to +// reach. +// +// THE FIX IS CONSTRUCTION, NOT A CORRECTED TRANSCRIPTION. GenerateNow now CARRIES the entry point +// that emits the area, so the claim cannot be written without naming the thing that would discharge +// it -- 'name the instrument, never transcribe its output', applied to a disposition. No such entry +// point can be named for any of the sixteen areas today, so no row is GenerateNow, and that is a +// fact about the tree rather than a judgement about it. Correspondingly UnclassifiedStopLine +// carries its cause, so a stop-line row says WHICH of the two situations it is in -- replacement +// deleted, or replacement never established -- which are different problems with different owners +// and were previously the same variant. +// +// WHAT THIS DOES NOT CLAIM: that the eight retyped areas are un-generatable, or that the six whose +// named authority still stands are as far from generation as the two whose authority was deleted. +// It claims only that nothing in the tree currently establishes readiness, which is what the row is +// now shaped to record. A row climbs back to GenerateNow the moment someone can name its emitting +// entry, and that is a one-field edit rather than an argument. type CliRunHandRustAreaDisposition = DeleteNow @@ -212,7 +288,11 @@ fn cli_run_hand_rust_area_ledger_covers_sixteen_areas() -> Bool { cli_run_hand_rust_area_row_count() == 16 } -data generate_now_row_count_note: String = "The readiness reader, and it counts ROWS rather than answering a Bool so that a caller can tell 'none are ready' from 'the ledger is empty'. Its RED is authorable in a fixture -- a test module constructs a row carrying GenerateNow with an emitting entry -- which is what keeps the live-tree assertion in dag/test/claim/cli_run_area_ledger_witness_test.dag a check rather than a decoration." +// The readiness reader, and it counts ROWS rather than answering a Bool so that a caller can tell +// 'none are ready' from 'the ledger is empty'. Its RED is authorable in a fixture -- a test module +// constructs a row carrying GenerateNow with an emitting entry -- which is what keeps the live-tree +// assertion in dag/test/claim/cli_run_area_ledger_witness_test.dag a check rather than a +// decoration. fn cli_run_area_generate_now_row_count(rows: List) -> Int { fold(rows, init: 0, f: (acc, r) => match r.disposition { diff --git a/dag/gunbc/cli_run_repo_grant.dag b/dag/gunbc/cli_run_repo_grant.dag index de1c50c4585..4c0b1da08ac 100644 --- a/dag/gunbc/cli_run_repo_grant.dag +++ b/dag/gunbc/cli_run_repo_grant.dag @@ -14,7 +14,16 @@ import std.effect_grant { import std.filesystem_path_grant { repo_rel_path_position } import std.materialization_ladder { Frame, SharedStateFrame } -data cli_run_repo_grant_note: String = "P-B repo-tree grant authority (docs/plans/effect-namespace-grants.md 6): dissolves the hand-rolled workspace_root containment gate in src/v1/stage0/src/cli_run.rs (repo_relative_path / repo_relative_path_normalized) into the landed std.effect_grant model. The process workspace is Read+Write under an abstract repo subtree; paths that escape (absolute outside repo, or a `..` path segment) are located std.access.Deny refusals from the EffectRequest profile, never widened (DESIGN 5). HAND-RUST until Chunk F (docs/plans/cli-run-reconcile-defork.md, seed-shrink-census Chunk F — scheduled, not indefinite): Rust gate and this authority must not diverge; pinned by dag/test/claim/cli_run_repo_grant_hand_rust_equivalence_witness_test.dag + cli_run.rs cli_run_repo_grant_equivalence_tests. dissolve-on = cli_run.rs Chunk F lands (GENERATED workflow host-effect apply) OR ROADMAP 5-dissolve-patches retires HAND path handling." +// P-B repo-tree grant authority (docs/plans/effect-namespace-grants.md 6): dissolves the +// hand-rolled workspace_root containment gate in src/v1/stage0/src/cli_run.rs (repo_relative_path / +// repo_relative_path_normalized) into the landed std.effect_grant model. The process workspace is +// Read+Write under an abstract repo subtree; paths that escape (absolute outside repo, or a `..` +// path segment) are located std.access.Deny refusals from the EffectRequest profile, never widened +// (DESIGN 5). HAND-RUST until Chunk F (docs/plans/cli-run-reconcile-defork.md, seed-shrink-census +// Chunk F — scheduled, not indefinite): Rust gate and this authority must not diverge; pinned by +// dag/test/claim/cli_run_repo_grant_hand_rust_equivalence_witness_test.dag + cli_run.rs +// cli_run_repo_grant_equivalence_tests. dissolve-on = cli_run.rs Chunk F lands (GENERATED workflow +// host-effect apply) OR ROADMAP 5-dissolve-patches retires HAND path handling. data cli_run_repo_frame: Frame = Frame { name: "cli-run-repo", diff --git a/dag/gunbc/codex_app_server_press.dag b/dag/gunbc/codex_app_server_press.dag index a31a92e99b6..fa990e16c5a 100644 --- a/dag/gunbc/codex_app_server_press.dag +++ b/dag/gunbc/codex_app_server_press.dag @@ -90,13 +90,40 @@ import extdeps.languages.json.parse { JsonMemberNotAnObject, } -data codex_press_note: String = "Account-standing observation for the Codex app-server specimen. This module does NOT claim a provider turn. Rate-limit standing is PER limit_id (rateLimitsByLimitId): a live Pro account can exhaust the codex bucket while codex_bengalfox remains at usedPercent 0 — collapsing to one account-level Ready|QuotaReached is false. CodexPressAccountReady gates on the native limit_id codex bucket only; every bucket verdict is retained on the evidence for ProviderNativeLimitBucket bridging. Empty rateLimits:\{\} is Incomplete. Trip stdin must send initialized after initialize or id=3 may never arrive." +// Account-standing observation for the Codex app-server specimen. This module does NOT claim a +// provider turn. Rate-limit standing is PER limit_id (rateLimitsByLimitId): a live Pro account can +// exhaust the codex bucket while codex_bengalfox remains at usedPercent 0 — collapsing to one +// account-level Ready|QuotaReached is false. CodexPressAccountReady gates on the native limit_id +// codex bucket only; every bucket verdict is retained on the evidence for ProviderNativeLimitBucket +// bridging. Empty rateLimits:{} is Incomplete. Trip stdin must send initialized after initialize or +// id=3 may never arrive. data codex_press_hermetic_evidence_root: FilePath = "/tmp/gunbc-codex-press-evidence" as FilePath -data account_trip_digest_mode_note: String = "Hermetic parse helpers (parse_account_trip_stdout_for_tests) use AccountTripDigestStructural — content_hash_of_value fingerprints with zero host effects, so discovery stays mock-free. Wet observe_codex_account_preflight uses AccountTripDigestRetainPrivateSha512 under a caller-supplied evidence_root. Structural digests are NOT durable content identities; private SHA-512 retention is." - -data account_standing_authenticated_rate_limits_readable_note: String = "CODEX-BUCKET-SCOPED, not account-wide entitlement or limit input. AccountStandingAuthenticatedRateLimitsReadable and CodexPressAccountReady gate on codex_default_rate_limit_id alone — the backward-compatible rateLimits mirror key (live limitId=codex), NOT proof that an unspecified-model turn/start draws that bucket (see extdeps.llm.codex_app_server codex_model_to_limit_bucket_gap_note). Ready means that one bucket shows remaining capacity (usedPercent below 100 without rateLimitReachedType). Sibling buckets stay on evidence.rate_limit_buckets and must not be erased into this classification. INVERTED FAILURE: codex free while a sibling bucket (e.g. codex_bengalfox) is exhausted still yields Ready here — fabricated-plausible account standing if a consumer treats CodexPressAccountReady or this classification as account-wide eligibility. Any consumer needing account-wide entitlement or per-bucket limit input MUST read evidence.rate_limit_buckets per limit_id (CodexRateLimitBucketVerdict) and bridge ProviderNativeLimitBucket \{ native_key: limit_id \} per codex_rate_limit_subject_encoding_note — never ProviderAccountLimit, and never CodexPressAccountReady alone as the limit axis. spendControlReached true is exhaustion when observed; live exhaustion often has it false — usedPercent and rateLimitReachedType are the operative facts. Bridge into gunbc.provider_standing LimitObservedAvailable|LimitUnavailableUntilReset dissolves when that consumer lands on this receipt." +// Hermetic parse helpers (parse_account_trip_stdout_for_tests) use AccountTripDigestStructural — +// content_hash_of_value fingerprints with zero host effects, so discovery stays mock-free. Wet +// observe_codex_account_preflight uses AccountTripDigestRetainPrivateSha512 under a caller-supplied +// evidence_root. Structural digests are NOT durable content identities; private SHA-512 retention +// is. + +// CODEX-BUCKET-SCOPED, not account-wide entitlement or limit input. +// AccountStandingAuthenticatedRateLimitsReadable and CodexPressAccountReady gate on +// codex_default_rate_limit_id alone — the backward-compatible rateLimits mirror key (live +// limitId=codex), NOT proof that an unspecified-model turn/start draws that bucket (see +// extdeps.llm.codex_app_server codex_model_to_limit_bucket_gap_note). Ready means that one bucket +// shows remaining capacity (usedPercent below 100 without rateLimitReachedType). Sibling buckets +// stay on evidence.rate_limit_buckets and must not be erased into this classification. INVERTED +// FAILURE: codex free while a sibling bucket (e.g. codex_bengalfox) is exhausted still yields Ready +// here — fabricated-plausible account standing if a consumer treats CodexPressAccountReady or this +// classification as account-wide eligibility. Any consumer needing account-wide entitlement or +// per-bucket limit input MUST read evidence.rate_limit_buckets per limit_id +// (CodexRateLimitBucketVerdict) and bridge ProviderNativeLimitBucket { native_key: limit_id } per +// codex_rate_limit_subject_encoding_note — never ProviderAccountLimit, and never +// CodexPressAccountReady alone as the limit axis. spendControlReached true is exhaustion when +// observed; live exhaustion often has it false — usedPercent and rateLimitReachedType are the +// operative facts. Bridge into gunbc.provider_standing +// LimitObservedAvailable|LimitUnavailableUntilReset dissolves when that consumer lands on this +// receipt. type AccountTripDigestMode = AccountTripDigestStructural @@ -129,16 +156,26 @@ fn codex_press_account_trip_methods() -> CodexPressItinerary { ] } -data codex_press_turn_itinerary_target_note: String = "TARGET plan only — not executed by observe_codex_account_preflight. A TurnStarted constructor must not exist until both thread/start and turn/start responses supply exact provider IDs joined to a WorkerTurn." +// TARGET plan only — not executed by observe_codex_account_preflight. A TurnStarted constructor +// must not exist until both thread/start and turn/start responses supply exact provider IDs joined +// to a WorkerTurn. -data codex_credential_liveness_note: String = "account/read IS NOT credential liveness (continuity receipt 2026-08-13). The same dead CODEX_HOME answered chatgpt/pro on one probe and account:null a minute later — realm is non-deterministic until a background token refresh fails. Liveness derives ONLY from account/rateLimits/read exercising the token upstream: decodable buckets mean the credential was live for that trip; an empty bucket list after a typed rate-limits result means dead, not unobserved. Realm and liveness are independent axes — a CodexChatgptRealm { plan: pro } beside CodexCredentialDead is the trap the standing bridge must not collapse." +// account/read IS NOT credential liveness (continuity receipt 2026-08-13). The same dead CODEX_HOME +// answered chatgpt/pro on one probe and account:null a minute later — realm is non-deterministic +// until a background token refresh fails. Liveness derives ONLY from account/rateLimits/read +// exercising the token upstream: decodable buckets mean the credential was live for that trip; an +// empty bucket list after a typed rate-limits result means dead, not unobserved. Realm and liveness +// are independent axes — a CodexChatgptRealm { plan: pro } beside CodexCredentialDead is the trap +// the standing bridge must not collapse. type CodexCredentialLiveness = CodexCredentialLive { bucket_count: Int } | CodexCredentialDead { reason: NonEmptyStr } | CodexCredentialLivenessUnobserved { reason: NonEmptyStr } -data codex_supervised_turn_delegation_note: String = "Supervised-runner execution lease, runner-written terminal receipt, and turn/start admission live in gunbc.codex_supervised_turn on std.temporal_effect HeldLease — not a forked session carrier in this module." +// Supervised-runner execution lease, runner-written terminal receipt, and turn/start admission live +// in gunbc.codex_supervised_turn on std.temporal_effect HeldLease — not a forked session carrier in +// this module. fn codex_press_turn_itinerary_target() -> CodexPressItinerary { concat( @@ -151,7 +188,17 @@ fn codex_press_method_itinerary() -> CodexPressItinerary { codex_press_turn_itinerary_target() } -data codex_press_standing_evidence_note: String = "Account standing is derived from ONE held-open app-server --stdio session: three JSON-RPC requests (ids 1/2/3) with per-id typed result/error correlation and duplicate-id refusal. Wire capture is retained privately under a caller-supplied evidence_root via gunbc.provider_wire_evidence (SHA-512 content-addressed stdout/stderr). Unknown NDJSON frames are digested SHA-512 through the same retention path; retention failure is Incomplete, never silent drop. AuthenticatedRateLimitsReadable requires AccountTripResponseUnique on ids 1/2/3 with typed result bodies AND retained wire evidence. Incomplete is refused (RED). Login/quota are typed arms. methods_attempted is the typed itinerary, never a free-form string that can disagree with the outcome. Production placement supplies gunbc.host_layout.ProviderRuntimePlacement.wire_evidence_root (srv1 via srv1_provider_runtime_placement) and GunbcHostLayout.roadmap_codex_home." +// Account standing is derived from ONE held-open app-server --stdio session: three JSON-RPC +// requests (ids 1/2/3) with per-id typed result/error correlation and duplicate-id refusal. Wire +// capture is retained privately under a caller-supplied evidence_root via +// gunbc.provider_wire_evidence (SHA-512 content-addressed stdout/stderr). Unknown NDJSON frames are +// digested SHA-512 through the same retention path; retention failure is Incomplete, never silent +// drop. AuthenticatedRateLimitsReadable requires AccountTripResponseUnique on ids 1/2/3 with typed +// result bodies AND retained wire evidence. Incomplete is refused (RED). Login/quota are typed +// arms. methods_attempted is the typed itinerary, never a free-form string that can disagree with +// the outcome. Production placement supplies +// gunbc.host_layout.ProviderRuntimePlacement.wire_evidence_root (srv1 via +// srv1_provider_runtime_placement) and GunbcHostLayout.roadmap_codex_home. type AccountTripFrameDisposition = AccountTripResultOk @@ -183,7 +230,21 @@ type CodexRateLimitBucketVerdict } | CodexBucketCapacityUnknown { limit_id: NonEmptyStr } -data codex_account_realm_note: String = "THE REALM IS THE FACT THE DISPOSITION THREW AWAY. disposition_from_account_read_result decodes `account` to AccountTripResultOk when the object is nonempty and stops there, so an apiKey account, a Bedrock account and a personal ChatGPT subscription all reach the same Ready. Those are three different credential realizations (extdeps.llm.codex_app_server codex_account_union_note), and a profile requiring personal ChatGPT OAuth is satisfied by exactly one of them.\n\nThis coproduct is the decode of that union, plus the states the wire can reach that the union does not name: a null or absent account, a `type` this pin does not recognize, and a plan string outside the cited enum. Every one of those is its own arm, because the remedies differ — log in, update the pin, refuse the profile.\n\nCodexChatgptRealm carries the decoded plan rather than a Bool because entitlement is NOT derivable here. Which plans entitle which work is gunbc policy and lives at the standing bridge; this module's job is to say truthfully what upstream reported." +// THE REALM IS THE FACT THE DISPOSITION THREW AWAY. disposition_from_account_read_result decodes +// `account` to AccountTripResultOk when the object is nonempty and stops there, so an apiKey +// account, a Bedrock account and a personal ChatGPT subscription all reach the same Ready. Those +// are three different credential realizations (extdeps.llm.codex_app_server +// codex_account_union_note), and a profile requiring personal ChatGPT OAuth is satisfied by exactly +// one of them. +// +// This coproduct is the decode of that union, plus the states the wire can reach that the union +// does not name: a null or absent account, a `type` this pin does not recognize, and a plan string +// outside the cited enum. Every one of those is its own arm, because the remedies differ — log in, +// update the pin, refuse the profile. +// +// CodexChatgptRealm carries the decoded plan rather than a Bool because entitlement is NOT +// derivable here. Which plans entitle which work is gunbc policy and lives at the standing bridge; +// this module's job is to say truthfully what upstream reported. type CodexPlanTypeReading = CodexPlanTypeObserved { plan: CodexPlanType } @@ -343,7 +404,15 @@ type CodexAccountStandingSummary { evidence: CodexAccountStandingEvidence } -data provider_process_exit_divergence_note: String = "ProviderProcessExit is the press receipt projection, not a second std.process authority. std.process.ProcessExit (ExitSuccess | ExitFailure with code and reason) models CLI/program exit semantics with a typed reason string. ProcessExitObservation in gunbc.provider_wire_evidence models shell.Exec transport (success bool + optional exit_code from the host). ProviderProcessExited carries only the parsed sentinel exit code while ProviderProcessObservationFailed means the mux marker or exit observation was absent — a press-parse outcome, not a host refusal reason. Converge when shell→intent typed stdio transport lands (docs/plans/shell-intent-emit-realization-design.md) and replaces sentinel reparsing — this coproduct deletes with that scaffold." +// ProviderProcessExit is the press receipt projection, not a second std.process authority. +// std.process.ProcessExit (ExitSuccess | ExitFailure with code and reason) models CLI/program exit +// semantics with a typed reason string. ProcessExitObservation in gunbc.provider_wire_evidence +// models shell.Exec transport (success bool + optional exit_code from the host). +// ProviderProcessExited carries only the parsed sentinel exit code while +// ProviderProcessObservationFailed means the mux marker or exit observation was absent — a +// press-parse outcome, not a host refusal reason. Converge when shell→intent typed stdio transport +// lands (docs/plans/shell-intent-emit-realization-design.md) and replaces sentinel reparsing — this +// coproduct deletes with that scaffold. type ProviderProcessExit = ProviderProcessExited { code: Int } @@ -497,7 +566,15 @@ fn json_rpc_id_as_int(v: JsonValue) -> Int? { } } -data json_rpc_error_message_note: String = "NO CANNED MESSAGE (review 50535). This read previously answered the literal \"json-rpc error\" whenever err.message was absent, empty, or any non-string shape — a structured error carrying code plus data, a nested message object, an array, null — so a real provider cause was reported as a fabricated string that reads exactly like a message the server sent (DESIGN §5 fabricated plausible output). The arms now separate: a genuine string message is carried verbatim, and every shape that is not one lands on AccountTripNativeCauseMessageUnreadable, which carries no text at all. That variant is deliberately field-free — inventing a description of the shape we failed to read would re-introduce the same fabrication one level down; the frame's own retained wire evidence is where the unread bytes stay recoverable." +// NO CANNED MESSAGE (review 50535). This read previously answered the literal "json-rpc error" +// whenever err.message was absent, empty, or any non-string shape — a structured error carrying +// code plus data, a nested message object, an array, null — so a real provider cause was reported +// as a fabricated string that reads exactly like a message the server sent (DESIGN §5 fabricated +// plausible output). The arms now separate: a genuine string message is carried verbatim, and every +// shape that is not one lands on AccountTripNativeCauseMessageUnreadable, which carries no text at +// all. That variant is deliberately field-free — inventing a description of the shape we failed to +// read would re-introduce the same fabrication one level down; the frame's own retained wire +// evidence is where the unread bytes stay recoverable. fn json_rpc_error_message(err: JsonValue) -> NonEmptyStr? { match json_object_unique_member(v: err, key: "message") { @@ -587,7 +664,17 @@ fn json_number_as_epoch_secs(v: JsonValue) -> EpochSecs? { } } -data rate_limit_used_percent_carrier_frontier: String = "usedPercent is a DIMENSIONLESS RATIO and std.measure.Percent (Measure) is its authority — this is a consumer of that carrier, not a new unit, and no nickname is minted here. It is carried as bounded Int TODAY for exactly the reason std.measure.percent_from_computed_int_frontier states: percent(count: Nat) admits a LITERAL Nat only, and runtime eval refuses a computed Int→Nat cast, while this value is decoded from the app-server's JSON (json_number_as_int) and is therefore computed by construction. Same wall, same disposition as extdeps.color.srgb HslProjection s_pct/l_pct, which are bounded Int percent counts citing that identical frontier. DISSOLVE-ON: the numeric-tower Int=Nat grounding extending to computed values (same lane as the CrossRepresentationEquality guard removal) — at which point this fn returns Percent? and the comparison against the exhaustion threshold reads through percent_count with no change of meaning." +// usedPercent is a DIMENSIONLESS RATIO and std.measure.Percent (Measure) +// is its authority — this is a consumer of that carrier, not a new unit, and no nickname is minted +// here. It is carried as bounded Int TODAY for exactly the reason +// std.measure.percent_from_computed_int_frontier states: percent(count: Nat) admits a LITERAL Nat +// only, and runtime eval refuses a computed Int→Nat cast, while this value is decoded from the +// app-server's JSON (json_number_as_int) and is therefore computed by construction. Same wall, same +// disposition as extdeps.color.srgb HslProjection s_pct/l_pct, which are bounded Int percent counts +// citing that identical frontier. DISSOLVE-ON: the numeric-tower Int=Nat grounding extending to +// computed values (same lane as the CrossRepresentationEquality guard removal) — at which point +// this fn returns Percent? and the comparison against the exhaustion threshold reads through +// percent_count with no change of meaning. fn rate_limit_window_used_percent(window: JsonValue) -> Int? { match json_object_unique_member( @@ -691,7 +778,20 @@ fn rate_limits_reached_type(snapshot: JsonValue) -> NonEmptyStr? { } } -data rate_limit_id_reading_note: String = "THREE DISTINGUISHABLE WIRE STATES, NOT ONE (review 50532). This read previously collapsed absent-key, empty-string, and wrong-shape into one synthesized codex_default_rate_limit_id, which consumers could not tell from a real wire id of the same text — the DESIGN section 5 absorbing fallback (a failure arm must refuse, never widen). The harm is specific and this module's own account_standing_authenticated_rate_limits_readable_note already names it: downstream reads buckets BY limit_id, so a bucket whose real id was codex_bengalfox emitted under the fabricated id codex misattributes a sibling bucket's limits to the codex bucket — exactly the inverted failure that note warns produces fabricated-plausible account standing. Only ONE of the three states legitimately defaults: an ABSENT key is the backward-compatible rateLimits mirror shape, whose id is codex by the mirror's definition rather than by fabrication. An empty or non-string id is malformed wire and refuses. On refusal the caller emits NO bucket rather than one under a guessed name: every CodexRateLimitBucketVerdict variant requires a limit_id, so a malformed id cannot name a bucket at all, and emitting none is strictly safer than emitting one that a per-limit_id consumer will read as a different bucket." +// THREE DISTINGUISHABLE WIRE STATES, NOT ONE (review 50532). This read previously collapsed +// absent-key, empty-string, and wrong-shape into one synthesized codex_default_rate_limit_id, which +// consumers could not tell from a real wire id of the same text — the DESIGN section 5 absorbing +// fallback (a failure arm must refuse, never widen). The harm is specific and this module's own +// account_standing_authenticated_rate_limits_readable_note already names it: downstream reads +// buckets BY limit_id, so a bucket whose real id was codex_bengalfox emitted under the fabricated +// id codex misattributes a sibling bucket's limits to the codex bucket — exactly the inverted +// failure that note warns produces fabricated-plausible account standing. Only ONE of the three +// states legitimately defaults: an ABSENT key is the backward-compatible rateLimits mirror shape, +// whose id is codex by the mirror's definition rather than by fabrication. An empty or non-string +// id is malformed wire and refuses. On refusal the caller emits NO bucket rather than one under a +// guessed name: every CodexRateLimitBucketVerdict variant requires a limit_id, so a malformed id +// cannot name a bucket at all, and emitting none is strictly safer than emitting one that a +// per-limit_id consumer will read as a different bucket. type CodexRateLimitIdReading = CodexRateLimitIdObserved { limit_id: NonEmptyStr } @@ -895,7 +995,10 @@ fn classification_from_codex_default_bucket( } } -data account_trip_structured_error_code_note: String = "Business classifications Login/Quota are authorized only by typed result members or a structured JSON-RPC error `code` string that exactly matches a known native code from extdeps.llm.codex_app_server. Free-text `message` alone yields AccountTripNativeCauseUnknown → Incomplete — never Ready/Login/Quota (provider-audit handback)." +// Business classifications Login/Quota are authorized only by typed result members or a structured +// JSON-RPC error `code` string that exactly matches a known native code from +// extdeps.llm.codex_app_server. Free-text `message` alone yields AccountTripNativeCauseUnknown → +// Incomplete — never Ready/Login/Quota (provider-audit handback). fn disposition_from_structured_rpc_error(err: JsonValue) -> AccountTripFrameDisposition { match json_object_unique_member(v: err, key: "code") { @@ -1073,7 +1176,11 @@ fn buckets_from_rate_limits_frame( } } -data codex_account_realm_duplicate_frame_note: String = "FIRST FRAME WINS, mirroring buckets_from_rate_limits_frame. A duplicate id=2 response already makes the disposition AccountTripResponseDuplicate, which the classifier treats as Incomplete, so the realm from a second frame could never be trusted anyway — and letting it overwrite would mean a later frame silently replacing the account the first one reported. Duplicates keep the prior value and the duplicate observation carries the refusal." +// FIRST FRAME WINS, mirroring buckets_from_rate_limits_frame. A duplicate id=2 response already +// makes the disposition AccountTripResponseDuplicate, which the classifier treats as Incomplete, so +// the realm from a second frame could never be trusted anyway — and letting it overwrite would mean +// a later frame silently replacing the account the first one reported. Duplicates keep the prior +// value and the duplicate observation carries the refusal. fn realm_from_account_read_frame( current: AccountTripResponseObservation, diff --git a/dag/gunbc/codex_provider_runtime_receipt.dag b/dag/gunbc/codex_provider_runtime_receipt.dag index e9e926eeaf5..c8fd76be5c9 100644 --- a/dag/gunbc/codex_provider_runtime_receipt.dag +++ b/dag/gunbc/codex_provider_runtime_receipt.dag @@ -8,7 +8,17 @@ import gunbc.host_layout { } import gunbc.package_delivery { codex_runtime_version_identity_marker } -data codex_provider_runtime_receipt_authority_note: String = "ONE authority for the Codex provider-runtime receipt #8224 consumes. Placement (all three ProviderRuntimePlacement roots), bundled executable path, exact bundle identity (receipt_identity hex from the materialized release sidecar), and process fingerprint are derived from what converge installed — never hand-authored literals agreeing across branches (operator ruling 2026-08-13). Process fingerprint is CodexProcessFingerprintUnobserved until a wet tmux observation on the bundled binary lands; dispatch must not treat the legacy dispatch_codex_process_fingerprint literal as evidence. Publication target: ProviderRuntimePlacement.wire_evidence_root/codex-provider-runtime/HOST.json — written when converge succeeds; absent until first successful fleet execution (#8226 prerequisite). srv2/srv3/srv4 standing is unobserved from the operator key; do not claim observed placement beyond what a host receipt proves." +// ONE authority for the Codex provider-runtime receipt #8224 consumes. Placement (all three +// ProviderRuntimePlacement roots), bundled executable path, exact bundle identity (receipt_identity +// hex from the materialized release sidecar), and process fingerprint are derived from what +// converge installed — never hand-authored literals agreeing across branches (operator ruling +// 2026-08-13). Process fingerprint is CodexProcessFingerprintUnobserved until a wet tmux +// observation on the bundled binary lands; dispatch must not treat the legacy +// dispatch_codex_process_fingerprint literal as evidence. Publication target: +// ProviderRuntimePlacement.wire_evidence_root/codex-provider-runtime/HOST.json — written when +// converge succeeds; absent until first successful fleet execution (#8226 prerequisite). +// srv2/srv3/srv4 standing is unobserved from the operator key; do not claim observed placement +// beyond what a host receipt proves. type CodexProcessFingerprintStanding = CodexProcessFingerprintObserved { fingerprint: NonEmptyStr } diff --git a/dag/gunbc/codex_runtime_bundle.dag b/dag/gunbc/codex_runtime_bundle.dag index 0e3d90fb52d..933491f8aa8 100644 --- a/dag/gunbc/codex_runtime_bundle.dag +++ b/dag/gunbc/codex_runtime_bundle.dag @@ -12,9 +12,17 @@ import gunbc.package_delivery { codex_runtime_manifest_path, } -data codex_runtime_bundle_note: String = "Release-bundled Codex runtime at codex_required_package_version (gunbc.package_delivery). Built via materialize_codex_runtime_bundle (exact wrapper+platform lock-nodes → admit HTTPS registry archives → isolated npm cache → offline ci → installed-tree+toolchain+canonical protocol-schema receipt → content-addressed release with current symlink read-back). ONE exact runtime produces ALL protocol facts cited in extdeps.llm.codex_app_server — never /usr/bin/codex npm shim, never ambient PATH. Prefer the platform-native Codex binary for app-server --stdio; Node is not the runtime entry." +// Release-bundled Codex runtime at codex_required_package_version (gunbc.package_delivery). Built +// via materialize_codex_runtime_bundle (exact wrapper+platform lock-nodes → admit HTTPS registry +// archives → isolated npm cache → offline ci → installed-tree+toolchain+canonical protocol-schema +// receipt → content-addressed release with current symlink read-back). ONE exact runtime produces +// ALL protocol facts cited in extdeps.llm.codex_app_server — never /usr/bin/codex npm shim, never +// ambient PATH. Prefer the platform-native Codex binary for app-server --stdio; Node is not the +// runtime entry. -data codex_native_app_server_probe_note: String = "Measured 2026-08-05 on linux/arm64: vendor/.../bin/codex app-server --stdio accepts NDJSON initialize and returns a JSON-RPC result. Prefer that native executable over the JS wrapper+Node path." +// Measured 2026-08-05 on linux/arm64: vendor/.../bin/codex app-server --stdio accepts NDJSON +// initialize and returns a JSON-RPC result. Prefer that native executable over the JS wrapper+Node +// path. fn codex_bundle_executable_path(bundle: CodexRuntimeBundle) -> FilePath { concat(bundle.root as String, concat("/", bundle.native_executable_rel as String)) as FilePath diff --git a/dag/gunbc/codex_runtime_paths.dag b/dag/gunbc/codex_runtime_paths.dag index 32d93164fea..be5c68ae9ef 100644 --- a/dag/gunbc/codex_runtime_paths.dag +++ b/dag/gunbc/codex_runtime_paths.dag @@ -2,7 +2,11 @@ module gunbc.codex_runtime_paths import std.types { String, NonEmptyStr, FilePath } -data codex_runtime_paths_note: String = "Path composition for the Codex provider-runtime release tree — link name, current symlink path, and bundled-native executable path under a release parent. Lives below gunbc.host_layout and gunbc.package_delivery so both can import without a cycle: package_delivery owns materialization/version facts; host_layout owns host records; this module owns only shared path algebra." +// Path composition for the Codex provider-runtime release tree — link name, current symlink path, +// and bundled-native executable path under a release parent. Lives below gunbc.host_layout and +// gunbc.package_delivery so both can import without a cycle: package_delivery owns +// materialization/version facts; host_layout owns host records; this module owns only shared path +// algebra. data codex_runtime_current_link_name: NonEmptyStr = "current" as NonEmptyStr diff --git a/dag/gunbc/codex_supervised_turn.dag b/dag/gunbc/codex_supervised_turn.dag index effce823e6a..15fcdf7fc3b 100644 --- a/dag/gunbc/codex_supervised_turn.dag +++ b/dag/gunbc/codex_supervised_turn.dag @@ -81,19 +81,58 @@ import extdeps.languages.json.parse { JsonMemberNotAnObject, } -data codex_supervised_turn_authority_note: String = "SUPERVISED-RUNNER REALIZATION (continuity receipt 2026-08-13). In-flight turns do NOT survive client death — tool calls execute in the local app-server process, so one supervised process must fence each WorkerTurn attempt. std.temporal_effect HeldLease is the execution lease — an issued LeaseEpoch (lease_key, resource_fingerprint, owner_fingerprint, generation) plus the observed reading of it; do not mint a parallel session carrier. Update the observation through held_lease_with_observed, never by respelling the epoch fields.\n\nTHREE LAYERS STAY SEPARATE (reviewer ruling):\n durable goal — WorkerTurn reaching a terminal disposition with an observed head\n execution lease — HeldLease over the supervised app-server process, fenced by generation\n controller — belt tick reconciling desired vs observed thread state\n\nTHE TRAP: an abandoned turn leaves thread status idle with partial work on disk. Thread idle alone NEVER authorizes turn/start; turn identity plus a runner-written terminal receipt is required. CodexRunnerTurnTerminalReceipt is written ONLY by the supervised runner when the process ends — nothing else writes one on crash.\n\nExactly-once: client_command_id is carried on the terminal receipt so a lost turn/start reply does not collapse into absent." - -data codex_supervised_process_supervision_note: String = "PROCESS SUPERVISION (srv1 receipt 2026-08-13). The lease fences the bundled native executable from CodexRuntimeBundle projection — NEVER /usr/bin/codex, which is a Node shim that spawns the platform binary. Killing the shim orphans the agent and produces a false 'work continued' reading. thread/start and turn/start share one held-open app-server stdin session realized through host_effect_apply CodexSupervisedWorkerTurn (gunbc.codex_supervised_turn_run). In-flight turns do NOT survive process death; thread/resume is the recovery path on a surviving thread." - -data codex_supervised_lease_selection_receipt_note: String = "OPERATOR RULING #8166 (2026-08-13). Disconnect branch settled by srv1 execution, not argument: killing the real app-server binary mid-turn stopped work at 2 of 8 files; the thread survived and thread/resume is the recovery path. Detached reconciliation is refuted; supervised HeldLease over the app-server process is the selected realization. Stay draft until a bounded wet turn returns CodexRunnerTurnTerminalReceipt — the admission fold alone is not mergeable. Wet access is a bounded srv1 execution grant, not a standing child credential." - -data codex_supervised_turn_wet_handoff_note: String = "BOUNDED WET HANDOFF — BISECT FIRST (srv1 OOM receipt 2026-08-14). Bisect entrypoints in dag/test/claim/codex_supervised_turn_wet_witness_test.dag are plain fn (NOT enrolled witnesses — admission gate refuses enrolled-with-zero-consumers). Operator runs under MemoryMax caps on srv1 only. Step 1: wet_committed_admitted_source_lock_admits (2G). Step 2b: wet_select_codex_exact_target_closure_only (2G). Step 2: wet_materialize_codex_runtime_bundle_only (8G). Full chain after materialize_codex_runtime_bundle fixed; then enroll via gunbc.explicit_witness_admission." +// SUPERVISED-RUNNER REALIZATION (continuity receipt 2026-08-13). In-flight turns do NOT survive +// client death — tool calls execute in the local app-server process, so one supervised process must +// fence each WorkerTurn attempt. std.temporal_effect HeldLease is the execution lease — an issued +// LeaseEpoch (lease_key, resource_fingerprint, owner_fingerprint, generation) plus the observed +// reading of it; do not mint a parallel session carrier. Update the observation through +// held_lease_with_observed, never by respelling the epoch fields. +// +// THREE LAYERS STAY SEPARATE (reviewer ruling): +// durable goal — WorkerTurn reaching a terminal disposition with an observed head +// execution lease — HeldLease over the supervised app-server process, fenced by generation +// controller — belt tick reconciling desired vs observed thread state +// +// THE TRAP: an abandoned turn leaves thread status idle with partial work on disk. Thread idle +// alone NEVER authorizes turn/start; turn identity plus a runner-written terminal receipt is +// required. CodexRunnerTurnTerminalReceipt is written ONLY by the supervised runner when the +// process ends — nothing else writes one on crash. +// +// Exactly-once: client_command_id is carried on the terminal receipt so a lost turn/start reply +// does not collapse into absent. + +// PROCESS SUPERVISION (srv1 receipt 2026-08-13). The lease fences the bundled native executable +// from CodexRuntimeBundle projection — NEVER /usr/bin/codex, which is a Node shim that spawns the +// platform binary. Killing the shim orphans the agent and produces a false 'work continued' +// reading. thread/start and turn/start share one held-open app-server stdin session realized +// through host_effect_apply CodexSupervisedWorkerTurn (gunbc.codex_supervised_turn_run). In-flight +// turns do NOT survive process death; thread/resume is the recovery path on a surviving thread. + +// OPERATOR RULING #8166 (2026-08-13). Disconnect branch settled by srv1 execution, not argument: +// killing the real app-server binary mid-turn stopped work at 2 of 8 files; the thread survived and +// thread/resume is the recovery path. Detached reconciliation is refuted; supervised HeldLease over +// the app-server process is the selected realization. Stay draft until a bounded wet turn returns +// CodexRunnerTurnTerminalReceipt — the admission fold alone is not mergeable. Wet access is a +// bounded srv1 execution grant, not a standing child credential. + +// BOUNDED WET HANDOFF — BISECT FIRST (srv1 OOM receipt 2026-08-14). Bisect entrypoints in +// dag/test/claim/codex_supervised_turn_wet_witness_test.dag are plain fn (NOT enrolled witnesses — +// admission gate refuses enrolled-with-zero-consumers). Operator runs under MemoryMax caps on srv1 +// only. Step 1: wet_committed_admitted_source_lock_admits (2G). Step 2b: +// wet_select_codex_exact_target_closure_only (2G). Step 2: +// wet_materialize_codex_runtime_bundle_only (8G). Full chain after materialize_codex_runtime_bundle +// fixed; then enroll via gunbc.explicit_witness_admission. data codex_supervised_client_command_id_file_name: String = "/codex_supervised_client_command_id" data codex_supervised_turn_generation_file_name: String = "/codex_supervised_turn_generation" -data codex_supervised_operator_verdict_note: String = "OPERATOR VERDICT #8166 (2026-08-16): RejectAndFinishNow on concat-built bash coprocess scaffold. codex_supervised_turn_script DELETED. Supervised turn executes only through host_effect_apply CodexSupervisedWorkerTurn + extdeps.llm.codex_app_server_stdio_session typed transport. PR stays draft until (1) hand-built JSON/sed shell path is gone — satisfied by deletion — and (2) wet CodexRunnerTurnTerminalReceipt executes through the modeled route. Wet srv1 grant held until session driver lands." +// OPERATOR VERDICT #8166 (2026-08-16): RejectAndFinishNow on concat-built bash coprocess scaffold. +// codex_supervised_turn_script DELETED. Supervised turn executes only through host_effect_apply +// CodexSupervisedWorkerTurn + extdeps.llm.codex_app_server_stdio_session typed transport. PR stays +// draft until (1) hand-built JSON/sed shell path is gone — satisfied by deletion — and (2) wet +// CodexRunnerTurnTerminalReceipt executes through the modeled route. Wet srv1 grant held until +// session driver lands. data codex_supervised_thread_id_slot: NonEmptyStr = "GUNBC_SUPERVISED_THREAD_ID_SLOT" as NonEmptyStr type CodexThreadActivity diff --git a/dag/gunbc/command_runner.dag b/dag/gunbc/command_runner.dag index e6f7220c4ec..33248912527 100644 --- a/dag/gunbc/command_runner.dag +++ b/dag/gunbc/command_runner.dag @@ -73,7 +73,12 @@ data ssh_exit_255_conflation_dissolution: DissolutionCondition = unbound_dissolu // mechanically preventable. Stated here rather than left to be inferred from a passing receipt, // because a green test that nothing runs is exactly the inert evidence DESIGN calls a lie. -data command_over_transport_refusal_reason_note: String = "Renders RemoteExecCommandRefusalCause (gunbc.remote_shell_command) into a diagnostic String -- the only refusal this generic runner can hit today is RemoteExecInterpretationUnknown, since every SshExec constructed in-tree names a determined interpretation (openssh_login_shell_interpretation / openbmc_dropbear_interpretation); the cause stays a match over the full type rather than a partial one so a newly added cause fails to compile here instead of falling through silently (DESIGN 4b: no untracked stall)." +// Renders RemoteExecCommandRefusalCause (gunbc.remote_shell_command) into a diagnostic String -- +// the only refusal this generic runner can hit today is RemoteExecInterpretationUnknown, since +// every SshExec constructed in-tree names a determined interpretation +// (openssh_login_shell_interpretation / openbmc_dropbear_interpretation); the cause stays a match +// over the full type rather than a partial one so a newly added cause fails to compile here instead +// of falling through silently (DESIGN 4b: no untracked stall). fn command_over_transport_refusal_reason(cause: RemoteExecCommandRefusalCause) -> String { match cause { diff --git a/dag/gunbc/commit_workflow.dag b/dag/gunbc/commit_workflow.dag index 75618652af6..a4cd8ab06e8 100644 --- a/dag/gunbc/commit_workflow.dag +++ b/dag/gunbc/commit_workflow.dag @@ -197,7 +197,46 @@ type CommitWriterAdmission refusals: List } -data commit_writer_admission_note: String = "ONE ADMISSION AUTHORITY FOR EVERY COMMIT WRITER. admit_commit_writer consumes one staged-index snapshot plus its blob-observation population, not a caller-selected path list. It refuses every non-normal GitIndexStage and independently refuses missing, extra, or duplicate blob observations; duplicate normal entries; index/blob identity disagreement; observed-content disagreement; and unavailable decode or classification. Text/non-text is carried only by the sole-constructor observer receipt. The marker grammar stores Git gitattributes conflict-marker-size once as PositiveInt and derives begin, separator, and end tokens, then parses complete begin -> separator -> end line blocks in the exact staged text; isolated and out-of-order tokens admit. Fixture exemption is not a naked Artifact argument: commit_writer_classify_artifact projects a sole-constructor receipt from v2.compiler.source_authority ModuleStorageBinding rows and binds it to the same GitPath and indexed GitObjectId. A source-path TestClaimArtifact, mismatched path, fabricated matching artifact, or receipt moved to another blob refuses. ABSENCE IS NOT INABILITY: CommitWriterArtifactClassificationReceipt stays sole_constructor with its one construction site in commit_writer_classify_artifact — no second way to earn a receipt exists. A blob that claims no exemption does not need one; the observer classifies it CommitWriterBlobTextUnclaimed or CommitWriterBlobNonTextUnclaimed instead of routing an absent claim through classification failure, and an unclaimed text blob still receives the full conflict-marker scan via commit_writer_text_marker_refusals. Only a claimed TestClaimArtifact receipt skips the scan, so omitting the receipt costs a forger the exemption and buys nothing. The observer is trusted on text-versus-non-text with no independent check today, for both claimed and unclaimed blobs alike; that residue is unchanged by the absence arm and is not fixed here. DESIGN section 4b names sole_constructor completeness as the structural-unforgeability ceiling; before the absence arm, admit_commit_writer refused every real heal-shaped commit because every non-.dag staged path had no ParsedFromSource binding to classify against, so it had never executed against a live population despite this note's earlier claim to have reached mechanical prevention. The generated pre-commit hook remains optional feedback, never enforcement. COMMIT 218dea61248d RECEIPT: its WIP-prefixed merge subject identifies DashboardAutocommit as the proven missing writer despite the reused gunbc-ci-auto-heal committer identity. HealAutocommit already refuses authored conflicts in ci_heal_skew_authored_conflict_refusal, but ci_heal_skew_conflict_arm writes through shape_git_commit_no_verify_no_edit_argv, so it bypasses even hook feedback and currently commits ours bytes before later regeneration. COUNTABLE MODEL/REALIZATION SPLIT: commit_writer_binding_work owns two typed rows, the in-repo ci_heal_skew_conflict_arm under CommitWriterHealOrchestrationLaneOwner and the out-of-repository dashboard daemon under CommitWriterDashboardInfrastructureOperatorOwner. Each row triggers when its complete staged-index/blob observer transport lands; that transport must observe the effective marker size and ModuleStorageBinding authority, then call admit_commit_writer immediately before its write. The heal realization must additionally move regeneration and byte-proof before commit. Until those owned bindings land, this PR closes the shared decision and executable incident witness, not the writer transports." +// ONE ADMISSION AUTHORITY FOR EVERY COMMIT WRITER. admit_commit_writer consumes one staged-index +// snapshot plus its blob-observation population, not a caller-selected path list. It refuses every +// non-normal GitIndexStage and independently refuses missing, extra, or duplicate blob +// observations; duplicate normal entries; index/blob identity disagreement; observed-content +// disagreement; and unavailable decode or classification. Text/non-text is carried only by the +// sole-constructor observer receipt. The marker grammar stores Git gitattributes +// conflict-marker-size once as PositiveInt and derives begin, separator, and end tokens, then +// parses complete begin -> separator -> end line blocks in the exact staged text; isolated and +// out-of-order tokens admit. Fixture exemption is not a naked Artifact argument: +// commit_writer_classify_artifact projects a sole-constructor receipt from +// v2.compiler.source_authority ModuleStorageBinding rows and binds it to the same GitPath and +// indexed GitObjectId. A source-path TestClaimArtifact, mismatched path, fabricated matching +// artifact, or receipt moved to another blob refuses. ABSENCE IS NOT INABILITY: +// CommitWriterArtifactClassificationReceipt stays sole_constructor with its one construction site +// in commit_writer_classify_artifact — no second way to earn a receipt exists. A blob that claims +// no exemption does not need one; the observer classifies it CommitWriterBlobTextUnclaimed or +// CommitWriterBlobNonTextUnclaimed instead of routing an absent claim through classification +// failure, and an unclaimed text blob still receives the full conflict-marker scan via +// commit_writer_text_marker_refusals. Only a claimed TestClaimArtifact receipt skips the scan, so +// omitting the receipt costs a forger the exemption and buys nothing. The observer is trusted on +// text-versus-non-text with no independent check today, for both claimed and unclaimed blobs alike; +// that residue is unchanged by the absence arm and is not fixed here. DESIGN section 4b names +// sole_constructor completeness as the structural-unforgeability ceiling; before the absence arm, +// admit_commit_writer refused every real heal-shaped commit because every non-.dag staged path had +// no ParsedFromSource binding to classify against, so it had never executed against a live +// population despite this note's earlier claim to have reached mechanical prevention. The generated +// pre-commit hook remains optional feedback, never enforcement. COMMIT 218dea61248d RECEIPT: its +// WIP-prefixed merge subject identifies DashboardAutocommit as the proven missing writer despite +// the reused gunbc-ci-auto-heal committer identity. HealAutocommit already refuses authored +// conflicts in ci_heal_skew_authored_conflict_refusal, but ci_heal_skew_conflict_arm writes through +// shape_git_commit_no_verify_no_edit_argv, so it bypasses even hook feedback and currently commits +// ours bytes before later regeneration. COUNTABLE MODEL/REALIZATION SPLIT: +// commit_writer_binding_work owns two typed rows, the in-repo ci_heal_skew_conflict_arm under +// CommitWriterHealOrchestrationLaneOwner and the out-of-repository dashboard daemon under +// CommitWriterDashboardInfrastructureOperatorOwner. Each row triggers when its complete +// staged-index/blob observer transport lands; that transport must observe the effective marker size +// and ModuleStorageBinding authority, then call admit_commit_writer immediately before its write. +// The heal realization must additionally move regeneration and byte-proof before commit. Until +// those owned bindings land, this PR closes the shared decision and executable incident witness, +// not the writer transports. type CommitWriterArtifactClassificationResolution = CommitWriterArtifactClassified { @@ -465,7 +504,31 @@ fn commit_writer_text_marker_refusals( ) } -data commit_writer_index_filter_capture_scaffold: String = "DECLARED SCAFFOLD (gunbc#7480, 2026-08-02). The body below selects entries with flat_map rather than the obvious filter(index, entry => entry.path == path), and that spelling is NOT a preference -- the filter form does not compile in this module. ROOT CAUSE, bisected and reproduced: src/v2/std/algebra.dag declares fn filter(xs: FreeMonoid, predicate: fn(T) -> Bool). This module does not import v2.std.algebra, but it imports v2.compiler.source_authority, which drags the whole v2 compiler closure -- including v2.std.algebra -- into the module pool. The bare name filter then binds to THAT declaration instead of the builtin, List does not unify with FreeMonoid, the lambda parameter stays an unbound T, and the module fails with `no field path on type T`. THE DISCRIMINATING MEASUREMENT: importing v2.std.algebra for `length` ALONE -- never filter -- is sufficient to break it, and deleting only the source_authority import makes the original filter form compile. So this is name capture by POOL MEMBERSHIP, not by the import list, which is exactly the DESIGN #6985 Class B failure mode in its inverting direction: an unrelated import pulls a module into the pool whose declaration captures a bare name that was resolving correctly. It is silent -- nothing reports that filter rebound. Hypotheses REFUTED by execution first, recorded so nobody repeats them: a trailing comma in the parameter list; filter over an imported record type; List not being imported; two filter calls over different element types in one module; a lambda or parameter name colliding with an imported symbol; cross-call-site unification collapsing the element type. The sibling function below uses flat_map over the same type with the same `entry` binding and compiles, which is what makes flat_map a safe carrier here. The source_authority import CANNOT simply be dropped: ModuleStorageBinding, ParsedFromSource and ProducedByBehavior are all genuinely used by this module. DISSOLVE-ON: the namespace-only name-resolution lane makes the containment tree the single naming authority, so a module not in the import list can no longer capture a bare reference; at that point restore the filter spelling and delete this row. This scaffold changes NO behaviour -- it selects exactly the entries whose path matches, as filter did." +// DECLARED SCAFFOLD (gunbc#7480, 2026-08-02). The body below selects entries with flat_map rather +// than the obvious filter(index, entry => entry.path == path), and that spelling is NOT a +// preference -- the filter form does not compile in this module. ROOT CAUSE, bisected and +// reproduced: src/v2/std/algebra.dag declares fn filter(xs: FreeMonoid, predicate: fn(T) -> +// Bool). This module does not import v2.std.algebra, but it imports v2.compiler.source_authority, +// which drags the whole v2 compiler closure -- including v2.std.algebra -- into the module pool. +// The bare name filter then binds to THAT declaration instead of the builtin, List +// does not unify with FreeMonoid, the lambda parameter stays an unbound T, and the module fails +// with `no field path on type T`. THE DISCRIMINATING MEASUREMENT: importing v2.std.algebra for +// `length` ALONE -- never filter -- is sufficient to break it, and deleting only the +// source_authority import makes the original filter form compile. So this is name capture by POOL +// MEMBERSHIP, not by the import list, which is exactly the DESIGN #6985 Class B failure mode in its +// inverting direction: an unrelated import pulls a module into the pool whose declaration captures +// a bare name that was resolving correctly. It is silent -- nothing reports that filter rebound. +// Hypotheses REFUTED by execution first, recorded so nobody repeats them: a trailing comma in the +// parameter list; filter over an imported record type; List not being imported; two filter calls +// over different element types in one module; a lambda or parameter name colliding with an imported +// symbol; cross-call-site unification collapsing the element type. The sibling function below uses +// flat_map over the same type with the same `entry` binding and compiles, which is what makes +// flat_map a safe carrier here. The source_authority import CANNOT simply be dropped: +// ModuleStorageBinding, ParsedFromSource and ProducedByBehavior are all genuinely used by this +// module. DISSOLVE-ON: the namespace-only name-resolution lane makes the containment tree the +// single naming authority, so a module not in the import list can no longer capture a bare +// reference; at that point restore the filter spelling and delete this row. This scaffold changes +// NO behaviour -- it selects exactly the entries whose path matches, as filter did. fn commit_writer_index_entries_for_path( index: List, @@ -890,7 +953,25 @@ fn admit_commit_writer( } } -data commit_writer_complete_staged_index_blob_observation_transport_note: String = "REALIZATION for commit_writer_complete_staged_index_blob_observation_transport (commit_writer_binding_work). Decodes the real staged index via git.Core.LsFilesStageZInRepo + git_decode_ls_files_stage_z, reads each GitIndexStageNormal blob's content via git.Core.CatFileBlobInRepo keyed on the index oid — both repo-scoped (git -C dispatched to the repo argument), matching the existing *InRepo operation family in extdeps.git.git rather than assuming the process cwd, so the same fold is exercisable against a throwaway test repository and not only the actuator's invocation directory (never a caller-supplied oid, so content_oid == indexed_oid is an honest fetch-by-that-exact-oid check, not a tautology), classifies each blob against heal_writer_claims_no_fixture_exemptions, the declared-empty ModuleStorageIndex policy this writer holds (see heal_writer_claims_no_fixture_exemptions_note for why and its named limitation), matched by GitPath octets, and falls to the CommitWriterBlobTextUnclaimed absence arm (still fully marker-scanned) for every blob because no binding is ever held. Two named residues, unchanged from commit_writer_admission_note: text-vs-non-text is not independently observed (every fetched blob is treated as text, matching the existing observer-trusted residue), and the marker grammar is Git's documented default width of seven rather than an observed gitattributes conflict-marker-size (no attribute-reading operation exists yet; a narrower grammar only widens what refuses, never what silently admits, so this is a strict subset of the true refusal set, not a fail-open). GitObjectFormatSha1 is hardcoded because this repository's object database is SHA-1; a SHA-256 repository would need this as a parameter, not assumed." +// REALIZATION for commit_writer_complete_staged_index_blob_observation_transport +// (commit_writer_binding_work). Decodes the real staged index via git.Core.LsFilesStageZInRepo + +// git_decode_ls_files_stage_z, reads each GitIndexStageNormal blob's content via +// git.Core.CatFileBlobInRepo keyed on the index oid — both repo-scoped (git -C dispatched to the +// repo argument), matching the existing *InRepo operation family in extdeps.git.git rather than +// assuming the process cwd, so the same fold is exercisable against a throwaway test repository and +// not only the actuator's invocation directory (never a caller-supplied oid, so content_oid == +// indexed_oid is an honest fetch-by-that-exact-oid check, not a tautology), classifies each blob +// against heal_writer_claims_no_fixture_exemptions, the declared-empty ModuleStorageIndex policy +// this writer holds (see heal_writer_claims_no_fixture_exemptions_note for why and its named +// limitation), matched by GitPath octets, and falls to the CommitWriterBlobTextUnclaimed absence +// arm (still fully marker-scanned) for every blob because no binding is ever held. Two named +// residues, unchanged from commit_writer_admission_note: text-vs-non-text is not independently +// observed (every fetched blob is treated as text, matching the existing observer-trusted residue), +// and the marker grammar is Git's documented default width of seven rather than an observed +// gitattributes conflict-marker-size (no attribute-reading operation exists yet; a narrower grammar +// only widens what refuses, never what silently admits, so this is a strict subset of the true +// refusal set, not a fail-open). GitObjectFormatSha1 is hardcoded because this repository's object +// database is SHA-1; a SHA-256 repository would need this as a parameter, not assumed. fn commit_writer_candidate_artifacts_for_path( module_storage: List, @@ -982,7 +1063,36 @@ type CommitWriterTransportObservation = CommitWriterTransportObserved { observation: CompleteStagedCommitObservation } | CommitWriterTransportRefused { reason: String } -data heal_writer_claims_no_fixture_exemptions_note: String = "POLICY, NOT A FALLBACK. observe_complete_staged_commit's caller (heal_admit / HealAutocommit) never stages genuine test fixtures — it stages only generated artifacts — so it declares up front that it holds no ModuleStorageBinding rows and therefore claims no TestClaimArtifact fixture exemption for anything it stages. This is v2.compiler.source_authority module_storage_bindings_for_source_roots's Empty ModuleStorageIndex written directly as a typed declaration at this call site, never produced by a Rejected/failure arm — no path through observe_complete_staged_commit can reach an empty or absent module_storage by way of a failure; the only way to get one is this declaration. Per commit_writer_admission_note's absence-arm design, an empty module_storage costs nothing on this path: every staged blob is CommitWriterBlobTextUnclaimed or CommitWriterBlobNonTextUnclaimed and still receives the full commit_writer_text_marker_refusals conflict-marker scan; only a claimed TestClaimArtifact receipt would have skipped it, and this writer never holds one. NAMED LIMITATION: a staged blob that is genuinely TestClaimArtifact-shaped and contains conflict-marker-looking bytes (a real fixture with embedded begin/separator/end tokens) refuses under this policy, because the declaration cannot distinguish it from ordinary content — a false refusal, not a missed defect, and diagnosable in one read from this note rather than hidden behind a relaxed gate. Not chosen for this callsite: real module-storage supply. module_storage_bindings_for_source_roots(source_roots: [\"dag\", \"src/v2\"]) is currently an always-Rejecting scaffold with no live in-graph host dispatch (source_authority.dag module_storage_bindings_for_source_roots_host_disposition / module_storage_supply_unbound_body_note), and the manifest-overlay-supply mechanism used elsewhere (module_binding_supply_transport.dag) does not fit here: discover_source_root_ingest mints ^-edge identifiers from source-file basenames with no keyword-escaping, so scanning dag/ mints ^capability, ^module, ^import — all reserved .dag keywords (extdeps_languages_dag_syntax.rs dag_keyword_set) — for dag/extdeps/bmc/capability.dag, dag/extdeps/languages/go/module.dag, and dag/std/import.dag respectively, producing syntactically invalid overlay output for any scope touching dag/. DISSOLUTION TRIGGER: this declaration dissolves in favor of a real module_storage_bindings_for_source_roots binding once (a) that scaffold gains a live host dispatch, or (b) the manifest-overlay identifier-minting defect is fixed and re-verified end-to-end; either bounds a real fixture-exemption receipt for this writer instead of the empty declaration." +// POLICY, NOT A FALLBACK. observe_complete_staged_commit's caller (heal_admit / HealAutocommit) +// never stages genuine test fixtures — it stages only generated artifacts — so it declares up front +// that it holds no ModuleStorageBinding rows and therefore claims no TestClaimArtifact fixture +// exemption for anything it stages. This is v2.compiler.source_authority +// module_storage_bindings_for_source_roots's Empty ModuleStorageIndex written directly as a typed +// declaration at this call site, never produced by a Rejected/failure arm — no path through +// observe_complete_staged_commit can reach an empty or absent module_storage by way of a failure; +// the only way to get one is this declaration. Per commit_writer_admission_note's absence-arm +// design, an empty module_storage costs nothing on this path: every staged blob is +// CommitWriterBlobTextUnclaimed or CommitWriterBlobNonTextUnclaimed and still receives the full +// commit_writer_text_marker_refusals conflict-marker scan; only a claimed TestClaimArtifact receipt +// would have skipped it, and this writer never holds one. NAMED LIMITATION: a staged blob that is +// genuinely TestClaimArtifact-shaped and contains conflict-marker-looking bytes (a real fixture +// with embedded begin/separator/end tokens) refuses under this policy, because the declaration +// cannot distinguish it from ordinary content — a false refusal, not a missed defect, and +// diagnosable in one read from this note rather than hidden behind a relaxed gate. Not chosen for +// this callsite: real module-storage supply. module_storage_bindings_for_source_roots(source_roots: +// ["dag", "src/v2"]) is currently an always-Rejecting scaffold with no live in-graph host dispatch +// (source_authority.dag module_storage_bindings_for_source_roots_host_disposition / +// module_storage_supply_unbound_body_note), and the manifest-overlay-supply mechanism used +// elsewhere (module_binding_supply_transport.dag) does not fit here: discover_source_root_ingest +// mints ^-edge identifiers from source-file basenames with no keyword-escaping, so scanning dag/ +// mints ^capability, ^module, ^import — all reserved .dag keywords (extdeps_languages_dag_syntax.rs +// dag_keyword_set) — for dag/extdeps/bmc/capability.dag, dag/extdeps/languages/go/module.dag, and +// dag/std/import.dag respectively, producing syntactically invalid overlay output for any scope +// touching dag/. DISSOLUTION TRIGGER: this declaration dissolves in favor of a real +// module_storage_bindings_for_source_roots binding once (a) that scaffold gains a live host +// dispatch, or (b) the manifest-overlay identifier-minting defect is fixed and re-verified +// end-to-end; either bounds a real fixture-exemption receipt for this writer instead of the empty +// declaration. data heal_writer_claims_no_fixture_exemptions: List = Empty @@ -1028,7 +1138,28 @@ fn heal_admit() -> ProcessExit { commit_writer_heal_admission_gate(repo: "." as FilePath, writer: HealAutocommit) } -data commit_writer_heal_skew_conflict_arm_provisional_bytes_residue_note: String = "commit_writer_heal_admission_gate DOES NOT discharge CommitWriterHealSkewConflictArm's full obligation in commit_writer_binding_work — a second, distinct defect in ci_heal_skew_guard_emit.dag's conflict arm survives this gate by construction, not by oversight. That arm resolves (git checkout --ours), stages (git add), and THEN commits provisional ours-side bytes BEFORE ci_heal_regen_step runs and produces the actually-correct bytes, which ci_heal_commit_push_step commits a second time — two pushed commits, the first authored by no one and immediately contradicted by the second (the #7870 shape, reproduced by the guard's own design, per operator analysis msg_8723bc93). This gate runs against exactly that provisional-ours commit: the index is clean (every entry GitIndexStageNormal, admit_commit_writer's unmerged-index check does not fire) and the resolved text carries no conflict markers (the conflict-marker check does not fire), so admit_commit_writer correctly ADMITS it — nothing this gate models is violated by provisional-but-unregenerated bytes, because staleness-versus-regenerated-authority is not a fact this observation carries. What this gate closes: a FAILED resolution reaching the arm's commit with unmerged stages or live conflict markers still present (the CommitWriterUnmergedIndexRefusal / CommitWriterConflictMarkerTextRefusal arms). What stays open, as its own row with its own trigger: the conflict arm should not commit provisional bytes at all — leave the resolution staged, let regen run, commit once with regenerated bytes — which is a merge-state semantics change to a load-bearing CI flow (a merge must be concluded; ci_heal_commit_push_step filters to auto-push-eligible paths, so whether it would carry the staged merge resolution as-is is unverified) requiring its own design pass, not inferred under this brief. Next trigger: that reorder lands as its own PR against ci_heal_skew_guard_emit.dag's ci_heal_skew_conflict_arm." +// commit_writer_heal_admission_gate DOES NOT discharge CommitWriterHealSkewConflictArm's full +// obligation in commit_writer_binding_work — a second, distinct defect in +// ci_heal_skew_guard_emit.dag's conflict arm survives this gate by construction, not by oversight. +// That arm resolves (git checkout --ours), stages (git add), and THEN commits provisional ours-side +// bytes BEFORE ci_heal_regen_step runs and produces the actually-correct bytes, which +// ci_heal_commit_push_step commits a second time — two pushed commits, the first authored by no one +// and immediately contradicted by the second (the #7870 shape, reproduced by the guard's own +// design, per operator analysis msg_8723bc93). This gate runs against exactly that provisional-ours +// commit: the index is clean (every entry GitIndexStageNormal, admit_commit_writer's unmerged-index +// check does not fire) and the resolved text carries no conflict markers (the conflict-marker check +// does not fire), so admit_commit_writer correctly ADMITS it — nothing this gate models is violated +// by provisional-but-unregenerated bytes, because staleness-versus-regenerated-authority is not a +// fact this observation carries. What this gate closes: a FAILED resolution reaching the arm's +// commit with unmerged stages or live conflict markers still present (the +// CommitWriterUnmergedIndexRefusal / CommitWriterConflictMarkerTextRefusal arms). What stays open, +// as its own row with its own trigger: the conflict arm should not commit provisional bytes at all +// — leave the resolution staged, let regen run, commit once with regenerated bytes — which is a +// merge-state semantics change to a load-bearing CI flow (a merge must be concluded; +// ci_heal_commit_push_step filters to auto-push-eligible paths, so whether it would carry the +// staged merge resolution as-is is unverified) requiring its own design pass, not inferred under +// this brief. Next trigger: that reorder lands as its own PR against ci_heal_skew_guard_emit.dag's +// ci_heal_skew_conflict_arm. type LocalTidyCheck = LocalTidyWitnessCheck { entry: String, check_fns: List } @@ -1980,9 +2111,64 @@ fn project_ci_floor_gates(roster: List) -> List { ) } -data gate_gauntlet_flip_basis_note: String = "THE MEASURED FLIPS (D3b, first application of the gate lane above). Basis is the per-run gate warm-cost receipt the floor already writes (floor-gate-warm-cost-receipt.tsv, published in the floor-receipts artifact; its [gate-warm-cost] stderr mirror was retired when the receipts gained a transport, and the TSV it names is unchanged) — a real measurement at the grain the placement law requires for a GATE, which is why these three could be priced when the 36 CommitWitnessClaim rows still cannot: the TSV measures gates, and falsifier_cadence_surface_note's blocker is per-ROW receipts. Conflating the two grains would have been the fabrication that note forbids. Warm costs from the green composition run (30181042050, srv fleet arm64): source_root_ingest 427s as ONE opaque entry (its heartbeat reads 'entry 0 of 1' for ~8 minutes), self_host_realized_comparison 223s. Both are ORDERS above the 5s fast-lane budget (gunbc_ci_fast_lane_eval_budget_ms), so PrTier is inadmissible under ci_placement_law_note and Gauntlet is not a preference but the only admissible placement — no signature can make a 427s check fast-lane-eligible. THE COST, stated rather than elided: a regression in either now has up to a 4-hour detection window instead of one PR, and this is the same trade provenance_wall_cadence_rehome_risk accepted three days earlier, priced the same way. WHAT KEEPS IT FROM BEING A BARE NARROW is structural, not a promise: the gate lane's projector and batch land in the same change (falsifier_gate_projection_note), and gauntlet_lane_gate_rehome_is_never_bare_deenrollment_holds proves by execution that these rows reach an executing surface rather than vanishing. FOLLOW-ON TRIGGER, deliberately NOT taken here: a diff-proportional PR-tier proxy for source_root_ingest (ingest only the affected roots per-PR, full ingest on cadence) would return per-PR coverage at a cost that scales with the diff instead of the corpus — that is the real fix, and this flip is the interim that stops paying 427s per PR while it is built. THE THIRD GATE IS NOT FLIPPED, and the reason is a finding rather than caution: CheapClaimPoolGate (75s) is a declared member of v2.workflow.ci_floor_plan gunbc_ci_cheap_floor_gate_membership, which makes it BOTH a DataDependsOn predecessor of the compile root (floor_cheap_gates_precede_compile_edges) AND a floor_resource_anchor whose exclusion from corpus→gate serialization is what that list's own note says 'avoids a scheduler deadlock' (cheap→compile data order, corpus→cheap resource order, compile→corpus data order closing the cycle). Moving it is therefore a scheduler rewire, not a surface edit, for the smallest of the three prizes — 75s of the 725s measured. It stays per-PR pending an operator call on whether that rewire is worth 10 percent of the recovery. DECLINED 2026-07-26 (operator ruling): CheapClaimPoolGate gauntlet flip stays per-PR. REASON 1 — §6 displaced-cost bar: 75s is ~9% of the measured 725s warm cost, insufficient payoff for a scheduler rewire. REASON 2 — load-bearing signal + deadlock: as a DataDependsOn predecessor of the compile root it is the floor's earliest fail signal (moving it costs signal latency), and its exclusion from corpus→gate serialization avoids the three-way deadlock (cheap→compile data order, corpus→cheap resource order, compile→corpus data order). RE-OPEN TRIGGER: only when a lane restructures the floor scheduler for its own reasons (diff-proportional ingest proxy, or floor-memo M2) — never its own lane." - -data falsifier_gate_projection_note: String = "The GATE half of the FalsifierCadenceJob lane, and it did not exist until 2026-07-26. FOUND BY AUDIT, not by a red: the lane shipped with a witness projector (project_falsifier_witness_entries) and no gate projector, while witness_entries_from_enrollments drops CommitSpecGate rows by construction (`CommitSpecGate { gate: _ } => acc`). So a gate row moved to surfaces: [FalsifierCadenceJob] projected into NOTHING — it left the per-PR floor and arrived on no lane — and bare_deenrollment_wall_note's wall could not catch it because that wall filters on enrollment_is_witness_claim, which answers false for exactly this row kind. A gate was therefore the one enrollment shape that could be silently de-enrolled by a one-line surface edit that reads, in the diff, identically to a legitimate re-home. This projector plus the scheduled-surface predicate close that (the predicate was gate_enrollment_is_scheduled; it collapsed into the shared enrollment_is_scheduled on 2026-08-19 when the regen root cut deleted the one surface that separated it from the witness form — enrollment_surface_asymmetry_retired_note): the lane can now receive gates, and the wall now counts them. THE ORDER MATTERS AND IS NOT COSMETIC: this projector and its executing batch (v2.workflow.ci_floor_plan gunbc_falsifier_gate_batch) land in the SAME change as the wall extension, because a wall that credits FalsifierCadenceJob for gates before any batch reads them would bless precisely the enrollment-by-illusion it exists to forbid — the same 'a surface tag with no batch reading it' trap falsifier_cadence_witness_batch_note names for witnesses." +// THE MEASURED FLIPS (D3b, first application of the gate lane above). Basis is the per-run gate +// warm-cost receipt the floor already writes (floor-gate-warm-cost-receipt.tsv, published in the +// floor-receipts artifact; its [gate-warm-cost] stderr mirror was retired when the receipts gained +// a transport, and the TSV it names is unchanged) — a real measurement at the grain the placement +// law requires for a GATE, which is why these three could be priced when the 36 CommitWitnessClaim +// rows still cannot: the TSV measures gates, and falsifier_cadence_surface_note's blocker is +// per-ROW receipts. Conflating the two grains would have been the fabrication that note forbids. +// Warm costs from the green composition run (30181042050, srv fleet arm64): source_root_ingest 427s +// as ONE opaque entry (its heartbeat reads 'entry 0 of 1' for ~8 minutes), +// self_host_realized_comparison 223s. Both are ORDERS above the 5s fast-lane budget +// (gunbc_ci_fast_lane_eval_budget_ms), so PrTier is inadmissible under ci_placement_law_note and +// Gauntlet is not a preference but the only admissible placement — no signature can make a 427s +// check fast-lane-eligible. THE COST, stated rather than elided: a regression in either now has up +// to a 4-hour detection window instead of one PR, and this is the same trade +// provenance_wall_cadence_rehome_risk accepted three days earlier, priced the same way. WHAT KEEPS +// IT FROM BEING A BARE NARROW is structural, not a promise: the gate lane's projector and batch +// land in the same change (falsifier_gate_projection_note), and +// gauntlet_lane_gate_rehome_is_never_bare_deenrollment_holds proves by execution that these rows +// reach an executing surface rather than vanishing. FOLLOW-ON TRIGGER, deliberately NOT taken here: +// a diff-proportional PR-tier proxy for source_root_ingest (ingest only the affected roots per-PR, +// full ingest on cadence) would return per-PR coverage at a cost that scales with the diff instead +// of the corpus — that is the real fix, and this flip is the interim that stops paying 427s per PR +// while it is built. THE THIRD GATE IS NOT FLIPPED, and the reason is a finding rather than +// caution: CheapClaimPoolGate (75s) is a declared member of v2.workflow.ci_floor_plan +// gunbc_ci_cheap_floor_gate_membership, which makes it BOTH a DataDependsOn predecessor of the +// compile root (floor_cheap_gates_precede_compile_edges) AND a floor_resource_anchor whose +// exclusion from corpus→gate serialization is what that list's own note says 'avoids a scheduler +// deadlock' (cheap→compile data order, corpus→cheap resource order, compile→corpus data order +// closing the cycle). Moving it is therefore a scheduler rewire, not a surface edit, for the +// smallest of the three prizes — 75s of the 725s measured. It stays per-PR pending an operator call +// on whether that rewire is worth 10 percent of the recovery. DECLINED 2026-07-26 (operator +// ruling): CheapClaimPoolGate gauntlet flip stays per-PR. REASON 1 — §6 displaced-cost bar: 75s is +// ~9% of the measured 725s warm cost, insufficient payoff for a scheduler rewire. REASON 2 — +// load-bearing signal + deadlock: as a DataDependsOn predecessor of the compile root it is the +// floor's earliest fail signal (moving it costs signal latency), and its exclusion from corpus→gate +// serialization avoids the three-way deadlock (cheap→compile data order, corpus→cheap resource +// order, compile→corpus data order). RE-OPEN TRIGGER: only when a lane restructures the floor +// scheduler for its own reasons (diff-proportional ingest proxy, or floor-memo M2) — never its own +// lane. + +// The GATE half of the FalsifierCadenceJob lane, and it did not exist until 2026-07-26. FOUND BY +// AUDIT, not by a red: the lane shipped with a witness projector +// (project_falsifier_witness_entries) and no gate projector, while witness_entries_from_enrollments +// drops CommitSpecGate rows by construction (`CommitSpecGate { gate: _ } => acc`). So a gate row +// moved to surfaces: [FalsifierCadenceJob] projected into NOTHING — it left the per-PR floor and +// arrived on no lane — and bare_deenrollment_wall_note's wall could not catch it because that wall +// filters on enrollment_is_witness_claim, which answers false for exactly this row kind. A gate was +// therefore the one enrollment shape that could be silently de-enrolled by a one-line surface edit +// that reads, in the diff, identically to a legitimate re-home. This projector plus the +// scheduled-surface predicate close that (the predicate was gate_enrollment_is_scheduled; it +// collapsed into the shared enrollment_is_scheduled on 2026-08-19 when the regen root cut deleted +// the one surface that separated it from the witness form — +// enrollment_surface_asymmetry_retired_note): the lane can now receive gates, and the wall now +// counts them. THE ORDER MATTERS AND IS NOT COSMETIC: this projector and its executing batch +// (v2.workflow.ci_floor_plan gunbc_falsifier_gate_batch) land in the SAME change as the wall +// extension, because a wall that credits FalsifierCadenceJob for gates before any batch reads them +// would bless precisely the enrollment-by-illusion it exists to forbid — the same 'a surface tag +// with no batch reading it' trap falsifier_cadence_witness_batch_note names for witnesses. fn project_falsifier_gates(roster: List) -> List { gates_from_enrollments( @@ -1994,7 +2180,10 @@ data ci_witness_optin_inversion: Disposition = Terminal { reason: "Opt-in inversion (operator, 2026-07-04) DISSOLVE FIRED at the discovery flip (operator acceptance 2026-07-09 on #6403; re-landed 2026-07-10, see gunbc.ci_spec ci_spec_discovery_flip_note): the corpus re-enrolled through CiSpec.discovery_scan_dirs with SelectionApplied, so per-PR CI runs tree-wide discovery shrunk to the diff's affected set. The scaffold's dissolve-on (affected-set selection + floor memoization make per-PR selection-by-affectedness affordable) is satisfied at discovery width=1 under union-resolve S1. project_ci_floor_witness_entries is Terminal, not interim: CommitWitnessClaim rows remain live as the explicit-entry roster (execution-kind rows with their own resource profile, and any row that must run as declared) - only the roster's EXCLUSIVITY as witness source dissolved." } -data witness_projection_explicit_arms_note: String = "The enrollment projections match EXPLICITLY on every CommitCheckKind variant (no wildcard arm): a future variant added to the coproduct then reds every projection at compile time instead of being silently dropped by a `_ => acc` absorption - fail-open enrollment (a declared row contributing zero entries with no diagnostic) is unwritable, not detected." +// The enrollment projections match EXPLICITLY on every CommitCheckKind variant (no wildcard arm): a +// future variant added to the coproduct then reds every projection at compile time instead of being +// silently dropped by a `_ => acc` absorption - fail-open enrollment (a declared row contributing +// zero entries with no diagnostic) is unwritable, not detected. fn witness_entries_from_enrollments(rows: List) -> List { fold(rows, init: [], f: fn(acc, row) { @@ -2021,7 +2210,26 @@ fn project_ci_floor_witness_entries(roster: List) -> List data falsifier_cadence_surface_note: String = "FalsifierCadenceJob is the SCHEDULED home for declared witness rows whose measured wall prices them out of the per-PR floor (batch-4 clamp FLOOR-BATCH-OVER-BUDGET, run 30148859947: wall_ms=613864 vs clamp_ms=420000 at units=74; authority gunbc.ci_spec gunbc_ci_floor_batch_clamp_params[3]). It extends the axis that ALREADY answers 'where does this check run' — the enrollment surface — rather than minting a second re-home authority beside gunbc.ci_layer_roots falsifier_rehomed_bin_wet_rows, which is specifically the bin_witness_wet_entries per-row-budget overflow (its constructor is bin_wet, its budget datum is bin_witness_wet_per_row_wall_budget_seconds) and does not model CommitWitnessClaim rows. Re-homing here is NEVER a bare de-enrollment: a row moved off GithubActionsCiJob must land on this surface, so it stays enrolled somewhere and its execution is receipted on the 4-hour cadence. The failure mode this guards is measured, not hypothetical — 63 *_test.dag files live under the two test/claim/long/ dirs, 9 are enrolled here, and the rest are enrolled NOWHERE (long/ is excluded from discovery at dir grain and the falsifier does not walk it), so 'deferred' has historically meant 'unscheduled'; this surface is the lane those rows enroll into. GRAIN: enrollment splits at the check_fn, not the file — an entry whose expensive fns re-home keeps its cheap fns on GithubActionsCiJob as the per-PR smoke grain (the mocks-layer-never-replace rule), so a re-home costs the corpus its wet grain per-PR, never the concern's per-PR coverage entirely. 🟡 dissolve-on: v2.workflow.ci_placement Placement (PrTier | Gauntlet) supersedes this surface tag as the witness-scheduling authority. It cannot today: PrTier is unwritable without a WarmCostReceipt { measured_ms, run_ref } AND measured_ms <= gunbc_ci_fast_lane_eval_budget_ms (5s), so wiring placement onto the 36 CommitWitnessClaim rows requires a per-row measured receipt for each — the D2 warm-cost probe on fleet srv hardware is the named filler (ci_placement_law_note), and it has not run. Fabricating those receipts would be the fail-open the placement law exists to forbid; defaulting the unmeasured rows to Gauntlet would de-enroll all 36 at once. Encouraging convergence receipt: the 5s law selects EXACTLY the twelve rows the run-30148859947 slow-witness tables measured over 5s (group A 141s/57s/15s/7s, group B 141s/29s/27s/14s/13s/13s/13s/13s) and admits every other batch-4 row at <=1s, so placement and this surface agree on today's roster — the wiring is blocked on receipts, not on a disputed boundary." -data provenance_wall_cadence_rehome_risk: String = "RETIRED 2026-08-20 — the subject of this row no longer exists, so 'rehome' no longer describes anything. ORIGINAL CLAIM (operator ruling 2026-07-25): three affected-set provenance walls — lens_affected_set_provenance_producer_rejects_malformed_ingest_holds, _rejects_beyond_surface_ingest_holds, _coverage_receipt_holds (14s + 13s + 13s measured, run 30148859947 group-B slow-witness table) — moved from per-PR to the 4-hour falsifier cadence, accepting up to a 24-hour detection window. WHY IT IS RETIRED RATHER THAN CORRECTED: commit 60fa7b248f (2026-08-17, 'Delete the affected-set selection machinery and the evidence that only tested it') deleted src/v2/lens/affected_set/provenance_producer.dag and src/v2/test/claim/long/affected_set_provenance_producer_witness_test.dag whole, dispositioned RETIRED WITH THE MACHINERY — 'a witness whose subject no longer exists cannot be re-enrolled against anything.' These three check_fns were never re-enrolled anywhere after that deletion, on FalsifierCadenceJob or otherwise, because there is nothing left to enroll. THE BUG THIS ROW WAS CAUSING: gauntlet_lane_enrollment_witness_test.dag's witness_rehomed_rows_are_cadence_enrolled kept asserting these three names appear in live_roster_falsifier_witness_entries(), which is unwitnessable now that no roster row and no source file can name them — the assertion was reading as a live rehome risk when it was actually three dangling citations to code deleted three days after this note was written, an orphaned reference this note itself failed to catch (§3 positional/nominal citation rot, applied to a function name rather than a file:line). That test is corrected in the same change that retires this note. dissolve-on: none — there is no compensating control left to build, because there is no wall left to compensate for." +// RETIRED 2026-08-20 — the subject of this row no longer exists, so 'rehome' no longer describes +// anything. ORIGINAL CLAIM (operator ruling 2026-07-25): three affected-set provenance walls — +// lens_affected_set_provenance_producer_rejects_malformed_ingest_holds, +// _rejects_beyond_surface_ingest_holds, _coverage_receipt_holds (14s + 13s + 13s measured, run +// 30148859947 group-B slow-witness table) — moved from per-PR to the 4-hour falsifier cadence, +// accepting up to a 24-hour detection window. WHY IT IS RETIRED RATHER THAN CORRECTED: commit +// 60fa7b248f (2026-08-17, 'Delete the affected-set selection machinery and the evidence that only +// tested it') deleted src/v2/lens/affected_set/provenance_producer.dag and +// src/v2/test/claim/long/affected_set_provenance_producer_witness_test.dag whole, dispositioned +// RETIRED WITH THE MACHINERY — 'a witness whose subject no longer exists cannot be re-enrolled +// against anything.' These three check_fns were never re-enrolled anywhere after that deletion, on +// FalsifierCadenceJob or otherwise, because there is nothing left to enroll. THE BUG THIS ROW WAS +// CAUSING: gauntlet_lane_enrollment_witness_test.dag's witness_rehomed_rows_are_cadence_enrolled +// kept asserting these three names appear in live_roster_falsifier_witness_entries(), which is +// unwitnessable now that no roster row and no source file can name them — the assertion was reading +// as a live rehome risk when it was actually three dangling citations to code deleted three days +// after this note was written, an orphaned reference this note itself failed to catch (§3 +// positional/nominal citation rot, applied to a function name rather than a file:line). That test +// is corrected in the same change that retires this note. dissolve-on: none — there is no +// compensating control left to build, because there is no wall left to compensate for. fn project_falsifier_witness_entries(roster: List) -> List { witness_entries_from_enrollments( @@ -2073,9 +2281,46 @@ fn live_roster_falsifier_gates() -> List { project_falsifier_gates(roster: commit_gate_roster) } -data doc_reachability_cadence_enrollment_note: String = "FOUND BY THE WALL, on its first execution (2026-07-25). gauntlet_lane_rehome_is_never_bare_deenrollment_holds redded against the LIVE roster, not a synthetic fixture: the doc-reachability witness (doc_graph_has_no_orphan_docs, doc_graph_has_no_dangling_links) rode surfaces: [GitPrePushHook] alone — enforced only by a mechanism this repo has already ruled is NOT an enforcement authority (commit_gate_rust_suite_removed_disposition: opt-in per clone via core.hooksPath, bypassable with --no-verify, absent in container worktrees, and PROVEN ineffective — #6658 landed an unformatted .rs on main 2026-07-15 and nothing caught it). So orphaned docs and dangling doc links have had no wall on any CI path; they were one --no-verify from landing. FIXED by adding FalsifierCadenceJob rather than by widening enrollment_is_scheduled to accept GitPrePushHook — widening the predicate until the failure disappears is the absorbing fallback §5 forbids, and it would have re-buried the finding in the same motion that surfaced it. The hook enrollment STAYS for fast local feedback; the cadence row is the authority. Kind is CorpusWitnessKind, not ExecutionWitnessKind: it reads the live doc graph, so per-PR admission would need a warm-cost receipt this change does not have — the same D2-probe gap falsifier_cadence_surface_note records. Promoting it to per-PR when that receipt exists is a strict improvement, not a requirement of this change." - -data bare_deenrollment_wall_note: String = "The construction wall behind falsifier_cadence_surface_note's 'a re-home is NEVER a bare de-enrollment'. A CommitWitnessClaim row must ride at least one EXECUTING witness surface — GithubActionsCiJob (per-PR floor) or FalsifierCadenceJob (4-hour cadence). GitPrePushHook does not count: it is an escape hatch by the standing ruling (opt-in per clone, bypassable with --no-verify, absent in container worktrees — commit_gate_rust_suite_removed_disposition), so neither is an enforcement authority for a witness. A row satisfying neither is enrolled NOWHERE while still LOOKING enrolled, which is precisely the ~54-dark-file state test/claim/long/ is in and the shape a lazy re-home would add to. This is detection, not construction (the surface list is still writable as []) — the honest classification per DESIGN §5: it reds in the per-PR floor via gauntlet_lane_enrollment_witness_test with a synthetic RED control, and the construction form waits on the same D2-probe-gated placement wiring as falsifier_cadence_surface_note's dissolve-on, where Gauntlet-vs-PrTier is total over the coproduct and 'neither' becomes unwritable rather than merely counted. AMENDED 2026-07-26: this note described the witness half only, and that scoping was itself the gap — CommitSpecGate rows had NO such wall, so the one row kind whose de-enrollment nothing could catch was the one the Gauntlet lane was about to be used on. The gate half is now roster_has_no_bare_deenrolled_gate over the shared enrollment_is_scheduled. AMENDED 2026-08-19: the surface asymmetry this sentence used to cite (regen counts for gates, never for witnesses) is RETIRED with the regen root cut — GithubActionsCiRegenJob was the whole of it and the surface is deleted; see enrollment_surface_asymmetry_retired_note. Both halves now read one predicate, and both still red on a row riding only GitPrePushHook. Both halves are still DETECTION, not construction, and both dissolve on the same placement wiring." +// FOUND BY THE WALL, on its first execution (2026-07-25). +// gauntlet_lane_rehome_is_never_bare_deenrollment_holds redded against the LIVE roster, not a +// synthetic fixture: the doc-reachability witness (doc_graph_has_no_orphan_docs, +// doc_graph_has_no_dangling_links) rode surfaces: [GitPrePushHook] alone — enforced only by a +// mechanism this repo has already ruled is NOT an enforcement authority +// (commit_gate_rust_suite_removed_disposition: opt-in per clone via core.hooksPath, bypassable with +// --no-verify, absent in container worktrees, and PROVEN ineffective — #6658 landed an unformatted +// .rs on main 2026-07-15 and nothing caught it). So orphaned docs and dangling doc links have had +// no wall on any CI path; they were one --no-verify from landing. FIXED by adding +// FalsifierCadenceJob rather than by widening enrollment_is_scheduled to accept GitPrePushHook — +// widening the predicate until the failure disappears is the absorbing fallback §5 forbids, and it +// would have re-buried the finding in the same motion that surfaced it. The hook enrollment STAYS +// for fast local feedback; the cadence row is the authority. Kind is CorpusWitnessKind, not +// ExecutionWitnessKind: it reads the live doc graph, so per-PR admission would need a warm-cost +// receipt this change does not have — the same D2-probe gap falsifier_cadence_surface_note records. +// Promoting it to per-PR when that receipt exists is a strict improvement, not a requirement of +// this change. + +// The construction wall behind falsifier_cadence_surface_note's 'a re-home is NEVER a bare +// de-enrollment'. A CommitWitnessClaim row must ride at least one EXECUTING witness surface — +// GithubActionsCiJob (per-PR floor) or FalsifierCadenceJob (4-hour cadence). GitPrePushHook does +// not count: it is an escape hatch by the standing ruling (opt-in per clone, bypassable with +// --no-verify, absent in container worktrees — commit_gate_rust_suite_removed_disposition), so +// neither is an enforcement authority for a witness. A row satisfying neither is enrolled NOWHERE +// while still LOOKING enrolled, which is precisely the ~54-dark-file state test/claim/long/ is in +// and the shape a lazy re-home would add to. This is detection, not construction (the surface list +// is still writable as []) — the honest classification per DESIGN §5: it reds in the per-PR floor +// via gauntlet_lane_enrollment_witness_test with a synthetic RED control, and the construction form +// waits on the same D2-probe-gated placement wiring as falsifier_cadence_surface_note's +// dissolve-on, where Gauntlet-vs-PrTier is total over the coproduct and 'neither' becomes +// unwritable rather than merely counted. AMENDED 2026-07-26: this note described the witness half +// only, and that scoping was itself the gap — CommitSpecGate rows had NO such wall, so the one row +// kind whose de-enrollment nothing could catch was the one the Gauntlet lane was about to be used +// on. The gate half is now roster_has_no_bare_deenrolled_gate over the shared +// enrollment_is_scheduled. AMENDED 2026-08-19: the surface asymmetry this sentence used to cite +// (regen counts for gates, never for witnesses) is RETIRED with the regen root cut — +// GithubActionsCiRegenJob was the whole of it and the surface is deleted; see +// enrollment_surface_asymmetry_retired_note. Both halves now read one predicate, and both still red +// on a row riding only GitPrePushHook. Both halves are still DETECTION, not construction, and both +// dissolve on the same placement wiring. fn bare_deenrolled_witness_rows(roster: List) -> List { filter(roster, e => enrollment_is_witness_claim(e: e) && !enrollment_is_scheduled(e: e)) @@ -2135,7 +2380,11 @@ fn witness_cost_basis_eq(a: WitnessCostBasis, b: WitnessCostBasis) -> Bool { } } -data scheduled_witness_envelope_path_classification_scope_note: String = "path_classification is WitnessConsumerCadence, the path-policy classification authority (std.witness_admission) — it does NOT answer whether anything executes a row; that question is answered by the separate fold in src/v2/workflow/witness_admission.dag, per commit_roster_enrollment_is_a_consumer_note there. This function and scheduled_witness_envelope_wellformed below compare/validate only what the field carries." +// path_classification is WitnessConsumerCadence, the path-policy classification authority +// (std.witness_admission) — it does NOT answer whether anything executes a row; that question is +// answered by the separate fold in src/v2/workflow/witness_admission.dag, per +// commit_roster_enrollment_is_a_consumer_note there. This function and +// scheduled_witness_envelope_wellformed below compare/validate only what the field carries. fn scheduled_witness_envelope_eq(a: ScheduledWitnessEnvelope, b: ScheduledWitnessEnvelope) -> Bool { witness_consumer_cadence_eq(a: a.path_classification, b: b.path_classification) @@ -2705,6 +2954,31 @@ fn realization_artifact_for_surface(surface: CommitWorkflowSurface) -> Generated } } -data commit_workflow_long_lane_note: String = "Two complexity-lens enrollments (fold_lowering_test, accumulator_copy_fold_analysis_test) DELETED from the GithubActionsCiJob surface 2026-07-12: their check_fns measured over the fast-lane 5-second witness eval budget (operator rule; authority gunbc_ci_fast_lane_rule_note in v2.workflow.ci_floor_plan), and an explicit enrollment row bypasses discovery exclusion, so re-pathing the row to test/claim/long/ would keep the over-budget rows on every PR. Offlining an explicit row means deleting its declaration, never teaching exclusions to veto the roster (the enforcement-coverage precedent in gunbc.ci_layer_roots). The files live under src/v2/test/claim/long/ with the local recipe; dissolve-on: the long-lane scheduled trigger (falsifier follow-on) enrolls them and this note retires. Contrast: the parse_table_memo_governed enrollment above was REPOINTED to its long/ home, not deleted — its four enrolled check_fns each measured 0ms (audit 2026-07-12); only unenrolled siblings in that file (warm_equals_cold) are over budget, and an explicit row runs exactly its named check_fns. Delete when the enrolled fns themselves are slow; repoint when only the file moved. Wave 1 Gate 1 body-producer keystones (wave1_gate1_b1_bare_call_body_producer_witness_test.dag, wave1_gate1_a1_symbol_index_body_producer_witness_test.dag — five enrolled check_fns, ~5s+ each under floor load) were DELETED 2026-07-14 for the same reason: explicit rows bypass test/claim/long/ discovery exclusion, so affected-set selection on PRs touching target_model.dag (live case #6589) enrolled them into the 5s fast lane and REDed at EvalBudgetExceeded (~5013ms). Fast receipts stay in self_host/ (discovered); execution proofs stay in long/ (local recipe: claim_batch --source-root dag --source-root src/v2 --entry --functions ). Separately, two rows (parse_test floor/perf, dag_compile_clean_perturb_receipts) were DELETED 2026-07-12 as §3 de-duplication, not offlining: the same check_fns are declared in gunbc.ci_layer_roots bin_witness_wet_entries (the Wet bin-execution lane), and the double enrollment ran them a second time inside the HERMETIC corpus batch, where their run_witness_bin host effects refuse (CI receipt: run 29208529942, IsExecutable refusals). One fact, one roster: the bin lane keeps the per-PR coverage." +// Two complexity-lens enrollments (fold_lowering_test, accumulator_copy_fold_analysis_test) DELETED +// from the GithubActionsCiJob surface 2026-07-12: their check_fns measured over the fast-lane +// 5-second witness eval budget (operator rule; authority gunbc_ci_fast_lane_rule_note in +// v2.workflow.ci_floor_plan), and an explicit enrollment row bypasses discovery exclusion, so +// re-pathing the row to test/claim/long/ would keep the over-budget rows on every PR. Offlining an +// explicit row means deleting its declaration, never teaching exclusions to veto the roster (the +// enforcement-coverage precedent in gunbc.ci_layer_roots). The files live under +// src/v2/test/claim/long/ with the local recipe; dissolve-on: the long-lane scheduled trigger +// (falsifier follow-on) enrolls them and this note retires. Contrast: the parse_table_memo_governed +// enrollment above was REPOINTED to its long/ home, not deleted — its four enrolled check_fns each +// measured 0ms (audit 2026-07-12); only unenrolled siblings in that file (warm_equals_cold) are +// over budget, and an explicit row runs exactly its named check_fns. Delete when the enrolled fns +// themselves are slow; repoint when only the file moved. Wave 1 Gate 1 body-producer keystones +// (wave1_gate1_b1_bare_call_body_producer_witness_test.dag, +// wave1_gate1_a1_symbol_index_body_producer_witness_test.dag — five enrolled check_fns, ~5s+ each +// under floor load) were DELETED 2026-07-14 for the same reason: explicit rows bypass +// test/claim/long/ discovery exclusion, so affected-set selection on PRs touching target_model.dag +// (live case #6589) enrolled them into the 5s fast lane and REDed at EvalBudgetExceeded (~5013ms). +// Fast receipts stay in self_host/ (discovered); execution proofs stay in long/ (local recipe: +// claim_batch --source-root dag --source-root src/v2 --entry --functions ). Separately, +// two rows (parse_test floor/perf, dag_compile_clean_perturb_receipts) were DELETED 2026-07-12 as +// §3 de-duplication, not offlining: the same check_fns are declared in gunbc.ci_layer_roots +// bin_witness_wet_entries (the Wet bin-execution lane), and the double enrollment ran them a second +// time inside the HERMETIC corpus batch, where their run_witness_bin host effects refuse (CI +// receipt: run 29208529942, IsExecutable refusals). One fact, one roster: the bin lane keeps the +// per-PR coverage. data wave1_gate1_d_census_class_c_split_note: String = "wave1_gate1_d_census (test-decomposition-wcf-cut.md Class C row, ~141s measured) split 2026-07-25: attribution-by-execution (claim_batch, cold) showed the census's ENROLLED per-PR check_fn ran a witness wall of 134526ms against a one-time 32034ms entry-closure resolve — the cost lives in the witness body itself, which builds 10 ingested_fixture_arrows fixtures (each a real tokenize->parse_module->normalize->resolve pass) across two aggregator fns; this is genuine corpus breadth (Class C), not a fixture-shaped cost-shape defect (Class F ruled out by measurement, not by assumption). The mechanism-only sibling (self_host/wave1_gate1_d_mvp_subsumption_witness_test.dag's five table_fixture check_fns, measured eval_self=0.000ms each on a cold run — proves Arrow+body well-formedness/behavior-kind mechanism via table_fixture producers, no census ingest) carries no explicit CommitCheckEnrollment here: it is already picked up per-PR by the source-root *_test.dag discovery walk (ci_spec_discovery_flip_note), so an explicit roster row would be a second, redundant admission path for the same DiscoverySelection-covered file — a §3 duplication, not an accept requirement (a check_fn that is unconditionally discovered needs no roster entry to run). The real census-ingest execution (wave1_gate1_d_census_subsumption_witness_holds, long/wave1_gate1_d_mvp_subsumption_witness_test.dag) is re-homed off per-PR discovery onto the falsifier's 4-hourly cadence as one more row on falsifier_substrate_long_lane_entries (v2.workflow.ci_floor_plan gunbc_falsifier_substrate_long_lane_batch; frontier row in gunbc.ci_layer_roots falsifier_substrate_long_lane_rows, joining the sibling provenance-producer rows from PR #7230/test-decomposition-wcf-cut.md) — the single hermetic long-lane falsifier consumer for this witness class, not a parallel batch: this witness is ExecutionWitnessKind/SubstrateInputsOnly interpreted computation with no host effects, so folding it into the subprocess-execution Wet lane (falsifier_rehomed_bin_wet_batch) would be a §3 nickname. A WitnessExclusionRow (pattern \"long/wave1_gate1_d_mvp_subsumption_witness_test.dag\", classification FalsifierSubstrateLongLane) carves the file out of per-PR discovery so the classification is honest, not a lie against an unhandled default. Enrolled, but NOT unconditionally executed — the original claim here ('the row always executes on the 4-hourly cadence') was falsified by the lane's first scheduled run (30176416535, 2026-07-25) and is corrected 2026-07-26: batch 6 declares node_frontier_selection: SelectionApplied and the falsifier diffs GUNBC_CI_DIFF_BASE=HEAD~20, so affected-set selection — a per-PR mechanism, like the fast-lane eval budget that mis-scoped onto the same lane — also applies here. That run reported '10 witness(es) across 3 entr(y/ies) · 1 unaffected (import-closure, skipped without resolve) · 9 in the affected closure' and emitted result lines for only 7: this census row and the two edit_locus rows produced NO receipt and were counted as not-failed. So the honest statement is that enrollment is non-empty and the row executes on the cadences where the last 20 commits touch its closure; a silent skip is indistinguishable from a pass in the batch tally today. Whether this lane should run SelectionOff (unconditional coverage, ~127-135s per cadence for this row alone) is an open cost/cadence decision, not settled here. Dissolve-on: the census ingest's per-fixture pipeline cost drops under the per-PR fast-lane budget on a measured cold run (e.g. shared parse-table/resolve memoization landing for this closure) — then re-enroll wave1_gate1_d_census_subsumption_witness_holds per-PR discovery and delete the falsifier_substrate_long_lane_rows entry, the exclusion row, and this note." diff --git a/dag/gunbc/compile_clean_diagnostic_policy.dag b/dag/gunbc/compile_clean_diagnostic_policy.dag index 9594324011d..17f065240f3 100644 --- a/dag/gunbc/compile_clean_diagnostic_policy.dag +++ b/dag/gunbc/compile_clean_diagnostic_policy.dag @@ -9,7 +9,15 @@ type UnlistedImportUseEnforcement = FloorNotYet | Enforced -data unlisted_import_use_enforcement_note: String = "Single policy authority for whether UnlistedImportUse blocks compile-clean (issue 11 / ci-floor-child-spawn-attribution section 7). FloorNotYet = advisory (current namespace-lane staging: the resolver emits UnlistedImportUse but compile-clean does not red). Enforced = blocking (terminal promotion when namespace-only resolution deletes the import grammar — DISSOLVES WHEN UnlistedImportUse promoted to an error per namespace_import_closure_behavioral_transport.dag:15). BOTH the floor receipt path (install_or_consume_floor_compile_clean_gate_receipt via floor_compile_clean_emit_ok_via_index) and the standalone CLI transport (gunbc compile --target dag) read this row — never restate the predicate in either consumer (DESIGN section 3)." +// Single policy authority for whether UnlistedImportUse blocks compile-clean (issue 11 / +// ci-floor-child-spawn-attribution section 7). FloorNotYet = advisory (current namespace-lane +// staging: the resolver emits UnlistedImportUse but compile-clean does not red). Enforced = +// blocking (terminal promotion when namespace-only resolution deletes the import grammar — +// DISSOLVES WHEN UnlistedImportUse promoted to an error per +// namespace_import_closure_behavioral_transport.dag:15). BOTH the floor receipt path +// (install_or_consume_floor_compile_clean_gate_receipt via floor_compile_clean_emit_ok_via_index) +// and the standalone CLI transport (gunbc compile --target dag) read this row — never restate the +// predicate in either consumer (DESIGN section 3). data compile_clean_unlisted_import_use_enforcement: UnlistedImportUseEnforcement = FloorNotYet @@ -40,7 +48,13 @@ type UnlistedImportBindingSource | PoolCoincidence | DefinerResolvable -data unlisted_import_binding_source_note: String = "Binding-source attribution for the UnlistedImportUse census (issue 11). ListedImport = the referencing module has an import edge to the definer module but the specific name is absent from that import's name list (classic selective-import SVN miss). PoolCoincidence = no import edge to the definer; the name resolves only because the definer module is already in the compilation pool via an unrelated import elsewhere (#6985 Class B). DefinerResolvable = the reference binds via namespace/containment (qualified projection) without an import edge or pool accident — the terminal namespace-lane shape every row must eventually reach." +// Binding-source attribution for the UnlistedImportUse census (issue 11). ListedImport = the +// referencing module has an import edge to the definer module but the specific name is absent from +// that import's name list (classic selective-import SVN miss). PoolCoincidence = no import edge to +// the definer; the name resolves only because the definer module is already in the compilation pool +// via an unrelated import elsewhere (#6985 Class B). DefinerResolvable = the reference binds via +// namespace/containment (qualified projection) without an import edge or pool accident — the +// terminal namespace-lane shape every row must eventually reach. data unlisted_import_use_census_hand_rust_dissolve_trigger: DissolutionCondition = unbound_dissolution(description: "dissolve-on: compile_clean_unlisted_import_census hand-Rust transport (CLI_RUN_COMPILE_CLEAN_UNLISTED_IMPORT_CENSUS_SCAFFOLD_MARKER) — DISSOLVES WHEN namespace-only resolution deletes the import grammar and binding-source classification is modeled in the substrate (namespace-resolution-design.md section 8), or a floor-enrolled census lens subsumes the host transport; receipt rg cli_run_compile_clean_unlisted_import_census src/v1/stage0 == 1 until deletion.") diff --git a/dag/gunbc/compile_diagnostic_census.dag b/dag/gunbc/compile_diagnostic_census.dag index b0ac043795d..965c808fe78 100644 --- a/dag/gunbc/compile_diagnostic_census.dag +++ b/dag/gunbc/compile_diagnostic_census.dag @@ -6,13 +6,58 @@ import std.types { String, NonEmptyStr, Bool, Int, List } import std.disposition { Disposition, Scaffold, SingleAuthority } import std.decl_ref { DeclarationRef, WholeDeclaration } -data compile_diagnostic_census_charter_note: String = "THE PROBE OBSERVATION SURFACE for the v1 compile path (roadmap node ladder-probe-corpus; gap analysis sec 12 Stage 1a). The probe corpus needs to observe WHICH judgment a synthetic source provoked, not merely that something refused: the Stage-0 receipt vocabulary gunbc.guarantee_measurement carries RefusedTyped \{diagnostic_class, count} and AcceptedCounted \{diagnostic_class, count}, and neither is inhabitable from a Bool. Before this surface the only .dag-callable v1 compile was compile_dag_rust_emit_check, which counts diagnostics passing compile_clean_diagnostic_is_hard and returns false when that count is nonzero - so class identity, severity, and every advisory were discarded inside the host. Three things that costs, each measured on the tests this corpus migrates: a probe refusing for ANY hard reason (including a typo in the probe source itself) reads as the wall firing; demoting a landed wall from blocking to advisory turns its RED silently GREEN, because the filter IS the severity predicate; and a positive control cannot state zero-diagnostics-of-ANY-severity, which is the assertion codex review 45357 added after an advisory MethodExistenceUndecided passed unnoticed as a green control. MEASUREMENT ONLY: no arm here judges acceptance. The rows carry what the compile decided, severity included AS DATA, and callers filter." +// THE PROBE OBSERVATION SURFACE for the v1 compile path (roadmap node ladder-probe-corpus; gap +// analysis sec 12 Stage 1a). The probe corpus must observe WHICH judgment a synthetic source +// provoked, not merely that something refused: the Stage-0 receipt vocabulary +// gunbc.guarantee_measurement carries RefusedTyped {diagnostic_class, count} and AcceptedCounted +// {diagnostic_class, count}, and neither is inhabitable from a Bool. Before this surface the only +// .dag-callable v1 compile was compile_dag_rust_emit_check, which counts diagnostics passing +// compile_clean_diagnostic_is_hard and returns false when nonzero - so class identity, severity, +// and every advisory were discarded inside the host. Three costs, each measured on the tests this +// corpus migrates: a probe refusing for ANY hard reason (including a typo in the probe source) +// reads as the wall firing; demoting a landed wall from blocking to advisory turns its RED silently +// GREEN, because the filter IS the severity predicate; and a positive control cannot state +// zero-diagnostics-of-ANY-severity, the assertion codex review 45357 added after an advisory +// MethodExistenceUndecided passed unnoticed as a green control. MEASUREMENT ONLY: no arm here +// judges acceptance. The rows carry what the compile decided, severity included AS DATA, and +// callers filter. -data compile_diagnostic_census_grounding_note: String = "SECTION 3 - this is a PROJECTION, not a second diagnostic vocabulary. diagnostic_class is exactly the class half of the host's compile_clean_diagnostic_histogram_key, whose total match over v1.std.core CompilerDiagnostic is the single authority for naming a variant; subject_name is that key's name half. Declaring the variants again here as a coproduct would fork the authority and drift the moment a variant lands, so the class arrives as a projected string and the compiler's own exhaustiveness is what keeps it total. blocking is likewise projected, not restated: the host reads it through the existing compile_clean_diagnostic_is_hard delegation to is_interpreter_blocking_diagnostic, so the severity POLICY has one home and this surface reports its verdict rather than re-deciding it." +// SECTION 3 - this is a PROJECTION, not a second diagnostic vocabulary. diagnostic_class is exactly +// the class half of the host's compile_clean_diagnostic_histogram_key, whose total match over +// v1.std.core CompilerDiagnostic is the single authority for naming a variant; subject_name is that +// key's name half. Redeclaring the variants here as a coproduct would fork the authority and drift +// the moment a variant lands, so the class arrives as a projected string and the compiler's own +// exhaustiveness keeps it total. blocking is likewise projected: the host reads it through the +// existing compile_clean_diagnostic_is_hard delegation to is_interpreter_blocking_diagnostic, so +// the severity POLICY has one home and this surface reports its verdict rather than re-deciding it. -data compile_diagnostic_census_path_scope_note: String = "THE ONE PATH THIS SURFACE MEASURES, stated so a receipt cannot claim coverage it does not have (DESIGN 4b: a class's rung is the MINIMUM across its in-scope paths, and citing the strongest path while another stays silent is inflation). The census compiles a synthetic single-module source through the v1 pipeline to the Rust render target - the same pipeline family compile_dag_rust_emit_check uses - WITH type_ref_hit_ne_bind_measure_active armed for that nested compile only (N1a; host bracket v1_rt::with_type_ref_hit_ne_bind_measure). That is a real path split on masked type-reference sites: census can emit blocking UnresolvedType for pool-present non-authority refs while compile_dag_rust_emit_check (measure off) stays on the production fail-open / UnlistedImportUse advisory path. Census-derived receipts must not be read as production compile-clean behavior for those positions (review 47457). In GuaranteePath axes that is subject_grain SyntheticProgram and acceptance_boundary CompileAccept. The pipeline is v1, and that is stated here as a fact about this surface rather than as a path axis: compile_mode was deleted from the path row (gunbc#7572, review 46308) because every landed control derives its pipeline from its boundary, so storing it was dual representation. If a path is ever measured under two pipelines at ONE boundary, that is the trigger returning the axis as a stored fact - not a reason to keep a copy of it here meanwhile. It observes NOTHING about the interpreter's disposition of the same program, nothing about other emission targets, and nothing about corpus-grain prevalence. A probe row keyed to a path this surface does not measure must not carry a receipt derived from it." +// THE ONE PATH THIS SURFACE MEASURES, stated so a receipt cannot claim coverage it does not have +// (DESIGN 4b: a class's rung is the MINIMUM across its in-scope paths; citing the strongest path +// while another stays silent is inflation). The census compiles a synthetic single-module source +// through the v1 pipeline to the Rust render target - the same pipeline family +// compile_dag_rust_emit_check uses - WITH type_ref_hit_ne_bind_measure_active armed for that nested +// compile only (N1a; host bracket v1_rt::with_type_ref_hit_ne_bind_measure). That is a real path +// split on masked type-reference sites: census can emit blocking UnresolvedType for pool-present +// non-authority refs while compile_dag_rust_emit_check (measure off) stays on the production +// fail-open / UnlistedImportUse advisory path. Census-derived receipts must not be read as +// production compile-clean behavior for those positions (review 47457). In GuaranteePath axes that +// is subject_grain SyntheticProgram and acceptance_boundary CompileAccept. The pipeline is v1, +// stated as a fact about this surface rather than a path axis: compile_mode was deleted from the +// path row (gunbc#7572, review 46308) because every landed control derives its pipeline from its +// boundary, so storing it was dual representation. If a path is ever measured under two pipelines +// at ONE boundary, that is the trigger returning the axis as a stored fact - not a reason to keep +// a copy here meanwhile. It observes NOTHING about the interpreter's disposition of the same +// program, other emission targets, or corpus-grain prevalence. A probe row keyed to a path this +// surface does not measure must not carry a receipt derived from it. -data compile_diagnostic_census_not_runnable_note: String = "CensusNotRunnable is a separate arm and NOT an empty row list, because an empty census is byte-identical to a clean compile (DESIGN 5: top-as-answer conflated with top-as-ignorance is the absorbing fallback). Could-not-measure must never be readable as the subject passing: the probe layer maps this arm to guarantee_measurement ProbeObservation ProbeNotRunnable, whose observed_is_verdict is false, and it can do so without guessing precisely because the separation is preserved here at the host seam rather than reconstructed downstream. An infrastructural failure of the compile itself - a harness error, an unreadable module index, a panic caught at the boundary - lands here with its cause, never as a green census." +// CensusNotRunnable is a separate arm and NOT an empty row list, because an empty census is +// byte-identical to a clean compile (DESIGN 5: top-as-answer conflated with top-as-ignorance is +// the absorbing fallback). Could-not-measure must never read as the subject passing: the probe +// layer maps this arm to guarantee_measurement ProbeObservation ProbeNotRunnable, whose +// observed_is_verdict is false, without guessing precisely because the separation is preserved at +// the host seam rather than reconstructed downstream. An infrastructural failure of the compile +// itself - a harness error, an unreadable module index, a panic caught at the boundary - lands +// here with its cause, never as a green census. data compile_diagnostic_census_hand_rust_dissolve_trigger: DissolutionCondition = unbound_dissolution(description: "dissolve-on for the HAND-RUST OBSERVATION SURFACE behind this vocabulary - cli_run compile_dag_diagnostic_census (the host census fold) and v1_interpreter compile_diagnostic_census_value (the substrate projection), together with the v1.compiler.infer_method builtin_function_registry row that types it. The registry row's own trigger is its sibling's, the PrimitiveDefinition identity join; THIS row covers the Rust surface itself, which that trigger does not reach. TRIGGER, one and checkable: the roadmap row v1-zero-hand-maintained-rust, the v1-exit lane's fourth finish line. Its acceptance condition is that no hand-maintained Rust remains in the seed, so this surface is gone when that row is accepted and still owed while it is not - a state readable from the roadmap authority rather than from anyone's judgment about how far v2 has got. An earlier cut of this row offered two open-ended alternatives instead - v2 self-hosting the pipeline 'far enough' that a probe could read modeled diagnostics, or a floor-enrolled diagnostic lens subsuming the projection - and named no row; neither was evaluable, and a trigger nobody can evaluate is indistinguishable from no trigger, which is exactly the untracked-stall shape DESIGN 4b forbids (codex review 46264). EARLIER REMOVAL IS PERMITTED AND EXPECTED, but it is not the trigger: if a diagnostic lens subsumes this projection, or v2 self-hosts the pipeline far enough to read modeled diagnostics directly, this surface should go then. A scaffold may always dissolve early; what a trigger fixes is the point by which it MUST be gone, and stating the early paths as alternatives was what made the obligation unfalsifiable. It does NOT dissolve merely because the registry is replaced: the projection is the scaffold, the registry row is only its type declaration. WHY IT IS A SCAFFOLD AND NOT A PERMANENT SURFACE: everything it computes already exists inside the compiler - compile_clean_diagnostic_histogram_key names the variant and compile_clean_diagnostic_is_hard decides severity - so this adds NO judgment, only a substrate-visible projection of judgments the compiler already made. That is precisely the shape that dissolves when the substrate can read them directly. Receipt while it stands: the surface has exactly one class of consumer, the guarantee probe corpus, and its executing evidence is dag/test/claim/compile_diagnostic_census_witness_test.dag.") @@ -36,7 +81,17 @@ type CompileDiagnosticCensus = CensusObserved { rows: List } | CensusNotRunnable { cause: NonEmptyStr } -data census_projection_note: String = "Pure reads over ROWS, never over the census. The selectors take List, so the only way to obtain rows is to MATCH the coproduct - which makes reading a row list out of a CensusNotRunnable structurally unwritable rather than warned against. An earlier cut exported census_rows(c: CompileDiagnosticCensus) answering [] on the NotRunnable arm, with a prose caution telling callers not to read a zero out of it. That was the fail-open this module exists to prevent, one layer up and in its own API: could-not-measure and observed-nothing became the same empty list at the export boundary, and a prose warning is validation standing exactly where construction was available (DESIGN 5; caught by codex review 46144). The caution deleted WITH the accessor rather than surviving beside it - a warning about a state that can no longer be written is dead text. None of these decides acceptance: blocking and advisory partition the SAME rows by the severity the host already reported, and census_total_count sums counts rather than rows so a class occurring twice is two occurrences." +// Pure reads over ROWS, never over the census. The selectors take List, +// so the only way to obtain rows is to MATCH the coproduct - which makes reading a row list out of +// a CensusNotRunnable structurally unwritable rather than warned against. An earlier cut exported +// census_rows(c: CompileDiagnosticCensus) answering [] on the NotRunnable arm, with a prose caution +// not to read a zero out of it. That was the fail-open this module exists to prevent, one layer up +// in its own API: could-not-measure and observed-nothing became the same empty list at the export +// boundary, and a prose warning is validation standing where construction was available (DESIGN 5; +// caught by codex review 46144). The caution deleted WITH the accessor - a warning about a state +// that can no longer be written is dead text. None of these decides acceptance: blocking and +// advisory partition the SAME rows by the severity the host already reported, and +// census_total_count sums counts rather than rows so a class occurring twice is two occurrences. fn census_blocking_rows(rows: List) -> List { rows |> filter(r => r.blocking) diff --git a/dag/gunbc/compile_pool_ensure.dag b/dag/gunbc/compile_pool_ensure.dag index deaf0eaa606..418a2be6efb 100644 --- a/dag/gunbc/compile_pool_ensure.dag +++ b/dag/gunbc/compile_pool_ensure.dag @@ -33,7 +33,12 @@ import gunbc.build_cache_instance { data compile_pool_ensure_scope_disposition: Disposition = SingleAuthority -data ensure_is_idempotent_note: String = "THE COMPILE-POOL SLICE IS A RECONCILE TARGET, NOT AN INSTALL-ON-EVERY-PASS MUTATION. A slice whose memory.max and memory.high already match the derived pool size is EnsureAlreadyConverged — writing the unit again would churn systemd and risk a brief cgroup reset while cache-miss compilers are the slice's children. The admission therefore compares OBSERVED limits against the sizing authority before emitting lines, exactly as build_cache_ensure consults placement before rendering a cache unit." +// THE COMPILE-POOL SLICE IS A RECONCILE TARGET, NOT AN INSTALL-ON-EVERY-PASS MUTATION. A slice +// whose memory.max and memory.high already match the derived pool size is EnsureAlreadyConverged — +// writing the unit again would churn systemd and risk a brief cgroup reset while cache-miss +// compilers are the slice's children. The admission therefore compares OBSERVED limits against the +// sizing authority before emitting lines, exactly as build_cache_ensure consults placement before +// rendering a cache unit. type CompilePoolEnsureRefusal = EnsurePoolTopologyUndeclared { detail: NonEmptyStr } @@ -47,7 +52,12 @@ type EnsureCompilePoolAdmission | EnsureCompilePoolAdmitted { slice_unit: NonEmptyStr, sized_bytes: ByteSize, slice_unit_file: SystemdUnitFile } | EnsureCompilePoolRefused { reason: CompilePoolEnsureRefusal } -data cache_placement_unknown_refuses_note: String = "A SERVER WHOSE PLACEMENT CANNOT BE ESTABLISHED IS NOT A CONVERGED SERVER, and ensuring a compile pool beside it would be deciding to parent every cache-miss compiler under a slice whose sibling cache endpoint may still be runner-owned or unread. ServerPlacementUnknown therefore refuses BEFORE slice observation is consulted — the same ordering build_cache_ensure uses for placement-unknown before path state, and for the same reason: unknown is not a positive reading that permits host mutation." +// A SERVER WHOSE PLACEMENT CANNOT BE ESTABLISHED IS NOT A CONVERGED SERVER, and ensuring a compile +// pool beside it would be deciding to parent every cache-miss compiler under a slice whose sibling +// cache endpoint may still be runner-owned or unread. ServerPlacementUnknown therefore refuses +// BEFORE slice observation is consulted — the same ordering build_cache_ensure uses for +// placement-unknown before path state, and for the same reason: unknown is not a positive reading +// that permits host mutation. type EnsureCompilePoolAdmissionFold { already_converged: fn(NonEmptyStr, ByteSize) -> R diff --git a/dag/gunbc/credential_argv_exposure.dag b/dag/gunbc/credential_argv_exposure.dag index 3e450d102ab..7c1ddeb58fd 100644 --- a/dag/gunbc/credential_argv_exposure.dag +++ b/dag/gunbc/credential_argv_exposure.dag @@ -14,7 +14,11 @@ import std.decl_ref { DeclarationRef, WholeDeclaration } // That is why this class is recorded as an incident carrier rather than as hygiene. The usual reason // argv exposure gets deprioritized is an implicit assumption that the reader must already be // privileged. Here the reader is unprivileged and arrives through the front door. -data credential_argv_exposure_measured_surface_note: String = "srv1, 2026-08-25: /proc mounted rw,nosuid,nodev,noexec,relatime with NO hidepid; /proc/PID/cmdline mode 0444; actions-runner@srv1-01 is a host systemd service in the host pid namespace, not containerized. A host-level ps enumerated 22 session-container tmux processes whose argv carried live forwarded credentials. Measurement performed by session warm-tern-755 with fleet SSH; this module records the reading, it does not re-derive it." +// srv1, 2026-08-25: /proc mounted rw,nosuid,nodev,noexec,relatime with NO hidepid; +// /proc/PID/cmdline mode 0444; actions-runner@srv1-01 is a host systemd service in the host pid +// namespace, not containerized. A host-level ps enumerated 22 session-container tmux processes +// whose argv carried live forwarded credentials. Measurement performed by session warm-tern-755 +// with fleet SSH; this module records the reading, it does not re-derive it. // THE ROOT CAUSE OF THE REPORTED INCIDENT IS NOT IN THIS REPOSITORY, and saying so precisely is the // point of this row. The observation that opened the incident -- GITHUB_TOKEN and BUILDBUDDY_API_KEY @@ -31,7 +35,13 @@ data credential_argv_exposure_measured_surface_note: String = "srv1, 2026-08-25: // A planted canary is present in the argv under the `-e` form and absent under the export form, with // the pane reading the correct value in both -- so the detector is discriminating rather than merely // quiet. `tmux setenv` is NOT the fix: it relocates the value from a server argv to a client argv. -data credential_argv_exposure_out_of_repo_root_cause_note: String = "The reported instance lives in the session container entrypoint (session-dashboard infrastructure), which projects a forwarded credential roster into `tmux new-session -e NAME=VALUE` arguments. Out of scope for this repository by ownership, not by severity. Verified repair shape: export in the parent shell, drop -e; the tmux server inherits its environment as the global environment and panes inherit it, with the value absent from every argv. Routing to the owning repository is an operator decision recorded on the incident, not a change this module can make." +// The reported instance lives in the session container entrypoint (session-dashboard +// infrastructure), which projects a forwarded credential roster into `tmux new-session -e +// NAME=VALUE` arguments. Out of scope for this repository by ownership, not by severity. Verified +// repair shape: export in the parent shell, drop -e; the tmux server inherits its environment as +// the global environment and panes inherit it, with the value absent from every argv. Routing to +// the owning repository is an operator decision recorded on the incident, not a change this module +// can make. type CredentialArgvInstance { subject: NonEmptyStr @@ -104,7 +114,15 @@ data bmcweb_session_token_instance: CredentialArgvInstance = CredentialArgvInsta // be a PLANTED POSITIVE rather than the absence of hits. That is not a nicety here -- it is the same // defect this whole carrier is about, turned on the instrument: a value in a command line read by // something that had no business reading it, which in that case was the measurement itself. -data bmcweb_token_journal_measurement_note: String = "MEASURED 2026-08-25, all four fleet hosts, unit glob nbd-proxy-*-ws: units=0 journal_lines=0 grep_journal=0 execstart_now=0 live_websocat=0. BOTH CONTROLS ZERO, so this is UNEXERCISED PATH, not clean -- the srv3 install route has not run on this fleet and the leak is LATENT, not absent. Queries: `systemctl list-units --all 'nbd-proxy-*'` and `journalctl -u 'nbd-proxy-*-ws' --output=cat | wc -l` as the controls; `grep -c -F 'Authorization: Token'` over the same journal selection and over `systemctl show 'nbd-proxy-*-ws' -p ExecStart` as the measurements. DETECTOR RULE, from three false positives in one day: a ps/proc detector MUST exclude its own process (the search pattern is an argv word ps can see), and a zero is only meaningful against a planted positive." +// MEASURED 2026-08-25, all four fleet hosts, unit glob nbd-proxy-*-ws: units=0 journal_lines=0 +// grep_journal=0 execstart_now=0 live_websocat=0. BOTH CONTROLS ZERO, so this is UNEXERCISED PATH, +// not clean -- the srv3 install route has not run on this fleet and the leak is LATENT, not absent. +// Queries: `systemctl list-units --all 'nbd-proxy-*'` and `journalctl -u 'nbd-proxy-*-ws' +// --output=cat | wc -l` as the controls; `grep -c -F 'Authorization: Token'` over the same journal +// selection and over `systemctl show 'nbd-proxy-*-ws' -p ExecStart` as the measurements. DETECTOR +// RULE, from three false positives in one day: a ps/proc detector MUST exclude its own process (the +// search pattern is an argv word ps can see), and a zero is only meaningful against a planted +// positive. // TWO AXES, AND A READER WHO COLLAPSES THEM WILL MIS-RANK THIS. Instance 3 is the most severe by // SURFACE COUNT AND LIFETIME, and it is also the only one NOT CURRENTLY FIRING -- measured above as @@ -117,7 +135,19 @@ data bmcweb_token_journal_measurement_note: String = "MEASURED 2026-08-25, all f // leaking right now, so no emergency forces a risky change to the srv3 install path under time // pressure, and the repair can be done properly -- with the two witnesses that currently assert the // token's presence inverted alongside it, rather than bolted on afterwards. -data credential_argv_open_instance_is_the_most_severe_note: String = "RANKING ON TWO AXES, both required. SEVERITY: the open instance is the worst of the three -- argv PLUS a durable record. LIVENESS: it is the only one NOT currently firing (measured unexercised on all four hosts), while instances 1 and 2 were live and are now closed. Severity alone over-ranks it against work leaking today; liveness alone under-ranks a three-surface exposure that fires the moment the install path runs. Latency is also why declaring rather than repairing was right: no emergency forces a rushed change to the srv3 install path, so the repair can land properly with its two inverted witnesses. Original note follows -- RANKING, stated because the intuitive order is backwards: the two REPAIRED instances were argv-only on short-lived processes and are closed; the one still OPEN (nbd_proxy_serve) is argv PLUS a durable record across three surfaces -- systemd-run's transient argv, websocat's argv for the lifetime of the served ISO, and the recorded ExecStart which persists in the journal and systemctl show after the process exits. Revoking the token stops it working without removing it from the record. This instance outranks the binder trigger scoped below for whoever picks up next work." +// RANKING ON TWO AXES, both required. SEVERITY: the open instance is the worst of the three -- argv +// PLUS a durable record. LIVENESS: it is the only one NOT currently firing (measured unexercised on +// all four hosts), while instances 1 and 2 were live and are now closed. Severity alone over-ranks +// it against work leaking today; liveness alone under-ranks a three-surface exposure that fires the +// moment the install path runs. Latency is also why declaring rather than repairing was right: no +// emergency forces a rushed change to the srv3 install path, so the repair can land properly with +// its two inverted witnesses. Original note follows -- RANKING, stated because the intuitive order +// is backwards: the two REPAIRED instances were argv-only on short-lived processes and are closed; +// the one still OPEN (nbd_proxy_serve) is argv PLUS a durable record across three surfaces -- +// systemd-run's transient argv, websocat's argv for the lifetime of the served ISO, and the +// recorded ExecStart which persists in the journal and systemctl show after the process exits. +// Revoking the token stops it working without removing it from the record. This instance outranks +// the binder trigger scoped below for whoever picks up next work. data credential_argv_exposure_instances: List = [ redfish_basic_auth_instance, @@ -138,7 +168,14 @@ data credential_argv_exposure_instances: List = [ // point `Secret` bound into an argv token becomes a typed refusal beside UndeclaredInputBound and the // bmc instance above becomes unwritable rather than merely rewritten. Naming that trigger is what // separates "cannot climb further" from "can climb after one grounding"; this is the second. -data credential_argv_exposure_rung_note: String = "RUNG: mitigatable. Two instances repaired by construction at their own call sites, one declared unrepaired with its mechanism and repair shape. NOT mechanically preventable: v2.std.operation_argv refuses undeclared names and a non-literal argv[0], but ShellTransportOperationRow carries declared_inputs as names without types, so a Secret-typed input bound into an argv token is invisible to the binder. NEXT-RUNG TRIGGER: carry the declared input TYPE on that row, then refuse Secret-into-argv-token as a typed cause alongside UndeclaredInputBound. Until that lands, this class is held by review diligence, which is strictly weaker and is recorded as such rather than counted as coverage." +// RUNG: mitigatable. Two instances repaired by construction at their own call sites, one declared +// unrepaired with its mechanism and repair shape. NOT mechanically preventable: +// v2.std.operation_argv refuses undeclared names and a non-literal argv[0], but +// ShellTransportOperationRow carries declared_inputs as names without types, so a Secret-typed +// input bound into an argv token is invisible to the binder. NEXT-RUNG TRIGGER: carry the declared +// input TYPE on that row, then refuse Secret-into-argv-token as a typed cause alongside +// UndeclaredInputBound. Until that lands, this class is held by review diligence, which is strictly +// weaker and is recorded as such rather than counted as coverage. // THE NEXT ITEM, SCOPED HERE AND DELIBERATELY NOT STARTED IN THIS CHANGE. Everything above is two // repairs and a declaration -- the class is held by review diligence, and review diligence is what @@ -160,7 +197,13 @@ data credential_argv_exposure_rung_note: String = "RUNG: mitigatable. Two instan // it under any version of this change. That half stays at review diligence plus the emission witness, // and closing it would need shell command construction to become a modeled surface -- a much larger // question that this trigger neither answers nor depends on. -data credential_argv_next_item_scope_note: String = "NEXT ITEM (scoped, not started): carry the declared input TYPE on v2.std.operation_argv ShellTransportOperationRow, which today holds declared_inputs as names only. That single fact is what makes a Secret-into-argv-token refusal expressible, turning the extdeps.bmc.http class from rewritten into unwritable. Explicitly out of its reach: gunbc.ci_spec's hand-authored shell is not declared operation argv and no binder change reaches it; that half stays at review diligence plus test.claim.credential_argv_exposure_witness until shell command construction is itself modeled." +// NEXT ITEM (scoped, not started): carry the declared input TYPE on v2.std.operation_argv +// ShellTransportOperationRow, which today holds declared_inputs as names only. That single fact is +// what makes a Secret-into-argv-token refusal expressible, turning the extdeps.bmc.http class from +// rewritten into unwritable. Explicitly out of its reach: gunbc.ci_spec's hand-authored shell is +// not declared operation argv and no binder change reaches it; that half stays at review diligence +// plus test.claim.credential_argv_exposure_witness until shell command construction is itself +// modeled. data credential_argv_binder_trigger_ref: DeclarationRef = DeclarationRef { module_path: "v2.std.operation_argv", diff --git a/dag/gunbc/cursor_sdk_auth_probe.dag b/dag/gunbc/cursor_sdk_auth_probe.dag index 2b38711d258..52468163a3f 100644 --- a/dag/gunbc/cursor_sdk_auth_probe.dag +++ b/dag/gunbc/cursor_sdk_auth_probe.dag @@ -18,7 +18,12 @@ import extdeps.llm.cursor_sdk_errors { cursor_sdk_error_code_label, } -data cursor_sdk_auth_probe_note: String = "Modeled SDK auth probe: classifies a terminal RunResult reconcile into authentication standing inputs only. authentication_error is the sole credential-rejection code; rate_limit_error and other terminal codes are auth-axis-neutral (credentials may still be valid — limits/turn axes own those failures). No live SDK call in this slice — witnesses supply fixture JSON. Never mints ProviderAuthenticated literals; AuthenticationAuthenticated is derived only from CursorSdkAuthProbeAuthenticated or auth-neutral terminal errors." +// Modeled SDK auth probe: classifies a terminal RunResult reconcile into authentication standing +// inputs only. authentication_error is the sole credential-rejection code; rate_limit_error and +// other terminal codes are auth-axis-neutral (credentials may still be valid — limits/turn axes own +// those failures). No live SDK call in this slice — witnesses supply fixture JSON. Never mints +// ProviderAuthenticated literals; AuthenticationAuthenticated is derived only from +// CursorSdkAuthProbeAuthenticated or auth-neutral terminal errors. type CursorSdkAuthProbeResult = CursorSdkAuthProbeAuthenticated diff --git a/dag/gunbc/cursor_sdk_local_binding.dag b/dag/gunbc/cursor_sdk_local_binding.dag index 1ce101a2e91..a508750f35e 100644 --- a/dag/gunbc/cursor_sdk_local_binding.dag +++ b/dag/gunbc/cursor_sdk_local_binding.dag @@ -26,7 +26,9 @@ import gunbc.dispatch_selection { CursorLocalLoginRef, } -data cursor_sdk_local_binding_note: String = "Slice-E bridge only — no live wiring. Maps dispatch CursorCredentialRef to SDK-local credential materialization. API key flows INTO the SDK call via SecretRef, never argv. CursorLocalLoginRef refuses because interactive login is not the SDK automation path." +// Slice-E bridge only — no live wiring. Maps dispatch CursorCredentialRef to SDK-local credential +// materialization. API key flows INTO the SDK call via SecretRef, never argv. CursorLocalLoginRef +// refuses because interactive login is not the SDK automation path. type CursorSdkLocalCredentialAdmission = CursorSdkLocalCredentialAdmitted { diff --git a/dag/gunbc/cursor_sdk_provider_standing.dag b/dag/gunbc/cursor_sdk_provider_standing.dag index c11d6d7da1f..f16b23030fa 100644 --- a/dag/gunbc/cursor_sdk_provider_standing.dag +++ b/dag/gunbc/cursor_sdk_provider_standing.dag @@ -33,7 +33,10 @@ import extdeps.llm.cursor_sdk_errors { cursor_sdk_error_code_is_enumerated_non_authentication_failure, } -data cursor_sdk_provider_standing_note: String = "Produces a six-axis ProviderStandingBundle for Cursor SDK local from modeled auth probe + typed capability gaps. Account headroom is LimitCapabilityGapUnobservable — never LimitObservedAvailable. AuthenticationAuthenticated is derived from probe only, never authored. Coordinates arm names with gunbc.provider_standing (valiant-stag-845 / PR 7926)." +// Produces a six-axis ProviderStandingBundle for Cursor SDK local from modeled auth probe + typed +// capability gaps. Account headroom is LimitCapabilityGapUnobservable — never +// LimitObservedAvailable. AuthenticationAuthenticated is derived from probe only, never authored. +// Coordinates arm names with gunbc.provider_standing (valiant-stag-845 / PR 7926). data cursor_sdk_entitlement_unobserved_reason: NonEmptyStr = "entitlement surface not observed for Cursor SDK local in this slice" as NonEmptyStr diff --git a/dag/gunbc/cursor_sdk_secret_admission.dag b/dag/gunbc/cursor_sdk_secret_admission.dag index e196792bb44..2988a209057 100644 --- a/dag/gunbc/cursor_sdk_secret_admission.dag +++ b/dag/gunbc/cursor_sdk_secret_admission.dag @@ -3,7 +3,8 @@ module gunbc.cursor_sdk_secret_admission import std.types { NonEmptyStr, String } import extdeps.cloud.gcp.secret_ref { SecretRef } -data cursor_sdk_secret_admission_note: String = "Slice after 7922: Cursor SDK local binds an EXACT GCP Secret Manager version — never latest. Key material flows into the SDK credential path only; it is not argv, logs, or probe stdout." +// Slice after 7922: Cursor SDK local binds an EXACT GCP Secret Manager version — never latest. Key +// material flows into the SDK credential path only; it is not argv, logs, or probe stdout. data secret_ref_latest_version_literal: NonEmptyStr = "latest" as NonEmptyStr diff --git a/dag/gunbc/dag_grammar_fork_census.dag b/dag/gunbc/dag_grammar_fork_census.dag index 5723e8c3e7d..fed30838c83 100644 --- a/dag/gunbc/dag_grammar_fork_census.dag +++ b/dag/gunbc/dag_grammar_fork_census.dag @@ -135,10 +135,19 @@ fn lhs_symbol_reach_inside_module_invocation(c: SymbolReachCensus) -> List List { ["grep", "-n", "^fn dag_formal_production_", "src/v2/extdeps/languages/dag.dag"] } +// Bounding a population is not scoping a migration. This receipt says how many and which; it says +// nothing about what the seven become, and it authorises nothing. + // WHAT A ZERO IN THE RIGHT-HAND COLUMN DOES AND DOES NOT LICENSE, stated here because the // attractive misreading is the one this repository keeps recording. Zero references outside the // language module is a real and useful bound: it says the flat table's IDENTITIES are confined, @@ -154,7 +163,6 @@ fn flat_production_roster_invocation() -> List { // asymmetric result and the most actionable thing in this census: the fn_add row is the only one // anything else reaches, so the population that a cut must adjudicate is far smaller than the // population that a cut must delete. -data blast_radius_is_not_the_symbol_census_note: String = "The lhs symbols are confined to one module (0 references elsewhere), and that bounds DELETION, not BREAKAGE. The dependents of the rows built on those productions are reached through dag_target_model, which 10 modules outside the language module name. Six of the seven relation rows have zero consumers outside the language module; only the fn_add row is reached from elsewhere, and by 4 references across 3 modules." // WHAT THIS CENSUS DOES NOT ANSWER, enumerated so a reader does not take a bounded population for // a scoped migration. It does not say what the seven productions should become; the target state @@ -163,4 +171,3 @@ data blast_radius_is_not_the_symbol_census_note: String = "The lhs symbols are c // dag can converge onto that substrate is a separate measurement, not a corollary of this one. // And it does not authorise a cut: the language module is named load-bearing by DESIGN, and this // receipt is input to that decision rather than a substitute for it. -data census_does_not_scope_the_migration_note: String = "Bounding a population is not scoping a migration. This receipt says how many and which; it says nothing about what the seven become, and it authorises nothing." diff --git a/dag/gunbc/declaration_index_seed_growth.dag b/dag/gunbc/declaration_index_seed_growth.dag index fa72bcb5e77..5206077d29d 100644 --- a/dag/gunbc/declaration_index_seed_growth.dag +++ b/dag/gunbc/declaration_index_seed_growth.dag @@ -338,18 +338,330 @@ data declaration_index_seed_growth_justification: SeedGrowthJustification = Seed current_boundary: "src/v1/stage0/src/declaration_index.rs; src/v1/stage0/src/cli_run.rs run_dag_parse_sweep; src/v1/stage0/tests/declaration_index_integrity.rs; src/v1/stage0/src/bin/claim_executor.rs; src/v1/stage0/src/bin/v1_src_dag_parse.rs; dag/gunbc/declaration_index_seed_growth.dag" } -data declaration_index_kernel_named_import_escape_note: String = "THE ONE HOLE THE IMPORT-MEMBER ARM DOES NOT CLOSE, HELD OPEN DELIBERATELY AND HELD COUNTABLE. v1.compiler.resolve get_exported_names builds a module's export surface as item names, variant names, the names that module itself LISTS in its own import blocks, and every key of kernel_type_set. The last term means EVERY module in the corpus exports every kernel type name, so the claim import m -- Int -- is admitted whatever m declares. The index mirrors that rather than refusing it, because refusing source the seed compiler accepts is a change to what the seed ACCEPTS, and editing get_exported_names classifies as NewLanguageBehavior, which the v1 freeze refuses and which a refusal dominates every admission of.\n\nMEASURED AGAINST THE INSTALLED COMPILER, not inferred from the code. On a throwaway source root, a module whose entire body is one fn returning Int was imported as import extdeps.probe_missing_anchor -- Int, String, Bool -- and compiled to 6 files with 0 diagnostics. THE ZERO IS READABLE BECAUSE THE NONZERO WAS RUN BESIDE IT: the same source root with a member named probe_absent_member_RED refused with a located MissingExport at the member token, 0 files emitted. So the wall is live and this class walks through it.\n\nTHE SPECIMEN IS NOT SYNTHETIC, and it is a finding in its own right rather than a count. std.types declares no Int, no String and no Float anywhere -- it names them as KEYS of kernel_type_set, which is a different fact -- and modules across the corpus author import std.types -- Int, String. Every one of those claims is false about std.types and always has been. The reason nobody noticed is precisely the escape: the claim is unfalsifiable, so its frequency is zero by construction, which is the absorbing-fallback shape in its purest form -- a deficit whose frequency is zero because nothing can ever report it, not because it does not occur.\n\nKERNEL-NAMED AND DECLARED ARE INDEPENDENT AXES, AND THE SPLIT IS THE FINDING. Measured over dag/std/types.dag against the eight keys of kernel_type_set: FIVE are genuinely declared there -- Bool, Secret, Json, Unit, Bytes -- and THREE are not -- Int, String, Float. So import std.types -- Bool is a TRUE claim and import std.types -- Int is a FALSE one, and a counter that renders them identically would be reporting that a name appears in kernel_type_set, which needs no census to learn. import_member_is_kernel_named splits them by ORDER: it asks import_surface_has FIRST and consults the kernel set only when the target really declares nothing. An ordering is an implementation detail until something asserts the behaviour it produces, so kernel_named_counter_splits_declared_from_undeclared plants both arms under one member name -- a module that declares it and one that does not -- and requires the counter to answer 1 rather than 2. Reversing the two conjuncts leaves every other arm green and reds that one. The COUNT over the live corpus is not transcribed here: it is emitted as import_members_kernel_named on every required run, and a number copied into prose is the positional citation DESIGN section 3 forbids.\n\nWHAT THIS CHANGE DOES ABOUT IT, which is the only thing DESIGN section 5 permits an arm that widens to do: make the frequency OBSERVABLE. A bare continue would have zeroed the deficit exactly as the corpus does, so the class could never rank for repair -- section 6 prices by displaced cost and a masked cost displaces nothing. It is now counted as import_members_kernel_named on DeclarationIndexPopulation and printed beside every other denominator in both the required-ci and the standalone sweep lines, so a green names it. It is NOT reported as a finding and does not stop the line.\n\nTHE EVIDENCE IS AT THE FIXTURE BOUNDARY, where DESIGN section 4b requires it and where the corpus cannot reach: kernel_named_import_member_is_admitted_but_counted plants the claim and requires BOTH no finding AND a count of exactly one, and a_declared_member_does_not_count_as_kernel_named requires a real member to leave the counter at zero. The first assertion alone would be satisfied by re-silencing the escape; the pair is what makes that a RED.\n\nNEXT-RUNG TRIGGER: get_exported_names grounds its export surface in DEFINITIONS rather than appending the kernel set to every module, at which point import_member_is_kernel_named deletes and the members it admits become ordinary ImportMemberAbsent findings. That is a v1 language-behavior change and is out of scope here; this row is what keeps it ranked." +// THE ONE HOLE THE IMPORT-MEMBER ARM DOES NOT CLOSE, HELD OPEN DELIBERATELY AND HELD COUNTABLE. +// v1.compiler.resolve get_exported_names builds a module's export surface as item names, variant +// names, the names that module itself LISTS in its own import blocks, and every key of +// kernel_type_set. The last term means EVERY module in the corpus exports every kernel type name, +// so the claim import m -- Int -- is admitted whatever m declares. The index mirrors that rather +// than refusing it, because refusing source the seed compiler accepts is a change to what the seed +// ACCEPTS, and editing get_exported_names classifies as NewLanguageBehavior, which the v1 freeze +// refuses and which a refusal dominates every admission of. +// +// MEASURED AGAINST THE INSTALLED COMPILER, not inferred from the code. On a throwaway source root, +// a module whose entire body is one fn returning Int was imported as import +// extdeps.probe_missing_anchor -- Int, String, Bool -- and compiled to 6 files with 0 diagnostics. +// THE ZERO IS READABLE BECAUSE THE NONZERO WAS RUN BESIDE IT: the same source root with a member +// named probe_absent_member_RED refused with a located MissingExport at the member token, 0 files +// emitted. So the wall is live and this class walks through it. +// +// THE SPECIMEN IS NOT SYNTHETIC, and it is a finding in its own right rather than a count. +// std.types declares no Int, no String and no Float anywhere -- it names them as KEYS of +// kernel_type_set, which is a different fact -- and modules across the corpus author import +// std.types -- Int, String. Every one of those claims is false about std.types and always has been. +// The reason nobody noticed is precisely the escape: the claim is unfalsifiable, so its frequency +// is zero by construction, which is the absorbing-fallback shape in its purest form -- a deficit +// whose frequency is zero because nothing can ever report it, not because it does not occur. +// +// KERNEL-NAMED AND DECLARED ARE INDEPENDENT AXES, AND THE SPLIT IS THE FINDING. Measured over +// dag/std/types.dag against the eight keys of kernel_type_set: FIVE are genuinely declared there -- +// Bool, Secret, Json, Unit, Bytes -- and THREE are not -- Int, String, Float. So import std.types +// -- Bool is a TRUE claim and import std.types -- Int is a FALSE one, and a counter that renders +// them identically would be reporting that a name appears in kernel_type_set, which needs no census +// to learn. import_member_is_kernel_named splits them by ORDER: it asks import_surface_has FIRST +// and consults the kernel set only when the target really declares nothing. An ordering is an +// implementation detail until something asserts the behaviour it produces, so +// kernel_named_counter_splits_declared_from_undeclared plants both arms under one member name -- a +// module that declares it and one that does not -- and requires the counter to answer 1 rather than +// 2. Reversing the two conjuncts leaves every other arm green and reds that one. The COUNT over the +// live corpus is not transcribed here: it is emitted as import_members_kernel_named on every +// required run, and a number copied into prose is the positional citation DESIGN section 3 forbids. +// +// WHAT THIS CHANGE DOES ABOUT IT, which is the only thing DESIGN section 5 permits an arm that +// widens to do: make the frequency OBSERVABLE. A bare continue would have zeroed the deficit +// exactly as the corpus does, so the class could never rank for repair -- section 6 prices by +// displaced cost and a masked cost displaces nothing. It is now counted as +// import_members_kernel_named on DeclarationIndexPopulation and printed beside every other +// denominator in both the required-ci and the standalone sweep lines, so a green names it. It is +// NOT reported as a finding and does not stop the line. +// +// THE EVIDENCE IS AT THE FIXTURE BOUNDARY, where DESIGN section 4b requires it and where the corpus +// cannot reach: kernel_named_import_member_is_admitted_but_counted plants the claim and requires +// BOTH no finding AND a count of exactly one, and a_declared_member_does_not_count_as_kernel_named +// requires a real member to leave the counter at zero. The first assertion alone would be satisfied +// by re-silencing the escape; the pair is what makes that a RED. +// +// NEXT-RUNG TRIGGER: get_exported_names grounds its export surface in DEFINITIONS rather than +// appending the kernel set to every module, at which point import_member_is_kernel_named deletes +// and the members it admits become ordinary ImportMemberAbsent findings. That is a v1 +// language-behavior change and is out of scope here; this row is what keeps it ranked. -data declaration_index_authorship_arm_is_not_a_mandatory_tag_region_note: String = "WHY THE LENS AUTHORSHIP ARM IS HOST-SIDE RATHER THAN A SECOND MandatoryTagRegion, recorded because it is the first thing a reviewer will propose and because the reason is a measurement rather than a preference.\n\nv2.lens.mandatory_tag already models exactly this shape: a MandatoryTagRegion names a dotted prefix, a required declaration name and a required type name, and mandatory_tag_law states that a violating module FAILS COMPILATION with a typed, located diagnostic -- unwritable at definition admission, not flagged post-hoc by a corpus scan. mandatory_tag_lens is enrolled in v2.compiler.00_compile always_required_root_lenses. So the obvious move is one more region with prefix v2.lens., required declaration construction_justification, required type ConstructionJustification.\n\nIT DOES NOT FIRE. Measured on the installed compiler: a module declaring itself extdeps.probe_missing_anchor, carrying no extdeps_external_authority_anchor and matched by none of that region's exempt_modules, exempt_module_prefixes or exempt_module_suffixes rows, compiled to 5 files with 0 diagnostics. The v2 compile door is not on the gunbc compile path; the executing compiler is the v1 seed. The zero is readable because the same source root produced a located MissingExport refusal in the control described in the sibling row above, so the compiler does refuse at ingestion -- this gate is simply not reached.\n\nWHAT IS AND IS NOT CLAIMED. This is a note about REACHABILITY of one gate from one path, and it is not a defect finding filed against v2.lens.mandatory_tag, whose fold is correct and whose own witness exercises it. It is the reason the authorship obligation is answered here instead: an arm enrolled in a door that the required run never opens would be green by construction, which DESIGN section 4b calls worse than absent because it gets cited as coverage. When the v2 compile door is the executing ingestion, this arm is the one that should dissolve into a MandatoryTagRegion, and that is its next rung." +// WHY THE LENS AUTHORSHIP ARM IS HOST-SIDE RATHER THAN A SECOND MandatoryTagRegion, recorded +// because it is the first thing a reviewer will propose and because the reason is a measurement +// rather than a preference. +// +// v2.lens.mandatory_tag already models exactly this shape: a MandatoryTagRegion names a dotted +// prefix, a required declaration name and a required type name, and mandatory_tag_law states that a +// violating module FAILS COMPILATION with a typed, located diagnostic -- unwritable at definition +// admission, not flagged post-hoc by a corpus scan. mandatory_tag_lens is enrolled in +// v2.compiler.00_compile always_required_root_lenses. So the obvious move is one more region with +// prefix v2.lens., required declaration construction_justification, required type +// ConstructionJustification. +// +// IT DOES NOT FIRE. Measured on the installed compiler: a module declaring itself +// extdeps.probe_missing_anchor, carrying no extdeps_external_authority_anchor and matched by none +// of that region's exempt_modules, exempt_module_prefixes or exempt_module_suffixes rows, compiled +// to 5 files with 0 diagnostics. The v2 compile door is not on the gunbc compile path; the +// executing compiler is the v1 seed. The zero is readable because the same source root produced a +// located MissingExport refusal in the control described in the sibling row above, so the compiler +// does refuse at ingestion -- this gate is simply not reached. +// +// WHAT IS AND IS NOT CLAIMED. This is a note about REACHABILITY of one gate from one path, and it +// is not a defect finding filed against v2.lens.mandatory_tag, whose fold is correct and whose own +// witness exercises it. It is the reason the authorship obligation is answered here instead: an arm +// enrolled in a door that the required run never opens would be green by construction, which DESIGN +// section 4b calls worse than absent because it gets cited as coverage. When the v2 compile door is +// the executing ingestion, this arm is the one that should dissolve into a MandatoryTagRegion, and +// that is its next rung. -data declaration_index_citation_debt_contract_note: String = "THE FOUR CONDITIONS DESIGN SECTION 5 PUTS ON A MONOTONE DEBT CONTRACT, ANSWERED ONE BY ONE, because without all four a one-sided ratchet over hand-authored rows is only a quieter snapshot -- it counts the subjects someone remembered to author.\n\n1. INDEPENDENTLY DISCOVERED AND CLOSED SUBJECT UNIVERSE. The universe is every authored DeclarationRef in a non-fixture module, discovered by this index from the corpus's own parse rather than enrolled by anyone. It is CLOSED because membership is decided by the same walk that decides the wall: a citation is in the universe if a swept module's source contains it, and there is no route by which a citation exists and the universe does not contain it. This is the condition the mechanism this change replaces FAILED -- v2.lens.cited_symbol_resolution's population was five hand-listed carriers, so a carrier nobody enrolled was outside the universe by default, and the census could not see DESIGN's own worked example.\n\n2. IDENTITY GRAIN, NOT COUNT. A row is the quintuple of CITING module path, the CITING DECLARATION that carries the citation, cited module path, cited declaration name and cited field. The citing module was added by gunbc#9328 and it is the difference between a contract and a permanent exemption: keyed on the cited target alone, one row suppressed that target's citations FROM EVERY MODULE, so the universe was closed over targets while the citing side stayed open and a citation authored tomorrow inherited a row authored today. Nothing anywhere compares a total. One row being replaced by a duplicate of another is not a state this contract can occupy, because the rows are a set of identities and the check is membership.\n\n3. ROADMAP-JUSTIFIED DIRECTION. The direction is toward zero authored citations that name nothing, which is the executing half of DESIGN section 3's cite-the-symbol-not-the-position rule; the lane that owns it is the one this carrier already names, v1-hand-queue-drain. The roster's terminal state is empty, at which point PRE_EXISTING_CITATION_DEBT and citation_debt_findings both delete.\n\n4. TYPED DISPOSITION ON EVERY REMOVAL, and this is the one worth spelling out because two edits that look identical in a diff must not be the same act. A row leaves this roster in exactly one way: the citation it names stops refusing, because someone repaired it. Both other directions are REFUSED, in opposite arms, which is what makes this a contract rather than a suppression list. Delete a row while its citation still refuses and the citation becomes an ordinary CitedDeclarationAbsent finding and stops the line -- so a row cannot be dropped to buy a green. Repair a citation and LEAVE its row standing and citation_debt_findings emits CitationDebtRowStale naming the spent row -- so the roster cannot rot into a list of things that used to be true. The two arms close the loop in both directions, and neither is a judgement call: each is decided by re-running the same resolution the wall runs.\n\nWHAT IS NOT CLAIMED. The rows are not repaired here, and that is a scope decision rather than a coverage claim: the repair for a citation is a judgement about what its author MEANT, across 38 distinct targets with 38 owners, and guessing it is how a stale citation becomes a confidently wrong one." +// THE FOUR CONDITIONS DESIGN SECTION 5 PUTS ON A MONOTONE DEBT CONTRACT, ANSWERED ONE BY ONE, +// because without all four a one-sided ratchet over hand-authored rows is only a quieter snapshot +// -- it counts the subjects someone remembered to author. +// +// 1. INDEPENDENTLY DISCOVERED AND CLOSED SUBJECT UNIVERSE. The universe is every authored +// DeclarationRef in a non-fixture module, discovered by this index from the corpus's own parse +// rather than enrolled by anyone. It is CLOSED because membership is decided by the same walk that +// decides the wall: a citation is in the universe if a swept module's source contains it, and there +// is no route by which a citation exists and the universe does not contain it. This is the +// condition the mechanism this change replaces FAILED -- v2.lens.cited_symbol_resolution's +// population was five hand-listed carriers, so a carrier nobody enrolled was outside the universe +// by default, and the census could not see DESIGN's own worked example. +// +// 2. IDENTITY GRAIN, NOT COUNT. A row is the quintuple of CITING module path, the CITING +// DECLARATION that carries the citation, cited module path, cited declaration name and cited field. +// The citing module was added by gunbc#9328 and it is the difference between a contract and a +// permanent exemption: keyed on the cited target alone, one row suppressed that target's citations +// FROM EVERY MODULE, so the universe was closed over targets while the citing side stayed open and +// a citation authored tomorrow inherited a row authored today. Nothing anywhere compares a total. +// One row being replaced by a duplicate of another is not a state this contract can occupy, because +// the rows are a set of identities and the check is membership. +// +// 3. ROADMAP-JUSTIFIED DIRECTION. The direction is toward zero authored citations that name +// nothing, which is the executing half of DESIGN section 3's cite-the-symbol-not-the-position rule; +// the lane that owns it is the one this carrier already names, v1-hand-queue-drain. The roster's +// terminal state is empty, at which point PRE_EXISTING_CITATION_DEBT and citation_debt_findings +// both delete. +// +// 4. TYPED DISPOSITION ON EVERY REMOVAL, and this is the one worth spelling out because two edits +// that look identical in a diff must not be the same act. A row leaves this roster in exactly one +// way: the citation it names stops refusing, because someone repaired it. Both other directions are +// REFUSED, in opposite arms, which is what makes this a contract rather than a suppression list. +// Delete a row while its citation still refuses and the citation becomes an ordinary +// CitedDeclarationAbsent finding and stops the line -- so a row cannot be dropped to buy a green. +// Repair a citation and LEAVE its row standing and citation_debt_findings emits +// CitationDebtRowStale naming the spent row -- so the roster cannot rot into a list of things that +// used to be true. The two arms close the loop in both directions, and neither is a judgement call: +// each is decided by re-running the same resolution the wall runs. +// +// WHAT IS NOT CLAIMED. The rows are not repaired here, and that is a scope decision rather than a +// coverage claim: the repair for a citation is a judgement about what its author MEANT, across 38 +// distinct targets with 38 owners, and guessing it is how a stale citation becomes a confidently +// wrong one. -data declaration_index_false_quantifier_residue_note: String = "DECLARED RESIDUE: THIS WALL CLOSES THE STALE-SYMBOL HALF AND LEAVES THE FALSE-QUANTIFIER HALF ENTIRELY OPEN. Named here so the next reader finds the gap rather than assuming the citation wall covers it.\n\nWHAT THE WALL CHECKS: that a named symbol RESOLVES. What it does not check: that a QUANTIFIED CLAIM ABOUT A POPULATION is true. The two are independent, and the distinction is not hypothetical -- it was measured twice on this branch, both times inside typed carriers, both times a claim about a population that nobody had counted.\n\nSPECIMEN ONE, in the debt roster's own doc comment: FOUR ROWS AT THE END ARE NOT DEBT ... the deleted census's planted controls. The rows were never added, only described, and the wall's first corpus run reported all four controls as ordinary refusals -- so prose describing rows that do not exist produced a real, imminent, wrong red.\n\nSPECIMEN TWO, in this very carrier, authored by the same hand that built the wall: decl_facts is no longer consulted by any required check. False. decl_facts has live .dag consumers and several are floor-discovered witness modules, so that corpus walk still executes inside the required floor.\n\nWHY THE WALL CANNOT REACH EITHER OF THEM, and this is the structural point rather than an omission: decl_facts is a REAL SYMBOL that RESOLVES. The sentence claiming nothing consults it would pass a cited-symbol census cleanly, because every name in it is true and only the QUANTIFIER is false. A resolver answers does this name exist; nothing in this construction answers is this count right. Section 4c states the general form -- prose is unfalsifiable by construction, which is exactly why an invariant, receipt, count or dissolution condition belongs in a typed carrier and not in a sentence.\n\nHOW THEY WERE ACTUALLY CAUGHT, stated because it is the honest rung: by running the wall over the corpus, and by re-reading the carrier against the request before merge. The first is a mechanism; the second is review diligence, which DESIGN calls strictly weaker in exactly the way it calls the deleted inert-lens census's replacement weaker. So this class sits at MITIGATABLE and nothing here climbs it.\n\nNEXT-RUNG TRIGGER: a quantified claim in a carrier is DERIVED rather than AUTHORED -- the count is a projection of the population it describes, so the sentence cannot disagree with the corpus because it is not a sentence. Until that lands, a prose quantifier inside a typed carrier is debt, and the two specimens above are its measured instances rather than its bound." +// DECLARED RESIDUE: THIS WALL CLOSES THE STALE-SYMBOL HALF AND LEAVES THE FALSE-QUANTIFIER HALF +// ENTIRELY OPEN. Named here so the next reader finds the gap rather than assuming the citation wall +// covers it. +// +// WHAT THE WALL CHECKS: that a named symbol RESOLVES. What it does not check: that a QUANTIFIED +// CLAIM ABOUT A POPULATION is true. The two are independent, and the distinction is not +// hypothetical -- it was measured twice on this branch, both times inside typed carriers, both +// times a claim about a population that nobody had counted. +// +// SPECIMEN ONE, in the debt roster's own doc comment: FOUR ROWS AT THE END ARE NOT DEBT ... the +// deleted census's planted controls. The rows were never added, only described, and the wall's +// first corpus run reported all four controls as ordinary refusals -- so prose describing rows that +// do not exist produced a real, imminent, wrong red. +// +// SPECIMEN TWO, in this very carrier, authored by the same hand that built the wall: decl_facts is +// no longer consulted by any required check. False. decl_facts has live .dag consumers and several +// are floor-discovered witness modules, so that corpus walk still executes inside the required +// floor. +// +// WHY THE WALL CANNOT REACH EITHER OF THEM, and this is the structural point rather than an +// omission: decl_facts is a REAL SYMBOL that RESOLVES. The sentence claiming nothing consults it +// would pass a cited-symbol census cleanly, because every name in it is true and only the +// QUANTIFIER is false. A resolver answers does this name exist; nothing in this construction +// answers is this count right. Section 4c states the general form -- prose is unfalsifiable by +// construction, which is exactly why an invariant, receipt, count or dissolution condition belongs +// in a typed carrier and not in a sentence. +// +// HOW THEY WERE ACTUALLY CAUGHT, stated because it is the honest rung: by running the wall over the +// corpus, and by re-reading the carrier against the request before merge. The first is a mechanism; +// the second is review diligence, which DESIGN calls strictly weaker in exactly the way it calls +// the deleted inert-lens census's replacement weaker. So this class sits at MITIGATABLE and nothing +// here climbs it. +// +// NEXT-RUNG TRIGGER: a quantified claim in a carrier is DERIVED rather than AUTHORED -- the count +// is a projection of the population it describes, so the sentence cannot disagree with the corpus +// because it is not a sentence. Until that lands, a prose quantifier inside a typed carrier is +// debt, and the two specimens above are its measured instances rather than its bound. -data declaration_index_hand_rust_roster_rot_specimen_note: String = "THE OUTSIDE-INDEX BOUNDARY IS NOT THEORETICAL: IT ROTTED THIS PR'S OWN ROSTER INSIDE THIS PR, AND REVIEW CAUGHT IT RATHER THAN THE WALL (review 55899, 2026-08-25). Recorded rather than quietly corrected, for the same reason the false-quantifier row above records its two specimens: a defect found in the carrier that declares the mechanism is evidence about the mechanism.\n\nWHAT HAPPENED. The roster below cited v1_compiler.declaration_index citation_is_pre_existing_debt. That predicate was RENAMED to citation_in_roster earlier on this same branch, as part of the repair that made the debt roster a PARAMETER defaulted to the identity element. The rename updated the code and every caller; it did not update this roster row, so the carrier declaring this PR's hand-Rust obligation cited a symbol this PR had deleted.\n\nWHY THE NEW WALL DID NOT REFUSE IT, and this is the structural point rather than an excuse. The wall resolves a citation against the INDEX, and the index is built from swept .dag modules. v1_compiler is a hand-Rust namespace root that no swept module declares, so citation_is_outside_index counts the row rather than refusing it -- exactly as the boundary disclosed in the note above says it will. The consequence was not disclosed there and is disclosed here: the seed-growth carriers are PRECISELY the rosters that name hand-Rust items, so they are the one population where a rename in the same commit silently rots its own citation. The wall's coverage and the rosters that most need covering are disjoint.\n\nMEASURED AFTER THE REPAIR, over all 55 rows rather than the one review named, because fixing the reported instance and not the class is how a second one survives: 55 rows, 54 resolved against a definition in the five hand-Rust files this carrier enumerates, 1 unresolved -- the row above, now corrected. The +55 enumeration claim in the main note is therefore true, and was true of the COUNT while being false of one row's CONTENT.\n\nNEXT-RUNG TRIGGER. A citation into a hand-Rust namespace resolves against an index of hand-Rust declarations -- the same keyed lookup, over a second source of records derived from the .rs files this carrier already enumerates -- at which case citation_is_outside_index narrows to genuinely unindexed roots and this class stops being writable. That is a strictly larger construction than this PR and is not attempted here; until it lands, a DeclarationRef into v1_compiler is checked by review diligence, which is the same MITIGATABLE rung the false-quantifier row sits at." +// THE OUTSIDE-INDEX BOUNDARY IS NOT THEORETICAL: IT ROTTED THIS PR'S OWN ROSTER INSIDE THIS PR, AND +// REVIEW CAUGHT IT RATHER THAN THE WALL (review 55899, 2026-08-25). Recorded rather than quietly +// corrected, for the same reason the false-quantifier row above records its two specimens: a defect +// found in the carrier that declares the mechanism is evidence about the mechanism. +// +// WHAT HAPPENED. The roster below cited v1_compiler.declaration_index +// citation_is_pre_existing_debt. That predicate was RENAMED to citation_in_roster earlier on this +// same branch, as part of the repair that made the debt roster a PARAMETER defaulted to the +// identity element. The rename updated the code and every caller; it did not update this roster +// row, so the carrier declaring this PR's hand-Rust obligation cited a symbol this PR had deleted. +// +// WHY THE NEW WALL DID NOT REFUSE IT, and this is the structural point rather than an excuse. The +// wall resolves a citation against the INDEX, and the index is built from swept .dag modules. +// v1_compiler is a hand-Rust namespace root that no swept module declares, so +// citation_is_outside_index counts the row rather than refusing it -- exactly as the boundary +// disclosed in the note above says it will. The consequence was not disclosed there and is +// disclosed here: the seed-growth carriers are PRECISELY the rosters that name hand-Rust items, so +// they are the one population where a rename in the same commit silently rots its own citation. The +// wall's coverage and the rosters that most need covering are disjoint. +// +// MEASURED AFTER THE REPAIR, over all 55 rows rather than the one review named, because fixing the +// reported instance and not the class is how a second one survives: 55 rows, 54 resolved against a +// definition in the five hand-Rust files this carrier enumerates, 1 unresolved -- the row above, +// now corrected. The +55 enumeration claim in the main note is therefore true, and was true of the +// COUNT while being false of one row's CONTENT. +// +// NEXT-RUNG TRIGGER. A citation into a hand-Rust namespace resolves against an index of hand-Rust +// declarations -- the same keyed lookup, over a second source of records derived from the .rs files +// this carrier already enumerates -- at which case citation_is_outside_index narrows to genuinely +// unindexed roots and this class stops being writable. That is a strictly larger construction than +// this PR and is not attempted here; until it lands, a DeclarationRef into v1_compiler is checked +// by review diligence, which is the same MITIGATABLE rung the false-quantifier row sits at. -data declaration_index_fixture_carrier_exemption_grain_note: String = "CARRIER IDENTITY IS NOT A LICENCE: THE FIXTURE EXEMPTION MOVED FROM MODULE GRAIN TO CITATION GRAIN (review 55939, 2026-08-26). Recorded here rather than silently amended, because the row above -- WHAT THE ARMS DO NOT COVER -- DISCLOSED this exemption as a bounded scope decision, and the disclosure was wrong about the boundary rather than merely incomplete about it.\n\nWHAT WAS HERE. Both citation arms skipped every citation in a module module_is_fixture_carrier answered true for. The justification was real and still is: a witness proving the resolver refuses an absent symbol has to AUTHOR an absent symbol, so its false citation is its evidence rather than its defect. The defect was that the EXEMPTION WAS KEYED ON THE MODULE while the justification is a property of THE CITATION. Carrier identity establishes that SOME citations in a witness are deliberately false; it never establishes that ALL are.\n\nWHAT SETTLED IT WAS THE MEASUREMENT, NOT THE ARGUMENT, and the ratio is the finding: of 161 citations authored inside fixture carriers, 128 RESOLVE. They are ordinary citations of real authorities that happen to live in a test module. The module-grained skip was shielding 128 real citations in order to protect 33 sites. Reporting the excluded population as in_fixtures did not restore integrity -- a counted hole is still a hole, and DESIGN section 5 says so about exactly this move.\n\nTHE HOLE WAS OCCUPIED, WHICH IS WHAT SEPARATES A DEFECT FROM A DISCLOSED BOUNDARY. Two of the enrolled identities are ordinary staleness with nothing to do with fixture intent: dag.test.claim.witness_purpose_taxonomy_witness is a dag.-prefixed module path no module declares -- the real module is test.claim.witness_purpose_taxonomy_witness, so a plain typo -- and std.disposition Disposition marker names a real authority and a real declaration with an absent field, cited twice. Both were invisible for as long as the skip stood, and both are the class this whole change exists to make decidable.\n\nWHAT THE ROSTER IS. FIXTURE_CARRIER_CITATION_EXEMPTIONS, 41 identities at the (citing module, citing declaration, module, declaration, field) grain -- the citing module and declaration both added by gunbc#9328, and the count moved with the grain rather than with the corpus. It is NOT a debt contract and does not claim to be: condition 3 of section 5's four requires a terminal state of empty, and this roster's is not -- a planted control such as NoSuchDecl_G1_RED is permanent by design and will never be repaired. What it shares with the debt contract is the property that makes either safe rather than a suppression list: it is MONOTONE and it REFUSES WHEN SPENT, through the same inverse arm, so a row whose citation stops refusing is reported and a row deleted while its citation still refuses turns that citation into an ordinary finding that stops the line. Neither direction is a judgement call.\n\nWHAT IS NOT CLASSIFIED, stated rather than hidden: the rows are not sorted into deliberate-control versus genuine-staleness. The two specimens above are named because they are decidable by inspection; deciding what the other authors MEANT is the judgement section 5 warns turns a stale citation into a confidently wrong one, and it is the same scope decision the 42 debt rows already carry. NEXT RUNG: a witness declares its planted controls as typed rows, at which point the deliberate half becomes DERIVABLE and only the genuine half survives here as debt.\n\nHOW THE ROWS WERE OBTAINED, because the first attempt was wrong and the mechanism caught it. Extracting the identities by parsing rendered diagnostics silently dropped the FIELD -- a CitedDeclarationAbsent message never prints one -- so rows for citations carrying a NamedField whose DECLARATION is absent matched nothing. The corpus run then reported the same citation as BOTH refusing AND its row as spent: the paired-inverse-arm desynchronization this carrier already documents, firing on its own author within one run. The rows are now derived from the index itself, from the same CitedSymbol values the matcher compares. Five further refusing identities are deliberately absent because they are already enrolled in PRE_EXISTING_CITATION_DEBT or PLANTED_CONTROL_CITATIONS, and a second row for one citation is duplicate authority with a double stale-arm report.\n\nRUNG: unchanged at MECHANICALLY PREVENTABLE, and this is a WIDENING of the enrolled population rather than a climb -- 128 citations that were never judged now are. The red is authorable and authored at the FIXTURE boundary: a_refusing_citation_inside_a_fixture_carrier_is_judged plants one in a module asserted to BE a fixture carrier, a_fixture_citation_exemption_suppresses_only_its_own_identity plants two siblings and requires exempting one to leave the other refusing, and a_fixture_exemption_whose_citation_resolves_is_spent_and_refuses plants a resolving citation and requires the spent-row arm to name the roster holding it." +// CARRIER IDENTITY IS NOT A LICENCE: THE FIXTURE EXEMPTION MOVED FROM MODULE GRAIN TO CITATION +// GRAIN (review 55939, 2026-08-26). Recorded here rather than silently amended, because the row +// above -- WHAT THE ARMS DO NOT COVER -- DISCLOSED this exemption as a bounded scope decision, and +// the disclosure was wrong about the boundary rather than merely incomplete about it. +// +// WHAT WAS HERE. Both citation arms skipped every citation in a module module_is_fixture_carrier +// answered true for. The justification was real and still is: a witness proving the resolver +// refuses an absent symbol has to AUTHOR an absent symbol, so its false citation is its evidence +// rather than its defect. The defect was that the EXEMPTION WAS KEYED ON THE MODULE while the +// justification is a property of THE CITATION. Carrier identity establishes that SOME citations in +// a witness are deliberately false; it never establishes that ALL are. +// +// WHAT SETTLED IT WAS THE MEASUREMENT, NOT THE ARGUMENT, and the ratio is the finding: of 161 +// citations authored inside fixture carriers, 128 RESOLVE. They are ordinary citations of real +// authorities that happen to live in a test module. The module-grained skip was shielding 128 real +// citations in order to protect 33 sites. Reporting the excluded population as in_fixtures did not +// restore integrity -- a counted hole is still a hole, and DESIGN section 5 says so about exactly +// this move. +// +// THE HOLE WAS OCCUPIED, WHICH IS WHAT SEPARATES A DEFECT FROM A DISCLOSED BOUNDARY. Two of the +// enrolled identities are ordinary staleness with nothing to do with fixture intent: +// dag.test.claim.witness_purpose_taxonomy_witness is a dag.-prefixed module path no module declares +// -- the real module is test.claim.witness_purpose_taxonomy_witness, so a plain typo -- and +// std.disposition Disposition marker names a real authority and a real declaration with an absent +// field, cited twice. Both were invisible for as long as the skip stood, and both are the class +// this whole change exists to make decidable. +// +// WHAT THE ROSTER IS. FIXTURE_CARRIER_CITATION_EXEMPTIONS, 41 identities at the (citing module, +// citing declaration, module, declaration, field) grain -- the citing module and declaration both +// added by gunbc#9328, and the count moved with the grain rather than with the corpus. It is NOT a +// debt contract and does not claim to be: condition 3 of section 5's four requires a terminal state +// of empty, and this roster's is not -- a planted control such as NoSuchDecl_G1_RED is permanent by +// design and will never be repaired. What it shares with the debt contract is the property that +// makes either safe rather than a suppression list: it is MONOTONE and it REFUSES WHEN SPENT, +// through the same inverse arm, so a row whose citation stops refusing is reported and a row +// deleted while its citation still refuses turns that citation into an ordinary finding that stops +// the line. Neither direction is a judgement call. +// +// WHAT IS NOT CLASSIFIED, stated rather than hidden: the rows are not sorted into +// deliberate-control versus genuine-staleness. The two specimens above are named because they are +// decidable by inspection; deciding what the other authors MEANT is the judgement section 5 warns +// turns a stale citation into a confidently wrong one, and it is the same scope decision the 42 +// debt rows already carry. NEXT RUNG: a witness declares its planted controls as typed rows, at +// which point the deliberate half becomes DERIVABLE and only the genuine half survives here as +// debt. +// +// HOW THE ROWS WERE OBTAINED, because the first attempt was wrong and the mechanism caught it. +// Extracting the identities by parsing rendered diagnostics silently dropped the FIELD -- a +// CitedDeclarationAbsent message never prints one -- so rows for citations carrying a NamedField +// whose DECLARATION is absent matched nothing. The corpus run then reported the same citation as +// BOTH refusing AND its row as spent: the paired-inverse-arm desynchronization this carrier already +// documents, firing on its own author within one run. The rows are now derived from the index +// itself, from the same CitedSymbol values the matcher compares. Five further refusing identities +// are deliberately absent because they are already enrolled in PRE_EXISTING_CITATION_DEBT or +// PLANTED_CONTROL_CITATIONS, and a second row for one citation is duplicate authority with a double +// stale-arm report. +// +// RUNG: unchanged at MECHANICALLY PREVENTABLE, and this is a WIDENING of the enrolled population +// rather than a climb -- 128 citations that were never judged now are. The red is authorable and +// authored at the FIXTURE boundary: a_refusing_citation_inside_a_fixture_carrier_is_judged plants +// one in a module asserted to BE a fixture carrier, +// a_fixture_citation_exemption_suppresses_only_its_own_identity plants two siblings and requires +// exempting one to leave the other refusing, and +// a_fixture_exemption_whose_citation_resolves_is_spent_and_refuses plants a resolving citation and +// requires the spent-row arm to name the roster holding it. +// THE SUPPRESSION ROSTERS MOVED FROM TARGET GRAIN TO SITE GRAIN (gunbc#9328). Recorded here rather +// than silently amended, for the reason the two rows above are: the grain of an exemption roster is +// the whole content of whether it is a contract or a hole, and this carrier had disclosed the +// previous grain as if it were sound. +// +// WHAT WAS HERE. All three rosters -- PRE_EXISTING_CITATION_DEBT, PLANTED_CONTROL_CITATIONS, +// FIXTURE_CARRIER_CITATION_EXEMPTIONS -- were keyed (cited module, declaration, field). +// citation_in_roster read the CITED symbol only, so a row exempted that target corpus-wide and +// permanently. A patch could author a BRAND NEW dangling DeclarationRef naming any enrolled target, +// from a module that had never cited it, and the wall stayed silent -- a fail-open inside the +// mechanism built to refuse exactly that class, and decidable from the patch alone. +// +// OCCUPIED, NOT MERELY REACHABLE, measured over DAG_PARSE_SWEEP_ROOTS: the 70 target-keyed rows +// covered 87 refusing sites, and seven targets were already cited from more than one module -- +// gunbc.host_effect host_effect_apply from three, std.bytes builtin_function_registry from three, +// extdeps.network.mac parse_mac_address from two, four more from two apiece. Every extra site was +// suppressed by a row authored about a different module. +// +// WHAT LANDED. A row is (citing module, citing declaration, cited module, declaration, field) and +// exempts THE SITE THAT AUTHORED IT. Both inverse arms read that one identity through a single +// refusing_sites set, because a suppression arm and a staleness arm keyed differently is the +// desynchronization this carrier already records once. The rosters are re-derived from the +// measurement rather than hand-extended: 42 debt, 41 fixture, 4 control, 87 sites, corpus clean. +// +// THE FIRST DERIVATION WAS TAKEN OVER THE WRONG DENOMINATOR, and it is recorded because it is this +// carrier's own recurring class arriving a third time. The sweep's roots are src/v1, dag and +// src/v2; the first measurement used only the last two, so five sites in modules the narrow walk +// never read were absent from the rosters and the required run refused them. A roster derived from +// a subset of the subject it governs is not a smaller roster, it is a wrong one. +// +// HAND-ITEM DELTA: +5, enumerated in the roster above rather than counted -- citation_site, +// refusing_sites and site_owned in declaration_index.rs, and two discriminating tests, +// a_new_citation_of_an_enrolled_target_from_another_module_still_refuses and +// a_roster_row_exempts_its_own_citer_and_no_other. No file is added, no impl block is introduced, +// and every one of the five is citable as a WholeDeclaration. The trigger is unchanged and now +// names 60: these dissolve with the index, not separately. +// +// RUNG: unchanged at MECHANICALLY PREVENTABLE. This is a repair of an open direction in an existing +// wall, not a climb -- the invalid state stays writable and safety still depends on the phase +// executing. The red is authorable and authored at the FIXTURE boundary: both tests above go GREEN +// under the target-keyed form, which is the state they exist to forbid. +// +// THE DECLARATION WAS ADDED AFTER REVIEW AND IT IS THE SAME REPAIR ONE LEVEL IN (review 56227). The +// first cut keyed a row on the citing MODULE, which left two citations of one target inside one +// module sharing a row -- so a new dangling citation authored BESIDE an enrolled one stayed +// suppressed. That was DISCLOSED as residue rather than closed, and the objection was that a +// residue whose closing identity is already available is not a residue. It was available: +// record_from_module already iterates top-level items, so the enclosing declaration name costs one +// string at extraction, and it is a NAME reachable from the containment tree rather than the offset +// DESIGN section 3 forbids. Closed, with +// a_second_citation_of_an_enrolled_target_in_another_declaration_still_refuses as the +// discriminating red -- it reports zero findings under the module grain. +// +// WHAT IS NOT REACHED, stated because a closed residue must not be reported as a total one: two +// citations of one target inside ONE DECLARATION still share a row. Only a position separates +// those, and a position is what this grain exists not to be, so this is a CEILING rather than a +// stall. The next rung would be an occurrence ordinal within the declaration -- representable in +// the record, needed by no measured site today. // THE ENROLLED ROWS ARE NOT DISTINGUISHED BETWEEN DELIBERATE-ABSENT AND STALE, AND THAT IS A STALL // RATHER THAN A SETTLED BOUNDARY. Two of the thirty-one are decidable by inspection and are named @@ -402,8 +714,140 @@ data declaration_index_fixture_exemption_classification_stall: GuaranteeStall = next_rung_trigger: "a witness declares each citation it plants to refuse as a typed row beside the citation -- at which point the deliberate half of this roster is DERIVED rather than enrolled, this roster shrinks to the genuinely stale remainder, and that remainder becomes ordinary debt with a terminal state of empty. Until then a new stale citation authored inside a fixture carrier is caught by the wall, but an existing enrolled one is indistinguishable from a control", } -data declaration_index_site_grain_roster_note: String = "THE SUPPRESSION ROSTERS MOVED FROM TARGET GRAIN TO SITE GRAIN (gunbc#9328). Recorded here rather than silently amended, for the reason the two rows above are: the grain of an exemption roster is the whole content of whether it is a contract or a hole, and this carrier had disclosed the previous grain as if it were sound.\n\nWHAT WAS HERE. All three rosters -- PRE_EXISTING_CITATION_DEBT, PLANTED_CONTROL_CITATIONS, FIXTURE_CARRIER_CITATION_EXEMPTIONS -- were keyed (cited module, declaration, field). citation_in_roster read the CITED symbol only, so a row exempted that target corpus-wide and permanently. A patch could author a BRAND NEW dangling DeclarationRef naming any enrolled target, from a module that had never cited it, and the wall stayed silent -- a fail-open inside the mechanism built to refuse exactly that class, and decidable from the patch alone.\n\nOCCUPIED, NOT MERELY REACHABLE, measured over DAG_PARSE_SWEEP_ROOTS: the 70 target-keyed rows covered 87 refusing sites, and seven targets were already cited from more than one module -- gunbc.host_effect host_effect_apply from three, std.bytes builtin_function_registry from three, extdeps.network.mac parse_mac_address from two, four more from two apiece. Every extra site was suppressed by a row authored about a different module.\n\nWHAT LANDED. A row is (citing module, citing declaration, cited module, declaration, field) and exempts THE SITE THAT AUTHORED IT. Both inverse arms read that one identity through a single refusing_sites set, because a suppression arm and a staleness arm keyed differently is the desynchronization this carrier already records once. The rosters are re-derived from the measurement rather than hand-extended: 42 debt, 41 fixture, 4 control, 87 sites, corpus clean.\n\nTHE FIRST DERIVATION WAS TAKEN OVER THE WRONG DENOMINATOR, and it is recorded because it is this carrier's own recurring class arriving a third time. The sweep's roots are src/v1, dag and src/v2; the first measurement used only the last two, so five sites in modules the narrow walk never read were absent from the rosters and the required run refused them. A roster derived from a subset of the subject it governs is not a smaller roster, it is a wrong one.\n\nHAND-ITEM DELTA: +5, enumerated in the roster above rather than counted -- citation_site, refusing_sites and site_owned in declaration_index.rs, and two discriminating tests, a_new_citation_of_an_enrolled_target_from_another_module_still_refuses and a_roster_row_exempts_its_own_citer_and_no_other. No file is added, no impl block is introduced, and every one of the five is citable as a WholeDeclaration. The trigger is unchanged and now names 60: these dissolve with the index, not separately.\n\nRUNG: unchanged at MECHANICALLY PREVENTABLE. This is a repair of an open direction in an existing wall, not a climb -- the invalid state stays writable and safety still depends on the phase executing. The red is authorable and authored at the FIXTURE boundary: both tests above go GREEN under the target-keyed form, which is the state they exist to forbid.\n\nTHE DECLARATION WAS ADDED AFTER REVIEW AND IT IS THE SAME REPAIR ONE LEVEL IN (review 56227). The first cut keyed a row on the citing MODULE, which left two citations of one target inside one module sharing a row -- so a new dangling citation authored BESIDE an enrolled one stayed suppressed. That was DISCLOSED as residue rather than closed, and the objection was that a residue whose closing identity is already available is not a residue. It was available: record_from_module already iterates top-level items, so the enclosing declaration name costs one string at extraction, and it is a NAME reachable from the containment tree rather than the offset DESIGN section 3 forbids. Closed, with a_second_citation_of_an_enrolled_target_in_another_declaration_still_refuses as the discriminating red -- it reports zero findings under the module grain.\n\nWHAT IS NOT REACHED, stated because a closed residue must not be reported as a total one: two citations of one target inside ONE DECLARATION still share a row. Only a position separates those, and a position is what this grain exists not to be, so this is a CEILING rather than a stall. The next rung would be an occurrence ordinal within the declaration -- representable in the record, needed by no measured site today." +// THE REFERENCE CHANNEL BECAME SELECTIVE BY NODE KIND (gunbc, this change). Recorded here rather +// than only in the Rust doc comment, because the previous behaviour was DECLARED SOUND on this +// carrier's own construction -- record_from_module collected every authored name in a module's tree +// and the field's doc argued the over-collection was harmless -- and a refuted argument has to be +// retired where it was made. +// +// THE ARGUMENT AND WHY IT IS WRONG. It read: the over-collection is SYMMETRIC across the two trees +// v1_compiler.namespace_wave_admission compares, so a spelling that denotes nothing on both sides +// contributes no delta. A symmetric COLLECTOR does not give a symmetric VERDICT. The supplier set +// the wall computes for a row is a function of the CORPUS, not of the site, so deleting an +// unrelated declaration moves it under every site that merely spells the same word -- and a field +// label spells words. +// +// THE SPECIMEN, MEASURED RATHER THAN PREDICTED. On gunbc#9106 a witness module deleted a helper fn +// live_tree_declined_entries and kept twelve RECORD FIELD LABELS of that spelling. Twelve labels, +// twelve enclosing declarations, twelve NewUnresolvedness rows, one-to-one, against a correct cut. +// The delta was TRUE about the declaration and FALSE about every site it named: a label binds to +// nothing and needs no supplier at all. +// +// WHAT LANDED, IN THE SHAPE THE CITED COLLECTOR ON THE SAME WALK ALREADY USED -- decide by node +// kind, never by name. Two kinds stop being references. First, a record literal's FIELD LABELS: +// ExprRecordLit's children are its field initializers and nothing else, so the label is decidable +// from the parent's kind with no guessing, and the initializer's VALUE is still walked because that +// is where a reference lives. Second, a field projection's MEMBER name: f.widget names a field of a +// value, not a declaration. The whole dotted spelling is still recorded, which is what +// module_prefix_of needs to keep a module-qualified reference such as probe.home.widget resolving, +// and the wall keys on the last segment either way. A name-based suppression list was refused: it +// would be the same defect one layer up. +// +// WHAT IS NOT REPAIRED, stated because a partial repair reported as a total one is worse than none. +// A record TYPE declaration's field labels, a named call argument's label, a parameter binder and a +// coproduct's variant names are STILL collected as references, and each can fabricate the same +// refusal from a different position. Measured on a fixture rather than assumed: a type declaring a +// field named tag contributes tag, and a call passing an argument labelled tag contributes tag. +// They are not swept in here because the parent kinds carrying them also carry children that ARE +// real references -- a refinement's base type expression is a Connective Conj child with a real +// type name -- so a parent-kind rule for them cannot be lifted from this one and needs its own +// fixture. Excluding them by guessing would risk the opposite defect, which is strictly worse: a +// wall that stops seeing genuine unresolvedness is a decoration. +// +// EVIDENCE, BOTH DIRECTIONS, AT THE FIXTURE BOUNDARY, in the wave wall's own fixture file. RED: +// deleting_a_declaration_a_record_field_label_merely_spells_carries_no_delta, which reports exactly +// the specimen's NewUnresolvedness under the previous collector and nothing under this one. GREEN, +// and this is the half that matters: +// deleting_a_declaration_a_body_still_references_is_still_unresolvedness and +// deleting_a_declaration_a_qualified_spelling_reaches_is_still_unresolvedness both require the wall +// to KEEP refusing a deletion that a real reference reaches, one bare and one dotted. The second is +// the control on the projection half specifically, because a repair that had dropped the dotted +// spelling instead of the member name would green the red and silence that arm with it. +// +// RUNG: unchanged at MECHANICALLY PREVENTABLE. This is a repair of a fabricated-refusal direction +// in an existing wall, not a climb. HAND-ITEM DELTA: plus one, collect_reference_occurrences, +// enumerated in the roster above; record_from_module's inline walk is replaced by a call to it +// rather than duplicated, and no other declaration is added. The three test arms live in the wave +// wall's fixture file, whose carrier gunbc.namespace_wave_admission enumerates lib declarations +// rather than test arms. -data declaration_index_reference_channel_selectivity_note: String = "THE REFERENCE CHANNEL BECAME SELECTIVE BY NODE KIND (gunbc, this change). Recorded here rather than only in the Rust doc comment, because the previous behaviour was DECLARED SOUND on this carrier's own construction -- record_from_module collected every authored name in a module's tree and the field's doc argued the over-collection was harmless -- and a refuted argument has to be retired where it was made.\n\nTHE ARGUMENT AND WHY IT IS WRONG. It read: the over-collection is SYMMETRIC across the two trees v1_compiler.namespace_wave_admission compares, so a spelling that denotes nothing on both sides contributes no delta. A symmetric COLLECTOR does not give a symmetric VERDICT. The supplier set the wall computes for a row is a function of the CORPUS, not of the site, so deleting an unrelated declaration moves it under every site that merely spells the same word -- and a field label spells words.\n\nTHE SPECIMEN, MEASURED RATHER THAN PREDICTED. On gunbc#9106 a witness module deleted a helper fn live_tree_declined_entries and kept twelve RECORD FIELD LABELS of that spelling. Twelve labels, twelve enclosing declarations, twelve NewUnresolvedness rows, one-to-one, against a correct cut. The delta was TRUE about the declaration and FALSE about every site it named: a label binds to nothing and needs no supplier at all.\n\nWHAT LANDED, IN THE SHAPE THE CITED COLLECTOR ON THE SAME WALK ALREADY USED -- decide by node kind, never by name. Two kinds stop being references. First, a record literal's FIELD LABELS: ExprRecordLit's children are its field initializers and nothing else, so the label is decidable from the parent's kind with no guessing, and the initializer's VALUE is still walked because that is where a reference lives. Second, a field projection's MEMBER name: f.widget names a field of a value, not a declaration. The whole dotted spelling is still recorded, which is what module_prefix_of needs to keep a module-qualified reference such as probe.home.widget resolving, and the wall keys on the last segment either way. A name-based suppression list was refused: it would be the same defect one layer up.\n\nWHAT IS NOT REPAIRED, stated because a partial repair reported as a total one is worse than none. A record TYPE declaration's field labels, a named call argument's label, a parameter binder and a coproduct's variant names are STILL collected as references, and each can fabricate the same refusal from a different position. Measured on a fixture rather than assumed: a type declaring a field named tag contributes tag, and a call passing an argument labelled tag contributes tag. They are not swept in here because the parent kinds carrying them also carry children that ARE real references -- a refinement's base type expression is a Connective Conj child with a real type name -- so a parent-kind rule for them cannot be lifted from this one and needs its own fixture. Excluding them by guessing would risk the opposite defect, which is strictly worse: a wall that stops seeing genuine unresolvedness is a decoration.\n\nEVIDENCE, BOTH DIRECTIONS, AT THE FIXTURE BOUNDARY, in the wave wall's own fixture file. RED: deleting_a_declaration_a_record_field_label_merely_spells_carries_no_delta, which reports exactly the specimen's NewUnresolvedness under the previous collector and nothing under this one. GREEN, and this is the half that matters: deleting_a_declaration_a_body_still_references_is_still_unresolvedness and deleting_a_declaration_a_qualified_spelling_reaches_is_still_unresolvedness both require the wall to KEEP refusing a deletion that a real reference reaches, one bare and one dotted. The second is the control on the projection half specifically, because a repair that had dropped the dotted spelling instead of the member name would green the red and silence that arm with it.\n\nRUNG: unchanged at MECHANICALLY PREVENTABLE. This is a repair of a fabricated-refusal direction in an existing wall, not a climb. HAND-ITEM DELTA: plus one, collect_reference_occurrences, enumerated in the roster above; record_from_module's inline walk is replaced by a call to it rather than duplicated, and no other declaration is added. The three test arms live in the wave wall's fixture file, whose carrier gunbc.namespace_wave_admission enumerates lib declarations rather than test arms." - -data declaration_index_authored_reference_channels_note: String = "TWO AUTHORED REFERENCES THE INDEX COULD NOT REACH, AND THE FALSE GREEN THEY PRODUCED (gunbc, this change). Recorded beside the selectivity note above rather than only in the Rust doc comment, because the two are the SAME FUNCTION moving in OPPOSITE DIRECTIONS -- that one removed fabricated references, this one recovers missing ones -- and a reader who finds only one will conclude the channel is being narrowed or widened as a policy rather than corrected against what the source authors.\n\nWHAT WAS MISSING. collect_reference_occurrences walks a Node through seven child slots. Two authored references are reachable through NEITHER. A VARIANT PATTERN HEAD is a raw String on MatchPattern, not a Node, so a module whose only use of an imported coproduct is naming its variants in match arms contributed nothing. A DECLARED FIELD TYPE is parked by v1.02_parse field_to_child_node in the inferred slot, and every authored-name reader correctly declines to read a slot whose contract says DERIVED.\n\nWHAT THE CONSEQUENCE WAS. v1_compiler.namespace_wave_admission membership_bound_through answered NO about an import whose name is still referenced in the very tree it examined, and the wall reported the live import as UnusedSubjectMembershipRemoved. That is a GREEN, not a refusal, so nothing stops -- and the verdict's natural next action is to delete the import the wall failed to see.\n\nTHE CONSTRUCTION, AND IT IS THE SECOND ONE AUTHORED FOR THIS DEFECT. The first read both facts back off the final Node. An operator ruling narrowed the permitted construction to consuming the parser's existing OccurrenceTransport, and the RATIONALE is what decides the shape rather than the letter: the parser already stamps the fact exactly, so a Node-reading projection is a SECOND AUTHORITY free to diverge from the first. Measured, that is exact for the declared field type -- stamp_parsed_inferred stamps a Resolved inferred node as ParsedOccurrenceReference TypeOccurrence, and the stamp is on the SLOT, so the generic-over-the-form property the first construction gained is precisely the property the transport already owned. The first construction was reimplementing stamp_parsed_inferred from its own output. The seam is one argument wide: CensusFillParse already carries occurrence_transport and both callers already hold one at the line they call record_from_module, so the fact was PRODUCED AND DISCARDED at a return boundary rather than absent.\n\nWHY THE PATTERN HEAD STILL READS AN AUTHORED STRING, and this is a scope finding rather than an exception taken. The rationale has NO REFERENT for that position: MatchPattern.VariantPattern.name is a String, stamp_parsed_pattern stamps field_bindings only, ConstructorOccurrence is stamped NOWHERE in v1_compiler_parse, and occurrence ids are minted per Node so a String can never carry one. The parser mints nothing there, so there is no first authority to be second to -- reading the authored String is not re-derivation, it is the only derivation, and a prohibition whose stated reason does not apply is not extended by its letter. The transport repair for that half is BLOCKED ON A RULING, NOT ON A RISK: the obvious objection was that a new stamp shifts every id allocated after it in a sequential allocator, and that objection is answered -- v2.std.node content_hash folds node kind, edge labels and child hashes and NOT the occurrence id, and v2.workflow.legacy_binding_delta states outright that an OccurrenceId is not a stable cross-compile name BECAUSE the counter is consumed in DFS order, naming inserted tokens as exactly the edit that shifts ids. The corpus does not merely lack a dependency on id stability, it declares that dependency illegitimate. What remains is that stamping a new occurrence is a parser change, which is an authority question.\n\nA PEER FIELD, NOT A WIDENED referenced, as the ruling directs, and the reason survives the ruling: the two sets have different authorities and different precision, and fusing them would hide which is which behind one name. authored_type_references carries the transport's answer; referenced remains the index's own walk. The consumer takes the union, so nothing downstream needs to know which channel supplied a row, and the walk can shrink toward zero as more positions gain transport entries.\n\nTHE FILTER THAT IS LOAD-BEARING RATHER THAN TIDINESS. Only references enclosed by a declaration THIS MODULE DECLARES are taken. The transport stamps an IMPORT MEMBER NAME as a TypeOccurrence enclosed by the import target -- measured, import probe.other { gadget } yields the row (probe.other, gadget) -- so consuming every TypeOccurrence makes each import bound-through by its own member and UnusedSubjectMembershipRemoved becomes UNREACHABLE. A disposition going permanently quiet is strictly worse than the false green this change closes: one wrong verdict against a wall that can no longer fire. It was caught by a PRE-EXISTING arm, not by review and not by any arm authored here.\n\nEVIDENCE, SEVEN ARMS AT THE FIXTURE BOUNDARY, ON THE REMOVAL SIDE DELIBERATELY, WITH A THREE-WAY MUTATION RECEIPT RE-TAKEN AGAINST THIS CONSTRUCTION. The add side cannot test this at all: gunbc#9490 made an import claim answer membership outright for ADD, so an add-side fixture goes green with the repair AND with it reverted -- measured, that arm stayed green under both channel mutations. Deleting the pattern-head read fails 2; yielding nothing from the transport fails 3; deleting the import-member filter fails 2, one of them the pre-existing unused-removal arm; unmutated, 27 pass. The scope question was settled by execution: a type alias RHS reproduces the same false green and is enrolled, a function return type does not reproduce and is recorded as a measured negative rather than enrolled, because an arm there would pass either way.\n\nRUNG: unchanged at MECHANICALLY PREVENTABLE. A repair of a false-green direction in an existing wall, the mirror of the selectivity note's false-refusal repair. HAND-ITEM DELTA: plus one, authored_type_references_from_transport, enumerated in the roster above; the pattern-head block sits inside collect_reference_occurrences which the roster already enumerates, and record_from_module gains one parameter rather than a declaration." +// TWO AUTHORED REFERENCES THE INDEX COULD NOT REACH, AND THE FALSE GREEN THEY PRODUCED (gunbc, this +// change). Recorded beside the selectivity note above rather than only in the Rust doc comment, +// because the two are the SAME FUNCTION moving in OPPOSITE DIRECTIONS -- that one removed +// fabricated references, this one recovers missing ones -- and a reader who finds only one will +// conclude the channel is being narrowed or widened as a policy rather than corrected against what +// the source authors. +// +// WHAT WAS MISSING. collect_reference_occurrences walks a Node through seven child slots. Two +// authored references are reachable through NEITHER. A VARIANT PATTERN HEAD is a raw String on +// MatchPattern, not a Node, so a module whose only use of an imported coproduct is naming its +// variants in match arms contributed nothing. A DECLARED FIELD TYPE is parked by v1.02_parse +// field_to_child_node in the inferred slot, and every authored-name reader correctly declines to +// read a slot whose contract says DERIVED. +// +// WHAT THE CONSEQUENCE WAS. v1_compiler.namespace_wave_admission membership_bound_through answered +// NO about an import whose name is still referenced in the very tree it examined, and the wall +// reported the live import as UnusedSubjectMembershipRemoved. That is a GREEN, not a refusal, so +// nothing stops -- and the verdict's natural next action is to delete the import the wall failed to +// see. +// +// THE CONSTRUCTION, AND IT IS THE SECOND ONE AUTHORED FOR THIS DEFECT. The first read both facts +// back off the final Node. An operator ruling narrowed the permitted construction to consuming the +// parser's existing OccurrenceTransport, and the RATIONALE is what decides the shape rather than +// the letter: the parser already stamps the fact exactly, so a Node-reading projection is a SECOND +// AUTHORITY free to diverge from the first. Measured, that is exact for the declared field type -- +// stamp_parsed_inferred stamps a Resolved inferred node as ParsedOccurrenceReference +// TypeOccurrence, and the stamp is on the SLOT, so the generic-over-the-form property the first +// construction gained is precisely the property the transport already owned. The first construction +// was reimplementing stamp_parsed_inferred from its own output. The seam is one argument wide: +// CensusFillParse already carries occurrence_transport and both callers already hold one at the +// line they call record_from_module, so the fact was PRODUCED AND DISCARDED at a return boundary +// rather than absent. +// +// WHY THE PATTERN HEAD STILL READS AN AUTHORED STRING, and this is a scope finding rather than an +// exception taken. The rationale has NO REFERENT for that position: +// MatchPattern.VariantPattern.name is a String, stamp_parsed_pattern stamps field_bindings only, +// ConstructorOccurrence is stamped NOWHERE in v1_compiler_parse, and occurrence ids are minted per +// Node so a String can never carry one. The parser mints nothing there, so there is no first +// authority to be second to -- reading the authored String is not re-derivation, it is the only +// derivation, and a prohibition whose stated reason does not apply is not extended by its letter. +// The transport repair for that half is BLOCKED ON A RULING, NOT ON A RISK: the obvious objection +// was that a new stamp shifts every id allocated after it in a sequential allocator, and that +// objection is answered -- v2.std.node content_hash folds node kind, edge labels and child hashes +// and NOT the occurrence id, and v2.workflow.legacy_binding_delta states outright that an +// OccurrenceId is not a stable cross-compile name BECAUSE the counter is consumed in DFS order, +// naming inserted tokens as exactly the edit that shifts ids. The corpus does not merely lack a +// dependency on id stability, it declares that dependency illegitimate. What remains is that +// stamping a new occurrence is a parser change, which is an authority question. +// +// A PEER FIELD, NOT A WIDENED referenced, as the ruling directs, and the reason survives the +// ruling: the two sets have different authorities and different precision, and fusing them would +// hide which is which behind one name. authored_type_references carries the transport's answer; +// referenced remains the index's own walk. The consumer takes the union, so nothing downstream +// needs to know which channel supplied a row, and the walk can shrink toward zero as more positions +// gain transport entries. +// +// THE FILTER THAT IS LOAD-BEARING RATHER THAN TIDINESS. Only references enclosed by a declaration +// THIS MODULE DECLARES are taken. The transport stamps an IMPORT MEMBER NAME as a TypeOccurrence +// enclosed by the import target -- measured, import probe.other { gadget } yields the row +// (probe.other, gadget) -- so consuming every TypeOccurrence makes each import bound-through by its +// own member and UnusedSubjectMembershipRemoved becomes UNREACHABLE. A disposition going +// permanently quiet is strictly worse than the false green this change closes: one wrong verdict +// against a wall that can no longer fire. It was caught by a PRE-EXISTING arm, not by review and +// not by any arm authored here. +// +// EVIDENCE, SEVEN ARMS AT THE FIXTURE BOUNDARY, ON THE REMOVAL SIDE DELIBERATELY, WITH A THREE-WAY +// MUTATION RECEIPT RE-TAKEN AGAINST THIS CONSTRUCTION. The add side cannot test this at all: +// gunbc#9490 made an import claim answer membership outright for ADD, so an add-side fixture goes +// green with the repair AND with it reverted -- measured, that arm stayed green under both channel +// mutations. Deleting the pattern-head read fails 2; yielding nothing from the transport fails 3; +// deleting the import-member filter fails 2, one of them the pre-existing unused-removal arm; +// unmutated, 27 pass. The scope question was settled by execution: a type alias RHS reproduces the +// same false green and is enrolled, a function return type does not reproduce and is recorded as a +// measured negative rather than enrolled, because an arm there would pass either way. +// +// RUNG: unchanged at MECHANICALLY PREVENTABLE. A repair of a false-green direction in an existing +// wall, the mirror of the selectivity note's false-refusal repair. HAND-ITEM DELTA: plus one, +// authored_type_references_from_transport, enumerated in the roster above; the pattern-head block +// sits inside collect_reference_occurrences which the roster already enumerates, and +// record_from_module gains one parameter rather than a declaration. diff --git a/dag/gunbc/declared_import_closure_binding.dag b/dag/gunbc/declared_import_closure_binding.dag index d08e24ed02e..67f5a5d4c26 100644 --- a/dag/gunbc/declared_import_closure_binding.dag +++ b/dag/gunbc/declared_import_closure_binding.dag @@ -4,7 +4,16 @@ import std.types { String, NonEmptyStr, Bool, Int, List } import v2.std.optional { Optional, Present, Absent } import gunbc.compile_clean_diagnostic_policy { UnlistedImportBindingSource, ListedImport, PoolCoincidence } -data declared_import_closure_binding_charter_note: String = "THE CLASS B OBSERVATION SURFACE for declared-import-closure binding (#6985). Host twin of observe_declared_import_closure_symbol_binding: compile an entry under primary-precedence pool_roots using ONLY its declared import-edge closure (no reference-derived widening), then report whether consumer_module binds symbol and through which UnlistedImportBindingSource arm. MEASUREMENT ONLY — callers judge ListedImport vs PoolCoincidence vs refusal. ObservationProductionRefused is distinct from BindingObserved refusal so could-not-measure, setup failure, compile graph:None (parse/frontend refusal — P1(b)), and any hard diagnostic on the compile (untrustworthy observation — producer refuses before populating the carrier) never read as the subject passing (DESIGN §5). BindingObserved requires a successfully ingested graph with zero hard diagnostics." +// THE CLASS B OBSERVATION SURFACE for declared-import-closure binding (#6985). Host twin of +// observe_declared_import_closure_symbol_binding: compile an entry under primary-precedence +// pool_roots using ONLY its declared import-edge closure (no reference-derived widening), then +// report whether consumer_module binds symbol and through which UnlistedImportBindingSource arm. +// MEASUREMENT ONLY — callers judge ListedImport vs PoolCoincidence vs refusal. +// ObservationProductionRefused is distinct from BindingObserved refusal so could-not-measure, setup +// failure, compile graph:None (parse/frontend refusal — P1(b)), and any hard diagnostic on the +// compile (untrustworthy observation — producer refuses before populating the carrier) never read +// as the subject passing (DESIGN §5). BindingObserved requires a successfully ingested graph with +// zero hard diagnostics. type DeclaredImportClosureBindingObservation = BindingObserved { diff --git a/dag/gunbc/deploy_transition_bootstrap.dag b/dag/gunbc/deploy_transition_bootstrap.dag index 17e681de5eb..954f1777c89 100644 --- a/dag/gunbc/deploy_transition_bootstrap.dag +++ b/dag/gunbc/deploy_transition_bootstrap.dag @@ -16,35 +16,32 @@ import gunbc.live_deploy.operations { // THE FIRST TRANSITION IS NOT PROTECTED BY ANY MARKER, AND THAT IS NOT A GAP IN THE MARKER. // -// `gunbc.deploy_transition` inhibits release-consuming actuators by writing a durable record that -// the actuator READS. That works for every actuator running a release which knows the record -// exists. It cannot work for the FIRST activation, because the belt timer and the HTTP dispatcher -// live during it are running the OLD binary -- the one installed before the mechanism was written -// -- and no file can inhibit a program that never looks for it. The new release is not installed -// until partway through the very transition that needs protecting. +// `gunbc.deploy_transition` inhibits release-consuming actuators by writing a durable record the +// actuator READS. That works for every release that knows the record exists. It cannot work for the +// FIRST activation: the belt timer and HTTP dispatcher alive during it run the OLD binary, installed +// before the mechanism existed, and no file inhibits a program that never looks for it. The new +// release is not installed until partway through the very transition needing protection. // -// So there are two regimes and the transition between them is itself a modeled step. +// So there are two regimes, and the transition between them is itself a modeled step. // -// BOOTSTRAP: the old release's actuators are stopped from OUTSIDE the process, by masking their +// BOOTSTRAP: the old release's actuators are stopped from OUTSIDE the process by masking their // systemd units. That requires nothing of the old binary and survives the death of the deploying -// process, which is the property an in-process gate cannot have here. +// process -- the property an in-process gate cannot have here. // -// THIS PARAGRAPH USED TO ALSO CLAIM IT SURVIVES A REBOOT, AND THAT CLAIM IS WITHDRAWN AS UNMEASURED. -// A persistent mask writes its /dev/null symlink at /etc/systemd/system/, which is the SAME -// PATH these units occupy as real installed files, so systemd may refuse it there and the working -// mechanism becomes `mask --runtime` in /run/systemd/system, which a reboot discards. Which one -// srv1 actually gives has not been measured, so the durable-across-reboot half is not asserted -- -// it is OBSERVED per unit and carried on `MaskPersistence`, and `mask_survives_reboot` answers from -// that observation rather than from this sentence. Unobserved answers false, because a durability -// nobody looked at is not a durability. It is -// stronger than the steady-state mechanism and correspondingly more expensive: the dashboard is -// down for the window, which the single-deployment ruling already accepts as the cost of a -// restart-in-place deployment. +// THIS PARAGRAPH USED TO ALSO CLAIM IT SURVIVES A REBOOT; THAT CLAIM IS WITHDRAWN AS UNMEASURED. A +// persistent mask writes its /dev/null symlink at /etc/systemd/system/, the SAME PATH these +// units occupy as real installed files, so systemd may refuse it there and the working mechanism +// becomes `mask --runtime` in /run/systemd/system, which a reboot discards. Which one srv1 gives has +// not been measured, so the durable-across-reboot half is not asserted -- it is OBSERVED per unit +// and carried on `MaskPersistence`, and `mask_survives_reboot` answers from that observation. +// Unobserved answers false: a durability nobody looked at is not a durability. Bootstrap is stronger +// than the steady-state mechanism and correspondingly more expensive: the dashboard is down for the +// window, which the single-deployment ruling already accepts as the cost of restart-in-place. // // STEADY STATE: the installed release consults the durable marker itself, so its units may run // while a transition is in force -- every actuation path inside them refuses on its own. The units -// are re-enabled at that point even though the inhibition is still set, because the inhibition is -// now enforced one level in. +// are re-enabled then even though the inhibition is still set, because it is now enforced one level +// in. // // THE SWITCH IS A WALL, NOT A STEP. Re-enabling the units is admitted only when the INSTALLED // release is observed to be marker-aware. Re-enabling over an unaware release would restore exactly @@ -65,10 +62,10 @@ type QuiescenceRegimeDecision | QuiescenceRegimeUndecidable { cause: String } // AN UNOBSERVED AWARENESS REFUSES RATHER THAN CHOOSING THE STRONGER REGIME. Defaulting to bootstrap -// would be safe in outcome and wrong in kind: it answers a question it could not compute with the -// most protective value, which zeroes the frequency of the observation deficit by construction and -// makes an unobservable release indistinguishable from an old one. A transition that cannot tell -// which release is running has no business starting. +// is safe in outcome and wrong in kind: it answers a question it could not compute with the most +// protective value, zeroing the observation deficit by construction and making an unobservable +// release indistinguishable from an old one. A transition that cannot tell which release is running +// has no business starting. fn quiescence_regime(awareness: ReleaseMarkerAwareness) -> QuiescenceRegimeDecision { match awareness { MarkerAware { release_identity: _ } => @@ -85,11 +82,11 @@ fn quiescence_regime(awareness: ReleaseMarkerAwareness) -> QuiescenceRegimeDecis } } -// THE QUIESCED SET IS EVERY UNIT THAT CAN ACTUATE A RELEASE, and both of the belt's units are in it -// rather than only the timer. Disabling the timer alone leaves the one-shot service startable by -// anything that reaches systemd, and the HTTP dispatcher lives in the serve unit -- the deploy -// convergence design record already records that stopping only the timer leaves the click path -// admitting against a half-published release, which is this defect one layer out. +// THE QUIESCED SET IS EVERY UNIT THAT CAN ACTUATE A RELEASE, so both belt units are in it, not only +// the timer. Disabling the timer alone leaves the one-shot service startable by anything reaching +// systemd, and the HTTP dispatcher lives in the serve unit -- the deploy convergence design record +// already notes that stopping only the timer leaves the click path admitting against a +// half-published release, which is this defect one layer out. fn bootstrap_quiesced_units(names: DeploymentNames) -> List { [ deployment_belt_timer_unit_name(names: names), @@ -99,32 +96,30 @@ fn bootstrap_quiesced_units(names: DeploymentNames) -> List { } // `mask --now` AND NOT `disable --now`, AND THIS ROW IS A CORRECTION RATHER THAN A PREFERENCE. It -// previously read `disable --now`, on the argument that disabling removes the enablement symlinks -// and is therefore durable where `stop` is not. The durability argument against `stop` still holds -// and is why this is not `stop`. The rest of it was FALSE FOR ONE OF THE THREE UNITS THIS QUIESCES, -// which is worse than a weak choice because the row asserted a guarantee the operation does not -// deliver. +// previously read `disable --now`, arguing that disabling removes the enablement symlinks and is +// therefore durable where `stop` is not. That argument against `stop` still holds. The rest was +// FALSE FOR ONE OF THE THREE UNITS THIS QUIESCES -- worse than a weak choice, because the row +// asserted a guarantee the operation does not deliver. // // MEASURED, not reasoned: `gunbc.live_deploy.emit` renders the belt SERVICE with -// `install: NotInstallable` -- it is a STATIC unit, activated by the belt timer rather than by an -// [Install] section. A static unit has no enablement symlinks, so `systemctl disable` has nothing -// to remove and does nothing; only the `--now` half took effect, and that half is exactly the -// non-durable `stop` the row above rejects. So for the belt service the declared durability was -// supplied by the timer being disabled separately, and not by anything this function did. +// `install: NotInstallable` -- a STATIC unit, activated by the belt timer rather than an [Install] +// section. A static unit has no enablement symlinks, so `systemctl disable` does nothing; only the +// `--now` half took effect, which is exactly the non-durable `stop` rejected above. The belt +// service's declared durability was supplied by the timer being disabled separately, not by this +// function. // -// `mask` is correct for both kinds and is stronger on the axis that actually matters here: it links -// the unit to /dev/null, so activation is refused whatever requests it -- the timer, a dependency, -// or a hand at a terminal. `disable` never prevented a manual `systemctl start`, and a bootstrap -// quiescence whose whole purpose is that NOTHING actuates during the first transition cannot leave -// that route open. +// `mask` is correct for both kinds and stronger on the axis that matters: it links the unit to +// /dev/null, so activation is refused whatever requests it -- timer, dependency, or a hand at a +// terminal. `disable` never prevented a manual `systemctl start`, and a bootstrap quiescence whose +// purpose is that NOTHING actuates during the first transition cannot leave that route open. // // AND MASK IS STATE-PRESERVING, WHICH IS WHY THE RESTORE BELOW GOT SIMPLER RATHER THAN LONGER. -// `disable` DESTROYS enablement state, so a restore has to re-decide what that state was -- and the -// previous restore re-enabled all three unconditionally, which would have ENABLED a unit an -// operator had deliberately disabled before the transition began, and would additionally have -// failed on the static one, since `enable` has no install section to act on either. `mask` layers -// over the existing state and `unmask` removes the layer, leaving prior enablement exactly as -// found. The restore therefore asserts nothing about what was enabled; it only stops overriding. +// `disable` DESTROYS enablement state, so a restore must re-decide it -- the previous restore +// re-enabled all three unconditionally, which would have ENABLED a unit an operator had +// deliberately disabled before the transition, and would have failed on the static one, since +// `enable` has no install section either. `mask` layers over existing state and `unmask` removes +// the layer, leaving prior enablement as found. The restore asserts nothing about what was enabled; +// it only stops overriding. fn bootstrap_quiesce_commands(names: DeploymentNames, privileged: Bool) -> List { map( bootstrap_quiesced_units(names: names), @@ -133,16 +128,16 @@ fn bootstrap_quiesce_commands(names: DeploymentNames, privileged: Bool) -> List< } // NO `start` HERE, AND ITS ABSENCE IS THE SAME STATE-PRESERVING ARGUMENT. The previous restore -// started all three units after enabling them, which asserts that all three were RUNNING before the -// quiescence. The belt timer starts the belt service on its own cadence and the serve unit is -// started by its enablement at boot, so starting them by hand here would both duplicate that and -// paper over the case where one of them was legitimately down. Unmasking returns the host to the -// state the transition found; it does not decide what that state should be. +// started all three units after enabling them, asserting all three were RUNNING before quiescence. +// The belt timer starts the belt service on its own cadence and the serve unit starts from its +// enablement at boot, so starting them by hand would duplicate that and paper over a unit that was +// legitimately down. Unmasking returns the host to the state the transition found; it does not +// decide what that state should be. // QUIESCENCE IS TWO INDEPENDENTLY OBSERVED FACTS, AND ONE COMMAND EXIT PROVES NEITHER. -// (Operator ruling relayed 2026-08-28.) `stop` establishes CURRENTLY INACTIVE. `mask` is intended +// (Operator ruling relayed 2026-08-28.) `stop` establishes CURRENTLY INACTIVE; `mask` is intended // to establish NOT STARTABLE. They are different propositions about different times -- "nothing is // running now" and "nothing can start while I am gone" -- and the transition needs BOTH. This is -// the same lease/inhibition split this lane already accepted, applied one layer out to the units. +// the lease/inhibition split this lane already accepted, applied one layer out to the units. // // AND THE ADMISSION LAW IS PHRASED ON THE OBSERVED REFUSAL, NEVER ON THE MECHANISM. It does not say // "a mask symlink was written": these units are installed as local /etc/systemd/system files, and @@ -152,31 +147,30 @@ fn bootstrap_quiesce_commands(names: DeploymentNames, privileged: Bool) -> List< // stronger fact. // // WHY A SUCCESSFUL EXIT CODE IS NOT THE RECEIPT. `systemctl mask` returning 0 says the request was -// accepted, not that activation is refused -- it cannot distinguish a real mask from a unit that is -// merely stopped and disabled, which is exactly the state this module shipped before the mask -// correction and exactly the state that reads as quiesced while remaining startable. So the -// activation standing is minted ONLY from the outcome of a PLANTED DIRECT START, and the arms are -// assigned against intuition: a start that FAILED is the admitting evidence, and a start that -// SUCCEEDED refuses the transition. There is no arm in which an exit code from the mask itself -// reaches this standing. +// accepted, not that activation is refused -- it cannot distinguish a real mask from a unit merely +// stopped and disabled, which is exactly the state this module shipped before the mask correction +// and reads as quiesced while remaining startable. So the activation standing is minted ONLY from +// the outcome of a PLANTED DIRECT START, with the arms assigned against intuition: a start that +// FAILED is the admitting evidence, and a start that SUCCEEDED refuses the transition. No arm lets +// the mask's own exit code reach this standing. // // THE PLANTED START IS DESTRUCTIVE IF IT SUCCEEDS, AND THAT IS PART OF THE MEASUREMENT RATHER THAN // A HAZARD TO SUPPRESS. If the probe starts the belt service, an actuator has just run against a -// tree mid-transition -- which is the failure this whole lane exists to prevent, now OBSERVED -// instead of assumed. `ActivationObservedAdmitted` therefore refuses loudly and names the unit; the -// caller re-quiesces and does not proceed. Suppressing the probe to avoid the risk would leave the -// same state unobserved, which is how it got here. +// mid-transition tree -- the failure this lane exists to prevent, now OBSERVED instead of assumed. +// `ActivationObservedAdmitted` therefore refuses loudly and names the unit; the caller re-quiesces +// and does not proceed. Suppressing the probe would leave the same state unobserved, which is how +// it got here. // WHEN THE PROBE SUCCEEDS, SOMETHING IS NOW RUNNING, AND RECORDING THAT IS NOT THE SAME AS ENDING // IT. (Operator question, 2026-08-28.) The refusing arm was correct and incomplete: in exactly the -// case the probe exists to catch -- the mask did not take -- a belt tick has been started against a +// case the probe exists to catch -- the mask did not take -- a belt tick has started against a // mid-transition tree, and a fold that only RECORDED the observation would leave it running. The -// refusal would be right and the side effect would still be the failure this lane exists to prevent. +// refusal would be right and the side effect would still be the failure this lane prevents. // -// SO CONTAINMENT IS A FIELD OF THE ADMITTED ARM RATHER THAN A STEP SOMEONE REMEMBERS. There is no -// spelling of "the planted start succeeded" that does not also carry what happened when we stopped -// it, which is the difference between a construction and a convention. And the stop's own outcome -// is OBSERVED rather than assumed: a stop that failed is strictly worse than one that succeeded -- -// an actuator is running right now -- so it keeps its own arm and its own remedy. +// SO CONTAINMENT IS A FIELD OF THE ADMITTED ARM RATHER THAN A STEP SOMEONE REMEMBERS. No spelling +// of "the planted start succeeded" omits what happened when we stopped it -- the difference between +// a construction and a convention. The stop's own outcome is OBSERVED, not assumed: a stop that +// failed is strictly worse than one that succeeded -- an actuator is running right now -- so it +// keeps its own arm and its own remedy. type ProbeContainment = ProbeContained { detail: String } | ProbeUncontained { detail: String } @@ -194,17 +188,16 @@ fn quiescence_probe_stop_command(unit: NonEmptyStr, privileged: Bool) -> String systemctl_stop_command(unit: unit, privileged: privileged) } -// WHICH MASK MECHANISM TOOK IS AN OBSERVED FACT, NOT AN ASSUMED ONE, and this carrier exists because +// WHICH MASK MECHANISM TOOK IS AN OBSERVED FACT, NOT AN ASSUMED ONE; this carrier exists because // this module made a reboot-durability claim it had not measured. A PERSISTENT mask writes its -// /dev/null symlink at /etc/systemd/system/ -- the same path these units occupy as real -// installed files -- so systemd may refuse it there, and the working mechanism becomes -// `mask --runtime`, which lands in /run/systemd/system and DOES NOT SURVIVE A REBOOT. +// /dev/null symlink at /etc/systemd/system/ -- the path these units occupy as real installed +// files -- so systemd may refuse it there, and the working mechanism becomes `mask --runtime`, +// which lands in /run/systemd/system and DOES NOT SURVIVE A REBOOT. // // Both mechanisms satisfy "cannot start while I am gone"; only the persistent one satisfies -// "survives a reboot". Those are different propositions and the module previously conflated them by -// asserting the stronger. Carrying the observation means the record cannot claim a durability it did -// not see, and a caller that needs the reboot-spanning property can read which it got instead of -// trusting a sentence written in August. +// "survives a reboot". The module previously conflated them by asserting the stronger. Carrying the +// observation means the record cannot claim a durability it did not see, and a caller needing the +// reboot-spanning property reads which it got instead of trusting a sentence written in August. type MaskPersistence = MaskPersistentObserved { unit: NonEmptyStr, path: String } | MaskRuntimeOnlyObserved { unit: NonEmptyStr, path: String } @@ -309,11 +302,11 @@ fn observe_unit_quiescence( // while I am gone" and "the inhibition survives a reboot" are different propositions, and only the // first is what quiescence means -- a transition that does not span a reboot is correctly admitted // over a runtime mask. So the fact travels on the record for a caller whose window DOES span one, -// and `unit_quiescence_admission` stays silent about it rather than refusing a state that is fine. +// and `unit_quiescence_admission` stays silent about it rather than refusing a fine state. // // THIS FIELD IS WHY THE TYPE EXISTS AT ALL. It was introduced carrying the withdrawn reboot claim // and then not wired to anything -- `MaskPersistence` had one Bool reader and no carrier, so the -// commit that added it asserted the mechanism was "observed per unit" while nothing observed it. +// commit adding it asserted the mechanism was "observed per unit" while nothing observed it. // Caught in review of the predicate, one level past what the review found. fn quiescence_survives_reboot(q: ExternalQuiescence) -> Bool { mask_survives_reboot(p: q.persistence) @@ -425,30 +418,29 @@ fn adopt_steady_state(installed: ReleaseMarkerAwareness) -> SteadyStateAdoption } } -// THE ORDER IS THE SAFETY PROPERTY, so it is a modeled successor relation rather than the order in -// which a caller happens to write the calls. Every adjacent pair below is one of the requirements -// this lane exists to hold, and a skipped or reordered step is a refusal naming both steps rather -// than a run that quietly omitted one. +// THE ORDER IS THE SAFETY PROPERTY, so it is a modeled successor relation rather than the order a +// caller happens to write the calls in. Every adjacent pair below is one of the requirements this +// lane holds; a skipped or reordered step is a refusal naming both steps, never a run that quietly +// omitted one. // -// THE SEQUENCE IS PRINCIPAL -> LEASE -> OPERANDS -> MUTATION (operator ruling, 2026-08-28), and the -// first arrow is the one an earlier revision of this vocabulary got wrong: it began at -// `StepLeaseHeld`, which reads as harmless and is not. ACQUISITION IS ITSELF A PRIVILEGED WRITE. A -// unit that acquires before establishing which principal it runs as creates the lease -- mode 0600, -// owned by whoever happened to be executing -- and that is precisely the unreadable-marker state -// this lane spent its existence removing, manufactured by the guard itself. The guard cannot be the -// thing that produces the hazard it exists to detect, so the principal is admitted first and -// everything else is downstream of it. +// THE SEQUENCE IS PRINCIPAL -> LEASE -> OPERANDS -> MUTATION (operator ruling, 2026-08-28), and +// the first arrow is the one an earlier revision got wrong: it began at `StepLeaseHeld`, which +// reads as harmless and is not. ACQUISITION IS ITSELF A PRIVILEGED WRITE. A unit that acquires +// before establishing its principal creates the lease -- mode 0600, owned by whoever happened to be +// executing -- precisely the unreadable-marker state this lane exists to remove, manufactured by +// the guard itself. The guard cannot produce the hazard it detects, so the principal is admitted +// first and everything else is downstream. // -// The four-phase reading maps onto the seven steps as: PRINCIPAL is `StepPrincipalAdmitted`; LEASE -// is `StepLeaseHeld`; OPERANDS are `StepExternallyQuiesced` and `StepInhibitionPersisted`, the two +// The four phases map onto the seven steps as: PRINCIPAL is `StepPrincipalAdmitted`; LEASE is +// `StepLeaseHeld`; OPERANDS are `StepExternallyQuiesced` and `StepInhibitionPersisted`, the two // preconditions that must stand before anything is written; MUTATION is `StepReleaseInstalled` and -// the three steps that give the host back. They are one vocabulary at two grains, not two. +// the three steps that give the host back. One vocabulary at two grains, not two. // -// Read the rest as the two invariants it encodes. Nothing mutates before the principal is known, the -// lease is held and the old actuators are quiesced. Nothing is released -- units, then marker, then -// lease -- until the layer beneath it is in place: units restore only over an aware release, the -// marker clears only after the units are governed in-process, and the lease releases last so a -// second transition cannot begin while any of it is outstanding. +// The rest encodes two invariants. Nothing mutates before the principal is known, the lease is +// held and the old actuators are quiesced. Nothing is released -- units, then marker, then lease -- +// until the layer beneath it is in place: units restore only over an aware release, the marker +// clears only after the units are governed in-process, and the lease releases last so a second +// transition cannot begin while any of it is outstanding. type TransitionStep = StepPrincipalAdmitted | StepLeaseHeld @@ -472,21 +464,20 @@ fn transition_step_label(step: TransitionStep) -> String { } } -// WHERE A CRASH LEAVES THE HOST, per step, and the property that holds at every one of them: no -// state below leaves an unguarded actuator over a tree that may be half-converged. +// WHERE A CRASH LEAVES THE HOST, per step, and the property holding at every one: no state below +// leaves an unguarded actuator over a tree that may be half-converged. // -// After principal-admitted: nothing has been written at all -- no lease, no marker -- so a crash here -// leaves the host exactly as it was. That is the whole reason this step is first. -// After lease-held: nothing mutated; the lease file is residue, resolved by the clearance path in -// gunbc.deploy_transition on evidence. After externally-quiesced: the units are disabled and stay -// quiesced across the window, so the dashboard is down -- an outage, and the SAFE side of this -// mechanism, restored by re-running the deployment or by an operator abandonment plus a deliberate -// enable, both of which leave a record. Through inhibition-persisted and release-installed both -// mechanisms are in force at once, which is the only overlap in the sequence and is deliberate: the -// marker takes over from external quiescence rather than replacing it in one uncovered instant. -// After steady-state-adopted the units run again and refuse in-process, because the switch was -// admitted only over a release that consults the marker. After inhibition-cleared only the lease is -// outstanding, which is the cheapest residue in the sequence. +// After principal-admitted: nothing written at all -- no lease, no marker -- so the host is exactly +// as it was; that is why this step is first. After lease-held: nothing mutated; the lease file is +// residue, resolved by the clearance path in gunbc.deploy_transition on evidence. After +// externally-quiesced: the units are disabled and stay quiesced across the window, so the dashboard +// is down -- an outage, the SAFE side of this mechanism, restored by re-running the deployment or by +// operator abandonment plus a deliberate enable, both of which leave a record. Through +// inhibition-persisted and release-installed both mechanisms are in force at once -- the only +// overlap in the sequence, deliberate: the marker takes over from external quiescence rather than +// replacing it in one uncovered instant. After steady-state-adopted the units run again and refuse +// in-process, because the switch was admitted only over a marker-consulting release. After +// inhibition-cleared only the lease is outstanding, the cheapest residue in the sequence. fn transition_step_successor(step: TransitionStep) -> TransitionStep? { match step { StepPrincipalAdmitted => Present { value: StepLeaseHeld } diff --git a/dag/gunbc/deployed_tree_remote.dag b/dag/gunbc/deployed_tree_remote.dag index 7d72444fbe3..a169e392501 100644 --- a/dag/gunbc/deployed_tree_remote.dag +++ b/dag/gunbc/deployed_tree_remote.dag @@ -19,21 +19,21 @@ import gunbc.repository { gunbc_repository } // WHY A DEPLOYED TREE NEEDS A REMOTE AT ALL, since it never fetches. // -// `gunbc.fleet_desired_observe` reads refs/fleet/desired with `git ls-remote origin`, which is a -// network call THROUGH A CONFIGURED REMOTE. The belt performs that observation on every tick, so a -// deployed tree with no remote cannot answer it and the belt refuses -- spawn and reap withheld, -// every roadmap node rendering a fault. Measured on srv1 2026-08-18: `/opt/gunbc-green/gunbc` -// carried no `.git/config` at all, while `/opt/gunbc/gunbc` carried a working one because it -// predates the sync whitelist and was cloned by hand. +// `gunbc.fleet_desired_observe` reads refs/fleet/desired with `git ls-remote origin`, a network +// call THROUGH A CONFIGURED REMOTE. The belt makes that observation every tick, so a deployed tree +// with no remote cannot answer it and the belt refuses -- spawn and reap withheld, every roadmap +// node rendering a fault. Measured on srv1 2026-08-18: `/opt/gunbc-green/gunbc` carried no +// `.git/config` at all, while `/opt/gunbc/gunbc` carried a working one because it predates the +// sync whitelist and was cloned by hand. // -// THE REMOTE IS ABSENT BY DESIGN, NOT BY ACCIDENT, and that is why it is written rather than -// synced. `live_deploy.emit` `tree_sync_git_whitelist_note` excludes `.git/config` from the tree -// sync because a blanket sync was once found carrying a live Actions extraheader token into the -// service tree. That exclusion is correct and untouched here. What the whitelist's reasoning -// enumerated was the belt's SPAWN path -- `git worktree add` resolves origin/main from -// refs/remotes/origin/*, which do sync and need no configured remote -- and the observation path -// was never checked against it. So the deploy produced a tree structurally incapable of an -// observation the belt makes every sixty seconds. +// THE REMOTE IS ABSENT BY DESIGN, NOT BY ACCIDENT, which is why it is written rather than synced. +// `live_deploy.emit` `tree_sync_git_whitelist_note` excludes `.git/config` from the tree sync +// because a blanket sync was once found carrying a live Actions extraheader token into the service +// tree. That exclusion is correct and untouched here. The whitelist's reasoning enumerated the +// belt's SPAWN path -- `git worktree add` resolves origin/main from refs/remotes/origin/*, which do +// sync and need no configured remote -- and the observation path was never checked against it. So +// the deploy produced a tree structurally incapable of an observation the belt makes every sixty +// seconds. // // Writing one derived key cannot reintroduce the leak the whitelist closed: the value is projected // from `gunbc_repository` through `github_https_clone_url`, a public HTTPS URL that is a function @@ -66,10 +66,10 @@ fn observe_deployed_tree_remote(at: GitRemoteUrlAt) -> GitRemoteUrlObservation { } // A ZERO EXIT FROM THE SETTER IS NOT CONVERGENCE. `git config` answering 0 says the command was -// accepted, not that the repository now holds the value -- and the whole reason this module exists -// is that a plausible-looking success masked a tree that could not answer. So `Converged` is -// reachable only through an INDEPENDENT read-back whose observed URL equals the desired one -// exactly. Every other path is a distinct, located refusal rather than a shrug. +// accepted, not that the repository now holds the value -- and this module exists because a +// plausible-looking success masked a tree that could not answer. So `Converged` is reachable only +// through an INDEPENDENT read-back whose observed URL equals the desired one exactly. Every other +// path is a distinct, located refusal rather than a shrug. type DeployedTreeRemoteOutcome = RemoteAlreadyConverged { member: GitRemoteUrlMember } | RemoteConverged { member: GitRemoteUrlMember, previous: GitRemoteUrlObservation } @@ -123,7 +123,7 @@ fn deployed_tree_remote_write( // NO WRITE HAPPENS AGAINST STATE THAT WAS NOT OBSERVED. An unusable repository and a refused read // return without touching anything: rendering either as "the remote is simply absent" would be the -// empty-observation narrow -- a read that could not answer, reported as the answer -- and it would +// empty-observation narrow -- a read that could not answer, reported as the answer -- and would // license a config write into a path the planner never established was a repository. fn ensure_deployed_tree_remote(repository_path: FilePath) -> DeployedTreeRemoteOutcome { let desired = deployed_tree_remote_desired(repository_path: repository_path) @@ -186,18 +186,18 @@ fn deployed_tree_remote_exit(outcome: DeployedTreeRemoteOutcome) -> ProcessExit } // THE UNIT'S ENTRY. The repository path is supplied by the caller rather than derived from an -// instance roster, because the executor is a unit belonging to ONE instance and its ExecStart -// already names the tree it converges -- deriving it here would mean this module re-answering a -// question the unit is the authority for, and getting it wrong on a host running two slots. +// instance roster, because the executor is a unit belonging to ONE instance whose ExecStart +// already names the tree it converges -- deriving it here would re-answer a question the unit is +// the authority for, and get it wrong on a host running two slots. // // CORRECTED 2026-08-19: this said the executor is "the tree-sync unit". It is not, and the -// difference is the whole reason a separate member exists. Tree-sync is started inside the -// GunbcSourceTree step, which is FIRST in deployment_owned_steps while ServeBinary is second, so -// on a fresh instance the gunbc binary this entry needs does not exist yet and the leg would red -// the first deploy of every new slot. The executor is the dedicated DeployedTreeRemoteUnit -// (live_deploy.spec deployment_tree_remote_unit_name), ordered third so the binary is a -// precondition the membership itself establishes. The argument above survives the correction -- -// the caller still knows its own tree -- but the sentence named the wrong caller. +// difference is why a separate member exists. Tree-sync is started inside the GunbcSourceTree +// step, FIRST in deployment_owned_steps while ServeBinary is second, so on a fresh instance the +// gunbc binary this entry needs does not exist yet and the leg would red the first deploy of every +// new slot. The executor is the dedicated DeployedTreeRemoteUnit (live_deploy.spec +// deployment_tree_remote_unit_name), ordered third so the binary is a precondition the membership +// itself establishes. The argument above survives -- the caller still knows its own tree -- but +// the sentence named the wrong caller. func ensure_deployed_tree_remote_wet(repository_path: String) -> ProcessExit { deployed_tree_remote_exit( outcome: ensure_deployed_tree_remote(repository_path: repository_path as FilePath), diff --git a/dag/gunbc/design/archetype.dag b/dag/gunbc/design/archetype.dag index 505236c090a..f2f701b4538 100644 --- a/dag/gunbc/design/archetype.dag +++ b/dag/gunbc/design/archetype.dag @@ -8,7 +8,20 @@ import gunbc.design.theme { } import gunbc.design.interaction { InteractionVerb, Approach, Dispatch, Await, Accept, verb_key } -data design_archetype_note: String = "L4 surface archetypes, minimal Phase-B landing (design-register-library.md sect 2; manager-signed 2026-07-21). The second real site — the roadmap dashboard — fired the library's declared trigger, so the axes land as closed enums and Dashboard lands as the FIRST PRESET ROW: a named bundle over independent axes, never a new primitive (the Hermetic|Wet lesson). Deliberately NOT here (each with its trigger): the emission-grammar generalization (GrammarRelationRow archetype machinery — purity trap off one consumer; the roadmap keeps its own page emitter, law-checked); DataVizArea demand (its introduction stays a conscious event, tokens_note pin); the remaining presets (MarketingSite et al land when their surface binds — gunb.ai's binding rides the Phase-A lift). role_demand is the roles the surface CONSUMES by name (names-not-values interface law); the roadmap witness cross-checks the declared demand against the serialized stylesheet in BOTH directions — a demanded-but-unused role and a used-but-undeclared role each red. Liveness is honest to the SERVED surface: the belt-B dashboard reads the tree in-process (Live); the committed ROADMAP.md is a second realization of the same content at StaticEmit, named here, modeled when a consumer needs it." +// L4 surface archetypes, minimal Phase-B landing (design-register-library.md sect 2; manager-signed +// 2026-07-21). The second real site — the roadmap dashboard — fired the library's declared trigger, +// so the axes land as closed enums and Dashboard lands as the FIRST PRESET ROW: a named bundle over +// independent axes, never a new primitive (the Hermetic|Wet lesson). Deliberately NOT here (each +// with its trigger): the emission-grammar generalization (GrammarRelationRow archetype machinery — +// purity trap off one consumer; the roadmap keeps its own page emitter, law-checked); DataVizArea +// demand (its introduction stays a conscious event, tokens_note pin); the remaining presets +// (MarketingSite et al land when their surface binds — gunb.ai's binding rides the Phase-A lift). +// role_demand is the roles the surface CONSUMES by name (names-not-values interface law); the +// roadmap witness cross-checks the declared demand against the serialized stylesheet in BOTH +// directions — a demanded-but-unused role and a used-but-undeclared role each red. Liveness is +// honest to the SERVED surface: the belt-B dashboard reads the tree in-process (Live); the +// committed ROADMAP.md is a second realization of the same content at StaticEmit, named here, +// modeled when a consumer needs it. type ReadingPosture = Contemplative @@ -34,7 +47,15 @@ type SurfaceArchetype { role_demand: List } -data dashboard_verb_admission_note: String = "Await and Accept join the dashboard's admitted verbs, and the archetype is exactly where that belongs rather than being a formality to satisfy: the archetype's whole claim is that a surface with Live content and an Operational posture has a DIFFERENT interaction vocabulary from a reference page, and machine-caused state is the clearest case of it. A static reference surface answers fingers only; a live operational surface answers a machine that reports back, so admitting these two verbs here is what makes the roadmap's latched-and-released control admissible while keeping the same verbs inadmissible on a surface that declares itself static — the cross-check runs both directions, so a verb used but not declared reds exactly as a verb declared but unused does." +// Await and Accept join the dashboard's admitted verbs, and the archetype is exactly where that +// belongs rather than being a formality to satisfy: the archetype's whole claim is that a surface +// with Live content and an Operational posture has a DIFFERENT interaction vocabulary from a +// reference page, and machine-caused state is the clearest case of it. A static reference surface +// answers fingers only; a live operational surface answers a machine that reports back, so +// admitting these two verbs here is what makes the roadmap's latched-and-released control +// admissible while keeping the same verbs inadmissible on a surface that declares itself static — +// the cross-check runs both directions, so a verb used but not declared reds exactly as a verb +// declared but unused does. data dashboard_archetype: SurfaceArchetype = SurfaceArchetype { name: "dashboard", diff --git a/dag/gunbc/design/catalog.dag b/dag/gunbc/design/catalog.dag index ef96e733a32..6b059581ad8 100644 --- a/dag/gunbc/design/catalog.dag +++ b/dag/gunbc/design/catalog.dag @@ -14,7 +14,10 @@ import extdeps.color.solarized { solarized_swatches, solarized_version, } -data design_catalog_note: String = "The palette catalogue: one home for approved color sets. Cited swatch rows live in extdeps/color/ as CitedColorRow; catalogue wraps provenance and SwatchMemberKind only. Register binding (gunbc.site.tokens quiet_envelope_srgb8) is checked at the (set × register) witness layer — island-pastel mint/ivory violate the quiet envelope by design." +// The palette catalogue: one home for approved color sets. Cited swatch rows live in extdeps/color/ +// as CitedColorRow; catalogue wraps provenance and SwatchMemberKind only. Register binding +// (gunbc.site.tokens quiet_envelope_srgb8) is checked at the (set × register) witness layer — +// island-pastel mint/ivory violate the quiet envelope by design. data island_pastel_provenance_note: String = "Soft high-key pastel register, operator-supplied swatches 2026-07-21, inspired by Animal Crossing: New Horizons UI colorways (inspiration, not citation — no upstream spec exists to cite). loam is DERIVED from camel (text ink in-family); witness pins WCAG capability via loam only." diff --git a/dag/gunbc/design/component.dag b/dag/gunbc/design/component.dag index 261233018a5..673fad5de8d 100644 --- a/dag/gunbc/design/component.dag +++ b/dag/gunbc/design/component.dag @@ -4,7 +4,19 @@ import std.types { Int, String, Bool } import gunbc.design.theme { ThemeRole, role_key, role_var } import gunbc.design.interaction { InteractionVerb, verb_key, ElementId, element_id_key } -data design_component_note: String = "The component concept — a sibling of the L4 surface archetype (archetype = whole surface; component = one widget), justified by its second-consumer arrival (the dispatch button, design-register-library.md's second-consumer discipline). A component is FOUR axes held as one row: element structure (the ElementId it renders as, the interaction register's closed enum) x role/material bindings (base_roles + a per-state chip role) x interaction STATES (idle | requested | terminal-per-wire-arm) x wire-contract binding (each TerminalState carries the wire label it renders). The wire binding is the load-bearing move the dispatch-button incident (§5, 2026-07-23) demands: the states are NOT a free enum, they are pinned to a served contract, so component_wire_total cross-checks the terminal set against the wire vocabulary authority BOTH directions — a wire label with no state row (labels_missing_state) and a state that fabricates a label the wire never sends (states_missing_label) each red. This module is generic (no roadmap deps); the instance that binds the belt vocabulary lives one layer up (gunbc.roadmap_component), the same split as Component-type-here / dashboard_archetype-instance-in-archetype." +// The component concept — a sibling of the L4 surface archetype (archetype = whole surface; +// component = one widget), justified by its second-consumer arrival (the dispatch button, +// design-register-library.md's second-consumer discipline). A component is FOUR axes held as one +// row: element structure (the ElementId it renders as, the interaction register's closed enum) x +// role/material bindings (base_roles + a per-state chip role) x interaction STATES (idle | +// requested | terminal-per-wire-arm) x wire-contract binding (each TerminalState carries the wire +// label it renders). The wire binding is the load-bearing move the dispatch-button incident (§5, +// 2026-07-23) demands: the states are NOT a free enum, they are pinned to a served contract, so +// component_wire_total cross-checks the terminal set against the wire vocabulary authority BOTH +// directions — a wire label with no state row (labels_missing_state) and a state that fabricates a +// label the wire never sends (states_missing_label) each red. This module is generic (no roadmap +// deps); the instance that binds the belt vocabulary lives one layer up (gunbc.roadmap_component), +// the same split as Component-type-here / dashboard_archetype-instance-in-archetype. type ComponentStateKind = IdleState diff --git a/dag/gunbc/design/instrument_audition.dag b/dag/gunbc/design/instrument_audition.dag index 838c3272257..1806f06ee16 100644 --- a/dag/gunbc/design/instrument_audition.dag +++ b/dag/gunbc/design/instrument_audition.dag @@ -25,7 +25,17 @@ import gunbc.roadmap_program_view { program_front_key, } -data audition_note: String = "THE AUDITION IS A COMPARISON, NOT A DEMO. Its purpose is to let one focal control be seen at three energies on the SAME real subject, so the choice between restrained, expressive and hero is made by looking rather than by argument — which is the only way the selective-spectacle amendment can be calibrated without re-litigating it in prose every time. Two properties make it an audition rather than a mock-up: the subject is a real v1-deletion finish line carrying V0's own name, claim and roadmap node, so what the control says is true; and every intensity is admitted through the register's OWN gates (emission_for, attraction_envelope, displacement_admission) rather than through audition-local bounds, so a variant that looks good but breaks the register cannot be rendered at all. The hero variant deliberately sits at the envelope's admissible maximum: an audition whose loudest option is comfortably inside the bound tells you nothing about where the bound is." +// THE AUDITION IS A COMPARISON, NOT A DEMO. Its purpose is to let one focal control be seen at +// three energies on the SAME real subject, so the choice between restrained, expressive and hero is +// made by looking rather than by argument — which is the only way the selective-spectacle amendment +// can be calibrated without re-litigating it in prose every time. Two properties make it an +// audition rather than a mock-up: the subject is a real v1-deletion finish line carrying V0's own +// name, claim and roadmap node, so what the control says is true; and every intensity is admitted +// through the register's OWN gates (emission_for, attraction_envelope, displacement_admission) +// rather than through audition-local bounds, so a variant that looks good but breaks the register +// cannot be rendered at all. The hero variant deliberately sits at the envelope's admissible +// maximum: an audition whose loudest option is comfortably inside the bound tells you nothing about +// where the bound is. type AuditionIntensity = AuditionRestrained @@ -40,7 +50,10 @@ fn audition_intensity_key(i: AuditionIntensity) -> String { } } -data restrained_has_no_emission_note: String = "The restrained variant carries no emission at all, and that is what makes the comparison honest: it is the register as it stood BEFORE the amendment, so the audition's baseline is the real previous state rather than a slightly-dimmer version of the new one. If restrained wins, the amendment costs nothing to withdraw." +// The restrained variant carries no emission at all, and that is what makes the comparison honest: +// it is the register as it stood BEFORE the amendment, so the audition's baseline is the real +// previous state rather than a slightly-dimmer version of the new one. If restrained wins, the +// amendment costs nothing to withdraw. type AuditionEmission = AuditionNoEmission @@ -94,7 +107,11 @@ fn audition_instrument(i: AuditionIntensity) -> InstrumentPreference { } } -data discriminator_note: String = "The non-emission discriminator emission_for demands is not a placeholder string here: it names the channel that actually carries selection in the emitted control — the selected state class plus the subject's own name text — so a no-emission or reduced-motion rendering keeps both. Deriving it from the subject key rather than writing a literal is what stops the three intensities from silently sharing one hardcoded discriminator that belongs to none of them." +// The non-emission discriminator emission_for demands is not a placeholder string here: it names +// the channel that actually carries selection in the emitted control — the selected state class +// plus the subject's own name text — so a no-emission or reduced-motion rendering keeps both. +// Deriving it from the subject key rather than writing a literal is what stops the three +// intensities from silently sharing one hardcoded discriminator that belongs to none of them. fn focal_discriminator(subject_key: String) -> String { concat("selected-state-class+name-text:", subject_key) @@ -104,7 +121,12 @@ data hero_travel_is_derived_note: String = "Hero used to request the register's COMPOSING THE ENVELOPE CHANGED THE AUDITION, WHICH IS THE POINT. Expressive used to borrow the register's canonical focal_attraction — activation 150, travel 12 — and under one envelope that no longer fits: 12 of the register's 14 declared pixels are already spent by the canonical field, so an enclosure carrying any character at all cannot also grant it. Rather than shrink the canonical field or inflate the envelope, the three variants now differ by exactly one channel each: restrained has neither emission nor attraction, expressive adds emission, hero adds the attraction the enclosure left affordable. That ordering is more useful for an audition than three variants differing on everything at once, and it surfaces the real constraint instead of hiding it — the register's own focal field is expensive enough that instrument character and canonical attraction are alternatives, not companions. If that trade is wrong, the number to revisit is attraction_travel_max_px, in the register, deliberately." -data audition_admission_note: String = "One admission per intensity, and every arm of it is the register's existing gate rather than a local re-check. A restrained variant is admitted with no emission grant to inspect; the other two must pass emission_for AT THE FOCAL ROLE, which is the point where an audition that tried to glow a Supporting subject would refuse. Instrument displacement is admitted separately because it is a property of the enclosure and not of the control, and both must hold for the variant to be renderable." +// One admission per intensity, and every arm of it is the register's existing gate rather than a +// local re-check. A restrained variant is admitted with no emission grant to inspect; the other two +// must pass emission_for AT THE FOCAL ROLE, which is the point where an audition that tried to glow +// a Supporting subject would refuse. Instrument displacement is admitted separately because it is a +// property of the enclosure and not of the control, and both must hold for the variant to be +// renderable. type AuditionAdmission = AuditionAdmitted { @@ -131,7 +153,12 @@ fn attraction_admissible(a: AuditionAttraction) -> Bool { } } -data no_emission_skips_the_gate_note: String = "A restrained variant does not request emission, so it does not pass a zero-alpha spec through emission_for to obtain a grant it does not want. That shortcut was written first and is worth naming: fabricating an admissible-looking spec purely to satisfy a gate produces a REAL grant for emission nobody asked for, and the receipt then reports a granted emission on the one variant whose entire point is having none. NoEmissionRequested is a distinct outcome from a granted one, so the gate is skipped rather than gamed." +// A restrained variant does not request emission, so it does not pass a zero-alpha spec through +// emission_for to obtain a grant it does not want. That shortcut was written first and is worth +// naming: fabricating an admissible-looking spec purely to satisfy a gate produces a REAL grant for +// emission nobody asked for, and the receipt then reports a granted emission on the one variant +// whose entire point is having none. NoEmissionRequested is a distinct outcome from a granted one, +// so the gate is skipped rather than gamed. type AuditionEmissionOutcome = NoEmissionRequested @@ -202,9 +229,34 @@ fn admit_audition(i: AuditionIntensity, role: SalienceRole, subject_key: String) data audition_intensities: List = [AuditionRestrained, AuditionExpressive, AuditionHero] -data reading_note: String = "THE CAMERA READS THE WORLD V0 ALREADY DERIVED, and the previous revision of this module did not — which is the defect worth recording rather than quietly fixing. It answered Status and Remaining out of gunbc.v1_deletion_plan's authored rows, and answered Why with a typed ReadingUnavailable carrying the cause 'no rationale carrier exists for a v1 finish line'. That cause was FALSE when it was written. gunbc.roadmap_program_view was already on this branch's base and its own header says the camera layers are deliberately absent because THAT module is the world the cameras look at; ConstraintView exists precisely to answer why-is-this-taking-so-long without inventing rationale prose. So the refusal was not the honest fail-closed arm its note claimed — it was the empty-observation narrow, rendering could-not-find as the verdict does-not-exist, in the module whose own notes warn against exactly that. It reached review green because nothing about a refusal looks wrong: no witness can tell a correct refusal from a lazy one without going and looking for the carrier, which is what nobody did. The lesson is cheap to state and was expensive here: before declaring a carrier absent, search for it." - -data reading_grain_note: String = "DEPTH SELECTS THE SUBJECT GRAIN, MODE SELECTS THE QUESTION, and the two are genuinely independent because V0 derives a RemainingShape and a ConstraintView at more than one grain. Program reads the whole program's shape — from view.remaining, at EVERY focus, which is a correction rather than a restatement: this sentence was written while read_camera_at routed a focused program detent into the line reader, so the claim held only with nothing selected and the page mislabelled outcome-grain rows as program ones (review 48719, fixed by making the program grain unrepresentable in SubjectDepth rather than by adding an arm — see subject_depth_note). Outcome reads one FinishLineView; Front reads one contributing FrontView; Node reads the primary constraint's own subject. Evidence is deliberately NOT answered from these carriers: acceptance here comes from RoadmapAcceptanceReceipt rows, but the verification-receipt join that would let a camera show WHY a receipt was granted is test-only until the belt wiring lands, and gunbc.roadmap_program_view says so itself. That is a real absence with a named trigger, so Evidence refuses — and it is the one place in this module where a refusal is the correct answer rather than a search nobody performed." +// THE CAMERA READS THE WORLD V0 ALREADY DERIVED, and the previous revision of this module did not — +// which is the defect worth recording rather than quietly fixing. It answered Status and Remaining +// out of gunbc.v1_deletion_plan's authored rows, and answered Why with a typed ReadingUnavailable +// carrying the cause 'no rationale carrier exists for a v1 finish line'. That cause was FALSE when +// it was written. gunbc.roadmap_program_view was already on this branch's base and its own header +// says the camera layers are deliberately absent because THAT module is the world the cameras look +// at; ConstraintView exists precisely to answer why-is-this-taking-so-long without inventing +// rationale prose. So the refusal was not the honest fail-closed arm its note claimed — it was the +// empty-observation narrow, rendering could-not-find as the verdict does-not-exist, in the module +// whose own notes warn against exactly that. It reached review green because nothing about a +// refusal looks wrong: no witness can tell a correct refusal from a lazy one without going and +// looking for the carrier, which is what nobody did. The lesson is cheap to state and was expensive +// here: before declaring a carrier absent, search for it. + +// DEPTH SELECTS THE SUBJECT GRAIN, MODE SELECTS THE QUESTION, and the two are genuinely independent +// because V0 derives a RemainingShape and a ConstraintView at more than one grain. Program reads +// the whole program's shape — from view.remaining, at EVERY focus, which is a correction rather +// than a restatement: this sentence was written while read_camera_at routed a focused program +// detent into the line reader, so the claim held only with nothing selected and the page +// mislabelled outcome-grain rows as program ones (review 48719, fixed by making the program grain +// unrepresentable in SubjectDepth rather than by adding an arm — see subject_depth_note). Outcome +// reads one FinishLineView; Front reads one contributing FrontView; Node reads the primary +// constraint's own subject. Evidence is deliberately NOT answered from these carriers: acceptance +// here comes from RoadmapAcceptanceReceipt rows, but the verification-receipt join that would let a +// camera show WHY a receipt was granted is test-only until the belt wiring lands, and +// gunbc.roadmap_program_view says so itself. That is a real absence with a named trigger, so +// Evidence refuses — and it is the one place in this module where a refusal is the correct answer +// rather than a search nobody performed. type ProgramReading = ReadingNoSubject @@ -245,7 +297,19 @@ fn read_constraint(subject_key: String, c: ConstraintView?, accepted: Bool) -> P } } -data subject_depth_note: String = "THE GRAIN THE LINE READER IS ALLOWED TO ANSWER AT, as a type rather than as a promise (review 48719, 2026-08-05). The bug it closes: read_line_view used to match ProgramDepth, so it carried a DepthProgram arm — and that arm was byte-identical to its DepthOutcome arm. read_camera_at then routed BOTH depths into it whenever a subject was focused, so the program detent silently produced outcome-grain readings while the page kept labelling the row program / …, and reading_grain_note's claim that Program reads the whole program's shape was true only when nothing was selected. That is the note-vs-code split this module has now produced three times, so the fix is not another corrected arm: the whole-program grain has no representation here at all. SubjectDepth enumerates exactly the grains that ARE a property of one finish line, DepthProgram cannot be projected into it, and read_line_view therefore cannot be handed the program grain by any caller. A DepthFront arm went with it, dead for a different reason — read_camera_at answers fronts through first_front_view, so the line reader's front arm had been unreachable since that fold landed." +// THE GRAIN THE LINE READER IS ALLOWED TO ANSWER AT, as a type rather than as a promise (review +// 48719, 2026-08-05). The bug it closes: read_line_view used to match ProgramDepth, so it carried a +// DepthProgram arm — and that arm was byte-identical to its DepthOutcome arm. read_camera_at then +// routed BOTH depths into it whenever a subject was focused, so the program detent silently +// produced outcome-grain readings while the page kept labelling the row program / …, and +// reading_grain_note's claim that Program reads the whole program's shape was true only when +// nothing was selected. That is the note-vs-code split this module has now produced three times, so +// the fix is not another corrected arm: the whole-program grain has no representation here at all. +// SubjectDepth enumerates exactly the grains that ARE a property of one finish line, DepthProgram +// cannot be projected into it, and read_line_view therefore cannot be handed the program grain by +// any caller. A DepthFront arm went with it, dead for a different reason — read_camera_at answers +// fronts through first_front_view, so the line reader's front arm had been unreachable since that +// fold landed. type SubjectDepth = SubjectOutcome diff --git a/dag/gunbc/design/instrument_bob.dag b/dag/gunbc/design/instrument_bob.dag index 1de9ecb6788..178633d4daa 100644 --- a/dag/gunbc/design/instrument_bob.dag +++ b/dag/gunbc/design/instrument_bob.dag @@ -13,21 +13,40 @@ data instrument_bob_note: String = "THE FISHING-BOBBER RESPONSE: the inner displ It is a CAUSED response, which is what makes it admissible at all: this register has no ambient-motion constructor by design, and the plate is still until something knocks it. A knock is a PhysicalImpulse, and the impulse does not say where it came from — a hand on a chassis, a phone's accelerometer, a Windows sensor, or the deterministic synthetic tap all arrive here identically, so the physics is written once and never learns which platform is underneath it." -data envelope_model_note: String = "WHY AN ENVELOPE RATHER THAN AN INTEGRATED SPRING. A discrete spring integration in integer arithmetic has to be argued into stability — a step size and a stiffness can be chosen that make it grow rather than decay, and the argument that they were not is exactly the kind of claim that holds until someone retunes a constant. A geometric envelope makes the decay STRUCTURAL: the amplitude at any time is the initial amplitude scaled by a factor strictly below one raised to the number of elapsed half-lives, so it cannot grow, and no choice of constants makes it grow. The oscillation rides inside that envelope and can never exceed it. So boundedness and settling are properties of the shape rather than of the tuning, which is the difference between a construction and a check (DESIGN §5)." +// WHY AN ENVELOPE RATHER THAN AN INTEGRATED SPRING. A discrete spring integration in integer +// arithmetic has to be argued into stability — a step size and a stiffness can be chosen that make +// it grow rather than decay, and the argument that they were not is exactly the kind of claim that +// holds until someone retunes a constant. A geometric envelope makes the decay STRUCTURAL: the +// amplitude at any time is the initial amplitude scaled by a factor strictly below one raised to +// the number of elapsed half-lives, so it cannot grow, and no choice of constants makes it grow. +// The oscillation rides inside that envelope and can never exceed it. So boundedness and settling +// are properties of the shape rather than of the tuning, which is the difference between a +// construction and a check (DESIGN §5). data bob_max_travel_px: Int = 9 -data bob_bound_note: String = "Nine pixels, and the number is derived rather than picked: the register's attraction travel is the ceiling any instrument character must stay under, and the bob is the largest single channel, so it takes a majority of that budget while leaving headroom for the resting character that coexists with it. The relationship is asserted below rather than trusted — a later edit that raised this past the register's bound reds." +// Nine pixels, and the number is derived rather than picked: the register's attraction travel is +// the ceiling any instrument character must stay under, and the bob is the largest single channel, +// so it takes a majority of that budget while leaving headroom for the resting character that +// coexists with it. The relationship is asserted below rather than trusted — a later edit that +// raised this past the register's bound reds. data bob_idle_character_px: Int = 2 -data idle_character_note: String = "The resting character is a couple of pixels: enough that the plate reads as a physical object with a little life rather than a rectangle, small enough that nobody watching it would call it motion. It is the floor the bob returns TO, not a separate animation, and the caused response is several times larger precisely so that a knock is unmistakably a knock." +// The resting character is a couple of pixels: enough that the plate reads as a physical object +// with a little life rather than a rectangle, small enough that nobody watching it would call it +// motion. It is the floor the bob returns TO, not a separate animation, and the caused response is +// several times larger precisely so that a knock is unmistakably a knock. data bob_half_life_ms: Milliseconds = 180 data bob_visibility_floor_milli_px: Int = 250 -data settle_definition_note: String = "Settled means the envelope has fallen below a quarter of a pixel, which is under the smallest displacement any renderer can show. Defining rest as a visibility floor rather than as exact zero is what makes the settle time FINITE and derivable: a geometric decay never reaches zero, so a model that waited for zero would describe a bob that never stops, and the honest bound is the point past which continuing to move is not observable." +// Settled means the envelope has fallen below a quarter of a pixel, which is under the smallest +// displacement any renderer can show. Defining rest as a visibility floor rather than as exact zero +// is what makes the settle time FINITE and derivable: a geometric decay never reaches zero, so a +// model that waited for zero would describe a bob that never stops, and the honest bound is the +// point past which continuing to move is not observable. type BobPhase = BobAtRest @@ -58,7 +77,13 @@ data bob_at_rest: BobState = BobState { phase: BobAtRest, } -data impulse_scaling_note: String = "HOW HARD A KNOCK BECOMES HOW FAR THE PLATE MOVES, and the clamp is the load-bearing half. A smack is not a proportional input: a hand can deliver an order of magnitude more acceleration than a fingertip, and a response that scaled linearly would either be invisible for a tap or would throw the plate off the screen for a real knock. So magnitude maps into the envelope and is then CLAMPED at the bound, which means the strongest possible impulse and an absurd one produce the same maximum travel. The clamp is applied at construction, so an over-bound state has no way to exist rather than being caught later." +// HOW HARD A KNOCK BECOMES HOW FAR THE PLATE MOVES, and the clamp is the load-bearing half. A smack +// is not a proportional input: a hand can deliver an order of magnitude more acceleration than a +// fingertip, and a response that scaled linearly would either be invisible for a tap or would throw +// the plate off the screen for a real knock. So magnitude maps into the envelope and is then +// CLAMPED at the bound, which means the strongest possible impulse and an absurd one produce the +// same maximum travel. The clamp is applied at construction, so an over-bound state has no way to +// exist rather than being caught later. data impulse_reference_magnitude_milli: Int = 4000 @@ -76,7 +101,12 @@ fn amplitude_for_impulse(magnitude_milli_m_per_s2: Int) -> Int { clamp_amplitude(milli: (magnitude_milli_m_per_s2 * bob_max_travel_px * 1000) / impulse_reference_magnitude_milli) } -data motion_preference_note: String = "Reduced motion removes the movement and keeps the receipt. That distinction is the whole content of the arm: a viewer who has asked for less motion still gets a real, typed, visible statement that an impulse arrived, what its magnitude was, and which binding produced it — they simply do not get a plate that moves. Suppressing the receipt along with the displacement would turn a preference about animation into a loss of information, and an impulse that silently did nothing would be indistinguishable from a sensor that never fired." +// Reduced motion removes the movement and keeps the receipt. That distinction is the whole content +// of the arm: a viewer who has asked for less motion still gets a real, typed, visible statement +// that an impulse arrived, what its magnitude was, and which binding produced it — they simply do +// not get a plate that moves. Suppressing the receipt along with the displacement would turn a +// preference about animation into a loss of information, and an impulse that silently did nothing +// would be indistinguishable from a sensor that never fired. type MotionPreference = MotionFull @@ -100,7 +130,10 @@ fn apply_impulse(i: PhysicalImpulse, m: MotionPreference) -> BobState { } } -data decay_note: String = "The envelope halves every half-life. Integer arithmetic does this by repeated halving rather than by a power function, which is both exact and obviously monotone: each step divides by two, so the sequence cannot rise, and it reaches the visibility floor in a number of steps bounded by the initial amplitude's magnitude." +// The envelope halves every half-life. Integer arithmetic does this by repeated halving rather than +// by a power function, which is both exact and obviously monotone: each step divides by two, so the +// sequence cannot rise, and it reaches the visibility floor in a number of steps bounded by the +// initial amplitude's magnitude. fn halve_n_times(milli: Int, n: Int) -> Int { if n <= 0 { @@ -160,7 +193,10 @@ fn advance_descent_evidence(s: BobState, dt_ms: Milliseconds) -> DescentEvidence } } -data settle_bound_note: String = "The derived finite settle time: the number of half-lives it takes the largest admissible envelope to fall under the visibility floor, times the half-life. It is a DERIVED bound rather than a declared timeout, so retuning the bound, the floor or the half-life moves it automatically and no second number goes stale." +// The derived finite settle time: the number of half-lives it takes the largest admissible envelope +// to fall under the visibility floor, times the half-life. It is a DERIVED bound rather than a +// declared timeout, so retuning the bound, the floor or the half-life moves it automatically and no +// second number goes stale. fn half_lives_to_settle(initial_milli: Int) -> Int { if initial_milli < bob_visibility_floor_milli_px { @@ -178,7 +214,9 @@ fn max_settle_time_ms() -> Milliseconds { settle_time_ms(initial_milli: bob_max_travel_px * 1000) } -data boundedness_note: String = "The bound stated as a predicate over every reachable state rather than as a property of the constructor alone, so it also covers states produced by advancing. It is the claim the register's own displacement law needs: instrument character never travels as far as a real response." +// The bound stated as a predicate over every reachable state rather than as a property of the +// constructor alone, so it also covers states produced by advancing. It is the claim the register's +// own displacement law needs: instrument character never travels as far as a real response. fn bob_is_within_register_bound(s: BobState) -> Bool { bob_amplitude_milli(s: s) <= bob_max_travel_px * 1000 @@ -187,13 +225,18 @@ fn bob_is_within_register_bound(s: BobState) -> Bool { && bob_max_travel_px <= instrument_displacement_max_px } -data inertness_note: String = "The bob rides the existing impulse-glitch channel, so it inherits that channel's law without restating it: no instrument channel may carry a domain fact. The predicate is re-asserted here as a claim rather than re-implemented, which is the difference between citing an authority and forking one." +// The bob rides the existing impulse-glitch channel, so it inherits that channel's law without +// restating it: no instrument channel may carry a domain fact. The predicate is re-asserted here as +// a claim rather than re-implemented, which is the difference between citing an authority and +// forking one. fn bob_carries_no_domain_fact() -> Bool { !channel_may_carry_domain_fact(c: ChannelImpulseGlitch) } -data displacement_projection_note: String = "The renderer receives whole pixels; the model keeps milli-pixels so the decay does not quantise to zero prematurely. Rounding happens once, at the boundary, which is where the target's own units demand it." +// The renderer receives whole pixels; the model keeps milli-pixels so the decay does not quantise +// to zero prematurely. Rounding happens once, at the boundary, which is where the target's own +// units demand it. fn bob_displacement_px(s: BobState) -> Int { bob_amplitude_milli(s: s) / 1000 @@ -211,7 +254,10 @@ fn bob_quarter_period_ms() -> Milliseconds { bob_half_period_ms / 2 } -data quarter_leg_note: String = "Four legs make one full period, and each is a straight line between a zero and an extreme. Leg 0 falls from rest to full down, leg 1 returns to rest, leg 2 rises to full up, leg 3 returns. Naming the legs rather than writing one closed form keeps each endpoint checkable by inspection, and the endpoints are what the crossing claim depends on." +// Four legs make one full period, and each is a straight line between a zero and an extreme. Leg 0 +// falls from rest to full down, leg 1 returns to rest, leg 2 rises to full up, leg 3 returns. +// Naming the legs rather than writing one closed form keeps each endpoint checkable by inspection, +// and the endpoints are what the crossing claim depends on. fn oscillation_leg_milli(leg: Int, into_leg: Milliseconds) -> Int { let travelled = (oscillation_unit_milli * into_leg) / bob_quarter_period_ms() @@ -232,7 +278,9 @@ fn oscillation_milli(elapsed_ms: Milliseconds) -> Int { into_leg: elapsed_ms % bob_quarter_period_ms()) } -data first_excursion_is_downward_note: String = "The first leg is negative because a knock on a chassis pushes the plate DOWN before it rebounds. Starting upward would read as the screen jumping toward the hand that hit it. It begins at exactly zero, so the sequence starts at rest rather than starting already displaced." +// The first leg is negative because a knock on a chassis pushes the plate DOWN before it rebounds. +// Starting upward would read as the screen jumping toward the hand that hit it. It begins at +// exactly zero, so the sequence starts at rest rather than starting already displaced. fn signed_displacement_milli(s: BobState) -> Int { (bob_amplitude_milli(s: s) * oscillation_milli(elapsed_ms: s.elapsed_ms)) / oscillation_unit_milli @@ -283,7 +331,12 @@ fn bob_sequence_refusal_key(r: BobSequenceRefusal) -> String { } } -data budget_allowed_not_remaining_note: String = "THE FIELD SAYS WHAT IT MEANS. An earlier revision reported the REMAINING budget at the moment of exhaustion — which is always zero — under a name a reader takes for the budget the sequence was given, and the control pinned that zero, making the inaccurate receipt load-bearing. A number rendered under a name it does not mean is worse than an absent number, because it reads as information. The allowed budget is threaded down so the refusal can name the bound it actually failed against." +// THE FIELD SAYS WHAT IT MEANS. An earlier revision reported the REMAINING budget at the moment of +// exhaustion — which is always zero — under a name a reader takes for the budget the sequence was +// given, and the control pinned that zero, making the inaccurate receipt load-bearing. A number +// rendered under a name it does not mean is worse than an absent number, because it reads as +// information. The allowed budget is threaded down so the refusal can name the bound it actually +// failed against. type BobSequence = BobSequenceSettled { frames: List } @@ -344,11 +397,21 @@ fn bob_sequence(i: PhysicalImpulse, m: MotionPreference, interval_ms: Millisecon } } -data starts_at_rest_note: String = "The start state is emitted as its own frame, so the sequence literally begins at rest at time zero rather than beginning already displaced one interval in. It costs one frame and it is what makes 'starts at rest, travels downward' a property of the shipped wire rather than of the reader's charity." +// The start state is emitted as its own frame, so the sequence literally begins at rest at time +// zero rather than beginning already displaced one interval in. It costs one frame and it is what +// makes 'starts at rest, travels downward' a property of the shipped wire rather than of the +// reader's charity. -data no_frames_or_empty_helper_note: String = "THERE IS DELIBERATELY NO HELPER THAT TURNS A REFUSAL INTO AN EMPTY LIST. One existed, and it reintroduced the absorbing fallback at a second seam one layer above the one the typed sequence had just closed: a refused sequence became [], the wire became the empty string, and a consumer asserting that the page contains that wire passed vacuously because every string contains the empty string. Every consumer matches the coproduct, which costs two lines and cannot be satisfied by a refusal." +// THERE IS DELIBERATELY NO HELPER THAT TURNS A REFUSAL INTO AN EMPTY LIST. One existed, and it +// reintroduced the absorbing fallback at a second seam one layer above the one the typed sequence +// had just closed: a refused sequence became [], the wire became the empty string, and a consumer +// asserting that the page contains that wire passed vacuously because every string contains the +// empty string. Every consumer matches the coproduct, which costs two lines and cannot be satisfied +// by a refusal. -data frame_bound_note: String = "Every frame in a sequence stays inside the envelope in BOTH directions, which is the bound restated over the signed value: a plate that travelled nine pixels down would violate the register just as surely as one that travelled nine pixels up." +// Every frame in a sequence stays inside the envelope in BOTH directions, which is the bound +// restated over the signed value: a plate that travelled nine pixels down would violate the +// register just as surely as one that travelled nine pixels up. fn frame_is_within_bound(f: BobFrame) -> Bool { f.displacement_px <= bob_max_travel_px && f.displacement_px >= (0 - bob_max_travel_px) @@ -358,7 +421,9 @@ fn frames_are_within_bound(fs: List) -> Bool { all(fs, f => frame_is_within_bound(f: f)) } -data terminates_once_note: String = "Exactly one frame in a sequence is settled, and counting is a stronger claim than checking the last one: it says the sequence reaches rest AND stops there, so a recursion that kept emitting frames past settling would fail even though its final frame looked correct." +// Exactly one frame in a sequence is settled, and counting is a stronger claim than checking the +// last one: it says the sequence reaches rest AND stops there, so a recursion that kept emitting +// frames past settling would fail even though its final frame looked correct. fn settled_frame_count(fs: List) -> Int { fold(fs, init: 0, f: (acc, x) => if bob_phase_key(p: x.phase) == "settled" { acc + 1 } else { acc }) @@ -373,7 +438,10 @@ fn frames_cross_rest(fs: List) -> Bool { && fold(fs, init: false, f: (acc, x) => acc || x.displacement_px > 0) } -data intermediate_positions_note: String = "A square wave contains values on both sides of rest and is still not a bob. This is the claim that separates them: the sequence must contain a displacement that is neither rest nor either extreme — a position the plate actually occupies on its way — which a two-valued waveform cannot satisfy no matter how long it runs." +// A square wave contains values on both sides of rest and is still not a bob. This is the claim +// that separates them: the sequence must contain a displacement that is neither rest nor either +// extreme — a position the plate actually occupies on its way — which a two-valued waveform cannot +// satisfy no matter how long it runs. fn frames_visit_an_intermediate_position(fs: List) -> Bool { fold(fs, init: false, f: (acc, x) => @@ -382,7 +450,10 @@ fn frames_visit_an_intermediate_position(fs: List) -> Bool { && x.displacement_px != (0 - bob_max_travel_px))) } -data envelope_falls_note: String = "The reach actually diminishes over the sequence: the largest displacement in the first third is strictly greater than the largest in the last third. It is stated over the SHIPPED frames rather than over the envelope function, because the defect this replaces was one where the envelope function was fine in isolation and the sequence it produced never fell." +// The reach actually diminishes over the sequence: the largest displacement in the first third is +// strictly greater than the largest in the last third. It is stated over the SHIPPED frames rather +// than over the envelope function, because the defect this replaces was one where the envelope +// function was fine in isolation and the sequence it produced never fell. fn frames_max_abs_between(fs: List, from_ms: Int, to_ms: Int) -> Int { fold(fs, init: 0, f: (acc, x) => { diff --git a/dag/gunbc/design/instrument_camera.dag b/dag/gunbc/design/instrument_camera.dag index 292686f3d4a..8839ee69f1d 100644 --- a/dag/gunbc/design/instrument_camera.dag +++ b/dag/gunbc/design/instrument_camera.dag @@ -4,7 +4,17 @@ import std.types { String, Bool, Int, List } import std.measure { Millisecond, millisecond, millisecond_count } import gunbc.v1_deletion_plan { V1FinishLine, v1_finish_line_key, v1_exit_finish_lines } -data state_ownership_note: String = "FOUR OWNERS, AND THE SPLIT IS THE WHOLE POINT (instrument wave, operator directive 2026-08-04). Before physical-device input can drive presentation, it has to be impossible for it to drive anything else. The authoritative program view is V0's derived v1-deletion facts and this module NEVER writes it — no fn here returns a finish line, an acceptance, or a roadmap edge. Local instrument state is what the person operating the instrument chose: which subject is selected, how deep the camera sits, which mode it reads. Instrument observation is what the world told us: whether a sensor exists, whether permission was granted, whether an impulse arrived. Renderer ephemera is plate position, velocity, glow interpolation, glitch phase — none of it modelled here at all, because it is per-frame and belongs to the realization. The law that makes the split real rather than a naming convention: a ProgramCamera carries only a depth, a mode, and a focus KEY, so it structurally cannot hold a program fact to corrupt." +// FOUR OWNERS, AND THE SPLIT IS THE WHOLE POINT (instrument wave, operator directive 2026-08-04). +// Before physical-device input can drive presentation, it has to be impossible for it to drive +// anything else. The authoritative program view is V0's derived v1-deletion facts and this module +// NEVER writes it — no fn here returns a finish line, an acceptance, or a roadmap edge. Local +// instrument state is what the person operating the instrument chose: which subject is selected, +// how deep the camera sits, which mode it reads. Instrument observation is what the world told us: +// whether a sensor exists, whether permission was granted, whether an impulse arrived. Renderer +// ephemera is plate position, velocity, glow interpolation, glitch phase — none of it modelled here +// at all, because it is per-frame and belongs to the realization. The law that makes the split real +// rather than a naming convention: a ProgramCamera carries only a depth, a mode, and a focus KEY, +// so it structurally cannot hold a program fact to corrupt. type StateOwner = AuthoritativeProgramView @@ -21,7 +31,10 @@ fn state_owner_key(o: StateOwner) -> String { } } -data owner_write_authority_note: String = "Which owner may WRITE a domain fact: exactly one, and it is not reachable from the instrument. This is the predicate the physical-input lane needs, because 'a tap changed the screen' and 'a tap changed the program' differ only by whether the writing owner was authoritative — and the tap's owner never is." +// Which owner may WRITE a domain fact: exactly one, and it is not reachable from the instrument. +// This is the predicate the physical-input lane needs, because 'a tap changed the screen' and 'a +// tap changed the program' differ only by whether the writing owner was authoritative — and the +// tap's owner never is. fn owner_may_write_domain_fact(o: StateOwner) -> Bool { match o { @@ -32,7 +45,14 @@ fn owner_may_write_domain_fact(o: StateOwner) -> Bool { } } -data depth_note: String = "The semantic camera's one axis, ordered outermost to innermost. The variants carry a Depth stem because a bare-name identifier is a corpus-wide name in this substrate, not a module-local one — an unprefixed Node variant would have collided head-on with the substrate's own Node type, which is the nicknaming violation before it is ever a resolution hazard. It is a DEPTH, not a zoom: each position is a discrete detent with its own subject grain, so there is no continuous scale and no arbitrary pan. History is deliberately absent — a mode that cannot be answered from a real historical projection would be a fabricated affordance, and the directive names it as out of scope until one exists." +// The semantic camera's one axis, ordered outermost to innermost. The variants carry a Depth stem +// because a bare-name identifier is a corpus-wide name in this substrate, not a module-local one — +// an unprefixed Node variant would have collided head-on with the substrate's own Node type, which +// is the nicknaming violation before it is ever a resolution hazard. It is a DEPTH, not a zoom: +// each position is a discrete detent with its own subject grain, so there is no continuous scale +// and no arbitrary pan. History is deliberately absent — a mode that cannot be answered from a real +// historical projection would be a fabricated affordance, and the directive names it as out of +// scope until one exists. type ProgramDepth = DepthProgram @@ -77,9 +97,15 @@ fn instrument_user_depth_keys() -> List { map(instrument_user_depths(), d => program_depth_key(d: d)) } -data depth_ordering_note: String = "program_depth_index exists so a detent crossing is DERIVED rather than authored: one wheel threshold or one arrow key moves the index by exactly one, and the dial's rendered position is the index. Authoring a per-pair transition table would be the same ordering said twice." +// program_depth_index exists so a detent crossing is DERIVED rather than authored: one wheel +// threshold or one arrow key moves the index by exactly one, and the dial's rendered position is +// the index. Authoring a per-pair transition table would be the same ordering said twice. -data depth_detent_step_note: String = "A depth change crosses AT MOST ONE detent, by construction: step_depth takes a direction, not a target, so no caller can jump from the outermost detent to the innermost in one gesture and no continuous zoom can exist. Clamping at the ends is deliberate and is not an absorbing fallback — the request is fully answered ('you are already at the outermost detent'), nothing is widened, and a caller that wants to know whether it moved compares the returned depth." +// A depth change crosses AT MOST ONE detent, by construction: step_depth takes a direction, not a +// target, so no caller can jump from the outermost detent to the innermost in one gesture and no +// continuous zoom can exist. Clamping at the ends is deliberate and is not an absorbing fallback — +// the request is fully answered ('you are already at the outermost detent'), nothing is widened, +// and a caller that wants to know whether it moved compares the returned depth. type DepthStep = Inward @@ -125,7 +151,11 @@ fn instrument_mode_keys() -> List { map(program_modes(), m => program_mode_key(m: m)) } -data focus_is_a_key_not_a_fact_note: String = "FocusSubject carries the finish line's KEY, not its spec. That is what keeps the camera structurally unable to hold a program fact: a key is an identity the authoritative view can resolve, while a spec would be a copy of V0's claim living in local state, free to drift from the thing it names. Selecting nothing is a real state — the instrument opens with no subject chosen — so it is a variant rather than an empty string." +// FocusSubject carries the finish line's KEY, not its spec. That is what keeps the camera +// structurally unable to hold a program fact: a key is an identity the authoritative view can +// resolve, while a spec would be a copy of V0's claim living in local state, free to drift from the +// thing it names. Selecting nothing is a real state — the instrument opens with no subject chosen — +// so it is a variant rather than an empty string. type FocusSubject = NoSubjectSelected @@ -152,7 +182,9 @@ type ProgramCamera { focus: FocusSubject } -data camera_opens_outermost_note: String = "The instrument opens at the outermost depth in Status mode with nothing selected — no subject is preselected, because a preselected subject is a claim about what matters that the program did not make." +// The instrument opens at the outermost depth in Status mode with nothing selected — no subject is +// preselected, because a preselected subject is a claim about what matters that the program did not +// make. data camera_initial: ProgramCamera = ProgramCamera { depth: DepthProgram, @@ -237,7 +269,8 @@ fn camera_set_mode(c: ProgramCamera, m: ProgramMode) -> ProgramCamera { ProgramCamera { depth: c.depth, mode: m, focus: c.focus } } -data selection_toggles_note: String = "Re-selecting the selected subject clears it, matching the production roadmap's row-selection rule so the two surfaces do not teach different gestures for the same act." +// Re-selecting the selected subject clears it, matching the production roadmap's row-selection rule +// so the two surfaces do not teach different gestures for the same act. fn camera_select(c: ProgramCamera, f: FocusSubject) -> ProgramCamera { if focus_subject_eq(a: c.focus, b: f) { @@ -247,7 +280,12 @@ fn camera_select(c: ProgramCamera, f: FocusSubject) -> ProgramCamera { } } -data camera_cannot_touch_the_program_note: String = "THE LAW THE PHYSICAL LANE DEPENDS ON, stated as a predicate over the camera's own shape rather than as a promise: every camera transition above returns a ProgramCamera built from a depth, a mode and a focus key, so no transition can carry a roadmap node, an acceptance, or a finish-line claim. A tap, a wheel, a key, or a pointer can therefore move the camera and nothing else. What this does NOT prove is that a RENDERER refrains from writing elsewhere; that is the renderer's own obligation and is witnessed at its own seam." +// THE LAW THE PHYSICAL LANE DEPENDS ON, stated as a predicate over the camera's own shape rather +// than as a promise: every camera transition above returns a ProgramCamera built from a depth, a +// mode and a focus key, so no transition can carry a roadmap node, an acceptance, or a finish-line +// claim. A tap, a wheel, a key, or a pointer can therefore move the camera and nothing else. What +// this does NOT prove is that a RENDERER refrains from writing elsewhere; that is the renderer's +// own obligation and is witnessed at its own seam. fn camera_preserves_subject_across_depth(c: ProgramCamera, step: DepthStep) -> Bool { focus_subject_eq(a: camera_step_depth(c: c, step: step).focus, b: c.focus) @@ -261,7 +299,11 @@ data camera_storage_key: String = "gunbc.instrument.camera" data camera_wire_sep: String = "|" -data camera_wire_note: String = "THE PERSISTENCE WIRE IS THREE KEYS, NOT A PROGRAM FACT. Depth, mode and focus travel as the same string keys program_depth_key, program_mode_key and focus_subject_key already emit, joined on a pipe that cannot appear in any of them, so the wire cannot be mistaken for a roadmap node or a finish-line claim. An unparseable or unknown segment resets to camera_initial rather than inventing a fourth state — the client refuses the bad wire, it does not widen it." +// THE PERSISTENCE WIRE IS THREE KEYS, NOT A PROGRAM FACT. Depth, mode and focus travel as the same +// string keys program_depth_key, program_mode_key and focus_subject_key already emit, joined on a +// pipe that cannot appear in any of them, so the wire cannot be mistaken for a roadmap node or a +// finish-line claim. An unparseable or unknown segment resets to camera_initial rather than +// inventing a fourth state — the client refuses the bad wire, it does not widen it. fn camera_wire(c: ProgramCamera) -> String { join([ diff --git a/dag/gunbc/design/instrument_physical.dag b/dag/gunbc/design/instrument_physical.dag index 83b173867f6..287239ea19d 100644 --- a/dag/gunbc/design/instrument_physical.dag +++ b/dag/gunbc/design/instrument_physical.dag @@ -11,7 +11,11 @@ import gunbc.design.reference_instrument { data physical_character_note: String = "THE ENCLOSING INSTRUMENT MAY HAVE PHYSICAL CHARACTER; THE CAUSAL WORLD MAY NOT. This is the register amendment the audition needs, and the line it draws is the whole content: still-until-touched governs the SUBJECTS — a roadmap node, a finish line, an operation never moves without a cause — while the instrument ENCLOSING them is a physical object whose glass, phosphor and chassis have their own idle behaviour. A CRT that sits perfectly still is not restraint, it is a picture of a CRT. So drift, jitter, vertical shift and impulse glitch are admitted HERE and nowhere else, under three conditions that make them incapable of lying: every effect displaces or perturbs the instrument's own presentation layer and can carry no domain fact; every effect is preference-controlled and its off position is total; and the composed displacement is bounded below the register's existing attraction travel, so instrument character can never move a subject as far as a real response does. The reference precedent travels as a typed edge into gunbc.design.reference_instrument rather than as prose here — see provenance_edge_note and channel_behaviour_axis below." -data preference_off_is_total_note: String = "EVERY CHANNEL'S OFF POSITION IS TOTAL, and that is a construction property rather than a promise: instrument_displacement_px returns zero for a disabled channel by matching the preference, so a realization reading the composed budget receives no displacement to apply. There is no global escape hatch and no 'reduced' middle mode that leaves a residue — a channel is off or it is at its declared amplitude." +// EVERY CHANNEL'S OFF POSITION IS TOTAL, and that is a construction property rather than a promise: +// instrument_displacement_px returns zero for a disabled channel by matching the preference, so a +// realization reading the composed budget receives no displacement to apply. There is no global +// escape hatch and no 'reduced' middle mode that leaves a residue — a channel is off or it is at +// its declared amplitude. type InstrumentChannel = ChannelOpticalJitter @@ -28,7 +32,11 @@ fn channel_key(c: InstrumentChannel) -> String { } } -data channel_carries_no_domain_fact_note: String = "The predicate the whole amendment rests on, stated over the channel set rather than per site: NO instrument channel may carry a domain fact. It is total and constantly false because that is the claim — if a fifth channel is ever added that DOES carry meaning, this match forces the author to write the arm and a reviewer sees a true in a function whose name says there should not be one. That is cheaper and louder than a lens over the same set." +// The predicate the whole amendment rests on, stated over the channel set rather than per site: NO +// instrument channel may carry a domain fact. It is total and constantly false because that is the +// claim — if a fifth channel is ever added that DOES carry meaning, this match forces the author to +// write the arm and a reviewer sees a true in a function whose name says there should not be one. +// That is cheaper and louder than a lens over the same set. fn channel_may_carry_domain_fact(c: InstrumentChannel) -> Bool { match c { @@ -56,7 +64,10 @@ fn glitch_duration_key(g: ImpulseGlitchDuration) -> String { } } -data latched_glitch_not_modelled_note: String = "The gap left by the correction above, stated so it is countable rather than forgotten: no arm of this type latches. Every value here decays on the settle timing, so an impulse cannot leave the display in a wrong state that a later impulse reseats. That is a missing INTERACTION, not a missing duration, which is why lengthening ImpulseLong would not close it." +// The gap left by the correction above, stated so it is countable rather than forgotten: no arm of +// this type latches. Every value here decays on the settle timing, so an impulse cannot leave the +// display in a wrong state that a later impulse reseats. That is a missing INTERACTION, not a +// missing duration, which is why lengthening ImpulseLong would not close it. fn glitch_is_armed(g: ImpulseGlitchDuration) -> Bool { match g { @@ -95,7 +106,9 @@ type InstrumentPreference { glitch_duration: ImpulseGlitchDuration } -data preference_still_note: String = "The all-off preference is a real, reachable configuration and not a degraded one: an instrument with every channel disabled is exactly the register's prior still surface, so the amendment strictly adds a lane rather than changing what the quiet setting produces." +// The all-off preference is a real, reachable configuration and not a degraded one: an instrument +// with every channel disabled is exactly the register's prior still surface, so the amendment +// strictly adds a lane rather than changing what the quiet setting produces. data preference_still: InstrumentPreference = InstrumentPreference { optical_jitter: ChannelDisabled, @@ -105,7 +118,11 @@ data preference_still: InstrumentPreference = InstrumentPreference { glitch_duration: ImpulseOff, } -data preference_default_note: String = "The shipped default is deliberately restrained rather than the reference product's full character: one px of optical jitter and two of buoyant drift are perceptible as life without being perceptible as motion, vertical shift is off because a rolling frame reads as a fault rather than as character, and the impulse glitch is armed at short because it only ever fires on a real physical impulse." +// The shipped default is deliberately restrained rather than the reference product's full +// character: one px of optical jitter and two of buoyant drift are perceptible as life without +// being perceptible as motion, vertical shift is off because a rolling frame reads as a fault +// rather than as character, and the impulse glitch is armed at short because it only ever fires on +// a real physical impulse. data preference_default: InstrumentPreference = InstrumentPreference { optical_jitter: ChannelAmplitudePx { px: 1 }, @@ -137,7 +154,11 @@ fn instrument_displacement_px(p: InstrumentPreference) -> Int { amplitude_px(a: p.impulse_glitch) } -data composed_displacement_note: String = "Every positional contribution the register can grant a single subject, carried as nonnegative magnitudes so the sum cannot cancel. focal_attraction_px is the plate travel a focal control may be granted; it is zero for a subject with no attraction. The record has no direction field on purpose: a direction would let two contributions be argued to oppose, which is how a composed budget gets talked out of being composed." +// Every positional contribution the register can grant a single subject, carried as nonnegative +// magnitudes so the sum cannot cancel. focal_attraction_px is the plate travel a focal control may +// be granted; it is zero for a subject with no attraction. The record has no direction field on +// purpose: a direction would let two contributions be argued to oppose, which is how a composed +// budget gets talked out of being composed. type ComposedVisualDisplacement { enclosure_character_px: Nat @@ -164,7 +185,11 @@ fn compose_displacement(p: InstrumentPreference, focal_attraction_px: Nat) -> Co } } -data enclosure_character_px_note: String = "Character is the IDLE contribution — jitter, shift, drift — and deliberately excludes the impulse transient, because the impulse only fires on a real physical cause and is therefore not the thing that could be mistaken for an unprompted response. The transient still counts against the composed total, since a transient that overruns the envelope is still a plate leaving its declared travel." +// Character is the IDLE contribution — jitter, shift, drift — and deliberately excludes the impulse +// transient, because the impulse only fires on a real physical cause and is therefore not the thing +// that could be mistaken for an unprompted response. The transient still counts against the +// composed total, since a transient that overruns the envelope is still a plate leaving its +// declared travel. fn admit_composed_displacement(d: ComposedVisualDisplacement) -> DisplacementAdmission { let total = composed_total_px(d: d) @@ -180,7 +205,23 @@ fn admit_composed_displacement(d: ComposedVisualDisplacement) -> DisplacementAdm } } -data focal_attraction_headroom_note: String = "The travel a focal control may still be granted once the enclosure has spent its share — DERIVED, so an audition cannot silently keep its old number when the enclosure's default changes, and cannot author an attraction that overruns the composed envelope FOR ANY PREFERENCE THE ENVELOPE ADMITS. That qualifier is the honest limit of the claim and it is stated because the first version of this note omitted it (self-audit, 2026-08-05). The subtraction is total on Int, not on Nat: a preference whose channels sum past instrument_displacement_max_px yields a NEGATIVE headroom, and the composed record's focal_attraction_px is a Nat whose refinement is not re-checked on a computed value, so the sign-cancellation class this module closed at the field is still reachable through the derived path. It is not reached today — preference_default spends 6 of 14 — and the configuration that would reach it is already refused by displacement_admission's EnclosureCharacterReachesResponseTravel arm, so the residual risk is that a caller reads headroom WITHOUT consulting that admission first. Rung: mechanically preventable, not structurally guaranteed; the ceiling is structural impossibility and the gap is one function's return type. NEXT-RUNG TRIGGER (DESIGN 4b, no untracked stall): headroom returns a typed HeadroomAvailable { px: Nat } | HeadroomUnavailable coproduct rather than a bare Int, so a caller cannot obtain a number at all when the enclosure has overspent — at which point this paragraph collapses back to the one-sentence construction claim it originally made." +// The travel a focal control may still be granted once the enclosure has spent its share — DERIVED, +// so an audition cannot silently keep its old number when the enclosure's default changes, and +// cannot author an attraction that overruns the composed envelope FOR ANY PREFERENCE THE ENVELOPE +// ADMITS. That qualifier is the honest limit of the claim and it is stated because the first +// version of this note omitted it (self-audit, 2026-08-05). The subtraction is total on Int, not on +// Nat: a preference whose channels sum past instrument_displacement_max_px yields a NEGATIVE +// headroom, and the composed record's focal_attraction_px is a Nat whose refinement is not +// re-checked on a computed value, so the sign-cancellation class this module closed at the field is +// still reachable through the derived path. It is not reached today — preference_default spends 6 +// of 14 — and the configuration that would reach it is already refused by displacement_admission's +// EnclosureCharacterReachesResponseTravel arm, so the residual risk is that a caller reads headroom +// WITHOUT consulting that admission first. Rung: mechanically preventable, not structurally +// guaranteed; the ceiling is structural impossibility and the gap is one function's return type. +// NEXT-RUNG TRIGGER (DESIGN 4b, no untracked stall): headroom returns a typed HeadroomAvailable { +// px: Nat } | HeadroomUnavailable coproduct rather than a bare Int, so a caller cannot obtain a +// number at all when the enclosure has overspent — at which point this paragraph collapses back to +// the one-sentence construction claim it originally made. fn focal_attraction_headroom_px(p: InstrumentPreference) -> Int { instrument_displacement_max_px - instrument_displacement_px(p: p) @@ -190,7 +231,15 @@ fn displacement_admission(p: InstrumentPreference) -> DisplacementAdmission { admit_composed_displacement(d: compose_displacement(p: p, focal_attraction_px: 0)) } -data provenance_edge_note: String = "THE PROVENANCE IS A TYPED EDGE NOW, NOT A PARAGRAPH. The first cut described the Pip-Boy precedent in prose and carried a DISSOLVE-ON saying the citation edge would arrive when the reference join reached main. It has (gunbc.design.reference_instrument, merged), so the prose is replaced by the edge it promised: every channel names the agnostic behaviour axis it inhabits, and that axis carries its own provenance — cited on a real declaration inside a product module, or originated here. Nothing in this module restates what the upstream shipped; it points at the join that does. ChannelBuoyantDrift resolving to an OriginatedHere axis is the case that makes the edge worth having: the drift is OURS, no reference product realizes it, and the edge says so rather than letting a reader assume the whole set was borrowed." +// THE PROVENANCE IS A TYPED EDGE NOW, NOT A PARAGRAPH. The first cut described the Pip-Boy +// precedent in prose and carried a DISSOLVE-ON saying the citation edge would arrive when the +// reference join reached main. It has (gunbc.design.reference_instrument, merged), so the prose is +// replaced by the edge it promised: every channel names the agnostic behaviour axis it inhabits, +// and that axis carries its own provenance — cited on a real declaration inside a product module, +// or originated here. Nothing in this module restates what the upstream shipped; it points at the +// join that does. ChannelBuoyantDrift resolving to an OriginatedHere axis is the case that makes +// the edge worth having: the drift is OURS, no reference product realizes it, and the edge says so +// rather than letting a reader assume the whole set was borrowed. fn channel_behaviour_axis(c: InstrumentChannel) -> InstrumentBehaviourAxis { match c { @@ -209,7 +258,14 @@ fn channel_is_cited_precedent(c: InstrumentChannel) -> Bool { !axis_is_originated(axis: channel_behaviour_axis(c: c)) } -data impulse_source_note: String = "NOT ANCHORED TO A BROWSER (operator standing directive: focus on the vision, then work backwards to what is available). An accelerometer is a HOST capability, so the variants name the capability and not the runtime that exposes it — HostAccelerometer and HostDeviceMotion are equally inhabited by a browser sensor API, a desktop application's platform binding, or a physical enclosure's own hardware, and choosing a desktop realization later adds a binding row rather than editing this coproduct. SyntheticImpulse is not a fallback for a missing sensor: it is the audition path, an authored impulse that lets the glitch be designed, reviewed and witnessed on a machine that has no accelerometer at all, which is every CI runner." +// NOT ANCHORED TO A BROWSER (operator standing directive: focus on the vision, then work backwards +// to what is available). An accelerometer is a HOST capability, so the variants name the capability +// and not the runtime that exposes it — HostAccelerometer and HostDeviceMotion are equally +// inhabited by a browser sensor API, a desktop application's platform binding, or a physical +// enclosure's own hardware, and choosing a desktop realization later adds a binding row rather than +// editing this coproduct. SyntheticImpulse is not a fallback for a missing sensor: it is the +// audition path, an authored impulse that lets the glitch be designed, reviewed and witnessed on a +// machine that has no accelerometer at all, which is every CI runner. type PhysicalImpulseSource = SyntheticImpulse @@ -232,7 +288,14 @@ WHY BOTH EXIST RIGHT NOW: the sandbox page still renders the old impulse matrix DISSOLVE-ON, all three together and countable: when the sandbox page's physical section reads TargetCapabilityVerdict instead of SensorAvailability, then PhysicalImpulseSource, SensorAvailability, ImpulseAdmission, admit_impulse, source_availability, sensor_may_deliver_impulses and sensor_availability_key delete, and project_sensor_availability deletes with them. Nothing here is intended to survive that page's migration." -data sensor_availability_note: String = "FIVE STATES, BECAUSE THE THREE THAT GET COLLAPSED ARE THE ONES THAT MATTER. A sensor that is absent, one that exists but has not been asked for, one whose permission was refused, and one we could not interrogate are four different worlds with four different remedies, and every one of them is routinely flattened into a single falsy 'no sensor' — the empty-observation narrow, rendering could-not-observe as the verdict nothing-is-there. SensorAvailabilityUnknown is therefore NOT a synonym for absent: sensor_may_deliver_impulses admits only SensorPresent, so an uninterrogated sensor delivers nothing while remaining visibly distinct from one that is genuinely missing, and a surface can honestly say which." +// FIVE STATES, BECAUSE THE THREE THAT GET COLLAPSED ARE THE ONES THAT MATTER. A sensor that is +// absent, one that exists but has not been asked for, one whose permission was refused, and one we +// could not interrogate are four different worlds with four different remedies, and every one of +// them is routinely flattened into a single falsy 'no sensor' — the empty-observation narrow, +// rendering could-not-observe as the verdict nothing-is-there. SensorAvailabilityUnknown is +// therefore NOT a synonym for absent: sensor_may_deliver_impulses admits only SensorPresent, so an +// uninterrogated sensor delivers nothing while remaining visibly distinct from one that is +// genuinely missing, and a surface can honestly say which. type SensorAvailability = SensorPresent @@ -261,7 +324,11 @@ fn sensor_may_deliver_impulses(a: SensorAvailability) -> Bool { } } -data impulse_admission_note: String = "An impulse is admitted only when its SOURCE is available and the glitch channel is armed, and the refusal says which of the two failed rather than returning a bare false. The synthetic source is always available because it is authored input, which is what keeps the audition runnable on hardware that has no sensor — and it is a source in its own right rather than a simulation of one, so nothing here pretends a reading occurred." +// An impulse is admitted only when its SOURCE is available and the glitch channel is armed, and the +// refusal says which of the two failed rather than returning a bare false. The synthetic source is +// always available because it is authored input, which is what keeps the audition runnable on +// hardware that has no sensor — and it is a source in its own right rather than a simulation of +// one, so nothing here pretends a reading occurred. type ImpulseAdmission = ImpulseAdmitted { source: String, settle: TimingToken } diff --git a/dag/gunbc/design/instrument_projection.dag b/dag/gunbc/design/instrument_projection.dag index 34459135930..644f2303c11 100644 --- a/dag/gunbc/design/instrument_projection.dag +++ b/dag/gunbc/design/instrument_projection.dag @@ -14,14 +14,24 @@ import gunbc.v1_deletion_plan { V1FinishLineSpec, v1_exit_finish_lines, v1_finis data projection_note: String = "THE PROJECTION THAT DECIDES A ROLE FROM REAL FACTS, firing gunbc.design.salience salience_note's declared dissolve-on. Until this landed, every SalienceRole in the corpus arrived as a hand-supplied input, so the budget table, the focal-admission fold, and the emission envelope were all lawful over roles NOBODY DERIVED — the section 6 coverage-by-illusion tier, one layer up from an inert lens. What decides a role here is the camera's selection and the region a subject sits in, and nothing else: there is no per-subject importance field, no authored priority, and no way for a caller to hand a role in. The four rules are exactly the operator's: the selected subject is Focal, the subjects sharing its region are Supporting, everything outside that region is Ground, and an active refusal needing action is Critical." -data subject_placement_note: String = "A subject is a KEY and the REGION it occupies — the two facts the projection needs and no more. It deliberately does not carry the subject's claim, status, or acceptance: the projection decides visual energy, and a projection that could read a claim could make energy mean truth, which is precisely the fabricated affordance the register forbids." +// A subject is a KEY and the REGION it occupies — the two facts the projection needs and no more. +// It deliberately does not carry the subject's claim, status, or acceptance: the projection decides +// visual energy, and a projection that could read a claim could make energy mean truth, which is +// precisely the fabricated affordance the register forbids. type SubjectPlacement { key: String region: String } -data refusal_is_aggregated_note: String = "AN ACTIVE REFUSAL IS ONE CRITICAL SUBJECT, NOT ONE PER REFUSAL (operator directive). Critical is the interrupt channel — strong chroma, admissible emission, sharp motion — so granting it per refusal would let a five-refusal state light five objects and destroy the calm field the register's whole thesis rests on. The count travels as data on the single aggregate so the surface can say how many without spending five energy budgets. NoActiveRefusal is a variant rather than a zero count because 'nothing is refusing' and 'something is refusing, zero times' are different states; the count is a std.integer PositiveInt so the second one has no literal that constructs it, rather than a guard that catches it after the fact." +// AN ACTIVE REFUSAL IS ONE CRITICAL SUBJECT, NOT ONE PER REFUSAL (operator directive). Critical is +// the interrupt channel — strong chroma, admissible emission, sharp motion — so granting it per +// refusal would let a five-refusal state light five objects and destroy the calm field the +// register's whole thesis rests on. The count travels as data on the single aggregate so the +// surface can say how many without spending five energy budgets. NoActiveRefusal is a variant +// rather than a zero count because 'nothing is refusing' and 'something is refusing, zero times' +// are different states; the count is a std.integer PositiveInt so the second one has no literal +// that constructs it, rather than a guard that catches it after the fact. type RefusalAggregate = NoActiveRefusal @@ -40,7 +50,9 @@ fn subject_region(placements: List, key: String) -> String { fold(placements, init: "", f: (acc, p) => if p.key == key { p.region } else { acc }) } -data focal_region_note: String = "With nothing selected there is no focal region, so no subject can be Supporting-by-sibling and the whole field reads Ground. That is the correct still state rather than a degenerate one: the instrument opens with the energy unspent, and the first selection is what spends it." +// With nothing selected there is no focal region, so no subject can be Supporting-by-sibling and +// the whole field reads Ground. That is the correct still state rather than a degenerate one: the +// instrument opens with the energy unspent, and the first selection is what spends it. fn focal_region(camera: ProgramCamera, placements: List) -> String { match camera.focus { @@ -82,7 +94,11 @@ fn project_salience( |> concat(refusal_assignments(refusal: refusal)) } -data admission_is_derived_not_asserted_note: String = "The at-most-one-Focal law is not restated here. project_salience produces assignments and region_focal_admission judges them, so the projection is checked by the SAME fold every hand-authored region is checked by — one authority, asked about derived rows instead of authored ones. A witness that runs the projection through it is therefore a real test of the projection, not a second copy of the rule." +// The at-most-one-Focal law is not restated here. project_salience produces assignments and +// region_focal_admission judges them, so the projection is checked by the SAME fold every +// hand-authored region is checked by — one authority, asked about derived rows instead of authored +// ones. A witness that runs the projection through it is therefore a real test of the projection, +// not a second copy of the rule. fn project_admission( camera: ProgramCamera, @@ -97,7 +113,11 @@ fn project_admission( data finish_line_region: String = "v1-finish-lines" -data finish_line_placements_note: String = "The instrument's subjects are V0's four v1-deletion finish lines, read from gunbc.v1_deletion_plan rather than restated — so a fifth finish line appears in the instrument by being declared once, and a renamed one cannot drift out of alignment with the roadmap node it binds to. They share one region because they are one field of peers: selecting any of them makes the other three its siblings." +// The instrument's subjects are V0's four v1-deletion finish lines, read from +// gunbc.v1_deletion_plan rather than restated — so a fifth finish line appears in the instrument by +// being declared once, and a renamed one cannot drift out of alignment with the roadmap node it +// binds to. They share one region because they are one field of peers: selecting any of them makes +// the other three its siblings. fn finish_line_placements() -> List { v1_exit_finish_lines() |> map(s => SubjectPlacement { diff --git a/dag/gunbc/design/interaction.dag b/dag/gunbc/design/interaction.dag index 7f2af0499a3..29ff63e322e 100644 --- a/dag/gunbc/design/interaction.dag +++ b/dag/gunbc/design/interaction.dag @@ -144,7 +144,12 @@ type DetentTravel = NoTravel | Travel { token: ScaleToken } -data detent_carries_position_only_note: String = "A detent carries POSITION and how it is reached, and deliberately NOT a duration — the word means a resting notch, not a movement. The weight of an edge lives on the BehaviorRow that causes it (element × verb × response × timing), so each fact has exactly one home: the table says where the control sits, the row says how heavily it gets there. Putting an entry timing here as well would have been the same fact in two places, and the usual outcome of that is a table and a row disagreeing while both look authoritative." +// A detent carries POSITION and how it is reached, and deliberately NOT a duration — the word means +// a resting notch, not a movement. The weight of an edge lives on the BehaviorRow that causes it +// (element × verb × response × timing), so each fact has exactly one home: the table says where the +// control sits, the row says how heavily it gets there. Putting an entry timing here as well would +// have been the same fact in two places, and the usual outcome of that is a table and a row +// disagreeing while both look authoritative. type Detent { state: String @@ -154,9 +159,15 @@ type Detent { turn_deg: Int } -data detent_turn_axis_note: String = "turn_deg is the ROTATION axis of a position (remodel W1e, 2026-07-24: the disclosure mark turns 90 degrees at its open detent). Zero emits nothing — the wire appends ' rotate(Ndeg)' only when the detent actually turns — so every pre-existing detent's emission is byte-identical and the site digests hold; a detent that turns is the only one that says so." +// turn_deg is the ROTATION axis of a position (remodel W1e, 2026-07-24: the disclosure mark turns +// 90 degrees at its open detent). Zero emits nothing — the wire appends ' rotate(Ndeg)' only when +// the detent actually turns — so every pre-existing detent's emission is byte-identical and the +// site digests hold; a detent that turns is the only one that says so. -data house_pointer_detents_note: String = "The three positions every control shares, so a second control cannot quietly invent its own throw. Rest at unity, hover risen by the lift token at unity scale, pressed travelling down and compressing. A surface adds only the positions that are ITS OWN — a latched in-flight state, a terminal — by extending this list rather than restating it." +// The three positions every control shares, so a second control cannot quietly invent its own +// throw. Rest at unity, hover risen by the lift token at unity scale, pressed travelling down and +// compressing. A surface adds only the positions that are ITS OWN — a latched in-flight state, a +// terminal — by extending this list rather than restating it. fn house_pointer_detents(rest_state: String, hover_state: String, pressed_state: String) -> List { [ @@ -183,7 +194,14 @@ type DetentEdge { to_state: String } -data detent_edge_fault_note: String = "Two faults, kept apart, because the first cut of this check collapsed them and fell open. It read each endpoint's transform through a lookup that returned an empty string for an undeclared state, then compared the two strings — so an edge naming TWO states that do not exist compared equal-to-equal and was reported as a coinciding-position defect, while an edge naming ONE nonexistent state compared '' against a real transform and was reported as FINE. A typo in an edge row would have gone green. So an undeclared endpoint is now its own fault carrying the name that failed to resolve, and coincidence is only ever reported between two positions that actually exist." +// Two faults, kept apart, because the first cut of this check collapsed them and fell open. It read +// each endpoint's transform through a lookup that returned an empty string for an undeclared state, +// then compared the two strings — so an edge naming TWO states that do not exist compared +// equal-to-equal and was reported as a coinciding-position defect, while an edge naming ONE +// nonexistent state compared '' against a real transform and was reported as FINE. A typo in an +// edge row would have gone green. So an undeclared endpoint is now its own fault carrying the name +// that failed to resolve, and coincidence is only ever reported between two positions that actually +// exist. type DetentEdgeFault = EndpointUndeclared { state: String } diff --git a/dag/gunbc/design/interaction_cause.dag b/dag/gunbc/design/interaction_cause.dag index 7fd94fab141..c79c3813794 100644 --- a/dag/gunbc/design/interaction_cause.dag +++ b/dag/gunbc/design/interaction_cause.dag @@ -23,7 +23,20 @@ fn interaction_cause_key(c: InteractionCause) -> String { } } -data machine_observed_note: String = "The semantic reading of the split the register already relied on: PointerApproach, FocusAcquired and PressEngaged are things a PERSON does to a control; ObservedStateEntered is something the MACHINE did to it. The still-until-touched amendment (material.dag) turned on exactly this distinction — motion on user touch OR observed state change, never ambient — and it stated the distinction in terms of pseudo-class versus class, which is the realization's way of seeing it. This is the same fact said in the interface's terms, so a non-CSS renderer can honour the law without inheriting the vocabulary that happened to express it first." +// The semantic reading of the split the register already relied on: PointerApproach, FocusAcquired +// and PressEngaged are things a PERSON does to a control; ObservedStateEntered is something the +// MACHINE did to it. The still-until-touched amendment (material.dag) turned on exactly this +// distinction — motion on user touch OR observed state change, never ambient — and it stated the +// distinction in terms of pseudo-class versus class, which is the realization's way of seeing it. +// This is the same fact said in the interface's terms, so a non-CSS renderer can honour the law +// without inheriting the vocabulary that happened to express it first. + +// There is still no ambient constructor, and that is load-bearing rather than incidental. Every +// variant here names a CAUSE, so motion with no cause cannot be expressed in this vocabulary at all +// — the same construction wall MotionTrigger carried, preserved verbatim across the de-fusion. The +// instrument-motion lane (InstrumentDrift) is the declared exception and it does NOT widen this +// coproduct: bounded instrument character is a property of the enclosing instrument, not a response +// of a semantic control, so it gets its own carrier rather than an ambient arm here. fn cause_is_machine_observed(c: InteractionCause) -> Bool { match c { @@ -33,5 +46,3 @@ fn cause_is_machine_observed(c: InteractionCause) -> Bool { ObservedStateEntered { state_name } => true } } - -data ambient_remains_unwritable_note: String = "There is still no ambient constructor, and that is load-bearing rather than incidental. Every variant here names a CAUSE, so motion with no cause cannot be expressed in this vocabulary at all — the same construction wall MotionTrigger carried, preserved verbatim across the de-fusion. The instrument-motion lane (InstrumentDrift) is the declared exception and it does NOT widen this coproduct: bounded instrument character is a property of the enclosing instrument, not a response of a semantic control, so it gets its own carrier rather than an ambient arm here." diff --git a/dag/gunbc/design/material.dag b/dag/gunbc/design/material.dag index 7b10eb14918..d03aa572aee 100644 --- a/dag/gunbc/design/material.dag +++ b/dag/gunbc/design/material.dag @@ -15,7 +15,26 @@ import gunbc.design.interaction_cause { } import extdeps.languages.css.properties { CssPropertyName, Transition, css_property_wire } import extdeps.languages.css.selectors { SelectorSubject, selector_subject_wire } -data tokens_note: String = "Layer 2 of the site stack: materials, themes, scales. Register laws living here as CONSTRUCTION. ChromaByArea: a Material carries its AreaClass; saturation ceilings are per-area. GlareEnvelope (operator seventh pass, 2026-07-20: the bright saturated cyan read as low-taste gamer blue): a material may be bright OR chromatic, never both — quiet_envelope refuses sat>45 AND light>68 together, so the gamer register is inadmissible by row. DataVizArea (review finding 4 on PR 6927, pre-modeled seam): status-dense product surfaces (node-level CI views, dashboards) need mutually distinguishable categorical series at exactly the chroma-and-brightness the quiet envelope forbids — so the envelope is AREA-SCOPED by construction: DataVizArea is the one declared exemption lane, its own admission law (distinguishability + CVD separation, cited categorical palettes) lands with its first consumer (the Dashboard archetype study), and today NO theme role projects it — the witness pins that its introduction is a conscious event, never a leak. ThemeCoherence: a Theme is a TOTAL assignment of roles to materials (theme_gaps countable, mirrors interaction coverage), each material's area class must equal the role's declared area class, and every color the page uses routes through a role var — one rule set, N themes; prefers-color-scheme selects, never forks. ContrastLaw (review finding 1, hardened 2026-07-21): theme contrast is computed WCAG relative-luminance contrast via extdeps.accessibility.wcag_contrast — the earlier HSL-lightness-gap check was a non-perceptual proxy that could green while legibility failed; text roles must clear AA normal text (4.5:1), figure/focus roles AA graphical (3:1), against BOTH canvas and surface. StillUntilTouched: MotionTrigger has no ambient variant; no animation/keyframes constructor exists." +// Layer 2 of the site stack: materials, themes, scales. Register laws living here as CONSTRUCTION. +// ChromaByArea: a Material carries its AreaClass; saturation ceilings are per-area. GlareEnvelope +// (operator seventh pass, 2026-07-20: the bright saturated cyan read as low-taste gamer blue): a +// material may be bright OR chromatic, never both — quiet_envelope refuses sat>45 AND light>68 +// together, so the gamer register is inadmissible by row. DataVizArea (review finding 4 on PR 6927, +// pre-modeled seam): status-dense product surfaces (node-level CI views, dashboards) need mutually +// distinguishable categorical series at exactly the chroma-and-brightness the quiet envelope +// forbids — so the envelope is AREA-SCOPED by construction: DataVizArea is the one declared +// exemption lane, its own admission law (distinguishability + CVD separation, cited categorical +// palettes) lands with its first consumer (the Dashboard archetype study), and today NO theme role +// projects it — the witness pins that its introduction is a conscious event, never a leak. +// ThemeCoherence: a Theme is a TOTAL assignment of roles to materials (theme_gaps countable, +// mirrors interaction coverage), each material's area class must equal the role's declared area +// class, and every color the page uses routes through a role var — one rule set, N themes; +// prefers-color-scheme selects, never forks. ContrastLaw (review finding 1, hardened 2026-07-21): +// theme contrast is computed WCAG relative-luminance contrast via +// extdeps.accessibility.wcag_contrast — the earlier HSL-lightness-gap check was a non-perceptual +// proxy that could green while legibility failed; text roles must clear AA normal text (4.5:1), +// figure/focus roles AA graphical (3:1), against BOTH canvas and surface. StillUntilTouched: +// MotionTrigger has no ambient variant; no animation/keyframes constructor exists. type AreaClass = PointArea @@ -160,7 +179,15 @@ Mechanically the amendment is one trigger variant, and the reason it needed to b One genuine asymmetry has to be respected, and it is where the analog reading pays off. A pseudo-class response is SYMMETRIC — hover in, hover out, and you want both to animate — so its transition belongs on the BASE selector where it governs the edge in both directions. A state-class response is an edge into a DETENT, and each detent is entered with its own weight (a sharp 90 ms travel into pressed, a spring return into accepted), so its transition belongs on the STATE selector. Placing both on the base would make the last-written timing govern every edge of the element, silently, because CSS transition is a shorthand and not a list. Hence transition_carrier_selector: the placement is derived from the trigger's kind rather than chosen per site." -data motion_trigger_is_the_css_realization_note: String = "MotionTrigger IS THE CSS REALIZATION OF gunbc.design.interaction_cause.InteractionCause, not the register's motion vocabulary (F2, review call 2026-08-04). Its variants are named for selector spellings and its only projections produce selector text, which is what made it a fused carrier: a semantic module reaching for 'the pointer approached' had to import ':hover'. The meaning now lives renderer-neutrally in interaction_cause and this type is one realization of it — the one this repository emits today. css_trigger_for below is the lowering, and because it is a total match over a closed coproduct, a realization that omits a cause fails to compile rather than silently dropping the response. Emission is unchanged by the split: the lowering is spelling-preserving, so every generated byte holds." +// MotionTrigger IS THE CSS REALIZATION OF gunbc.design.interaction_cause.InteractionCause, not the +// register's motion vocabulary (F2, review call 2026-08-04). Its variants are named for selector +// spellings and its only projections produce selector text, which is what made it a fused carrier: +// a semantic module reaching for 'the pointer approached' had to import ':hover'. The meaning now +// lives renderer-neutrally in interaction_cause and this type is one realization of it — the one +// this repository emits today. css_trigger_for below is the lowering, and because it is a total +// match over a closed coproduct, a realization that omits a cause fails to compile rather than +// silently dropping the response. Emission is unchanged by the split: the lowering is +// spelling-preserving, so every generated byte holds. type MotionTrigger = OnHover @@ -266,7 +293,11 @@ fn css_block(selector: String, decls: List) -> String { join([selector, " { ", serialize_decls(decls: decls), " }"], "") } -data serialize_rule_carries_no_transition_note: String = "A rule serializes its DECLARATIONS only; its transition is emitted by the carrier pass. That split is what makes the shorthand collision unwritable rather than merely avoided: there is exactly one place a `transition` can come from, so no number of rules on one selector can produce two of them. The base block is omitted entirely when a rule declares no base declarations, which is the common case for a response — an empty block is noise in a generated stylesheet." +// A rule serializes its DECLARATIONS only; its transition is emitted by the carrier pass. That +// split is what makes the shorthand collision unwritable rather than merely avoided: there is +// exactly one place a `transition` can come from, so no number of rules on one selector can produce +// two of them. The base block is omitted entirely when a rule declares no base declarations, which +// is the common case for a response — an empty block is noise in a generated stylesheet. fn serialize_rule(rule: BuildRule) -> String { match rule { diff --git a/dag/gunbc/design/palette.dag b/dag/gunbc/design/palette.dag index 5fa31aa96db..55e60b7d8a9 100644 --- a/dag/gunbc/design/palette.dag +++ b/dag/gunbc/design/palette.dag @@ -11,7 +11,13 @@ import extdeps.accessibility.wcag_contrast { wcag_aaa_normal_text_hundredths, } -data design_palette_note: String = "L2 palette-catalogue machinery (design-register-library.md). A SwatchSet is register-independent catalog data; admissibility against the house register (gunbc.site.tokens quiet_envelope_hsl_pct) is checked at the (set × register) binding in witnesses, never here. swatch_names_distinct is O(n^2) over names — acceptable at catalogue scale, dissolve if a set-ordering witness lands. SwatchMemberKind (Filed | Derived) is the relationship vocabulary. Set-level structure rows live on extdeps palette modules and are pinned by witnesses. PairingGrade derives from the cited WCAG kernel — approval is computed, never hand-flagged." +// L2 palette-catalogue machinery (design-register-library.md). A SwatchSet is register-independent +// catalog data; admissibility against the house register (gunbc.site.tokens quiet_envelope_hsl_pct) +// is checked at the (set × register) binding in witnesses, never here. swatch_names_distinct is +// O(n^2) over names — acceptable at catalogue scale, dissolve if a set-ordering witness lands. +// SwatchMemberKind (Filed | Derived) is the relationship vocabulary. Set-level structure rows live +// on extdeps palette modules and are pinned by witnesses. PairingGrade derives from the cited WCAG +// kernel — approval is computed, never hand-flagged. type Swatch { name: String @@ -154,7 +160,14 @@ fn set_text_capable(s: SwatchSet) -> Bool { acc || grade_rank(g: p.grade) >= grade_rank(g: TextAa)) } -data swatch_set_for_use_note: String = "Role completion at the consuming layer (operator ruling 2026-07-24, roadmap-workspace-remodel-plan W1b): a base SwatchSet stays provenance-pure — island-pastel has no danger hue BY DESIGN, and serving a UI's need never edits the base. A use that needs a hue class the base cannot serve declares a SwatchSetForUse: the base plus completion members, each completion carrying its own provenance in its member kind or the use_note (the loam-from-camel precedent generalized — derivation-at-need, homed where the need lives). Admissibility composes: the base admissible, at least one completion (an empty completion list IS the base — use the base directly), and the combined membership still admissible, so a completion shadowing a base name is unwritable." +// Role completion at the consuming layer (operator ruling 2026-07-24, +// roadmap-workspace-remodel-plan W1b): a base SwatchSet stays provenance-pure — island-pastel has +// no danger hue BY DESIGN, and a UI's need never edits the base. A use needing a hue class the base +// cannot serve declares a SwatchSetForUse: base plus completion members, each carrying its own +// provenance in its member kind or the use_note (the loam-from-camel precedent generalized — +// derivation-at-need, homed where the need lives). Admissibility composes: base admissible, at +// least one completion (an empty completion list IS the base — use it directly), combined +// membership still admissible, so a completion shadowing a base name is unwritable. type SwatchSetForUse { name: String diff --git a/dag/gunbc/design/principles.dag b/dag/gunbc/design/principles.dag index fc901324441..8a88895ec34 100644 --- a/dag/gunbc/design/principles.dag +++ b/dag/gunbc/design/principles.dag @@ -2,7 +2,11 @@ module gunbc.design.principles import std.decl_ref { DeclarationRef, WholeDeclaration } -data register_principles_note: String = "The base layer of the gunb.ai site stack (docs/plans/gunb-ai-site-subsumption-design.md sections 4k/4l). Each principle is a signed register law. enforced_by points at the carrier whose CONSTRUCTION enforces it — the principle is data; the wall is the type or refusing constructor it names. A principle with no carrier is a review vibe, which is exactly what this layer exists to eliminate." +// The base layer of the gunb.ai site stack (docs/plans/gunb-ai-site-subsumption-design.md sections +// 4k/4l). Each principle is a signed register law. enforced_by points at the carrier whose +// CONSTRUCTION enforces it — the principle is data; the wall is the type or refusing constructor it +// names. A principle with no carrier is a review vibe, which is exactly what this layer exists to +// eliminate. type PrincipleId = QuietSurface @@ -24,7 +28,14 @@ type RegisterPrinciple { enforced_by: DeclarationRef } -data register_thesis_amendment_note: String = "THESIS REVISED (operator recalibration, 2026-08-02 — the S1 selective-spectacle amendment). The former thesis, quiet at arm's length / responsive up close / every response true, correctly rejected cheap uniform spectacle but was read as uniform austerity: it prevented good FOCAL spectacle along with bad ambient spectacle, and the shipped surfaces came out coherent but emotionally flat (the operator's gap ruling: no protagonist). The revision keeps every existing law — the calm field IS quiet-at-arm's-length — and adds the missing half as three new principles carried by gunbc.design.salience: energy is scarce, focal, and caused. The prior thesis string remains in history; this note is the amendment record." +// THESIS REVISED (operator recalibration, 2026-08-02 — the S1 selective-spectacle amendment). The +// former thesis, quiet at arm's length / responsive up close / every response true, correctly +// rejected cheap uniform spectacle but was read as uniform austerity: it prevented good FOCAL +// spectacle along with bad ambient spectacle, and the shipped surfaces came out coherent but +// emotionally flat (the operator's gap ruling: no protagonist). The revision keeps every existing +// law — the calm field IS quiet-at-arm's-length — and adds the missing half as three new principles +// carried by gunbc.design.salience: energy is scarce, focal, and caused. The prior thesis string +// remains in history; this note is the amendment record. data register_thesis: String = "Calm field; luminous focus; physical response; every response true." diff --git a/dag/gunbc/design/reference_instrument.dag b/dag/gunbc/design/reference_instrument.dag index b14004bb198..1b6f9d33375 100644 --- a/dag/gunbc/design/reference_instrument.dag +++ b/dag/gunbc/design/reference_instrument.dag @@ -5,9 +5,24 @@ import std.decl_ref { DeclarationRef, decl_ref } import extdeps.external_authority { ExternalSubjectRef, external_subject_ref_eq } import std.dissolution { DissolutionCondition, unbound_dissolution } -data reference_instrument_note: String = "THE JOIN, NOT A HUB (DESIGN section 3 'External upstream decomposition', whose own worked example puts extdeps.browser.chromium and extdeps.browser.google_chrome under a gunbc join rather than a generic browser hub). The two cited Pip-Boy products are independently governed upstream subjects with their own modules; this module names the agnostic behaviour axis they inhabit, records which product each axis is cited on BY POINTING AT THE FACT THAT CITES IT, and holds the coverage obligations our own reading created. Nothing here is a fact owned by the observed upstream. Adding a third reference instrument adds evidence rows here and one extdeps module; it must not require editing either existing product." - -data subject_identity_note: String = "SUBJECTS ARE REFERENCED SYMBOLICALLY, NEVER RENAMED (review finding, 2026-08-04). An earlier revision declared a local coproduct — CitedInstrument = PipBoy3000FalloutNewVegas | PipBoy3000MkVTvSeries — beside the real extdeps subject declarations, which is a second identity system for subjects the containment tree already names, and exactly the nicknaming class DESIGN section 3 forbids. Identity now travels as ExternalSubjectRef over DeclarationRef, the same carrier extdeps.external_authority uses for scope subjects, so a subject is named by module and declaration or not at all. The refs are built here rather than imported because both product modules name their scope declaration extdeps_model_scope, so importing the pair would collide; the symbolic form is the same one every scope row already carries." +// THE JOIN, NOT A HUB (DESIGN section 3 'External upstream decomposition', whose worked example +// puts extdeps.browser.chromium and extdeps.browser.google_chrome under a gunbc join rather than a +// generic browser hub). The two cited Pip-Boy products are independently governed upstream subjects +// with their own modules; this module names the agnostic behaviour axis they inhabit, records which +// product each axis is cited on BY POINTING AT THE FACT THAT CITES IT, and holds the coverage +// obligations our own reading created. Nothing here is a fact owned by the observed upstream. A +// third reference instrument adds evidence rows here and one extdeps module, never an edit to +// either existing product. + +// SUBJECTS ARE REFERENCED SYMBOLICALLY, NEVER RENAMED (review finding, 2026-08-04). An earlier +// revision declared a local coproduct — CitedInstrument = PipBoy3000FalloutNewVegas | +// PipBoy3000MkVTvSeries — beside the real extdeps subject declarations: a second identity system +// for subjects the containment tree already names, the nicknaming class DESIGN section 3 forbids. +// Identity now travels as ExternalSubjectRef over DeclarationRef, the carrier +// extdeps.external_authority uses for scope subjects, so a subject is named by module and +// declaration or not at all. The refs are built here rather than imported because both product +// modules name their scope declaration extdeps_model_scope, so importing the pair would collide; +// the symbolic form is the one every scope row already carries. data pip_boy_3000_subject: ExternalSubjectRef = ExternalSubjectRef { declaration: decl_ref( @@ -49,14 +64,34 @@ fn behaviour_axis_eq(a: InstrumentBehaviourAxis, b: InstrumentBehaviourAxis) -> behaviour_axis_key(a: a) == behaviour_axis_key(a: b) } -data evidence_join_note: String = "ATTRIBUTION IS DERIVED FROM EVIDENCE, NOT AUTHORED BESIDE IT (review finding, 2026-08-04). An earlier revision authored which products realized which axes as its own rows, importing the product settings only to count them — so correcting a setting in a product module did not correct the axis attribution, and the two representations could disagree silently. That is the parallel-representation failure the corpus already knows, and it is what let the whole attribution invert while ten witnesses stayed green. Every citation carries a DeclarationRef to the FACT that grounds it, inside the cited subject's own module, so an axis cannot be attached to a product without naming a declaration in that product." +// ATTRIBUTION IS DERIVED FROM EVIDENCE, NOT AUTHORED BESIDE IT (review finding, 2026-08-04). An +// earlier revision authored which products realized which axes as its own rows, importing the +// product settings only to count them — so correcting a setting in a product module did not correct +// the axis attribution, and the two could disagree silently: the parallel-representation failure, +// which let the whole attribution invert while ten witnesses stayed green. Every citation carries a +// DeclarationRef to the FACT that grounds it, inside the cited subject's own module, so an axis +// cannot be attached to a product without naming a declaration in that product. type InstrumentCitation { subject: ExternalSubjectRef fact: DeclarationRef } -data provenance_by_construction_note: String = "ONE TOTAL FUNCTION, NOT TWO LISTS AND A CHECK (review finding 48240, 2026-08-04, and the correct reading of DESIGN section 5). An earlier revision carried reference_instrument_evidence and originated_axes as INDEPENDENT lists with axis_provenance_is_total asserting the XOR over them — an axis with both kinds of provenance, or with none, was writable and merely detected. Because InstrumentBehaviourAxis is a CLOSED coproduct, an exhaustive match gives the same guarantee by construction: every axis has exactly one provenance because the match must answer for every variant and can answer only once, so both bad states lose their constructor and the XOR check is deleted rather than kept beside the shape that makes it unnecessary (section 4b(4): a climb dissolves the production machinery it obsoletes, never accumulates beside it). CitedOn is nonempty by the same first/rest split ExternalModelScope uses for citations, so a cited axis with zero citations is likewise unwritable. WHAT STAYS VALIDATION, honestly: an InstrumentCitation can still name a subject and a fact in different modules, and making THAT unwritable needs the citation constructors to be the only way to build one — a sole_constructor guarantee the corpus does not yet verify for generic carriers. The per-subject constructors below are the safe path and evidence_containment is the residue check over it." +// ONE TOTAL FUNCTION, NOT TWO LISTS AND A CHECK (review finding 48240, 2026-08-04, and the correct +// reading of DESIGN section 5). An earlier revision carried reference_instrument_evidence and +// originated_axes as INDEPENDENT lists with axis_provenance_is_total asserting the XOR over them — +// an axis with both kinds of provenance, or none, was writable and merely detected. Because +// InstrumentBehaviourAxis is a CLOSED coproduct, an exhaustive match gives the guarantee by +// construction: every axis has exactly one provenance because the match must answer for every +// variant exactly once, so both bad states lose their constructor and the XOR check is deleted +// rather than kept beside the shape that makes it unnecessary (section 4b(4): a climb dissolves the +// production machinery it obsoletes). CitedOn is nonempty by the same first/rest split +// ExternalModelScope uses for citations, so a cited axis with zero citations is likewise +// unwritable. WHAT STAYS VALIDATION, honestly: an InstrumentCitation can still name a subject and a +// fact in different modules, and making THAT unwritable needs the citation constructors to be the +// only way to build one — a sole_constructor guarantee the corpus does not yet verify for generic +// carriers. The per-subject constructors below are the safe path and evidence_containment is the +// residue check over it. fn pip_boy_3000_citation(decl_name: String) -> InstrumentCitation { InstrumentCitation { @@ -71,7 +106,12 @@ type AxisProvenance = CitedOn { first: InstrumentCitation, rest: List } | OriginatedHere { rationale: NonEmptyStr } -data originated_axis_note: String = "OriginatedHere is a first-class arm, not a gap. An axis the design lane wants that no cited instrument realizes has no upstream provenance to claim, and saying so is the point of the join: it separates what we cite from what we invent. BuoyantDrift is ours — no reference product realizes a continuous buoyant drift of the display, the axis entered this repository from the operator's fishing-hook-bobber description, and the InstrumentDrift register amendment it motivates must therefore stand on its own laws rather than borrow a Pip-Boy citation." +// OriginatedHere is a first-class arm, not a gap. An axis the design lane wants that no cited +// instrument realizes has no upstream provenance to claim, and saying so is the point of the join: +// it separates what we cite from what we invent. BuoyantDrift is ours — no reference product +// realizes a continuous buoyant drift of the display; the axis entered from the operator's +// fishing-hook-bobber description, so the InstrumentDrift register amendment it motivates must +// stand on its own laws rather than borrow a Pip-Boy citation. fn axis_provenance(a: InstrumentBehaviourAxis) -> AxisProvenance { match a { @@ -101,7 +141,11 @@ fn axis_provenance(a: InstrumentBehaviourAxis) -> AxisProvenance { } } -data mk_v_has_no_citations_note: String = "No axis names the Mk V, and that is the corrected reading rather than an omission. Its manual grounds display colour, brightness, demo mode, clock, alarm and radio — none of which inhabits an axis this join names. Whether it realizes any display-effect axis is UNOBSERVED, held below as a typed coverage obligation, never as an absence authored inside its upstream module. When a Mk V citation is grounded it joins the relevant axis's rest list; it does not need a new arm." +// No axis names the Mk V — the corrected reading, not an omission. Its manual grounds display +// colour, brightness, demo mode, clock, alarm and radio, none of which inhabits an axis this join +// names. Whether it realizes any display-effect axis is UNOBSERVED, held below as a typed coverage +// obligation, never as an absence authored inside its upstream module. When a Mk V citation is +// grounded it joins the relevant axis's rest list; no new arm is needed. fn axis_citations(a: InstrumentBehaviourAxis) -> List { match axis_provenance(a: a) { @@ -126,7 +170,11 @@ fn axis_has_any_evidence(axis: InstrumentBehaviourAxis) -> Bool { !axis_is_originated(axis: axis) } -data axis_iteration_roster_note: String = "The one place a roster can still lag the coproduct: this list is what corpus-wide folds iterate. Adding an axis variant without adding it here does NOT silently weaken a provenance guarantee — axis_provenance is an exhaustive match, so a new variant fails to compile until it is answered — it only narrows what the containment fold below walks. Dissolve-on: a derived variant enumeration over the closed coproduct." +// The one place a roster can still lag the coproduct: this list is what corpus-wide folds iterate. +// Adding an axis variant without adding it here does NOT silently weaken a provenance guarantee — +// axis_provenance is an exhaustive match, so a new variant fails to compile until it is answered — +// it only narrows what the containment fold below walks. Dissolve-on: a derived variant enumeration +// over the closed coproduct. data all_behaviour_axes: List = [ OpticalInstability, @@ -137,7 +185,14 @@ data all_behaviour_axes: List = [ BuoyantDrift ] -data evidence_containment_note: String = "THE LAW THAT WOULD HAVE CAUGHT THE INVERSION: a citation's fact must live in the cited subject's OWN module. The first revision attributed this product's glitch settings to its sibling; under this law that attribution requires pointing at a declaration inside the sibling's module, where no such declaration exists. This one remains a CHECK rather than a construction wall — see provenance_by_construction_note — because a hand-built InstrumentCitation can still cross modules; the per-subject constructors are the path that cannot. NEXT TRIGGER: v2.std.decl_ref_resolution.resolve_declaration_ref over decl_facts decides whether the named declaration EXISTS, which this predicate cannot — it decides only that the citation is internally coherent." +// THE LAW THAT WOULD HAVE CAUGHT THE INVERSION: a citation's fact must live in the cited subject's +// OWN module. The first revision attributed this product's glitch settings to its sibling; under +// this law that requires pointing at a declaration inside the sibling's module, where none exists. +// This remains a CHECK rather than a construction wall — see provenance_by_construction_note — +// because a hand-built InstrumentCitation can still cross modules; the per-subject constructors are +// the path that cannot. NEXT TRIGGER: v2.std.decl_ref_resolution.resolve_declaration_ref over +// decl_facts decides whether the named declaration EXISTS, which this predicate cannot — it decides +// only that the citation is internally coherent. fn citation_is_contained(c: InstrumentCitation) -> Bool { c.fact.module_path as String == c.subject.declaration.module_path as String @@ -155,7 +210,9 @@ fn all_evidence_is_contained() -> Bool { data named_referent: ExternalSubjectRef = pip_boy_3000_subject -data named_referent_note: String = "The product the frontend directive said it was referring to: the Fallout 3 / New Vegas unit. Held as its own declaration so that 'what was named' and 'what the axes resolve to' stay two rows that can be compared, rather than a sentence someone keeps true by hand." +// The product the frontend directive said it was referring to: the Fallout 3 / New Vegas unit. Its +// own declaration so 'what was named' and 'what the axes resolve to' stay two comparable rows +// rather than a sentence kept true by hand. data directive_requested_axes: List = [ OpticalInstability, @@ -168,7 +225,15 @@ fn subject_covers_axes(subject: ExternalSubjectRef, axes: List Bool { subject_covers_axes(subject: named_referent, axes: directive_requested_axes) @@ -178,7 +243,12 @@ fn mk_v_covers_directive() -> Bool { subject_covers_axes(subject: pip_boy_mk_v_subject, axes: directive_requested_axes) } -data source_read_path_note: String = "OUR read path, not the upstream's property (DESIGN section 3: observations this repository produces are receipts in the observing layer). Both manuals are now read DIRECTLY. The prior ReadViaSearchIndex receipt recorded an HTTP 403 as though the source were unreadable; the refusal was the fetcher's user agent, and a browser user agent retrieves both manuals at HTTP 200. That is the sharper lesson than the attribution error it caused: a tool's refusal was recorded as a property of the world, and every downstream fact inherited the weaker evidence for as long as it stood." +// OUR read path, not the upstream's property (DESIGN section 3: observations this repository +// produces are receipts in the observing layer). Both manuals are now read DIRECTLY. The prior +// ReadViaSearchIndex receipt recorded an HTTP 403 as though the source were unreadable; the refusal +// was the fetcher's user agent, and a browser user agent retrieves both manuals at HTTP 200. The +// sharper lesson than the attribution error it caused: a tool's refusal was recorded as a property +// of the world, and every downstream fact inherited the weaker evidence for as long as it stood. type SourceReadPath = ReadDirectly @@ -238,9 +308,14 @@ data mk_v_accelerometer_obligation: CoverageObligation = CoverageObligation { dissolution: unbound_dissolution(description: "A Mk V source grounds or denies onboard motion sensing."), } +// No exact-count assertion over either product's roster lives in this join or its witnesses. An +// earlier witness froze both settings rosters at four, so legitimately adding a setting read from a +// manual would have redded a claim unrelated to counts — a change detector, not a check (DESIGN +// section 5 on numeric literals measured from the tree). The witnesses assert identity facts +// instead: which settings exist, which subject an axis resolves to, and that every evidence row +// lands inside its own subject's module. + data reference_instrument_obligations: List = [ mk_v_display_effect_obligation, mk_v_accelerometer_obligation ] - -data roster_count_note: String = "No exact-count assertion over either product's roster lives in this join or its witnesses. An earlier witness froze both settings rosters at four, so legitimately adding a setting read from a manual would have redded a claim that has nothing to do with counts — a change detector rather than a check (DESIGN section 5 on numeric literals measured from the tree). What the witnesses assert instead are identity facts: which settings exist, which subject an axis resolves to, and that every evidence row lands inside its own subject's module." diff --git a/dag/gunbc/design/salience.dag b/dag/gunbc/design/salience.dag index fc6a678e419..d763a466e07 100644 --- a/dag/gunbc/design/salience.dag +++ b/dag/gunbc/design/salience.dag @@ -7,7 +7,26 @@ import extdeps.languages.css.values { } import gunbc.design.material { TimingToken, settle_spring } -data salience_note: String = "REGISTER AMENDMENT: SELECTIVE SPECTACLE (operator recalibration, 2026-08-02). The earlier register overcorrected against cheap spectacle into uniform austerity: QuietSurface, the glare envelope, and still-until-touched prevented bad spectacle and good FOCAL spectacle alike, which is why the site read coherent but emotionally flat. The corrected thesis is calm field, luminous focus, physical response, every response true: showiness is not forbidden, it is SCARCE, FOCAL, and CAUSED. This module is the salience model that makes that lawful rather than vibes: every energy channel (chroma, emission, motion, sound) is granted per role from one budget table, and the grants are typed refusals rather than per-site judgement. THE PROJECTION HAS LANDED, firing this paragraph's former scope clause (instrument wave, 2026-08-04). Roles are no longer only an input: gunbc.design.instrument_projection project_salience DECIDES a role from real facts — the camera's selected subject is Focal, its region siblings are Supporting, everything outside that region is Ground, and an aggregated active refusal is one Critical subject — and its output is judged by region_focal_admission below, the same fold that judges hand-authored assignments. A caller may still supply assignments by hand, which is correct rather than residual: this module is the budget authority and the projection is one consumer of it, not its only legitimate entry. The reference specimen is the operator's dynamic-tierlist login control (read from source 2026-08-02): a three-layer corona at 1:2:3 blur ratios with decaying alphas, proximity-scaled brightness, and a low-pass drift whose LAG is the material — its lesson is one focal object can be unusually alive when the surrounding field gives it room, NOT that every row glows. The field stays calm because the subject gets the energy." +// REGISTER AMENDMENT: SELECTIVE SPECTACLE (operator recalibration, 2026-08-02). The earlier +// register overcorrected against cheap spectacle into uniform austerity: QuietSurface, the glare +// envelope, and still-until-touched prevented bad spectacle and good FOCAL spectacle alike, which +// is why the site read coherent but emotionally flat. The corrected thesis is calm field, luminous +// focus, physical response, every response true: showiness is not forbidden, it is SCARCE, FOCAL, +// and CAUSED. This module is the salience model that makes that lawful rather than vibes: every +// energy channel (chroma, emission, motion, sound) is granted per role from one budget table, and +// the grants are typed refusals rather than per-site judgement. THE PROJECTION HAS LANDED, firing +// this paragraph's former scope clause (instrument wave, 2026-08-04). Roles are no longer only an +// input: gunbc.design.instrument_projection project_salience DECIDES a role from real facts — the +// camera's selected subject is Focal, its region siblings are Supporting, everything outside that +// region is Ground, and an aggregated active refusal is one Critical subject — and its output is +// judged by region_focal_admission below, the same fold that judges hand-authored assignments. A +// caller may still supply assignments by hand, which is correct rather than residual: this module +// is the budget authority and the projection is one consumer of it, not its only legitimate entry. +// The reference specimen is the operator's dynamic-tierlist login control (read from source +// 2026-08-02): a three-layer corona at 1:2:3 blur ratios with decaying alphas, proximity-scaled +// brightness, and a low-pass drift whose LAG is the material — its lesson is one focal object can +// be unusually alive when the surrounding field gives it room, NOT that every row glows. The field +// stays calm because the subject gets the energy. type SalienceRole = SalienceGround @@ -24,7 +43,16 @@ fn salience_key(role: SalienceRole) -> String { } } -data salience_budget_note: String = "ONE budget table, derived per role — never per-site energy decisions (section 2: model once, derive every use). Ground is the world context: minimal chroma, no emission, no motion, no sound. Supporting is the ordinary sibling: restrained chroma, no emission, small interaction motion (hover/press responses it already owns), no sound. Focal is the selected subject or its primary operation: strong chroma, emission admissible, physical bounded motion, the detent/engage sounds. Critical is an active refusal needing action: strong-but-distinct chroma, emission admissible, sharp bounded motion, the blocked/refusal sound. Emission and physical motion are ENHANCEMENT channels by law — the projection that grants them must carry a non-emission discriminator (state class, text, shape), so reduced-motion and no-emission renderings preserve selection and operability by construction, and a steady glow can never be the only claim a state makes." +// ONE budget table, derived per role — never per-site energy decisions (section 2: model once, +// derive every use). Ground is the world context: minimal chroma, no emission, no motion, no sound. +// Supporting is the ordinary sibling: restrained chroma, no emission, small interaction motion +// (hover/press responses it already owns), no sound. Focal is the selected subject or its primary +// operation: strong chroma, emission admissible, physical bounded motion, the detent/engage sounds. +// Critical is an active refusal needing action: strong-but-distinct chroma, emission admissible, +// sharp bounded motion, the blocked/refusal sound. Emission and physical motion are ENHANCEMENT +// channels by law — the projection that grants them must carry a non-emission discriminator (state +// class, text, shape), so reduced-motion and no-emission renderings preserve selection and +// operability by construction, and a steady glow can never be the only claim a state makes. type ChromaGrant = MinimalChroma @@ -90,7 +118,19 @@ fn salience_budget(role: SalienceRole) -> SalienceBudget { } } -data emission_note: String = "MATERIAL AND EMISSION ARE SEPARATE LAYERS (the CRT model): a material is the matte surface or glass and stays inside the quiet envelope; emission is light PRODUCED by the active instrument, layered over the restrained material. Emission is therefore the register's second declared exemption lane beside DataVizArea — scoped by SALIENCE ROLE where DataVizArea is scoped by area, and admitted only through emission_for, whose refusals are typed: a Ground or Supporting subject cannot be granted emission at all, a spec outside the envelope refuses with the measured violation, and a grant with no non-emission discriminator refuses because emission may never be the only state discriminator. The envelope keeps the login reference's shape and rejects its excess: a crisp core near the object, one soft corona carrying identity, the corona strictly wider than the core and strictly low-alpha, total spread far below the reference's maximum bloom. EmissionSpec deliberately has NO pulse, duration, or animation field: a periodic glow is ambient motion (still-until-touched forbids it by construction — no constructor exists) and a steady glow cannot imply progress." +// MATERIAL AND EMISSION ARE SEPARATE LAYERS (the CRT model): a material is the matte surface or +// glass and stays inside the quiet envelope; emission is light PRODUCED by the active instrument, +// layered over the restrained material. Emission is therefore the register's second declared +// exemption lane beside DataVizArea — scoped by SALIENCE ROLE where DataVizArea is scoped by area, +// and admitted only through emission_for, whose refusals are typed: a Ground or Supporting subject +// cannot be granted emission at all, a spec outside the envelope refuses with the measured +// violation, and a grant with no non-emission discriminator refuses because emission may never be +// the only state discriminator. The envelope keeps the login reference's shape and rejects its +// excess: a crisp core near the object, one soft corona carrying identity, the corona strictly +// wider than the core and strictly low-alpha, total spread far below the reference's maximum bloom. +// EmissionSpec deliberately has NO pulse, duration, or animation field: a periodic glow is ambient +// motion (still-until-touched forbids it by construction — no constructor exists) and a steady glow +// cannot imply progress. type EmissionSpec { core_radius: CssLength @@ -102,7 +142,24 @@ type EmissionSpec { data emission_corona_alpha_max_pct: Int = 25 data emission_corona_radius_max_px: Int = 48 -data length_unit_comparability_note: String = "THE UNIT WORD IS NOT THE UNIT (review finding, 2026-08-02). CssLength carries n AND unit (Px | Rem | Ch | Percent), while every bound in this module is denominated in PX. Comparing spec.corona_radius.n against emission_corona_radius_max_px therefore compared a bare magnitude against a px bound with no unit check: a corona authored as 40rem passed the envelope at '40 <= 48' while denoting roughly 640px — about 13x the intended maximum. That is a silent widen inside the exact construction wall this module exists to be (section 5: a failure arm must refuse, never widen), and it was reported as a formatting nit. Non-px is REFUSED rather than converted, because conversion is not decidable here: rem needs the root font size and percent needs a containing block, neither of which this authority carries, so fabricating a px equivalent would be the fabricated-plausible-output arm one level down. The refusal is a DISTINCT state from a measured overflow, mirroring the ContentHash cross-family rule (a comparison across incomparable representations is a third state, never a Bool collapse) — and it has to be, because EmissionOutsideEnvelope carries a field named corona_radius_px, so reporting a rem magnitude through it would be an actively false receipt. Attraction takes the strict half only: its envelope is a bare predicate with no receipt to mislabel, so unit-strictness there is complete. The bounds stay bare Int by deliberate scope: once non-px refuses, their px denomination is ENFORCED rather than assumed, and re-typing them belongs to the std.measure grounding lane (dissolve-on: lengths ground on std.measure and the bound carries its own unit)." +// THE UNIT WORD IS NOT THE UNIT (review finding, 2026-08-02). CssLength carries n AND unit (Px | +// Rem | Ch | Percent), while every bound in this module is denominated in PX. Comparing +// spec.corona_radius.n against emission_corona_radius_max_px therefore compared a bare magnitude +// against a px bound with no unit check: a corona authored as 40rem passed the envelope at '40 <= +// 48' while denoting roughly 640px — about 13x the intended maximum. That is a silent widen inside +// the exact construction wall this module exists to be (section 5: a failure arm must refuse, never +// widen), and it was reported as a formatting nit. Non-px is REFUSED rather than converted, because +// conversion is not decidable here: rem needs the root font size and percent needs a containing +// block, neither of which this authority carries, so fabricating a px equivalent would be the +// fabricated-plausible-output arm one level down. The refusal is a DISTINCT state from a measured +// overflow, mirroring the ContentHash cross-family rule (a comparison across incomparable +// representations is a third state, never a Bool collapse) — and it has to be, because +// EmissionOutsideEnvelope carries a field named corona_radius_px, so reporting a rem magnitude +// through it would be an actively false receipt. Attraction takes the strict half only: its +// envelope is a bare predicate with no receipt to mislabel, so unit-strictness there is complete. +// The bounds stay bare Int by deliberate scope: once non-px refuses, their px denomination is +// ENFORCED rather than assumed, and re-typing them belongs to the std.measure grounding lane +// (dissolve-on: lengths ground on std.measure and the bound carries its own unit). fn length_is_px(l: CssLength) -> Bool { match l.unit { @@ -162,7 +219,13 @@ fn emission_for(role: SalienceRole, spec: EmissionSpec, discriminator: String) - } } -data focal_budget_note: String = "AT MOST ONE ORDINARY FOCAL SUBJECT PER INTERACTION REGION; CRITICAL MAY INTERRUPT. This is the law that makes showy compatible with hierarchy: the field stays calm because exactly one subject holds the energy, and the admission is computed over the region's actual assignments rather than trusted per site. A second Focal in one region is a typed, counted refusal (never a silent widen, section 5); Critical subjects are deliberately outside the ordinary budget — an active refusal needing action may interrupt the focal subject without dethroning it, because truth outranks visual quiet." +// AT MOST ONE ORDINARY FOCAL SUBJECT PER INTERACTION REGION; CRITICAL MAY INTERRUPT. This is the +// law that makes showy compatible with hierarchy: the field stays calm because exactly one subject +// holds the energy, and the admission is computed over the region's actual assignments rather than +// trusted per site. A second Focal in one region is a typed, counted refusal (never a silent widen, +// section 5); Critical subjects are deliberately outside the ordinary budget — an active refusal +// needing action may interrupt the focal subject without dethroning it, because truth outranks +// visual quiet. type SalienceAssignment { subject: String @@ -191,7 +254,14 @@ fn role_is_critical(role: SalienceRole) -> Bool { } } -data count_roles_where_note: String = "COUNT BY PREDICATE, NOT BY A BOOLEAN MODE FLAG (review finding, 2026-08-02). This fold was count_roles(assignments, focal: Bool), where the flag selected between two independent counts — so the call site read count_roles(assignments, focal: false) to mean 'count the critical ones', a domain concept encoded as a bare Bool and legible only by knowing which branch false picks. The parameter now carries the admission predicate itself, which both names the intent at the call site and leaves ONE fold instead of a two-mode one. Shape follows std.materialization_ladder first_provider_where, which takes wanted: fn(CacheProvider) -> Bool and is called with a lambda over a named coproduct predicate." +// COUNT BY PREDICATE, NOT BY A BOOLEAN MODE FLAG (review finding, 2026-08-02). This fold was +// count_roles(assignments, focal: Bool), where the flag selected between two independent counts — +// so the call site read count_roles(assignments, focal: false) to mean 'count the critical ones', a +// domain concept encoded as a bare Bool and legible only by knowing which branch false picks. The +// parameter now carries the admission predicate itself, which both names the intent at the call +// site and leaves ONE fold instead of a two-mode one. Shape follows std.materialization_ladder +// first_provider_where, which takes wanted: fn(CacheProvider) -> Bool and is called with a lambda +// over a named coproduct predicate. fn count_roles_where(assignments: List, admits: fn(SalienceRole) -> Bool) -> Int { fold(assignments, init: 0, f: (acc, a) => @@ -208,7 +278,19 @@ fn region_focal_admission(region: String, assignments: List) } } -data attraction_note: String = "PHYSICAL RESPONSE MOVES THE VISUAL PLATE, NEVER THE HIT REGION. The reference login control follows the pointer across the whole viewport because it is the only target; a roadmap operation must never become an evasive moving target. AttractionField is therefore a bounded LOCAL field: the pointer entering activation_radius attracts the visual plate by at most max_travel inside a FIXED hit region, and the carrier structurally cannot express anything else — it has no position, offset, or hit-region field, so a realization that moved the target would need facts this authority does not carry (correctness by construction, section 5). The watery quality is inertia and damping, not a pre-authored path: settle borrows the register's one non-monotone easing (settle_spring, derived by reference — no second spring is minted), and the reference's ~16-second low-pass lag is recorded as the lesson LAG IS THE MATERIAL, to be realized inside these bounds at audition. Keyboard focus receives the same focal identity without displacement; touch presses and settles without pointer attraction; renderer-local motion is ephemera and never advances domain state or implies workflow progress." +// PHYSICAL RESPONSE MOVES THE VISUAL PLATE, NEVER THE HIT REGION. The reference login control +// follows the pointer across the whole viewport because it is the only target; a roadmap operation +// must never become an evasive moving target. AttractionField is therefore a bounded LOCAL field: +// the pointer entering activation_radius attracts the visual plate by at most max_travel inside a +// FIXED hit region, and the carrier structurally cannot express anything else — it has no position, +// offset, or hit-region field, so a realization that moved the target would need facts this +// authority does not carry (correctness by construction, section 5). The watery quality is inertia +// and damping, not a pre-authored path: settle borrows the register's one non-monotone easing +// (settle_spring, derived by reference — no second spring is minted), and the reference's +// ~16-second low-pass lag is recorded as the lesson LAG IS THE MATERIAL, to be realized inside +// these bounds at audition. Keyboard focus receives the same focal identity without displacement; +// touch presses and settles without pointer attraction; renderer-local motion is ephemera and never +// advances domain state or implies workflow progress. type AttractionField { activation_radius: CssLength diff --git a/dag/gunbc/design/scale.dag b/dag/gunbc/design/scale.dag index 8c68ce2d180..fc82ffc40a4 100644 --- a/dag/gunbc/design/scale.dag +++ b/dag/gunbc/design/scale.dag @@ -9,7 +9,22 @@ import extdeps.languages.css.values { css_font_stack_wire, } -data design_scale_note: String = "Layer 2 token axes (design-register-library.md, the sibling axes to material/theme): spacing, type, font stack, plus the dimension tokens (radius, border width, measure, fixed element extents) the roadmap surface consumes. THE single authority for every length/font literal the register emits — the whole point is that a page rule references a token (var(--space-8)), never a raw pixel, so the untokened-length census (gunbc.design.theme_transition.untokened_length_decls) has exactly one place to exclude by scope (this :root block, the analog of the theme var block the unthemed-color census excludes). D3 grounding: a token's value is a TYPED CssLength (magnitude x unit, extdeps.languages.css.values, cited CSS Values and Units) or a typed CssFontStack (a list of families) — the fused \"4px\"/'Noto Sans, ...' strings are decompressed (DESIGN section 2), and the value-encoded token NAME is DERIVED from the magnitude by the length_token constructor (space-4 from px 4), so name and value can no longer diverge — the fork is UNWRITABLE by construction, not caught by a witness (DESIGN section 5). A semantic re-tuning of the step values (a rhythm, not a per-value alias) is a named follow-on; today the axis's job is DRY (each value lives once) + census-ability + no magnitude-vs-name fork, all served now. Spacing/type/font are the three axes the brief names; dimension_scale is their companion so 'no raw px' is total, not partial." +// Layer 2 token axes (design-register-library.md, the sibling axes to material/theme): spacing, +// type, font stack, plus the dimension tokens (radius, border width, measure, fixed element +// extents) the roadmap surface consumes. THE single authority for every length/font literal the +// register emits — the whole point is that a page rule references a token (var(--space-8)), never a +// raw pixel, so the untokened-length census (gunbc.design.theme_transition.untokened_length_decls) +// has exactly one place to exclude by scope (this :root block, the analog of the theme var block +// the unthemed-color census excludes). D3 grounding: a token's value is a TYPED CssLength +// (magnitude x unit, extdeps.languages.css.values, cited CSS Values and Units) or a typed +// CssFontStack (a list of families) — the fused "4px"/'Noto Sans, ...' strings are decompressed +// (DESIGN section 2), and the value-encoded token NAME is DERIVED from the magnitude by the +// length_token constructor (space-4 from px 4), so name and value can no longer diverge — the fork +// is UNWRITABLE by construction, not caught by a witness (DESIGN section 5). A semantic re-tuning +// of the step values (a rhythm, not a per-value alias) is a named follow-on; today the axis's job +// is DRY (each value lives once) + census-ability + no magnitude-vs-name fork, all served now. +// Spacing/type/font are the three axes the brief names; dimension_scale is their companion so 'no +// raw px' is total, not partial. type ScaleValue = LengthValue { length: CssLength } @@ -27,7 +42,10 @@ type ScaleToken { value: ScaleValue } -data scale_token_derive_note: String = "length_token DERIVES the token name from the magnitude (space-4 from px 4), so the number lives exactly once and a name/value divergence is unwritable (D3). named_length_token is for the semantic-named dimension tokens whose name does NOT encode the magnitude (measure, h-mark) — no fork to close there. font_token carries a typed CssFontStack." +// length_token DERIVES the token name from the magnitude (space-4 from px 4), so the number lives +// exactly once and a name/value divergence is unwritable (D3). named_length_token is for the +// semantic-named dimension tokens whose name does NOT encode the magnitude (measure, h-mark) — no +// fork to close there. font_token carries a typed CssFontStack. fn length_token(prefix: String, px: Int) -> ScaleToken { ScaleToken { name: join([prefix, "-", to_string(px)], ""), value: LengthValue { length: css_px(n: px) } } @@ -108,16 +126,35 @@ data border_1: ScaleToken = length_token(prefix: "border", px: 1) data border_2: ScaleToken = length_token(prefix: "border", px: 2) data measure_page: ScaleToken = named_length_token(name: "measure", px: 960) -data height_control_note: String = "The tactile target height (remodel W1c, operator archetype 2026-07-24: the GitHub merge button — a control you press with confidence needs the hand-scale target, ~40px class, not a text-line strip). This token plus radius-999 (the oblong/pill radius) are the first two rows of the control-geometry axis; the axis stays token-grain until a SECOND instrument lands (the archetype library's own second-consumer discipline), at which point the rows lift into a typed ControlGeometry carrier." +// The tactile target height (remodel W1c, operator archetype 2026-07-24: the GitHub merge button — +// a control you press with confidence needs the hand-scale target, ~40px class, not a text-line +// strip). This token plus radius-999 (the oblong/pill radius) are the first two rows of the +// control-geometry axis; the axis stays token-grain until a SECOND instrument lands (the archetype +// library's own second-consumer discipline), at which point the rows lift into a typed +// ControlGeometry carrier. data height_control: ScaleToken = named_length_token(name: "h-control", px: 40) -data height_row_note: String = "The roadmap row's density token (composition slice 2, the signed actuator ruling 2026-07-24: chip-scale in rows, sixty pills must be quiet at arm's length — the instrument feel is carried by press physics, never by area). The ROW owns this height and the in-row actuator DERIVES its extent from it (derived-not-set, witnessed): there is no separate chip-height token to drift against the row that contains it. h-control stays the FULL-SIZE instrument's height, consumed by the sandbox gallery specimen where design work lives. 24px SIGNED at the operator's pass (2026-07-25, chosen against 22 and 28 on the live page) — the former PROVISIONAL mark discharges." +// The roadmap row's density token (composition slice 2, the signed actuator ruling 2026-07-24: +// chip-scale in rows, sixty pills must be quiet at arm's length — the instrument feel is carried by +// press physics, never by area). The ROW owns this height and the in-row actuator DERIVES its +// extent from it (derived-not-set, witnessed): there is no separate chip-height token to drift +// against the row that contains it. h-control stays the FULL-SIZE instrument's height, consumed by +// the sandbox gallery specimen where design work lives. 24px SIGNED at the operator's pass +// (2026-07-25, chosen against 22 and 28 on the live page) — the former PROVISIONAL mark discharges. data height_row: ScaleToken = named_length_token(name: "h-row", px: 24) data width_status: ScaleToken = named_length_token(name: "w-status", px: 52) -data travel_token_note: String = "A control's TRAVEL is a dimension, so it belongs on the dimension axis with every other length rather than as a literal inside a transform value — otherwise the first raw pixel in the register arrives through the one property nobody thought to look at, and the untokened-length census (which keys on 'px' appearing in a value) would flag it exactly as it flags a stray padding. Two tokens rather than one signed magnitude used twice: travel-lift is NEGATIVE because rising is negative Y in the coordinate system CSS actually has, and hiding that sign inside a calc() at each use site would put arithmetic in the value string where a token belongs. 1 px is deliberately the whole vocabulary — a chip-sized control has a chip-sized throw, and the register earns nothing from a travel-2 with no consumer." +// A control's TRAVEL is a dimension, so it belongs on the dimension axis with every other length +// rather than as a literal inside a transform value — otherwise the first raw pixel in the register +// arrives through the one property nobody thought to look at, and the untokened-length census +// (which keys on 'px' appearing in a value) would flag it exactly as it flags a stray padding. Two +// tokens rather than one signed magnitude used twice: travel-lift is NEGATIVE because rising is +// negative Y in the coordinate system CSS actually has, and hiding that sign inside a calc() at +// each use site would put arithmetic in the value string where a token belongs. 1 px is +// deliberately the whole vocabulary — a chip-sized control has a chip-sized throw, and the register +// earns nothing from a travel-2 with no consumer. data travel_lift: ScaleToken = named_length_token(name: "travel-lift", px: -1) data travel_press: ScaleToken = named_length_token(name: "travel-press", px: 1) @@ -136,7 +173,11 @@ fn scale_var_decls() -> List { map(all_scale_tokens(), t => scale_decl(t: t)) } -data scale_root_css_note: String = "The token :root block. Emitted alongside theme_root_css so a page's stylesheet carries both authorities in :root and every rule references them by var. This block is the ONE home of raw px/font literals; the untokened-length census excludes it by scope (it runs over the page's BuildRule rows, never this block) exactly as the unthemed-color census excludes the theme var block." +// The token :root block. Emitted alongside theme_root_css so a page's stylesheet carries both +// authorities in :root and every rule references them by var. This block is the ONE home of raw +// px/font literals; the untokened-length census excludes it by scope (it runs over the page's +// BuildRule rows, never this block) exactly as the unthemed-color census excludes the theme var +// block. fn scale_root_css() -> String { css_block(selector: ":root", decls: scale_var_decls()) diff --git a/dag/gunbc/design/sound.dag b/dag/gunbc/design/sound.dag index 1ac1ecd86ef..60f83d17838 100644 --- a/dag/gunbc/design/sound.dag +++ b/dag/gunbc/design/sound.dag @@ -20,7 +20,11 @@ data sound_occasion_axis_note: String = "The occasion AXIS is deliberately not a A substrate note, recorded because it shaped the decision rather than being routed around: a single-variant coproduct is not expressible here — `type T = OnlyVariant` parses as an alias to a type named OnlyVariant, so the constructor never exists. That is a real property of the grammar and it happens to point the same way the consumption rule does." -data sound_growth_trigger: String = "SoundOccasion becomes a closed sum — MechanismEngage | MechanismSettle | MechanismBlocked — in the PR that lands checkpoint 7b (the accept settle tone and the refusal thud). At that point sound_specs becomes a total occasion→spec assignment with a coverage census mirroring interaction coverage_gaps, and every_declared_sound_admissible folds over the occasions instead of over a hand list." +// SoundOccasion becomes a closed sum — MechanismEngage | MechanismSettle | MechanismBlocked — in +// the PR that lands checkpoint 7b (the accept settle tone and the refusal thud). At that point +// sound_specs becomes a total occasion→spec assignment with a coverage census mirroring interaction +// coverage_gaps, and every_declared_sound_admissible folds over the occasions instead of over a +// hand list. type SoundSpec { frequency: Hertz diff --git a/dag/gunbc/design/state_response.dag b/dag/gunbc/design/state_response.dag index 8f97a79aa47..e1f35b66e2d 100644 --- a/dag/gunbc/design/state_response.dag +++ b/dag/gunbc/design/state_response.dag @@ -51,7 +51,9 @@ type StateFamily { members: List } -data member_subject_note: String = "RestBinding realizes on the component's own class selector — the rest state IS the unqualified component, which is why the base rule owns the rest paint. A class-bound member composes component class + state class, the same compound the client program flips." +// RestBinding realizes on the component's own class selector — the rest state IS the unqualified +// component, which is why the base rule owns the rest paint. A class-bound member composes +// component class + state class, the same compound the client program flips. fn member_selector_text(f: StateFamily, m: StateFamilyMember) -> String { match m.binding { @@ -83,7 +85,10 @@ fn member_channel_decl(f: StateFamily, m: StateFamilyMember, prop: CssPropertyNa decl(prop: prop, value: state_var_ref(prefix: f.var_prefix, key: m.key, role: role)) } -data state_rest_decl_note: String = "The rest member's channel decls, for splicing into the component's base rule at the positions its authoring order demands. Returns a LIST so an absent rest member yields an absent decl rather than a fabricated one — the absence then surfaces through the byte oracle and the census, loudly, instead of a placeholder value surviving review." +// The rest member's channel decls, for splicing into the component's base rule at the positions its +// authoring order demands. Returns a LIST so an absent rest member yields an absent decl rather +// than a fabricated one — the absence then surfaces through the byte oracle and the census, loudly, +// instead of a placeholder value surviving review. fn state_rest_decl(f: StateFamily, prop: CssPropertyName, role: StateVarRole) -> List { match rest_member(f: f) { @@ -102,7 +107,11 @@ fn scope_rest_decls(f: StateFamily, m: StateFamilyMember, channels: List) -> List { fold(f.members, init: [], f: (acc, m) => match m.binding { @@ -112,7 +121,13 @@ fn state_paint_rules(f: StateFamily, channels: List) -> List List { map(f.members, m => @@ -171,7 +186,12 @@ fn foreign_cell_decl(f: StateFamily, foreign_key: String, c: StateChannel) -> St join([channel_prop_wire(c: c), ": ", state_var_ref(prefix: f.var_prefix, key: foreign_key, role: channel_role(c: c))], "") } -data census_block_probe_note: String = "A class-bound member's cell is probed as selector-adjacent text: the member's selector followed by a block containing the expected decl would be exact, but blocks interleave channels, so the probe pairs (selector present) with (expected decl present) and, on a miss, substitutes every other member's key into the same decl to name the foreign family it found instead. The rest member's decls live inside the component's base rule among geometry decls, so its probe is the decl alone and cross-family detection is declared out of its reach (state_response_census_note)." +// A class-bound member's cell is probed as selector-adjacent text: the member's selector followed +// by a block containing the expected decl would be exact, but blocks interleave channels, so the +// probe pairs (selector present) with (expected decl present) and, on a miss, substitutes every +// other member's key into the same decl to name the foreign family it found instead. The rest +// member's decls live inside the component's base rule among geometry decls, so its probe is the +// decl alone and cross-family detection is declared out of its reach (state_response_census_note). fn member_block_contains(css: String, f: StateFamily, m: StateFamilyMember, expected: String) -> Bool { string_contains(s: css, pattern: member_selector_text(f: f, m: m)) && string_contains(s: css, pattern: expected) diff --git a/dag/gunbc/design/theme.dag b/dag/gunbc/design/theme.dag index c765391c978..6511c3d53a0 100644 --- a/dag/gunbc/design/theme.dag +++ b/dag/gunbc/design/theme.dag @@ -13,7 +13,8 @@ import gunbc.design.material { } import extdeps.languages.css.properties { CssPropertyName } -data theme_note: String = "Layer 2 theme machinery: total role-to-material binding, WCAG contrast law, night/day atlas rows. Lifted from gunbc.site.tokens (design-register-library.md Phase A)." +// Layer 2 theme machinery: total role-to-material binding, WCAG contrast law, night/day atlas rows. +// Lifted from gunbc.site.tokens (design-register-library.md Phase A). type ThemeRole = CanvasRole | SurfaceRole @@ -219,7 +220,16 @@ fn theme_var_decls(t: Theme) -> List { [decl(prop: ColorScheme, value: t.scheme)]) } -data theme_scoped_blocks_note: String = "ONE THEME-SELECTOR GRAMMAR, N VAR FAMILIES. The three-block shape below — :root for the base theme, :root[data-theme=NAME] for the alternate, and the same alternate again under prefers-color-scheme for readers who never touched the toggle — is what makes a theme switchable at all, and it was written out longhand once here for role vars and again in gunbc.roadmap_style band_root_css for band vars. A second copy of a selector grammar is a second place for the toggle to break in only one of them, which is precisely the failure a reader would never think to look for: the day/night switch would keep working for colors and silently stop working for bands. The family of declarations is now a parameter, so theme_root_css is the role-var instantiation, band_root_css is the band-var instantiation, and the instrument's emission vars are a third — one grammar, three families, and emission is unchanged in all of them." +// ONE THEME-SELECTOR GRAMMAR, N VAR FAMILIES. The three-block shape below — :root for the base +// theme, :root[data-theme=NAME] for the alternate, and the same alternate again under +// prefers-color-scheme for readers who never touched the toggle — is what makes a theme switchable +// at all, and it was written out longhand once here for role vars and again in gunbc.roadmap_style +// band_root_css for band vars. A second copy of a selector grammar is a second place for the toggle +// to break in only one of them, which is precisely the failure a reader would never think to look +// for: the day/night switch would keep working for colors and silently stop working for bands. The +// family of declarations is now a parameter, so theme_root_css is the role-var instantiation, +// band_root_css is the band-var instantiation, and the instrument's emission vars are a third — one +// grammar, three families, and emission is unchanged in all of them. fn theme_scoped_blocks(base: Theme, alternate: Theme, decls_for: fn(Theme) -> List) -> String { join([ diff --git a/dag/gunbc/design/theme_transition.dag b/dag/gunbc/design/theme_transition.dag index 9b16a1e2f3c..994304d8893 100644 --- a/dag/gunbc/design/theme_transition.dag +++ b/dag/gunbc/design/theme_transition.dag @@ -76,7 +76,14 @@ fn themed_props_of(decls: List) -> List { }) } -data state_class_census_reach_note: String = "Both censuses reach state-class responses with no new arm, and that is the payoff of landing the still-until-touched amendment as a TRIGGER variant rather than a second rule shape. A new BuildRule variant would have left these two readers with their old arms while the emitter grew a shape — the wall silently ceasing to cover part of the surface it claims to cover, coverage by illusion in the one mechanism whose whole job is coverage. Because a detent is an ordinary ResponseRule carrying a different suffix, an unthemed colour or a raw pixel in a state's declarations is censused exactly as it is anywhere else, and rule_selector keeps reporting the base selector because that is the subject a census locates a violation ON." +// Both censuses reach state-class responses with no new arm, and that is the payoff of landing the +// still-until-touched amendment as a TRIGGER variant rather than a second rule shape. A new +// BuildRule variant would have left these two readers with their old arms while the emitter grew a +// shape — the wall silently ceasing to cover part of the surface it claims to cover, coverage by +// illusion in the one mechanism whose whole job is coverage. Because a detent is an ordinary +// ResponseRule carrying a different suffix, an unthemed colour or a raw pixel in a state's +// declarations is censused exactly as it is anywhere else, and rule_selector keeps reporting the +// base selector because that is the subject a census locates a violation ON. fn rule_selector(rule: BuildRule) -> String { match rule { @@ -133,7 +140,13 @@ fn unthemed_color_decls(rules: List) -> List { })) } -data untokened_length_note: String = "The length/font sibling of the unthemed-color census (P2): a rule value carrying a raw pixel literal or a quoted font family bypasses the scale (gunbc.design.scale), so it is neither DRY (the value forks per site) nor retunable in one place. The rule is the same shape as the color census — every length/font must route through a token var — and the exclusion is the same by scope: this census runs over a surface's BuildRule rows, never the scale :root block (the one home of the literals). value_has_raw_length keys on 'px' and the single-quote of a font family; unitless 0, %, em, ms (a time, from the timing scale) are not lengths and are not flagged." +// The length/font sibling of the unthemed-color census (P2): a rule value carrying a raw pixel +// literal or a quoted font family bypasses the scale (gunbc.design.scale), so it is neither DRY +// (the value forks per site) nor retunable in one place. The rule is the same shape as the color +// census — every length/font must route through a token var — and the exclusion is the same by +// scope: this census runs over a surface's BuildRule rows, never the scale :root block (the one +// home of the literals). value_has_raw_length keys on 'px' and the single-quote of a font family; +// unitless 0, %, em, ms (a time, from the timing scale) are not lengths and are not flagged. type UntokenedLengthDecl { selector: String diff --git a/dag/gunbc/devboot/program.dag b/dag/gunbc/devboot/program.dag index 481444f9cd3..0fe5c650e38 100644 --- a/dag/gunbc/devboot/program.dag +++ b/dag/gunbc/devboot/program.dag @@ -3,11 +3,22 @@ module gunbc.devboot.program import std.types { String, Bool, List } import extdeps.cargo { CargoTarget, Bin } -// WHAT `gunbc build ` MEANS BY A PROGRAM. The public operation takes a name, and a name that reaches cargo unchecked fails at the far end of the pipeline: after a snapshot, after a push to another host, after a producer materializes a tree, cargo reports that it matched no target and the caller learns their typo minutes later and one host away. The roster is what turns that into an immediate, local, typed refusal. +// WHAT `gunbc build ` MEANS BY A PROGRAM. The public operation takes a name, and a name +// that reaches cargo unchecked fails at the far end of the pipeline: after a snapshot, a push to +// another host, and a producer materializing a tree, cargo reports no matching target and the caller +// learns their typo minutes later and one host away. The roster turns that into an immediate, +// local, typed refusal. // -// THE ROSTER IS A DECLARED FRONTIER, NOT AN AUTHORITY. Cargo.toml already enumerates these targets; this list is a second spelling of rows that exist there, which is the parallel representation DESIGN section 3 forbids. It is written down anyway because the alternative available today is passing an unvalidated string to a remote builder, and a counted, named duplicate is honest where a silent one is not. +// THE ROSTER IS A DECLARED FRONTIER, NOT AN AUTHORITY. Cargo.toml already enumerates these targets; +// this list is a second spelling of them, the parallel representation DESIGN section 3 forbids. It +// exists because the alternative today is passing an unvalidated string to a remote builder, and a +// counted, named duplicate is honest where a silent one is not. // -// Dissolve-on: a cargo manifest ingest that yields the [[bin]] rows as data, at which point this row is deleted rather than maintained -- the selection becomes a lookup in the manifest model and a program absent from the manifest refuses for the reason it actually refuses. Until then a program added to Cargo.toml and not added here refuses, which is the fail-closed direction: the caller is told the name is not offered rather than being handed a build of something else. +// Dissolve-on: a cargo manifest ingest that yields the [[bin]] rows as data, at which point this row +// is deleted -- selection becomes a lookup in the manifest model and a program absent from the +// manifest refuses for the reason it actually refuses. Until then a program added to Cargo.toml and +// not here refuses, the fail-closed direction: the caller is told the name is not offered rather +// than handed a build of something else. data devboot_program_package: String = "v1-compiler" fn offered_programs() -> List { diff --git a/dag/gunbc/devboot/transport.dag b/dag/gunbc/devboot/transport.dag index 969bbe1a052..651d68ef1dc 100644 --- a/dag/gunbc/devboot/transport.dag +++ b/dag/gunbc/devboot/transport.dag @@ -19,7 +19,12 @@ import extdeps.git.plumbing { GitRefObservationRefused, } -data devboot_shell_transport_note: String = "Counted retained-transport roster for the WitnessBin.Run sites of gunbc.devboot.build, gunbc.devboot.produce, and this module -- the directory ensure runs HERE, on the six callers' behalf, so a roster scoped to the other two modules would no longer name every site. Runtime-present devboot modules are not yet on host_effect_apply typed effects; every raw argv site names a RetainedWitnessRun row with reason + dissolve-on = shell→intent Phase 2 (gunbc.host_effect_realize host_effect_apply). No unmarked WitnessBin.Run calls survive review." +// Counted retained-transport roster for the WitnessBin.Run sites of gunbc.devboot.build, +// gunbc.devboot.produce, and this module -- the directory ensure runs HERE, on the six callers' +// behalf, so a roster scoped to the other two modules would no longer name every site. +// Runtime-present devboot modules are not yet on host_effect_apply typed effects; every raw argv +// site names a RetainedWitnessRun row with reason + dissolve-on = shell→intent Phase 2 +// (gunbc.host_effect_realize host_effect_apply). No unmarked WitnessBin.Run calls survive review. // THE SEVENTEEN GIT ROWS THAT USED TO SIT HERE ARE DELETED RATHER THAN REPOINTED, and the // distinction is the point. Every row in this roster declares the same dissolution target, @@ -157,7 +162,6 @@ fn devboot_observe_ref(repo_root: String, ref_name: String) -> GitRefObservation devboot_ref_observation(exit_code: read.exit_code, revision: read.revision, stderr: read.stderr) } - type WitnessBinRunOutcome { success: Bool exit_code: Int diff --git a/dag/gunbc/diff_baseline.dag b/dag/gunbc/diff_baseline.dag index 15d47f7d098..04e54bfd2d9 100644 --- a/dag/gunbc/diff_baseline.dag +++ b/dag/gunbc/diff_baseline.dag @@ -20,19 +20,56 @@ import extdeps.github.actions { github_event_name_schedule } -data diff_baseline_law: String = "DiffBaseline is the typed answer to 'what does the affected-set diff observe HEAD against?' — the de-fork of the origin/main policy-as-literal (DESIGN §3(c)). Arms: MergeTarget (pull_request GITHUB_BASE_REF branch name only → origin/, merge-base/triple-dot), ExactReplayBoundary (GUNBC_DIFF_WINDOW_PATH exact base/head GitObjectIds, direct/two-dot), PushBefore (push payload before SHA), PushParent (default branch for non-PR CI events), OperatorOverride (GUNBC_CI_DIFF_BASE). resolve_diff_baseline is PURE over CiDiffEvent; env reads live in v2.workflow.floor_diff_observe.floor_observe_ci_diff_event." +// DiffBaseline is the typed answer to 'what does the affected-set diff observe HEAD against?' — the +// de-fork of the origin/main policy-as-literal (DESIGN §3(c)). Arms: MergeTarget (pull_request +// GITHUB_BASE_REF branch name only → origin/, merge-base/triple-dot), ExactReplayBoundary +// (GUNBC_DIFF_WINDOW_PATH exact base/head GitObjectIds, direct/two-dot), PushBefore (push payload +// before SHA), PushParent (default branch for non-PR CI events), OperatorOverride +// (GUNBC_CI_DIFF_BASE). resolve_diff_baseline is PURE over CiDiffEvent; env reads live in +// v2.workflow.floor_diff_observe.floor_observe_ci_diff_event. data diff_baseline_fail_closed_law: String = "A CI event this resolver cannot map to a base REFUSES (BaselineRefused) — it never substitutes a constant. The refusal propagates through floor_diff_observe's UnifiedDiffFail / NameStatusDiffFail into the executor's diff observation (cli_run.rs floor_git_diff_range), which HALTS the floor with a typed AFFECTED-SET REFUSAL (cause=DiffObservationRefusal): 'refuses every enrolled row rather than widening to a full-corpus run' (operator ruling 2026-07-05). The ignorance state is a loud, located, HARD STOP — NOT a run-everything widen (an earlier draft of this note misdescribed it as widen-to-whole-tree; the executor refuses, it does not widen — DESIGN §5, the exact absorbing-fallback shape being avoided). Only a genuinely-unrecognized event name lands here: the five live workflow triggers (push / pull_request / workflow_dispatch / merge_group from ci_workflow, schedule from falsifier_workflow) all map to a base arm, so a future-added trigger halts loudly until it is modeled, rather than silently defaulting to origin/main — the exact defect being deleted, now typed." -data diff_baseline_bootstrap_arm_note: String = "The empty-event-name arm ('' — no GITHUB_EVENT_NAME) is an EXPLICIT recognized state, not the catch-all: it is the local / pre-CI-bootstrap window where there is no GitHub event at all, and its correct baseline is the repo's default merge target (PushParent), which is exactly today's behavior for a bare local floor_diff_observe invocation (DiffPolicy.base = origin/main). It is grouped with workflow_dispatch / merge_group / schedule because all mean 'observe against the default branch'. Push is NOT grouped here: a push event always requires a before SHA. The catch-all (a future GitHub trigger not yet modeled — ⊤-as-ignorance, genuinely not-knowing the base) is the final else → BaselineRefused, which HALTS the floor (never a silent origin/main). Conflating the two would be the absorbing fallback DESIGN §5 forbids; keeping them distinct keeps 'no CI event' (a known state, PushParent) apart from 'CI event we cannot map' (real ignorance, halt)." +// The empty-event-name arm ('' — no GITHUB_EVENT_NAME) is an EXPLICIT recognized state, not the +// catch-all: it is the local / pre-CI-bootstrap window where there is no GitHub event at all, and +// its correct baseline is the repo's default merge target (PushParent), which is exactly today's +// behavior for a bare local floor_diff_observe invocation (DiffPolicy.base = origin/main). It is +// grouped with workflow_dispatch / merge_group / schedule because all mean 'observe against the +// default branch'. Push is NOT grouped here: a push event always requires a before SHA. The +// catch-all (a future GitHub trigger not yet modeled — ⊤-as-ignorance, genuinely not-knowing the +// base) is the final else → BaselineRefused, which HALTS the floor (never a silent origin/main). +// Conflating the two would be the absorbing fallback DESIGN §5 forbids; keeping them distinct keeps +// 'no CI event' (a known state, PushParent) apart from 'CI event we cannot map' (real ignorance, +// halt). -data diff_baseline_push_before_law: String = "Push events resolve PushBefore from the webhook payload's before member (extdeps.github.push_event), NOT PushParent. Absent, empty, zero (0000000000000000000000000000000000000000 — new-branch creation per cited payload semantics, not observed in-repo), equal-to-head, or head-unknown before SHAs REFUSE: a push with no prior commit has no honest affected-set window; a before SHA equal to head is a self-compare; and head unknown cannot rule out self-compare. Observation-layer refusals (unreadable event payload, malformed JSON) arrive as PushBeforeInputRefused and propagate with their computed cause — they never collapse to a generic absent. These refusals halt the floor; they never widen to origin/main. The baseline SOURCE (payload before SHA) stays separable from these policy arms so a future last-green source swap does not rewrite refusal logic." +// Push events resolve PushBefore from the webhook payload's before member +// (extdeps.github.push_event), NOT PushParent. Absent, empty, zero +// (0000000000000000000000000000000000000000 — new-branch creation per cited payload semantics, not +// observed in-repo), equal-to-head, or head-unknown before SHAs REFUSE: a push with no prior commit +// has no honest affected-set window; a before SHA equal to head is a self-compare; and head unknown +// cannot rule out self-compare. Observation-layer refusals (unreadable event payload, malformed +// JSON) arrive as PushBeforeInputRefused and propagate with their computed cause — they never +// collapse to a generic absent. These refusals halt the floor; they never widen to origin/main. The +// baseline SOURCE (payload before SHA) stays separable from these policy arms so a future +// last-green source swap does not rewrite refusal logic. -data diff_baseline_merge_target_law: String = "pull_request MergeTarget reads GITHUB_BASE_REF as a GitHub TARGET BRANCH NAME only (extdeps.github.actions github_ci_event_surface_note). It always becomes origin/ via git_remote_ref — even when the branch name is lexically 40 lowercase hex characters, because Git ref names are not reserved against object-id spellings and semantic kind cannot be recovered from text alone. Exact merge-base object identities belong in gunbc.git_diff_change_window (GUNBC_DIFF_WINDOW_PATH), never in GITHUB_BASE_REF." +// pull_request MergeTarget reads GITHUB_BASE_REF as a GitHub TARGET BRANCH NAME only +// (extdeps.github.actions github_ci_event_surface_note). It always becomes origin/ via +// git_remote_ref — even when the branch name is lexically 40 lowercase hex characters, because Git +// ref names are not reserved against object-id spellings and semantic kind cannot be recovered from +// text alone. Exact merge-base object identities belong in gunbc.git_diff_change_window +// (GUNBC_DIFF_WINDOW_PATH), never in GITHUB_BASE_REF. -data diff_baseline_exact_replay_law: String = "Exact replay selects ExactReplayBoundary with typed GitObjectId base/head from gunbc.git_diff_change_window. Git CLI operands are rendered only at the realization boundary via diff_baseline_diff_base/diff_baseline_diff_head (git_object_id_wire_hex). Comparison is direct/two-dot — not triple-dot — because the producer already claims the merge-base boundary. Repository availability, object kind, ancestry verification, and checkout/head pinning are PR-2 Git endpoint repairs; this arm still refuses malformed wire input and self-compare at parse time." +// Exact replay selects ExactReplayBoundary with typed GitObjectId base/head from +// gunbc.git_diff_change_window. Git CLI operands are rendered only at the realization boundary via +// diff_baseline_diff_base/diff_baseline_diff_head (git_object_id_wire_hex). Comparison is +// direct/two-dot — not triple-dot — because the producer already claims the merge-base boundary. +// Repository availability, object kind, ancestry verification, and checkout/head pinning are PR-2 +// Git endpoint repairs; this arm still refuses malformed wire input and self-compare at parse time. -data diff_baseline_source_conflict_law: String = "GUNBC_DIFF_WINDOW_PATH and GUNBC_CI_DIFF_BASE are independent baseline authorities. When both are set, resolve_diff_baseline refuses with BaselineSourceConflict rather than picking an order — the collision is typed evidence, not a silent precedence guess." +// GUNBC_DIFF_WINDOW_PATH and GUNBC_CI_DIFF_BASE are independent baseline authorities. When both are +// set, resolve_diff_baseline refuses with BaselineSourceConflict rather than picking an order — the +// collision is typed evidence, not a silent precedence guess. type DiffBaseline = MergeTarget { ref: GitRef } @@ -64,7 +101,12 @@ type DiffBaselineResolution | BaselineRefused { reason: String } | BaselineSourceConflict { window_detail: String, override_ref: String } -data diff_baseline_kind_law: String = "WHICH ARM SELECTED THIS BASELINE, as a closed projection rather than a rendered name. A consumer that must LOCATE a refusal needs the arm identity, and the alternative — a String kind — would be a second naming scheme for variants the coproduct already names (DESIGN section 3). It is deliberately NOT the comparison mode: mode is floor_diff_mode_for_baseline's answer and depends on policy for three of the five arms, so a consumer that switched on kind to recover mode would fork that decision. Kind locates; mode decides." +// WHICH ARM SELECTED THIS BASELINE, as a closed projection rather than a rendered name. A consumer +// that must LOCATE a refusal needs the arm identity, and the alternative — a String kind — would be +// a second naming scheme for variants the coproduct already names (DESIGN section 3). It is +// deliberately NOT the comparison mode: mode is floor_diff_mode_for_baseline's answer and depends +// on policy for three of the five arms, so a consumer that switched on kind to recover mode would +// fork that decision. Kind locates; mode decides. type DiffBaselineKind = MergeTargetBaseline diff --git a/dag/gunbc/dispatch_pipe_pane_emit.dag b/dag/gunbc/dispatch_pipe_pane_emit.dag index 1b7fa2b3474..c33a3c615be 100644 --- a/dag/gunbc/dispatch_pipe_pane_emit.dag +++ b/dag/gunbc/dispatch_pipe_pane_emit.dag @@ -7,7 +7,20 @@ import v2.workflow.bash_command_fold_serialize { bash_fold_serialize_node } import v2.std.diagnostic { Accepted, Rejected } import v2.std.node { Node } -data dispatch_event_pipe_emit_note: String = "review 44063. tmux pipe-pane's input contract IS one shell-command string, so the SEAM is permanent — but the payload is no longer hand-assembled. dispatch_event_pipe_intent builds the command as a bash AST (one command, five literal words) and dispatch_event_pipe_emission renders it through the signed v2.workflow.bash_command_fold_serialize backend, which is the same grammar the ingest side reads forward (DESIGN 4, one grammar read in both directions). This module exists BECAUSE the payload construction has to sit on a realization edge: v2.lens.realization_vocabulary_containment confines the bash-AST vocabulary to realization_edge_path_prefixes, and dag/gunbc/roadmap/roadmap_dispatch_actuator.dag is a workflow module, not an emitter. The split is the containment rule working as designed, not an exception to it — the precedent is dag/gunbc/live_deploy/emit. Quoting is now the grammar's job, not the caller's: the lit-word production single-quotes every word and escapes an embedded quote, so extdeps.exec.command.shell_quote is no longer applied at this site. Verified by execution that the emitted spelling is behaviourally identical for this payload — a quoted command name still resolves the exec builtin (quoting suppresses alias and reserved-word recognition, not builtin lookup)." +// review 44063. tmux pipe-pane's input contract IS one shell-command string, so the SEAM is +// permanent — but the payload is no longer hand-assembled. dispatch_event_pipe_intent builds the +// command as a bash AST (one command, five literal words) and dispatch_event_pipe_emission renders +// it through the signed v2.workflow.bash_command_fold_serialize backend, the grammar ingest reads +// forward (DESIGN 4, one grammar read in both directions). This module exists BECAUSE payload +// construction must sit on a realization edge: v2.lens.realization_vocabulary_containment confines +// the bash-AST vocabulary to realization_edge_path_prefixes, and +// dag/gunbc/roadmap/roadmap_dispatch_actuator.dag is a workflow module, not an emitter. The split +// is the containment rule working as designed (precedent: dag/gunbc/live_deploy/emit). Quoting is +// the grammar's job: the lit-word production single-quotes every word and escapes an embedded +// quote, so extdeps.exec.command.shell_quote is no longer applied here. Verified by execution that +// the emitted spelling is behaviourally identical for this payload — a quoted command name still +// resolves the exec builtin (quoting suppresses alias and reserved-word recognition, not builtin +// lookup). fn dispatch_event_pipe_intent(tee_program: String, events_path: String) -> Node { bash_build_command_from_lits(lits: [ @@ -23,7 +36,13 @@ type PipePaneEmission = PipeEmitted { command: TmuxPipeCommand } | PipeEmitRefused { detail: String } -data dispatch_event_pipe_emit_refusal_note: String = "review 44113. The first version of this module answered a serializer Rejected with a TmuxPipeCommand carrying a non-executable poison marker. That was wrong in the way DESIGN 5 names precisely: a refusal that still produces a valid effect request is a widen wearing a refusal's clothes — the actuator could not tell it apart from a successful serialization, so an un-emittable intent still reached tmux as a command, and the serializer's own diagnostics were discarded on the way. The emission is now a typed sum. A refusal carries its located detail and has NO arm that yields a command, so dispatching one is unwritable rather than merely unlikely; the spawn plan above refuses whole and the belt surfaces it as SpawnFailed at the step that could not be built." +// review 44113. The first version answered a serializer Rejected with a TmuxPipeCommand carrying a +// non-executable poison marker — the DESIGN 5 widen wearing a refusal's clothes: the actuator could +// not tell it from a successful serialization, so an un-emittable intent still reached tmux as a +// command and the serializer's diagnostics were discarded. The emission is now a typed sum: a +// refusal carries its located detail and has NO arm yielding a command, so dispatching one is +// unwritable; the spawn plan above refuses whole and the belt surfaces it as SpawnFailed at the +// step that could not be built. fn dispatch_event_pipe_emission( tee_program: String, diff --git a/dag/gunbc/dispatch_preflight.dag b/dag/gunbc/dispatch_preflight.dag index add83d408e2..80e5d2ec235 100644 --- a/dag/gunbc/dispatch_preflight.dag +++ b/dag/gunbc/dispatch_preflight.dag @@ -40,11 +40,56 @@ import extdeps.languages.json.emit { } import extdeps.shell -data dispatch_preflight_note: String = "THE CAPABILITY ADMISSION ONLY RUNS INSIDE A SPAWN, AND WHILE THE BELT REFUSES THERE IS NO SPAWN. gunbc.roadmap_dispatch_environment observes every declared CapabilityRealization and grounds it, but its only production caller is the per-attempt spawn path in gunbc.roadmap_belt_actuate. belt_tick_for_instance evaluates the fleet revision standing BEFORE it reaches any spawn, so a host whose deployed tree has drifted from refs/fleet/desired refuses spawn and reap and never once probes an executable. Measured on srv1 2026-08-28: the belt had been answering that refusal on every tick since 2026-08-20 with the deployed tree 664 commits behind main, and the capability roster had therefore not been exercised on the host for eight days. Whether git, tmux, the pipe shell, tee and jq still resolve for the service principal was, over that whole window, unmeasured rather than measured-good.\n\nThis module is the read-only route that answers it independently of whether a spawn is admissible. It observes and it does not actuate: capability_ensure_observe runs shell.Test.IsExecutable and the realization's own declared identity_args (the --version probes), and fleet_revision_standing runs git ls-remote and rev-parse in the deployed tree. No worktree is created, no tmux session is started, no file is written, and there is no flag that opens a write path — a preflight that can also change what it measures is a preflight whose green proves nothing.\n\nTHE SUBJECT IS THE DECLARED ROSTER, NOT A CONTRACT'S REQUIREMENTS. dispatch_environment_admit probes the capabilities one WorkItemExecutionContract requires; a preflight has no work item, and narrowing to some contract's subset would report a green over capabilities the next work item needs. So the subject is environment.realizations itself — everything the instance declares it can realize — which is a superset of any contract's requirement set and is derived from the instance rather than chosen here.\n\nSIBLINGS CONTINUE. Every axis is evaluated and every refusal is carried, because the first refusal is a prefix of the truth and a preflight that returns at the first one reports the producer's patience rather than the host's state. The verdict counts them." +// THE CAPABILITY ADMISSION ONLY RUNS INSIDE A SPAWN, AND WHILE THE BELT REFUSES THERE IS NO SPAWN. +// gunbc.roadmap_dispatch_environment observes every declared CapabilityRealization and grounds it, +// but its only production caller is the per-attempt spawn path in gunbc.roadmap_belt_actuate. +// belt_tick_for_instance evaluates the fleet revision standing BEFORE it reaches any spawn, so a +// host whose deployed tree has drifted from refs/fleet/desired refuses spawn and reap and never +// once probes an executable. Measured on srv1 2026-08-28: the belt had been answering that refusal +// on every tick since 2026-08-20 with the deployed tree 664 commits behind main, and the capability +// roster had therefore not been exercised on the host for eight days. Whether git, tmux, the pipe +// shell, tee and jq still resolve for the service principal was, over that whole window, unmeasured +// rather than measured-good. +// +// This module is the read-only route that answers it independently of whether a spawn is +// admissible. It observes and it does not actuate: capability_ensure_observe runs +// shell.Test.IsExecutable and the realization's own declared identity_args (the --version probes), +// and fleet_revision_standing runs git ls-remote and rev-parse in the deployed tree. No worktree is +// created, no tmux session is started, no file is written, and there is no flag that opens a write +// path — a preflight that can also change what it measures is a preflight whose green proves +// nothing. +// +// THE SUBJECT IS THE DECLARED ROSTER, NOT A CONTRACT'S REQUIREMENTS. dispatch_environment_admit +// probes the capabilities one WorkItemExecutionContract requires; a preflight has no work item, and +// narrowing to some contract's subset would report a green over capabilities the next work item +// needs. So the subject is environment.realizations itself — everything the instance declares it +// can realize — which is a superset of any contract's requirement set and is derived from the +// instance rather than chosen here. +// +// SIBLINGS CONTINUE. Every axis is evaluated and every refusal is carried, because the first +// refusal is a prefix of the truth and a preflight that returns at the first one reports the +// producer's patience rather than the host's state. The verdict counts them. data dispatch_preflight_schema: String = "roadmap-dispatch-preflight/v1" -data dispatch_preflight_provider_auth_gap_note: String = "PROVIDER AUTHENTICATION IS NOT AN AXIS HERE, AND NAMING IT AS A PERMANENTLY-UNOBSERVED ONE WOULD BE WORSE THAN OMITTING IT. gunbc.roadmap_dispatch_environment's own header already draws this line — cached OAuth and upstream quota are provider protocol facts, not executable identity — and gunbc.provider_standing already models the shape the answer would take (ProviderAuthenticationStanding), while gunbc.claude_setup_token_enrollment models the classification (ClaudeCredentialStanding). What does not exist is an OBSERVER: nothing in the corpus reads a credential store and derives either. An arm that could only ever answer Unobserved is a decoration that reads as coverage (DESIGN 4b), so this module carries none.\n\nThe gap is real and was measured rather than assumed. On srv1 2026-08-28 the service principal's Claude credential file was present at 0600 with a refresh token, and its claudeAiOauth.expiresAt was roughly ninety minutes in the past; whether the CLI would have refreshed on first use is untested, because a refresh is a write and that pass was read-only. NEXT-RUNG TRIGGER: a modeled read-then-decode observation over the credential store — extdeps.filesystem.filesystem_io Read into extdeps.languages.json.parse, compared against Clock.Now — producing ClaudeCredentialStanding from live bytes. When that lands it joins this report as a fourth axis and this row dissolves. Until then the honest statement is that this preflight does not answer it." +// PROVIDER AUTHENTICATION IS NOT AN AXIS HERE, AND NAMING IT AS A PERMANENTLY-UNOBSERVED ONE WOULD +// BE WORSE THAN OMITTING IT. gunbc.roadmap_dispatch_environment's own header already draws this +// line — cached OAuth and upstream quota are provider protocol facts, not executable identity — and +// gunbc.provider_standing already models the shape the answer would take +// (ProviderAuthenticationStanding), while gunbc.claude_setup_token_enrollment models the +// classification (ClaudeCredentialStanding). What does not exist is an OBSERVER: nothing in the +// corpus reads a credential store and derives either. An arm that could only ever answer Unobserved +// is a decoration that reads as coverage (DESIGN 4b), so this module carries none. +// +// The gap is real and was measured rather than assumed. On srv1 2026-08-28 the service principal's +// Claude credential file was present at 0600 with a refresh token, and its claudeAiOauth.expiresAt +// was roughly ninety minutes in the past; whether the CLI would have refreshed on first use is +// untested, because a refresh is a write and that pass was read-only. NEXT-RUNG TRIGGER: a modeled +// read-then-decode observation over the credential store — extdeps.filesystem.filesystem_io Read +// into extdeps.languages.json.parse, compared against Clock.Now — producing +// ClaudeCredentialStanding from live bytes. When that lands it joins this report as a fourth axis +// and this row dissolves. Until then the honest statement is that this preflight does not answer +// it. // One axis of the preflight. The label names WHAT was examined and is the join key a // reader uses; grounded/refused is the verdict. A refusal carries its own reason so a @@ -53,7 +98,6 @@ type PreflightAxisVerdict = AxisGrounded { axis_label: String, evidence: String } | AxisRefused { axis_label: String, reason: String } - fn axis_verdict_is_refused(verdict: PreflightAxisVerdict) -> Bool { match verdict { AxisGrounded { axis_label: _, evidence: _ } => false @@ -138,7 +182,19 @@ fn revision_axis_verdict(standing: FleetRevisionStanding) -> PreflightAxisVerdic } } -data dispatch_preflight_single_authority_note: String = "THE REPORT HOLDS THE OBSERVATIONS AND DERIVES THE AXES, and the first cut did not (review 57110, gunbc#9559). It stored capability_receipts AND the axes projected from them side by side, which is the second representation DESIGN 3 forbids: the two could disagree, and nothing anywhere would have said which one was right. This was not hypothetical and the proof was in this change's own witnesses — they constructed a report with an EMPTY receipt list and populated capability axes, so the inconsistent state was demonstrably authorable by the very tests that were supposed to establish the carrier's honesty.\n\nThe repair is construction rather than a consistency check (DESIGN 5): the report carries the capability receipts and the fleet revision standing — two independent observations, neither derived from the other — and preflight_axes projects them on demand for the verdict and for the wire. A report whose axes disagree with its receipts now has no spelling, so there is nothing left to validate." +// THE REPORT HOLDS THE OBSERVATIONS AND DERIVES THE AXES, and the first cut did not (review 57110, +// gunbc#9559). It stored capability_receipts AND the axes projected from them side by side, which +// is the second representation DESIGN 3 forbids: the two could disagree, and nothing anywhere would +// have said which one was right. This was not hypothetical and the proof was in this change's own +// witnesses — they constructed a report with an EMPTY receipt list and populated capability axes, +// so the inconsistent state was demonstrably authorable by the very tests that were supposed to +// establish the carrier's honesty. +// +// The repair is construction rather than a consistency check (DESIGN 5): the report carries the +// capability receipts and the fleet revision standing — two independent observations, neither +// derived from the other — and preflight_axes projects them on demand for the verdict and for the +// wire. A report whose axes disagree with its receipts now has no spelling, so there is nothing +// left to validate. type DispatchPreflightReport { instance_id: String @@ -196,7 +252,6 @@ fn dispatch_preflight_for_instance( ) } - fn dispatch_preflight_json_value(preflight: DispatchPreflight) -> JsonValue { match preflight { PreflightAdmitted { report } => @@ -242,7 +297,20 @@ fn dispatch_preflight_json_value(preflight: DispatchPreflight) -> JsonValue { } } -data dispatch_preflight_spelling_note: String = "TWO SPELLINGS, ONE IMPLEMENTATION, for the same reason belt_run_once_in exists: dispatch_preflight_json is the surface a reader invokes by hand to SEE the report whatever it says, and dispatch_preflight_cli is the surface a caller invokes to have the line STOP on a refusal. Both route through dispatch_preflight_for_instance, so the printed report and the exit status cannot disagree. The refusal carries the whole report in its reason rather than a summary, because a caller that has just been told the preflight failed needs the axes, not the count.\n\nONLY THE BY-HAND SPELLING DEFAULTS TO PRODUCTION, and the asymmetry is deliberate rather than an oversight in one of the two. dispatch_preflight_json takes no channel and names srv1_live_dashboard_instance directly, exactly as belt_run_once does and for the same reason: it is an inspection surface someone invokes against production, where the subject is what they came for. dispatch_preflight_cli is the SCHEDULED spelling -- the one a unit or another host could invoke -- so it reads the workdir channel and fail-closes on its absence rather than inheriting a default that would silently make a lab caller a production reader." +// TWO SPELLINGS, ONE IMPLEMENTATION, for the same reason belt_run_once_in exists: +// dispatch_preflight_json is the surface a reader invokes by hand to SEE the report whatever it +// says, and dispatch_preflight_cli is the surface a caller invokes to have the line STOP on a +// refusal. Both route through dispatch_preflight_for_instance, so the printed report and the exit +// status cannot disagree. The refusal carries the whole report in its reason rather than a summary, +// because a caller that has just been told the preflight failed needs the axes, not the count. +// +// ONLY THE BY-HAND SPELLING DEFAULTS TO PRODUCTION, and the asymmetry is deliberate rather than an +// oversight in one of the two. dispatch_preflight_json takes no channel and names +// srv1_live_dashboard_instance directly, exactly as belt_run_once does and for the same reason: it +// is an inspection surface someone invokes against production, where the subject is what they came +// for. dispatch_preflight_cli is the SCHEDULED spelling -- the one a unit or another host could +// invoke -- so it reads the workdir channel and fail-closes on its absence rather than inheriting a +// default that would silently make a lab caller a production reader. fn dispatch_preflight_json_for_instance(instance: HostDashboardInstance) -> String { serialize_json(v: dispatch_preflight_json_value( @@ -283,7 +351,13 @@ fn dispatch_preflight_cli_in(spawn_workdir: String) -> ProcessExit { } } -data dispatch_preflight_workdir_channel_note: String = "THE PREFLIGHT READS THE SAME CHANNEL THE TICK READS, AND FAIL-CLOSES THE SAME WAY, because a preflight that defaults to production's tree answers about a host the caller did not ask about. belt_spawn_workdir_is_a_parameter_note records why the belt refuses a missing or empty GUNBC_BELT_SPAWN_WORKDIR rather than defaulting it; the identical argument applies with the identical force here, and one direction weaker: a preflight that silently probed production would report a green that a lab caller would read as being about their own instance. shell.Env.Get returns Present-but-empty for an unset variable wet, so both fold into one refusal." +// THE PREFLIGHT READS THE SAME CHANNEL THE TICK READS, AND FAIL-CLOSES THE SAME WAY, because a +// preflight that defaults to production's tree answers about a host the caller did not ask about. +// belt_spawn_workdir_is_a_parameter_note records why the belt refuses a missing or empty +// GUNBC_BELT_SPAWN_WORKDIR rather than defaulting it; the identical argument applies with the +// identical force here, and one direction weaker: a preflight that silently probed production would +// report a green that a lab caller would read as being about their own instance. shell.Env.Get +// returns Present-but-empty for an unset variable wet, so both fold into one refusal. fn dispatch_preflight_cli() -> ProcessExit { match shell.Env.Get(name: belt_spawn_workdir_env_var).value { diff --git a/dag/gunbc/dispatch_selection.dag b/dag/gunbc/dispatch_selection.dag index 0d537c418a6..e0e85b13c72 100644 --- a/dag/gunbc/dispatch_selection.dag +++ b/dag/gunbc/dispatch_selection.dag @@ -80,7 +80,12 @@ import gunbc.provider_account { admitted_single_session_binding, } -data dispatch_selection_note: String = "Slice A selection kernel (operator execution-profile directive 2026-08-01, addendum 2026-08-01). This carrier names the three-valued coproduct ExecutionProfile; the operator also names the same semantics ExecutionPreference — see dispatch_execution_preference_alias_note. Provider offers are a per-provider coproduct; volume and intricacy from NodeSizing never flow into model or effort selection. Every dispatch records requested_selection beside resolved_execution bound fields in DispatchExecutionReceipt so what was REQUESTED is never displayed as proof of what RAN." +// Slice A selection kernel (operator execution-profile directive 2026-08-01, addendum 2026-08-01). +// This carrier names the three-valued coproduct ExecutionProfile; the operator also names the same +// semantics ExecutionPreference — see dispatch_execution_preference_alias_note. Provider offers are +// a per-provider coproduct; volume and intricacy from NodeSizing never flow into model or effort +// selection. Every dispatch records requested_selection beside resolved_execution bound fields in +// DispatchExecutionReceipt so what was REQUESTED is never displayed as proof of what RAN. data dispatch_execution_preference_alias_note: String = "Naming authority: ExecutionProfile. Operator synonym: ExecutionPreference. Both denote the same closed coproduct Automatic | Deep | ExactOverride with identical semantics." @@ -88,7 +93,9 @@ data dispatch_deep_selection_heuristic_note: String = "Deep selection heuristic data dispatch_execution_receipt_bar_note: String = "Receipt bar: the system must never display what was REQUESTED as though it proved what RAN. DispatchExecutionReceipt binds provider_instance, credential_binding, model_identity, provider_control, provider_version, temporal_bounds, usage_observations, and attempt_identity under resolved_execution, separate from requested_selection." -data dispatch_lane_priority_note: String = "Operator priority (2026-08-01): this lane ranks Very High alongside the landing cockpit; Claude/Cursor integration (slices B/C) is explicitly more valuable than placement science. No randomized experiments, no universal scores." +// Operator priority (2026-08-01): this lane ranks Very High alongside the landing cockpit; +// Claude/Cursor integration (slices B/C) is explicitly more valuable than placement science. No +// randomized experiments, no universal scores. type ExecutionProfile = ProfileAutomatic @@ -248,7 +255,15 @@ type DispatchSelectionResult reason: NonEmptyStr } -data dispatch_codex_process_fingerprint_note: String = "SUPERSEDED AS EVIDENCE (operator ruling 2026-08-13): the literal below is NOT a process fingerprint receipt — two branches agreeing on a hand-authored string is agreement, not observation. Authoritative standing lives in gunbc.codex_provider_runtime_receipt CodexProviderRuntimeReceipt.process_fingerprint, derived from the bundled runtime converge installs; #8224 must consume that receipt, not this constant. Until wet tmux observation on the bundled binary lands, process_fingerprint is CodexProcessFingerprintUnobserved and dispatch must refuse rather than substitute this literal. Historical context (srv1 npm-shim, 2026-07-27): authenticated /usr/bin/codex (0.133.0) reported pane_current_command node during exec — that observation applied to the npm launcher, not the bundled-native 0.146.0 tree this PR converges." +// SUPERSEDED AS EVIDENCE (operator ruling 2026-08-13): the literal below is NOT a process +// fingerprint receipt — two branches agreeing on a hand-authored string is agreement, not +// observation. Authoritative standing lives in gunbc.codex_provider_runtime_receipt +// CodexProviderRuntimeReceipt.process_fingerprint, derived from the bundled runtime converge +// installs; #8224 must consume that receipt, not this constant. Until wet tmux observation on the +// bundled binary lands, process_fingerprint is CodexProcessFingerprintUnobserved and dispatch must +// refuse rather than substitute this literal. Historical context (srv1 npm-shim, 2026-07-27): +// authenticated /usr/bin/codex (0.133.0) reported pane_current_command node during exec — that +// observation applied to the npm launcher, not the bundled-native 0.146.0 tree this PR converges. data dispatch_codex_process_fingerprint: NonEmptyStr = "node" as NonEmptyStr data dispatch_codex_process_fingerprint_unobserved_selection_refusal: NonEmptyStr = "Codex dispatch selection refused: bundled-native process fingerprint unobserved; derive from CodexProviderRuntimeReceipt after wet tmux observation — never from dispatch_codex_process_fingerprint literal" as NonEmptyStr @@ -258,9 +273,18 @@ data dispatch_cursor_process_fingerprint: NonEmptyStr = "cursor-agent" as NonEmp data dispatch_codex_default_model: NonEmptyStr = "account-default" as NonEmptyStr data dispatch_claude_default_model: NonEmptyStr = "account-default" as NonEmptyStr -data dispatch_limit_draw_note: String = "LimitDispatchDrawAutomatic fail-closes on any observed ProviderNativeLimitBucket exhaustion because model-to-limitId join is unobserved (extdeps.llm.codex_app_server codex_model_to_limit_bucket_gap_note). ProfileExactOverride SelectCodex may carry operator_asserted_native_limit_bucket — a human-asserted native_key attribution recorded on the request, NOT a cited join and NOT inferred from model or limitName. DISSOLVES ON: cited model-to-bucket join lands; automatic draw becomes decidable without operator assertion." +// LimitDispatchDrawAutomatic fail-closes on any observed ProviderNativeLimitBucket exhaustion +// because model-to-limitId join is unobserved (extdeps.llm.codex_app_server +// codex_model_to_limit_bucket_gap_note). ProfileExactOverride SelectCodex may carry +// operator_asserted_native_limit_bucket — a human-asserted native_key attribution recorded on the +// request, NOT a cited join and NOT inferred from model or limitName. DISSOLVES ON: cited +// model-to-bucket join lands; automatic draw becomes decidable without operator assertion. -data codex_operator_asserted_native_limit_bucket_note: String = "Operator assertion path (deep-raven-684, 2026-08-06): ProfileExactOverride plus Present operator_asserted_native_limit_bucket names which native limit bucket the operator asserts this dispatch draws on. The system records the assertion; it does not observe or infer a model-to-bucket join. Absent assertion on override, or any non-override SelectCodex, uses LimitDispatchDrawAutomatic." +// Operator assertion path (deep-raven-684, 2026-08-06): ProfileExactOverride plus Present +// operator_asserted_native_limit_bucket names which native limit bucket the operator asserts this +// dispatch draws on. The system records the assertion; it does not observe or infer a +// model-to-bucket join. Absent assertion on override, or any non-override SelectCodex, uses +// LimitDispatchDrawAutomatic. fn codex_limit_dispatch_draw_for_selection( profile: ExecutionProfile, @@ -355,7 +379,12 @@ fn automatic_refusal_reason_for_inventory( data dispatch_claude_default_workflow_mode: NonEmptyStr = "default" as NonEmptyStr data dispatch_claude_default_credential_root: FilePath = "~/.claude" as FilePath -data claude_offer_inventory_wiring_note: String = "PRODUCTION WIRING DEFERRED (operator ruling msg_3b291a6a / msg_0ec68b71): OfferClaude is omitted from provider_inventory_for_instance until the WorkerTurn PR. resolve_provider_offer still delegates OfferClaude to resolve_claude_offer when a fixture or future inventory row includes it — production deferral is inventory omission, not a blanket resolver refusal. Fixture controls (fixture_inventory_codex_then_claude, resolve_dispatch_selection) prove a future eligible Claude standing is selectable." +// PRODUCTION WIRING DEFERRED (operator ruling msg_3b291a6a / msg_0ec68b71): OfferClaude is omitted +// from provider_inventory_for_instance until the WorkerTurn PR. resolve_provider_offer still +// delegates OfferClaude to resolve_claude_offer when a fixture or future inventory row includes it +// — production deferral is inventory omission, not a blanket resolver refusal. Fixture controls +// (fixture_inventory_codex_then_claude, resolve_dispatch_selection) prove a future eligible Claude +// standing is selectable. data dispatch_selection_temporal_unobserved_reason: NonEmptyStr = "selection time; temporal bounds assigned at spawn observation" as NonEmptyStr data dispatch_selection_usage_unobserved_reason: NonEmptyStr = "selection time; usage observations assigned during provider runtime" as NonEmptyStr @@ -363,13 +392,24 @@ data dispatch_selection_attempt_unassigned_reason: NonEmptyStr = "selection time data claude_offer_not_wired_reason: NonEmptyStr = "Claude offer is not wired on this host inventory" as NonEmptyStr -data claude_state_root_denotes_profile_not_authority_note: String = "CARRIER-SHAPE (eager-raven-845 live receipt, 2026-08-06): ClaudeStateRootRef { root: FilePath } denotes the credential profile tree on disk, not the authority to act. A faithful file-tree copy reproduces subscription metadata (claude_max, stripe_subscription) while tokenSource remains none. Slice C credential binding must derive authentication from token state; assuming file-residence silently mis-denotes. See gunbc.provider_standing claude_profile_present_token_absent_live_fixture_note." +// CARRIER-SHAPE (eager-raven-845 live receipt, 2026-08-06): ClaudeStateRootRef { root: FilePath } +// denotes the credential profile tree on disk, not the authority to act. A faithful file-tree copy +// reproduces subscription metadata (claude_max, stripe_subscription) while tokenSource remains +// none. Slice C credential binding must derive authentication from token state; assuming +// file-residence silently mis-denotes. See gunbc.provider_standing +// claude_profile_present_token_absent_live_fixture_note. data dispatch_selection_standing_ineligible_reason: NonEmptyStr = "no provider offer is selection-eligible for the requested profile" as NonEmptyStr data dispatch_selection_shape_mismatch_reason: NonEmptyStr = "no provider offer matches the requested selection shape" as NonEmptyStr -data dispatch_production_standing_unobserved_refusal_note: String = "Production Codex inventory rows carry declared provider_standing_inventory_unobserved standing and CodexProcessFingerprintUnobserved at selection time; wet probe-derived standing via provider_standing_from_live_codex_probes is reserved for dashboard readiness, not hermetic inventory build. Entitlement and account-limit axes stay honestly unobserved at this bridge; selection refuses when those axes block eligibility. RecordedFact standing observations (nimble-owl-833) supersede probe-derived bundles. Spawn-shape witnesses use fixture_inventory_* builders." +// Production Codex inventory rows carry declared provider_standing_inventory_unobserved standing +// and CodexProcessFingerprintUnobserved at selection time; wet probe-derived standing via +// provider_standing_from_live_codex_probes is reserved for dashboard readiness, not hermetic +// inventory build. Entitlement and account-limit axes stay honestly unobserved at this bridge; +// selection refuses when those axes block eligibility. RecordedFact standing observations +// (nimble-owl-833) supersede probe-derived bundles. Spawn-shape witnesses use fixture_inventory_* +// builders. fn dispatch_temporal_unobserved() -> DispatchTemporalBounds { DispatchTemporalUnobserved { reason: dispatch_selection_temporal_unobserved_reason } diff --git a/dag/gunbc/doc_graph_roots.dag b/dag/gunbc/doc_graph_roots.dag index bbd55574eb7..8dbd2d64ee6 100644 --- a/dag/gunbc/doc_graph_roots.dag +++ b/dag/gunbc/doc_graph_roots.dag @@ -25,7 +25,35 @@ import std.keyed_roster { keyed_roster_build, } -data doc_graph_roots_note: String = "The single authority for doc-graph roots. Registered plans derive theirs (a PlanArtifact registration IS the document binding); hand-authored docs declare typed HandAuthoredDocBind rows — symbolic identity (home + slug, the same key space as Plan.slug; the file path is a derived storage projection, never stored), the .dag work the doc binds to, and its PlanRetirement. EVERY ROW RETIRES: the condition names the change that deletes the row. It was not always so -- three cited-symbol-resolution discriminating controls sat here carrying PlanHasNoRetirement, and a slug-matched predicate subtracted them again from document production. They were never documents: they are machine discriminators for v2.lens.cited_symbol_resolution, and after the 2026-08-24 bankruptcy deleted docs/probes they were document binds naming no document. They moved to that lens as a typed control carrier on 2026-08-25 (operator ruling), so the exemption, the predicate and the non-retiring arm all left with them. A bind that does not retire now has no home here.\n\nSIXTEEN PROBE-DOC BINDS WERE DELETED THE SAME DAY, AND THE HOME AXIS DELIBERATELY SURVIVED THEM. Every remaining ProbeDoc row named a file under docs/probes that the 2026-08-24 bankruptcy deleted -- all sixteen, with no survivor -- and a bind whose document does not exist is not a root: build_doc_graph_report admits an extra root only when it is present in the doc universe and counts what it admitted, so those rows made doc_graph_registered_plan_roots_all_admitted RED, invisible only because that witness declares ReadsLiveTree and the floor declines it before the fold. Deleting the DocHome coproduct along with them was drafted and then REVERTED, because the population is not closed the way an empty variant suggests: the bankruptcy principle is that UNCONSUMED TRANSCRIPTION disappears while CONSUMED EVIDENCE stays attached to its consumer, and the directory a file sits in is not the authority on which it is -- the exact consumer relation is. At least one probe document is consumed evidence and IS NOW RE-BOUND (leading_minus_continuation_silently_truncates_2026-08-23, the parser-defect receipt extdeps.cpu_attachment.lotes_azifa072 cites). Deleting the axis and restoring it one change later would have been work authored to be thrown away, and would have put a permanence claim in this note that was false before the week ended. No prose 'bind:' scan remains: an unbound doc is an orphan, loudly; a hand-authored slug colliding with a registered plan slug is a red (the row outlived its migration)." +// The single authority for doc-graph roots. Registered plans derive theirs (a PlanArtifact +// registration IS the document binding); hand-authored docs declare typed HandAuthoredDocBind rows +// — symbolic identity (home + slug, the same key space as Plan.slug; the file path is a derived +// storage projection, never stored), the .dag work the doc binds to, and its PlanRetirement. EVERY +// ROW RETIRES: the condition names the change that deletes the row. It was not always so -- three +// cited-symbol-resolution discriminating controls sat here carrying PlanHasNoRetirement, and a +// slug-matched predicate subtracted them again from document production. They were never documents: +// they are machine discriminators for v2.lens.cited_symbol_resolution, and after the 2026-08-24 +// bankruptcy deleted docs/probes they were document binds naming no document. They moved to that +// lens as a typed control carrier on 2026-08-25 (operator ruling), so the exemption, the predicate +// and the non-retiring arm all left with them. A bind that does not retire now has no home here. +// +// SIXTEEN PROBE-DOC BINDS WERE DELETED THE SAME DAY, AND THE HOME AXIS DELIBERATELY SURVIVED THEM. +// Every remaining ProbeDoc row named a file under docs/probes that the 2026-08-24 bankruptcy +// deleted -- all sixteen, with no survivor -- and a bind whose document does not exist is not a +// root: build_doc_graph_report admits an extra root only when it is present in the doc universe and +// counts what it admitted, so those rows made doc_graph_registered_plan_roots_all_admitted RED, +// invisible only because that witness declares ReadsLiveTree and the floor declines it before the +// fold. Deleting the DocHome coproduct along with them was drafted and then REVERTED, because the +// population is not closed the way an empty variant suggests: the bankruptcy principle is that +// UNCONSUMED TRANSCRIPTION disappears while CONSUMED EVIDENCE stays attached to its consumer, and +// the directory a file sits in is not the authority on which it is -- the exact consumer relation +// is. At least one probe document is consumed evidence and IS NOW RE-BOUND +// (leading_minus_continuation_silently_truncates_2026-08-23, the parser-defect receipt +// extdeps.cpu_attachment.lotes_azifa072 cites). Deleting the axis and restoring it one change later +// would have been work authored to be thrown away, and would have put a permanence claim in this +// note that was false before the week ended. No prose 'bind:' scan remains: an unbound doc is an +// orphan, loudly; a hand-authored slug colliding with a registered plan slug is a red (the row +// outlived its migration). fn plan_doc_graph_roots() -> List { map(all_plans, p => plan_path(p: p)) @@ -63,7 +91,53 @@ fn hand_authored_doc_declaration_refs() -> List { list_flat_map(xs: hand_authored_doc_binds(), f: fn(b) { hand_authored_bind_works(b: b) }) } -data outside_index_works_note: String = "TWO DOCUMENTS BIND TO THE WITNESS THAT IS THEIR EVIDENCE, AND NO RESOLVER CAN REACH IT. IT WAS FOUR UNTIL 2026-08-25, AND THE OTHER TWO WERE DELETED BY THE POPULATION THEY EXEMPTED GOING AWAY: both named the claude paired-entitlement probe's witnesses, and the only bind carrying those references was the ProbeDoc row for a document the measurement bankruptcy deleted. An exemption whose subject no longer exists is not harmless bookkeeping -- this roster is a literal list beside the bind population rather than a projection of it, so an orphaned row breaks the partition identity that proves no authored work falls out of every bucket, and the fast witness cited_symbol_production_population_loses_nothing went RED by exactly two when the bind was deleted without them. That red is the mechanism working: the roster and the binds are two representations of one population, and the substrate refused to let them drift apart silently. v2.std.decl_index decl_facts does not index declarations in test .dag files — deliberately, and gunbc.ci_layer_roots records that exclusion in the row explaining why a second walk must not repeat it — so a reference into a witness module refuses as DeclarationRefDeclarationAbsent no matter how correct it is. These two references are correct: the declarations exist, and the citation is the honest one to make, because the witness IS what the document binds to.\n\nMEASURED, WITH A POSITIVE CONTROL, so this is a cause and not an assumption (2026-08-21, BuildBuddy). Both files report indexed_in_decl_facts=false and module_declared=true, while src/v2/lens/cited_symbol_resolution.dag and dag/gunbc/doc_graph_roots.dag — the controls — report true and true. The src/v2 root is therefore walked; the exclusion is on the TEST file, not the root, which is why the src/v2/test row belongs to the same class as the three under dag/test/claim rather than to a second one.\n\nWHAT THIS IS NOT: an exemption list. The rows carry std.decl_ref CitationIndexCoverage — one axis, the same one gunbc.lambda_argument_typing_gate and gunbc.list_membership_operation_gap carry, not a second shape — and the population is COUNTED rather than silently subtracted. The dodge this admits in principle, marking a resolvable reference outside-index to escape a red, is refused by execution rather than by review: v2.lens.cited_symbol_resolution's report REFUSES a row marked outside-index that actually resolves, and refuses a count disagreement, and that report REFUSES BY EXECUTION rather than by review whenever it is run. WHAT IT IS NOT RUN BY, corrected 2026-08-25: nothing on a push. This sentence read \"runs on every CI run as claim_executor --required-cited-symbol, its own required check in the cited-symbol job of .github/workflows/witnesses.yml\", and that was true when written and false from 2026-08-23, when the operator directive deleted the cited-symbol job. The flag was DELETED with the Rust census behind it, so this lens's report cannot be run at all, and a marking that lies no longer fails the run that made it because no run is made. THAT IS NOT THE CURRENT STATE OF THE RULE, and reading it as one is the error this row keeps correcting in itself: as of 2026-08-25 v1_compiler.declaration_index resolves every authored DeclarationRef at INGESTION, inside the parse phase of --required-ci, so DESIGN RETIRES the drop and the rung is mechanically preventable again. It bears directly on THESE rows rather than merely nearby: the new index INDEXES TEST MODULES, which is exactly the exclusion that forced the outside-index disposition, so the class these rows exist to carry is dissolving at its cause rather than being marked around. The rows stay only until that cut lands.\n\nDISSOLVE-ON: decl_facts covers test modules, or a witness-declaration index becomes reachable from the resolver — at which point these rows delete and both references join the ordinary citable population." +// TWO DOCUMENTS BIND TO THE WITNESS THAT IS THEIR EVIDENCE, AND NO RESOLVER CAN REACH IT. IT WAS +// FOUR UNTIL 2026-08-25, AND THE OTHER TWO WERE DELETED BY THE POPULATION THEY EXEMPTED GOING AWAY: +// both named the claude paired-entitlement probe's witnesses, and the only bind carrying those +// references was the ProbeDoc row for a document the measurement bankruptcy deleted. An exemption +// whose subject no longer exists is not harmless bookkeeping -- this roster is a literal list +// beside the bind population rather than a projection of it, so an orphaned row breaks the +// partition identity that proves no authored work falls out of every bucket, and the fast witness +// cited_symbol_production_population_loses_nothing went RED by exactly two when the bind was +// deleted without them. That red is the mechanism working: the roster and the binds are two +// representations of one population, and the substrate refused to let them drift apart silently. +// v2.std.decl_index decl_facts does not index declarations in test .dag files — deliberately, and +// gunbc.ci_layer_roots records that exclusion in the row explaining why a second walk must not +// repeat it — so a reference into a witness module refuses as DeclarationRefDeclarationAbsent no +// matter how correct it is. These two references are correct: the declarations exist, and the +// citation is the honest one to make, because the witness IS what the document binds to. +// +// MEASURED, WITH A POSITIVE CONTROL, so this is a cause and not an assumption (2026-08-21, +// BuildBuddy). Both files report indexed_in_decl_facts=false and module_declared=true, while +// src/v2/lens/cited_symbol_resolution.dag and dag/gunbc/doc_graph_roots.dag — the controls — report +// true and true. The src/v2 root is therefore walked; the exclusion is on the TEST file, not the +// root, which is why the src/v2/test row belongs to the same class as the three under +// dag/test/claim rather than to a second one. +// +// WHAT THIS IS NOT: an exemption list. The rows carry std.decl_ref CitationIndexCoverage — one +// axis, the same one gunbc.lambda_argument_typing_gate and gunbc.list_membership_operation_gap +// carry, not a second shape — and the population is COUNTED rather than silently subtracted. The +// dodge this admits in principle, marking a resolvable reference outside-index to escape a red, is +// refused by execution rather than by review: v2.lens.cited_symbol_resolution's report REFUSES a +// row marked outside-index that actually resolves, and refuses a count disagreement, and that +// report REFUSES BY EXECUTION rather than by review whenever it is run. WHAT IT IS NOT RUN BY, +// corrected 2026-08-25: nothing on a push. This sentence read "runs on every CI run as +// claim_executor --required-cited-symbol, its own required check in the cited-symbol job of +// .github/workflows/witnesses.yml", and that was true when written and false from 2026-08-23, when +// the operator directive deleted the cited-symbol job. The flag was DELETED with the Rust census +// behind it, so this lens's report cannot be run at all, and a marking that lies no longer fails +// the run that made it because no run is made. THAT IS NOT THE CURRENT STATE OF THE RULE, and +// reading it as one is the error this row keeps correcting in itself: as of 2026-08-25 +// v1_compiler.declaration_index resolves every authored DeclarationRef at INGESTION, inside the +// parse phase of --required-ci, so DESIGN RETIRES the drop and the rung is mechanically preventable +// again. It bears directly on THESE rows rather than merely nearby: the new index INDEXES TEST +// MODULES, which is exactly the exclusion that forced the outside-index disposition, so the class +// these rows exist to carry is dissolving at its cause rather than being marked around. The rows +// stay only until that cut lands. +// +// DISSOLVE-ON: decl_facts covers test modules, or a witness-declaration index becomes reachable +// from the resolver — at which point these rows delete and both references join the ordinary +// citable population. type OutsideIndexWork { reference: DeclarationRef, @@ -136,7 +210,13 @@ fn hand_authored_doc_path(b: HandAuthoredDocBind) -> String { } } -data body_lowering_ref_home_note: String = "THE THREE BODY-LOWERING SYSTEM REFERENCES LIVE HERE, ONCE, BECAUSE TWO DOCUMENTS NAME THEM. The generated design document names them in its body-lowering paragraph and the hand-authored general-body-producer plan names them in its discharged-prerequisite paragraph, so authoring one DeclarationRef literal in each place would be the dual representation this program exists to remove -- and the copies would then rot independently, which is precisely the failure that produced the bind below. The home is this module rather than gunbc.documentary_refs because the union projection imports in that direction; reversing it would be a cycle." +// THE THREE BODY-LOWERING SYSTEM REFERENCES LIVE HERE, ONCE, BECAUSE TWO DOCUMENTS NAME THEM. The +// generated design document names them in its body-lowering paragraph and the hand-authored +// general-body-producer plan names them in its discharged-prerequisite paragraph, so authoring one +// DeclarationRef literal in each place would be the dual representation this program exists to +// remove -- and the copies would then rot independently, which is precisely the failure that +// produced the bind below. The home is this module rather than gunbc.documentary_refs because the +// union projection imports in that direction; reversing it would be a cycle. data body_lowering_symbol_index_ref: DeclarationRef = DeclarationRef { module_path: "v2.std.symbol_index", decl_name: "SymbolIndex", field: WholeDeclaration } @@ -144,7 +224,14 @@ data body_lowering_lexical_lookup_ref: DeclarationRef = DeclarationRef { module_ data body_lowering_production_consumer_ref: DeclarationRef = DeclarationRef { module_path: "v2.compiler.resolve", decl_name: "lookup_symbol_index_atom_identity", field: WholeDeclaration } -data mock_corpus_prefilter_control_note: String = "LIVE CONTROL FOR THE MOCK-CORPUS DECLARER OBSERVATION, and the reason this sentence names the type it does. This module imports v2 modules and sits under dag/, so it is exactly the shape that broke the floor when whole-tree published-mock declarer selection was decided by a substring scan: a documentary row naming PublishedMockCase made this file read as a corpus declarer, its closure could not resolve under the dag-only roots that precompute uses, and the run refused before any witness executed. The declarer decision is now the exact type-annotation check. This sentence therefore MENTIONS PublishedMockCase without declaring one, so if the substring shortcut is ever restored the floor reds here first." +// LIVE CONTROL FOR THE MOCK-CORPUS DECLARER OBSERVATION, and the reason this sentence names the +// type it does. This module imports v2 modules and sits under dag/, so it is exactly the shape that +// broke the floor when whole-tree published-mock declarer selection was decided by a substring +// scan: a documentary row naming PublishedMockCase made this file read as a corpus declarer, its +// closure could not resolve under the dag-only roots that precompute uses, and the run refused +// before any witness executed. The declarer decision is now the exact type-annotation check. This +// sentence therefore MENTIONS PublishedMockCase without declaring one, so if the substring shortcut +// is ever restored the floor reds here first. data hand_authored_doc_bind_incomings: List = [ HandAuthoredDocBind { diff --git a/dag/gunbc/documentary_refs.dag b/dag/gunbc/documentary_refs.dag index e6eea31a043..99f156d1881 100644 --- a/dag/gunbc/documentary_refs.dag +++ b/dag/gunbc/documentary_refs.dag @@ -4,9 +4,36 @@ import std.types { String } import std.decl_ref { DeclarationRef, decl_ref } import gunbc.doc_graph_roots { hand_authored_doc_declaration_refs } -data documentary_refs_note: String = "THE SYSTEMS A GENERATED DOCUMENT NAMES, CARRIED AS TYPED REFERENCES RATHER THAN SPELLED IN PROSE. gunbc.doc_graph_roots already does exactly this for HAND-AUTHORED documents: HandAuthoredDocBind carries primary_work plus additional_works as DeclarationRef rows, and v2.lens.cited_symbol_resolution resolves that population against live declaration facts and refuses a missing, ambiguous, or field-absent reference. Generated documents had no equivalent, so a system named inside gunbc.design_document existed only as characters inside a string literal -- unreachable by that lens and by every other structural reader.\n\nTHE MEASURED CONSEQUENCE, which is why this module exists rather than a wider one. The body-lowering paragraph named v2.std.symbol_index SymbolIndex as an open prerequisite. The prerequisite was discharged; the declaration existed the whole time; the sentence stayed. Nothing was wrong with the declaration, so no declaration-level check could fire, and the sentence was not a reference to anything, so no reference-level check could either. Four copies of that one claim were live across DESIGN.md and two plan documents.\n\nWHAT THIS DOES AND DOES NOT ESTABLISH, stated because the distinction is the whole scope of this change. A resolved reference proves the named system EXISTS and is uniquely identified -- so a rename, a deletion, or a fork into two same-named declarations reds through the required cited-symbol path without anyone editing the document. It does NOT prove a production relationship between the named systems: that a consumer actually invokes an implementation on a live path is a different claim, and a reference population cannot carry it. Where a document needs that stronger claim it cites an executing witness for that exact relationship. Reference resolution is the honest rung here, and claiming the stronger one from the weaker mechanism is what the earlier draft of this work did." - -data generated_document_ref_rows_note: String = "ONE ROW PER SYSTEM THE GENERATED DESIGN DOCUMENT NAMES IN A MIGRATED PARAGRAPH, and the rendered bytes come from these rows rather than beside them. A row typed here while the paragraph independently spells the same name would be two representations of one fact, which is the shape this program exists to remove -- so the paragraph builds its symbol text by rendering the row, and a rename that moves the row moves the document." +// THE SYSTEMS A GENERATED DOCUMENT NAMES, CARRIED AS TYPED REFERENCES RATHER THAN SPELLED IN PROSE. +// gunbc.doc_graph_roots already does exactly this for HAND-AUTHORED documents: HandAuthoredDocBind +// carries primary_work plus additional_works as DeclarationRef rows, and +// v2.lens.cited_symbol_resolution resolves that population against live declaration facts and +// refuses a missing, ambiguous, or field-absent reference. Generated documents had no equivalent, +// so a system named inside gunbc.design_document existed only as characters inside a string literal +// -- unreachable by that lens and by every other structural reader. +// +// THE MEASURED CONSEQUENCE, which is why this module exists rather than a wider one. The +// body-lowering paragraph named v2.std.symbol_index SymbolIndex as an open prerequisite. The +// prerequisite was discharged; the declaration existed the whole time; the sentence stayed. Nothing +// was wrong with the declaration, so no declaration-level check could fire, and the sentence was +// not a reference to anything, so no reference-level check could either. Four copies of that one +// claim were live across DESIGN.md and two plan documents. +// +// WHAT THIS DOES AND DOES NOT ESTABLISH, stated because the distinction is the whole scope of this +// change. A resolved reference proves the named system EXISTS and is uniquely identified -- so a +// rename, a deletion, or a fork into two same-named declarations reds through the required +// cited-symbol path without anyone editing the document. It does NOT prove a production +// relationship between the named systems: that a consumer actually invokes an implementation on a +// live path is a different claim, and a reference population cannot carry it. Where a document +// needs that stronger claim it cites an executing witness for that exact relationship. Reference +// resolution is the honest rung here, and claiming the stronger one from the weaker mechanism is +// what the earlier draft of this work did. + +// ONE ROW PER SYSTEM THE GENERATED DESIGN DOCUMENT NAMES IN A MIGRATED PARAGRAPH, and the rendered +// bytes come from these rows rather than beside them. A row typed here while the paragraph +// independently spells the same name would be two representations of one fact, which is the shape +// this program exists to remove -- so the paragraph builds its symbol text by rendering the row, +// and a rename that moves the row moves the document. data v1_maintenance_standing_ref: DeclarationRef = decl_ref(module_path: "gunbc.v1_maintenance_standing", decl_name: "v1_seed_standing") @@ -18,7 +45,12 @@ fn generated_document_declaration_refs() -> List { [v1_maintenance_standing_ref] } -data documentary_population_union_note: String = "ONE POPULATION, TWO SOURCES, AND THE UNION IS THE POINT. The cited-symbol lens already had a denominator; it counted hand-authored binds only, so a generated document could name a system that no longer existed and stay green while a hand-authored document naming the same system reddened. Widening the denominator rather than adding a second resolution algorithm keeps v2.std.decl_ref_resolution the single authority for what resolving a reference means -- a second algorithm agreeing today is a fork that disagrees later." +// ONE POPULATION, TWO SOURCES, AND THE UNION IS THE POINT. The cited-symbol lens already had a +// denominator; it counted hand-authored binds only, so a generated document could name a system +// that no longer existed and stay green while a hand-authored document naming the same system +// reddened. Widening the denominator rather than adding a second resolution algorithm keeps +// v2.std.decl_ref_resolution the single authority for what resolving a reference means -- a second +// algorithm agreeing today is a fork that disagrees later. fn all_documentary_declaration_refs() -> List { concat(hand_authored_doc_declaration_refs(), generated_document_declaration_refs()) diff --git a/dag/gunbc/econ/acquisition.dag b/dag/gunbc/econ/acquisition.dag index 21852e43e2a..f22b0a47cb9 100644 --- a/dag/gunbc/econ/acquisition.dag +++ b/dag/gunbc/econ/acquisition.dag @@ -8,7 +8,18 @@ import std.types { List, NonEmptyStr, list_length } // Generic acquisition funnel mechanism, sibling to gunbc.econ.scm_serving_model: stages are PARAMETERS (declared labels on scenario rows, never baked into the mechanism), conversion rates are exact Nat fractions (never floats), and every underivable input refuses with a typed, located cause. This module is mechanism only -- instantiations with actual assumptions carry their own citations and live elsewhere. Vendor stage vocabularies to instantiate stage labels against are cited in extdeps.crm (Salesforce opportunity stages, HubSpot lifecycle stages); channel pricing interfaces for entry unit costs are cited in extdeps.advertising. type ConversionRate = FieldOfFractions -data conversion_rate_note: String = "A conversion rate is an exact fraction and CONSUMES the std.algebra FieldOfFractions carrier at Nat (non-negative fractions): num is the converted count, denom is the cohort count -- 1 of 40 stays 1/40, never 0.025. Composition multiplies num and denom; nothing in the mechanism divides until a consumer explicitly asks for the floor projection, so the exact fraction is the authority and the floor is a declared lossy view. Fixed-scale dimensionless carriers (Percent, BasisPoint) do not absorb this concept -- projecting onto any fixed scale loses the exactness (1 of 3 has no exact basis-point image). The at-most-unity semantics (num at most denom) is a conversion-rate FACT enforced by typed refusal at composition, not a property of the generic fraction carrier. An earlier revision of this note claimed no std exact-rational carrier existed on main and minted a local pair; refuted against the tree by review 45963 (std.algebra FieldOfFractions carries the grounded num/denom body and std.rational.Rational builds on it), so the local pair was dissolved into the authority." +// A conversion rate is an exact fraction and CONSUMES the std.algebra FieldOfFractions carrier at +// Nat (non-negative fractions): num is the converted count, denom is the cohort count -- 1 of 40 +// stays 1/40, never 0.025. Composition multiplies num and denom; nothing in the mechanism divides +// until a consumer explicitly asks for the floor projection, so the exact fraction is the authority +// and the floor is a declared lossy view. Fixed-scale dimensionless carriers (Percent, BasisPoint) +// do not absorb this concept -- projecting onto any fixed scale loses the exactness (1 of 3 has no +// exact basis-point image). The at-most-unity semantics (num at most denom) is a conversion-rate +// FACT enforced by typed refusal at composition, not a property of the generic fraction carrier. An +// earlier revision of this note claimed no std exact-rational carrier existed on main and minted a +// local pair; refuted against the tree by review 45963 (std.algebra FieldOfFractions carries the +// grounded num/denom body and std.rational.Rational builds on it), so the local pair was dissolved +// into the authority. type AcquisitionStageStep { stage_label: NonEmptyStr @@ -40,7 +51,16 @@ type AcquisitionCostForecast { entry_unit_cost: MoneyOnce } -data acquisition_forecast_distinctness_note: String = "AcquisitionCostForecast is a FORECAST carrier: a derived consequence of declared assumptions. It shares no type with any observation or Measured row anywhere in the tree, so a forecast cannot be passed where an actual is required -- the distinction is structural, not a naming convention. Money stays on the std.measure MoneyOnce carrier (currency and micro denomination live there, never as bare scalars beside it), and the cost per outcome is DERIVED, never stored: the exact value is entry_unit_cost scaled by denom over num on the ConversionRate fraction. Every forecast the derivation PRODUCES carries a nonzero num (a zero numerator refuses before composition), but the record itself remains publicly constructible with num zero, so the floor projection below does not trust its caller: it is total by typed refusal, returning none on a zero num rather than dividing." +// AcquisitionCostForecast is a FORECAST carrier: a derived consequence of declared assumptions. It +// shares no type with any observation or Measured row anywhere in the tree, so a forecast cannot be +// passed where an actual is required -- the distinction is structural, not a naming convention. +// Money stays on the std.measure MoneyOnce carrier (currency and micro denomination live there, +// never as bare scalars beside it), and the cost per outcome is DERIVED, never stored: the exact +// value is entry_unit_cost scaled by denom over num on the ConversionRate fraction. Every forecast +// the derivation PRODUCES carries a nonzero num (a zero numerator refuses before composition), but +// the record itself remains publicly constructible with num zero, so the floor projection below +// does not trust its caller: it is total by typed refusal, returning none on a zero num rather than +// dividing. type AcquisitionForecastResult = AcquisitionForecastDerived { forecast: AcquisitionCostForecast } diff --git a/dag/gunbc/econ/free_tier_serving.dag b/dag/gunbc/econ/free_tier_serving.dag index e6afbd3a8bc..f20593383e9 100644 --- a/dag/gunbc/econ/free_tier_serving.dag +++ b/dag/gunbc/econ/free_tier_serving.dag @@ -16,9 +16,21 @@ import std.measure { } import std.nat { Nat, nat_max } -data free_tier_serving_derive_not_store_note: String = "Both reads here are DERIVED at read time from cited rows (DESIGN section 2): the bottom-up floor prices a declared per-user resource envelope against a cited infrastructure price row, and the top-down glean divides a filed cost-of-revenue fact by a filed registered-user floor. Nothing about serving cost is stored as a fact of its own." +// Both reads here are DERIVED at read time from cited rows (DESIGN section 2): the bottom-up floor +// prices a declared per-user resource envelope against a cited infrastructure price row, and the +// top-down glean divides a filed cost-of-revenue fact by a filed registered-user floor. Nothing +// about serving cost is stored as a fact of its own. -data free_tier_floor_semantics_note: String = "The bottom-up result is deliberately a FLOOR: box rent for storage capacity (times replica count), with egress admitted against a DECLARED per-box monthly allowance. It excludes operations labor, offsite backup, support, and abuse handling. The egress allowance is an assumption rather than a row fact because the cited Hetzner rows do not yet carry a traffic-allowance field; dissolve-on: citing Hetzner traffic terms into extdeps.pricing.hetzner_dedicated, at which point the allowance argument collapses onto the row. Compute is not a constraint at free-SCM envelopes (git-shaped operations are millisecond-scale against 12-32 hardware threads per box); the moment a CI product is bundled, metered compute becomes the dominant axis -- the cited receipt is GitLab's own free-tier fence (400 compute minutes per month, and a pricing-page FAQ attributing card-verification to cryptomining abuse of free minutes)." +// The bottom-up result is deliberately a FLOOR: box rent for storage capacity (times replica +// count), with egress admitted against a DECLARED per-box monthly allowance. It excludes operations +// labor, offsite backup, support, and abuse handling. The egress allowance is an assumption, not a +// row fact, because the cited Hetzner rows do not yet carry a traffic-allowance field; dissolve-on: +// citing Hetzner traffic terms into extdeps.pricing.hetzner_dedicated, at which point the allowance +// argument collapses onto the row. Compute is not a constraint at free-SCM envelopes (git-shaped +// operations are millisecond-scale against 12-32 hardware threads per box); once a CI product is +// bundled, metered compute becomes the dominant axis -- the cited receipt is GitLab's own free-tier +// fence (400 compute minutes per month, and a pricing-page FAQ attributing card-verification to +// cryptomining abuse of free minutes). type FreeTierUserEnvelope { storage_bytes_per_user: ByteSize @@ -93,7 +105,11 @@ fn derive_free_tier_serving_floor( } } -// Top-down glean: annual cost of revenue divided by the filed registered-user FLOOR ('more than N million registered users', Item 1) is an UPPER BOUND on average serving cost per registered user -- actual users >= floor, so cost/floor >= cost/actual -- and the average itself is inflated by enterprise SaaS compute and support personnel inside cost of revenue. The marginal free git user sits far below it. +// Top-down glean: annual cost of revenue divided by the filed registered-user FLOOR ('more than N +// million registered users', Item 1) is an UPPER BOUND on average serving cost per registered user +// -- actual users >= floor, so cost/floor >= cost/actual -- and the average itself is inflated by +// enterprise SaaS compute and support personnel inside cost of revenue. The marginal free git user +// sits far below it. fn average_serving_cost_per_registered_user_year_micros_upper_bound( annual_cost_of_revenue: FiledMonetaryFact, registered_users: RegisteredUserCountRow diff --git a/dag/gunbc/econ/knowable_wedge.dag b/dag/gunbc/econ/knowable_wedge.dag index 86579b1e97d..558ae328604 100644 --- a/dag/gunbc/econ/knowable_wedge.dag +++ b/dag/gunbc/econ/knowable_wedge.dag @@ -25,10 +25,14 @@ import std.measure { } import std.nat { Nat } -data knowable_wedge_note: String = "Knowable wedge = publicly cited umbrella price minus a cost floor derivable from cited hardware rows, given explicitly declared utilization/amortization/exchange-rate assumptions. Opaque inputs refuse — never estimate around Unknown." +// Knowable wedge = publicly cited umbrella price minus a cost floor derivable from cited hardware +// rows, given explicitly declared utilization/amortization/exchange-rate assumptions. Opaque inputs +// refuse — never estimate around Unknown. // Per-thread cost allocation divides by HardwareThreadCount on both sides — umbrella_row.vcpus (GithubActionsRunnerPriceRow) and hetzner_dedicated_server_threads (CpuFacts catalog threads). The carrier grounds SMT-sibling counting; no parallel Bool assertion. -data wedge_fx_assumption_frontier: String = "eur_to_usd_numerator/denominator are explicitly declared WedgeAssumptions inputs (refuses ExchangeRateUnknown on zero denominator), not cited extdeps facts. dissolve-on: extdeps.forex authority + MoneyRate carrier when a second FX consumer appears." +// eur_to_usd_numerator/denominator are explicitly declared WedgeAssumptions inputs (refuses +// ExchangeRateUnknown on zero denominator), not cited extdeps facts. dissolve-on: extdeps.forex +// authority + MoneyRate carrier when a second FX consumer appears. type WedgeAssumptions { runner_utilization_bp: BasisPoint diff --git a/dag/gunbc/econ/llm_attempt_receipt.dag b/dag/gunbc/econ/llm_attempt_receipt.dag index b65f83d85d4..765ca7196eb 100644 --- a/dag/gunbc/econ/llm_attempt_receipt.dag +++ b/dag/gunbc/econ/llm_attempt_receipt.dag @@ -5,16 +5,52 @@ import std.measure { TokenCount, Nanosecond, Joule, nanosecond_count, token_coun import std.nat { Nat } import std.types { Bool, List, NonEmptyStr } -data llm_attempt_receipt_note: String = "Typed receipt for one LLM attempt against gunbc work, sibling to gunbc.econ.knowable_wedge and gunbc.econ.scm_serving_model. This is a RECEIPT in the observing product layer, never a fact owned by an upstream provider (DESIGN external-upstream-decomposition rule): extdeps.llm.anthropic owns the provider wire contract, this module owns what WE observed when we ran something. It carries counts, durations and outcomes only -- no throughput, no cost, no tokens-per-second field exists anywhere in the record, so a transcribed rate is unrepresentable rather than lens-caught (same move as the guarantee-recovery claims carrier, which stores no rung). Every rate below is DERIVED from the stored pair and refuses when the pair cannot support it." - -data llm_token_count_authority_note: String = "TokenCount, token_count and token_count_value are CONSUMED from std.measure, never redeclared here. An earlier revision of this module declared the alias locally as Measure of Count at One over Nat and cited extdeps.pricing.billing_units as precedent for the downstream placement; review 48816 refused that citation and was right. The billing_units aliases introduce their own phantom markers, so MoneyRate of PerStoredUnitMonth is a genuinely new point in the type space that cannot be written in std without importing vendor vocabulary; Measure of Count at One over Nat takes only arguments std.measure already owns, and that module already names the shape three times (HardwareThreadCount, CharacterCount, AmortizationMonths). A local fourth name was therefore the DESIGN section 3 fork, not a downstream instantiation, and the regen cost cited to justify it was a cost of doing the right thing rather than a reason not to. The carrier now lives beside its siblings under std.measure token_count_note." - -data llm_model_identity_brand_note: String = "Requested and resolved model identity are DISTINCT named carriers, not one string used twice. The dispatch receipt bar (gunbc.dispatch_selection dispatch_execution_receipt_bar_note) says the system must never display what was REQUESTED as though it proved what RAN, and holding them in separate fields on separate types is what keeps the two facts addressable. MEASURED LIMIT, NOT A WALL (probe executed 2026-08-05 on this branch): a scratch entry passing a RequestedModelIdentity into a parameter declared ResolvedModelIdentity RESOLVED AND RAN GREEN, so brand() on NonEmptyStr does not refuse cross-brand substitution at this slice. An earlier revision of this note claimed the substitution 'does not compile' and cited a witness named requested_model_identity_is_not_resolved_model_identity that was never written -- both the claim and the citation were false, caught by executing the probe rather than by reading the type. So the honest rung here is MITIGATABLE, the same rung std.measure token_count_note records for the Count-axis family: distinct names buy reading clarity and separate construction sites, not unwritability. NEXT-RUNG TRIGGER: the brand-refinement enforcement path that would make cross-brand assignment a typecheck refusal, or distinct record carriers rather than two brands over one primitive; until one of those lands, a compile-red control is the only evidence that would establish the wall, and no such control exists." +// Typed receipt for one LLM attempt against gunbc work, sibling to gunbc.econ.knowable_wedge and +// gunbc.econ.scm_serving_model. A RECEIPT in the observing product layer, never a fact owned by an +// upstream provider (DESIGN external-upstream-decomposition rule): extdeps.llm.anthropic owns the +// provider wire contract; this module owns what WE observed. It carries counts, durations and +// outcomes only -- no throughput, cost or tokens-per-second field exists in the record, so a +// transcribed rate is unrepresentable rather than lens-caught (as the guarantee-recovery claims +// carrier stores no rung). Every rate below is DERIVED from the stored pair and refuses when the +// pair cannot support it. + +// TokenCount, token_count and token_count_value are CONSUMED from std.measure, never redeclared +// here. An earlier revision declared the alias locally as Measure of Count at One over Nat, citing +// extdeps.pricing.billing_units as precedent for downstream placement; review 48816 rightly refused +// the citation. The billing_units aliases introduce their own phantom markers, so MoneyRate of +// PerStoredUnitMonth is a genuinely new point in the type space unwritable in std without vendor +// vocabulary; Measure of Count at One over Nat takes only arguments std.measure already owns, and +// that module already names the shape three times (HardwareThreadCount, CharacterCount, +// AmortizationMonths). A local fourth name was the DESIGN section 3 fork, not a downstream +// instantiation, and the regen cost cited to justify it was the cost of doing the right thing. The +// carrier now lives beside its siblings under std.measure token_count_note. + +// Requested and resolved model identity are DISTINCT named carriers, not one string used twice: +// the dispatch receipt bar (gunbc.dispatch_selection dispatch_execution_receipt_bar_note) forbids +// displaying what was REQUESTED as though it proved what RAN, and separate fields on separate +// types keep the two facts addressable. MEASURED LIMIT, NOT A WALL (probe executed 2026-08-05 on +// this branch): a scratch entry passing a RequestedModelIdentity into a parameter declared +// ResolvedModelIdentity RESOLVED AND RAN GREEN, so brand() on NonEmptyStr does not refuse +// cross-brand substitution at this slice. An earlier revision claimed the substitution 'does not +// compile' and cited a never-written witness +// requested_model_identity_is_not_resolved_model_identity -- claim and citation both false, caught +// by executing the probe rather than reading the type. So the honest rung is MITIGATABLE, as +// std.measure token_count_note records for the Count-axis family: distinct names buy reading +// clarity and separate construction sites, not unwritability. +// NEXT-RUNG TRIGGER: the brand-refinement enforcement path making cross-brand assignment a +// typecheck refusal, or distinct record carriers rather than two brands over one primitive; until +// one lands, a compile-red control is the only evidence that would establish the wall, and none +// exists. type RequestedModelIdentity = NonEmptyStr where brand("RequestedModelIdentity") type ResolvedModelIdentity = NonEmptyStr where brand("ResolvedModelIdentity") -data llm_artifact_identity_note: String = "A marketing model name is not an artifact. serving_runtime_identity and model_artifact_identity bind the exact served thing -- quantization, build, adapter set -- because two attempts naming the same model at different quantizations are different economic subjects. These stay NonEmptyStr at this slice: the cited artifact rows that would ground them (model catalog, GGUF sizes, runtime versions) land with the pricing rows in the follow-on, and grounding them early would mint a second artifact authority beside the one those rows will carry." +// A marketing model name is not an artifact. serving_runtime_identity and model_artifact_identity +// bind the exact served thing -- quantization, build, adapter set -- because two attempts naming +// one model at different quantizations are different economic subjects. They stay NonEmptyStr at +// this slice: the cited artifact rows that would ground them (model catalog, GGUF sizes, runtime +// versions) land with the pricing rows in the follow-on, and grounding them early would mint a +// second artifact authority beside the one those rows will carry. type LlmTaskClass = DagReview @@ -33,7 +69,12 @@ type LlmTokenUsage { output: TokenCount } -data llm_usage_observation_note: String = "Usage is a three-state observation, never a zero default. An attempt whose provider reported no usage is NOT an attempt that consumed nothing: conflating the two is the absorbing-fallback shape DESIGN section 5 forbids, one level down -- bottom-as-answer standing in for bottom-as-ignorance. So LlmUsageUnobserved carries a located reason and every economic derivation over it refuses, while the attempt itself and its disposition remain fully readable. Execution is not refused because accounting is missing." +// Usage is a three-state observation, never a zero default. An attempt whose provider reported no +// usage is NOT one that consumed nothing: conflating them is the absorbing-fallback shape DESIGN +// section 5 forbids, one level down -- bottom-as-answer for bottom-as-ignorance. So +// LlmUsageUnobserved carries a located reason and every economic derivation over it refuses, while +// the attempt and its disposition stay fully readable: execution is not refused because accounting +// is missing. type LlmUsageObservation = LlmUsageObserved { usage: LlmTokenUsage } @@ -44,14 +85,22 @@ type PrefixCacheObservation | PrefixCacheMiss | PrefixCacheUnobserved { reason: NonEmptyStr } -data llm_concurrency_grain_note: String = "Admitted sessions and active sequences are separate observations because they are separate facts: sixteen review agents do not ordinarily decode at once -- most are cloning, compiling, waiting on witnesses or on review. A server sustaining four active sequences can therefore admit considerably more than four agent sessions. Reporting either number as the other is the measurement error that makes a concurrency sweep unreadable, so the receipt records both and derives neither." +// Admitted sessions and active sequences are separate facts: sixteen review agents do not +// ordinarily decode at once -- most are cloning, compiling, waiting on witnesses or review -- so a +// server sustaining four active sequences can admit considerably more than four sessions. +// Reporting either number as the other makes a concurrency sweep unreadable, so the receipt +// records both and derives neither. type ConcurrencyObservation { admitted_sessions: Nat active_sequences: Nat } -data llm_phase_duration_note: String = "Durations are optional per phase and stay on Nanosecond, the canonical exact elapsed-time carrier (std.measure nanosecond_millisecond_projection_note: measurement, ordering, joining and attribution must retain Nanosecond; millisecond is presentation only). Queue delay is recorded beside prefill and decode because time-to-first-token under load is the quantity a concurrency sweep is actually buying, and a receipt that stored only decode would make a queue-bound host look fast." +// Durations are optional per phase and stay on Nanosecond, the canonical exact elapsed-time carrier +// (std.measure nanosecond_millisecond_projection_note: measurement, ordering, joining and +// attribution retain Nanosecond; millisecond is presentation only). Queue delay is recorded beside +// prefill and decode because time-to-first-token under load is what a concurrency sweep buys, and +// a receipt storing only decode would make a queue-bound host look fast. type AttemptPhaseDurations { queue: Nanosecond? @@ -59,7 +108,12 @@ type AttemptPhaseDurations { decode: Nanosecond? } -data llm_substrate_identity_note: String = "The receipt binds the substrate revision that produced the task, not only the model that answered it. Escalation rate is endogenous to TWO independent variables -- the local model improving, and gunbc's own construction guarantees making tasks need less judgment -- and both push it down. Without the substrate identity on the economic receipt a falling escalation rate is credited to whichever variable was being worked on at the time, and the 'progressively smaller models suffice' thesis is a claim about the SECOND one, so it cannot be measured without this field." +// The receipt binds the substrate revision that produced the task, not only the answering model. +// Escalation rate is endogenous to TWO independent variables -- the local model improving, and +// gunbc's construction guarantees making tasks need less judgment -- both pushing it down. Without +// the substrate identity a falling rate is credited to whichever variable was being worked on, and +// the 'progressively smaller models suffice' thesis is a claim about the SECOND, so it cannot be +// measured without this field. type SubstrateIdentity { compiler_revision: NonEmptyStr @@ -90,7 +144,13 @@ type LlmAttemptReceipt { disposition: ReviewDisposition } -// Throughput is an exact fraction over the SAME FieldOfFractions of Nat authority gunbc.econ.acquisition ConversionRate consumes -- output tokens over elapsed nanoseconds -- and nothing divides until a consumer explicitly asks for the floor projection. Storing tokens-per-second as a scalar would bake one rounding into the authority and make a cross-host comparison depend on when the division happened. The floor projection below is a DECLARED lossy view and is total by typed refusal: it returns none on a zero denominator rather than dividing, because the record stays publicly constructible even though every derivation that PRODUCES one refuses a zero window first. +// Throughput is an exact fraction over the SAME FieldOfFractions of Nat authority +// gunbc.econ.acquisition ConversionRate consumes -- output tokens over elapsed nanoseconds -- and +// nothing divides until a consumer asks for the floor projection. A tokens-per-second scalar would +// bake one rounding into the authority and make cross-host comparison depend on when the division +// happened. The floor projection below is a DECLARED lossy view, total by typed refusal: none on a +// zero denominator rather than dividing, because the record stays publicly constructible even +// though every derivation that PRODUCES one refuses a zero window first. type TokenThroughput = FieldOfFractions type ThroughputRefusalCause @@ -136,7 +196,12 @@ fn throughput_per_second_floor(t: TokenThroughput) -> Nat? { } } -// Accepted task goodput is the primary quantity, not token throughput: a cheap attempt that produced a rejected finding is not useful work, and a host that doubles its token rate while halving its acceptance rate has bought nothing. So the numerator counts ACCEPTED dispositions only -- escalated and rejected attempts consume the window and contribute zero -- which is exactly what makes this metric fall when a smaller local model is pushed past its envelope. Refuses on an empty roster and on a zero window rather than reporting a goodput of nothing over nothing. +// Accepted task goodput is the primary quantity, not token throughput: a cheap attempt producing a +// rejected finding is not useful work, and a host doubling its token rate while halving its +// acceptance rate has bought nothing. The numerator counts ACCEPTED dispositions only -- escalated +// and rejected attempts consume the window and contribute zero -- which is what makes this metric +// fall when a smaller local model is pushed past its envelope. Refuses on an empty roster and on a +// zero window rather than reporting nothing over nothing. fn attempt_is_accepted(d: ReviewDisposition) -> Bool { match d { ReviewAccepted => true @@ -203,7 +268,10 @@ fn derive_accepted_goodput( } } -data llm_escalation_fraction_note: String = "Frontier escalation fraction is derived over the same roster as an exact fraction, so it joins the substrate identity above: the pair of them is what distinguishes 'the local model got better' from 'the work got easier'. Refuses on an empty roster rather than reporting zero escalations out of zero attempts, which reads as a perfect local tier." +// Frontier escalation fraction is an exact fraction over the same roster, joining the substrate +// identity above: the pair distinguishes 'the local model got better' from 'the work got easier'. +// Refuses on an empty roster rather than reporting zero of zero, which reads as a perfect local +// tier. type EscalationFraction = FieldOfFractions @@ -211,6 +279,18 @@ type EscalationFractionResult = EscalationFractionDerived { fraction: EscalationFraction } | EscalationFractionRefused { cause: GoodputRefusalCause } +// DECLARED FRONTIER -- this slice lands the mechanism and its discriminating evidence, NOT a +// production consumer. The gunbc.dispatch_selection seam that will carry these receipts types its +// observed arm as DispatchUsageObserved of a List of NonEmptyStr, and its only construction site +// is dispatch_usage_unobserved, so no real attempt has ever been recorded and nothing exists to +// re-home; calling that seam typed would be the specification-without-execution trap DESIGN +// section 5 names. NEXT TRIGGER: provider usage capture on the dispatch path replaces the untyped +// arm with LlmUsageObservation and persists one receipt per real attempt; the cited token-rate rows +// and cost-per-accepted-task derivation follow in their own slice, where +// extdeps.pricing.billing_units gains per-token markers beside its storage markers. This module +// derives no cost precisely because those rates are not yet cited; fabricating a rate to make the +// arithmetic run is what the follow-on exists to prevent. + fn derive_escalation_fraction(receipts: List) -> EscalationFractionResult { let total = attempt_roster_count(receipts: receipts) if total == 0 { @@ -224,5 +304,3 @@ fn derive_escalation_fraction(receipts: List) -> EscalationFr } } } - -data llm_attempt_receipt_frontier_note: String = "DECLARED FRONTIER -- this slice lands the mechanism and its discriminating evidence, NOT a production consumer. The gunbc.dispatch_selection seam that will carry these receipts today types its observed arm as DispatchUsageObserved of a List of NonEmptyStr, and its only construction site in the tree is dispatch_usage_unobserved, so no real attempt has ever been recorded and there is nothing yet to re-home. Calling that seam typed would be the specification-without-execution trap DESIGN section 5 names. NEXT TRIGGER: provider usage capture on the dispatch path replaces the untyped arm with LlmUsageObservation and persists one receipt per real attempt; the cited token-rate rows and cost-per-accepted-task derivation follow in their own slice, where extdeps.pricing.billing_units gains per-token markers beside its storage markers. This module derives no cost precisely because those rates are not yet cited, and fabricating a rate to make the arithmetic run is what the follow-on exists to prevent." diff --git a/dag/gunbc/econ/regional_compute_premium.dag b/dag/gunbc/econ/regional_compute_premium.dag index 6297da819a8..8f7672f9a58 100644 --- a/dag/gunbc/econ/regional_compute_premium.dag +++ b/dag/gunbc/econ/regional_compute_premium.dag @@ -10,9 +10,20 @@ import extdeps.pricing.regional_compute { import std.measure { basis_point, BasisPoint, hardware_thread_count_value } import std.nat { Nat } -data regional_premium_note: String = "The read the regional rows exist for: what does the SAME machine cost in region X versus a baseline region, expressed as a basis-point premium. Derived at read time from cited rows, never stored (DESIGN section 2). The comparison is only meaningful when the two rows hold the hardware fixed, so the derivation refuses across providers -- an AWS m7i.large against an Azure D2s_v5 is a vendor comparison wearing a region comparison's clothes, and the two vendors price their generations differently even where the vCPU and memory match." +// The read the regional rows exist for: what the SAME machine costs in region X versus a baseline +// region, as a basis-point premium. Derived at read time from cited rows, never stored (DESIGN +// section 2). Meaningful only with hardware held fixed, so the derivation refuses across providers +// -- an AWS m7i.large against an Azure D2s_v5 is a vendor comparison wearing a region comparison's +// clothes; the vendors price generations differently even where vCPU and memory match. -data wage_intuition_note: String = "Why this read exists at all (operator question 2026-07-25: 'wages in India are much lower than in America -- is compute cheaper there?'). The measured answer is no, and it is no in EVERY geography sampled: every region priced above its provider's US baseline, from +5 percent (Mumbai, Central India) to +59 percent (Sao Paulo, Brazil South -- the same figure on BOTH providers independently). Datacenter cost is dominated by globally-priced capital equipment, power, cooling, land and network transit; wages are a thin slice of hyperscale opex, so a low-wage economy does not buy cheap compute. The practical consequence runs the opposite way from the intuition: buying compute where it is cheap and serving it outward beats provisioning locally, for any workload whose latency budget tolerates the distance." +// Why this read exists (operator question 2026-07-25: 'wages in India are much lower than in +// America -- is compute cheaper there?'). Measured answer: no, in EVERY geography sampled -- every +// region priced above its provider's US baseline, from +5 percent (Mumbai, Central India) to +59 +// percent (Sao Paulo, Brazil South -- the same figure on BOTH providers independently). Datacenter +// cost is dominated by globally-priced capital equipment, power, cooling, land and transit; wages +// are a thin slice of hyperscale opex. The practical consequence inverts the intuition: buy compute +// where it is cheap and serve it outward, for any workload whose latency budget tolerates the +// distance. type RegionalPremiumRefusalCause = BaselineRateUnavailable @@ -25,7 +36,9 @@ type RegionalPremiumResult = RegionalPremiumDerived { premium_bp: BasisPoint } | RegionalPremiumRefused { cause: RegionalPremiumRefusalCause } -// premium_bp is the region's excess over the baseline in basis points: (region - baseline) * 10000 / baseline. A region at parity reads 0; Sao Paulo against US East reads about 5937 (a 59.4 percent premium). Integer basis points on integer micro-USD, so there is no float in the derivation and the value is exact for the cited rates. +// premium_bp is the region's excess over the baseline in basis points: (region - baseline) * 10000 +// / baseline. Parity reads 0; Sao Paulo against US East about 5937 (59.4 percent). Integer basis +// points on integer micro-USD: no float, exact for the cited rates. fn derive_regional_premium( baseline: RegionalComputeRow, region: RegionalComputeRow, @@ -53,7 +66,10 @@ fn derive_regional_premium( } } -// Monthly cost for the row's whole instance at the conventional 730-hour billing month, and the same figure divided by the instance's vCPU count -- the per-vCPU number is what makes a 2-vCPU cloud instance comparable to a 12-thread dedicated box (extdeps.pricing.hetzner_dedicated), which is the comparison that shows on-demand cloud compute costing several times a rented machine. +// Monthly cost of the whole instance at the conventional 730-hour billing month, and the same +// divided by vCPU count -- the per-vCPU figure makes a 2-vCPU cloud instance comparable to a +// 12-thread dedicated box (extdeps.pricing.hetzner_dedicated), the comparison showing on-demand +// cloud compute costing several times a rented machine. fn monthly_micros(row: RegionalComputeRow) -> Nat? { match hourly_micros(row: row) { Present { value: h } => Present { value: h * 730 } diff --git a/dag/gunbc/econ/scm_serving_model.dag b/dag/gunbc/econ/scm_serving_model.dag index 457bbcb0017..289aa5a9160 100644 --- a/dag/gunbc/econ/scm_serving_model.dag +++ b/dag/gunbc/econ/scm_serving_model.dag @@ -21,7 +21,14 @@ import std.measure { import std.nat { Nat, nat_max, nat_min } import std.types { Bool, List, NonEmptyStr, list_length } -data scm_serving_model_note: String = "How to compute an SCM's serving bill from workload primitives instead of guessing at a monthly number. Four axes, each derived from the profile and each priced by its own cited vendor row (extdeps.pricing.object_storage): CAPACITY held (stored bytes after dedup, times a billing month), NETWORK moved (egress bytes -- clones and fetches leaving the network), OPERATIONS counted (object reads and writes, the axis most models forget entirely), and COMPUTE, which is deliberately NOT modeled here because it already has carriers: gunbc.econ.knowable_wedge prices CI minutes against cited runner rows and gunbc.econ.free_tier_serving prices self-managed boxes. Storing a second compute model here would be the dual representation DESIGN section 3 forbids." +// How to compute an SCM's serving bill from workload primitives instead of guessing at a monthly +// number. Four axes, each derived from the profile and each priced by its own cited vendor row +// (extdeps.pricing.object_storage): CAPACITY held (stored bytes after dedup, times a billing +// month), NETWORK moved (egress bytes -- clones and fetches leaving the network), OPERATIONS +// counted (object reads and writes, the axis most models forget entirely), and COMPUTE, which is +// deliberately NOT modeled here because it already has carriers: gunbc.econ.knowable_wedge prices +// CI minutes against cited runner rows and gunbc.econ.free_tier_serving prices self-managed boxes. +// Storing a second compute model here would be the dual representation DESIGN section 3 forbids. // The replica-count multiplication that gunbc.econ.free_tier_serving applies to Hetzner DEDICATED boxes must NOT be applied to these object-storage rates. A cited object-storage GB-month already buys eleven-nines durability (extdeps.pricing.object_storage durability_standard_note), i.e. the vendor's own erasure-coded redundancy across facilities; multiplying it by a replica count charges twice for redundancy already purchased. Self-managed block storage is the opposite case -- one box is one copy, so replicas are the modeler's responsibility. Two storage realizations, two different cost shapes, one reason: who owns the durability. type ScmWorkloadProfile { @@ -101,7 +108,11 @@ fn assumed_basis(assumption: ByteBasisAssumption) -> ByteBasis? { } } -data basis_reconciliation_note: String = "A cited row that DOES state its byte basis (Google's replication row states GiB) outranks the caller's assumption: if they disagree the derivation refuses with BasisContradictsCitedRow rather than silently preferring either. A row whose basis is ByteBasisUnstated -- every storage row in the catalogue today -- accepts the declared assumption, because that is exactly the knowable-wedge shape: proceed on a declared assumption, refuse on an absent one." +// A cited row that DOES state its byte basis (Google's replication row states GiB) outranks the +// caller's assumption: if they disagree the derivation refuses with BasisContradictsCitedRow rather +// than silently preferring either. A row whose basis is ByteBasisUnstated -- every storage row in +// the catalogue today -- accepts the declared assumption, because that is exactly the +// knowable-wedge shape: proceed on a declared assumption, refuse on an absent one. fn basis_compatible_with_row(row_basis: ByteBasis, assumed: ByteBasis) -> Bool { match row_basis { @@ -223,9 +234,22 @@ fn cited_egress_basis(row: ObjectStorageEgressRow) -> ByteBasis { } } -data egress_dominance_note: String = "The reason this model separates network from capacity rather than quoting one blended number: for an SCM they diverge by more than an order of magnitude. A git host's read traffic is clones and fetches -- bytes LEAVING the network -- so egress scales with developer activity while stored bytes scale only with distinct content, and content-addressed dedup deliberately flattens the second. On a metered-egress vendor the network term therefore dominates the bill; on a zero-egress vendor it vanishes and capacity dominates. The witness sizes both against the SAME profile, so the spread is a derived fact rather than a claim." - -data progressive_tier_note: String = "Flat-rate multiplication is correct only below the first tier boundary, and an SCM crosses those boundaries exactly when it starts to matter: at a million users on this profile the workload holds 100 TB and moves 500 TB a month, which is past AWS's 50 TB storage step and past all three of its egress steps. Vendor tiers are PROGRESSIVE (each rate applies only to the slice inside its own band, like tax brackets), so pricing the whole volume at the top band's rate under-states the bill and pricing it at the first band's rate over-states it. This fold applies each band to its own slice; a ladder is a list of rows ascending by lower bound, and the last band runs to the workload's actual volume." +// The reason this model separates network from capacity rather than quoting one blended number: for +// an SCM they diverge by more than an order of magnitude. A git host's read traffic is clones and +// fetches -- bytes LEAVING the network -- so egress scales with developer activity while stored +// bytes scale only with distinct content, and content-addressed dedup deliberately flattens the +// second. On a metered-egress vendor the network term therefore dominates the bill; on a +// zero-egress vendor it vanishes and capacity dominates. The witness sizes both against the SAME +// profile, so the spread is a derived fact rather than a claim. + +// Flat-rate multiplication is correct only below the first tier boundary, and an SCM crosses those +// boundaries exactly when it starts to matter: at a million users on this profile the workload +// holds 100 TB and moves 500 TB a month, which is past AWS's 50 TB storage step and past all three +// of its egress steps. Vendor tiers are PROGRESSIVE (each rate applies only to the slice inside its +// own band, like tax brackets), so pricing the whole volume at the top band's rate under-states the +// bill and pricing it at the first band's rate over-states it. This fold applies each band to its +// own slice; a ladder is a list of rows ascending by lower bound, and the last band runs to the +// workload's actual volume. // Nat subtraction below zero is not representable, and a tier slice legitimately goes negative when the workload never reaches that band -- so the clamp is explicit rather than an if-branch returning a bare literal (which types as Int and would fork the carrier). fn saturating_sub(a: Nat, b: Nat) -> Nat { @@ -296,7 +320,10 @@ fn egress_ladder(rows: List) -> List? { if acc.ok && (list_length(items: acc.tiers) > 0) { Present { value: acc.tiers } } else { none } } -data request_pricing_note: String = "Two vendors here state that requests are not billed at all (Wasabi: 'No fees for egress or API requests'; Storj: 'No API fees'). That is a DIFFERENT fact from a zero rate and from an unknown rate, so it gets its own arm -- a model that encoded it as rate zero could not tell 'the vendor says free' apart from 'nobody filled this in yet'." +// Two vendors here state that requests are not billed at all (Wasabi: 'No fees for egress or API +// requests'; Storj: 'No API fees'). That is a DIFFERENT fact from a zero rate and from an unknown +// rate, so it gets its own arm -- a model that encoded it as rate zero could not tell 'the vendor +// says free' apart from 'nobody filled this in yet'. type RequestPricing = RequestsBilled { read_row: ObjectStorageRequestRow, write_row: ObjectStorageRequestRow } @@ -391,7 +418,15 @@ fn quote_total_micros(result: ProviderMonthlyQuoteResult) -> Nat? { } } -data agentic_request_amplification_note: String = "The read/write counts in a human-authored profile are the wrong order of magnitude for agent-driven development, and the axis they load is the one that looks negligible at human rates: operations. A human developer reads a few hundred objects a month; an agent working continuously reads thousands an HOUR, and it writes far more often because it commits intermediate states a human would never bother to save. Multiply that by the node grain a .dag-native SCM actually wants -- where a read is a NODE, not a file -- and the object count amplifies again. Operations are ~0.4 percent of a request-metered bill at human rates and can exceed 99 percent at agent rates, which means the vendor ranking computed on a human profile does not survive contact with the workload this SCM is being built for." +// The read/write counts in a human-authored profile are the wrong order of magnitude for +// agent-driven development, and the axis they load is the one that looks negligible at human rates: +// operations. A human developer reads a few hundred objects a month; an agent working continuously +// reads thousands an HOUR, and it writes far more often because it commits intermediate states a +// human would never bother to save. Multiply that by the node grain a .dag-native SCM actually +// wants -- where a read is a NODE, not a file -- and the object count amplifies again. Operations +// are ~0.4 percent of a request-metered bill at human rates and can exceed 99 percent at agent +// rates, which means the vendor ranking computed on a human profile does not survive contact with +// the workload this SCM is being built for. // The mitigation, and the load-bearing design decision it exposes: per-request billing punishes fine granularity, so a node-grain store that maps one node to one object pays a request fee per node. Packing many nodes into one stored object (git's own packfile move, and the reason git does not store one file per blob on the wire) divides the operation count by the packing factor while leaving the byte count alone. The policy is modeled rather than assumed because it is a real architectural choice with a real cost consequence, and because UnpackedNodeGrain is the shape a naive content-addressed store falls into by default -- the expensive default is exactly the one worth making visible. type PackingPolicy diff --git a/dag/gunbc/emit_stage_blocking_population_census.dag b/dag/gunbc/emit_stage_blocking_population_census.dag index 259d43ca160..7a905ecc3b0 100644 --- a/dag/gunbc/emit_stage_blocking_population_census.dag +++ b/dag/gunbc/emit_stage_blocking_population_census.dag @@ -22,7 +22,18 @@ data emit_stage_blocking_population_census_disposition: Disposition = Terminal { reason: "THE RECEIPT OF ONE WHOLE-CORPUS EMIT-STAGE CENSUS, taken to satisfy the bounded-population requirement DESIGN section 4b(3) places on the declared drop titled A BLOCKING EMIT-STAGE DIAGNOSTIC CAN SIT ON MAIN INDEFINITELY WITH NO REQUIRED PHASE THAT FAILS. That row states its own defect plainly -- POPULATION: UNCOUNTED AND UNBOUNDED -- and names two specimens found by tripping over them rather than by searching. This module carries the measurement that replaces the word UNCOUNTED with a figure, and it carries nothing else: it is a receipt, not a gate, not an instrument, and not a repair." } -data census_bounds_it_does_not_close_it_note: String = "WHAT A CENSUS BUYS AND WHAT IT DOES NOT, stated first because the neighbouring claim would be the rung inflation DESIGN section 4b(1) calls worse than sitting low. A census BOUNDS the population, which is exactly what that row lacks. It DOES NOT CLOSE THE CLASS: the row's restoration trigger is A REQUIRED PHASE THAT EMITS OVER CLOSURES, and one measurement taken by hand on one revision is not that phase. Nothing here executes on any push, nothing refuses, and the population this receipt bounds is free to grow the moment it is written -- which is the difference between knowing a number and having a wall.\n\nSO THE ROW'S RUNG DOES NOT MOVE. It stays outside the ladder, for the reason it already gives: an operation that lost its only route has nothing to mitigate. What changes is one clause of it -- the population is now measured, at a named revision, by a named command, with the run's termination and peak residency recorded beside the count." +// WHAT A CENSUS BUYS AND WHAT IT DOES NOT, stated first because the neighbouring claim would be the +// rung inflation DESIGN section 4b(1) calls worse than sitting low. A census BOUNDS the population, +// which is exactly what that row lacks. It DOES NOT CLOSE THE CLASS: the row's restoration trigger +// is A REQUIRED PHASE THAT EMITS OVER CLOSURES, and one measurement taken by hand on one revision +// is not that phase. Nothing here executes on any push, nothing refuses, and the population this +// receipt bounds is free to grow the moment it is written -- which is the difference between +// knowing a number and having a wall. +// +// SO THE ROW'S RUNG DOES NOT MOVE. It stays outside the ladder, for the reason it already gives: an +// operation that lost its only route has nothing to mitigate. What changes is one clause of it -- +// the population is now measured, at a named revision, by a named command, with the run's +// termination and peak residency recorded beside the count. // --------------------------------------------------------------------------------------------- // THE CONSTRUCTION MOVE, AND IT IS THE WHOLE REASON THIS CARRIER HAS A SHAPE AT ALL. @@ -265,7 +276,17 @@ fn census_population_bound(r: CensusReading) -> PopulationBound { data census_revision: CommitSha = "d198ff6eae007bfae4bb1d1796ec9162f6a03402" -data census_subject_scope_note: String = "WHAT `--source-root dag --source-root src/v2` MEASURES, stated because it is narrower than the phrase WHOLE CORPUS suggests and a reader who assumes otherwise will over-read every figure below. The FIRST source root is the primary root and the rest are a dependency POOL: the compiler emits the primary root's whole population plus whatever the pool supplies to it. Measured on this run, that is 3006 resolved sources against 2785 `.dag` files under dag/ and 1278 under src/v2 -- so the census covers dag/ entire, plus the 221 src/v2 modules dag/ actually reaches, and leaves the rest of src/v2 unmeasured by THIS invocation.\n\nThe unmeasured remainder is named rather than elided: a census with src/v2 as the primary root is a second run of the same command with the roots exchanged, and it has not been taken here. Any claim about src/v2's own emit-stage population is therefore outside this receipt." +// WHAT `--source-root dag --source-root src/v2` MEASURES, stated because it is narrower than the +// phrase WHOLE CORPUS suggests and a reader who assumes otherwise will over-read every figure +// below. The FIRST source root is the primary root and the rest are a dependency POOL: the compiler +// emits the primary root's whole population plus whatever the pool supplies to it. Measured on this +// run, that is 3006 resolved sources against 2785 `.dag` files under dag/ and 1278 under src/v2 -- +// so the census covers dag/ entire, plus the 221 src/v2 modules dag/ actually reaches, and leaves +// the rest of src/v2 unmeasured by THIS invocation. +// +// The unmeasured remainder is named rather than elided: a census with src/v2 as the primary root is +// a second run of the same command with the roots exchanged, and it has not been taken here. Any +// claim about src/v2's own emit-stage population is therefore outside this receipt. // ── LAYER 1: THE PRISTINE TREE ──────────────────────────────────────────────────────────────── // @@ -456,7 +477,6 @@ data layer_three_census: CensusReading = NoPopulation { cause: "emit reached its code-generation body with both gates clear and had not returned after four hours and thirty-eight minutes inside that stage; no tree was written and no population was produced" as NonEmptyStr } - // ── THE OTHER ROOT: src/v2 AS PRIMARY ───────────────────────────────────────────────────────── // // The first census covers dag/ entire and only the src/v2 modules dag/ reaches. This is the same @@ -478,6 +498,29 @@ data layer_three_census: CensusReading = NoPopulation { // gates ran to any depth is unestablished -- the same masking question one stage up, and the // reason this reading's extent is completeness-unestablished like the others. +// TWO ROOTS, TWO POPULATIONS, NEITHER OF THEM THE CORPUS. The command takes ONE primary root and +// treats the rest as a dependency pool, so a census of both roots is two runs and two subjects; a +// reader who unions them has invented a third population nobody measured. +// +// ROOT dag/ -- OBSERVABLE: three, all emit-produced. That is what the command answers on the +// revision as it stands, and it is a lower bound rather than the population, because emit's first +// gate returned ahead of every later check. LATENT: at least eleven. The eight the second gate +// reports were standing on the pristine tree the whole time; clearing the first gate is what let +// anyone see them. UPPER BOUND: NONE, with the cause named -- emit does not finish at whole-root +// scale within five hours, so the route that would have bounded the population from above does not +// return. +// +// ROOT src/v2 -- twenty hard diagnostics at the emit refusal, of which ZERO are emit-produced: +// nineteen are one module's cascade behind an import of v1.compiler.ownership, which lies outside +// both source roots, and the twentieth is a planted negative fixture doing exactly what it was +// planted to do. Whether emit's own gates ran at all here is unestablished, so zero-observed is not +// a clean bill. +// +// WHAT MOVED, precisely: the word UNCOUNTED. What did not move: UNBOUNDED, the rung, and the +// trigger. The class keeps its DESIGN standing -- outside the ladder, and its restoration trigger +// remains a REQUIRED PHASE THAT EMITS OVER CLOSURES. This receipt is not that phase and does not +// claim to be one: it executes on no push, refuses nothing, and describes one revision on one host. + data src_v2_root_census: CensusReading = PopulationRead { run: CensusRun { source_revision: census_revision, @@ -606,5 +649,3 @@ data src_v2_root_census: CensusReading = PopulationRead { // It is deliberately three statements and not one number, because the one number does not exist // and writing one would close a row that is still open. // --------------------------------------------------------------------------------------------- - -data emit_stage_population_standing_note: String = "TWO ROOTS, TWO POPULATIONS, NEITHER OF THEM THE CORPUS. The command takes ONE primary root and treats the rest as a dependency pool, so a census of both roots is two runs and two subjects; a reader who unions them has invented a third population nobody measured.\n\nROOT dag/ -- OBSERVABLE: three, all emit-produced. That is what the command answers on the revision as it stands, and it is a lower bound rather than the population, because emit's first gate returned ahead of every later check. LATENT: at least eleven. The eight the second gate reports were standing on the pristine tree the whole time; clearing the first gate is what let anyone see them. UPPER BOUND: NONE, with the cause named -- emit does not finish at whole-root scale within five hours, so the route that would have bounded the population from above does not return.\n\nROOT src/v2 -- twenty hard diagnostics at the emit refusal, of which ZERO are emit-produced: nineteen are one module's cascade behind an import of v1.compiler.ownership, which lies outside both source roots, and the twentieth is a planted negative fixture doing exactly what it was planted to do. Whether emit's own gates ran at all here is unestablished, so zero-observed is not a clean bill.\n\nWHAT MOVED, precisely: the word UNCOUNTED. What did not move: UNBOUNDED, the rung, and the trigger. The class keeps its DESIGN standing -- outside the ladder, and its restoration trigger remains a REQUIRED PHASE THAT EMITS OVER CLOSURES. This receipt is not that phase and does not claim to be one: it executes on no push, refuses nothing, and describes one revision on one host." diff --git a/dag/gunbc/emitted_closure_compile_seed_growth.dag b/dag/gunbc/emitted_closure_compile_seed_growth.dag index e1da77f9e1c..3ba6959de97 100644 --- a/dag/gunbc/emitted_closure_compile_seed_growth.dag +++ b/dag/gunbc/emitted_closure_compile_seed_growth.dag @@ -9,9 +9,98 @@ import gunbc.seed_growth { SeedGrowthJustification } // the obligation it declares, rather than inside gunbc.seed_growth_admission, which owns the // ROSTER and the join and would otherwise accumulate every lane's rows in the module that // adjudicates them. -data emitted_closure_compile_seed_growth_note: String = "Seed-growth obligation for the host behind the required emit-compile phase.\n\nWHAT THE CHANGE IS. The v2-emission phase emits one entry's closure and stops at the emitter. Its own header in cli_run.rs enumerates what it therefore cannot see, and the first item is 'a rustc error in the emitted tree (nothing here compiles the emission)'. DESIGN's Building-&-checks section carries a declared rung drop headed 'A BLOCKING EMIT-STAGE DIAGNOSTIC CAN SIT ON MAIN INDEFINITELY WITH NO REQUIRED PHASE THAT FAILS', whose restoration trigger reads: this row retires when a required phase EMITS over a closure that reaches call sites -- the compile re-add on the queue the floor cut created. This host is that phase: same producer (compile_entry_emission), the emitted files written as a crate, cargo run over it.\n\nWHY IT IS NOT A SECOND EMITTER. The phase calls the SAME transaction the emission phase calls and adds one stage after it. A green there and a green here cannot be two facts about two emissions, which is exactly the property a separately-authored probe would have given up.\n\nWHY THE MANIFEST IS DERIVED AND NOT AUTHORED. The corpus already carries a hand-concatenated probe manifest (tools.self_host_curated_seed_linked_harness cssl_v1_compiled_probe_lib_cargo_toml), marked scaffold debt in its own module for being concat-authored TOML. Consuming it from a merge-blocking gate would have pinned that debt open on the required path, and authoring a second one would have been new scaffolding the operator declined on 2026-08-25. The manifest here is rendered from the modeled cargo authorities instead -- extdeps.rust.version render_cargo_package_header_prefix for the package header, v1.compiler.stage0_crates stage0_foundation_runtime_dependencies for the seed's own runtime dependency set, and render_stage0_crate_dep per row -- so no new markup is authored at all.\n\nNO IMPL BLOCK, AND THAT IS DELIBERATE. Every item in the file is a free function, a type, or the one private MODULE that carries the mutation subject's privacy boundary, because an impl method has no DeclarationRef spelling -- std.decl_ref offers WholeDeclaration or NamedField and neither names a method on an impl block -- so methods would have grown the class gunbc.seed_growth_admission reports as seed_growth_uncitable_item_keys. v1_compiler.declaration_index took the same route for the same reason. Uncitable items added by this change: ZERO.\n\nTHE ONE `mod` ITEM, DISPOSITIONED RATHER THAN GLOSSED. `entry_own_subject` is a private module and is the only item in this file that is neither a free function nor a type. It exists because Rust privacy is MODULE-scoped: a private field on a struct declared beside its constructor is a wall against other modules and mere convention within the declaring one, so the carrier and its single constructor sit inside a submodule and everything else in the file is outside that boundary. Without it, `MutationSubject { rust_module: ... }` compiles anywhere in the file and the shared-core substitution this phase exists to prevent is unwritable only by agreement. IT IS CITABLE and it is enumerated: a module is a whole declaration, so `WholeDeclaration` names it and the uncitable-item count stays ZERO. Its members are enumerated beside it -- the type, the constructor, and the accessor -- which is why the accessor is a free function inside the boundary rather than an impl method.\n\nHAND-ITEM DELTA: enumerated below rather than counted. src/v1/stage0/src/cli_run.rs adds no declaration -- one #[path] mod line and one re-export list -- and src/v1/stage0/src/bin/claim_executor.rs adds one variant to an existing exhaustive enum and one phase body inside an existing function; both dispositions are ExistingSeedItemModified, and listing them would net a modification into an addition census.\n\nWHY THIS ROSTER DRIFTED ONCE, RECORDED SO IT IS NOT REPEATED. Review 56685 found it citing PROBE_PACKAGE_NAME, which resolves to nothing: an earlier revision carried a package-name CONSTANT, the cargo-fingerprint-aliasing repair replaced it with the per-entry function probe_package_name, and the receipt was not updated with the code. An audit of the whole roster then found 1 stale name and 16 unaccounted declarations -- every one of the 16 added by a LATER repair in this same PR (probe_root, the selection digests, retention, the probe-line attribution fix, two tests), each of which grew the file without growing its receipt. That is the exact failure the carrier exists to catch, committed inside the carrier. The roster is now exact against the file: 55 rows, 55 declarations, zero stale, zero unaccounted. THE CENSUS COUNTS EVERY ITEM KIND AT EVERY INDENT -- const, static, fn, struct, enum, type and mod -- with exactly one stated exclusion, the `#[cfg(test)] mod tests` harness itself, whose member tests ARE enumerated individually. The exclusion is written down because an unstated narrowing is how the two preceding defects survived: a scan of only column-0 and 4-space indents could not see an impl method, and a scan omitting `mod` could not see the privacy boundary. THE STANDING HAZARD IS THAT IT IS A HAND ROSTER BESIDE ITS SUBJECT, so it can only be re-verified, never trusted: it drifts silently on the next declaration added, and nothing in the required run compares the two. Its dissolution is the same v1-hand-queue-drain lane this obligation already names.\n\nWHAT THE EXECUTED EVIDENCE IS, because the seed's own unit tests are not it. The Rust suite was removed from CI on 2026-07-11, so nothing under #[cfg(test)] executes on the merge path and none of it may be cited as coverage. The executed evidence is the phase itself: establish_discriminating_red injects one type error into one emitted file on EVERY required run, requires it to fail alone, restores the bytes and requires the green back -- and a mutation that fails to go red is a PHASE FAILURE, not a note. That is DESIGN 4b's authorable-RED question answered by execution rather than by inspection, and it is why a green from this phase carries information that a bare cargo-exit-status phase would not." +// Seed-growth obligation for the host behind the required emit-compile phase. +// +// WHAT THE CHANGE IS. The v2-emission phase emits one entry's closure and stops at the emitter. Its +// own header in cli_run.rs enumerates what it therefore cannot see, and the first item is 'a rustc +// error in the emitted tree (nothing here compiles the emission)'. DESIGN's Building-&-checks +// section carries a declared rung drop headed 'A BLOCKING EMIT-STAGE DIAGNOSTIC CAN SIT ON MAIN +// INDEFINITELY WITH NO REQUIRED PHASE THAT FAILS', whose restoration trigger reads: this row +// retires when a required phase EMITS over a closure that reaches call sites -- the compile re-add +// on the queue the floor cut created. This host is that phase: same producer +// (compile_entry_emission), the emitted files written as a crate, cargo run over it. +// +// WHY IT IS NOT A SECOND EMITTER. The phase calls the SAME transaction the emission phase calls and +// adds one stage after it. A green there and a green here cannot be two facts about two emissions, +// which is exactly the property a separately-authored probe would have given up. +// +// WHY THE MANIFEST IS DERIVED AND NOT AUTHORED. The corpus already carries a hand-concatenated +// probe manifest (tools.self_host_curated_seed_linked_harness +// cssl_v1_compiled_probe_lib_cargo_toml), marked scaffold debt in its own module for being +// concat-authored TOML. Consuming it from a merge-blocking gate would have pinned that debt open on +// the required path, and authoring a second one would have been new scaffolding the operator +// declined on 2026-08-25. The manifest here is rendered from the modeled cargo authorities instead +// -- extdeps.rust.version render_cargo_package_header_prefix for the package header, +// v1.compiler.stage0_crates stage0_foundation_runtime_dependencies for the seed's own runtime +// dependency set, and render_stage0_crate_dep per row -- so no new markup is authored at all. +// +// NO IMPL BLOCK, AND THAT IS DELIBERATE. Every item in the file is a free function, a type, or the +// one private MODULE that carries the mutation subject's privacy boundary, because an impl method +// has no DeclarationRef spelling -- std.decl_ref offers WholeDeclaration or NamedField and neither +// names a method on an impl block -- so methods would have grown the class +// gunbc.seed_growth_admission reports as seed_growth_uncitable_item_keys. +// v1_compiler.declaration_index took the same route for the same reason. Uncitable items added by +// this change: ZERO. +// +// THE ONE `mod` ITEM, DISPOSITIONED RATHER THAN GLOSSED. `entry_own_subject` is a private module +// and is the only item in this file that is neither a free function nor a type. It exists because +// Rust privacy is MODULE-scoped: a private field on a struct declared beside its constructor is a +// wall against other modules and mere convention within the declaring one, so the carrier and its +// single constructor sit inside a submodule and everything else in the file is outside that +// boundary. Without it, `MutationSubject { rust_module: ... }` compiles anywhere in the file and +// the shared-core substitution this phase exists to prevent is unwritable only by agreement. IT IS +// CITABLE and it is enumerated: a module is a whole declaration, so `WholeDeclaration` names it and +// the uncitable-item count stays ZERO. Its members are enumerated beside it -- the type, the +// constructor, and the accessor -- which is why the accessor is a free function inside the boundary +// rather than an impl method. +// +// HAND-ITEM DELTA: enumerated below rather than counted. src/v1/stage0/src/cli_run.rs adds no +// declaration -- one #[path] mod line and one re-export list -- and +// src/v1/stage0/src/bin/claim_executor.rs adds one variant to an existing exhaustive enum and one +// phase body inside an existing function; both dispositions are ExistingSeedItemModified, and +// listing them would net a modification into an addition census. +// +// WHY THIS ROSTER DRIFTED ONCE, RECORDED SO IT IS NOT REPEATED. Review 56685 found it citing +// PROBE_PACKAGE_NAME, which resolves to nothing: an earlier revision carried a package-name +// CONSTANT, the cargo-fingerprint-aliasing repair replaced it with the per-entry function +// probe_package_name, and the receipt was not updated with the code. An audit of the whole roster +// then found 1 stale name and 16 unaccounted declarations -- every one of the 16 added by a LATER +// repair in this same PR (probe_root, the selection digests, retention, the probe-line attribution +// fix, two tests), each of which grew the file without growing its receipt. That is the exact +// failure the carrier exists to catch, committed inside the carrier. The roster is now exact +// against the file: 55 rows, 55 declarations, zero stale, zero unaccounted. THE CENSUS COUNTS EVERY +// ITEM KIND AT EVERY INDENT -- const, static, fn, struct, enum, type and mod -- with exactly one +// stated exclusion, the `#[cfg(test)] mod tests` harness itself, whose member tests ARE enumerated +// individually. The exclusion is written down because an unstated narrowing is how the two +// preceding defects survived: a scan of only column-0 and 4-space indents could not see an impl +// method, and a scan omitting `mod` could not see the privacy boundary. THE STANDING HAZARD IS THAT +// IT IS A HAND ROSTER BESIDE ITS SUBJECT, so it can only be re-verified, never trusted: it drifts +// silently on the next declaration added, and nothing in the required run compares the two. Its +// dissolution is the same v1-hand-queue-drain lane this obligation already names. +// +// WHAT THE EXECUTED EVIDENCE IS, because the seed's own unit tests are not it. The Rust suite was +// removed from CI on 2026-07-11, so nothing under #[cfg(test)] executes on the merge path and none +// of it may be cited as coverage. The executed evidence is the phase itself: +// establish_discriminating_red injects one type error into one emitted file on EVERY required run, +// requires it to fail alone, restores the bytes and requires the green back -- and a mutation that +// fails to go red is a PHASE FAILURE, not a note. That is DESIGN 4b's authorable-RED question +// answered by execution rather than by inspection, and it is why a green from this phase carries +// information that a bare cargo-exit-status phase would not. -data probe_root_selection_seed_growth_delta_note: String = "HAND-RUST RECEIPT FOR THE REQUIRED-PATH ROOT CORRECTION. Additions: PROBE_ROOT_DIR_NAME, required_ci_probe_root_from_runner_temp, required_ci_emit_compile_probe_root, local_emit_compile_probe_root, and the_probe_root_does_not_exist_without_runner_temp -- five exact declarations, all enumerated in emitted_closure_compile_seed_growth_justification below. Deletion: probe_root, whose environment-dependent fallback mixed the required and standalone policies. Existing items probe_crate_dir, write_probe_crate, run_emit_compile_entry, emit_compile_report, run_required_emit_compile, the cli_run re-export, and the two claim_executor call sites are modified rather than added. Hand-Rust line delta against this PR's session/deep-gull-307 base: +79/-31 in emitted_closure_compile_host.rs, +9/-3 in claim_executor.rs, and +2/-1 in cli_run.rs. WHY RUST IS STILL NEEDED, owning lane, deletion trigger, and current boundary are unchanged from emitted_closure_compile_seed_growth_justification: the root choice feeds the same host filesystem and cargo-actuation boundary; owning_dissolution_lane remains v1-hand-queue-drain, and the whole selection surface deletes when those two effects are modeled and actuated." +// HAND-RUST RECEIPT FOR THE REQUIRED-PATH ROOT CORRECTION. Additions: PROBE_ROOT_DIR_NAME, +// required_ci_probe_root_from_runner_temp, required_ci_emit_compile_probe_root, +// local_emit_compile_probe_root, and the_probe_root_does_not_exist_without_runner_temp -- five +// exact declarations, all enumerated in emitted_closure_compile_seed_growth_justification below. +// Deletion: probe_root, whose environment-dependent fallback mixed the required and standalone +// policies. Existing items probe_crate_dir, write_probe_crate, run_emit_compile_entry, +// emit_compile_report, run_required_emit_compile, the cli_run re-export, and the two claim_executor +// call sites are modified rather than added. Hand-Rust line delta against this PR's +// session/deep-gull-307 base: +79/-31 in emitted_closure_compile_host.rs, +9/-3 in +// claim_executor.rs, and +2/-1 in cli_run.rs. WHY RUST IS STILL NEEDED, owning lane, deletion +// trigger, and current boundary are unchanged from +// emitted_closure_compile_seed_growth_justification: the root choice feeds the same host filesystem +// and cargo-actuation boundary; owning_dissolution_lane remains v1-hand-queue-drain, and the whole +// selection surface deletes when those two effects are modeled and actuated. data emitted_closure_compile_seed_growth_justification: SeedGrowthJustification = SeedGrowthJustification { hand_authored_declarations: [ diff --git a/dag/gunbc/emitted_record_deserialize_gap.dag b/dag/gunbc/emitted_record_deserialize_gap.dag index ba6620e95c3..cbb06681eec 100644 --- a/dag/gunbc/emitted_record_deserialize_gap.dag +++ b/dag/gunbc/emitted_record_deserialize_gap.dag @@ -17,7 +17,10 @@ import std.decl_ref { DeclarationRef, decl_ref } // well-formed and accepted; nothing in the ordinary compile path says the shape is unsupported. So // the author learns it from a Rust build failure in generated code they did not write, which is the // furthest possible position from the source construct that caused it. -data emitted_record_deserialize_gap_note: String = "A .dag record embedding a coproduct whose variant carries a sole_constructor type emits a Rust record deriving Deserialize over a Serialize-only private-field struct. The seed then fails to compile with E0277, and reading the carried field fails with E0616. The .dag is accepted; the refusal appears only in emitted Rust." +// A .dag record embedding a coproduct whose variant carries a sole_constructor type emits a Rust +// record deriving Deserialize over a Serialize-only private-field struct. The seed then fails to +// compile with E0277, and reading the carried field fails with E0616. The .dag is accepted; the +// refusal appears only in emitted Rust. type EmittedRecordDeserializeEvidence = RustcRefusal { @@ -121,7 +124,10 @@ data emitted_record_deserialize_gap_evidence: List = EnsurePlanAvailable { plan: P } | EnsurePlanUnavailable { reason: String } -data ensure_algebra_note: String = "Ensure is a convergence modality, never a roster of packages or tools. A caller supplies a typed desired requirement, an observation, its own satisfaction predicate, an authority policy, and an optional typed convergence plan. ensure_decide projects those facts onto the existing std.upsert_decision authority: satisfied -> Noop; known absent/drift + authorized plan -> Apply; observe-only, no-plan, and inaccessible -> Refuse. Effects remain capability-specific adapters. ensure_readback then projects the independent post-apply observation through std.realization_reconcile, so an Apply receipt cannot claim convergence without grounded readback. Fine grain means one requirement/observation/receipt per semantic capability; it does not mean one ensure function per executable." +// Ensure is a convergence modality, never a roster of packages or tools. A caller supplies a typed +// desired requirement, an observation, its own satisfaction predicate, an authority policy, and an +// optional typed convergence plan. ensure_decide projects those facts onto the existing +// std.upsert_decision authority: satisfied -> Noop; known absent/drift + authorized plan -> Apply; +// observe-only, no-plan, and inaccessible -> Refuse. Effects remain capability-specific adapters. +// ensure_readback then projects the independent post-apply observation through +// std.realization_reconcile, so an Apply receipt cannot claim convergence without grounded +// readback. Fine grain means one requirement/observation/receipt per semantic capability; it does +// not mean one ensure function per executable. fn ensure_decide( policy: EnsurePolicy, diff --git a/dag/gunbc/executor_schedule_retention.dag b/dag/gunbc/executor_schedule_retention.dag index 4b74c99ebfa..d7a1e8a2852 100644 --- a/dag/gunbc/executor_schedule_retention.dag +++ b/dag/gunbc/executor_schedule_retention.dag @@ -9,18 +9,35 @@ data schedule_retention_grain_is_per_module: Bool = true data schedule_retention_heads_stay_resident: Bool = true -data schedule_retention_heads_note: String = "Heads (the whole-pool parse snapshot and the bare/qualified census layers) are NOT per-module state and are never evicted - the #6848 bare-name census requires the whole-pool heads resident for the run's lifetime (heads-only parse #6956/#6972 precedent). Held by construction: only per-module cache keys are recorded for eviction." +// Heads (the whole-pool parse snapshot and the bare/qualified census layers) are NOT per-module +// state and are never evicted - the #6848 bare-name census requires the whole-pool heads resident +// for the run's lifetime (heads-only parse #6956/#6972 precedent). Held by construction: only +// per-module cache keys are recorded for eviction. data schedule_retention_retain_on_unknown: Bool = true // When reachability CANNOT be computed for a cached module (a provenance gap), the safe arm is RETAIN - correctness-fail-closed but memory-fail-open - so it is a typed, per-module, COUNTED RetentionUnknown row, never a silent retain-everything (that absorbing fallback is a T-as-ignorance widen whose frequency the corpus would never see, S5). data schedule_retention_release_and_eviction_counted_apart: Bool = true -data schedule_retention_release_vs_eviction_note: String = "A module is RELEASED when its retention refcount reaches zero; it is EVICTED only when that release actually dropped recorded cache state. The two are different facts and are counted apart (schedule_releases vs schedule_evictions, released_modules vs evicted_modules). They diverge on every run whose arming reaches modules the run never reconciles - the ordinary case, because arming spans the whole DISCOVERED roster while the affected set resolves a subset. Fusing them overstates the mechanism by that entire population: a live CI receipt read schedule_evictions=2200 against modules_refcounted=2200 and typed_cache_peak=883, i.e. it claimed to have evicted every module it had ever heard of, ~2.5x the modules that were ever cached, and a counter that equals the arm count by construction carries no information at all. Rust lockstep: SCHEDULE_RETENTION_RELEASE_AND_EVICTION_COUNTED_APART in cli_run.rs; discriminating RED: unreconciled_module_releases_without_evicting (an armed-but-never-reconciled module must release without evicting - re-fusing the counters reds it)." +// A module is RELEASED when its retention refcount reaches zero; it is EVICTED only when that +// release actually dropped recorded cache state. The two are different facts and are counted apart +// (schedule_releases vs schedule_evictions, released_modules vs evicted_modules). They diverge on +// every run whose arming reaches modules the run never reconciles - the ordinary case, because +// arming spans the whole DISCOVERED roster while the affected set resolves a subset. Fusing them +// overstates the mechanism by that entire population: a live CI receipt read +// schedule_evictions=2200 against modules_refcounted=2200 and typed_cache_peak=883, i.e. it claimed +// to have evicted every module it had ever heard of, ~2.5x the modules that were ever cached, and a +// counter that equals the arm count by construction carries no information at all. Rust lockstep: +// SCHEDULE_RETENTION_RELEASE_AND_EVICTION_COUNTED_APART in cli_run.rs; discriminating RED: +// unreconciled_module_releases_without_evicting (an armed-but-never-reconciled module must release +// without evicting - re-fusing the counters reds it). data schedule_retention_entry_line_asserts_execution: Bool = false +// A decrement past zero (a scheduled entry demands state the refcount said was fully consumed) is a +// schedule-derivation defect and REFUSES, typed and located - the mechanism never serves a wrong +// verdict against evicted state. Content-key recompute-on-miss is the correctness license; the +// refusal is the loudness that keeps a derivation bug from hiding. + // The per-entry drain line witnesses ONE fact: the schedule moved PAST that entry, so its per-module state can no longer be read. It asserts nothing about selection or execution - index_arm_schedule_retention walks every DISCOVERED row before any predict-skip, so the line also appears for entries the affected set skipped without ever resolving them. It was formerly spelled entry='...', which two independent readers hours apart took as proof the entry had been scheduled and run, and that misreading cost hours of misdirected diagnosis on a real selection defect. It is now schedule_passed_entry='...' beside an ARMED-line legend, because a label that asserts a fact its mechanism cannot support is a defect of the same family as a wrong number (DESIGN section 5: a diagnostic must be typed and located, and locating it on the wrong subject is not locating it). FOLDED as of the batch-grain projection: the line is routine work by observation_class_density, so it prints only when retention_unknown GROWS - the drain admitting it could not classify a module - or when the fold is off (verbose). The trigger is the increase rather than the running total, because the total is monotone and gating on it being nonzero would un-fold every remaining entry the moment the first unknown appeared. No count is lost: the aggregate [floor-drain] receipt: line still reports the totals at close, and what folding removes is the per-entry retelling, not the fact. data schedule_retention_underflow_refuses: Bool = true - -data schedule_retention_underflow_note: String = "A decrement past zero (a scheduled entry demands state the refcount said was fully consumed) is a schedule-derivation defect and REFUSES, typed and located - the mechanism never serves a wrong verdict against evicted state. Content-key recompute-on-miss is the correctness license; the refusal is the loudness that keeps a derivation bug from hiding." diff --git a/dag/gunbc/explicit_witness_admission.dag b/dag/gunbc/explicit_witness_admission.dag index 52b00d33b00..b1c3090e895 100644 --- a/dag/gunbc/explicit_witness_admission.dag +++ b/dag/gunbc/explicit_witness_admission.dag @@ -35,11 +35,55 @@ import std.types { NonEmptyStr, String, List, Bool, Int } import std.algebra { Cons, Empty } import std.dissolution { DissolutionCondition, unbound_dissolution } -data explicit_witness_admission_authority_note: String = "THE function-grain authority for exact witness admission. One row per admitted witness, carrying the witness identity, the cadence that executes it, and its own reason and dissolve_on. Everything else about that witness is DERIVED: the cadence roster the scheduler runs, the discovery exclusion, and the consumer manifest all read these rows; none of them is authored beside them. It replaces a DUAL REPRESENTATION carried until 2026-08-03 — a path-substring WitnessExclusionRow classified QuarantineProbeExpectRed (removing a whole FILE from discovery) sitting beside a probe_red(entry, f) roster row (enrolling one FUNCTION on the probe cadence). Those are not the same fact, but they were two hand-kept representations of one admission relationship with nothing deriving either from the other, and the reconciliation that guarded them was weaker than its name: it proved only that some scheduled row's entry CONTAINED the exclusion pattern as a substring. It proved neither exact identity, nor one schedule row per exclusion, nor reverse coverage, nor that every function hidden by the file-level exclusion had a consumer, nor that the pair deleted atomically. The cost was paid in a real merge: #7688 had to delete one exclusion/probe pair while hand-preserving two newly added ones, and taking either side wholesale would have dropped required admission rows with no conflict marker. The exact grain also dissolves the trade that forced the workaround: a green sibling in a quarantined file used to be excluded with it, so an author had to split the file or downgrade its classification and say so in prose (the receipt is the self_host_03_normalize exclusion row, whose reason reads that QuarantineProbeExpectRed on the file would orphan its sibling). A row here reaches exactly one function; siblings are untouched." - -data explicit_witness_admission_separation_note: String = "SEPARATION FROM PATH POLICY (operator ruling, this lane). Exact witness admission and broad path policy are different kinds of fact and must not share a substring-based representation — that fusion is the root of the substring-overlap weakness above. Directory- and file-wide policies (`test/claim/long/` is the long-test home; `test/manual/` is offline) stay in gunbc.ci_layer_roots witness_exclusion_frontier, which is now a PATH POLICY carrier only: witness_exclusion_row_well_formed REFUSES a QuarantineProbeExpectRed row there, so the fused representation is unwritable for this cadence rather than merely reconciled. PRECEDENCE, and it is total: a function with an exact admission is decided by that admission; path policy applies only to functions with none. That is why a path-offline file may legitimately contain an exact-admitted function (self_host_03_normalize_behavioral_witness_test.dag: the receipt is quarantined here, its sibling is offline by the file's path row) without either fact restating the other. THE REMAINING FUSION IS COUNTED, NOT SILENT: BinWitnessWet, FalsifierRehomedBinWet, FalsifierSubstrateLongLane and FalsifierSelfHostWet still author a path row beside their function-grain rosters. Their populations are not the bad state today — every one is either a file-grain roster row (ScheduleWitnessEntry's empty-function form, which covers every function in the entry by construction) or fully enumerated, measured on this change's tree — so they are a migration frontier, not a live defect. Dissolve-on: each cadence's roster moves into explicit_witness_admissions and its path rows delete, at which point witness_exclusion_row_well_formed refuses that cadence too and the last enum arm that can appear in the path carrier is gone." - -data explicit_witness_admission_axis_note: String = "One row carries all three axes of std.witness_admission independently: cadence (which consumer runs it), budget (how much eval time IT needs, never a property of the batch holding it) and expected (which verdict is agreement, at FUNCTION grain). The row is the single authority for each; the batch rosters, the scheduler ceiling and the expectation the executor matches per result are all DERIVED from it. Before this split a row declaring a long budget had to join a second batch to obtain one — see witness_admission_axis_separation_note for the measurement that produced the correction." +// THE function-grain authority for exact witness admission. One row per admitted witness, carrying +// the witness identity, the cadence that executes it, and its own reason and dissolve_on. +// Everything else about that witness is DERIVED: the cadence roster the scheduler runs, the +// discovery exclusion, and the consumer manifest all read these rows; none of them is authored +// beside them. It replaces a DUAL REPRESENTATION carried until 2026-08-03 — a path-substring +// WitnessExclusionRow classified QuarantineProbeExpectRed (removing a whole FILE from discovery) +// sitting beside a probe_red(entry, f) roster row (enrolling one FUNCTION on the probe cadence). +// Those are not the same fact, but they were two hand-kept representations of one admission +// relationship with nothing deriving either from the other, and the reconciliation that guarded +// them was weaker than its name: it proved only that some scheduled row's entry CONTAINED the +// exclusion pattern as a substring. It proved neither exact identity, nor one schedule row per +// exclusion, nor reverse coverage, nor that every function hidden by the file-level exclusion had a +// consumer, nor that the pair deleted atomically. The cost was paid in a real merge: #7688 had to +// delete one exclusion/probe pair while hand-preserving two newly added ones, and taking either +// side wholesale would have dropped required admission rows with no conflict marker. The exact +// grain also dissolves the trade that forced the workaround: a green sibling in a quarantined file +// used to be excluded with it, so an author had to split the file or downgrade its classification +// and say so in prose (the receipt is the self_host_03_normalize exclusion row, whose reason reads +// that QuarantineProbeExpectRed on the file would orphan its sibling). A row here reaches exactly +// one function; siblings are untouched. + +// SEPARATION FROM PATH POLICY (operator ruling, this lane). Exact witness admission and broad path +// policy are different kinds of fact and must not share a substring-based representation — that +// fusion is the root of the substring-overlap weakness above. Directory- and file-wide policies +// (`test/claim/long/` is the long-test home; `test/manual/` is offline) stay in +// gunbc.ci_layer_roots witness_exclusion_frontier, which is now a PATH POLICY carrier only: +// witness_exclusion_row_well_formed REFUSES a QuarantineProbeExpectRed row there, so the fused +// representation is unwritable for this cadence rather than merely reconciled. PRECEDENCE, and it +// is total: a function with an exact admission is decided by that admission; path policy applies +// only to functions with none. That is why a path-offline file may legitimately contain an +// exact-admitted function (self_host_03_normalize_behavioral_witness_test.dag: the receipt is +// quarantined here, its sibling is offline by the file's path row) without either fact restating +// the other. THE REMAINING FUSION IS COUNTED, NOT SILENT: BinWitnessWet, FalsifierRehomedBinWet, +// FalsifierSubstrateLongLane and FalsifierSelfHostWet still author a path row beside their +// function-grain rosters. Their populations are not the bad state today — every one is either a +// file-grain roster row (ScheduleWitnessEntry's empty-function form, which covers every function in +// the entry by construction) or fully enumerated, measured on this change's tree — so they are a +// migration frontier, not a live defect. Dissolve-on: each cadence's roster moves into +// explicit_witness_admissions and its path rows delete, at which point +// witness_exclusion_row_well_formed refuses that cadence too and the last enum arm that can appear +// in the path carrier is gone. + +// One row carries all three axes of std.witness_admission independently: cadence (which consumer +// runs it), budget (how much eval time IT needs, never a property of the batch holding it) and +// expected (which verdict is agreement, at FUNCTION grain). The row is the single authority for +// each; the batch rosters, the scheduler ceiling and the expectation the executor matches per +// result are all DERIVED from it. Before this split a row declaring a long budget had to join a +// second batch to obtain one — see witness_admission_axis_separation_note for the measurement that +// produced the correction. type ExplicitWitnessAdmission { witness: ScheduleWitnessEntry @@ -88,7 +132,13 @@ fn known_red_probe( ) } -data source_root_ingest_gate_admitted_witness_note: String = "Green exact admission for witnesses excluded from per-PR discovery by OfflineLocalRecipe transport posture but executed on the per-PR floor by v2.workflow.source_root_ingest_gate (run_compiler_closure_ingest_gate and siblings). Cadence is BinWitnessWet as the Wet host-overlay execution class only — these rows are NOT enrolled on gunbc.ci_layer_roots bin_witness_wet_entries; hermetic discovery there would bypass the file exclusion and refuse on an empty manifest. The executing consumer is the gate overlay ClaimRun list in v2.workflow.compiler_closure_ingest_transport." +// Green exact admission for witnesses excluded from per-PR discovery by OfflineLocalRecipe +// transport posture but executed on the per-PR floor by v2.workflow.source_root_ingest_gate +// (run_compiler_closure_ingest_gate and siblings). Cadence is BinWitnessWet as the Wet host-overlay +// execution class only — these rows are NOT enrolled on gunbc.ci_layer_roots +// bin_witness_wet_entries; hermetic discovery there would bypass the file exclusion and refuse on +// an empty manifest. The executing consumer is the gate overlay ClaimRun list in +// v2.workflow.compiler_closure_ingest_transport. fn source_root_ingest_gate_admitted_witness( entry: NonEmptyStr, @@ -108,7 +158,14 @@ fn source_root_ingest_gate_admitted_witness( } } -data known_red_pre_verdict_probe_note: String = "The narrow arm: a witness whose assertion never RUNS, because the run stops in an earlier phase. Its agreement is a typed refusal at the declared phase and cause, not a false verdict, and separating it is what stops an arbitrary corpus resolve failure from reading as every quarantine holding — see std.witness_admission witness_expected_verdict_split_note for the defect and for the bound on how exactly the observed cause is matched today. A row belongs here only when the phase and the cause are BOTH known and named; a witness that merely happens to be failing to resolve for an undiagnosed reason is ordinary known_red_probe, and its resolve failure is an infrastructure fact rather than agreement." +// The narrow arm: a witness whose assertion never RUNS, because the run stops in an earlier phase. +// Its agreement is a typed refusal at the declared phase and cause, not a false verdict, and +// separating it is what stops an arbitrary corpus resolve failure from reading as every quarantine +// holding — see std.witness_admission witness_expected_verdict_split_note for the defect and for +// the bound on how exactly the observed cause is matched today. A row belongs here only when the +// phase and the cause are BOTH known and named; a witness that merely happens to be failing to +// resolve for an undiagnosed reason is ordinary known_red_probe, and its resolve failure is an +// infrastructure fact rather than agreement. fn known_red_pre_verdict_probe( entry: NonEmptyStr, @@ -341,7 +398,12 @@ fn explicit_witness_admission_matches( && ((row.witness.function == function) || (row.witness.function == "")) } -data explicit_witness_admission_file_grain_note: String = "The empty function is ScheduleWitnessEntry's declared file-grain form (the same authority v1_claim_scoped_witness_batch_note names and expand_explicit_entries realizes for execution): it admits every test declaration in the entry. It is still EXACT — the entry is a path identity, not a substring — which is what separates it from the path policy carrier. Today no known-red row uses it; the arm exists so the derivations stay one fold when a cadence that does use it migrates in." +// The empty function is ScheduleWitnessEntry's declared file-grain form (the same authority +// v1_claim_scoped_witness_batch_note names and expand_explicit_entries realizes for execution): it +// admits every test declaration in the entry. It is still EXACT — the entry is a path identity, not +// a substring — which is what separates it from the path policy carrier. Today no known-red row +// uses it; the arm exists so the derivations stay one fold when a cadence that does use it migrates +// in. fn explicit_witness_admission_covers_in( rows: List, @@ -413,7 +475,13 @@ fn explicit_witness_admission_duplicate_count() -> Int { explicit_witness_admission_duplicate_count_in(rows: explicit_witness_admissions) } -data explicit_witness_admission_polarity_source_note: String = "POLARITY IS READ FROM THE EXPECTATION AXIS, NOT THE CADENCE. This predicate matched row.cadence until the axis split: QuarantineProbeExpectRed => true, every other arm false. That made the batch a witness's polarity — an expensive known-red row moved onto a long batch to obtain its budget silently became expected-GREEN, which is exactly how a witness that is red BY DESIGN reached the falsifier alert as a component failure. Reading row.expected instead means moving a row between consumers cannot change what counts as agreement, and a mixed batch is ordinary rather than a trap." +// POLARITY IS READ FROM THE EXPECTATION AXIS, NOT THE CADENCE. This predicate matched row.cadence +// until the axis split: QuarantineProbeExpectRed => true, every other arm false. That made the +// batch a witness's polarity — an expensive known-red row moved onto a long batch to obtain its +// budget silently became expected-GREEN, which is exactly how a witness that is red BY DESIGN +// reached the falsifier alert as a component failure. Reading row.expected instead means moving a +// row between consumers cannot change what counts as agreement, and a mixed batch is ordinary +// rather than a trap. fn explicit_witness_admission_is_known_red(row: ExplicitWitnessAdmission) -> Bool { match row.expected { @@ -445,7 +513,12 @@ fn explicit_witness_admission_executing_witness_roster() -> List List { reverse(fold( @@ -467,7 +540,11 @@ fn explicit_witness_admission_requires_long_eval_budget(row: ExplicitWitnessAdmi } } -data explicit_witness_admission_long_budget_roster_note: String = "The budget half of the split, derived. gunbc.ci_layer_roots unions this with the long-lane BATCH roster to give the scheduler its ceiling condition, so a row obtains its required eval time from its own declaration and never by joining a second batch. The union is the whole mechanism: batch membership still routes execution, this roster only sizes the ceiling, and the residual (declared by neither) keeps the fast lane so an undeclared lane reds at 5s rather than running unbounded." +// The budget half of the split, derived. gunbc.ci_layer_roots unions this with the long-lane BATCH +// roster to give the scheduler its ceiling condition, so a row obtains its required eval time from +// its own declaration and never by joining a second batch. The union is the whole mechanism: batch +// membership still routes execution, this roster only sizes the ceiling, and the residual (declared +// by neither) keeps the fast lane so an undeclared lane reds at 5s rather than running unbounded. fn witness_long_eval_budget_roster() -> List { reverse(fold( @@ -504,7 +581,14 @@ fn known_red_entry_is_v1_scoped(entry: String) -> Bool { data known_red_v1_envelope_dag_entries: List = [] -data known_red_v1_envelope_dag_entries_note: String = "dag/test/claim witnesses that import v1.* AND are enrolled QuarantineProbeExpectRed in explicit_witness_admissions — both are required for known_red_probe_v1_envelope_roster. Path prefix alone cannot detect v1 imports; this list names dag witnesses that need the dag plus src/v1 envelope beside v1_claim_scoped_witness_batch. Empty while no known-red row needs that envelope; enrolling a row here without the companion clamp row in gunbc_ci_floor_batch_clamp_params fails the ENROLLMENT COUPLING clause in v1_claim_scoped_known_red_probe_batch_note. Dissolve-on when closure-independent binding makes the envelope unnecessary." +// dag/test/claim witnesses that import v1.* AND are enrolled QuarantineProbeExpectRed in +// explicit_witness_admissions — both are required for known_red_probe_v1_envelope_roster. Path +// prefix alone cannot detect v1 imports; this list names dag witnesses that need the dag plus +// src/v1 envelope beside v1_claim_scoped_witness_batch. Empty while no known-red row needs that +// envelope; enrolling a row here without the companion clamp row in +// gunbc_ci_floor_batch_clamp_params fails the ENROLLMENT COUPLING clause in +// v1_claim_scoped_known_red_probe_batch_note. Dissolve-on when closure-independent binding makes +// the envelope unnecessary. fn known_red_entry_is_v1_envelope_dag_entry(entry: String) -> Bool { fold( @@ -533,9 +617,33 @@ fn known_red_probe_corpus_roster() -> List { )) } -data known_red_probe_budget_partition_note: String = "The known-red probe lane partitions into BUDGET-HOMOGENEOUS batches, and the partition is DERIVED from each row's declared budget rather than naming any witness (operator ruling, 2026-08-06). The first shape of the budget separation left the lane one mixed batch and let the eval ceiling be armed for it as a whole, because claim_executor scopes a ceiling to a batch; that raised the ceiling for every fast row sharing the batch with one declared-long row — a real widening, disclosed but still a widening, and the ceiling is the only thing standing between a runaway fast witness and the step budget. Two batches over the same roster remove it: each batch is homogeneous in declared budget, so the ceiling it arms is the budget its rows actually declared and no row is widened by a neighbour. Nothing here is a singleton and nothing names direct_rust — the long population is whatever declares SubstrateLongLaneEvalBudget, so it grows and shrinks with the admission rows and an empty side schedules no batch at all. Dissolve-on is unchanged and shared with witness_long_eval_budget_entries_note: per-witness declared cost envelopes replace the lane ceiling entirely, at which point the partition collapses back to one batch because every row carries its own bound." - -data known_red_probe_expectation_partition_note: String = "The lane partitions on BOTH axes, and the second half was missing until review caught it (cursor review 50216, 2026-08-07). Budget homogeneity alone still let an ExpectTypedPreVerdictRefusal row share a batch with ExpectAssertionFalse rows, and those two cannot coexist: a pre-verdict row stops the whole batch at RESOLVE, so no per-witness outcome exists for anyone, and the Err arm admits that stop only when EVERY entry declared a pre-verdict refusal. One pre-verdict row in a mixed batch therefore reds its siblings, which were holding quarantine correctly — the batch-wide coupling this PR exists to remove, reintroduced one axis over. So the corpus lane splits first by EXPECTATION KIND and then by DECLARED BUDGET: a batch is homogeneous in the fact that decides its verdict AND in the ceiling it needs. Note the pre-verdict batches are non-green by construction today (a declaration classifies a stop, it does not verify it), so the point of isolating them is not to make them pass — it is that their refusal stays THEIR refusal, located and countable, instead of being spread over rows it says nothing about." +// The known-red probe lane partitions into BUDGET-HOMOGENEOUS batches, and the partition is DERIVED +// from each row's declared budget rather than naming any witness (operator ruling, 2026-08-06). The +// first shape of the budget separation left the lane one mixed batch and let the eval ceiling be +// armed for it as a whole, because claim_executor scopes a ceiling to a batch; that raised the +// ceiling for every fast row sharing the batch with one declared-long row — a real widening, +// disclosed but still a widening, and the ceiling is the only thing standing between a runaway fast +// witness and the step budget. Two batches over the same roster remove it: each batch is +// homogeneous in declared budget, so the ceiling it arms is the budget its rows actually declared +// and no row is widened by a neighbour. Nothing here is a singleton and nothing names direct_rust — +// the long population is whatever declares SubstrateLongLaneEvalBudget, so it grows and shrinks +// with the admission rows and an empty side schedules no batch at all. Dissolve-on is unchanged and +// shared with witness_long_eval_budget_entries_note: per-witness declared cost envelopes replace +// the lane ceiling entirely, at which point the partition collapses back to one batch because every +// row carries its own bound. + +// The lane partitions on BOTH axes, and the second half was missing until review caught it (cursor +// review 50216, 2026-08-07). Budget homogeneity alone still let an ExpectTypedPreVerdictRefusal row +// share a batch with ExpectAssertionFalse rows, and those two cannot coexist: a pre-verdict row +// stops the whole batch at RESOLVE, so no per-witness outcome exists for anyone, and the Err arm +// admits that stop only when EVERY entry declared a pre-verdict refusal. One pre-verdict row in a +// mixed batch therefore reds its siblings, which were holding quarantine correctly — the batch-wide +// coupling this PR exists to remove, reintroduced one axis over. So the corpus lane splits first by +// EXPECTATION KIND and then by DECLARED BUDGET: a batch is homogeneous in the fact that decides its +// verdict AND in the ceiling it needs. Note the pre-verdict batches are non-green by construction +// today (a declaration classifies a stop, it does not verify it), so the point of isolating them is +// not to make them pass — it is that their refusal stays THEIR refusal, located and countable, +// instead of being spread over rows it says nothing about. fn known_red_probe_corpus_assertion_false_roster() -> List { reverse(fold( @@ -671,7 +779,13 @@ fn known_red_probe_execution_roster() -> List { known_red_probe_roster_of_kind(kind: ExecutionWitnessKind) } -data explicit_witness_admission_cadence_wall_note: String = "Only a cadence that EXECUTES may appear on an admission row: an admission is the statement that this witness runs somewhere, so DiscoverySelection (which is the absence of an admission), OfflineLocalRecipe and FixtureExplicitRoster (which are path policies, not executing consumers) and NoConsumer (which is the refusal itself) are all refused here by explicit_witness_admission_cadence_executes. Without that arm an author could write an admission whose cadence runs nothing and satisfy the every-witness-has-a-consumer wall with a row that executes nowhere — the coverage-by-illusion tier, one enum arm away." +// Only a cadence that EXECUTES may appear on an admission row: an admission is the statement that +// this witness runs somewhere, so DiscoverySelection (which is the absence of an admission), +// OfflineLocalRecipe and FixtureExplicitRoster (which are path policies, not executing consumers) +// and NoConsumer (which is the refusal itself) are all refused here by +// explicit_witness_admission_cadence_executes. Without that arm an author could write an admission +// whose cadence runs nothing and satisfy the every-witness-has-a-consumer wall with a row that +// executes nowhere — the coverage-by-illusion tier, one enum arm away. fn explicit_witness_admission_cadence_executes(cadence: WitnessConsumerCadence) -> Bool { match cadence { diff --git a/dag/gunbc/extdeps_scope_frontier.dag b/dag/gunbc/extdeps_scope_frontier.dag index 016424bf64d..d0331b3d1ce 100644 --- a/dag/gunbc/extdeps_scope_frontier.dag +++ b/dag/gunbc/extdeps_scope_frontier.dag @@ -1,14 +1,61 @@ module gunbc.extdeps_scope_frontier -data extdeps_scope_frontier_law: String = "Staged, fail-closed enrollment of the ExternalModelScope requirement (DESIGN §3 'External upstream decomposition'; operator verdicts on gunbc#7556 and gunbc#7571): every extdeps module must eventually carry one extdeps_model_scope: ExternalModelScope declaration. The corpus cannot be gated retroactively red in one change, so enrollment is staged at the storage grain the cover witness walks: every .dag file under dag/extdeps is EXACTLY ONE of scope_carrier_paths (carries a declared scope, admitted by execution), scope_machinery_exempt_paths (the citation vocabulary itself plus synthetic mock corpora — the v2.lens.mandatory_tag region-1 exemption semantics), or a row of the FROZEN legacy manifest (legacy_extdeps_scope_frontier_manifest_path). The live-cover witness (dag/test/claim/external_model_scope_live_cover_witness_test.dag — wet, discovery-excluded per the gunbc.ci_layer_roots wet-integration posture with a documented local recipe; its pure decision fns live HERE and their hermetic RED controls stay discovery-enrolled in dag/test/claim/external_model_scope_witness_test.dag) walks dag/extdeps live, parses the manifest, and refuses any file in none of the three. GRAIN HONESTY (codex review 46258 — the enforcement grain must be stated, not implied): the per-PR MERGE-GATED half is the hermetic one — every rostered path resolves on disk (roster_paths_resolve_on_disk over all three rosters via the filesystem_read checkout carve-out, so a stale or fabricated roster row reds per-PR), the three rosters stay disjoint, carrier files byte-declare the scope, and every pure decision fn keeps its discriminating RED control. The disk-to-roster direction — a NEW dag/extdeps file enrolled in none of the three — and the manifest freeze are ALSO per-PR merge-gated, at diff grain: tools.extdeps_scope_placement_gate (a wet cheap-floor gate added per codex review 46281; its pattern source, the publication placement gate, was later deleted by operator ruling in #7591 — this gate is independent of it) reads TWO bases, one per question: it refuses any dag/extdeps .dag file added by THIS CHANGE (origin/main...HEAD) outside carriers-union-machinery (scope_placement_refused_paths), and refuses any manifest row naming a file added after the freeze anchor over the whole post-freeze population (legacy_manifest_freeze_sha..HEAD, manifest_freeze_holds), with diff failure and parse truncation as refusals, never skips. Merge-base grain on the membership arm is what keeps the refusal attributable to the change that caused it; feeding that arm the freeze base instead made one unrostered file refuse every open branch (gunbc#7644 diagnosis), and the coverage that reading incidentally provided is declared, bounded, and triggered in scope_placement_base_split_note rather than dropped silently. The wet live-cover witness remains the whole-population re-verification (it walks what IS on disk rather than what the diff says was added) under its documented local recipe. The storage-grain frontier as a whole still dissolves into the v2.lens.mandatory_tag region promotion (compile grain, module-name grain); a new file's only landing states are scope carrier or genuine machinery; it does NOT join the manifest. There are no other exemption channels." +// Staged, fail-closed enrollment of the ExternalModelScope requirement (DESIGN §3 'External +// upstream decomposition'; operator verdicts on gunbc#7556 and gunbc#7571): every extdeps module +// must eventually carry one extdeps_model_scope: ExternalModelScope declaration. The corpus cannot +// be gated retroactively red in one change, so enrollment is staged at the storage grain the cover +// witness walks: every .dag file under dag/extdeps is EXACTLY ONE of scope_carrier_paths (carries a +// declared scope, admitted by execution), scope_machinery_exempt_paths (the citation vocabulary +// itself plus synthetic mock corpora — the v2.lens.mandatory_tag region-1 exemption semantics), or +// a row of the FROZEN legacy manifest (legacy_extdeps_scope_frontier_manifest_path). The live-cover +// witness (dag/test/claim/external_model_scope_live_cover_witness_test.dag — wet, +// discovery-excluded per the gunbc.ci_layer_roots wet-integration posture with a documented local +// recipe; its pure decision fns live HERE and their hermetic RED controls stay discovery-enrolled +// in dag/test/claim/external_model_scope_witness_test.dag) walks dag/extdeps live, parses the +// manifest, and refuses any file in none of the three. GRAIN HONESTY (codex review 46258 — the +// enforcement grain must be stated, not implied): the per-PR MERGE-GATED half is the hermetic one — +// every rostered path resolves on disk (roster_paths_resolve_on_disk over all three rosters via the +// filesystem_read checkout carve-out, so a stale or fabricated roster row reds per-PR), the three +// rosters stay disjoint, carrier files byte-declare the scope, and every pure decision fn keeps its +// discriminating RED control. The disk-to-roster direction — a NEW dag/extdeps file enrolled in +// none of the three — and the manifest freeze are ALSO per-PR merge-gated, at diff grain: +// tools.extdeps_scope_placement_gate (a wet cheap-floor gate added per codex review 46281; its +// pattern source, the publication placement gate, was later deleted by operator ruling in #7591 — +// this gate is independent of it) reads TWO bases, one per question: it refuses any dag/extdeps +// .dag file added by THIS CHANGE (origin/main...HEAD) outside carriers-union-machinery +// (scope_placement_refused_paths), and refuses any manifest row naming a file added after the +// freeze anchor over the whole post-freeze population (legacy_manifest_freeze_sha..HEAD, +// manifest_freeze_holds), with diff failure and parse truncation as refusals, never skips. +// Merge-base grain on the membership arm is what keeps the refusal attributable to the change that +// caused it; feeding that arm the freeze base instead made one unrostered file refuse every open +// branch (gunbc#7644 diagnosis), and the coverage that reading incidentally provided is declared, +// bounded, and triggered in scope_placement_base_split_note rather than dropped silently. The wet +// live-cover witness remains the whole-population re-verification (it walks what IS on disk rather +// than what the diff says was added) under its documented local recipe. The storage-grain frontier +// as a whole still dissolves into the v2.lens.mandatory_tag region promotion (compile grain, +// module-name grain); a new file's only landing states are scope carrier or genuine machinery; it +// does NOT join the manifest. There are no other exemption channels. data legacy_manifest_shape_note: String = "The legacy population lives OUTSIDE the semantic module as a frozen manifest artifact (operator verdict on gunbc#7571, 2026-08-01: 399 hand-listed rows plus a hand-moved count inside the .dag authority were a second, churn-heavy representation of the extdeps file population). The manifest is one repo-relative path per line; its row count is DERIVED by the parser, never hand-declared; the exact live diff (both directions) is the enforcement. Lifecycle: FROZEN, MECHANICALLY (codex review 46215 — 'review red by inspection' is not enforcement): every manifest row must name a file that already existed at legacy_manifest_freeze_sha, checked by the cover witness against git.Core.DiffNameStatus(freeze..HEAD) — a manifest row for a file added after the freeze refuses (manifest_freeze_holds over DiffAddedObservation), so a new module cannot be smuggled into the legacy population; rows are only ever REMOVED (a module migrates by adding its extdeps_model_scope declaration, moving to scope_carrier_paths, and deleting its manifest row in the same change) — enforced mechanically at line grain by the placement gate, not by review: see manifest_remove_only_note (codex review 46378). Carrier rows are content-verified by the same witness: each scope_carrier_paths file must actually declare extdeps_model_scope on disk, so an asserted-but-undeclared carrier refuses rather than passing on path membership. DISSOLVE-ON: when the manifest drains, the requirement is promoted to a second v2.lens.mandatory_tag region (required_decl_name extdeps_model_scope, required_type_name ExternalModelScope — the same compile-grain wall region 1 already runs for extdeps_external_authority_anchor), at which point the manifest, the parser, the freeze anchor, and the cover witness delete and the module-name grain replaces the storage grain. Paths are discriminators, not gospel (DESIGN §3): the storage grain exists only because the interim cover walker sees files." -data legacy_manifest_freeze_sha_note: String = "The git anchor that makes the manifest's freeze mechanical: the origin/main commit whose tree contains every manifest row. Any file first added AFTER this commit can never be a manifest row (tools.extdeps_scope_placement_gate and the live-cover witness diff freeze..HEAD and refuse the intersection); it must adopt a scope or be genuinely machinery. This anchor never advances while the manifest lives — advancing it would re-open the smuggling channel the freeze closes; it deletes with the manifest on region promotion. ONE recorded re-anchor, in the gate-landing change itself and legitimate exactly once: the original anchor d552ff4036b48bae835f24e9ead322475895d159 predated the gate's existence, and dag/extdeps/languages/lean/overflow.dag (gunbc#7550) then dag/extdeps/project_schedule/cpm_pert.dag (gunbc#7567) merged to main inside that wall-less interim; the anchor moved to the origin/main commit the gate lands against and those rows joined the manifest, because the freeze can only bind from the commit where the wall actually exists. From this commit forward the gate is per-PR merge-gated and the interim class is unreachable." +// The git anchor that makes the manifest's freeze mechanical: the origin/main commit whose tree +// contains every manifest row. Any file first added AFTER this commit can never be a manifest row +// (tools.extdeps_scope_placement_gate and the live-cover witness diff freeze..HEAD and refuse the +// intersection); it must adopt a scope or be genuinely machinery. This anchor never advances while +// the manifest lives — advancing it would re-open the smuggling channel the freeze closes; it +// deletes with the manifest on region promotion. ONE recorded re-anchor, in the gate-landing change +// itself and legitimate exactly once: the original anchor d552ff4036b48bae835f24e9ead322475895d159 +// predated the gate's existence, and dag/extdeps/languages/lean/overflow.dag (gunbc#7550) then +// dag/extdeps/project_schedule/cpm_pert.dag (gunbc#7567) merged to main inside that wall-less +// interim; the anchor moved to the origin/main commit the gate lands against and those rows joined +// the manifest, because the freeze can only bind from the commit where the wall actually exists. +// From this commit forward the gate is per-PR merge-gated and the interim class is unreachable. data legacy_manifest_freeze_sha: String = "5669d42719ba2a63439a0d3c867d963f0d9e8bc8" -data extdeps_scope_frontier_fixture_note: String = "The v2 red-control plants (src/v2/test/fixture/extdeps_external_authority/*/plant.dag) declare extdeps.* module names outside dag/extdeps; they are outside this walk and outside the requirement, exactly as they are exempt from mandatory-tag region 1." +// The v2 red-control plants (src/v2/test/fixture/extdeps_external_authority/*/plant.dag) declare +// extdeps.* module names outside dag/extdeps; they are outside this walk and outside the +// requirement, exactly as they are exempt from mandatory-tag region 1. data legacy_extdeps_scope_frontier_manifest_path: String = "dag/gunbc/legacy_extdeps_scope_frontier.tsv" @@ -25,13 +72,24 @@ fn scope_cover_holds(walked: List, union: List) -> Bool { && all(union, p => scope_frontier_strings_contain(xs: walked, s: p)) } -data carrier_content_verification_note: String = "codex review 46215, finding half 1: carrier membership was path-asserted only, so a scope_carrier_paths row whose file never declares extdeps_model_scope passed the cover. Closed: every carrier file's bytes must carry the declaration marker, checked by the live-cover witness. This is a text presence scan — a declared scaffold (the v2.lens.mandatory_tag corpus_scan / language_source_scaffold_index precedent), strictly better than path assertion, dissolving with the whole storage-grain frontier into the mandatory-tag region whose parse-grain reader replaces it." +// codex review 46215, finding half 1: carrier membership was path-asserted only, so a +// scope_carrier_paths row whose file never declares extdeps_model_scope passed the cover. Closed: +// every carrier file's bytes must carry the declaration marker, checked by the live-cover witness. +// This is a text presence scan — a declared scaffold (the v2.lens.mandatory_tag corpus_scan / +// language_source_scaffold_index precedent), strictly better than path assertion, dissolving with +// the whole storage-grain frontier into the mandatory-tag region whose parse-grain reader replaces +// it. fn carrier_content_declares_scope(content: String) -> Bool { string_contains(s: content, pattern: "data extdeps_model_scope:") } -data manifest_freeze_check_note: String = "codex review 46215, finding half 2: the manifest's remove-only lifecycle was review discipline, not mechanics. Closed: every manifest row must name a file that already existed at legacy_manifest_freeze_sha — the live-cover witness diffs freeze..HEAD with the same git.Core.DiffNameStatus + parse the publication placement gate uses, and refuses any manifest row in the added/copied/renamed set. Diff failure and parse truncation are typed refusal arms of DiffAddedObservation, never skips (a broken freeze probe must not report frozen)." +// codex review 46215, finding half 2: the manifest's remove-only lifecycle was review discipline, +// not mechanics. Closed: every manifest row must name a file that already existed at +// legacy_manifest_freeze_sha — the live-cover witness diffs freeze..HEAD with the same +// git.Core.DiffNameStatus + parse the publication placement gate uses, and refuses any manifest row +// in the added/copied/renamed set. Diff failure and parse truncation are typed refusal arms of +// DiffAddedObservation, never skips (a broken freeze probe must not report frozen). type DiffAddedObservation = AddedPathsObserved { paths: List } @@ -47,7 +105,25 @@ fn manifest_freeze_holds(manifest: List, observation: DiffAddedObservati } } -data manifest_remove_only_note: String = "The remove-only lifecycle, enforced mechanically at line grain (codex review 46378: manifest_freeze_holds judges file CREATION dates, so a pre-freeze file migrated out of the manifest could be re-added later and the freeze check would pass — the escape hatch re-opened after migration). Closed: the placement gate also observes git.Core.DiffUnified0(origin/main...HEAD) and refuses any line the change itself ADDS to the manifest (manifest_line_observation; the ONE verdict surface over the observation is tools.extdeps_scope_placement_gate.manifest_remove_only_exit — no parallel Bool predicate exists, per codex review 46387). The one admitted arm is ManifestIntroduced — the file absent at the merge base (the unified diff's /dev/null old-side), which is exactly the PR that first lands the manifest and is unforgeable afterward: once the manifest exists on origin/main, a delete-and-rewrite inside a branch still diffs as modification, never introduction. Unrecognized structure refuses, never skips, and the refusal arm is REACHABLE FROM THE PARSER (codex review 46387: an unreadable arm nothing constructs is fail-open by fall-through): any diff text that mentions the manifest path without ever presenting its recognized new-side header yields ManifestDiffUnreadable — which also makes a manifest DELETION refuse conservatively (its old-side header mentions the path, no new-side header follows; the promotion PR that legitimately deletes the manifest deletes this gate in the same change). Removals stay free (a migration deletes its row), so the manifest is monotonically shrinking BY CONSTRUCTION at the merge gate, not by review discipline." +// The remove-only lifecycle, enforced mechanically at line grain (codex review 46378: +// manifest_freeze_holds judges file CREATION dates, so a pre-freeze file migrated out of the +// manifest could be re-added later and the freeze check would pass — the escape hatch re-opened +// after migration). Closed: the placement gate also observes +// git.Core.DiffUnified0(origin/main...HEAD) and refuses any line the change itself ADDS to the +// manifest (manifest_line_observation; the ONE verdict surface over the observation is +// tools.extdeps_scope_placement_gate.manifest_remove_only_exit — no parallel Bool predicate exists, +// per codex review 46387). The one admitted arm is ManifestIntroduced — the file absent at the +// merge base (the unified diff's /dev/null old-side), which is exactly the PR that first lands the +// manifest and is unforgeable afterward: once the manifest exists on origin/main, a +// delete-and-rewrite inside a branch still diffs as modification, never introduction. Unrecognized +// structure refuses, never skips, and the refusal arm is REACHABLE FROM THE PARSER (codex review +// 46387: an unreadable arm nothing constructs is fail-open by fall-through): any diff text that +// mentions the manifest path without ever presenting its recognized new-side header yields +// ManifestDiffUnreadable — which also makes a manifest DELETION refuse conservatively (its old-side +// header mentions the path, no new-side header follows; the promotion PR that legitimately deletes +// the manifest deletes this gate in the same change). Removals stay free (a migration deletes its +// row), so the manifest is monotonically shrinking BY CONSTRUCTION at the merge gate, not by review +// discipline. type ManifestLineObservation = ManifestUntouched @@ -111,8 +187,21 @@ fn manifest_line_observation(diff_text: String) -> ManifestLineObservation { } } - -data scope_placement_decision_note: String = "The per-PR merge-gated disk-to-roster direction (codex review 46281: the new-file invariant must not wait for the wet lane). scope_placement_refused_paths is the pure decision kernel of tools.extdeps_scope_placement_gate: given the paths a diff reports as ADDED, every such dag/extdeps .dag file must be a member of scope_carrier_paths or scope_machinery_exempt_paths — the frozen manifest is not a landing state for a new file, so it is not consulted for admission (only for the freeze check, manifest_freeze_holds). THE BASE THIS KERNEL IS FED IS THE MERGE BASE origin/main...HEAD, not the freeze anchor (corrected in the gunbc#7644 follow-up; see tools.extdeps_scope_placement_gate scope_placement_base_split_note for the full rationale and the declared coverage narrowing). An earlier revision of this note claimed 'only paths the PR itself adds are judged' while the gate fed the kernel the freeze-base population, so the sentence was true of the INTENT and false of the MECHANISM — the exact defect that let one unrostered file refuse every branch in the fleet. The kernel itself is base-agnostic: it judges whatever added-path list it is handed, which is why the fix was a call-site base and not a change here. The wet live-cover witness remains the whole-population re-verification (walks what IS on disk rather than what the diff SAYS was added)." +// The per-PR merge-gated disk-to-roster direction (codex review 46281: the new-file invariant must +// not wait for the wet lane). scope_placement_refused_paths is the pure decision kernel of +// tools.extdeps_scope_placement_gate: given the paths a diff reports as ADDED, every such +// dag/extdeps .dag file must be a member of scope_carrier_paths or scope_machinery_exempt_paths — +// the frozen manifest is not a landing state for a new file, so it is not consulted for admission +// (only for the freeze check, manifest_freeze_holds). THE BASE THIS KERNEL IS FED IS THE MERGE BASE +// origin/main...HEAD, not the freeze anchor (corrected in the gunbc#7644 follow-up; see +// tools.extdeps_scope_placement_gate scope_placement_base_split_note for the full rationale and the +// declared coverage narrowing). An earlier revision of this note claimed 'only paths the PR itself +// adds are judged' while the gate fed the kernel the freeze-base population, so the sentence was +// true of the INTENT and false of the MECHANISM — the exact defect that let one unrostered file +// refuse every branch in the fleet. The kernel itself is base-agnostic: it judges whatever +// added-path list it is handed, which is why the fix was a call-site base and not a change here. +// The wet live-cover witness remains the whole-population re-verification (walks what IS on disk +// rather than what the diff SAYS was added). fn scope_placement_refused_paths(added: List, carriers: List, machinery: List) -> List { added |> filter(p => diff --git a/dag/gunbc/fabric/fabric_capacity_class_gap.dag b/dag/gunbc/fabric/fabric_capacity_class_gap.dag index a61f18e9f29..c99457839b9 100644 --- a/dag/gunbc/fabric/fabric_capacity_class_gap.dag +++ b/dag/gunbc/fabric/fabric_capacity_class_gap.dag @@ -7,29 +7,30 @@ import std.decl_ref { DeclarationRef, decl_ref } // // Capacity has classes. Customer execution -- CI, agent tasks, batch -- is offerable. Control plane // -- scheduler, reconciliation, admission, receipts -- is not: if control capacity appears as -// available vCPU, available RAM, or a cheap slot, the allocator can schedule customer work onto the -// resources that run the allocator. Nothing in the fabric supply model expresses that distinction, -// so nothing refuses it. +// available vCPU, RAM, or a cheap slot, the allocator can schedule customer work onto the resources +// that run the allocator. Nothing in the fabric supply model expresses that distinction, so +// nothing refuses it. // // WHAT MAKES THIS A FINDING RATHER THAN A PREFERENCE is that the protection appears to exist and // does not reach. gunbc.fabric_control_plane_charge is real, host-parametric, and CHARGED: on the -// owned fleet a control-plane resident is SUBTRACTED from what a host can commit, which is exactly -// why control capacity never becomes offerable there. That mechanism is host-budget arithmetic and -// it lives entirely on the fleet side. An Offer is a product-side record. Nothing joins them. +// owned fleet a control-plane resident is SUBTRACTED from what a host can commit, which is why +// control capacity never becomes offerable there. That is host-budget arithmetic living entirely +// on the fleet side. An Offer is a product-side record. Nothing joins them. // // So the protection does not WEAKEN at the external-supplier seam -- it was never present on that -// side of it. Reading "control-plane capacity is charged" as a property of the fabric is the +// side. Reading "control-plane capacity is charged" as a property of the fabric is the // authority-substitution failure: the fact lives in gunbc.fleet_host_budget, the operation is // governed by product.fabric.supply, and no carrier claims the arrow between them. -data fabric_capacity_class_gap_note: String = "Measured gap: the fabric supply model has no capacity class, so nothing structurally prevents control-plane capacity from being offered as customer-executable supply. The fleet-side charge that protects the owned fleet has no product-side reach." +// Measured gap: the fabric supply model has no capacity class, so nothing structurally prevents +// control-plane capacity from being offered as customer-executable supply. The fleet-side charge +// protecting the owned fleet has no product-side reach. // EACH MEASUREMENT NAMES THE TREE IT WAS TAKEN AGAINST, BECAUSE THE OFFER RECORD IS MOVING. // -// The Offer field census below was taken on the fabric/isolation-model head (gunbc#8981), which is -// the WIDEST that record has ever been -- it adds isolation to the fields main carries. Measuring -// the narrower record on main and reporting "no capacity class" would have been true of a smaller -// surface and would have invited the reply that the field landed since. Naming the base makes the -// measurement re-runnable rather than merely asserted. +// The Offer field census below was taken on the fabric/isolation-model head (gunbc#8981), the +// WIDEST that record has ever been -- it adds isolation to the fields main carries. Measuring the +// narrower record on main would have been true of a smaller surface and invited the reply that the +// field landed since. Naming the base makes the measurement re-runnable rather than asserted. data fabric_capacity_class_measurement_base: String = "gunbc#8981 head (fabric/isolation-model), the widest the Offer record has been; main carries a strict subset of these fields" type CapacityClassEvidence @@ -49,11 +50,10 @@ type CapacityClassEvidence unreached_layer: NonEmptyStr } -// MEASUREMENT ONE. The vocabulary a capacity class would have to be spelled in appears nowhere in -// the supply model. The single hit is the word "customer" inside a prose comment about displacement -// -- prose, not a declaration -- which is recorded rather than rounded to zero, because "one hit, -// and here is why it does not count" is a stronger claim than "no hits" and survives someone -// re-running the grep. +// MEASUREMENT ONE. The vocabulary a capacity class would be spelled in appears nowhere in the +// supply model. The single hit is the word "customer" inside a prose comment about displacement -- +// prose, not a declaration -- recorded rather than rounded to zero, because "one hit, and why it +// does not count" is a stronger claim than "no hits" and survives someone re-running the grep. data no_class_carrier: CapacityClassEvidence = NoClassCarrierAnywhere { searched_modules: [ decl_ref(module_path: "product.fabric.supply", decl_name: "Offer"), @@ -66,9 +66,9 @@ data no_class_carrier: CapacityClassEvidence = NoClassCarrierAnywhere { } // MEASUREMENT TWO. The one field shaped like it could hold the distinction cannot hold it as a -// fact. This is the measurement that makes the gap worse than an absence: a branded string can be -// made to carry the class by convention, and a convention READS AS COVERAGE while being -// unenforceable. Absence is at least visible. +// fact. This makes the gap worse than an absence: a branded string can carry the class by +// convention, and a convention READS AS COVERAGE while being unenforceable. Absence is at least +// visible. data plausible_carrier_is_untyped: CapacityClassEvidence = PlausibleCarrierIsUntyped { carrier: decl_ref(module_path: "product.fabric.work", decl_name: "TrustDomainRef"), representation: "NonEmptyStr where brand" as NonEmptyStr, @@ -95,16 +95,15 @@ data fabric_capacity_class_evidence: List = [ // // Ruled by the product-direction lane 2026-08-23: THE CLASS GOES ON THE WORK, NOT THE OFFER. // -// An offer is a thing a SUPPLIER SAYS ABOUT CAPACITY. Putting the class there asks the party with -// the least knowledge and the most incentive to say yes to make the safety assertion, and a -// supplier that simply omits the field is admitted -- the fail-open arm in its purest form. It is -// also unfalsifiable from this side: we cannot check a claim about the nature of someone else's -// machine. +// An offer is what a SUPPLIER SAYS ABOUT CAPACITY. Putting the class there asks the party with the +// least knowledge and the most incentive to say yes to make the safety assertion, and a supplier +// that omits the field is admitted -- the fail-open arm in its purest form. It is also +// unfalsifiable from this side: we cannot check a claim about someone else's machine. // -// The work side is where the fact's authority already is. We know whether a unit of demand is -// scheduler, reconciliation, admission or receipt work, because we originated it. Shape already -// carries hard requirements and an envelope, so a capacity class beside them makes "the allocator -// consumed the allocator" a MATCHING REFUSAL rather than a policy someone remembered to apply. +// The work side already holds the fact's authority: we know whether a unit of demand is scheduler, +// reconciliation, admission or receipt work, because we originated it. Shape already carries hard +// requirements and an envelope, so a capacity class beside them makes "the allocator consumed the +// allocator" a MATCHING REFUSAL rather than a policy someone remembered to apply. type CapacityClassRemedy = ClassOnWorkShape { rationale: NonEmptyStr } | ClassOnSupplierOffer { rationale: NonEmptyStr } @@ -133,45 +132,46 @@ data capacity_class_refused_remedies: List = [ // WHAT THIS CARRIER DOES AND DOES NOT ASSERT. // -// It records three executed measurements and the ruling that followed them. It does NOT assert that -// the class has been modeled, that any refusal exists, or that any offer is currently being checked -// against one. Nothing in this module is consumed by admission, and that is the honest state: the -// gap is open, and this is the row that keeps it countable rather than rediscovered. +// It records three executed measurements and the ruling that followed. It does NOT assert that the +// class has been modeled, that any refusal exists, or that any offer is checked against one. +// Nothing here is consumed by admission: the gap is open, and this row keeps it countable rather +// than rediscovered. // -// RUNG: this class sits BELOW mitigatable, because there is no failure to contain -- the invalid -// state is simply representable and unremarked. It is not on the ladder yet. Its next-rung trigger -// is the ruled remedy landing on Shape, at which point it becomes a matching refusal and can be -// measured as one. Recorded here rather than left to be rediscovered, per §4b(2): the difference -// between "cannot climb" and "can climb now but unbuilt" is the whole point of naming the trigger. +// RUNG: BELOW mitigatable, because there is no failure to contain -- the invalid state is simply +// representable and unremarked. It is not on the ladder yet. Its next-rung trigger is the ruled +// remedy landing on Shape, at which point it becomes a matching refusal and can be measured as one. +// Recorded per §4b(2): the difference between "cannot climb" and "can climb now but unbuilt" is the +// whole point of naming the trigger. // THE CITATIONS IN THIS MODULE WERE NOT CHECKED BY ANYTHING, AND THEY NOW ARE. The measurement -// below is kept because it was taken rather than assumed, and because the state it found is the -// exact gap that closed: as of 2026-08-25 `v1_compiler.declaration_index` resolves EVERY authored +// below is kept because it was taken rather than assumed, and the state it found is the exact gap +// that closed: as of 2026-08-25 `v1_compiler.declaration_index` resolves EVERY authored // `DeclarationRef` in a non-fixture module inside the parse phase of `--required-ci`, so the eight -// above are enrolled and a stale citation here now stops the line. Read what follows as the -// account of a closed gap, not a live one -- this heading asserted the opposite in the present -// tense until 2026-08-26. +// above are enrolled and a stale citation here stops the line. What follows is the account of a +// closed gap, not a live one -- this heading asserted the opposite in the present tense until +// 2026-08-26. // -// The eight DeclarationRefs above look like the ones the cited-symbol gate enforces. They are not. -// Replacing "TrustDomainRef" with a fabricated name and re-running --required-cited-symbol +// The eight DeclarationRefs above look like the ones the cited-symbol gate enforces. They were +// not. Replacing "TrustDomainRef" with a fabricated name and re-running --required-cited-symbol // REPORTED OK with checked=390, unchanged -- before and after this module gained an importing -// witness. So a stale citation here would not be caught BY THAT CENSUS. Past tense as of -// 2026-08-26: that flag and its census are deleted and the measurement cannot be re-taken through -// them. What replaced them does not share the limitation that made this measurement worth taking: -// the declaration index enrolls by walking the `Node` tree for `DeclarationRef` literals rather -// than from a hand-named carrier population, so a module cannot be outside it by omission. +// witness. So a stale citation here was not caught BY THAT CENSUS. Past tense as of 2026-08-26: +// that flag and its census are deleted and the measurement cannot be re-taken through them. The +// replacement does not share the limitation: the declaration index enrolls by walking the `Node` +// tree for `DeclarationRef` literals rather than from a hand-named carrier population, so a +// module cannot be outside it by omission. // -// THIS IS A DECLARED SCOPE, NOT A DEFECT, and saying so is the point of writing it down. The lens's -// own law states its population: every DeclarationRef enrolled in -// hand_authored_production_doc_declaration_refs, "structural, not corpus text scan" -- the doc -// graph's binds, which is exactly why a stale ref in gunbc.doc_graph_roots IS refused. Its declared -// dissolve-on already names this: enrollment widens to every structural DeclarationRef carrier when -// the Node-tree reader lands. +// THIS IS A DECLARED SCOPE, NOT A DEFECT. The lens's own law states its population: every +// DeclarationRef enrolled in hand_authored_production_doc_declaration_refs, "structural, not +// corpus text scan" -- the doc graph's binds, which is why a stale ref in gunbc.doc_graph_roots IS +// refused. Its declared dissolve-on already names this: enrollment widens to every structural +// DeclarationRef carrier when the Node-tree reader lands. // // So the honest state for a reader of THIS module: its citations were verified once, by hand, at -// authoring time, and nothing re-verifies them. That is strictly weaker than the gate a reader -// would reasonably assume from seeing decl_ref used, and assuming it is the mistake this block -// exists to prevent -- a citation that looks enforced and is not is worse than a plain string, -// because the shape reads as coverage. -data fabric_capacity_class_citation_coverage: String = "The DeclarationRefs in this module are OUTSIDE the cited-symbol lens population, which enrolls doc-graph binds rather than every carrier. Measured: a fabricated decl_name here leaves the gate green at checked=390. Verified by hand at authoring; nothing re-verifies. Closes when the lens's declared dissolve-on lands (enrollment widens to every structural DeclarationRef carrier)." +// authoring time, and nothing re-verified them -- strictly weaker than the gate a reader would +// assume from seeing decl_ref used. A citation that looks enforced and is not is worse than a +// plain string, because the shape reads as coverage. +// The DeclarationRefs in this module are OUTSIDE the cited-symbol lens population, which enrolls +// doc-graph binds rather than every carrier. Measured: a fabricated decl_name here leaves the gate +// green at checked=390. Verified by hand at authoring; nothing re-verifies. Closes when the lens's +// declared dissolve-on lands (enrollment widens to every structural DeclarationRef carrier). data fabric_capacity_class_next_rung_trigger: String = "a capacity class on product.fabric.work Shape, making control-plane demand a matching refusal rather than an unexpressed distinction; until then this class is not on the guarantee ladder at all" diff --git a/dag/gunbc/fabric/fabric_cell_converge.dag b/dag/gunbc/fabric/fabric_cell_converge.dag index ee5b31c26be..33379b8fca7 100644 --- a/dag/gunbc/fabric/fabric_cell_converge.dag +++ b/dag/gunbc/fabric/fabric_cell_converge.dag @@ -42,7 +42,13 @@ data fabric_cell_converge_authority_disposition: Disposition = SingleAuthority // spelling of what parts a cell has, and would have had to be kept in step with this one by hand. // It is gone: the addresses are the parts, each with its own observation and its own remedy, and // ownership is a member field rather than a step. -data fabric_cell_substrate_is_not_offerable_note: String = "Substrate-provisioned establishes that the cell root, its attempt-state root and its resource boundary exist as declared. It does NOT mean an executor is ready, and this module publishes no SupplierOffer, exposes no member to an allocator, and starts no process. There is no fabric agent in this tree; when an executable entry and its configuration exist, its unit and registration become establishable facts and enter as their own addresses. Offerability is a separate join over host capacity standing, cell readiness and admitted capacity class, and it lives with supply publication." +// Substrate-provisioned establishes that the cell root, its attempt-state root and its resource +// boundary exist as declared. It does NOT mean an executor is ready, and this module publishes no +// SupplierOffer, exposes no member to an allocator, and starts no process. There is no fabric agent +// in this tree; when an executable entry and its configuration exist, its unit and registration +// become establishable facts and enter as their own addresses. Offerability is a separate join over +// host capacity standing, cell readiness and admitted capacity class, and it lives with supply +// publication. // THE CELL'S IDENTITY IS THE SLOT'S IDENTITY. gunbc.build_cache_instance runner_slot_instance_name // is already the single authority for how a slot identity is spelled ("srv3-06"), and a fabric cell @@ -963,7 +969,6 @@ fn fabric_cell_unexpected_insert_ordered( ) } - fn fabric_cell_slot_is_expected( expected_slots: List, slot: RunnerSlotIdentity, diff --git a/dag/gunbc/fabric/fabric_control_plane_charge.dag b/dag/gunbc/fabric/fabric_control_plane_charge.dag index 074b1c312c8..9b765370380 100644 --- a/dag/gunbc/fabric/fabric_control_plane_charge.dag +++ b/dag/gunbc/fabric/fabric_control_plane_charge.dag @@ -11,10 +11,10 @@ data fabric_control_plane_charge_authority: Disposition = SingleAuthority { // THE THREE ARMS ARE THE POINT AND A ByteSize? WOULD HAVE MERGED TWO OF THEM. "Not placed on this // host" and "placed on this host, size unknown" have OPPOSITE conservation consequences: the first -// is genuinely a zero charge, the second must refuse the host plan. An optional byte size with -// Absent meaning zero cannot tell them apart, and the arm that gets silently chosen is the one that -// keeps the current plan green -- the fabricated-plausible-output failure with a capacity number -// attached. So the distinction is carried in the type rather than in a convention about Absent. +// is a genuine zero charge, the second must refuse the host plan. An optional byte size with +// Absent meaning zero cannot tell them apart, and the arm silently chosen is the one that keeps +// the current plan green -- fabricated plausible output with a capacity number attached. So the +// distinction is carried in the type rather than in a convention about Absent. type HostFabricControlPlaneClaim = ControlPlaneNotPlaced | ControlPlaneClaimResolved { memory: ByteSize } @@ -34,40 +34,40 @@ type FabricControlPlanePlacement // THE LIVE ROW IS Unplaced AND THAT IS THE TRUE FACT RATHER THAN A STAND-IN. The fabric control // plane is not deployed on any host, so every host's claim is a genuine zero and conservation is -// unchanged by this module landing. Writing StaticPinned here to manufacture a forcing function -// would be authoring a false row about the fleet, which is worse than the deferral it buys. +// unchanged by this module landing. Writing StaticPinned to manufacture a forcing function would +// author a false row about the fleet, worse than the deferral it buys. // // WHY THIS IS NOT A WALL THAT CANNOT FIRE, stated with the test rather than the assurance. The -// discriminating question is: what edit makes the refusal fire, and is it an edit someone makes in -// the ordinary course of the work? Here it is ONE row, written by the person deploying the control -// plane, at the moment they deploy it -- the refusal fires exactly when the fact it guards becomes -// true. Contrast the case this pattern is usually hiding, where no edit to any row reaches the -// refusal because the production input is a different population and the producer must be rebuilt -// first. That one is unfirable; this one has a precondition that has not occurred. +// discriminating question: what edit makes the refusal fire, and is it an edit someone makes in +// the ordinary course of the work? Here it is ONE row, written by the person deploying the +// control plane, at the moment they deploy it -- the refusal fires exactly when the fact it guards +// becomes true. Contrast the case this pattern usually hides, where no edit to any row reaches the +// refusal because the production input is a different population and the producer must be +// rebuilt first. That one is unfirable; this one has a precondition that has not occurred. // -// THAT DISTINCTION ONLY HOLDS WHILE THE PRECONDITION IS SCHEDULED, so the trigger is named rather -// than left open-ended: NEXT STEP is to measure the broker's resource envelope by running it and -// recording what it uses, which is a task producing a FACT and not a decision awaiting a ruling. -// A wall whose precondition never occurs becomes indistinguishable from an unfirable one, and the -// whole difference is whether anyone is scheduled to make it true. +// THAT DISTINCTION ONLY HOLDS WHILE THE PRECONDITION IS SCHEDULED, so the trigger is named: NEXT +// STEP is to measure the broker's resource envelope by running it and recording what it uses, a +// task producing a FACT and not a decision awaiting a ruling. A wall whose precondition never +// occurs becomes indistinguishable from an unfirable one, and the whole difference is whether +// anyone is scheduled to make it true. data fabric_control_plane_placement: FabricControlPlanePlacement = ControlPlaneUnplaced // AND THE FIRST VERSION OF THIS MODULE SHIPPED A Bool PREDICATE THAT UNDERCUT THE PARAGRAPH ABOVE. // control_plane_claim_is_unmeasured returned true for one arm and false for the others, had zero -// call sites anywhere in the tree, and existed three lines below a comment explaining why no such -// projection should exist. The point of having no projection is that the coproduct must be matched; -// shipping an unconsumed Bool view of it reopens exactly the spelling the paragraph forbids, and -// leaves a reader unsure which of the two the module actually means. Deleted (review 54686). -// THERE IS NO claim -> ByteSize PROJECTION, AND ITS ABSENCE IS THE DESIGN. An earlier revision of -// this module had one, with an annotation saying the unmeasured arm was deliberately absent from it -// -- and a body mapping that arm to zero. The prose and the code disagreed inside one declaration, -// and the code was doing the thing the prose forbade: charging nothing for a resident whose size -// nobody knows. A total function from this coproduct to ByteSize CANNOT exist honestly, because one -// arm has no bytes to return and every value it could return is a lie about a different arm. +// call sites, and sat three lines below a comment explaining why no such projection should exist. +// The point of having no projection is that the coproduct must be matched; an unconsumed Bool view +// reopens exactly the spelling the paragraph forbids and leaves a reader unsure which the module +// means. Deleted (review 54686). +// THERE IS NO claim -> ByteSize PROJECTION, AND ITS ABSENCE IS THE DESIGN. An earlier revision had +// one, with an annotation saying the unmeasured arm was deliberately absent from it -- and a body +// mapping that arm to zero. Prose and code disagreed inside one declaration, and the code did what +// the prose forbade: charged nothing for a resident whose size nobody knows. A total function from +// this coproduct to ByteSize CANNOT exist honestly, because one arm has no bytes to return and +// every value it could return is a lie about a different arm. // // So the consumer matches the claim itself and refuses on the unmeasured arm. That is construction // rather than validation: with no projection to call, "charge the control plane" has no spelling -// that skips the refusal, instead of having one that a reviewer must notice. +// that skips the refusal, instead of one a reviewer must notice. // fn host_fabric_control_plane_claim(host: HostIdentity) -> HostFabricControlPlaneClaim { host_fabric_control_plane_claim_under(host: host, placement: fabric_control_plane_placement) diff --git a/dag/gunbc/fabric/fabric_witness_run.dag b/dag/gunbc/fabric/fabric_witness_run.dag index d24ebe39711..6c3af08bb64 100644 --- a/dag/gunbc/fabric/fabric_witness_run.dag +++ b/dag/gunbc/fabric/fabric_witness_run.dag @@ -310,94 +310,75 @@ fn required_witnesses_build_frozen_identity_wire( // THE FLOOR'S PROGRAM AND CONTRACTS. NOTHING ELSE. // -// This module used to own authorize_floor_run and a FloorRunRefusal coproduct. -// Both moved to product.fabric.supply, because comparing a Work's requirements -// against a SupplierOffer's capabilities is provider-neutral fungibility and nothing in -// ShapeNotCovered / TrustDomainMismatch / CapabilitiesNotOffered was ever about -// the floor. A gunbc module answering a fabric question is a gunbc module that -// will answer it differently from the next consumer. +// authorize_floor_run and the FloorRunRefusal coproduct moved to product.fabric.supply: comparing +// a Work's requirements against a SupplierOffer's capabilities is provider-neutral fungibility, and +// nothing in ShapeNotCovered / TrustDomainMismatch / CapabilitiesNotOffered was about the floor. A +// gunbc module answering a fabric question answers it differently from the next consumer. // // THE FABRIC DEFINES THE WITNESS RUN. IT DOES NOT YET RUN IT. // -// THIS PARAGRAPH PREVIOUSLY CLAIMED THE OPPOSITE AND THE CLAIM WAS FALSE. It read -// that the argv is "PRODUCED BY THE FABRIC UNDER A GRANT" and that "no grant, no -// command -- so the authorization is not a label beside the run, it is the only -// thing that can yield a run". Measured against the live tree: the run command -// is a lane word away from NULLARY, there is no grant anywhere on the workflow path, and of the ten -// functions in this module exactly ONE has a production consumer outside it -- -// this one (`required_ci_lane_command`, through its two lane bindings), imported by -// gunbc.witness_floor_workflow to render a shell word. -// Work contract, execution requirements, offer fungibility, affordability, buy -// terms, admission terms, satisfaction requirement and grant duration have ZERO. -// -// So the sentence described a wall that does not exist, in the module that IS this -// repository's claim to running its own CI on the fabric. That is rung inflation -// (DESIGN section 4b), and it is worse than sitting low: a class reported at the -// top rung never ranks for climbing, and a reader planning the cutover against -// this paragraph would have budgeted for extending an allocator that was never -// built. It cost exactly that once already -- answering "where is the fabric in -// our CI" required measuring past this prose rather than reading it. -// -// WHAT IS TRUE: the command is DEFINED BY THE FABRIC WORK AUTHORITY and CURRENTLY -// TRANSPORTED BY GITHUB ACTIONS. GitHub owns triggering, queueing, runner -// selection, concurrency, execution lifecycle, logs and check publication. The -// fabric owns what the Work IS. -// -// AND THE WALL BELONGS SOMEWHERE ELSE THAN WHERE THAT SENTENCE PUT IT, which is -// the more useful half of the correction. A Grant authorizes REALIZATION OF AN -// ATTEMPT, not construction of a command value. Inspecting, hashing, comparing, -// caching or rendering a command spends no resource; executing it does. So the -// invariant to build toward is: +// THIS PARAGRAPH PREVIOUSLY CLAIMED THE OPPOSITE AND THE CLAIM WAS FALSE. It read that the argv is +// "PRODUCED BY THE FABRIC UNDER A GRANT" and that "no grant, no command -- so the authorization is +// not a label beside the run, it is the only thing that can yield a run". Measured against the +// live tree: the run command is a lane word away from NULLARY, there is no grant anywhere on the +// workflow path, and of the ten functions in this module exactly ONE has a production consumer +// outside it -- `required_ci_lane_command`, through its two lane bindings, imported by +// gunbc.witness_floor_workflow to render a shell word. Work contract, execution requirements, +// offer fungibility, affordability, buy terms, admission terms, satisfaction requirement and +// grant duration have ZERO. +// +// That is rung inflation (DESIGN section 4b) in the module that IS this repository's claim to +// running its own CI on the fabric -- worse than sitting low, because a class reported at the top +// rung never ranks for climbing, and a reader planning the cutover would have budgeted for +// extending an allocator never built. It cost that once already: answering "where is the fabric +// in our CI" required measuring past this prose. +// +// WHAT IS TRUE: the command is DEFINED BY THE FABRIC WORK AUTHORITY and CURRENTLY TRANSPORTED BY +// GITHUB ACTIONS. GitHub owns triggering, queueing, runner selection, concurrency, execution +// lifecycle, logs and check publication. The fabric owns what the Work IS. +// +// AND THE WALL BELONGS SOMEWHERE ELSE. A Grant authorizes REALIZATION OF AN ATTEMPT, not +// construction of a command value: inspecting, hashing, comparing, caching or rendering a command +// spends no resource; executing it does. The invariant to build toward is: // // no committed Grant -> no process and no host effect // -// NOT "no grant, no command". Threading a Grant into this constructor would fuse -// semantic Work identity with scheduling authority and make one Work compare -// unequal to itself depending on whose Grant it ran under -- a defect, not a -// stricter version of the same idea. NEXT-RUNG TRIGGER: an executor that holds an -// ExecutionGrant against a reserved slot, at which point the invariant above -// becomes structural and this paragraph is rewritten as an ordinary description. -// -// What is deliberately NOT here, because GitHub Actions still supplies it while -// it still triggers: polling, durable state, the compare-and-swap authority -// transition, supersession, and check publication. Those are the CONTROL plane -// and they are only needed once the fabric owns triggering. Attempting them now -// would build a second scheduler beside a live one. - -// The floor's contract. Every field is a fact about WHAT is promised, never -// about where or when it runs -- scheduling facts live in ExecutionRequirements -// beside it, so revising a thread estimate cannot mint a new semantic Work. -// -// ONE STEP, THREE PHASES (2026-08-20; phase list narrowed 2026-08-21). This read "Two steps, not one, because the -// workflow gates them separately and the v1 parse gate is a blocking step in its -// own right: collapsing them into one step would make a parse failure and a floor -// failure indistinguishable in the receipt, which is the distinction -// gunbc#8466 -> #8519 was paid to learn." -// -// THE CONCERN WAS RIGHT AND IS NOW BETTER SERVED, which is why the row changed -// rather than the concern being dropped. The workflow no longer gates the phases -// separately -- the operator directed that the sequencing move out of the GitHub -// Actions step list and into the binary -- so a contract listing two steps is a -// SECOND, now-false description of a job shape that has one. What made the two -// steps worth having was the receipt's ability to say WHICH part failed, and that -// distinction survives at a finer grain: `--required-ci` reports its phases and -// prints `FAILED PHASE ` per failure, so parse, regen and floor are three -// distinguishable outcomes where there were two. Measured on the first CI run of -// the composed step (32347573121), which reported `phases_run=4 failed=2` and -// named both -- a regen drift AND a floor failure -- where a step ladder would -// have surfaced them one merge at a time. That measurement is quoted at its own -// date: the run had four phases because the 2026-08-21 operator ruling had not -// yet cut the roster to three (merge-admission-capture, regen-determinism, -// receipt-selftest, receipt-vs-changed-authorities and merge-admission-stamp are -// gone from the required mode). The property it establishes -- per-phase -// attribution in one receipt -- is unchanged by the count. -// -// WHAT THIS DOES COST, stated rather than glossed: resumability was per-step, so -// a green parse could previously be receipt-satisfied and skipped on a rerun. -// One step means one receipt and the whole run repeats. That is a real -// consequence of the consolidation, not an oversight, and the phase-grain -// resumability that would restore it belongs with the cost basis rather than -// here. +// NOT "no grant, no command". Threading a Grant into this constructor would fuse semantic Work +// identity with scheduling authority and make one Work compare unequal to itself depending on +// whose Grant it ran under -- a defect, not a stricter idea. NEXT-RUNG TRIGGER: an executor that +// holds an ExecutionGrant against a reserved slot; then the invariant becomes structural and this +// paragraph is rewritten as an ordinary description. +// +// Deliberately NOT here while GitHub Actions still triggers: polling, durable state, the +// compare-and-swap authority transition, supersession, and check publication. Those are the +// CONTROL plane, needed only once the fabric owns triggering; building them now would put a +// second scheduler beside a live one. + +// The floor's contract. Every field is a fact about WHAT is promised, never where or when it runs +// -- scheduling facts live in ExecutionRequirements beside it, so revising a thread estimate cannot +// mint a new semantic Work. +// +// ONE STEP, THREE PHASES (2026-08-20; phase list narrowed 2026-08-21). This read "Two steps, not +// one, because the workflow gates them separately and the v1 parse gate is a blocking step in its +// own right: collapsing them into one step would make a parse failure and a floor failure +// indistinguishable in the receipt, which is the distinction gunbc#8466 -> #8519 was paid to +// learn." +// +// THE CONCERN WAS RIGHT AND IS NOW BETTER SERVED. The operator directed that the sequencing move +// out of the GitHub Actions step list and into the binary, so a contract listing two steps is a +// SECOND, now-false description of a one-step job. The distinction the two steps bought survives +// at a finer grain: `--required-ci` reports its phases and prints `FAILED PHASE ` per +// failure, so parse, regen and floor are three distinguishable outcomes. Measured on the first CI +// run of the composed step (32347573121): `phases_run=4 failed=2`, naming a regen drift AND a +// floor failure that a step ladder would have surfaced one merge at a time. That run had four +// phases because the 2026-08-21 ruling had not yet cut the roster to three +// (merge-admission-capture, regen-determinism, receipt-selftest, receipt-vs-changed-authorities +// and merge-admission-stamp are gone from the required mode); per-phase attribution in one receipt +// is unchanged by the count. +// +// WHAT THIS DOES COST: resumability was per-step, so a green parse could be receipt-satisfied and +// skipped on a rerun. One step means one receipt and the whole run repeats. Phase-grain +// resumability that would restore it belongs with the cost basis, not here. fn floor_work_contract(source: SourceManifestRef) -> WorkContract { WorkContract { program: "gunbc.claim_executor", @@ -415,55 +396,40 @@ fn floor_work_contract(source: SourceManifestRef) -> WorkContract { // OUR FLOOR IS arm64. THE CLASS IS NOT. // // This function previously spelled the architecture INSIDE a capability tag -- -// "gunbc.capabilities.linux-aarch64-rust-toolchain" -- which made an accident of -// our own supply look like a property of the execution class. Ampere is what we -// happen to stock; the product must serve x86 and arm at the lowest cost, and -// rented x86 is a supplier we now anticipate. A class that names its -// architecture cannot admit one it did not, which is the GitHub-vocabulary -// argument one layer over. -// -// The capability tag below therefore names the toolchain and NOT the machine. -// That our floor happens to be arm64 is a fact about our fleet's supply, not a -// property of the class -- see the note below for why it is not yet a value. -// -// WHY NO Architecture VALUE IS DECLARED HERE YET, and the receipt for it. -// Matching a declared architecture against an offer's requires the OFFER to -// carry one, and SupplierOffer has 25 constructors in tree, so the field and the arm -// that reads it land together. An earlier revision of this module declared -// `floor_architecture() -> Architecture = Aarch64` anyway, with a comment -// explaining that the consuming arm would come later. It had ZERO consumers, -// and v2.lens.inert_carrier caught it on CI as an unrostered inert carrier -- -// in the same commit whose message refused to add an unread field to SupplierOffer by -// citing review 53848's declared-but-nothing-derives-it defect. Refusing the -// defect on one side of a module boundary and authoring it on the other is the -// same defect, and the lens was a better reader of that than I was. -// -// So the architecture decision lives in prose and in the recut plan until it has -// a consumer. That is the honest state: a decision recorded is not a carrier -// modeled, and minting the carrier early buys nothing except a row that lies -// about being load-bearing. +// "gunbc.capabilities.linux-aarch64-rust-toolchain" -- making an accident of our supply look like +// a property of the execution class. Ampere is what we stock; the product must serve x86 and arm +// at the lowest cost, and rented x86 is an anticipated supplier. A class that names its +// architecture cannot admit one it did not -- the GitHub-vocabulary argument one layer over. So +// the tag below names the toolchain and NOT the machine. +// +// WHY NO Architecture VALUE IS DECLARED HERE YET. Matching a declared architecture needs the OFFER +// to carry one, and SupplierOffer has 25 constructors in tree, so the field and the arm that reads +// it land together. An earlier revision declared `floor_architecture() -> Architecture = Aarch64` +// anyway, with ZERO consumers; v2.lens.inert_carrier caught it on CI as an unrostered inert +// carrier -- in the same commit whose message refused to add an unread field to SupplierOffer by +// citing review 53848's declared-but-nothing-derives-it defect. Refusing the defect on one side of +// a module boundary and authoring it on the other is the same defect. +// +// So the architecture decision lives in prose and in the recut plan until it has a consumer: a +// decision recorded is not a carrier modeled, and minting it early buys only a row that lies about +// being load-bearing. // WHAT A FLOOR RUN REQUIRES OF ITS EXECUTOR, AND WHY THAT IS HONESTLY NOTHING. // -// A floor run is OUR OWN work on OUR OWN fleet, and it demonstrably runs today on slots that -// provide none of the isolation guarantees -- a reused writable root by design, the host's process -// namespace, and /home/ghrunner shared across every slot. Declaring a requirement the floor does -// not actually have would be inventing a gap: the run would be modeled as ineligible for the -// executor it has been green on for months, and the first person to trust the model would go -// looking for a break that is not there. -// -// SO THE EMPTY PROFILE HERE IS A MEASUREMENT, NOT A PLACEHOLDER. It says the floor asks for no -// tenancy guarantee, which is true and is exactly why the floor was safe to run this way. The gap -// this model exists to express belongs to CUSTOMER work, where the tenant is not us -- see -// product.fabric.isolation tenant_workload_isolation_requirement and the witness that measures -// today's slots against it. -// -// CONSUMPTION STATUS, STATED PLAINLY BECAUSE THIS MODULE'S OWN HEADER INSISTS ON IT: the fold that -// reads this (floor_offer_fungibility -> offer_fungibility_for) has NO production consumer, exactly -// as its siblings ShapeNotCovered and CapabilitiesNotOffered do not. This is the modeled decision -// the broker will consume, not a live wall, and calling it enforcement would be the rung inflation -// the paragraph above already records for affordability. NEXT-RUNG TRIGGER is the same one: a -// broker fold that selects an Offer for this Demand. +// A floor run is OUR OWN work on OUR OWN fleet, and it runs today on slots providing none of the +// isolation guarantees -- a reused writable root by design, the host's process namespace, and +// /home/ghrunner shared across every slot. Declaring a requirement the floor does not have would +// invent a gap: the run modeled as ineligible for the executor it has been green on for months. +// +// SO THE EMPTY PROFILE HERE IS A MEASUREMENT, NOT A PLACEHOLDER: the floor asks for no tenancy +// guarantee. The gap this model expresses belongs to CUSTOMER work -- see product.fabric.isolation +// tenant_workload_isolation_requirement and the witness measuring today's slots against it. +// +// CONSUMPTION STATUS: the fold that reads this (floor_offer_fungibility -> offer_fungibility_for) +// has NO production consumer, like its siblings ShapeNotCovered and CapabilitiesNotOffered. It is +// the modeled decision the broker will consume, not a live wall; calling it enforcement would be +// the rung inflation recorded above for affordability. NEXT-RUNG TRIGGER is the same: a broker +// fold that selects an Offer for this Demand. fn floor_isolation_requirement() -> IsolationProfile { IsolationProfile { guarantees: [] } } @@ -486,22 +452,17 @@ fn floor_execution_requirements() -> ExecutionRequirements { } } -// THE SOURCE ROOTS ARE NOT THIS MODULE'S FACT. gunbc.ci_layer_roots -// witness_layer_roots is the existing authority -- the live workflow already -// folds it into its --source-root flags -- so declaring a list here was a fork -// of it, which is the violation one level above the one review 53848 caught. -// Consumed, not redeclared. -// -// The argv is a TOTAL fold over that authority, so adding a root changes the -// command with nothing to remember. An earlier revision of this module hand- -// expanded the pairs and declared a substrate gap with a dissolution trigger, -// on the grounds that expanding one element into two needs a flat-map and the -// substrate has neither flatten nor list destructuring. Both observations were -// true and the conclusion was wrong: `fold` exists and is exactly the primitive -// required -- the workflow module two files away was already using it for this -// same job. I declared a language-layer gap without enumerating the language, -// which is the failure my own notes name as searching by remembered name rather -// than reading the authority surface. +// THE SOURCE ROOTS ARE NOT THIS MODULE'S FACT. gunbc.ci_layer_roots witness_layer_roots is the +// authority -- the live workflow already folds it into its --source-root flags -- so a list here +// was a fork of it, the violation one level above the one review 53848 caught. Consumed, not +// redeclared. +// +// The argv is a TOTAL fold over that authority, so adding a root changes the command with nothing +// to remember. An earlier revision hand-expanded the pairs and declared a substrate gap with a +// dissolution trigger, on the grounds that one-to-two expansion needs a flat-map and the substrate +// has neither flatten nor list destructuring. Both observations were true and the conclusion was +// wrong: `fold` is exactly the primitive required, and the workflow module two files away already +// used it for this job -- a language-layer gap declared without enumerating the language. fn floor_inputs_manifest_ref() -> ArtifactManifestRef { concat("gunbc.floor.inputs.source-roots:", join(witness_layer_roots, "+")) @@ -511,29 +472,26 @@ fn floor_inputs_manifest_ref() -> ArtifactManifestRef { // "basically i would put regen + v2 full compile in one job, and witnesses into another // one"). // -// The required run's phases were composed into one process on 2026-08-20 and were therefore -// SERIAL. They are also independent, which is the expensive combination: the witness floor -// costs ~30 minutes and everything else waited behind it for no reason any data dependency -// names, so the required check's wall clock was a SUM of things that could have been a MAX. -// Two jobs with no `needs` edge make it a max. -// -// THE LANE IS THE ONLY THING THE TRANSPORT NAMES. It does not name phases, order them, or -// wire one phase's precondition to another step's verdict -- which phases a lane owns is -// decided in the binary, by an exhaustive match, so re-routing the roster is a change there -// and never an edit to a YAML step list. That is the surviving half of the 2026-08-20 -// consolidation directive; the half the split supersedes is stated in -// `gunbc.witness_floor_workflow`, beside the jobs it is about. -// -// WHY THIS PARTITION AND NOT ANOTHER. `build` is the EMISSION lane -- regen compares the -// emitted stage0 mirrors against what is committed, and v2-emission compiles a v2 entry to -// Rust; both ask "does the compiler still produce its outputs". `witnesses` is the CORPUS -// lane -- the parse sweep and the floor fold, both asking "does the authored corpus hold". -// The split is along the subject, not along a stopwatch, which is why it stays legible when -// the costs move. -// -// A SINGLE ARGV, NOT TWO. Two hand-built invocations would be two spellings of the binary -// path, the mode flag and the source roots, free to drift silently in exactly the way #8629 -// removed once already; the lane is a parameter of one command rather than a second command. +// The phases composed into one process on 2026-08-20 were SERIAL yet independent: the witness +// floor costs ~30 minutes and everything else waited behind it with no data dependency, so the +// required check's wall clock was a SUM that could be a MAX. Two jobs with no `needs` edge make +// it a max. +// +// THE LANE IS THE ONLY THING THE TRANSPORT NAMES. It does not name or order phases, or wire one +// phase's precondition to another step's verdict -- which phases a lane owns is an exhaustive +// match in the binary, so re-routing the roster is never a YAML edit. That is the surviving half +// of the 2026-08-20 consolidation directive; the half the split supersedes is stated in +// `gunbc.witness_floor_workflow`. +// +// WHY THIS PARTITION. `build` is the EMISSION lane -- regen compares the emitted stage0 mirrors +// against what is committed, and v2-emission compiles a v2 entry to Rust; both ask "does the +// compiler still produce its outputs". `witnesses` is the CORPUS lane -- the parse sweep and the +// floor fold, asking "does the authored corpus hold". Split along the subject, not a stopwatch, +// so it stays legible when costs move. +// +// A SINGLE ARGV, NOT TWO. Two hand-built invocations would be two spellings of the binary path, +// mode flag and source roots, free to drift as #8629 removed once already; the lane is a +// parameter of one command. fn required_ci_lane_command(lane: String) -> ArgvCommand { claim_executor_command( mode_flag: "--required-ci", @@ -569,47 +527,37 @@ fn build_lane_run_command() -> ArgvCommand { // THE FLOOR IS MODELED AS AN ORDINARY PRICED DEMAND. NO PRICE IS CLEARED TODAY. // -// The design intent is real and unchanged: gunbc's own CI should enter the market -// on the same terms as any other demand, so that the opportunity cost of running -// our own work stays a computable quantity and the arbitrage -- run our CI on our -// hardware, or sell that capacity and rent -- does not degrade into a -// hand-maintained spreadsheet. This fold takes the budget ceiling as an argument -// for exactly that reason. -// -// WHAT WAS OVERSTATED, corrected in the same pass as the paragraph above: this -// previously read that the ceiling is an argument "precisely so that there is no -// arm here that admits a floor run without clearing a price", present tense, as -// though the guard were live. It is not reachable. `floor_offer_affordability` and -// `floor_buy_terms` have NO production consumer -- the only function in this module -// that any non-test module calls is `required_ci_lane_command` -- so no floor run has ever -// cleared a price, and no arm could have admitted one without doing so, because no -// arm runs at all. A fold with no caller cannot enforce anything, and describing it -// as enforcement is the same inflation this file's header paragraph now records. -// -// The fold stays: it is the modeled decision the broker will consume, and deleting -// it would be deleting the design rather than the overstatement. What changes is -// that it is described as UNCONSUMED, so its status is a countable gap instead of -// an assumed guarantee. NEXT-RUNG TRIGGER: a broker fold that selects a SupplierOffer for -// this Demand, at which point affordability is on the live path and this row -// becomes an ordinary description of a check that runs. -// -// Control-plane work is a separately privileged class. That exclusion must not -// leak here: a floor run is customer work that happens to be ours. +// The intent is unchanged: gunbc's own CI enters the market on the same terms as any other demand, +// so the opportunity cost of our own work stays computable and the arbitrage -- run our CI on our +// hardware, or sell that capacity and rent -- never degrades into a hand-maintained spreadsheet. +// The budget ceiling is an argument for exactly that reason. +// +// WHAT WAS OVERSTATED: this previously read that the ceiling is an argument "precisely so that +// there is no arm here that admits a floor run without clearing a price", as though the guard +// were live. It is not reachable. `floor_offer_affordability` and `floor_buy_terms` have NO +// production consumer -- the only function here any non-test module calls is +// `required_ci_lane_command` -- so no floor run has ever cleared a price and no arm runs at all. +// A fold with no caller enforces nothing; describing it as enforcement is the inflation the +// header paragraph records. +// +// The fold stays as the modeled decision the broker will consume; deleting it would delete the +// design, not the overstatement. It is described as UNCONSUMED, a countable gap. NEXT-RUNG +// TRIGGER: a broker fold that selects a SupplierOffer for this Demand, putting affordability on +// the live path. +// +// Control-plane work is a separately privileged class. That exclusion must not leak here: a floor +// run is customer work that happens to be ours. fn floor_offer_fungibility

(offer: SupplierOffer

) -> OfferFungibility { offer_fungibility_for(requirements: floor_execution_requirements(), offer: offer) } -// THE FLOOR'S GRANT DURATION IS AN ESTIMATE, NOT A MEASUREMENT. `~30 min wall` -// is the wall-clock this document's own "Building & checks" section records -// for the three consecutive green `--required-ci` runs on main (32203661157 -// and neighbors) -- a measured receipt, not a guess -- but it is still an -// ESTIMATE of what THIS run will take, not the exact duration of a run that -// has not happened yet. Spelling it as EstimatedGrantDuration rather than -// ExactGrantDuration is therefore not caution for its own sake: it is the -// only honest constructor available, and it is what lets a rate-quoted offer -// land on AffordableUnderEstimate instead of silently borrowing the -// certainty ExactGrantDuration would claim. +// THE FLOOR'S GRANT DURATION IS AN ESTIMATE, NOT A MEASUREMENT. `~30 min wall` is the wall-clock +// this document's "Building & checks" section records for the three consecutive green +// `--required-ci` runs on main (32203661157 and neighbors) -- a measured receipt -- but still an +// ESTIMATE of what THIS run will take. EstimatedGrantDuration rather than ExactGrantDuration is +// the only honest constructor, and it lets a rate-quoted offer land on AffordableUnderEstimate +// instead of borrowing certainty ExactGrantDuration would claim. fn floor_grant_duration() -> GrantDuration { EstimatedGrantDuration { seconds: second(count: 1800) } } @@ -627,22 +575,17 @@ fn floor_offer_affordability

( ) } -// THE FLOOR'S DEMAND. This is where "ordinary priced demand" stops being a -// property of a fold's signature and becomes a row someone can read. +// THE FLOOR'S DEMAND: where "ordinary priced demand" becomes a row someone can read. // -// REUSE IS A POLICY ON THE DEMAND, NOT A PROPERTY OF THE WORK. The cutover runs -// with terminal_receipt_may_satisfy = false and new_attempt_required = true, so -// an identical Work that already has an accepted receipt still executes. That -// is deliberately conservative -- it preserves today's Actions behaviour while -// the purity of the floor as a function of the tree is unproven -- and it is -// expressed HERE rather than in the Work key, because turning reuse on later -// must edit a policy row and not edit what a Work IS. The two were conflated in -// the plan's own section 13 and in the sign-off that answered it; the carriers -// never conflated them, which is why this needed no new modeling. +// REUSE IS A POLICY ON THE DEMAND, NOT A PROPERTY OF THE WORK. The cutover runs with +// terminal_receipt_may_satisfy = false and new_attempt_required = true, so an identical Work with +// an accepted receipt still executes -- deliberately conservative while the floor's purity as a +// function of the tree is unproven -- and expressed HERE rather than in the Work key, so turning +// reuse on later edits a policy row, not what a Work IS. The plan's section 13 and its sign-off +// conflated the two; the carriers never did, so no new modeling was needed. // -// step_reuse_permitted stays TRUE: a materialization provider may still -// accelerate a rerun. That is not the same claim as "the step completed" -- a -// build cache making the second attempt faster is not a receipt. +// step_reuse_permitted stays TRUE: a materialization provider may accelerate a rerun. A build +// cache making the second attempt faster is not a receipt. fn floor_satisfaction_requirement() -> SatisfactionRequirement { SatisfactionRequirement { @@ -667,21 +610,17 @@ fn floor_buy_terms( // THE FLOOR HAS NO PRIVILEGE FIELD BECAUSE THERE IS NO LONGER A FIELD TO HAVE. // -// This note used to read that priority_class is not a privilege escape -- that -// the floor competes on price like any other demand, and that a class orders -// demands that can all be afforded rather than admitting one that cannot. The -// claim was correct and it was prose: the field was a String, so nothing -// enforced it and a witness beside it could only assert the string's spelling. -// -// The field is deleted (product.fabric.demand records why) and the ordering it -// gestured at is derived by product.fabric.contention from the margin between -// what a buyer will pay and what serving them costs. So the floor's terms carry -// no rank at all, and there is no spelling in which gunbc jumps the queue: to -// win contention the floor must post the better margin, which is the same test -// every other buyer takes. That is the standing claim made structural rather -// than asserted -- if this ever became the field that let gunbc jump the queue, -// the arbitrage would be lost and the opportunity cost of our own work would -// have stopped being computable. +// This note used to read that priority_class is not a privilege escape -- the floor competes on +// price, and a class orders demands that can all be afforded rather than admitting one that +// cannot. Correct, and prose: the field was a String, nothing enforced it, and a witness could only +// assert its spelling. +// +// The field is deleted (product.fabric.demand records why); product.fabric.contention derives the +// ordering from the margin between what a buyer will pay and what serving them costs. The floor's +// terms carry no rank, and there is no spelling in which gunbc jumps the queue: to win contention +// it must post the better margin like every other buyer. If this ever became the field that let +// gunbc jump the queue, the arbitrage would be lost and the opportunity cost of our own work would +// stop being computable. fn floor_admission_terms(buy: BuyTerms, deadline: Timestamp?) -> AdmissionTerms { AdmissionTerms { diff --git a/dag/gunbc/fleet/fleet_bmc_firmware_evidence.dag b/dag/gunbc/fleet/fleet_bmc_firmware_evidence.dag index 17f05d8d7fa..f5558363e08 100644 --- a/dag/gunbc/fleet/fleet_bmc_firmware_evidence.dag +++ b/dag/gunbc/fleet/fleet_bmc_firmware_evidence.dag @@ -91,7 +91,15 @@ fn bmc_firmware_capability_standing( } } -data fleet_bmc_firmware_attestation_dissolution_trigger: String = "🟡 dissolve-on: an authenticated Redfish read of /redfish/v1/Managers/bmc FirmwareVersion against srv1 (192.168.1.183) and srv2 (192.168.1.184), landed as BmcFirmwareObservation rows in gunbc.fleet_bmc_observation. Both endpoints answer Redfish and return 401 unauthenticated (measured 2026-08-10); the credential is bmc-srvN-admin in Secret Manager, which the session that authored these rows could not reach. Until that read runs, srv1/srv2 firmware is an operator statement and the BmcFirmwareEvidenceAttested arm is what says so. When the observations land, srv1_bmc_firmware_evidence and srv2_bmc_firmware_evidence move to BmcFirmwareEvidenceObserved and the two attestation rows below are DELETED — an attestation superseded by a read is not kept beside it (DESIGN §2)." +// 🟡 dissolve-on: an authenticated Redfish read of /redfish/v1/Managers/bmc FirmwareVersion against +// srv1 (192.168.1.183) and srv2 (192.168.1.184), landed as BmcFirmwareObservation rows in +// gunbc.fleet_bmc_observation. Both endpoints answer Redfish and return 401 unauthenticated +// (measured 2026-08-10); the credential is bmc-srvN-admin in Secret Manager, which the session that +// authored these rows could not reach. Until that read runs, srv1/srv2 firmware is an operator +// statement and the BmcFirmwareEvidenceAttested arm is what says so. When the observations land, +// srv1_bmc_firmware_evidence and srv2_bmc_firmware_evidence move to BmcFirmwareEvidenceObserved and +// the two attestation rows below are DELETED — an attestation superseded by a read is not kept +// beside it (DESIGN §2). data srv1_bmc_firmware_attestation: BmcFirmwareAttestation = BmcFirmwareAttestation { endpoint: srv1_baseboard.bmc, diff --git a/dag/gunbc/fleet/fleet_bmc_observation.dag b/dag/gunbc/fleet/fleet_bmc_observation.dag index a8d7a6d5553..6b9230ee592 100644 --- a/dag/gunbc/fleet/fleet_bmc_observation.dag +++ b/dag/gunbc/fleet/fleet_bmc_observation.dag @@ -275,7 +275,13 @@ fn bmc_fan_realization_definition_linkages() -> StateRealizationDefinitionLinkag } } -data srv4_bmc_fan_realization_definition_linkages_scaffold_note: String = "Until the namespace content-addressed SymbolIndex subtree-digest authority lands, every realization role is CallerAssertedDefinitionLinkage and durable qualification refuses with typed DurabilityDefinitionLinkageUnavailable. The condition is not restated per row: std.state_durability state_definition_linkage_frontier_note is its single authority, and the arm's own name carries the temporary state, so the deleted per-instance dissolve_on string was one module fact copied to eight sites — including a witness that asserted the sentence verbatim, which made the prose the contract rather than the behaviour." +// Until the namespace content-addressed SymbolIndex subtree-digest authority lands, every +// realization role is CallerAssertedDefinitionLinkage and durable qualification refuses with typed +// DurabilityDefinitionLinkageUnavailable. The condition is not restated per row: +// std.state_durability state_definition_linkage_frontier_note is its single authority, and the +// arm's own name carries the temporary state, so the deleted per-instance dissolve_on string was +// one module fact copied to eight sites — including a witness that asserted the sentence verbatim, +// which made the prose the contract rather than the behaviour. data srv4_bmc_fan_realization: StateRealizationIdentity = StateRealizationIdentity { realization_id: srv4_bmc_fan_realization_id, @@ -378,14 +384,42 @@ data srv4_bmc_fan_post_reboot_policy_observation: BmcFanPolicyObservation = BmcF observed_at: 1785433000000, } -data srv4_bmc_fan_projection_probe_note: String = "Authenticated BMC SSH, 2026-07-30: srv4's config-asrr.json was rebuilt to the srv3 2.07.00 vendor stock aggressive curve by running the modeled TEMP_SOC jq realization on srv4's pre-onboarding base config and writing the canonical bytes back through a staged same-filesystem atomic rename; the live digest read back equal to altrad8ud_openbmc_322_fleet_quiet_projected_config_sha256_hex. srv4_bmc_fan_convergence_receipt is the typed live receipt of the converged state (12 samples over 120 seconds, TEMP_SOC 60-63C, guarded component max 69C, aggregate FAN1 4600 RPM). This replaces the earlier 2026-07-27 lazy-curve projection provenance." - -data bmc_historical_receipt_not_realization_identity_note: String = "These July 30 rows are immutable historical evidence snapshots, not live producers and not StateDurabilityEvidence. In particular BmcFanConvergenceReceipt deliberately has no StateRealizationIdentity: the content-addressed SymbolIndex subtree-digest authority does not exist yet, so std.state_durability exposes only counted CallerAssertedDefinitionLinkage rows and refuses durable qualification. Hashing a remembered label or these observed output values would leave definition changes invisible; these historical rows therefore do not pretend to supply realization linkage." - -data srv4_bmc_fan_reboot_persistence_note: String = "Live perturbation receipt, 2026-07-30 (aggressive stock curve): Redfish Manager.Reset advertised and accepted GracefulRestart with HTTP 200. The BMC was observed down for a 170-second reachability interval while srv4's host OS remained up (ComputerSystem PowerState On). After return, OpenBMC still reported 3.22.00; the active config-asrr.json SHA-256 digest was unchanged and equal to the srv3 stock aggressive curve projection (f559aff7...); phosphor-pid-control was active; zone0 FailSafe=false. This proves the JFFS2 upper override persists the aggressive curve across one graceful BMC restart. It does not prove persistence across a firmware replacement." - -data fleet_bmc_surface_observation_note: String = "Authenticated read-only Redfish observations, 2026-07-27. srv3 OpenBMC 2.07.00 advertises ThermalSubsystem and its 51-member Chassis/Sensors collection includes temperature and fan readings; the legacy Chassis/Thermal URI is absent. srv4 OpenBMC 3.22.00 advertises legacy Chassis/Thermal; its Chassis/Sensors request still returns HTTP success but has only 9 power/utilization members and omits temperature/fan readings. A collection-only observer therefore fails open on srv4: transport success is not telemetry completeness. Direct known sensor URIs still resolve, but they do not make an inventory query complete." - -data fleet_bmc_update_service_observation_note: String = "Authenticated read-only Redfish observations, 2026-07-27. Both srv3 2.07.00 and srv4 3.22.00 advertise /redfish/v1/UpdateService/update through HttpPushUri and MultipartHttpPushUri, and each active BMC image is Updateable=true. Each FirmwareInventory exposed exactly one BMC image plus non-BMC members; neither exposed a backup BMC image. This is update-surface capability evidence, not an approved image, apply receipt, or recovery proof." +// Authenticated BMC SSH, 2026-07-30: srv4's config-asrr.json was rebuilt to the srv3 2.07.00 vendor +// stock aggressive curve by running the modeled TEMP_SOC jq realization on srv4's pre-onboarding +// base config and writing the canonical bytes back through a staged same-filesystem atomic rename; +// the live digest read back equal to altrad8ud_openbmc_322_fleet_quiet_projected_config_sha256_hex. +// srv4_bmc_fan_convergence_receipt is the typed live receipt of the converged state (12 samples +// over 120 seconds, TEMP_SOC 60-63C, guarded component max 69C, aggregate FAN1 4600 RPM). This +// replaces the earlier 2026-07-27 lazy-curve projection provenance. + +// These July 30 rows are immutable historical evidence snapshots, not live producers and not +// StateDurabilityEvidence. In particular BmcFanConvergenceReceipt deliberately has no +// StateRealizationIdentity: the content-addressed SymbolIndex subtree-digest authority does not +// exist yet, so std.state_durability exposes only counted CallerAssertedDefinitionLinkage rows and +// refuses durable qualification. Hashing a remembered label or these observed output values would +// leave definition changes invisible; these historical rows therefore do not pretend to supply +// realization linkage. + +// Live perturbation receipt, 2026-07-30 (aggressive stock curve): Redfish Manager.Reset advertised +// and accepted GracefulRestart with HTTP 200. The BMC was observed down for a 170-second +// reachability interval while srv4's host OS remained up (ComputerSystem PowerState On). After +// return, OpenBMC still reported 3.22.00; the active config-asrr.json SHA-256 digest was unchanged +// and equal to the srv3 stock aggressive curve projection (f559aff7...); phosphor-pid-control was +// active; zone0 FailSafe=false. This proves the JFFS2 upper override persists the aggressive curve +// across one graceful BMC restart. It does not prove persistence across a firmware replacement. + +// Authenticated read-only Redfish observations, 2026-07-27. srv3 OpenBMC 2.07.00 advertises +// ThermalSubsystem and its 51-member Chassis/Sensors collection includes temperature and fan +// readings; the legacy Chassis/Thermal URI is absent. srv4 OpenBMC 3.22.00 advertises legacy +// Chassis/Thermal; its Chassis/Sensors request still returns HTTP success but has only 9 +// power/utilization members and omits temperature/fan readings. A collection-only observer +// therefore fails open on srv4: transport success is not telemetry completeness. Direct known +// sensor URIs still resolve, but they do not make an inventory query complete. + +// Authenticated read-only Redfish observations, 2026-07-27. Both srv3 2.07.00 and srv4 3.22.00 +// advertise /redfish/v1/UpdateService/update through HttpPushUri and MultipartHttpPushUri, and each +// active BMC image is Updateable=true. Each FirmwareInventory exposed exactly one BMC image plus +// non-BMC members; neither exposed a backup BMC image. This is update-surface capability evidence, +// not an approved image, apply receipt, or recovery proof. data bmc_fan_symbol_index_subtree_digest_dissolve_on: DissolutionCondition = unbound_dissolution(description: "namespace content-addressed SymbolIndex subtree digest") diff --git a/dag/gunbc/fleet/fleet_bmc_state.dag b/dag/gunbc/fleet/fleet_bmc_state.dag index 6dc9c6186bf..dac53a09430 100644 --- a/dag/gunbc/fleet/fleet_bmc_state.dag +++ b/dag/gunbc/fleet/fleet_bmc_state.dag @@ -406,8 +406,13 @@ fn bmc_fan_durability_evidence_built_items( } } +// Durability evidence is derived solely from the typed perturbation receipt, including the exact +// projection capability recorded at perturbation time. Admission and the graceful-restart boundary +// are not separately writable caller labels. Activation is established from independent controller, +// content, and safety readbacks; a later converge no-op is a consequence, never a premise. Ordinary +// convergence additionally requires the live canonical config digest to equal the admitted digest +// derived from the projected curve. + data srv4_bmc_fan_durability_evidence_construction: BmcFanDurabilityEvidenceConstruction = bmc_fan_reboot_durability_evidence( receipt: srv4_bmc_fan_reboot_persistence_receipt, ) - -data srv4_bmc_fan_policy_qualification_note: String = "Durability evidence is derived solely from the typed perturbation receipt, including the exact projection capability recorded at perturbation time. Admission and the graceful-restart boundary are not separately writable caller labels. Activation is established from independent controller, content, and safety readbacks; a later converge no-op is a consequence, never a premise. Ordinary convergence additionally requires the live canonical config digest to equal the admitted digest derived from the projected curve." diff --git a/dag/gunbc/fleet/fleet_capacity_control.dag b/dag/gunbc/fleet/fleet_capacity_control.dag index 8a4d4d5b4ac..11b974ab7e3 100644 --- a/dag/gunbc/fleet/fleet_capacity_control.dag +++ b/dag/gunbc/fleet/fleet_capacity_control.dag @@ -22,7 +22,9 @@ import gunbc.fleet_intent_network { // dark rather than staying up, and a new host must be added in two places before it can ever be // used. Withdrawal fails toward capacity remaining available, which is the recoverable direction: // too much capacity is a cost, too little is an outage. -data fleet_capacity_control_note: String = "Operator withdrawal rosters for fleet capacity on two axes: whole hosts and provider runtimes. Consumed at the dispatch inventory seam so a withdrawn provider makes no offer. Absent from a roster means active." +// Operator withdrawal rosters for fleet capacity on two axes: whole hosts and provider runtimes. +// Consumed at the dispatch inventory seam so a withdrawn provider makes no offer. Absent from a +// roster means active. // A WITHDRAWAL CARRIES ITS REASON BECAUSE THE ROSTER OUTLIVES THE SITUATION THAT CAUSED IT. // diff --git a/dag/gunbc/fleet/fleet_capacity_panel.dag b/dag/gunbc/fleet/fleet_capacity_panel.dag index 37235ce78e4..a300d799a9b 100644 --- a/dag/gunbc/fleet/fleet_capacity_panel.dag +++ b/dag/gunbc/fleet/fleet_capacity_panel.dag @@ -23,7 +23,8 @@ import gunbc.fleet_capacity_control { // a display copy. That is the whole difference between a control and a cockpit: a panel with its // own roster drifts from the thing it claims to steer, and the drift is invisible because both // sides look internally consistent. -data fleet_capacity_panel_note: String = "Daily-workspace panel over declared fleet capacity. Reads gunbc.fleet_capacity_control directly so the panel and the dispatch gate cannot disagree." +// Daily-workspace panel over declared fleet capacity. Reads gunbc.fleet_capacity_control directly +// so the panel and the dispatch gate cannot disagree. // EVERY NUMBER HERE IS DECLARED, NOT OBSERVED, AND THE PANEL SAYS SO IN THE RENDERED OUTPUT. // @@ -34,7 +35,9 @@ data fleet_capacity_panel_note: String = "Daily-workspace panel over declared fl // fleet is running 22, and a capacity decision made on that number would be wrong on the only axis // the panel exists to inform. Labelling the column "committed" costs one word and removes the // class of error entirely. -data fleet_capacity_panel_declared_note: String = "Widths are committed allocations, not observed incarnations. srv3/srv4 commit 6 while running 5 (runner_slot_allocation gunbc_runner_slot_width_ruling_note): the model leads the fleet on that axis." +// Widths are committed allocations, not observed incarnations. srv3/srv4 commit 6 while running 5 +// (runner_slot_allocation gunbc_runner_slot_width_ruling_note): the model leads the fleet on that +// axis. // THE PANEL NAMES WHICH AXIS BOUND EACH HOST, BECAUSE THE TOTAL CANNOT SAY IT AND THE REMEDIES // DIFFER. A committed width is a minimum over axes, and the number that survives says nothing diff --git a/dag/gunbc/fleet/fleet_converge_cli.dag b/dag/gunbc/fleet/fleet_converge_cli.dag index 8737e16ac58..0eb811a13c9 100644 --- a/dag/gunbc/fleet/fleet_converge_cli.dag +++ b/dag/gunbc/fleet/fleet_converge_cli.dag @@ -589,7 +589,9 @@ data converge_cli_receipt_owner_readback_required: Bool = true // Interim owner readback routes through gunbc.command_runner.stat_owner_name_capture // (command_runner_scaffold population). Dissolve-on: extdeps.filesystem.filesystem_io // typed owner observation retires the stat argv arm. -data converge_cli_receipt_owner_readback_interim_note: String = "Owner readback uses command_runner stat_owner_name_capture (command_runner_scaffold) until extdeps.filesystem.filesystem_io carries a typed owner observation; then converge_cli_receipt_owner_readback routes through that extdep." +// Owner readback uses command_runner stat_owner_name_capture (command_runner_scaffold) until +// extdeps.filesystem.filesystem_io carries a typed owner observation; then +// converge_cli_receipt_owner_readback routes through that extdep. fn converge_cli_expected_receipt_owner_name() -> String { fleet_posix_operator_user.name diff --git a/dag/gunbc/fleet/fleet_converge_plan.dag b/dag/gunbc/fleet/fleet_converge_plan.dag index 6c9fb956b7f..b0b35a8446e 100644 --- a/dag/gunbc/fleet/fleet_converge_plan.dag +++ b/dag/gunbc/fleet/fleet_converge_plan.dag @@ -184,7 +184,16 @@ import gunbc.remote_operation { type TimerRetirementMemberAt = NonEmptyStr -data fleet_converge_plan_authority_note: String = "On-demand fleet converge PLAN authority (operator directive 2026-08-13, sleek-heron-218): membership_reconcile computes the typed hunks — MemberAdded, MemberChanged, MemberRemoved, MemberRemovalRefused — that a human reviews BEFORE apply. Plan and apply are the same spine at different poles; this module owns serialization, the content-addressed artifact, subject identity, std.temporal_effect LeaseEpoch generation protection, and the observed baseline fingerprint apply must CAS against. A plan names its subject (host + member-set fingerprint) and mints a generation-scoped lease so two applies against the same observed baseline cannot both act. Timer retirement: desired has NO fleet-converge timers; observed lists legacy units still on the host. MemberRemovalRefused arms are rendered in plan output and never filtered — they are precisely what a reviewer needs to see before approving." +// On-demand fleet converge PLAN authority (operator directive 2026-08-13, sleek-heron-218): +// membership_reconcile computes the typed hunks — MemberAdded, MemberChanged, MemberRemoved, +// MemberRemovalRefused — that a human reviews BEFORE apply. Plan and apply are the same spine at +// different poles; this module owns serialization, the content-addressed artifact, subject +// identity, std.temporal_effect LeaseEpoch generation protection, and the observed baseline +// fingerprint apply must CAS against. A plan names its subject (host + member-set fingerprint) and +// mints a generation-scoped lease so two applies against the same observed baseline cannot both +// act. Timer retirement: desired has NO fleet-converge timers; observed lists legacy units still on +// the host. MemberRemovalRefused arms are rendered in plan output and never filtered — they are +// precisely what a reviewer needs to see before approving. // SCOPE IS A PROPERTY OF THE REQUEST, NOT A FLAG BESIDE IT. A fleet converge answers for a set of // resource families, and the fabric-execution-cells cut needs to answer for exactly one of them. @@ -602,7 +611,22 @@ fn membership_plan_lines_cap(plan: MembershipPlan) -> Li map(plan.actions, a => cap_action_line(action: a)) } -data slot_family_is_the_third_member_family_note: String = "THE SLOT ROSTER IS RECONCILED HERE RATHER THAN STOOD UP BY A SEPARATE SCRIPT, which is the whole point of the consolidation (operator directive 2026-08-22: this all belongs in fleet convergence). Timers and caps already flowed through membership_reconcile and slot standup did not, so the fleet had two ways to change a host: a reviewed plan artifact with a lease, a member-set fingerprint and a CAS against the observed baseline, and a hand-run provisioning path with none of those. A width change landing through the second one is unreviewed by construction.\n\nDESIRED COMES FROM THE DEPLOY ROW AND THE DEPLOY ROW DERIVES ITS WIDTH, so the roster this plan proposes is the width the admission axes committed -- not a number authored beside them. An unmodeled host emits a REFUSED line and no actions; it does not fall through to zero desired members, because zero desired against a populated host reconciles to REMOVE EVERY SLOT, which is the absorbing failure arm pointed the destructive direction.\n\nOBSERVATION REFUSAL IS UPSTREAM. gunbc.runner_slot_provision RunnerSlotsUnobserved is resolved by the caller before it reaches this module; a List arriving here has already been established to be a genuine reading of the host rather than an unread one rendered as empty." +// THE SLOT ROSTER IS RECONCILED HERE RATHER THAN STOOD UP BY A SEPARATE SCRIPT, which is the whole +// point of the consolidation (operator directive 2026-08-22: this all belongs in fleet +// convergence). Timers and caps already flowed through membership_reconcile and slot standup did +// not, so the fleet had two ways to change a host: a reviewed plan artifact with a lease, a +// member-set fingerprint and a CAS against the observed baseline, and a hand-run provisioning path +// with none of those. A width change landing through the second one is unreviewed by construction. +// +// DESIRED COMES FROM THE DEPLOY ROW AND THE DEPLOY ROW DERIVES ITS WIDTH, so the roster this plan +// proposes is the width the admission axes committed -- not a number authored beside them. An +// unmodeled host emits a REFUSED line and no actions; it does not fall through to zero desired +// members, because zero desired against a populated host reconciles to REMOVE EVERY SLOT, which is +// the absorbing failure arm pointed the destructive direction. +// +// OBSERVATION REFUSAL IS UPSTREAM. gunbc.runner_slot_provision RunnerSlotsUnobserved is resolved by +// the caller before it reaches this module; a List arriving here has already been +// established to be a genuine reading of the host rather than an unread one rendered as empty. fn slot_action_line(action: MemberAction) -> String { match action { diff --git a/dag/gunbc/fleet/fleet_converge_workflow.dag b/dag/gunbc/fleet/fleet_converge_workflow.dag index ba4a1ab5b5e..ffae436300e 100644 --- a/dag/gunbc/fleet/fleet_converge_workflow.dag +++ b/dag/gunbc/fleet/fleet_converge_workflow.dag @@ -67,7 +67,14 @@ import extdeps.languages.yaml.emit { serialize_yaml } import extdeps.languages.yaml.gha_workflow { project_workflow_to_yaml } import std.types { NonEmptyStr, List, String, Int } -data fleet_converge_workflow_note: String = "Emitted on-demand fleet converge workflow (operator directive 2026-08-13, sleek-heron-218). workflow_dispatch with host=srv1|srv2|srv3|srv4 (executor runner pin) and mode=plan|apply|spark_linger: plan observes the RUNNER host; apply downloads a prior plan artifact; spark_linger runs executor-on-runner → target-on-spark (srv5|srv6) typed argv over FleetSsh via run_typed_argv_transport. Replaces the retired 15-minute ctrl-fleet-converge.timer cadence on every enrolled CI host (timer install route is TimerRetired fleet-wide). Standalone dispatch carries its own build job (release-bins pack/upload) so the converge job never downloads an artifact the current run did not produce." +// Emitted on-demand fleet converge workflow (operator directive 2026-08-13, sleek-heron-218). +// workflow_dispatch with host=srv1|srv2|srv3|srv4 (executor runner pin) and +// mode=plan|apply|spark_linger: plan observes the RUNNER host; apply downloads a prior plan +// artifact; spark_linger runs executor-on-runner → target-on-spark (srv5|srv6) typed argv over +// FleetSsh via run_typed_argv_transport. Replaces the retired 15-minute ctrl-fleet-converge.timer +// cadence on every enrolled CI host (timer install route is TimerRetired fleet-wide). Standalone +// dispatch carries its own build job (release-bins pack/upload) so the converge job never downloads +// an artifact the current run did not produce. data fleet_converge_enrolled_host_options: List = ["srv1", "srv2", "srv3", "srv4"] @@ -134,7 +141,6 @@ data fleet_converge_spark_linger_step_if: String = "github.event.inputs.mode == data fleet_converge_spark_observe_step_if: String = "github.event.inputs.mode == 'spark_observe'" - data fleet_converge_spark_reboot_step_if: String = "github.event.inputs.mode == 'spark_reboot'" data fleet_converge_spark_durability_step_if: String = "github.event.inputs.mode == 'spark_durability'" @@ -381,7 +387,6 @@ fn fleet_converge_spark_grants_step() -> Step { } } - fn fleet_converge_spark_observe_step() -> Step { RunStep { name: Present { value: "Spark serving observation (read-only: unit state, runtime version, model tags, driver)" }, @@ -396,7 +401,9 @@ fn fleet_converge_spark_observe_step() -> Step { } } -data fleet_converge_consumer_prelude_note: String = "Plain fetch-depth-0 checkout only — this workflow has no workflow_dispatch expected_healed_sha input (unlike ci.yml heal revalidation). ci_artifact_consumer_prelude_steps paste-through would reference an undeclared input and emit a dormant preflight guard." +// Plain fetch-depth-0 checkout only — this workflow has no workflow_dispatch expected_healed_sha +// input (unlike ci.yml heal revalidation). ci_artifact_consumer_prelude_steps paste-through would +// reference an undeclared input and emit a dormant preflight guard. fn fleet_converge_consumer_prelude_steps() -> List { [ci_checkout_step()] diff --git a/dag/gunbc/fleet/fleet_desired_admission.dag b/dag/gunbc/fleet/fleet_desired_admission.dag index 4033d0f26a5..1f19c37ff23 100644 --- a/dag/gunbc/fleet/fleet_desired_admission.dag +++ b/dag/gunbc/fleet/fleet_desired_admission.dag @@ -94,7 +94,8 @@ import std.decl_ref { DeclarationRef, WholeDeclaration } // ordinary spawn". Advancing desired to whatever last landed makes that comparison agree // unconditionally, so the gate passes silently. Gating the advance is what gives the // dispatch gate something real to compare against. -data fleet_desired_admission_note: String = "The live consumer of gunbc.fleet_main_revision decide_fleet_desired_advance. See the annotation above it." +// The live consumer of gunbc.fleet_main_revision decide_fleet_desired_advance. See the annotation +// above it. // The floor run's event payload, copied to a modeled path by the workflow step so the // decision reads a path this module names rather than an environment variable it cannot diff --git a/dag/gunbc/fleet/fleet_desired_admission_workflow.dag b/dag/gunbc/fleet/fleet_desired_admission_workflow.dag index 32323ffc2d1..41c7fa7ce32 100644 --- a/dag/gunbc/fleet/fleet_desired_admission_workflow.dag +++ b/dag/gunbc/fleet/fleet_desired_admission_workflow.dag @@ -58,7 +58,7 @@ import std.types { String } // an admitted one at a glance, which is the silence DESIGN section 5 forbids. The if_condition // below is scope, not a safety gate -- it keeps pull-request floor runs from opening a job whose // only possible outcome is a branch refusal, and the model re-checks the branch regardless. -data fleet_desired_admission_workflow_note: String = "This module emits the fleet-desired admission workflow. See the annotation above it." +// This module emits the fleet-desired admission workflow. See the annotation above it. data fleet_desired_admission_workflow_job_id: String = "admit" diff --git a/dag/gunbc/fleet/fleet_desired_observe.dag b/dag/gunbc/fleet/fleet_desired_observe.dag index 35def67f7ec..74ddeb359a3 100644 --- a/dag/gunbc/fleet/fleet_desired_observe.dag +++ b/dag/gunbc/fleet/fleet_desired_observe.dag @@ -63,8 +63,10 @@ fn observe_fleet_desired_revision(repo: FilePath) -> FleetDesiredRevision { data fleet_desired_observe_repository_path: String = "." -data fleet_desired_observe_repository_path_note: String = "The repository whose ancestry this observer inspects. It is `.` in production because the admission workflow runs in the checkout, and it is a DECLARED `.` rather than an ambient one so that a witness can point the same observation at a fixture repository without relocating the process." - +// The repository whose ancestry this observer inspects. It is `.` in production because the +// admission workflow runs in the checkout, and it is a DECLARED `.` rather than an ambient one so +// that a witness can point the same observation at a fixture repository without relocating the +// process. // THE EFFECTFUL HALF OF THE ANCESTRY OBSERVATION, AND THE SOLE PRODUCTION CONSTRUCTOR OF A // MERGE-BASE-DERIVED RELATION. diff --git a/dag/gunbc/fleet/fleet_hardware_standing_panel.dag b/dag/gunbc/fleet/fleet_hardware_standing_panel.dag index 80e2a0b2d50..2cb502e9c68 100644 --- a/dag/gunbc/fleet/fleet_hardware_standing_panel.dag +++ b/dag/gunbc/fleet/fleet_hardware_standing_panel.dag @@ -26,7 +26,9 @@ import gunbc.fleet_physical_inventory { fleet_dimm_verdicts, fleet_processor_ver // failed training and was reverted, so the tree asserted a memory population the machine did not // have, and the slot widths computed from it were wrong on the one host that differed. The verdict // existed the whole time and said so. Nobody could see it. -data fleet_hardware_standing_panel_note: String = "Daily-workspace panel over gunbc.fleet_physical_inventory's per-host reconcile verdicts. Renders expectation-versus-observation standing for memory and processor; reads the verdicts directly rather than re-deriving them." +// Daily-workspace panel over gunbc.fleet_physical_inventory's per-host reconcile verdicts. Renders +// expectation-versus-observation standing for memory and processor; reads the verdicts directly +// rather than re-deriving them. // THREE STANDINGS, NOT TWO, AND THE THIRD IS THE ONE THE PANEL EXISTS FOR. // diff --git a/dag/gunbc/fleet/fleet_host_budget.dag b/dag/gunbc/fleet/fleet_host_budget.dag index e60203ca9d4..b4880afd052 100644 --- a/dag/gunbc/fleet/fleet_host_budget.dag +++ b/dag/gunbc/fleet/fleet_host_budget.dag @@ -121,7 +121,6 @@ fn resolve_headroom(policy: HeadroomPolicy, floor: ByteSize) -> HeadroomResoluti } } - // OOMD GROUNDING IS A PER-HOST QUESTION AND WAS ASKED FLEET-WIDE. The thresholds it checks the // installed configuration against are a per-session ceiling and a PARENT SLICE CAP, and the slice // cap is exactly the host aggregate that gunbc.session_reservation_policy now answers per host -- @@ -145,11 +144,25 @@ fn gunbc_fleet_oomd_evidence_for(host: HostIdentity) -> OomdEnforcementEvidence } } -data gunbc_compile_pool_doc: String = "THE COMPILE POOL IS A CLAIM ON HOST RAM THAT THE PER-SLOT MODEL DID NOT SEE. extdeps.cache.sccache.sccache_server_process_facts cites that cache-miss compiles execute as CHILDREN OF THE SCCACHE SERVER. So compile RSS is charged to the SERVER's cgroup, not to the runner slot that issued the compile, and a per-slot memory cap therefore does NOT bound compile memory. Measured consequence on srv1, 2026-08-01: the CI server was lazily auto-spawned inside slot srv1-10's cgroup -- that slot's unit was inactive and disabled at the time -- and kept alive by SCCACHE_IDLE_TIMEOUT=0, so the five slots srv1 then served ran their compiles under one disabled slot's 15G high / 16G max caps and accumulated 229780 memory.max breach events and 12 oom_kills. THAT SENTENCE IS DATED BECAUSE NEITHER HALF OF IT DESCRIBES THE HOST NOW: measured 2026-08-25T18:43Z, srv1 serves 21 contiguous slots srv1-01..21 with srv1-10 ACTIVE and ENABLED among them, and no sccache process runs on the host at all -- the CI socket is present with zero listeners behind it. The MECHANISM this paragraph exists for is untouched by that, which is why the sentence is corrected rather than dropped: cache-miss compiles are children of whichever server holds the endpoint, so that server's cgroup bounds them wherever it lands. Sizing is jobserver_build_pool_bytes (tokens * mib_per_job) — the same cited authority that bounds build concurrency, NOT a fresh literal: the pool must hold exactly the compiles the jobserver admits." +// THE COMPILE POOL IS A CLAIM ON HOST RAM THAT THE PER-SLOT MODEL DID NOT SEE. +// extdeps.cache.sccache.sccache_server_process_facts cites that cache-miss compiles execute as +// CHILDREN OF THE SCCACHE SERVER. So compile RSS is charged to the SERVER's cgroup, not to the +// runner slot that issued the compile, and a per-slot memory cap therefore does NOT bound compile +// memory. Measured consequence on srv1, 2026-08-01: the CI server was lazily auto-spawned inside +// slot srv1-10's cgroup -- that slot's unit was inactive and disabled at the time -- and kept alive +// by SCCACHE_IDLE_TIMEOUT=0, so the five slots srv1 then served ran their compiles under one +// disabled slot's 15G high / 16G max caps and accumulated 229780 memory.max breach events and 12 +// oom_kills. THAT SENTENCE IS DATED BECAUSE NEITHER HALF OF IT DESCRIBES THE HOST NOW: measured +// 2026-08-25T18:43Z, srv1 serves 21 contiguous slots srv1-01..21 with srv1-10 ACTIVE and ENABLED +// among them, and no sccache process runs on the host at all -- the CI socket is present with zero +// listeners behind it. The MECHANISM this paragraph exists for is untouched by that, which is why +// the sentence is corrected rather than dropped: cache-miss compiles are children of whichever +// server holds the endpoint, so that server's cgroup bounds them wherever it lands. Sizing is +// jobserver_build_pool_bytes (tokens * mib_per_job) — the same cited authority that bounds build +// concurrency, NOT a fresh literal: the pool must hold exactly the compiles the jobserver admits. data gunbc_jobserver_token_override_note: String = "THE JOBSERVER OVERRIDE IS A PER-HOST FACT AND WAS RECORDED HERE AS A FLEET ONE. Observed on srv1 and srv2 on 2026-08-06, re-measured on all four hosts 2026-08-25T18:43Z; it is recorded here because it lands on exactly the axis the compile pool governs, and it is stated per host because the fleet is not uniform on either input the derivation takes.\n\n/usr/local/sbin/ctrl-jobserver derives tokens = min(cores - MARGIN, floor((MemTotal_gib - MEM_RESERVE_GIB) * 1024 / MIB_PER_JOB)) — the same derivation extdeps.jobserver.derive_jobserver_tokens carries. With the cited defaults (margin 8, reserve 16GiB, 1792MiB per job) the two bounds land differently on each machine, because MemTotal does. Measured 2026-08-25T18:43Z: srv2 at 125.09GiB yields min(120, 62) = 62 and the MEMORY bound binds below the core bound; srv1, srv3 and srv4 at about 502.25GiB yield min(120, 277) = 120 and the CORE bound binds. AN EARLIER REVISION OF THIS PARAGRAPH GAVE THE 125.09GiB FIGURE AS A PROPERTY OF `these hosts`, AND THAT SENTENCE COST REAL WORK: srv1 took the 64GiB DIMM population on 2026-08-22 that srv2's failed memory training reverted, so a session sizing the compile-pool flip read a memory bound that had stopped binding on three hosts of four and derived a pool wrong by roughly a factor of two. The figure was never false when written; it was written as a standing fact about a fleet, and a MemTotal is a fact about one machine at one moment.\n\nA SECOND NON-UNIFORMITY, measured the same moment and load-bearing for anyone reading the derivation as one authority: /usr/local/sbin/ctrl-jobserver exists on all four hosts and is TWO DIFFERENT PROGRAMS -- 14019 bytes dated May 31 on srv1 and srv2, 15406 bytes dated Jul 13 and Jul 22 on srv3 and srv4. The three defaults above are still documented in it, and citing `the installed script` cites four files of which two are a different version.\n\nsrv1 and srv2 set CTRL_JOBSERVER_TOKENS=128 in /etc/default/ctrl-jobserver and srv3 and srv4 set nothing there at all (measured 2026-08-25T18:43Z: both files carry no non-comment content), so two hosts short-circuit and two fall through to the derivation above. The script short-circuits on that variable — `if [[ -z \"$\{CTRL_JOBSERVER_TOKENS:-}\" ]]` guards the whole derivation, so an explicit setting skips the memory bound entirely rather than being clamped by it. 128 tokens at 1792MiB each is 224GiB of admissible concurrent compile memory, which is the over-admission on srv2's 125.09GiB and is no longer one on srv1's 502.24GiB -- the same override is a defect on one host and harmless on the other, which is precisely why it cannot be carried as a fleet claim.\n\nWHY THIS IS RECORDED AND NOT FIXED HERE: it is an operator configuration fact about a ctrl-era unit, not a gunbc-derived value, and gunbc has no realized knob for it (JobserverTokens is KnobUnimplemented — its presence is what makes interpret_host_converge refuse the whole host). Changing it is a host action with real consequences for build throughput and belongs to the jobserver re-homing lane, not to a budget module. What matters for THIS module is that the compile pool cannot be sized honestly against an admission ceiling that its own inputs disown — so when gunbc_compile_pool_placement flips to CompilePoolSliceDeclared, the pool must be sized from the DERIVED tokens, and the gap between derived and configured is the over-admission that flip would expose rather than create.\n\nTHE SWAP CORROBORATION THAT USED TO CLOSE THIS PARAGRAPH IS WITHDRAWN, AND THE WITHDRAWAL IS THE MORE USEFUL RECORD. It read: srv1 had 85GiB of swap in use against srv2's 1.3GiB, on identical hardware with identical swap provisioning (248GiB across two files), consistent with srv1's compile server thrashing under the starved cgroup gunbc_compile_pool_doc describes. It carried three claims and 2026-08-25T18:43Z falsifies two of them. Swap IN USE is now 5.4GiB on srv1 against 21MiB on srv2, so the asymmetry it inferred from is gone -- as it should be, since no sccache server runs on srv1 at all today. The hosts are not identical hardware, by the MemTotal split above. Only the provisioning half survives and only partly: srv1, srv2 and srv3 each carry 248GiB across two files and srv4 carries 128GiB." - type CompilePoolResolution = CompilePoolDerived { bytes: ByteSize } | CompilePoolUnresolved { reason: String } @@ -201,8 +214,30 @@ fn gunbc_runner_pool_budget_for(host: HostIdentity) -> ByteSize { data gunbc_per_runner_memory_cap: ByteSize = gunbc_runner_slot_desired().memory_max -data session_container_memory_deleted_note: String = "gunbc_session_container_memory (byte_size(1073741824)), session_pool_bytes and max_concurrent_sessions are DELETED. No capacity value moved; an unconsumed carrier with no decidable policy meaning was removed.\n\nWHY IT GOES, stated without claiming it nicknamed anything. It had no provenance — a bare literal with no note and no citation, in a module where every other figure carries its derivation. It had no production consumer: the census at deletion found only test.claim.host_allocation_conservation. And its two helpers merely multiplied and divided by it, so the only witness over them divided by the same constant it had just multiplied by and held for every value of that constant, including the wrong one it was reading.\n\nWHAT IS DELIBERATELY NOT CLAIMED, because an earlier revision of this note claimed it and was wrong. This row is NOT established as a second name for the host aggregate reservation. A per-session amount and a per-host aggregate slice are different axes, as are the per-session OOMD eviction ceiling and the ctrl-visible per-container cap; asserting that this literal nicknamed host_session_slice_bytes would repeat the per-container-versus-aggregate fusion this program exists to remove. The literal is deleted because it is groundless and unconsumed, and the question of which legitimate session-capacity axis eventually replaces it is left open here.\n\nNOR IS ANY LIVE VERDICT ATTRIBUTED TO IT. Nothing consumed it, so it cannot have produced a wrong production answer. The claim is counterfactual: had a budget consumed it, a 1 GiB per-session charge would have undercharged against the per-session peaks cited at this seam (min 0.28 / median ~2.06 / max 8.27 GiB) — below the median. The live sessions aggregate is and remains the Regime-2 row in gunbc.ci_floor_measurement, untouched by this change." - +// gunbc_session_container_memory (byte_size(1073741824)), session_pool_bytes and +// max_concurrent_sessions are DELETED. No capacity value moved; an unconsumed carrier with no +// decidable policy meaning was removed. +// +// WHY IT GOES, stated without claiming it nicknamed anything. It had no provenance — a bare literal +// with no note and no citation, in a module where every other figure carries its derivation. It had +// no production consumer: the census at deletion found only +// test.claim.host_allocation_conservation. And its two helpers merely multiplied and divided by it, +// so the only witness over them divided by the same constant it had just multiplied by and held for +// every value of that constant, including the wrong one it was reading. +// +// WHAT IS DELIBERATELY NOT CLAIMED, because an earlier revision of this note claimed it and was +// wrong. This row is NOT established as a second name for the host aggregate reservation. A +// per-session amount and a per-host aggregate slice are different axes, as are the per-session OOMD +// eviction ceiling and the ctrl-visible per-container cap; asserting that this literal nicknamed +// host_session_slice_bytes would repeat the per-container-versus-aggregate fusion this program +// exists to remove. The literal is deleted because it is groundless and unconsumed, and the +// question of which legitimate session-capacity axis eventually replaces it is left open here. +// +// NOR IS ANY LIVE VERDICT ATTRIBUTED TO IT. Nothing consumed it, so it cannot have produced a wrong +// production answer. The claim is counterfactual: had a budget consumed it, a 1 GiB per-session +// charge would have undercharged against the per-session peaks cited at this seam (min 0.28 / +// median ~2.06 / max 8.27 GiB) — below the median. The live sessions aggregate is and remains the +// Regime-2 row in gunbc.ci_floor_measurement, untouched by this change. // fabric_control_plane_pool IS A NAMED CHILD RATHER THAN AN ADDITION TO fixed_overhead, because the // resident it charges has an exact identity, an exact placement, an explicit lifecycle, a measurable @@ -304,7 +339,14 @@ fn host_allocation_verdict_charged( // about it. Making it a parameter means every caller must say which host's claim it is charging, // and the ones that cannot say so stop compiling -- which is the census, not an inconvenience. -data declared_runner_count_derivation_note: String = "This still divides the declared pool by the declared per-slot cap rather than reading gunbc_runner_slots_per_host directly, and the redundancy is now GONE rather than merely tolerated. The pool is derived as slots × MemoryMax (gunbc.runner_slot_allocation gunbc_runner_slice_cap_declared), so floor(pool ÷ MemoryMax) returns that same slot count by construction — the two agree because one is the other's inverse, not because an author kept them coherent. The old note admitted the difference: it said 80 ÷ 16 = 5 was 'coherent with slots_per_host by construction at these values', which is a coincidence with a caveat attached, and the caveat was doing the work." +// This still divides the declared pool by the declared per-slot cap rather than reading +// gunbc_runner_slots_per_host directly, and the redundancy is now GONE rather than merely +// tolerated. The pool is derived as slots × MemoryMax (gunbc.runner_slot_allocation +// gunbc_runner_slice_cap_declared), so floor(pool ÷ MemoryMax) returns that same slot count by +// construction — the two agree because one is the other's inverse, not because an author kept them +// coherent. The old note admitted the difference: it said 80 ÷ 16 = 5 was 'coherent with +// slots_per_host by construction at these values', which is a coincidence with a caveat attached, +// and the caveat was doing the work. fn declared_runner_count_for(host: HostIdentity) -> Int { measure_fit_count_floor( diff --git a/dag/gunbc/fleet/fleet_intent.dag b/dag/gunbc/fleet/fleet_intent.dag index 477ad534d8c..c5b9b6c2054 100644 --- a/dag/gunbc/fleet/fleet_intent.dag +++ b/dag/gunbc/fleet/fleet_intent.dag @@ -256,7 +256,20 @@ data fleet_host_memory_nominal_vs_usable_note: String = "Two DISTINCT memory fac data srv1_memory_population_observation_2026_07_16_note: String = "srv1 BMC 192.168.1.183 Redfish observation 2026-07-16 (rotated cred, operator-supplied): MemorySummary TotalSystemMemoryGiB=128; per-DIMM dimm0..dimm7 all HMA82GR7AFR4N-VK RankCount=1 DataWidthBits=64 BusWidthBits=72 CapacityMiB=16384 OperatingSpeedMhz=2666 (×8). Uniform 8-stick population of the existing cited row hma82gr7afr4n_vk_catalog (AFR4N=x4 1R) totals 128 GiB — dissolves the gibibyte(128) placeholder." -data srv1_memory_population_observation_2026_08_22_note: String = "srv1 UPGRADED to the 64 GiB Samsung population on 2026-08-22 and POSTed cleanly on the first attempt. OPERATOR-SUPPLIED OBSERVATION, not a read this lane took: reported from the host as 8 x M393A8G40D40-CRB totalling 502 GB as dmidecode/free render it. srv1's host does not accept the fleet-automation key and its BMC credential is not in Secret Manager, so no independent confirmation was available here.\n\nATTRIBUTION IS THE POINT OF THAT SENTENCE. srv3 and srv4 carry BMC Redfish reads taken directly; this row does not, and recording the difference is what keeps a relayed figure from being cited later as though it had the same standing (DESIGN 4b, rung honesty at a declared subject grain). It is admitted because the reporting path is the operator observing their own machine, which is the same class of evidence srv1 and srv2's 2026-07-16 rows already rest on.\n\nDissolve-on: an independent read of srv1 at either grain -- a BMC Redfish Memory collection once a credential exists, or /proc/meminfo plus dmidecode over a key authorized on that host." +// srv1 UPGRADED to the 64 GiB Samsung population on 2026-08-22 and POSTed cleanly on the first +// attempt. OPERATOR-SUPPLIED OBSERVATION, not a read this lane took: reported from the host as 8 x +// M393A8G40D40-CRB totalling 502 GB as dmidecode/free render it. srv1's host does not accept the +// fleet-automation key and its BMC credential is not in Secret Manager, so no independent +// confirmation was available here. +// +// ATTRIBUTION IS THE POINT OF THAT SENTENCE. srv3 and srv4 carry BMC Redfish reads taken directly; +// this row does not, and recording the difference is what keeps a relayed figure from being cited +// later as though it had the same standing (DESIGN 4b, rung honesty at a declared subject grain). +// It is admitted because the reporting path is the operator observing their own machine, which is +// the same class of evidence srv1 and srv2's 2026-07-16 rows already rest on. +// +// Dissolve-on: an independent read of srv1 at either grain -- a BMC Redfish Memory collection once +// a credential exists, or /proc/meminfo plus dmidecode over a key authorized on that host. data srv1_memory_population: HostMemoryPopulation = HostMemoryPopulation { rows: [ @@ -272,9 +285,21 @@ data srv2_memory_population: HostMemoryPopulation = HostMemoryPopulation { ], } -data srv3_memory_population_observation_2026_07_16_note: String = "srv3 BMC 192.168.1.192 Redfish observation 2026-07-16 (factory cred): MemorySummary TotalSystemMemoryGiB=128; per-DIMM dimm0 HMA82GR7AFR8N-VK RankCount=2 DataWidthBits=64 BusWidthBits=72 CapacityMiB=16384 OperatingSpeedMhz=2666 (×7); dimm7 HMA82GR7AFR4N-VK RankCount=1 DataWidthBits=64 BusWidthBits=72 CapacityMiB=16384 OperatingSpeedMhz=2666 (×1). Part-number decode: AFR8N=x8 2R → hma82gr7afr8n_vk_catalog; AFR4N=x4 1R → existing hma82gr7afr4n_vk_catalog. Mixed 7+1 population totals 128 GiB — the fact the old gibibyte(128) literal was hiding." - -data srv3_memory_population_superseded_note: String = "THE 2026-07-16 MIXED HYNIX POPULATION IS HISTORY, NOT THE CURRENT FACT, and it is kept because a superseded observation is still a true observation of its moment (DESIGN A3). It read 7 x HMA82GR7AFR8N-VK plus 1 x HMA82GR7AFR4N-VK, 128 GiB total, at 2666 MT/s. Those modules left the chassis in the 2026-08-22 replacement; the row below is what is in it now. Deleting the old reading would erase the only record of what the 7+1 mix was, which is the population every measurement taken before 2026-08-22 was taken against -- including gunbc_ci_srv3_memavailable_high_water, the receipt the managed-host overhead bound still rests on." +// srv3 BMC 192.168.1.192 Redfish observation 2026-07-16 (factory cred): MemorySummary +// TotalSystemMemoryGiB=128; per-DIMM dimm0 HMA82GR7AFR8N-VK RankCount=2 DataWidthBits=64 +// BusWidthBits=72 CapacityMiB=16384 OperatingSpeedMhz=2666 (×7); dimm7 HMA82GR7AFR4N-VK RankCount=1 +// DataWidthBits=64 BusWidthBits=72 CapacityMiB=16384 OperatingSpeedMhz=2666 (×1). Part-number +// decode: AFR8N=x8 2R → hma82gr7afr8n_vk_catalog; AFR4N=x4 1R → existing hma82gr7afr4n_vk_catalog. +// Mixed 7+1 population totals 128 GiB — the fact the old gibibyte(128) literal was hiding. + +// THE 2026-07-16 MIXED HYNIX POPULATION IS HISTORY, NOT THE CURRENT FACT, and it is kept because a +// superseded observation is still a true observation of its moment (DESIGN A3). It read 7 x +// HMA82GR7AFR8N-VK plus 1 x HMA82GR7AFR4N-VK, 128 GiB total, at 2666 MT/s. Those modules left the +// chassis in the 2026-08-22 replacement; the row below is what is in it now. Deleting the old +// reading would erase the only record of what the 7+1 mix was, which is the population every +// measurement taken before 2026-08-22 was taken against -- including +// gunbc_ci_srv3_memavailable_high_water, the receipt the managed-host overhead bound still rests +// on. data srv3_memory_population_observation_2026_08_22_note: String = "srv3 BMC 192.168.1.192 Redfish observation 2026-08-22T20:15:38Z, taken through the rotated credential in Secret Manager (bmc-srv3-admin). MemorySummary TotalSystemMemoryGiB=512; per-DIMM dimm0..dimm7 ALL M393A8G40D40-CRB CapacityMiB=65536 OperatingSpeedMhz=2133 RankCount=2 Manufacturer=Samsung, eight distinct serial numbers, banks DDR4_A1 through H1 (one per channel). Uniform 8-stick population of m393a8g40d40_crb0q_catalog totals 512 GiB.\n\nTHE READ WAS TAKEN MID-BOOT AND THAT IS WHY THE TIMESTAMP IS PRECISE. At 20:14:31 the same endpoint still reported 128 GiB and the old Hynix parts with BootProgress=None; the host had powered on at 20:13:15 after a blackout and had not completed POST. It reported SystemHardwareInitializationComplete at 20:15:05 and 512 GiB at 20:15:38. A reader who catches this endpoint before POST completes gets the PREVIOUS population with no indication it is stale, which is a live trap for exactly this kind of observation and is recorded so the next observer waits for BootProgress rather than trusting a quiet answer.\n\nOPERATING SPEED IS THE PART'S RATING, NOT A DOWNTRAIN. 2133 is what this DDR4-2133 module is specified at; the fleet moved 2666 -> 2133 by installing it. That is a real bandwidth reduction bought for 4x capacity and it is a trade the operator made deliberately, not a fault to chase.\n\nREDFISH RankCount=2 IS RECORDED HERE AND NOT IN THE CATALOG ROW. It is an observation of this host's controller enumeration; the module's logical rank organization is the OEM's fact and lives in extdeps.memory.samsung. See that row's note for why the two are not competing answers." @@ -312,7 +337,6 @@ data srv3_host: ComputeHost = ComputeHost { network_interfaces: [], } - data srv4_memory_population_observation_note: String = "srv4 BMC 192.168.1.195 Redfish observation 2026-08-22, taken between 19:46Z and 20:15Z (the exact minute was not recorded at read time and is NOT reconstructed here -- the window is bounded below by the 19:45:57Z correctable-ECC entry this session read first and above by the srv3 read at 20:15:38Z). srv3 carries a precise timestamp because its read was taken against a live boot transition and the clock mattered; this one was not, and a fabricated second would be worth less than an honest window. Taken through the rotated credential in Secret Manager (bmc-srv4-admin) -- the authenticated read this host's deferral named as its dissolution condition, so that scaffold is RETIRED by this row rather than carried forward. MemorySummary TotalSystemMemoryGiB=512; per-DIMM dimm0..dimm7 ALL M393A8G40D40-CRB CapacityMiB=65536 OperatingSpeedMhz=2133 RankCount=2 Manufacturer=Samsung, eight distinct serials, banks DDR4_A1 through H1, every module State=Enabled Health=OK and system HealthRollup=OK.\n\nTHE DEFERRAL'S OWN INSTRUCTION SAID TO DECODE AGAINST THE SK HYNIX CATALOG AND THAT INSTRUCTION IS SUPERSEDED, which is worth saying because following it literally would have produced a wrong row. It was written when the expected finding was srv1's uniform Hynix population; what is actually installed is a Samsung part that had no catalog row at all until 2026-08-22. The deferral was right that srv1's population must not be assumed, and wrong about which catalog the answer would come from.\n\nONE CORRECTABLE ECC EVENT IS ON RECORD FOR THIS POPULATION, logged 2026-08-22T19:45:57Z on Socket 0 / Channel 0 / DIMM 0 (bank 13, row 166, column 96), about three minutes after an unclean restart following a power blackout. It was corrected by ECC and no uncorrectable event accompanied it. It is recorded here rather than left in the BMC log because it is the only memory event this population has produced and because a repeat at the same bank/row/column would distinguish a marginal device from a single upset -- see gunbc.host_memory_qualification for the qualification standing this does not by itself change." data srv4_memory_population: HostMemoryPopulation = HostMemoryPopulation { diff --git a/dag/gunbc/fleet/fleet_intent_network.dag b/dag/gunbc/fleet/fleet_intent_network.dag index ac346556a5e..093648ff25b 100644 --- a/dag/gunbc/fleet/fleet_intent_network.dag +++ b/dag/gunbc/fleet/fleet_intent_network.dag @@ -26,7 +26,22 @@ data operator_host_srv4: HostIdentity = "srv4" data operator_host_srv5: HostIdentity = "srv5" data operator_host_srv6: HostIdentity = "srv6" -data network_endpoint_address_grounding_residual_note: String = "🟡 dissolve-on: the NetworkEndpoint.address / NetworkSegment.cidr / gateway literals in this file are still bare `\"192.168.1.x\"` / CIDR strings even though this same lane grounded the address leaves (extdeps.network.ipv4 Ipv4Address = 4 bounded Octets, extdeps.network.address IpAddress = IpV4|IpV6, and the DHCP lease rows migrated to the ipv4_address constructor). The octet decomposition is half-consumed here: product.network_topology.NetworkEndpoint.address is typed String, so grounding these sites means re-typing that field to extdeps.network.address.IpAddress (and cidr/gateway to their grounded carriers) and migrating every NetworkEndpoint/NetworkSegment consumer — a product-layer field change, not a gunbc-local edit, so it is deliberately deferred rather than half-done. DISSOLVES WHEN NetworkEndpoint.address re-grounds onto IpAddress; until then these literals are the §3 residual the reach lane closes, tracked here so the loop is visible rather than silently forked. The grounded rows the reach lane already built — gunbc.fleet_reach.srv2_lan_ipv4 / srv3_lan_ipv4 (extdeps.network.ipv4.Ipv4Address, consumed by srv3_reach) — are the CONVERGENCE TARGET these bare strings dissolve onto, not an independent second grounding: srv2/srv3's address becomes those rows when the field re-types, so the transitional dual representation is marked debt heading toward one authority, not a permanent fork." +// 🟡 dissolve-on: the NetworkEndpoint.address / NetworkSegment.cidr / gateway literals in this file +// are still bare `"192.168.1.x"` / CIDR strings even though this same lane grounded the address +// leaves (extdeps.network.ipv4 Ipv4Address = 4 bounded Octets, extdeps.network.address IpAddress = +// IpV4|IpV6, and the DHCP lease rows migrated to the ipv4_address constructor). The octet +// decomposition is half-consumed here: product.network_topology.NetworkEndpoint.address is typed +// String, so grounding these sites means re-typing that field to extdeps.network.address.IpAddress +// (and cidr/gateway to their grounded carriers) and migrating every NetworkEndpoint/NetworkSegment +// consumer — a product-layer field change, not a gunbc-local edit, so it is deliberately deferred +// rather than half-done. DISSOLVES WHEN NetworkEndpoint.address re-grounds onto IpAddress; until +// then these literals are the §3 residual the reach lane closes, tracked here so the loop is +// visible rather than silently forked. The grounded rows the reach lane already built — +// gunbc.fleet_reach.srv2_lan_ipv4 / srv3_lan_ipv4 (extdeps.network.ipv4.Ipv4Address, consumed by +// srv3_reach) — are the CONVERGENCE TARGET these bare strings dissolve onto, not an independent +// second grounding: srv2/srv3's address becomes those rows when the field re-types, so the +// transitional dual representation is marked debt heading toward one authority, not a permanent +// fork. data operator_lan_segment: NetworkSegment = NetworkSegment { cidr: "192.168.1.0/24", @@ -102,7 +117,34 @@ data srv4_host_lan_endpoint: NetworkEndpoint = NetworkEndpoint { locality: Lan, } -data srv3_host_lan_endpoint_note: String = "srv3's HOST OS endpoint, absent from this topology until 2026-07-24 — only srv3_bmc_endpoint (192.168.1.192) was modeled, so the model could name srv3's out-of-band controller but not the machine itself. Observed live from the operator's DHCP table (192.168.1.221, MAC 48:21:0B:81:C7:1D) and confirmed addressable by probe (sshd answers on :22). REACHABLE via the srv2 hop (corrected 2026-07-25): ubuntu@srv3 runs commands today authenticated by srv2's local node-orch key (proven live — hostname=srv3, aarch64, 128 cores). What is NOT yet present is the FLEET identity: the fleet-automation-ssh-key is authorized for no user on srv3 (born Jul 13 with ssh.authorized-keys EMPTY, hand-keyed post-install with node-orch@srv2 / briansrls@srv1 / macbook only), the B2 runner-user + A1 secret-materialization gap in gunbc.plans.fleet_subsumption_manual_gaps — a one-time host_authorized_keys_reconcile upsert over the working node-orch hop, not a networking fact and not an unreachable host. LEASE STATUS: as of 2026-07-24 the operator converted this to a STATIC reservation (192.168.1.221 ↔ MAC 48:21:0B:81:C7:1D), so the literal is now a fact rather than a lease-lifetime observation — matching the srv1/srv2 hosts and the srv1/srv2/srv3 BMCs. STILL DYNAMIC and therefore provisional: srv4's host (192.168.1.196) and srv4's BMC (192.168.1.195); those two literals remain only as stable as their leases until the operator reserves them. The DHCP-reservation act itself is an unmodeled manual step for srv1..4 — a static-lease intent (MAC ↔ address binding on the operator's router) has no carrier for those hosts; see gunbc.plans.fleet_subsumption_manual_gaps for where that gap is tracked. PARTIAL CLOSURE (SPARK-LANE-A, 2026-08-07): gunbc.spark.dgx_procurement's DgxSparkRouterBinding is that carrier for srv5/srv6, holding the cited extdeps.router.verizon_cr1000a reservation row Optionally and refusing deployment admission while it is Absent — and the two reservations the operator authored for the DGX Spark units ARE modeled there as observed router rows, held apart from the slot bindings until the operator states which box serves which slot. It is deliberately NOT retrofitted to srv1..4 in the same change: those four hosts are already reserved and enrolled, so a binding row for them would be authored from the live router rather than ahead of arrival, and that is a read-transport question (the cr1000a lease-table transport is itself an open extdeps frontier row) rather than the pre-arrival intent question srv5/srv6 pose." +// srv3's HOST OS endpoint, absent from this topology until 2026-07-24 — only srv3_bmc_endpoint +// (192.168.1.192) was modeled, so the model could name srv3's out-of-band controller but not the +// machine itself. Observed live from the operator's DHCP table (192.168.1.221, MAC +// 48:21:0B:81:C7:1D) and confirmed addressable by probe (sshd answers on :22). REACHABLE via the +// srv2 hop (corrected 2026-07-25): ubuntu@srv3 runs commands today authenticated by srv2's local +// node-orch key (proven live — hostname=srv3, aarch64, 128 cores). What is NOT yet present is the +// FLEET identity: the fleet-automation-ssh-key is authorized for no user on srv3 (born Jul 13 with +// ssh.authorized-keys EMPTY, hand-keyed post-install with node-orch@srv2 / briansrls@srv1 / macbook +// only), the B2 runner-user + A1 secret-materialization gap in +// gunbc.plans.fleet_subsumption_manual_gaps — a one-time host_authorized_keys_reconcile upsert over +// the working node-orch hop, not a networking fact and not an unreachable host. LEASE STATUS: as of +// 2026-07-24 the operator converted this to a STATIC reservation (192.168.1.221 ↔ MAC +// 48:21:0B:81:C7:1D), so the literal is now a fact rather than a lease-lifetime observation — +// matching the srv1/srv2 hosts and the srv1/srv2/srv3 BMCs. STILL DYNAMIC and therefore +// provisional: srv4's host (192.168.1.196) and srv4's BMC (192.168.1.195); those two literals +// remain only as stable as their leases until the operator reserves them. The DHCP-reservation act +// itself is an unmodeled manual step for srv1..4 — a static-lease intent (MAC ↔ address binding on +// the operator's router) has no carrier for those hosts; see +// gunbc.plans.fleet_subsumption_manual_gaps for where that gap is tracked. PARTIAL CLOSURE +// (SPARK-LANE-A, 2026-08-07): gunbc.spark.dgx_procurement's DgxSparkRouterBinding is that carrier +// for srv5/srv6, holding the cited extdeps.router.verizon_cr1000a reservation row Optionally and +// refusing deployment admission while it is Absent — and the two reservations the operator authored +// for the DGX Spark units ARE modeled there as observed router rows, held apart from the slot +// bindings until the operator states which box serves which slot. It is deliberately NOT +// retrofitted to srv1..4 in the same change: those four hosts are already reserved and enrolled, so +// a binding row for them would be authored from the live router rather than ahead of arrival, and +// that is a read-transport question (the cr1000a lease-table transport is itself an open extdeps +// frontier row) rather than the pre-arrival intent question srv5/srv6 pose. data srv3_host_lan_endpoint: NetworkEndpoint = NetworkEndpoint { label: "srv3-host-lan", diff --git a/dag/gunbc/fleet/fleet_posix_accounts.dag b/dag/gunbc/fleet/fleet_posix_accounts.dag index 3274a5e13fc..e56f8da1251 100644 --- a/dag/gunbc/fleet/fleet_posix_accounts.dag +++ b/dag/gunbc/fleet/fleet_posix_accounts.dag @@ -36,7 +36,13 @@ type PosixSudoGrant { probe: SudoNopasswdProbeKind } -data posix_sudo_grant_materialization_note: String = "PosixSudoGrant is the fleet-domain MATERIALIZATION carrier for std.effect_grant.Grant over ExecuteEffect: posix_sudo_grant_to_effect_grant projects command_path to filesystem_position and carries verb via verb_of_effect_shape(ExecuteEffect) -- never a parallel grant vocabulary (DESIGN 3). probe chooses the typed verification operation (execute-check, or grant-list when the binary may be absent); it is observation policy, not grant reach. WHO may sudo remains GroundedPosixPrincipal / std.access. dissolve-on = deploy-preflight envelope admits via admit_effect over projected grants." +// PosixSudoGrant is the fleet-domain MATERIALIZATION carrier for std.effect_grant.Grant over +// ExecuteEffect: posix_sudo_grant_to_effect_grant projects command_path to filesystem_position and +// carries verb via verb_of_effect_shape(ExecuteEffect) -- never a parallel grant vocabulary (DESIGN +// 3). probe chooses the typed verification operation (execute-check, or grant-list when the binary +// may be absent); it is observation policy, not grant reach. WHO may sudo remains +// GroundedPosixPrincipal / std.access. dissolve-on = deploy-preflight envelope admits via +// admit_effect over projected grants. fn absolute_command_path_segments(path: NonEmptyStr) -> List { filter(xs: split(s: path as String, delimiter: "/"), f: s => s != "") @@ -62,7 +68,10 @@ type DeployRunnerPrincipal { job_user: NonEmptyStr } -data deploy_runner_principal_note: String = "Two-phase runner principal (#6096 declared-vs-effective): unit_user is the systemd User= on the actions-runner unit (root on srv1); job_user is the deprivileged effective principal for workflow steps (ghrunner on srv1). Deploy authorization and sudoers roster ground on job_user only — never unit_user." +// Two-phase runner principal (#6096 declared-vs-effective): unit_user is the systemd User= on the +// actions-runner unit (root on srv1); job_user is the deprivileged effective principal for workflow +// steps (ghrunner on srv1). Deploy authorization and sudoers roster ground on job_user only — never +// unit_user. data srv1_ci_deploy_runner_principal: DeployRunnerPrincipal = DeployRunnerPrincipal { unit_user: "root", @@ -172,7 +181,9 @@ fn fleet_posix_operator_sudo_grant_is_hostnamectl_only() -> Bool { // Precedent: fleet_posix_ci_runner_sudo_grants already carries both binaries for ghrunner — // that is a finding, not automatic justification for the operator account. // Receipt publication: gunbc.host_layout fleet_converge_receipt_atomic_publish_note. -data fleet_converge_operator_privilege_hold_note: String = "b1 moved gunbc-roadmap-fleet-converge.service to User=briansrls. Whole-binary NOPASSWD install/systemctl on the operator roster is held — root-equivalent. MemoryMax is deploy-time only (gunbc.host_axis_caps); converge does not assert MemoryMax via set-property." +// b1 moved gunbc-roadmap-fleet-converge.service to User=briansrls. Whole-binary NOPASSWD +// install/systemctl on the operator roster is held — root-equivalent. MemoryMax is deploy-time only +// (gunbc.host_axis_caps); converge does not assert MemoryMax via set-property. data fleet_apt_get_binary_path: NonEmptyStr = "/usr/bin/apt-get" @@ -261,7 +272,6 @@ fn fleet_posix_srv3_host_sudo_grants() -> List { [] } - // --------------------------------------------------------------------------------------------- // NAME REQUIREMENTS -- what a name must BE, kept separate from what it must SPELL. // @@ -519,7 +529,6 @@ fn fleet_posix_publication_helper_sudo_grants() -> List { [] } - // --------------------------------------------------------------------------------------------- // ALLOCATION -- the host-local handle, and the numeric identity, kept apart from both. // --------------------------------------------------------------------------------------------- @@ -573,7 +582,11 @@ type PosixAccountAllocationStanding // GROUNDING -- six states, because Optional collapses six remedies into one absence. // --------------------------------------------------------------------------------------------- -data grounding_outcome_note: String = "Absent cannot carry a remedy. Intent-absent, allocated-but-unapplied, applied-but-unobserved, observation-contradicts-allocation, drifted and grounded are six different situations with six different next actions, and an Optional answer makes them one. Only PrincipalGrounded may enter live SSH or deployment access -- that is the construction wall replacing the reverted commit's attempt to present a desired account as a grounded one." +// Absent cannot carry a remedy. Intent-absent, allocated-but-unapplied, applied-but-unobserved, +// observation-contradicts-allocation, drifted and grounded are six different situations with six +// different next actions, and an Optional answer makes them one. Only PrincipalGrounded may enter +// live SSH or deployment access -- that is the construction wall replacing the reverted commit's +// attempt to present a desired account as a grounded one. type PosixAccountConflict = ObservedNameDiffersFromAllocation { observed: NonEmptyStr, allocated: NonEmptyStr } @@ -755,8 +768,6 @@ fn spark_inference_realization_standing(host: HostIdentity) -> ServicePrincipalR ServicePrincipalRealizationUnselected } - - // TOTAL PER-ACCOUNT ACCESSORS for the four legacy principals. These exist because their accounts // WERE observed on srv1/srv3, so the question "what is this account" has an answer that needs no // host parameter and no optional. Callers asking about a legacy srv1 account use these; callers @@ -794,7 +805,6 @@ fn fleet_posix_srv3_host_principal() -> GroundedPosixPrincipal { } } - // The legacy call sites all ask about an srv1/srv3 account that WAS observed. This keeps them total // without reintroducing a fabricated value: the four legacy keys are grounded, and the two // host-scoped principals REFUSE here rather than returning an inventable placeholder. A caller that @@ -904,7 +914,6 @@ fn fleet_posix_account_exists(name: NonEmptyStr) -> Bool { } } - // ── Reported contradiction of an authored row ───────────────────────────────────────────── // fleet_posix_ci_runner_user carries uid 1001 / gid 1001 and a declared_probe claiming // probe_command "id ghrunner" on srv1. A live readback of that exact command on that exact host @@ -950,14 +959,30 @@ fn reported_contradiction_is_open(c: ReportedAccountContradiction) -> Bool { c.modeled_uid != c.reported_uid || c.modeled_gid != c.reported_gid } -data fleet_account_contradiction_dissolve_on: String = "dissolve-on: fleet_posix_ci_runner_user carries authored uid/gid contradicted by a live readback on two hosts, and its declared_probe stamps a probe whose own command returns the contradicting values. DISSOLVES WHEN a PosixAccountObservation with a real ProcessReceipt grounds the ghrunner row through deploy_access_check_observed, at which point the observed values replace the authored ones and this contradiction row is deleted rather than kept beside them. Until then the model must not report the ghrunner account as grounded. FURTHER EVIDENCE 2026-08-22: `getent passwd ghrunner` returns nothing on srv5 and srv6 — the account is ABSENT there, not merely carrying different numbers, so the authored row is wrong on those hosts in a second and different way. This is recorded as EVIDENCE ONLY and deliberately does not correct the authored values: hand-editing observed numbers into the row would satisfy the letter of this condition while bypassing the ProcessReceipt grounding the condition exists to require, which is the failure mode the condition was written against. It also narrows what a corrected row must say — uid/gid alone cannot express an account that does not exist on a host, so grounding has to carry per-host presence rather than one fleet-wide pair." +// dissolve-on: fleet_posix_ci_runner_user carries authored uid/gid contradicted by a live readback +// on two hosts, and its declared_probe stamps a probe whose own command returns the contradicting +// values. DISSOLVES WHEN a PosixAccountObservation with a real ProcessReceipt grounds the ghrunner +// row through deploy_access_check_observed, at which point the observed values replace the authored +// ones and this contradiction row is deleted rather than kept beside them. Until then the model +// must not report the ghrunner account as grounded. FURTHER EVIDENCE 2026-08-22: `getent passwd +// ghrunner` returns nothing on srv5 and srv6 — the account is ABSENT there, not merely carrying +// different numbers, so the authored row is wrong on those hosts in a second and different way. +// This is recorded as EVIDENCE ONLY and deliberately does not correct the authored values: +// hand-editing observed numbers into the row would satisfy the letter of this condition while +// bypassing the ProcessReceipt grounding the condition exists to require, which is the failure mode +// the condition was written against. It also narrows what a corrected row must say — uid/gid alone +// cannot express an account that does not exist on a host, so grounding has to carry per-host +// presence rather than one fleet-wide pair. // The stale duplicate is a filesystem-grain second representation of one grant authority, and it // has a consequence beyond tidiness: srv1's effective privilege is the UNION of both files, so a // grant readback that reads one file reports a strict subset of real privilege and would classify // an over-granted host as exact. -data srv1_duplicate_sudoers_dropin_note: String = "srv1 carries /etc/sudoers.d/gunbc-deploy.bak-wavec-verify-179889 duplicating the managed grant list, reported by witty-raven-412 (msg_051d4971). Effective privilege is the union of both files, so any effective-access observation that reads a single dropin under-reports it. An effective-grant readback must enumerate the whole sudoers.d population before classifying a host exact." - +// srv1 carries /etc/sudoers.d/gunbc-deploy.bak-wavec-verify-179889 duplicating the managed grant +// list, reported by witty-raven-412 (msg_051d4971). Effective privilege is the union of both files, +// so any effective-access observation that reads a single dropin under-reports it. An +// effective-grant readback must enumerate the whole sudoers.d population before classifying a host +// exact. // ── The applied FleetAccountAutomation allocation ───────────────────────────────────────── // Label gunbc-fleet, THE SAME ON EVERY HOST THIS PRINCIPAL COVERS -- srv1 through srv4. That @@ -1025,6 +1050,17 @@ fn fleet_automation_allocations() -> List { ] } +// gunbc-fleet is uid 1001 on srv1 and srv2 (next free id). fleet_posix_ci_runner_user also claims +// 1001, wrongly -- ghrunner is 999/987 on both hosts. MEASURED 2026-08-22, a THIRD account reads +// 1001: gunbc-automation on srv5 and srv6 (1001:1001, /bin/bash), and ghrunner is ABSENT on those +// hosts entirely, so the ci-runner row is wrong there in a second way. Three accounts now read 1001 +// across four hosts. The hazard the earlier revision named got WORSE rather than different: uid is +// host-local and is never fleet principal identity, but a reader who treats a uid as identity now +// fuses three principals instead of two, and one of the three has disjoint sudo grants from the +// other two. It raises the priority of grounding the ghrunner row through +// deploy_access_check_observed rather than by hand-editing numbers, which would satisfy the letter +// of the contradiction row while bypassing the grounding it exists to require. + fn automation_allocation_is_applied(a: FleetAutomationAllocationStanding) -> Bool { match a { AutomationAllocationApplied { host: _, user: _, source: _ } => true @@ -1038,4 +1074,3 @@ fn automation_allocation_is_applied(a: FleetAutomationAllocationStanding) -> Boo // host. The numbers are not identity, so nothing is broken; but a reader comparing uids to decide // which account a file belongs to would fuse them, and that is precisely why the ghrunner // contradiction must be resolved by observation rather than left standing. -data automation_uid_coincidence_note: String = "gunbc-fleet is uid 1001 on srv1 and srv2 (next free id). fleet_posix_ci_runner_user also claims 1001, wrongly -- ghrunner is 999/987 on both hosts. MEASURED 2026-08-22, a THIRD account reads 1001: gunbc-automation on srv5 and srv6 (1001:1001, /bin/bash), and ghrunner is ABSENT on those hosts entirely, so the ci-runner row is wrong there in a second way. Three accounts now read 1001 across four hosts. The hazard the earlier revision named got WORSE rather than different: uid is host-local and is never fleet principal identity, but a reader who treats a uid as identity now fuses three principals instead of two, and one of the three has disjoint sudo grants from the other two. It raises the priority of grounding the ghrunner row through deploy_access_check_observed rather than by hand-editing numbers, which would satisfy the letter of the contradiction row while bypassing the grounding it exists to require." diff --git a/dag/gunbc/fleet/fleet_probe_identity_observe.dag b/dag/gunbc/fleet/fleet_probe_identity_observe.dag index 98c2bb7c77c..96d1324afe0 100644 --- a/dag/gunbc/fleet/fleet_probe_identity_observe.dag +++ b/dag/gunbc/fleet/fleet_probe_identity_observe.dag @@ -12,20 +12,19 @@ import gunbc.command_runner { run_shell_command_capture, process_outcome_receipt import extdeps.shell.exec { ProcessOutcome, ProcessOutputPresent, ProcessOutputAbsent, ProcessRefused } import gunbc.fleet_ssh_access { fleet_automation_ssh_key } -// Observe-first for the probe-identity decision (parent msg_156e62bd): READ-ONLY -// observation of (a) whether the fleet automation PUBLIC key is present in the -// runner-reachable authorized_keys on the host this job runs on (srv1 — the only host -// locally observable before first auth; srv2-4 are recorded pending-first-auth, never -// assumed either way), and (b) what GCP credential path the runner actually holds today -// (the A2 seam precondition for in-run private-key materialization). SECRET HYGIENE: -// only PUBLIC key material and presence verdicts are read or written — no private key -// bytes, no payload digests, no secret payloads of any kind touch this module. +// Observe-first for the probe-identity decision (parent msg_156e62bd): READ-ONLY observation of +// (a) whether the fleet automation PUBLIC key is present in the runner-reachable authorized_keys +// on the host this job runs on (srv1 — the only host locally observable before first auth; srv2-4 +// are recorded pending-first-auth, never assumed either way), and (b) what GCP credential path +// the runner holds today (the A2 seam precondition for in-run private-key materialization). +// SECRET HYGIENE: only PUBLIC key material and presence verdicts are read or written — no private +// key bytes, payload digests, or secret payloads of any kind touch this module. data fleet_probe_identity_receipt_path: String = "target/fleet-probe-identity-observe-receipt.txt" -// IT TAKES THE COMMAND, NOT ITS WORDS. As a List parameter this was a laundering seam -- -// a caller handed it a hand-spelled vector and it constructed the record on their behalf, so the -// words never passed a builder. The label stays a separate parameter: what a receipt line is CALLED -// is a fact about the receipt, not about the process. +// IT TAKES THE COMMAND, NOT ITS WORDS. As a List parameter this was a laundering seam -- a +// caller handed it a hand-spelled vector and it built the record on their behalf, so the words +// never passed a builder. The label stays separate: what a receipt line is CALLED is a fact about +// the receipt, not the process. fn observe_capture_line(label: String, command: ArgvCommand) -> String { match run_shell_command_capture(command: command, transport: LocalExec) { ProcessOutputPresent { text: t } => diff --git a/dag/gunbc/fleet/fleet_reach.dag b/dag/gunbc/fleet/fleet_reach.dag index fe18268329e..4eb67d1edba 100644 --- a/dag/gunbc/fleet/fleet_reach.dag +++ b/dag/gunbc/fleet/fleet_reach.dag @@ -11,7 +11,14 @@ import extdeps.network.reach { reach_target, } -data fleet_reach_grounded_address_note: String = "The grounded fleet host addresses — the SINGLE authority the gunbc.fleet_intent_network endpoint literals (still bare `\"192.168.1.x\"` strings, tracked by network_endpoint_address_grounding_residual_note) will consume when NetworkEndpoint.address re-grounds onto extdeps.network.address.IpAddress. Declaring them here as extdeps.network.ipv4.Ipv4Address (four bounded Octets) rather than re-parsing the bare strings is what keeps this a CONVERGENCE target and not a §3 fork: there is one grounded row per address, and the reach rows below consume it. srv2 = 192.168.1.188 (the reachable jump host), srv3 = 192.168.1.221 (the fabric-only target, static-leased 2026-07-24)." +// The grounded fleet host addresses — the SINGLE authority the gunbc.fleet_intent_network endpoint +// literals (still bare `"192.168.1.x"` strings, tracked by +// network_endpoint_address_grounding_residual_note) will consume when NetworkEndpoint.address +// re-grounds onto extdeps.network.address.IpAddress. Declaring them here as +// extdeps.network.ipv4.Ipv4Address (four bounded Octets) rather than re-parsing the bare strings is +// what keeps this a CONVERGENCE target and not a §3 fork: there is one grounded row per address, +// and the reach rows below consume it. srv2 = 192.168.1.188 (the reachable jump host), srv3 = +// 192.168.1.221 (the fabric-only target, static-leased 2026-07-24). data srv2_lan_ipv4: Ipv4Address = ipv4_address(octet1: 192, octet2: 168, octet3: 1, octet4: 188) data srv3_lan_ipv4: Ipv4Address = ipv4_address(octet1: 192, octet2: 168, octet3: 1, octet4: 221) @@ -19,11 +26,39 @@ data srv3_lan_ipv4: Ipv4Address = ipv4_address(octet1: 192, octet2: 168, octet3: data srv2_lan_reach_host: NetworkHost = host_by_ipv4(addr: srv2_lan_ipv4) data srv3_lan_reach_host: NetworkHost = host_by_ipv4(addr: srv3_lan_ipv4) -data srv3_reach_note: String = "How packets reach srv3's host OS — a network-reach fact, DELIBERATELY INDEPENDENT of the shell/auth transport that runs on srv3 once reached (operator rule 2026-07-24: 'the network transport should be independent of the shell transport'). srv3 is a fabric-only target: the intended long-term reach is FabricTunnel, which does not work yet ('none of that actually works'), so the operator-sanctioned interim is a ProxyJump through srv2 — something actually reachable that stands in for the fabric ('proxy to something actually reachable to the target (i.e. srv2) and pretend you are reaching it from a fabric — that is okay for now'). GROUNDED LIVE 2026-07-25: from srv2, `ssh -i ~/.node-orch/id_ed25519 ubuntu@192.168.1.221` runs a real command on srv3 (hostname=srv3, aarch64, 128 cores) — so the network reach via srv2 is realizable AND a shell path works, using srv2's local node-orch key which ubuntu@srv3's authorized_keys trusts. REALIZATION DETAIL: the reach realizes as NESTED EXEC (ssh srv2 'ssh srv3'), NOT a -J TCP tunnel — srv2 sets AllowTcpForwarding no, so a -J / -W attempt returns 'administratively prohibited'; the ProxyJump reach therefore realizes through the ProxyCommand-family nested-exec handler (also cited in reach.dag), which the reach abstraction is agnostic to (one reach, the realization srv2's policy permits). The reach-vs-shell split this model makes explicit still holds and is still worth keeping separate: reach_is_realizable(srv3_reach) is a NETWORK fact (packets flow via srv2), independent of WHICH identity authenticates once reached. The bootstrap identity (node-orch@srv2) works, and the FLEET identity (fleet-automation) was ADDED to ubuntu@srv3 on 2026-07-25 — the host_authorized_keys_reconcile srv3 upsert executed live over this hop (fleet-automation appended, the 3 pre-existing keys untouched per R5, backup taken), so srv3 now trusts the fleet identity too. srv3 was autoinstalled Jul 13 with empty ssh.authorized-keys and hand-keyed post-install; that A1/B2 drift is what the reconcile closed. NOT an unreachable host. DISSOLUTION TRIGGER: srv3_reach becomes FabricTunnel { target: srv3_lan_reach_host } when tunneling over the network fabric is operational; the ProxyJump-through-srv2 arm is a named interim, not a hidden hack, and it retires then." +// How packets reach srv3's host OS — a network-reach fact, DELIBERATELY INDEPENDENT of the +// shell/auth transport that runs on srv3 once reached (operator rule 2026-07-24: 'the network +// transport should be independent of the shell transport'). srv3 is a fabric-only target: the +// intended long-term reach is FabricTunnel, which does not work yet ('none of that actually +// works'), so the operator-sanctioned interim is a ProxyJump through srv2 — something actually +// reachable that stands in for the fabric ('proxy to something actually reachable to the target +// (i.e. srv2) and pretend you are reaching it from a fabric — that is okay for now'). GROUNDED LIVE +// 2026-07-25: from srv2, `ssh -i ~/.node-orch/id_ed25519 ubuntu@192.168.1.221` runs a real command +// on srv3 (hostname=srv3, aarch64, 128 cores) — so the network reach via srv2 is realizable AND a +// shell path works, using srv2's local node-orch key which ubuntu@srv3's authorized_keys trusts. +// REALIZATION DETAIL: the reach realizes as NESTED EXEC (ssh srv2 'ssh srv3'), NOT a -J TCP tunnel +// — srv2 sets AllowTcpForwarding no, so a -J / -W attempt returns 'administratively prohibited'; +// the ProxyJump reach therefore realizes through the ProxyCommand-family nested-exec handler (also +// cited in reach.dag), which the reach abstraction is agnostic to (one reach, the realization +// srv2's policy permits). The reach-vs-shell split this model makes explicit still holds and is +// still worth keeping separate: reach_is_realizable(srv3_reach) is a NETWORK fact (packets flow via +// srv2), independent of WHICH identity authenticates once reached. The bootstrap identity +// (node-orch@srv2) works, and the FLEET identity (fleet-automation) was ADDED to ubuntu@srv3 on +// 2026-07-25 — the host_authorized_keys_reconcile srv3 upsert executed live over this hop +// (fleet-automation appended, the 3 pre-existing keys untouched per R5, backup taken), so srv3 now +// trusts the fleet identity too. srv3 was autoinstalled Jul 13 with empty ssh.authorized-keys and +// hand-keyed post-install; that A1/B2 drift is what the reconcile closed. NOT an unreachable host. +// DISSOLUTION TRIGGER: srv3_reach becomes FabricTunnel { target: srv3_lan_reach_host } when +// tunneling over the network fabric is operational; the ProxyJump-through-srv2 arm is a named +// interim, not a hidden hack, and it retires then. data srv3_reach: Reach = proxy_jump(jumps: [srv2_lan_reach_host], target: srv3_lan_reach_host) -data srv3_reach_fabric_intended_note: String = "The intended long-term reach srv3_reach dissolves TO when the fabric works — modeled so the dissolution target is a real value, not just prose. It REFUSES realization today (reach_realizability -> ReachRefused { FabricTunnelNotOperational }), which is why srv3_reach uses the ProxyJump interim instead. This row is the dissolution trigger made concrete, not a second live reach." +// The intended long-term reach srv3_reach dissolves TO when the fabric works — modeled so the +// dissolution target is a real value, not just prose. It REFUSES realization today +// (reach_realizability -> ReachRefused { FabricTunnelNotOperational }), which is why srv3_reach +// uses the ProxyJump interim instead. This row is the dissolution trigger made concrete, not a +// second live reach. data srv3_reach_fabric_intended: Reach = fabric_tunnel(target: srv3_lan_reach_host) diff --git a/dag/gunbc/fleet/fleet_runner_connectivity.dag b/dag/gunbc/fleet/fleet_runner_connectivity.dag index c89cca87d8b..1beccb7e8a2 100644 --- a/dag/gunbc/fleet/fleet_runner_connectivity.dag +++ b/dag/gunbc/fleet/fleet_runner_connectivity.dag @@ -28,9 +28,27 @@ data fleet_runner_connectivity_authority_disposition: Disposition = Terminal { reason: "Single authority for GITHUB REGISTRATION CONNECTIVITY versus LOCAL RUNNER INCARNATIONS. It joins one org-level registration roster (remote ground truth from the GitHub Actions runners API) to per-slot stable identities (host, index) and their local systemd incarnations. It does NOT repair wedged runners — that is PR B. It refuses to fold connected width into desired width, and it refuses to treat a roster read failure as 'everything detached' (empty-observation narrow)." } -data organization_roster_read_locus_note: String = "THE ROSTER IS ONE CENTRAL ORG READ, not a per-host poll: slot health is derived by joining the org-level registration roster to stable (host, index) identities. The gunbai-ci GitHub App carries organization_self_hosted_runners read permission for this producer. Local journal mining and per-host gh api calls are not the authority for whether GitHub will schedule work onto a registration.\n\nTHIS ROW SAYS NOTHING ABOUT COMPLETENESS, AND THAT IS DELIBERATE. It replaces organization_roster_is_one_org_read_note, which asserted that the API 'returns every self-hosted registration for the organization in one response'. That was false and is now decided by OrganizationRunnerRosterEvidence rather than claimed here; a corrected sentence beside a carrier that decides the same question would be a second authority for it (DESIGN section 3). The three facts above are the surviving contents of that row, enumerated rather than swept away with it -- a prose row is deleted for one reason and takes everything in it unless its contents are named first." - -data achieved_widths_are_orthogonal_to_desired_width_note: String = "THE ACHIEVED AXES HERE ARE ORTHOGONAL TO gunbc.runner_slot_allocation's desired-side widths (#7930 candidate/actuatable/observed). registered_width counts GitHub org roster rows; connected_width counts status == online; schedulable_width counts online AND NOT busy; executing_width counts online AND busy. desired_width is INTENT from gunbc_runner_slots_per_host and does NOT reconcile down when connected collapses during an outage. materialized_width and unit_active_width are LOCAL incarnation facts pinned from the incident corpus until the local producer grounds them." +// THE ROSTER IS ONE CENTRAL ORG READ, not a per-host poll: slot health is derived by joining the +// org-level registration roster to stable (host, index) identities. The gunbai-ci GitHub App +// carries organization_self_hosted_runners read permission for this producer. Local journal mining +// and per-host gh api calls are not the authority for whether GitHub will schedule work onto a +// registration. +// +// THIS ROW SAYS NOTHING ABOUT COMPLETENESS, AND THAT IS DELIBERATE. It replaces +// organization_roster_is_one_org_read_note, which asserted that the API 'returns every self-hosted +// registration for the organization in one response'. That was false and is now decided by +// OrganizationRunnerRosterEvidence rather than claimed here; a corrected sentence beside a carrier +// that decides the same question would be a second authority for it (DESIGN section 3). The three +// facts above are the surviving contents of that row, enumerated rather than swept away with it -- +// a prose row deleted for one reason takes everything in it unless its contents are named first. + +// THE ACHIEVED AXES HERE ARE ORTHOGONAL TO gunbc.runner_slot_allocation's desired-side widths +// (#7930 candidate/actuatable/observed). registered_width counts GitHub org roster rows; +// connected_width counts status == online; schedulable_width counts online AND NOT busy; +// executing_width counts online AND busy. desired_width is INTENT from gunbc_runner_slots_per_host +// and does NOT reconcile down when connected collapses during an outage. materialized_width and +// unit_active_width are LOCAL incarnation facts pinned from the incident corpus until the local +// producer grounds them. type OrganizationRunnerRosterEvidence = RosterObserved { observed_on: NonEmptyStr, registrations: List } @@ -45,10 +63,9 @@ type OrganizationRunnerRosterEvidence // COMPLETENESS IS DERIVED AT THE BOUNDARY, NEVER ASSERTED BY A CALLER. The org runners API is // PAGINATED -- measured 2026-08-27 at total_count 522 against a 100-row page -- so a read that // succeeds is not thereby a read that saw everything. A caller cannot hand this constructor a -// prefix and have it enter as RosterObserved: the only way to reach that variant is for the -// observed length to equal the authority's own total, so "I saw all of it" is a computed fact -// about the response rather than a claim about the request (DESIGN section 5, construction over -// validation). +// prefix and have it enter as RosterObserved: the only way to that variant is for the observed +// length to equal the authority's own total, so "I saw all of it" is a computed fact about the +// response, not a claim about the request (DESIGN section 5, construction over validation). fn organization_runner_roster_from_page( observed_on: NonEmptyStr, registrations: List, @@ -81,33 +98,32 @@ type RegistrationSlotDecoding | RegistrationNamesNoCommittedSlot { registration_name: NonEmptyStr } // THE DECODER DOES NOT PARSE. Splitting "srv3-06" back into a host and an index would be a SECOND -// naming authority read in the opposite direction from the first, and the two could disagree without -// anything detecting it. The committed population is finite and already enumerable, so decoding is a -// total decision over it: which committed identity's own name equals this registration's name. That is -// the forward naming authority read backward (DESIGN section 4, one grammar in both directions) rather -// than an inverse re-implemented beside it, and a name matching nothing is not a parse failure -- it is -// the answer RegistrationNamesNoCommittedSlot, which is a fact about the fleet rather than about text. +// naming authority read in the opposite direction from the first, and the two could disagree +// undetected. The committed population is finite and enumerable, so decoding is a total decision +// over it: which committed identity's own name equals this registration's name. That is the +// forward naming authority read backward (DESIGN section 4, one grammar in both directions), not +// an inverse re-implemented beside it, and a name matching nothing is not a parse failure -- it is +// the answer RegistrationNamesNoCommittedSlot, a fact about the fleet rather than about text. // // THE FORK IS DISSOLVED, AND IT WAS FIVE SITES RATHER THAN THE THREE AN EARLIER REVISION OF THIS // NOTE CLAIMED. One rule -- pad a slot index to two digits -- had four independent implementations // (runner_lifecycle, runner_host_deploy, runner_incarnation, and host_registration_name_for_slot // just below, which reached it through a shared bucketed helper). Three derived the pad // arithmetically; runner_incarnation's slot_suffix ENUMERATED indices 1 through 8 with an else arm -// returning the literal "00". Measured before the fix: srv3-09 from the derived sites, srv3-00 from -// the enumerator -- a COLLISION rather than a wrong name, since every index above 8 produced the -// same string and two slots would contend for one GitHub registration. +// returning the literal "00". Measured before the fix: srv3-09 from the derived sites, srv3-00 +// from the enumerator -- a COLLISION rather than a wrong name, since every index above 8 produced +// the same string and two slots would contend for one GitHub registration. // -// All of them now route through gunbc.build_cache_instance runner_slot_index_suffix, which is the -// identity's own module and therefore the rule's layer. Derived, total over Nat, no fall-through to -// reach. My census of this fork said three sites twice -- to a reviewer and in a PR body -- and it -// was five; the two I missed were the ones that reached the rule INDIRECTLY, which is the same -// shape as every other undercount in this lane: I grepped for the thing I was looking for rather -// than enumerating the declarations. +// All now route through gunbc.build_cache_instance runner_slot_index_suffix, the identity's own +// module and therefore the rule's layer. Derived, total over Nat, no fall-through to reach. My +// census of this fork said three sites twice -- to a reviewer and in a PR body -- and it was five; +// the two I missed reached the rule INDIRECTLY, the same shape as every other undercount in this +// lane: I grepped for the thing I was looking for rather than enumerating the declarations. // // ONE INDEPENDENT IMPLEMENTATION SURVIVES DELIBERATELY, in host_converge_slice1_witness_test, which -// builds its own expected name inline. That is not a missed site: a witness that computes its -// expectation by calling the production function proves only that the function equals itself. Its -// pad is an independent oracle and consolidating it would convert a check into a tautology. +// builds its own expected name inline. Not a missed site: a witness that computes its expectation +// by calling the production function proves only that the function equals itself. Its pad is an +// independent oracle and consolidating it would convert a check into a tautology. fn fleet_committed_slot_identities() -> List { fold( fleet_hosts(), @@ -147,17 +163,17 @@ type RunnerSlotConnectivityVerdict decoded: RegistrationSlotDecoding } -// THIS ARM CARRIES THE REGISTRATION'S OWN IDENTITY AND NOT A SLOT, AND THAT IS THE WHOLE POINT OF IT. -// The other four arms each carry a RunnerSlotIdentity taken from a LOCAL incarnation, because they are -// produced by mapping over local_runner_slot_incarnations. A registration GitHub holds that we never -// provisioned locally has no incarnation to be mapped from, so it was not mis-reported by those four -- -// it was unsayable, and no check could close it because there was nothing for a check to read. -// Giving this arm a slot field would have rebuilt that same blindness inside the fix: it would have -// forced an unexpected registration to be described in terms of a local slot that by construction does -// not exist. It carries `decoded` instead, so a consumer can tell the two cases apart -- a registration -// whose name resolves to a COMMITTED slot identity is the disjointness violation this model exists for -// (an actions-runner sitting on a fabric-purpose slot), while one that resolves to nothing is a -// foreign runner in the org. Those are different facts and a single Bool would have merged them. +// THIS ARM CARRIES THE REGISTRATION'S OWN IDENTITY AND NOT A SLOT, AND THAT IS THE WHOLE POINT. +// The other four arms each carry a RunnerSlotIdentity from a LOCAL incarnation, being produced by +// mapping over local_runner_slot_incarnations. A registration GitHub holds that we never +// provisioned locally has no incarnation to map from, so it was not mis-reported by those four -- +// it was unsayable, and no check could close it because there was nothing to read. Giving this +// arm a slot field would rebuild that blindness inside the fix, forcing an unexpected registration +// to be described in terms of a local slot that by construction does not exist. It carries +// `decoded` instead, so a consumer can tell the two cases apart -- a registration whose name +// resolves to a COMMITTED slot identity is the disjointness violation this model exists for (an +// actions-runner sitting on a fabric-purpose slot), while one resolving to nothing is a foreign +// runner in the org. Different facts; a single Bool would have merged them. type FleetRunnerCapacityWidthSnapshot { desired_width: Int @@ -166,7 +182,18 @@ type FleetRunnerCapacityWidthSnapshot { achieved: FleetAchievedRunnerWidths } -data incident_2026_08_06_roster_note: String = "PINNED ACCEPTANCE CORPUS from the live GitHub org runners API during the 2026-08-06 broker-session incident (15 of 16 registrations offline; 1 functional runner). srv1 registered 01-05 with only 01 online+busy; srv2 registered 01,02,05 all offline with 03,04 absent; srv3 registered 01,02,04,05 all offline with 03 absent; srv4 registered 01,02,04,05 all offline with 03 absent. desired_width stays 22 while connected is 1 — derived at incident_fleet_desired_width_at_outage (srv4 still carried the five-slot disk row-pin on 2026-08-06. The live total is DERIVED by fleet_desired_width_total rather than pinned here, so it tracks gunbc_runner_slots_per_host: it read 24 while srv3/srv4 committed 7, and reads 22 since the 2026-08-17 16GiB ruling moved both managed hosts to 6. The incident figure of 22 is a DIFFERENT QUANTITY that happens to share the number — it is what the roster desired on 2026-08-06 (5+5+7+5, srv4 row-pinned), and it comes from the pinned per-host rows in incident_pinned_widths_note, never from the live allocation.)" +// PINNED ACCEPTANCE CORPUS from the live GitHub org runners API during the 2026-08-06 +// broker-session incident (15 of 16 registrations offline; 1 functional runner). srv1 registered +// 01-05 with only 01 online+busy; srv2 registered 01,02,05 all offline with 03,04 absent; srv3 +// registered 01,02,04,05 all offline with 03 absent; srv4 registered 01,02,04,05 all offline with +// 03 absent. desired_width stays 22 while connected is 1 — derived at +// incident_fleet_desired_width_at_outage (srv4 still carried the five-slot disk row-pin on +// 2026-08-06. The live total is DERIVED by fleet_desired_width_total rather than pinned here, so it +// tracks gunbc_runner_slots_per_host: it read 24 while srv3/srv4 committed 7, and reads 22 since +// the 2026-08-17 16GiB ruling moved both managed hosts to 6. The incident figure of 22 is a +// DIFFERENT QUANTITY that happens to share the number — it is what the roster desired on 2026-08-06 +// (5+5+7+5, srv4 row-pinned), and it comes from the pinned per-host rows in +// incident_pinned_widths_note, never from the live allocation.) data incident_srv4_committed_width_at_outage: Int = 5 @@ -263,7 +290,10 @@ fn find_registration_by_name( ) } -data incident_local_materialized_width_note: String = "ACTIVATED WIDTH vs INSTALL-TREE COUNT (operator correction 2026-08-07): counts ACTIVATED units, not install trees. srv1: 50 trees / 5 active; srv3: 8 materialized trees (includes 08) / 5 active — committed width 7 is memory ceiling, not materialization cap. Dormant trees are SlotMaterializedNotActive; stale .runner does not block ADD." +// ACTIVATED WIDTH vs INSTALL-TREE COUNT (operator correction 2026-08-07): counts ACTIVATED units, +// not install trees. srv1: 50 trees / 5 active; srv3: 8 materialized trees (includes 08) / 5 active +// — committed width 7 is memory ceiling, not materialization cap. Dormant trees are +// SlotMaterializedNotActive; stale .runner does not block ADD. fn materialized_width_for_host(host: HostIdentity) -> Int { if (host as String) == (operator_host_srv1 as String) { @@ -283,7 +313,6 @@ fn unit_active_width_for_host(host: HostIdentity) -> Int { materialized_width_for_host(host: host) } - fn host_registration_name_for_slot(host: HostIdentity, slot_index: Nat) -> NonEmptyStr { join([host as String, "-", runner_slot_index_suffix(index: slot_index)], "") as NonEmptyStr } @@ -336,12 +365,12 @@ fn count_host_online_at_width( } // THE CONVERGED-SEVEN FIXTURE CLUSTER IS DELETED HERE -- six declarations, all dead. It described -// a fleet that stopped existing with the 2026-08-17 16GiB ruling, and once registrations are decoded -// against the committed population rather than counted, srv3-07 resolves to no committed slot at all, -// so the planner refused it. Its last consumer was retargeted onto the roster below, which is the +// a fleet that stopped existing with the 2026-08-17 16GiB ruling, and once registrations are +// decoded against the committed population rather than counted, srv3-07 resolves to no committed +// slot, so the planner refused it. Its last consumer was retargeted onto the roster below, the // same concept authored once: both managed hosts registered 01-05 and online, converged at the -// materialized width. Keeping the seven-wide rows beside it would have been a second name for a -// fleet state, and a test fixture is not exempt from that. +// materialized width. Keeping the seven-wide rows beside it would be a second name for a fleet +// state, and a test fixture is not exempt from that. data width_five_online_srv3_registrations: List = [ incident_registration(id: 301, name: "srv3-01" as NonEmptyStr, status: GitHubRunnerOnline, busy: false), incident_registration(id: 302, name: "srv3-02" as NonEmptyStr, status: GitHubRunnerOnline, busy: false), @@ -421,11 +450,11 @@ fn registration_verdict_or( } // THE REFUSAL IS PER-QUESTION, NOT PER-ROSTER. A truncated read still answers exactly about every -// slot INSIDE the prefix, so refusing the whole roster would be a widen (DESIGN section 5: a failure -// arm must refuse, never widen) and would stop reconciliation on hosts this read covered perfectly -// well. What changes is only the ABSENT arm: a slot missing from a prefix was never observed to be -// missing, and rendering it Orphaned is the empty-observation narrow -- could-not-see reported as -// the verdict "it is gone", whose remedy is a restart at connectivity_replace_action. +// slot INSIDE the prefix, so refusing the whole roster would be a widen (DESIGN section 5: a +// failure arm must refuse, never widen) and would stop reconciliation on hosts this read covered +// perfectly well. Only the ABSENT arm changes: a slot missing from a prefix was never observed to +// be missing, and rendering it Orphaned is the empty-observation narrow -- could-not-see reported +// as the verdict "it is gone", whose remedy is a restart at connectivity_replace_action. fn runner_slot_connectivity_verdict_for_incarnation( incarnation: LocalRunnerSlotIncarnation, roster: OrganizationRunnerRosterEvidence, @@ -515,7 +544,19 @@ fn fleet_hosts() -> List { [operator_host_srv1, operator_host_srv2, operator_host_srv3, operator_host_srv4] } -data incident_pinned_widths_note: String = "THE 2026-08-06 CORPUS IS PINNED ON EVERY HOST, NOT JUST srv4, AND THAT IS THE WHOLE POINT OF IT. This fold previously pinned srv4 alone and read gunbc_runner_slots_per_host live for srv1-srv3, so a historical incident total silently tracked present-day allocation. The 2026-08-17 16GiB ruling moved srv3 from 7 to 6 and the outage total would have become 21 while incident_2026_08_06_roster_note and its witness both say 22 — a pinned corpus contradicting itself because it was only half pinned.\n\nDESIGN section 5's test-oracle discipline is the authority: a historical measurement compared against a literal is legitimate only when the measurement is independently fixed, and half of this one was live. The widths below are what the fleet committed ON 2026-08-06 (srv1 5, srv2 5, srv3 7, srv4 5 with its then-live disk row-pin), summing to the 22 the roster note records. They are frozen by date and must never be updated to follow an allocation ruling; a future ruling changes what the fleet desires TODAY, not what it desired during that outage." +// THE 2026-08-06 CORPUS IS PINNED ON EVERY HOST, NOT JUST srv4, AND THAT IS THE WHOLE POINT. This +// fold previously pinned srv4 alone and read gunbc_runner_slots_per_host live for srv1-srv3, so a +// historical incident total silently tracked present-day allocation. The 2026-08-17 16GiB ruling +// moved srv3 from 7 to 6 and the outage total would have become 21 while +// incident_2026_08_06_roster_note and its witness both say 22 — a pinned corpus contradicting +// itself because it was only half pinned. +// +// DESIGN section 5's test-oracle discipline is the authority: a historical measurement compared +// against a literal is legitimate only when the measurement is independently fixed, and half of +// this one was live. The widths below are what the fleet committed ON 2026-08-06 (srv1 5, srv2 5, +// srv3 7, srv4 5 with its then-live disk row-pin), summing to the 22 the roster note records. +// Frozen by date, never to be updated to follow an allocation ruling: a future ruling changes what +// the fleet desires TODAY, not what it desired during that outage. data incident_srv1_committed_width_at_outage: Int = 5 diff --git a/dag/gunbc/fleet/fleet_shell_transport_observation.dag b/dag/gunbc/fleet/fleet_shell_transport_observation.dag index 47102c9bf02..7dffcbf3dfa 100644 --- a/dag/gunbc/fleet/fleet_shell_transport_observation.dag +++ b/dag/gunbc/fleet/fleet_shell_transport_observation.dag @@ -11,7 +11,16 @@ import gunbc.fleet_reach { srv3_reach_is_realizable } data fleet_shell_transport_authority_disposition: Disposition = SingleAuthority -data fleet_shell_transport_scope_note: String = "SHELL TRANSPORT IS NOT NETWORK REACH. gunbc.fleet_reach answers whether packets can route to a host; this module answers whether an SSH session from the executing environment can authenticate and run a command. Both must be modeled separately (operator rule 2026-07-24). A ReachRefused or Permission-denied transport observation is a RECEIPT — not EndpointAbsent, swap Absent, or SessionsObservedAbsent. THE PRODUCER returns ShellTransportUnobserved until host_effect_apply can run the probe; it does not hand-enter host facts. still_ram_738_session_* rows below are dated execution receipts for one environment (2026-08-06 ssh BatchMode probes), not fabrication of swap, disk, or session counts. Review 49656: interim only — downstream modules must not treat these as permanent authority; fleet_shell_transport_observed_producer_dissolve_on names HostEffectTransport as the successor." +// SHELL TRANSPORT IS NOT NETWORK REACH. gunbc.fleet_reach answers whether packets can route to a +// host; this module answers whether an SSH session from the executing environment can authenticate +// and run a command. Both must be modeled separately (operator rule 2026-07-24). A ReachRefused or +// Permission-denied transport observation is a RECEIPT — not EndpointAbsent, swap Absent, or +// SessionsObservedAbsent. THE PRODUCER returns ShellTransportUnobserved until host_effect_apply can +// run the probe; it does not hand-enter host facts. still_ram_738_session_* rows below are dated +// execution receipts for one environment (2026-08-06 ssh BatchMode probes), not fabrication of +// swap, disk, or session counts. Review 49656: interim only — downstream modules must not treat +// these as permanent authority; fleet_shell_transport_observed_producer_dissolve_on names +// HostEffectTransport as the successor. type HostShellTransportObservation = ShellTransportSucceeded { @@ -54,7 +63,9 @@ data parent_probe_2026_08_07_srv3_shell_transport: HostShellTransportObservation detail: "vivid-eagle-131 reached srv3 on this path (supersedes still_ram_738 2026-08-06 ReachRefused from srv1-as-briansrls direct attempt; srv4 remains only unreachable host)" as NonEmptyStr, } -data srv3_shell_transport_observation_correction_note: String = "CORRECTION (2026-08-07): srv3 IS reachable via srv2->ubuntu@192.168.1.221. still_ram_738_srv3_shell_transport is a stale session receipt; parent_probe_2026_08_07_srv3_shell_transport is the current observation for planning." +// CORRECTION (2026-08-07): srv3 IS reachable via srv2->ubuntu@192.168.1.221. +// still_ram_738_srv3_shell_transport is a stale session receipt; +// parent_probe_2026_08_07_srv3_shell_transport is the current observation for planning. data still_ram_738_srv4_shell_transport: HostShellTransportObservation = ShellTransportReachRefused { observed_on: "2026-08-06" as NonEmptyStr, @@ -79,7 +90,10 @@ fn still_ram_738_session_shell_transport(host: HostIdentity) -> HostShellTranspo } } -data fleet_shell_transport_observed_producer_note: String = "SUCCESSOR IS A HOST-EFFECT PROBE, not a session receipt table. fleet_shell_transport_observed_producer will run ssh (or nested ssh for srv3_reach) through host_effect_apply. Until it lands, enrolled hosts fall through to the session receipt when the producer returns ShellTransportUnobserved." +// SUCCESSOR IS A HOST-EFFECT PROBE, not a session receipt table. +// fleet_shell_transport_observed_producer will run ssh (or nested ssh for srv3_reach) through +// host_effect_apply. Until it lands, enrolled hosts fall through to the session receipt when the +// producer returns ShellTransportUnobserved. fn fleet_shell_transport_observed_producer(host: HostIdentity) -> HostShellTransportObservation { if (host as String) == (operator_host_srv3 as String) { @@ -91,7 +105,10 @@ fn fleet_shell_transport_observed_producer(host: HostIdentity) -> HostShellTrans } } -data still_ram_738_session_shell_transport_receipt_authority_note: String = "DATED SESSION RECEIPTS are pinned census rows for one principal/environment (2026-08-06). They are NOT the live observation authority: fleet_shell_transport_observation keeps producer-Unobserved as Unobserved and cites these rows only as non-authoritative provenance (review 49707 — inverted fallback would graduate stale ReachRefused for principals with keys)." +// DATED SESSION RECEIPTS are pinned census rows for one principal/environment (2026-08-06). They +// are NOT the live observation authority: fleet_shell_transport_observation keeps +// producer-Unobserved as Unobserved and cites these rows only as non-authoritative provenance +// (review 49707 — inverted fallback would graduate stale ReachRefused for principals with keys). fn still_ram_738_session_shell_transport_receipt_note(host: HostIdentity) -> String { match still_ram_738_session_shell_transport(host: host) { @@ -125,7 +142,6 @@ fn fleet_shell_transport_observation(host: HostIdentity) -> HostShellTransportOb } } - fn srv3_network_reach_realizable() -> Bool { srv3_reach_is_realizable() } diff --git a/dag/gunbc/fleet/fleet_workflow_steps.dag b/dag/gunbc/fleet/fleet_workflow_steps.dag index 66fc40279cd..463f4cb5ea1 100644 --- a/dag/gunbc/fleet/fleet_workflow_steps.dag +++ b/dag/gunbc/fleet/fleet_workflow_steps.dag @@ -190,7 +190,6 @@ fn ci_setup_rust_step() -> Step { } } - fn ci_cache_cargo_step() -> Step { UsesStep { name: Present { value: "Cache Cargo" }, @@ -416,13 +415,20 @@ fn ci_fleet_key_agent_cleanup_step() -> Step { data ci_native_cache_derive_step_name: String = "Derive native-cache root (rustc-version segment; after setup-rust-toolchain)" - -data ci_native_cache_prelude_order_bridge_note: String = "Adjacency-as-proxy ordering witness (not a structural Step-order predicate): ci_native_cache_prelude_order_bridge builds the setup-rust HOME env line immediately followed by the native-cache step name at the emitter's six-space yaml indent. witness_prelude_native_cache_root_step_follows_setup_rust asserts expected_ci_yml/falsifier_yml contain that substring. The real invariant is only that segment derivation runs after setup-rust-toolchain installs rustc; this check over-constrains to textual adjacency and can false-RED on a legitimate step inserted between setup-rust and native-cache, emitter yaml reformatting, or setup-rust gaining fields after HOME — but not false-green if ordering regresses. A hand-rolled match on Step would be the structural alternative; non-fold-residue forbids the wildcard arms that predicate would need (receipt: nfr floor on 14bc815db4). Dissolves when prelude ordering is witnessed without coproduct-residue match arms." - +// Adjacency-as-proxy ordering witness (not a structural Step-order predicate): +// ci_native_cache_prelude_order_bridge builds the setup-rust HOME env line immediately followed by +// the native-cache step name at the emitter's six-space yaml indent. +// witness_prelude_native_cache_root_step_follows_setup_rust asserts expected_ci_yml/falsifier_yml +// contain that substring. The real invariant is only that segment derivation runs after +// setup-rust-toolchain installs rustc; this check over-constrains to textual adjacency and can +// false-RED on a legitimate step inserted between setup-rust and native-cache, emitter yaml +// reformatting, or setup-rust gaining fields after HOME — but not false-green if ordering +// regresses. A hand-rolled match on Step would be the structural alternative; non-fold-residue +// forbids the wildcard arms that predicate would need (receipt: nfr floor on 14bc815db4). Dissolves +// when prelude ordering is witnessed without coproduct-residue match arms. data ci_release_bins_artifact_name: String = "release-bins" - data gunbc_ci_aux_step_timeout_minutes: Duration = 5 data gunbc_ci_spark_serving_step_timeout_minutes: Duration = 60 @@ -439,19 +445,45 @@ DISSOLVE-ON: the first successful srv6 canary produces the measurement this enve data gunbc_ci_v1_compiler_tests_compile_gate_timeout_minutes: Duration = 15 -data gunbc_ci_v1_compiler_tests_compile_gate_timeout_note: String = "v1-compiler-tests retained-kernel execution gate step budget. Originally a compile-contract gate (operator ruling 2026-08-05, PR A / fierce-heron-177): reordered after the release build and switched to cargo test --release --no-run so v1-compiler compiles once per job. --no-run removed 2026-08-11 with the suite cutover; the crate shrank from 98 modules / 740 tests to the 9 declared retained modules / 30 tests, which execute rather than only compile. Warm runs should complete in seconds once the release artifact is warm; 15m retains the prior cold-host envelope without raising the cap." +// v1-compiler-tests retained-kernel execution gate step budget. Originally a compile-contract gate +// (operator ruling 2026-08-05, PR A / fierce-heron-177): reordered after the release build and +// switched to cargo test --release --no-run so v1-compiler compiles once per job. --no-run removed +// 2026-08-11 with the suite cutover; the crate shrank from 98 modules / 740 tests to the 9 declared +// retained modules / 30 tests, which execute rather than only compile. Warm runs should complete in +// seconds once the release artifact is warm; 15m retains the prior cold-host envelope without +// raising the cap. data gunbc_ci_stage0_partition_compile_gate_timeout_minutes: Duration = 15 -data gunbc_ci_stage0_partition_compile_gate_timeout_note: String = "stage0 partition compile gate step budget (sharp-fox-403 C1): seven sequential cargo check -p v1-stage0-* partition crates enrolled as the build-job compile backstop over stage0_partition_crate_rows. Cold-host envelope matches v1-compiler-tests compile gate until a dedicated receipt exists; warm partition crates are smaller than the monolith test crate." +// stage0 partition compile gate step budget (sharp-fox-403 C1): seven sequential cargo check -p +// v1-stage0-* partition crates enrolled as the build-job compile backstop over +// stage0_partition_crate_rows. Cold-host envelope matches v1-compiler-tests compile gate until a +// dedicated receipt exists; warm partition crates are smaller than the monolith test crate. data gunbc_ci_v1_compiler_test_targets_compile_gate_timeout_minutes: Duration = 10 -data gunbc_ci_v1_compiler_test_targets_compile_gate_timeout_note: String = "v1-compiler test-targets compile gate step budget. 10 minutes against 67s MEASURED for this step's exact argv (cargo check -p v1-compiler --all-targets, cold, remote runner, on the repaired tree after gunbc#8167 / gunbc#8173 landed the lib-test compile fix). The earlier figure recorded here was 99s from cargo check --workspace --all-targets, which was an upper BOUND rather than this step's cost — it was all that could be measured while main's lib test target did not compile, and it is superseded rather than kept beside the real number. Deliberately BELOW the 15m carried by the v1-compiler-tests and partition gates rather than copied from them: those two inherited a cold-host envelope with no dedicated receipt, and repeating a number nobody measured is how an unmeasured envelope becomes precedent. The gate runs AFTER the release build for the same reason the retained-kernel gate does — v1-compiler is already compiled in release with the shared feature set, so the check reuses that artifact instead of forcing a second compile. Raising this needs a measurement, not a round number." +// v1-compiler test-targets compile gate step budget. 10 minutes against 67s MEASURED for this +// step's exact argv (cargo check -p v1-compiler --all-targets, cold, remote runner, on the repaired +// tree after gunbc#8167 / gunbc#8173 landed the lib-test compile fix). The earlier figure recorded +// here was 99s from cargo check --workspace --all-targets, which was an upper BOUND rather than +// this step's cost — it was all that could be measured while main's lib test target did not +// compile, and it is superseded rather than kept beside the real number. Deliberately BELOW the 15m +// carried by the v1-compiler-tests and partition gates rather than copied from them: those two +// inherited a cold-host envelope with no dedicated receipt, and repeating a number nobody measured +// is how an unmeasured envelope becomes precedent. The gate runs AFTER the release build for the +// same reason the retained-kernel gate does — v1-compiler is already compiled in release with the +// shared feature set, so the check reuses that artifact instead of forcing a second compile. +// Raising this needs a measurement, not a round number. data gunbc_ci_artifact_transfer_step_timeout_minutes: Duration = 10 -data gunbc_ci_artifact_transfer_step_note: String = "Cross-job handoff of the release bins (operator job-split ruling 2026-07-11): the build job packs ci_floor_required_artifact_names into one tgz (tar preserves the executable bits a raw artifact zip would drop) and uploads it; the ci floor job downloads, unpacks into target/release, and re-verifies via claim_executor --verify-build-artifacts before any step consumes a bin (fail-closed: a missing or truncated artifact reds the job at the verify, never as a mid-floor bin fallback). 10m is the transfer envelope for a ~200MB archive under fleet pressure; upload uses if-no-files-found: error so an empty pack is loud." +// Cross-job handoff of the release bins (operator job-split ruling 2026-07-11): the build job packs +// ci_floor_required_artifact_names into one tgz (tar preserves the executable bits a raw artifact +// zip would drop) and uploads it; the ci floor job downloads, unpacks into target/release, and +// re-verifies via claim_executor --verify-build-artifacts before any step consumes a bin +// (fail-closed: a missing or truncated artifact reds the job at the verify, never as a mid-floor +// bin fallback). 10m is the transfer envelope for a ~200MB archive under fleet pressure; upload +// uses if-no-files-found: error so an empty pack is loud. data gunbc_ci_prelude_allowance_minutes: Duration = 5 @@ -518,7 +550,14 @@ type JobTimeoutBudget = JobTimeoutWithinCeiling { minutes: Int } | JobTimeoutExceedsCeiling { minutes: Int, ceiling: Int } -data checked_job_timeout_minutes_note: String = "Consumes extdeps.github.actions max_job_timeout_minutes by construction (operator review, bright-ferret-335, gunbc#8782): a computed backstop that would exceed GitHub Actions' platform ceiling must refuse rather than emit a timeout-minutes value the platform will reject or silently clamp — clamping is the fabricated-plausible-output failure one layer up, since the job would then die at the clamped cap while the model believed it had the computed value. This is a wall on any future per-tier raise, not evidence the current backstop is at risk: see gunbc_ci_fleet_job_backstop_timeout_note for why the honest worst-case-single-run total sits well under the ceiling today." +// Consumes extdeps.github.actions max_job_timeout_minutes by construction (operator review, +// bright-ferret-335, gunbc#8782): a computed backstop that would exceed GitHub Actions' platform +// ceiling must refuse rather than emit a timeout-minutes value the platform will reject or silently +// clamp — clamping is the fabricated-plausible-output failure one layer up, since the job would +// then die at the clamped cap while the model believed it had the computed value. This is a wall on +// any future per-tier raise, not evidence the current backstop is at risk: see +// gunbc_ci_fleet_job_backstop_timeout_note for why the honest worst-case-single-run total sits well +// under the ceiling today. fn checked_job_timeout_minutes(computed: Int) -> JobTimeoutBudget { if computed <= max_job_timeout_minutes { diff --git a/dag/gunbc/float_add_associativity_observation.dag b/dag/gunbc/float_add_associativity_observation.dag index 8ed14081afd..380726768ff 100644 --- a/dag/gunbc/float_add_associativity_observation.dag +++ b/dag/gunbc/float_add_associativity_observation.dag @@ -12,9 +12,16 @@ import extdeps.standards.ieee_754_2019 { Ieee754Standard, ieee_754_2019 } data float_add_associativity_cited_standard: Ieee754Standard = ieee_754_2019 -data float_add_associativity_observation_note: String = "Observing-layer execution receipt for Float add associativity under the IEEE 754 standard modeled in extdeps.standards.ieee_754_2019. Witness magnitudes derive from host execution via observe_float_add_associativity_triple, not hand-authored fold results. The associativity verdict shape lives in std.algebra_law; this module records only the observed folds and assembled receipt." - -data float_add_operation_identity_grounding_gap_note: String = "GROUNDING GAP (honest prerequisite, not a string nickname): Float addition is not structurally referenceable as a DeclarationRef today. This slice binds associativity evidence to AssociativityOperationSubject.AssociativityFloatAdd — the closed coproduct variant naming kernel Float plus BinOp.Add — until primitive_identity supplies a joinable operation DeclarationRef." +// Observing-layer execution receipt for Float add associativity under the IEEE 754 standard modeled +// in extdeps.standards.ieee_754_2019. Witness magnitudes derive from host execution via +// observe_float_add_associativity_triple, not hand-authored fold results. The associativity verdict +// shape lives in std.algebra_law; this module records only the observed folds and assembled +// receipt. + +// GROUNDING GAP (honest prerequisite, not a string nickname): Float addition is not structurally +// referenceable as a DeclarationRef today. This slice binds associativity evidence to +// AssociativityOperationSubject.AssociativityFloatAdd — the closed coproduct variant naming kernel +// Float plus BinOp.Add — until primitive_identity supplies a joinable operation DeclarationRef. data float_add_associativity_subject: AssociativityOperationSubject = AssociativityFloatAdd {} diff --git a/dag/gunbc/floor/floor_component_receipt.dag b/dag/gunbc/floor/floor_component_receipt.dag index a40adb2223a..e48a4cb9dfb 100644 --- a/dag/gunbc/floor/floor_component_receipt.dag +++ b/dag/gunbc/floor/floor_component_receipt.dag @@ -63,27 +63,143 @@ import gunbc.floor_component_receipt_document { ComponentOutcomeRead, ComponentConcluded, ComponentNotConcluded, floor_component_outcome_tag, floor_component_outcome_wire_tag} -data floor_component_receipt_note: String = "The floor's per-COMPONENT receipt, machine-readable, written on every exit path including a red one. WHY IT EXISTS, priced: .github/workflows/falsifier-alert.yml read only the overall workflow conclusion and hardcoded one causal story for every failure — 'affected-set cold control is dark, per-PR selection UNVERIFIED, Gate-1 receipts do not execute'. On run 30509024051 all three clauses were FALSE: the predict-only affected-set control concluded Done over 5217 witnesses and the expect_red batches produced their receipts; the single failing component was the silent-pick gate, killed at its whole-receipt wall budget. An overall-red composite that erases the green state of its completed children is a state-space conflation (DESIGN section 5), and it misdirects triage every time it fires. This module is the projection that makes the alert read state instead of inferring it. THE RECORD IS NOT NEW: each component is one batch-grain Concluded std.observation.ObservationEvent — the same authority gunbc.witness_row_cost names as THE record ('not a TSV schema') — so Refused stays distinct from Failed and a budget kill is TimedOut with its budget and elapsed, not a flattened red. A bespoke status schema here would have been a second representation of that record (DESIGN section 3)." - -data floor_component_receipt_role_note: String = "Components are identified by STRUCTURAL ROLE, never by batch index. Indices are not stable: gunbc_falsifier_batches composes conditionally on enrollment flags (falsifier_self_host_wet_batch_enrolled and siblings), so one flag flip shifts every later index — which is why the silent-pick gate reports as batch 4 today while sitting fifth in source order. An alert keyed on 'batch 1' would relocate the hardcoded assumption rather than remove it. The affected-set cold control is instead identified by the property that DEFINES it: node_frontier_selection = SelectionPredictOnly, reusing the std.realization_schedule authority (the per-PR corpus counterpart is SelectionApplied). Ambiguity fails closed: zero predict-only components yields ControlAbsent and two or more yields ControlAmbiguous, never a guess." - -data floor_component_receipt_timed_out_frontier_note: String = "TimedOut is MODELLED and PROJECTED here but the floor does not yet emit it, and that gap is declared rather than papered over. A whole-receipt wall-budget kill carries its numbers structurally at the point it is raised — v1_interpreter InterpError::WitnessWallBudgetExceeded { elapsed_ms, budget_ms } — but ClaimOutcome::RuntimeError flattens them to a formatted String before the walk sees them, so the executor cannot fill TimedOut's budget and elapsed without re-parsing its own prose. It therefore emits Refused with the interpreter's own located diagnostic, which is not a downgrade: a kill at a declared ceiling IS the deliberate, located stop that observation_refused_distinct_note defines Refused to mean. Fabricating 0 for budget and elapsed to reach the more specific arm would be the fabricated-plausible-output DESIGN section 5 forbids. Dissolve-on: ClaimOutcome carries the typed WitnessWallBudgetExceeded pair through to ClaimResult, at which point the budget arm becomes TimedOut and this note deletes. THAT TRIGGER HAS FIRED (2026-08-18): the seed no longer flattens a raised budget refusal into RuntimeError prose — both raised forms map onto ClaimOutcome::TimedOut at the eval seam with the pair typed. This note is therefore no longer a modelling frontier; it is unwired work. BUT DO NOT WIRE IT AS IT STANDS, and this is the whole reason the note is being amended rather than deleted. The seed now carries a THIRD axis this arm has no field for: BudgetCompletion, Interrupted or CompletedOverBudget. An armed deadline aborts the witness, so its elapsed is a right-censored LOWER BOUND — a row that would cost 2s and one that would cost 40s report the same figure, because the figure is the ceiling plus poll granularity. The completion-side backstop instead reclassifies a witness that RAN TO COMPLETION, so its elapsed is exact. Projecting both into one TimedOut { basis, budget, elapsed } re-erases at the authority layer precisely the distinction the seed was just corrected to keep, and it does it silently, because a censored figure and a measured one are the same shape. The precedent is this arm own basis field, added by operator ruling 2026-08-05 for the identical reason one axis over: two things surfaced as one arm and the tag alone never carried what told them apart. That is now twice on one arm, which is the tell that it is a product wearing a coproduct clothes. PREREQUISITE, and it is a std change rather than a floor one: std.observation TimedOut gains completion beside basis, with the same ONE-governs-BOTH argument observation_timed_out_basis_note already makes — a bound and a measurement cannot be compared, so a reader must be able to tell which it holds before it uses either number. The projection side needs no change then — floor_component_outcome_of_tag already admits the tag and floor_component_outcome_members already renders the fields, proven by execution today. The arm now also names its CLOCK (std.measure ClockBasis, operator ruling 2026-08-05), and the wall-budget kill this note is about is exactly why: a whole-receipt wall ceiling and the fast-lane thread-CPU fail-stop both surface as TimedOut, so the clock is what tells them apart. floor_component_outcome_of_tag takes the basis as a parameter rather than assuming one, because the tag alone has never carried it." - -data floor_component_receipt_seed_transport_deferral_note: String = "PURE BOOTSTRAP RECEIPT for the seed-side transport this module is consumed by (review 45058 asked for it, correctly — new hand-Rust owes a named deferral, not a description). WHAT IS ADDED: no new Rust file and no parallel authority — functions inside the existing claim_executor bin that locate this module, resolve it, call floor_component_row_of_failure_mode / floor_component_receipt_document, and write the returned bytes. WHAT IS NOT IN RUST: the receipt's shape, its schema, the failure-mode-to-outcome mapping, the role identification, and every judgment about success — all of that is here, under dag/test/claim/floor_component_receipt_witness_test.dag. The seed side carries no classification of its own; its `_ => failed` catch-all was removed in favour of floor_component_outcome_of_failure_mode's refusal arm. BOUNDARY SHAPE: the seed calls millisecond(count:) through the interpreter and hands back the resulting Millisecond, so no carrier is constructed in Rust and no flat-scalar duration crosses the seam. The sibling seam gunbc.observation_seed_render (render_phase_concluded_line, same bin) still takes elapsed_ms: Nat; that is un-migrated debt on its own lane and was deliberately NOT copied here. DEFERRED TO: the v1 exit finish line ZeroHandMaintainedRust, roadmap node v1-zero-hand-maintained-rust (gunbc.v1_deletion_plan v1_exit_finish_lines). CONCRETE DISSOLUTION: the transport deletes when the floor's receipt write is issued from .dag as a typed host effect (Filesystem.Write through host_effect_apply, the shell-to-intent Phase 2 routing for runtime-present sites) instead of std::fs::write in the seed — at that point the resolve-and-call marshalling has no reason to exist and this module's document function is invoked directly by the emitted walk." - -data floor_component_receipt_unit_surface_note: String = "Every duration on this module's public surface is the std.measure carrier Millisecond (= Measure, the ratified Measure shape), never a bare Nat named *_ms. Two independent reviews (review 45058, review 45070) flagged the earlier *_ms: Nat constructor parameters, and they were right: a duration whose unit lives only in the field name is form 3 of the standing unit-modeling merge-hold, and the fact that a sibling seed seam still spells it that way makes that seam debt rather than a licence. Immediate millisecond(count:) wrapping inside the body does NOT discharge it — the parameter surface is what callers can get wrong, and a caller passing seconds into wall_ms would have typechecked. The seed constructs no carrier: it calls millisecond(count:) across the interpreter boundary and passes the Millisecond back in, so the constructor stays the single authority (a Value::Record built in Rust would have forked it). There is exactly ONE seed entry point, floor_component_row_of_failure_mode, whose failure_mode vocabulary covers every disposition the walk can report including not_reached." +// The floor's per-COMPONENT receipt, machine-readable, written on every exit path including a red +// one. WHY IT EXISTS, priced: .github/workflows/falsifier-alert.yml read only the overall workflow +// conclusion and hardcoded one causal story for every failure — 'affected-set cold control is dark, +// per-PR selection UNVERIFIED, Gate-1 receipts do not execute'. On run 30509024051 all three +// clauses were FALSE: the predict-only affected-set control concluded Done over 5217 witnesses and +// the expect_red batches produced their receipts; the single failing component was the silent-pick +// gate, killed at its whole-receipt wall budget. An overall-red composite that erases the green +// state of its completed children is a state-space conflation (DESIGN section 5), and it misdirects +// triage every time it fires. This module is the projection that makes the alert read state instead +// of inferring it. THE RECORD IS NOT NEW: each component is one batch-grain Concluded +// std.observation.ObservationEvent — the same authority gunbc.witness_row_cost names as THE record +// ('not a TSV schema') — so Refused stays distinct from Failed and a budget kill is TimedOut with +// its budget and elapsed, not a flattened red. A bespoke status schema here would have been a +// second representation of that record (DESIGN section 3). + +// Components are identified by STRUCTURAL ROLE, never by batch index. Indices are not stable: +// gunbc_falsifier_batches composes conditionally on enrollment flags +// (falsifier_self_host_wet_batch_enrolled and siblings), so one flag flip shifts every later index +// — which is why the silent-pick gate reports as batch 4 today while sitting fifth in source order. +// An alert keyed on 'batch 1' would relocate the hardcoded assumption rather than remove it. The +// affected-set cold control is instead identified by the property that DEFINES it: +// node_frontier_selection = SelectionPredictOnly, reusing the std.realization_schedule authority +// (the per-PR corpus counterpart is SelectionApplied). Ambiguity fails closed: zero predict-only +// components yields ControlAbsent and two or more yields ControlAmbiguous, never a guess. + +// TimedOut is MODELLED and PROJECTED here but the floor does not yet emit it, and that gap is +// declared rather than papered over. A whole-receipt wall-budget kill carries its numbers +// structurally at the point it is raised — v1_interpreter InterpError::WitnessWallBudgetExceeded { +// elapsed_ms, budget_ms } — but ClaimOutcome::RuntimeError flattens them to a formatted String +// before the walk sees them, so the executor cannot fill TimedOut's budget and elapsed without +// re-parsing its own prose. It therefore emits Refused with the interpreter's own located +// diagnostic, which is not a downgrade: a kill at a declared ceiling IS the deliberate, located +// stop that observation_refused_distinct_note defines Refused to mean. Fabricating 0 for budget and +// elapsed to reach the more specific arm would be the fabricated-plausible-output DESIGN section 5 +// forbids. Dissolve-on: ClaimOutcome carries the typed WitnessWallBudgetExceeded pair through to +// ClaimResult, at which point the budget arm becomes TimedOut and this note deletes. THAT TRIGGER +// HAS FIRED (2026-08-18): the seed no longer flattens a raised budget refusal into RuntimeError +// prose — both raised forms map onto ClaimOutcome::TimedOut at the eval seam with the pair typed. +// This note is therefore no longer a modelling frontier; it is unwired work. BUT DO NOT WIRE IT AS +// IT STANDS, and this is the whole reason the note is being amended rather than deleted. The seed +// now carries a THIRD axis this arm has no field for: BudgetCompletion, Interrupted or +// CompletedOverBudget. An armed deadline aborts the witness, so its elapsed is a right-censored +// LOWER BOUND — a row that would cost 2s and one that would cost 40s report the same figure, +// because the figure is the ceiling plus poll granularity. The completion-side backstop instead +// reclassifies a witness that RAN TO COMPLETION, so its elapsed is exact. Projecting both into one +// TimedOut { basis, budget, elapsed } re-erases at the authority layer precisely the distinction +// the seed was just corrected to keep, and it does it silently, because a censored figure and a +// measured one are the same shape. The precedent is this arm own basis field, added by operator +// ruling 2026-08-05 for the identical reason one axis over: two things surfaced as one arm and the +// tag alone never carried what told them apart. That is now twice on one arm, which is the tell +// that it is a product wearing a coproduct clothes. PREREQUISITE, and it is a std change rather +// than a floor one: std.observation TimedOut gains completion beside basis, with the same +// ONE-governs-BOTH argument observation_timed_out_basis_note already makes — a bound and a +// measurement cannot be compared, so a reader must be able to tell which it holds before it uses +// either number. The projection side needs no change then — floor_component_outcome_of_tag already +// admits the tag and floor_component_outcome_members already renders the fields, proven by +// execution today. The arm now also names its CLOCK (std.measure ClockBasis, operator ruling +// 2026-08-05), and the wall-budget kill this note is about is exactly why: a whole-receipt wall +// ceiling and the fast-lane thread-CPU fail-stop both surface as TimedOut, so the clock is what +// tells them apart. floor_component_outcome_of_tag takes the basis as a parameter rather than +// assuming one, because the tag alone has never carried it. + +// PURE BOOTSTRAP RECEIPT for the seed-side transport this module is consumed by (review 45058 asked +// for it, correctly — new hand-Rust owes a named deferral, not a description). WHAT IS ADDED: no +// new Rust file and no parallel authority — functions inside the existing claim_executor bin that +// locate this module, resolve it, call floor_component_row_of_failure_mode / +// floor_component_receipt_document, and write the returned bytes. WHAT IS NOT IN RUST: the +// receipt's shape, its schema, the failure-mode-to-outcome mapping, the role identification, and +// every judgment about success — all of that is here, under +// dag/test/claim/floor_component_receipt_witness_test.dag. The seed side carries no classification +// of its own; its `_ => failed` catch-all was removed in favour of +// floor_component_outcome_of_failure_mode's refusal arm. BOUNDARY SHAPE: the seed calls +// millisecond(count:) through the interpreter and hands back the resulting Millisecond, so no +// carrier is constructed in Rust and no flat-scalar duration crosses the seam. The sibling seam +// gunbc.observation_seed_render (render_phase_concluded_line, same bin) still takes elapsed_ms: +// Nat; that is un-migrated debt on its own lane and was deliberately NOT copied here. DEFERRED TO: +// the v1 exit finish line ZeroHandMaintainedRust, roadmap node v1-zero-hand-maintained-rust +// (gunbc.v1_deletion_plan v1_exit_finish_lines). CONCRETE DISSOLUTION: the transport deletes when +// the floor's receipt write is issued from .dag as a typed host effect (Filesystem.Write through +// host_effect_apply, the shell-to-intent Phase 2 routing for runtime-present sites) instead of +// std::fs::write in the seed — at that point the resolve-and-call marshalling has no reason to +// exist and this module's document function is invoked directly by the emitted walk. + +// Every duration on this module's public surface is the std.measure carrier Millisecond (= +// Measure, the ratified Measure shape), never a bare Nat named *_ms. Two +// independent reviews (review 45058, review 45070) flagged the earlier *_ms: Nat constructor +// parameters, and they were right: a duration whose unit lives only in the field name is form 3 of +// the standing unit-modeling merge-hold, and the fact that a sibling seed seam still spells it that +// way makes that seam debt rather than a licence. Immediate millisecond(count:) wrapping inside the +// body does NOT discharge it — the parameter surface is what callers can get wrong, and a caller +// passing seconds into wall_ms would have typechecked. The seed constructs no carrier: it calls +// millisecond(count:) across the interpreter boundary and passes the Millisecond back in, so the +// constructor stays the single authority (a Value::Record built in Rust would have forked it). +// There is exactly ONE seed entry point, floor_component_row_of_failure_mode, whose failure_mode +// vocabulary covers every disposition the walk can report including not_reached. data floor_component_receipt_schema: String = "floor-component-receipt/v1" -data floor_component_resource_checkpoint_note: String = "OPERATOR RULING 2026-08-13 (#8239 → #8163): this receipt is a per-component RESOURCE CHECKPOINT for alert routing — batch/component outcomes, run_terminal incomplete envelope, selection degradation — NOT a second observation ledger and NOT a carrier for the exact active workset. Subject identity (entry/module/phase) and live measurements (RSS, swap, PSI, progress) belong on the one attempt ledger #8163 lands (std.observation RecordedObservation at target/floor-attempts//events/.jsonl, carrier a974f44 / sunny-pike-825), which the coordinator tails. A receipt field named active_workset would be empty by construction at every checkpoint the sequential floor writes today, while the subjects it would have named ride GUNBC_FLOOR_PHASE_JOURNAL instead — two channels reconciled by hand is the split the ruling refuses. The field is therefore absent from this schema; fold absorption into #8163 rather than mint a parallel authority here." - -data floor_component_phase_journal_scaffold_note: String = "TEMPORARY PROJECTION (operator ruling 2026-08-13, verdict bound to #8239 head): GUNBC_FLOOR_PHASE_JOURNAL active-workset admit/completed rows are the interim channel for in-flight witness identity until measurements and subjects emit as RecordedObservation events on the #8163 per-producer ledger (own producer identity — sequence-checked files refuse a second writer). Dissolve-on: #8163 carrier on main + floor walk emits subject/measurement events through that ledger; delete journal rows and cli_run active-workset append once FLOOR2 reducer consumes the ledger path green by execution." +// OPERATOR RULING 2026-08-13 (#8239 → #8163): this receipt is a per-component RESOURCE CHECKPOINT +// for alert routing — batch/component outcomes, run_terminal incomplete envelope, selection +// degradation — NOT a second observation ledger and NOT a carrier for the exact active workset. +// Subject identity (entry/module/phase) and live measurements (RSS, swap, PSI, progress) belong on +// the one attempt ledger #8163 lands (std.observation RecordedObservation at +// target/floor-attempts//events/.jsonl, carrier a974f44 / sunny-pike-825), which +// the coordinator tails. A receipt field named active_workset would be empty by construction at +// every checkpoint the sequential floor writes today, while the subjects it would have named ride +// GUNBC_FLOOR_PHASE_JOURNAL instead — two channels reconciled by hand is the split the ruling +// refuses. The field is therefore absent from this schema; fold absorption into #8163 rather than +// mint a parallel authority here. + +// TEMPORARY PROJECTION (operator ruling 2026-08-13, verdict bound to #8239 head): +// GUNBC_FLOOR_PHASE_JOURNAL active-workset admit/completed rows are the interim channel for +// in-flight witness identity until measurements and subjects emit as RecordedObservation events on +// the #8163 per-producer ledger (own producer identity — sequence-checked files refuse a second +// writer). Dissolve-on: #8163 carrier on main + floor walk emits subject/measurement events through +// that ledger; delete journal rows and cli_run active-workset append once FLOOR2 reducer consumes +// the ledger path green by execution. data floor_component_receipt_path: String = "target/floor-component-receipt.json" data floor_component_receipt_artifact_name: String = "floor-component-receipt" -data floor_component_failure_mode_edge_note: String = "WHY THE MODE RIDES BESIDE THE OUTCOME INSTEAD OF BEING RECOVERED FROM IT. floor_component_outcome_of_failure_mode is deliberately many-to-one: Infra and WitnessRed BOTH map to Failed, because both are a component that broke and ObservationOutcome is the right grain for the observation record. But they are not the same fault and they do not route to the same place — Infra is the fleet's (a runner died, a toolchain went missing), WitnessRed is the witness owner's (a claim is genuinely false). A consumer holding only the Failed arm cannot tell them apart, so it either routes both to one owner or re-derives the class by sniffing the diagnostic prose, which is the string-classification this module already refuses at the seed boundary. The mode therefore rides ON THE ROW, at row grain, and std.observation is NOT widened: ObservationOutcome stays the canonical record of WHAT the transition was, and the mode records WHICH declared failure vocabulary produced it. The two are derived from ONE tag in one place (floor_component_row_of_failure_mode), so they cannot disagree — a row whose mode says Infra while its outcome says Done is not constructible through the seed path. ModeUnrecorded is the honest arm for the tag-built constructor, which is handed an outcome directly and genuinely never saw a mode; the seed path never produces it." +// WHY THE MODE RIDES BESIDE THE OUTCOME INSTEAD OF BEING RECOVERED FROM IT. +// floor_component_outcome_of_failure_mode is deliberately many-to-one: Infra and WitnessRed BOTH +// map to Failed, because both are a component that broke and ObservationOutcome is the right grain +// for the observation record. But they are not the same fault and they do not route to the same +// place — Infra is the fleet's (a runner died, a toolchain went missing), WitnessRed is the witness +// owner's (a claim is genuinely false). A consumer holding only the Failed arm cannot tell them +// apart, so it either routes both to one owner or re-derives the class by sniffing the diagnostic +// prose, which is the string-classification this module already refuses at the seed boundary. The +// mode therefore rides ON THE ROW, at row grain, and std.observation is NOT widened: +// ObservationOutcome stays the canonical record of WHAT the transition was, and the mode records +// WHICH declared failure vocabulary produced it. The two are derived from ONE tag in one place +// (floor_component_row_of_failure_mode), so they cannot disagree — a row whose mode says Infra +// while its outcome says Done is not constructible through the seed path. ModeUnrecorded is the +// honest arm for the tag-built constructor, which is handed an outcome directly and genuinely never +// saw a mode; the seed path never produces it. type FloorComponentRow { event: ObservationEvent @@ -140,7 +256,16 @@ fn floor_component_outcome_of_tag( else { none } } -data floor_component_failure_mode_note: String = "The seed's failure-mode vocabulary (claim_executor falsifier_failure_mode: BudgetExceeded | Infra | WitnessRed, plus the no-failures case) mapped onto ObservationOutcome HERE rather than in Rust, so the classification sits under a witness and the seed transport carries no judgment of its own. BudgetExceeded becomes Refused, not Failed: a kill at a declared ceiling is the deliberate, located stop observation_refused_distinct_note defines Refused to mean, and its remedy (re-basis the dated ceiling, or fix the row's cost) differs from a broken witness's. UNKNOWN MODE REFUSES. The first version of this mapping lived in the seed with a `_ => failed` catch-all, which would have absorbed a newly added mode into `failed` silently — a widen with no diagnostic, exactly the absorbing fallback DESIGN section 5 forbids. Here an unmodelled mode returns absent, the row cannot be built, and the walk fails closed with the mode named." +// The seed's failure-mode vocabulary (claim_executor falsifier_failure_mode: BudgetExceeded | Infra +// | WitnessRed, plus the no-failures case) mapped onto ObservationOutcome HERE rather than in Rust, +// so the classification sits under a witness and the seed transport carries no judgment of its own. +// BudgetExceeded becomes Refused, not Failed: a kill at a declared ceiling is the deliberate, +// located stop observation_refused_distinct_note defines Refused to mean, and its remedy (re-basis +// the dated ceiling, or fix the row's cost) differs from a broken witness's. UNKNOWN MODE REFUSES. +// The first version of this mapping lived in the seed with a `_ => failed` catch-all, which would +// have absorbed a newly added mode into `failed` silently — a widen with no diagnostic, exactly the +// absorbing fallback DESIGN section 5 forbids. Here an unmodelled mode returns absent, the row +// cannot be built, and the walk fails closed with the mode named. fn floor_component_outcome_of_failure_mode(mode: NonEmptyStr, detail: NonEmptyStr) -> ObservationOutcome? { match floor_component_failure_mode_of(mode: mode) { @@ -324,7 +449,19 @@ fn floor_component_wall_members(w: Measured) -> List { } } -data floor_component_is_done_locality_note: String = "Why this predicate is LOCAL and not a std.observation addition (review 45058 asked for the canonical fold/query surface instead). Checked: std.observation exposes observation_outcome_class and observation_outcome_is_anomaly, and BOTH are themselves total hand-written matches over ObservationOutcome — there is no fold or query surface over that coproduct to consume, so sibling-style matching IS the canonical shape at the authority. It is also not a fork of is_anomaly, which answers a different question and disagrees on two arms: Skipped is NOT an anomaly (nothing broke) yet is plainly not Done, and Final is not an anomaly either. So !observation_outcome_is_anomaly would report a skipped batch as successful — the exact conflation this receipt exists to prevent. Routing through observation_outcome_class would replace 6 arms with 10 and still lose the Final { shown_failures } reading. Kept local under second-consumer discipline (DESIGN section 6, model just-in-time): it has exactly one consumer today. Dissolve-on: a SECOND consumer needs a component-success predicate, at which point it lifts to std.observation beside observation_outcome_is_anomaly and this row deletes." +// Why this predicate is LOCAL and not a std.observation addition (review 45058 asked for the +// canonical fold/query surface instead). Checked: std.observation exposes observation_outcome_class +// and observation_outcome_is_anomaly, and BOTH are themselves total hand-written matches over +// ObservationOutcome — there is no fold or query surface over that coproduct to consume, so +// sibling-style matching IS the canonical shape at the authority. It is also not a fork of +// is_anomaly, which answers a different question and disagrees on two arms: Skipped is NOT an +// anomaly (nothing broke) yet is plainly not Done, and Final is not an anomaly either. So +// !observation_outcome_is_anomaly would report a skipped batch as successful — the exact conflation +// this receipt exists to prevent. Routing through observation_outcome_class would replace 6 arms +// with 10 and still lose the Final { shown_failures } reading. Kept local under second-consumer +// discipline (DESIGN section 6, model just-in-time): it has exactly one consumer today. +// Dissolve-on: a SECOND consumer needs a component-success predicate, at which point it lifts to +// std.observation beside observation_outcome_is_anomaly and this row deletes. fn floor_component_row_is_done(row: FloorComponentRow) -> Bool { match floor_component_transition_outcome(t: row.event.transition) { @@ -426,7 +563,18 @@ fn floor_component_receipt_json_value( } } -data selection_degradation_member_deleted_note: String = "The receipt carried two members about affected-set selection — selection_degradation and affected_set_cold_control — and both are deleted with the mechanism they described (2026-08-15). Neither can be reconstructed from a run because the fact they reported no longer exists: nothing selects, so there is no degradation to report and no cold control to identify.\n\nWHAT THE DELETION MUST NOT BE READ AS. selection_degradation was made an explicit null on every path (review 49594) precisely so that ABSENT could mean the writer did not tell you either way. Removing the member returns it to absent — but for every reader, unconditionally, rather than for one path. That is only sound because the corresponding read-side requirement in gunbc.floor_component_receipt_document is removed in the same change: a writer that stops emitting a member a decoder still requires is exactly the broken encode-then-decode that ruling was about." +// The receipt carried two members about affected-set selection — selection_degradation and +// affected_set_cold_control — and both are deleted with the mechanism they described (2026-08-15). +// Neither can be reconstructed from a run because the fact they reported no longer exists: nothing +// selects, so there is no degradation to report and no cold control to identify. +// +// WHAT THE DELETION MUST NOT BE READ AS. selection_degradation was made an explicit null on every +// path (review 49594) precisely so that ABSENT could mean the writer did not tell you either way. +// Removing the member returns it to absent — but for every reader, unconditionally, rather than for +// one path. That is only sound because the corresponding read-side requirement in +// gunbc.floor_component_receipt_document is removed in the same change: a writer that stops +// emitting a member a decoder still requires is exactly the broken encode-then-decode that ruling +// was about. fn floor_component_receipt_document( workflow_name: NonEmptyStr, diff --git a/dag/gunbc/floor/floor_component_receipt_document.dag b/dag/gunbc/floor/floor_component_receipt_document.dag index 78b1e4b335e..b4ce4f2c252 100644 --- a/dag/gunbc/floor/floor_component_receipt_document.dag +++ b/dag/gunbc/floor/floor_component_receipt_document.dag @@ -20,9 +20,27 @@ import extdeps.languages.json.parse { import extdeps.github.workflow_run_event { WorkflowRunEvent } import std.observation { ObservationOutcome, Done, Refused, Failed, TimedOut, Skipped, Final } -data floor_component_receipt_document_purpose_note: String = "ALERT-3. The floor writes a per-component receipt as JSON built directly from typed rows, and until now there was no carrier for the DOCUMENT itself and no way back: the receipt could be produced but not read, so its only consumer decoded it by hand in another language and every field access was an unchecked guess about a shape nothing declared. This module gives the document a type and derives BOTH directions across it — document to JSON, and JSON to document or a typed, located refusal.\n\nWHY BOTH DIRECTIONS MATTER HERE and not just the writing one: the receipt crosses a real trust boundary. It is uploaded by one workflow run, downloaded by another, and rendered by a program holding issues: write. A decoder that cannot refuse is a decoder that will render whatever it is handed, and the failure mode is not a crash — it is a confident, wrong issue body describing a state that no run was ever in. Refusal is the point of the return direction, which is why every arm of the decoding names what did not decode rather than substituting a default." - -data floor_component_receipt_document_member_authority_note: String = "THE MEMBER NAMES ARE DECLARED HERE ONCE AND CONSUMED BY BOTH SIDES. The encoder in gunbc.floor_component_receipt imports these constants rather than repeating the literals it used to carry inline, so the writer and the reader cannot drift: renaming a member is one edit, and a member this module can read is by construction a member that module writes. Two independent string tables for one wire shape is exactly the second representation DESIGN §3 forbids, and it is the specific defect that makes decoders rot silently — the writer changes a key, the reader keeps looking for the old one, and the field simply reads as absent forever." +// ALERT-3. The floor writes a per-component receipt as JSON built directly from typed rows, and +// until now there was no carrier for the DOCUMENT itself and no way back: the receipt could be +// produced but not read, so its only consumer decoded it by hand in another language and every +// field access was an unchecked guess about a shape nothing declared. This module gives the +// document a type and derives BOTH directions across it — document to JSON, and JSON to document or +// a typed, located refusal. +// +// WHY BOTH DIRECTIONS MATTER HERE and not just the writing one: the receipt crosses a real trust +// boundary. It is uploaded by one workflow run, downloaded by another, and rendered by a program +// holding issues: write. A decoder that cannot refuse is a decoder that will render whatever it is +// handed, and the failure mode is not a crash — it is a confident, wrong issue body describing a +// state that no run was ever in. Refusal is the point of the return direction, which is why every +// arm of the decoding names what did not decode rather than substituting a default. + +// THE MEMBER NAMES ARE DECLARED HERE ONCE AND CONSUMED BY BOTH SIDES. The encoder in +// gunbc.floor_component_receipt imports these constants rather than repeating the literals it used +// to carry inline, so the writer and the reader cannot drift: renaming a member is one edit, and a +// member this module can read is by construction a member that module writes. Two independent +// string tables for one wire shape is exactly the second representation DESIGN §3 forbids, and it +// is the specific defect that makes decoders rot silently — the writer changes a key, the reader +// keeps looking for the old one, and the field simply reads as absent forever. data receipt_member_schema: String = "schema" data receipt_member_run_id: String = "run_id" @@ -34,7 +52,20 @@ data receipt_member_run_terminal: String = "run_terminal" data receipt_member_workflow_name: String = "workflow_name" data receipt_member_head_sha: String = "head_sha" -data floor_component_receipt_subject_note: String = "THE SUBJECT IS WHAT MAKES THE RECEIPT ATTRIBUTABLE, and its absence is the gap this row closes. A receipt that says only `here are some component outcomes` cannot answer the one question its consumer must ask before rendering it: IS THIS ABOUT THE RUN I AM LOOKING AT. Without a subject the alert downloads an artifact named floor-component-receipt from a run id it was given and trusts the association, so a stale artifact, a mismatched run-id parameter, or an artifact from a different workflow entirely all render as the current state of the current run with no way to notice.\n\nThe subject binds four facts: the workflow identity, the GitHub run id, the head SHA, and the receipt schema revision. Workflow and run id answer WHICH RUN; head SHA answers WHICH TREE, which is the fact an operator actually acts on and which a run id alone does not give; the schema revision answers WHETHER THIS READER UNDERSTANDS IT, and it is part of the subject rather than a sibling field because a document whose schema this reader does not know is not a document with an unreadable body — it is a document about something this reader cannot speak about at all." +// THE SUBJECT IS WHAT MAKES THE RECEIPT ATTRIBUTABLE, and its absence is the gap this row closes. A +// receipt that says only `here are some component outcomes` cannot answer the one question its +// consumer must ask before rendering it: IS THIS ABOUT THE RUN I AM LOOKING AT. Without a subject +// the alert downloads an artifact named floor-component-receipt from a run id it was given and +// trusts the association, so a stale artifact, a mismatched run-id parameter, or an artifact from a +// different workflow entirely all render as the current state of the current run with no way to +// notice. +// +// The subject binds four facts: the workflow identity, the GitHub run id, the head SHA, and the +// receipt schema revision. Workflow and run id answer WHICH RUN; head SHA answers WHICH TREE, which +// is the fact an operator actually acts on and which a run id alone does not give; the schema +// revision answers WHETHER THIS READER UNDERSTANDS IT, and it is part of the subject rather than a +// sibling field because a document whose schema this reader does not know is not a document with an +// unreadable body — it is a document about something this reader cannot speak about at all. type FloorComponentReceiptSubject { schema: NonEmptyStr @@ -53,7 +84,22 @@ type FloorComponentReceiptDocument { run_terminal: JsonValue } -data floor_component_receipt_component_grain_note: String = "COMPONENTS ARE CARRIED AT JSON GRAIN INSIDE THE DOCUMENT, AND THAT IS A DECLARED BOUNDARY RATHER THAN LAZINESS. The document's own identity — subject, counts, which components were unsuccessful — is fully typed and fully round-tripped, because that is what the consumer decides on. A component's INTERIOR is a projection of std.observation.ObservationEvent, whose decoding is a separate and larger obligation: ObservationEvent carries transitions, outcomes with per-variant payloads, measured walls with unavailability causes, and a clock basis, and a half-decoding of it here would be a second, weaker representation of a coproduct that already has an authority — the §3 fork this module's own member-name note argues against.\n\nWHAT THAT COSTS, STATED PLAINLY: a component whose interior is malformed is not caught by this decoder. It is caught by the consumer that reads a field it needs, and that consumer must answer an absent field rather than defaulting it. DISSOLVE-ON: gunbc.floor_component_receipt gains a total ObservationEvent decoder — the reverse of floor_component_row_json — at which point components become List here and this note deletes. Until then the honest claim is that the document is typed and the component interiors are transported, not that the whole receipt is validated." +// COMPONENTS ARE CARRIED AT JSON GRAIN INSIDE THE DOCUMENT, AND THAT IS A DECLARED BOUNDARY RATHER +// THAN LAZINESS. The document's own identity — subject, counts, which components were unsuccessful +// — is fully typed and fully round-tripped, because that is what the consumer decides on. A +// component's INTERIOR is a projection of std.observation.ObservationEvent, whose decoding is a +// separate and larger obligation: ObservationEvent carries transitions, outcomes with per-variant +// payloads, measured walls with unavailability causes, and a clock basis, and a half-decoding of it +// here would be a second, weaker representation of a coproduct that already has an authority — the +// §3 fork this module's own member-name note argues against. +// +// WHAT THAT COSTS, STATED PLAINLY: a component whose interior is malformed is not caught by this +// decoder. It is caught by the consumer that reads a field it needs, and that consumer must answer +// an absent field rather than defaulting it. DISSOLVE-ON: gunbc.floor_component_receipt gains a +// total ObservationEvent decoder — the reverse of floor_component_row_json — at which point +// components become List here and this note deletes. Until then the honest claim +// is that the document is typed and the component interiors are transported, not that the whole +// receipt is validated. type FloorComponentReceiptDecoding = ReceiptDecoded { document: FloorComponentReceiptDocument } @@ -126,7 +172,25 @@ fn receipt_array_at(v: JsonValue, key: String) -> ReceiptElements { } } -data receipt_required_member_not_null_note: String = "AN ABSENT OR DUPLICATED REQUIRED MEMBER REFUSES; IT DOES NOT BECOME JsonNull. The earlier helper collapsed absent, duplicated and structurally-invalid into the same null value, which is the ⊥-as-answer conflation DESIGN names: `the receipt says the cold control is null` and `I could not read what the receipt says about the cold control` are different states, and the first is a claim about the floor while the second is a claim about the decoder. The alert renders per-component state from these, so a decoder that answers null for an unreadable member produces an issue body asserting something about a run that nothing in the receipt supports.\n\nThe affected-set control and the selection degradation are REQUIRED members and are decoded as such. A member that is genuinely present and genuinely null still decodes to null, which is a fact the receipt stated; an ABSENT member is a defect in the writer.\n\nTHAT SENTENCE WAS NOT TRUE WHEN IT WAS FIRST WRITTEN, and the gap it papered over was real. gunbc.floor_component_receipt emitted selection_degradation only when a selection snapshot existed and omitted the member entirely otherwise, so this decoder would have refused documents its own writer still produced on the no-selection path — encode-then-decode broken for the exact case claim_executor takes when no selection ran (review 49594). The writer now emits an explicit null on that path, so the claim holds by construction rather than by assertion, and the round trip is witnessed in both directions." +// AN ABSENT OR DUPLICATED REQUIRED MEMBER REFUSES; IT DOES NOT BECOME JsonNull. The earlier helper +// collapsed absent, duplicated and structurally-invalid into the same null value, which is the +// ⊥-as-answer conflation DESIGN names: `the receipt says the cold control is null` and `I could not +// read what the receipt says about the cold control` are different states, and the first is a claim +// about the floor while the second is a claim about the decoder. The alert renders per-component +// state from these, so a decoder that answers null for an unreadable member produces an issue body +// asserting something about a run that nothing in the receipt supports. +// +// The affected-set control and the selection degradation are REQUIRED members and are decoded as +// such. A member that is genuinely present and genuinely null still decodes to null, which is a +// fact the receipt stated; an ABSENT member is a defect in the writer. +// +// THAT SENTENCE WAS NOT TRUE WHEN IT WAS FIRST WRITTEN, and the gap it papered over was real. +// gunbc.floor_component_receipt emitted selection_degradation only when a selection snapshot +// existed and omitted the member entirely otherwise, so this decoder would have refused documents +// its own writer still produced on the no-selection path — encode-then-decode broken for the exact +// case claim_executor takes when no selection ran (review 49594). The writer now emits an explicit +// null on that path, so the claim holds by construction rather than by assertion, and the round +// trip is witnessed in both directions. type ReceiptValue = ReceiptValueFound { value: JsonValue } @@ -143,7 +207,14 @@ fn receipt_required_value_at(v: JsonValue, key: String) -> ReceiptValue { } } -data receipt_run_terminal_legacy_absent_note: String = "TRANSITIONAL BACK-COMPAT for receipts under GitHub Actions artifact retention written before this PR: run_terminal did not exist, so the member is absent rather than null. Treating absent as ReceiptMemberAbsent would refuse the exact population the falsifier alert still consumes (review 51856). Absent therefore decodes to json_null(), meaning no incomplete envelope was recorded — the same reading as a concluded walk that explicitly emits run_terminal:null. The writer always emits the member on new receipts; only the reader carries this legacy arm. Dissolve-on: counted census shows zero retention receipts lacking the member, then delete this helper and require the member like selection_degradation." +// TRANSITIONAL BACK-COMPAT for receipts under GitHub Actions artifact retention written before this +// PR: run_terminal did not exist, so the member is absent rather than null. Treating absent as +// ReceiptMemberAbsent would refuse the exact population the falsifier alert still consumes (review +// 51856). Absent therefore decodes to json_null(), meaning no incomplete envelope was recorded — +// the same reading as a concluded walk that explicitly emits run_terminal:null. The writer always +// emits the member on new receipts; only the reader carries this legacy arm. Dissolve-on: counted +// census shows zero retention receipts lacking the member, then delete this helper and require the +// member like selection_degradation. fn receipt_run_terminal_at(v: JsonValue) -> ReceiptValue { match json_object_unique_member(v: v, key: receipt_member_run_terminal) { @@ -242,7 +313,18 @@ fn floor_component_outcome_tag(o: ObservationOutcome) -> String { } } -data component_outcome_wire_note: String = "THE WIRE OUTCOME VOCABULARY HAS ONE AUTHORITY, AND IT IS SEVEN TAGS, NOT SIX. floor_component_outcome_tag covers the six ObservationOutcome arms; the writer also emits `not_concluded` for a row whose transition never reached Concluded, and that seventh tag lived as a bare literal at the writer with no reader able to name it. A decoder validating against the six-arm table would have refused a receipt the floor itself produces.\\n\\nComponentOutcomeRead is that distinction and nothing more. It is NOT a second outcome vocabulary — the concluded arm carries the real ObservationOutcome authority inside it, and the tag table below delegates to floor_component_outcome_tag rather than restating the six names. What it adds is the one fact ObservationOutcome cannot express, which is its own absence, and that fact already exists upstream as ObservationTransition.Concluded; this is its projection onto the wire. Writer and reader now share the table, so a tag the writer can emit is a tag the reader can name, by construction rather than by both being edited together." +// THE WIRE OUTCOME VOCABULARY HAS ONE AUTHORITY, AND IT IS SEVEN TAGS, NOT SIX. +// floor_component_outcome_tag covers the six ObservationOutcome arms; the writer also emits +// `not_concluded` for a row whose transition never reached Concluded, and that seventh tag lived as +// a bare literal at the writer with no reader able to name it. A decoder validating against the +// six-arm table would have refused a receipt the floor itself produces.\n\nComponentOutcomeRead is +// that distinction and nothing more. It is NOT a second outcome vocabulary — the concluded arm +// carries the real ObservationOutcome authority inside it, and the tag table below delegates to +// floor_component_outcome_tag rather than restating the six names. What it adds is the one fact +// ObservationOutcome cannot express, which is its own absence, and that fact already exists +// upstream as ObservationTransition.Concluded; this is its projection onto the wire. Writer and +// reader now share the table, so a tag the writer can emit is a tag the reader can name, by +// construction rather than by both being edited together. type ComponentOutcomeRead = ComponentConcluded { outcome: ObservationOutcome } @@ -250,7 +332,12 @@ type ComponentOutcomeRead data floor_component_outcome_wire_not_concluded_tag: String = "not_concluded" data floor_component_outcome_wire_pending_tag: String = "pending" -data component_outcome_wire_legacy_pending_tag_note: String = "component_outcome_read_of_tag still accepts the legacy not_concluded wire tag beside pending so in-flight receipts written before the rename decode without refusal. No writer emits not_concluded anymore; the dual acceptance is a deliberate transitional window, not a second vocabulary. Dissolve-on: delete the legacy branch once no receipt-on-disk (including uploaded CI artifacts under retention) carries not_concluded — verified by a counted census, not by grep alone." +// component_outcome_read_of_tag still accepts the legacy not_concluded wire tag beside pending so +// in-flight receipts written before the rename decode without refusal. No writer emits +// not_concluded anymore; the dual acceptance is a deliberate transitional window, not a second +// vocabulary. Dissolve-on: delete the legacy branch once no receipt-on-disk (including uploaded CI +// artifacts under retention) carries not_concluded — verified by a counted census, not by grep +// alone. fn floor_component_outcome_wire_tag(r: ComponentOutcomeRead) -> String { match r { @@ -259,7 +346,19 @@ fn floor_component_outcome_wire_tag(r: ComponentOutcomeRead) -> String { } } -data failure_mode_wire_reader_note: String = "TWO READERS FOR ONE VOCABULARY, BECAUSE THE TWO VOCABULARIES ARE GENUINELY DIFFERENT, AND THE WIRE ONE IS THE EXACT INVERSE OF THE WRITER. floor_component_failure_mode_of is the SEED reader: it maps the executor's reported mode and deliberately cannot produce ModeUnrecorded, because the seed never observes `no mode was recorded` — it either reports a mode or the row fails closed. floor_component_failure_mode_of_wire_tag is the WIRE reader, and it must be total over exactly what floor_component_failure_mode_tag can emit, which includes `unrecorded`.\\n\\nThe asymmetry was found by execution, not by reading: the strict decoder refused a receipt the floor itself produces, because rows built through the outcome-tag constructor carry ModeUnrecorded and serialize it, while the only reader in tree rejected that tag as unmodelled. The existing note asserting ModeUnrecorded is unreachable was true of the SEED path and not of the wire, and a single reader serving both is what let that distinction go unnoticed. Reading the wire with the seed's vocabulary is the same class of error as reading it with prose: a narrower vocabulary than the writer's, presented as validation." +// TWO READERS FOR ONE VOCABULARY, BECAUSE THE TWO VOCABULARIES ARE GENUINELY DIFFERENT, AND THE +// WIRE ONE IS THE EXACT INVERSE OF THE WRITER. floor_component_failure_mode_of is the SEED reader: +// it maps the executor's reported mode and deliberately cannot produce ModeUnrecorded, because the +// seed never observes `no mode was recorded` — it either reports a mode or the row fails closed. +// floor_component_failure_mode_of_wire_tag is the WIRE reader, and it must be total over exactly +// what floor_component_failure_mode_tag can emit, which includes `unrecorded`.\n\nThe asymmetry was +// found by execution, not by reading: the strict decoder refused a receipt the floor itself +// produces, because rows built through the outcome-tag constructor carry ModeUnrecorded and +// serialize it, while the only reader in tree rejected that tag as unmodelled. The existing note +// asserting ModeUnrecorded is unreachable was true of the SEED path and not of the wire, and a +// single reader serving both is what let that distinction go unnoticed. Reading the wire with the +// seed's vocabulary is the same class of error as reading it with prose: a narrower vocabulary than +// the writer's, presented as validation. fn floor_component_failure_mode_of_wire_tag(tag: String) -> FloorComponentFailureMode? { if tag == floor_component_failure_mode_tag(m: ModeNoFailure) { Present { value: ModeNoFailure } } @@ -277,7 +376,20 @@ fn floor_component_failure_mode_of_wire_tag(tag: String) -> FloorComponentFailur else { none } } -data strict_natural_decode_note: String = "A NUMERIC LEXEME IS NOT A NATURAL NUMBER. The JSON grammar admits `-1`, `6.5`, `6e3` and `1e400` as numbers, and parse_int on any of them produces something — a truncation, a wrong magnitude, or a value the carrier cannot hold — with no signal that the input was not what the field means. Component indices and counts are cardinals: they are positions and tallies, and a negative position or a fractional count is not a small error in a valid value, it is a different kind of thing.\\n\\nSo the lexeme is checked before it is trusted, and the check is a ROUND TRIP rather than a character scan: the parse must reproduce the original lexeme exactly, and the result must be non-negative. That rejects `-1` on the sign test and rejects `6.5`, `6e3`, `+6`, ` 6` and `06` because none of them is what its own parse renders back. It is stronger than a digit scan and needs no character iteration — and it catches the out-of-range case for free, since a magnitude the carrier cannot hold does not render back to what was read. This is the boundary the parse-then-hope shape was hiding: parse_int is total, so it answered for inputs the field cannot represent, and a total function answering for an impossible input is exactly the fabricated-plausible-output the rest of this decoder refuses." +// A NUMERIC LEXEME IS NOT A NATURAL NUMBER. The JSON grammar admits `-1`, `6.5`, `6e3` and `1e400` +// as numbers, and parse_int on any of them produces something — a truncation, a wrong magnitude, or +// a value the carrier cannot hold — with no signal that the input was not what the field means. +// Component indices and counts are cardinals: they are positions and tallies, and a negative +// position or a fractional count is not a small error in a valid value, it is a different kind of +// thing.\n\nSo the lexeme is checked before it is trusted, and the check is a ROUND TRIP rather +// than a character scan: the parse must reproduce the original lexeme exactly, and the result must +// be non-negative. That rejects `-1` on the sign test and rejects `6.5`, `6e3`, `+6`, ` 6` and `06` +// because none of them is what its own parse renders back. It is stronger than a digit scan and +// needs no character iteration — and it catches the out-of-range case for free, since a magnitude +// the carrier cannot hold does not render back to what was read. This is the boundary the +// parse-then-hope shape was hiding: parse_int is total, so it answered for inputs the field cannot +// represent, and a total function answering for an impossible input is exactly the +// fabricated-plausible-output the rest of this decoder refuses. fn strict_natural_of_lexeme(text: String) -> Nat? { match parse_int(s: text) { @@ -308,7 +420,17 @@ type ReceiptNatural = ReceiptNaturalFound { value: Nat } | ReceiptNaturalFailed { decoding: FloorComponentReceiptDecoding } -data component_alert_summary_note: String = "THE STRICT TYPED PROJECTION OF THE FACTS THE ALERT ACTUALLY DECIDES ON. Component interiors are still transported as JSON residue, because a total reverse decoder for std.observation.ObservationEvent is a separate obligation and half of one would fork a coproduct that already has an authority. But the four fields the alert READS — index, label, outcome, failure_mode — are exactly the fields its incident signature is keyed on, so they are not residue and must not be typed as residue. They are decoded, each required, each exactly once, each with its own located refusal.\n\nThe distinction that makes this coherent rather than arbitrary: unknown OPTIONAL detail may remain JSON, decision-driving facts may not. A field that changes what the alert says must be one the decoder has checked." +// THE STRICT TYPED PROJECTION OF THE FACTS THE ALERT ACTUALLY DECIDES ON. Component interiors are +// still transported as JSON residue, because a total reverse decoder for +// std.observation.ObservationEvent is a separate obligation and half of one would fork a coproduct +// that already has an authority. But the four fields the alert READS — index, label, outcome, +// failure_mode — are exactly the fields its incident signature is keyed on, so they are not residue +// and must not be typed as residue. They are decoded, each required, each exactly once, each with +// its own located refusal. +// +// The distinction that makes this coherent rather than arbitrary: unknown OPTIONAL detail may +// remain JSON, decision-driving facts may not. A field that changes what the alert says must be one +// the decoder has checked. type ComponentAlertSummary { index: Nat @@ -321,7 +443,15 @@ type ComponentAlertSummaryDecoding = ComponentSummaryDecoded { summary: ComponentAlertSummary } | ComponentSummaryFailed { decoding: FloorComponentReceiptDecoding } -data receipt_numeric_member_note: String = "A NUMERIC MEMBER IS READ AS A NUMBER, NOT AS TEXT THAT HAPPENS TO LOOK LIKE ONE. The component index is an identity — routing joins on it, the incident signature is keyed on it, and it is inserted between delimiters when a marker is serialized. A text rendering of an identity is not an identity: `6` and `\"6\"` and `\" 6\"` are three strings and one component, and the string form is the one that later gets concatenated into a signature where a stray delimiter changes what the alert believes failed.\\n\\nSo this reader admits a JSON number and refuses everything else, including a numeric-looking string. The writer emits json_int, so a number is not merely tolerated here — it is the only shape the producer makes, and anything else means the producer changed or something other than the producer wrote the file." +// A NUMERIC MEMBER IS READ AS A NUMBER, NOT AS TEXT THAT HAPPENS TO LOOK LIKE ONE. The component +// index is an identity — routing joins on it, the incident signature is keyed on it, and it is +// inserted between delimiters when a marker is serialized. A text rendering of an identity is not +// an identity: `6` and `"6"` and `" 6"` are three strings and one component, and the string form is +// the one that later gets concatenated into a signature where a stray delimiter changes what the +// alert believes failed.\n\nSo this reader admits a JSON number and refuses everything else, +// including a numeric-looking string. The writer emits json_int, so a number is not merely +// tolerated here — it is the only shape the producer makes, and anything else means the producer +// changed or something other than the producer wrote the file. fn receipt_numeric_member_at(v: JsonValue, key: String) -> ReceiptText { match json_object_unique_member(v: v, key: key) { @@ -376,9 +506,38 @@ fn receipt_summary_required_string_at(v: JsonValue, key: String) -> ReceiptText } } -data component_index_numeric_admission_note: String = "index admits a JSON number because the writer emits it with json_int, so a number is its NORMAL form here — unlike the event envelope's generic members, where a number signals a shape change. label, outcome and failure_mode are emitted with json_string and are decoded by a string-only helper, so the widening is confined to the one member whose writer produces it, which is the same rule the event decoder follows.\n\nTHIS NOTE ONCE DESCRIBED THE OPPOSITE OF THE CODE, and that is worth keeping rather than quietly correcting. One decoder served all four fields and admitted numbers for all four, while this paragraph asserted the confinement it did not implement (review 49571). A prose claim contradicting the mechanism beside it is worse than either an undocumented widening or a documented one: a reader checking the boundary finds the rule stated, believes it holds, and stops looking. The split is now structural — two functions, and the permissive one is reachable from exactly one call site — so the confinement is a property of the code rather than of this sentence." - -data component_outcome_recovery_note: String = "THE OUTCOME IS RECOVERED THROUGH THE AUTHORITY THAT WROTE IT, NOT ACCEPTED AS PROSE. The field previously guaranteed only `a non-empty string is present`, so {\"outcome\":\"banana\"} decoded cleanly and then decided incident identity and routing. What makes a tag real is that floor_component_outcome_of_tag admits it, and that function is the same table floor_component_outcome_tag serializes with — one vocabulary, read in both directions, so a tag the writer cannot emit is a tag the reader refuses.\\n\\nThe payload rides with it rather than being invented. Refused, Failed, Skipped and Final each carry fields the writer emits beside the tag (floor_component_outcome_members), so they are read back from the same object; a tag whose payload is missing refuses rather than defaulting, because a Failed with a fabricated empty error is a plausible-looking outcome nobody observed.\\n\\nTIMED_OUT REFUSES, AND THE REASON IS A DECLARED WIRE GAP, NOT A DECODER LIMITATION. TimedOut carries a ClockBasis — the fact that distinguishes a whole-receipt wall ceiling from a fast-lane thread-CPU stop — and floor_component_outcome_members does not serialize it, so nothing on the wire can reconstruct it and choosing a basis here would fabricate the one field the arm exists to disambiguate. The floor does not emit timed_out today (its own declared frontier), so this refuses a shape that cannot currently arrive. Dissolve-on: the writer serializes the basis, at which point this arm reads it like the others." +// index admits a JSON number because the writer emits it with json_int, so a number is its NORMAL +// form here — unlike the event envelope's generic members, where a number signals a shape change. +// label, outcome and failure_mode are emitted with json_string and are decoded by a string-only +// helper, so the widening is confined to the one member whose writer produces it, which is the same +// rule the event decoder follows. +// +// THIS NOTE ONCE DESCRIBED THE OPPOSITE OF THE CODE, and that is worth keeping rather than quietly +// correcting. One decoder served all four fields and admitted numbers for all four, while this +// paragraph asserted the confinement it did not implement (review 49571). A prose claim +// contradicting the mechanism beside it is worse than either an undocumented widening or a +// documented one: a reader checking the boundary finds the rule stated, believes it holds, and +// stops looking. The split is now structural — two functions, and the permissive one is reachable +// from exactly one call site — so the confinement is a property of the code rather than of this +// sentence. + +// THE OUTCOME IS RECOVERED THROUGH THE AUTHORITY THAT WROTE IT, NOT ACCEPTED AS PROSE. The field +// previously guaranteed only `a non-empty string is present`, so {"outcome":"banana"} decoded +// cleanly and then decided incident identity and routing. What makes a tag real is that +// floor_component_outcome_of_tag admits it, and that function is the same table +// floor_component_outcome_tag serializes with — one vocabulary, read in both directions, so a tag +// the writer cannot emit is a tag the reader refuses.\n\nThe payload rides with it rather than +// being invented. Refused, Failed, Skipped and Final each carry fields the writer emits beside the +// tag (floor_component_outcome_members), so they are read back from the same object; a tag whose +// payload is missing refuses rather than defaulting, because a Failed with a fabricated empty error +// is a plausible-looking outcome nobody observed.\n\nTIMED_OUT REFUSES, AND THE REASON IS A +// DECLARED WIRE GAP, NOT A DECODER LIMITATION. TimedOut carries a ClockBasis — the fact that +// distinguishes a whole-receipt wall ceiling from a fast-lane thread-CPU stop — and +// floor_component_outcome_members does not serialize it, so nothing on the wire can reconstruct it +// and choosing a basis here would fabricate the one field the arm exists to disambiguate. The floor +// does not emit timed_out today (its own declared frontier), so this refuses a shape that cannot +// currently arrive. Dissolve-on: the writer serializes the basis, at which point this arm reads it +// like the others. type ComponentOutcomeRecovery = ComponentOutcomeRecovered { read: ComponentOutcomeRead } @@ -437,19 +596,97 @@ fn component_outcome_read_of_tag(tag: String, component: JsonValue) -> Component } } -data floor_component_outcome_of_mode_single_authority_note: String = "ONE tag table, not two. An earlier revision of the failure-mode edge parsed the seed's tag twice — once to a FloorComponentFailureMode and once, separately, to an ObservationOutcome — so the tag vocabulary was written out in two parallel string tables that had to be edited together (caught by review 48341 on gunbc#7827, non-blocking; fixed here rather than left, because two representations of one vocabulary is the second-representation DESIGN 3 forbids and the fact that today's only caller invokes them back-to-back makes the drift local, not impossible). The outcome is now derived FROM THE MODE rather than from the tag, so the tag appears exactly once (floor_component_failure_mode_of) and the mode-to-outcome collapse appears exactly once here. A newly added mode is a compile error in this match instead of a silently unmapped string, which is the construction form of the fail-closed property the note above states. ModeUnrecorded REFUSES rather than being given an outcome: it is not a failure mode at all, it is the absence of a recorded one, so inventing an outcome for it would be the fabricated-plausible-output DESIGN 5 forbids. It is unreachable from the tag path by construction — floor_component_failure_mode_of never returns it — so the refusal is a total-match obligation, not a live arm." - -data floor_component_expected_red_pre_verdict_unverified_mode_note: String = "ModeExpectedRedPreVerdictUnverified is the third arm on this axis and it exists because a DECLARATION is not an OBSERVATION (operator ruling, 2026-08-07). A batch may stop before any verdict with every entry declaring a typed pre-verdict refusal; the previous shape read that declaration as agreement and returned green. But no ClaimOutcome exists on that path — run_claim has already formatted the typed InterpError into a String — so the observed phase and cause cannot be matched against the declared ones, and a row declaring one cause was admitted when a different pre-verdict fault occurred. It maps to Refused: the stop is real and typed, the classification is available, and the verification is not. Distinct from ModeExpectedRedEvidenceAbsent (which says a rostered row produced NO observation and must be made to RUN) and from ModeStaleKnownRed (which says an executed row PASSED and its admission must be DELETED); this one says the row stopped as declared and nothing yet proves it stopped for the declared REASON. Dissolve-on: EXPECTED-RED-CAUSE-1 — ClaimOutcome preserves typed phase and cause, after which agreement requires observed == declared and either mismatch refuses." - -data floor_component_expected_red_evidence_absent_mode_note: String = "ModeExpectedRedEvidenceAbsent is the COVERAGE half of the same axis and it is a separate mode because its remedy is the opposite of its sibling's: a stale quarantine says DELETE the admission row, an absent observation says make the row RUN. It maps to Refused, never Done, and that is the whole content of the arm — a batch whose admitted known-red row produced no observation at all was reporting green, because the absence was written to stderr and to nothing that could stop the line. Absence establishes neither agreement nor failure; treating it as either is the empty-observation narrow, and treating it as nothing is coverage by illusion. Like its sibling it is derived from ROSTER MEMBERSHIP joined against per-witness outcomes and carried as typed data, never recovered from a message." - -data floor_component_stale_known_red_mode_note: String = "ModeStaleKnownRed is its own mode, not a WitnessRed, because a known-red witness that starts PASSING and a witness that regresses are opposite events with opposite remedies — delete the admission row that is now stale, versus fix the code — and collapsing them makes an un-quarantine indistinguishable from a regression in the alert's class signature. It maps to Refused rather than Failed for the same reason BudgetExceeded does: nothing about the witness is broken, the LINE STOPS for analysis and the diagnostic names the exact identities to un-quarantine (DESIGN 5, the factory model). Critically it is NOT Done: an expected-red row that silently went green would otherwise accumulate on a roster nobody revisits, which is the fail-open this arm closes. The producer derives it from ROSTER MEMBERSHIP joined against per-witness outcomes and carries it as typed data (claim_executor ExpectationRefusal), never from a message, so it is not exposed to the substring classifier's WitnessRed fallback." - -data floor_component_run_incomplete_note: String = "ModeRunIncomplete is NOT a terminal component verdict — it names a component whose fate is UNKNOWN because the receipt was written while the run was still in progress. Such rows use the pending wire outcome (ComponentNotConcluded / Begin transition), never skipped: skipped claims the walk produced a verdict of none, and manufacturing that for a batch that never executed is the fabricated-plausible-output failure at run level (run 31685755058: batches 7–9 reported outcome=skipped with failure_mode=run_incomplete although they never ran). ModeNotReached remains Skipped: the plan concluded and an earlier batch stopped the line under the stop policy — a deliberate skip with a known cause. The MODE still records which of the two non-terminal situations applies; the OUTCOME now records whether a verdict exists at all. Collapsing run_incomplete into skipped made a killed run's checkpoint indistinguishable from a concluded skip and changed the incident signature with every batch that happened to finish before the kill. WHY IT EXISTS AT ALL: the falsifier's floor step carries a foreign 170-minute SIGKILL and the receipt is checkpointed after each batch so a kill leaves evidence; the tail is pending, and run_terminal.disposition=incomplete names the run-level fact separately from per-component rows." - -data floor_component_deadline_reached_note: String = "ModeDeadlineReached is the THIRD unadmitted-component cause and the only one of the three that is Refused rather than Skipped — the split is deliberate and is the whole reason it is not folded into its neighbours. Skipped says the component produced no verdict and the walk is not claiming responsibility for that; Refused says THE LINE STOPPED DELIBERATELY, LOCATED, AT A DECLARED CEILING, which observation_refused_distinct_note defines as the stopped line an operator is meant to analyse. That is exactly what the internal soft deadline is (gunbc.falsifier_workflow gunbc_falsifier_soft_deadline_minutes), so this mode routes with ModeBudgetExceeded — same remedy family, re-basis the ceiling or reduce the cost — rather than with the two Skipped causes whose remedies are 'fix the earlier component' and 'find out why the run never finished'. WHY IT IS NOT ModeBudgetExceeded ITSELF: that mode means THIS component crossed ITS OWN budget and was killed. A deadline-reached component was never STARTED — the walk declined to admit it because the RUN was out of time — so reusing BudgetExceeded would attribute a whole-run cost problem to whichever component happened to be next in the plan, which is a wrong location on a receipt whose entire purpose is locating things. WHAT REACHING IT MEANS: the corpus did not fit in the lane's time. Every unadmitted component is counted and the run is red. The deadline measures that; it does not fix it, and it is not licence to raise the cap (DESIGN's five-minute-CI-gate lane: do not raise the CI step cap first)." - -data floor_component_host_dependency_absent_mode_note: String = "ModeHostDependencyAbsent is the falsifier recovery arm for wet Codex materialization when npm is not on PATH (run 31685755058 component 6). Before the host-cli check, the shell transport raised failed to execute npm and claim_executor classified the batch as WitnessRed — indistinguishable from a subject failure. The remedy is fleet provisioning (apt install npm on falsifier runners executing falsifier_rehomed_bin_wet_rows Codex entries), not witness deletion and not auto-install inside the witness. It maps to Refused like BudgetExceeded: the line stops with a located host obligation, not a broken claim." +// ONE tag table, not two. An earlier revision of the failure-mode edge parsed the seed's tag twice +// — once to a FloorComponentFailureMode and once, separately, to an ObservationOutcome — so the tag +// vocabulary was written out in two parallel string tables that had to be edited together (caught +// by review 48341 on gunbc#7827, non-blocking; fixed here rather than left, because two +// representations of one vocabulary is the second-representation DESIGN 3 forbids and the fact that +// today's only caller invokes them back-to-back makes the drift local, not impossible). The outcome +// is now derived FROM THE MODE rather than from the tag, so the tag appears exactly once +// (floor_component_failure_mode_of) and the mode-to-outcome collapse appears exactly once here. A +// newly added mode is a compile error in this match instead of a silently unmapped string, which is +// the construction form of the fail-closed property the note above states. ModeUnrecorded REFUSES +// rather than being given an outcome: it is not a failure mode at all, it is the absence of a +// recorded one, so inventing an outcome for it would be the fabricated-plausible-output DESIGN 5 +// forbids. It is unreachable from the tag path by construction — floor_component_failure_mode_of +// never returns it — so the refusal is a total-match obligation, not a live arm. + +// ModeExpectedRedPreVerdictUnverified is the third arm on this axis and it exists because a +// DECLARATION is not an OBSERVATION (operator ruling, 2026-08-07). A batch may stop before any +// verdict with every entry declaring a typed pre-verdict refusal; the previous shape read that +// declaration as agreement and returned green. But no ClaimOutcome exists on that path — run_claim +// has already formatted the typed InterpError into a String — so the observed phase and cause +// cannot be matched against the declared ones, and a row declaring one cause was admitted when a +// different pre-verdict fault occurred. It maps to Refused: the stop is real and typed, the +// classification is available, and the verification is not. Distinct from +// ModeExpectedRedEvidenceAbsent (which says a rostered row produced NO observation and must be made +// to RUN) and from ModeStaleKnownRed (which says an executed row PASSED and its admission must be +// DELETED); this one says the row stopped as declared and nothing yet proves it stopped for the +// declared REASON. Dissolve-on: EXPECTED-RED-CAUSE-1 — ClaimOutcome preserves typed phase and +// cause, after which agreement requires observed == declared and either mismatch refuses. + +// ModeExpectedRedEvidenceAbsent is the COVERAGE half of the same axis and it is a separate mode +// because its remedy is the opposite of its sibling's: a stale quarantine says DELETE the admission +// row, an absent observation says make the row RUN. It maps to Refused, never Done, and that is the +// whole content of the arm — a batch whose admitted known-red row produced no observation at all +// was reporting green, because the absence was written to stderr and to nothing that could stop the +// line. Absence establishes neither agreement nor failure; treating it as either is the +// empty-observation narrow, and treating it as nothing is coverage by illusion. Like its sibling it +// is derived from ROSTER MEMBERSHIP joined against per-witness outcomes and carried as typed data, +// never recovered from a message. + +// ModeStaleKnownRed is its own mode, not a WitnessRed, because a known-red witness that starts +// PASSING and a witness that regresses are opposite events with opposite remedies — delete the +// admission row that is now stale, versus fix the code — and collapsing them makes an un-quarantine +// indistinguishable from a regression in the alert's class signature. It maps to Refused rather +// than Failed for the same reason BudgetExceeded does: nothing about the witness is broken, the +// LINE STOPS for analysis and the diagnostic names the exact identities to un-quarantine (DESIGN 5, +// the factory model). Critically it is NOT Done: an expected-red row that silently went green would +// otherwise accumulate on a roster nobody revisits, which is the fail-open this arm closes. The +// producer derives it from ROSTER MEMBERSHIP joined against per-witness outcomes and carries it as +// typed data (claim_executor ExpectationRefusal), never from a message, so it is not exposed to the +// substring classifier's WitnessRed fallback. + +// ModeRunIncomplete is NOT a terminal component verdict — it names a component whose fate is +// UNKNOWN because the receipt was written while the run was still in progress. Such rows use the +// pending wire outcome (ComponentNotConcluded / Begin transition), never skipped: skipped claims +// the walk produced a verdict of none, and manufacturing that for a batch that never executed is +// the fabricated-plausible-output failure at run level (run 31685755058: batches 7–9 reported +// outcome=skipped with failure_mode=run_incomplete although they never ran). ModeNotReached remains +// Skipped: the plan concluded and an earlier batch stopped the line under the stop policy — a +// deliberate skip with a known cause. The MODE still records which of the two non-terminal +// situations applies; the OUTCOME now records whether a verdict exists at all. Collapsing +// run_incomplete into skipped made a killed run's checkpoint indistinguishable from a concluded +// skip and changed the incident signature with every batch that happened to finish before the kill. +// WHY IT EXISTS AT ALL: the falsifier's floor step carries a foreign 170-minute SIGKILL and the +// receipt is checkpointed after each batch so a kill leaves evidence; the tail is pending, and +// run_terminal.disposition=incomplete names the run-level fact separately from per-component rows. + +// ModeDeadlineReached is the THIRD unadmitted-component cause and the only one of the three that is +// Refused rather than Skipped — the split is deliberate and is the whole reason it is not folded +// into its neighbours. Skipped says the component produced no verdict and the walk is not claiming +// responsibility for that; Refused says THE LINE STOPPED DELIBERATELY, LOCATED, AT A DECLARED +// CEILING, which observation_refused_distinct_note defines as the stopped line an operator is meant +// to analyse. That is exactly what the internal soft deadline is (gunbc.falsifier_workflow +// gunbc_falsifier_soft_deadline_minutes), so this mode routes with ModeBudgetExceeded — same remedy +// family, re-basis the ceiling or reduce the cost — rather than with the two Skipped causes whose +// remedies are 'fix the earlier component' and 'find out why the run never finished'. WHY IT IS NOT +// ModeBudgetExceeded ITSELF: that mode means THIS component crossed ITS OWN budget and was killed. +// A deadline-reached component was never STARTED — the walk declined to admit it because the RUN +// was out of time — so reusing BudgetExceeded would attribute a whole-run cost problem to whichever +// component happened to be next in the plan, which is a wrong location on a receipt whose entire +// purpose is locating things. WHAT REACHING IT MEANS: the corpus did not fit in the lane's time. +// Every unadmitted component is counted and the run is red. The deadline measures that; it does not +// fix it, and it is not licence to raise the cap (DESIGN's five-minute-CI-gate lane: do not raise +// the CI step cap first). + +// ModeHostDependencyAbsent is the falsifier recovery arm for wet Codex materialization when npm is +// not on PATH (run 31685755058 component 6). Before the host-cli check, the shell transport raised +// failed to execute npm and claim_executor classified the batch as WitnessRed — indistinguishable +// from a subject failure. The remedy is fleet provisioning (apt install npm on falsifier runners +// executing falsifier_rehomed_bin_wet_rows Codex entries), not witness deletion and not +// auto-install inside the witness. It maps to Refused like BudgetExceeded: the line stops with a +// located host obligation, not a broken claim. fn floor_component_outcome_of_mode(m: FloorComponentFailureMode, detail: NonEmptyStr) -> ObservationOutcome? { match m { @@ -468,7 +705,22 @@ fn floor_component_outcome_of_mode(m: FloorComponentFailureMode, detail: NonEmpt } } -data outcome_mode_pair_note: String = "THE TWO FACTS ARE RECOVERED INDEPENDENTLY, SO THE PAIR MUST BE CHECKED. Typing each field separately closed the `outcome: banana` hole and opened a subtler one: outcome=done with failure_mode=WitnessRed, or outcome=failed with failure_mode=none, are two individually valid values forming a combination the writer cannot construct. The alert routes on the mode and decides incident identity on both, so an impossible pair is a receipt claiming a component both passed and broke — and neither field alone is wrong enough to refuse.\\n\\nThe agreement is DERIVED FROM THE WRITER, not restated. floor_component_outcome_of_mode is the single place the collapse happens, so the check asks that same function what outcome the mode implies and compares tags. Writing the valid pairs out as a table here would be a second copy of that mapping, and the two would drift the moment a mode is added — which is precisely the failure this receipt keeps finding elsewhere.\\n\\nTWO ARMS ARE DELIBERATELY BROADER, and both are properties of the writer rather than concessions. ModeUnrecorded pairs with ANY outcome because it is what the direct-outcome constructor emits: that path is handed an outcome and genuinely observed no mode, so there is no mode for the outcome to agree with. A NOT-CONCLUDED row likewise pairs with any mode, because a row whose transition never reached Concluded has no outcome at all — the mode records what the walk knew, and the absent conclusion is the fact being reported." +// THE TWO FACTS ARE RECOVERED INDEPENDENTLY, SO THE PAIR MUST BE CHECKED. Typing each field +// separately closed the `outcome: banana` hole and opened a subtler one: outcome=done with +// failure_mode=WitnessRed, or outcome=failed with failure_mode=none, are two individually valid +// values forming a combination the writer cannot construct. The alert routes on the mode and +// decides incident identity on both, so an impossible pair is a receipt claiming a component both +// passed and broke — and neither field alone is wrong enough to refuse.\n\nThe agreement is DERIVED +// FROM THE WRITER, not restated. floor_component_outcome_of_mode is the single place the collapse +// happens, so the check asks that same function what outcome the mode implies and compares tags. +// Writing the valid pairs out as a table here would be a second copy of that mapping, and the two +// would drift the moment a mode is added — which is precisely the failure this receipt keeps +// finding elsewhere.\n\nTWO ARMS ARE DELIBERATELY BROADER, and both are properties of the writer +// rather than concessions. ModeUnrecorded pairs with ANY outcome because it is what the +// direct-outcome constructor emits: that path is handed an outcome and genuinely observed no mode, +// so there is no mode for the outcome to agree with. A NOT-CONCLUDED row likewise pairs with any +// mode, because a row whose transition never reached Concluded has no outcome at all — the mode +// records what the walk knew, and the absent conclusion is the fact being reported. fn outcome_matches_implied_mode(o: ObservationOutcome, mode: FloorComponentFailureMode) -> Bool { match floor_component_outcome_of_mode(m: mode, detail: "pair-check" as NonEmptyStr) { @@ -570,7 +822,14 @@ fn summary_outcome_is_unsuccessful(s: ComponentAlertSummary) -> Bool { } } -data unsuccessful_component_identity_note: String = "EVERY UNSUCCESSFUL COMPONENT MUST HAVE A UNIQUE IDENTITY, and the check is an identity join rather than a count. The incident signature is built by mapping over these rows, so two rows sharing an index and label are either one row duplicated — which silently doubles a member of the signature and makes a red look like it changed when it did not — or two genuinely distinct components the receipt cannot tell apart, which means the signature cannot distinguish them either. Both are defects in the thing the alert decides on, so the decoder refuses rather than deduplicating: deduplicating would be the absorbing fallback, quietly repairing a producer defect until nobody knows it exists." +// EVERY UNSUCCESSFUL COMPONENT MUST HAVE A UNIQUE IDENTITY, and the check is an identity join +// rather than a count. The incident signature is built by mapping over these rows, so two rows +// sharing an index and label are either one row duplicated — which silently doubles a member of the +// signature and makes a red look like it changed when it did not — or two genuinely distinct +// components the receipt cannot tell apart, which means the signature cannot distinguish them +// either. Both are defects in the thing the alert decides on, so the decoder refuses rather than +// deduplicating: deduplicating would be the absorbing fallback, quietly repairing a producer defect +// until nobody knows it exists. fn identity_list_contains(seen: List, needle: Nat) -> Bool { fold(seen, init: false, f: (acc, s) => acc || (s == needle)) @@ -583,7 +842,22 @@ fn summaries_have_unique_identity(summaries: List) -> Boo })) == count(summaries) } -data unsuccessful_population_join_note: String = "THE UNSUCCESSFUL LIST IS JOINED TO THE COMPLETE POPULATION, NOT MERELY CHECKED FOR INTERNAL UNIQUENESS. Uniqueness inside unsuccessful_components proves the list does not repeat itself. It proves nothing about whether the list IS the unsuccessful set: a row could name a component that does not exist, a successful component could be listed as unsuccessful, or a genuinely failing component could be missing from the list entirely — and the third is the one that matters, because the alert would then report a smaller incident than the one that happened and the operator would be told a component is fine while its receipt says otherwise.\\n\\nSo the law is an identity join, in the form DESIGN states it: the decoded unsuccessful identities must equal the identities of exactly those complete-population components whose TYPED outcome is unsuccessful. Equality of sets, at identity grain, in both directions — an unresolved identity refuses, a successful row listed as unsuccessful refuses, an omitted failing row refuses. It is not a count comparison; two counts can agree while naming different components, which is the failure mode a count check is structurally unable to see.\\n\\nThis is why the complete components list is decoded through the same summary reader rather than left as JSON residue. Deciding whether a row is unsuccessful requires its typed outcome, and asking that question of untyped residue would mean reading the tag as prose — the exact widening the typed outcome recovery closes one layer down." +// THE UNSUCCESSFUL LIST IS JOINED TO THE COMPLETE POPULATION, NOT MERELY CHECKED FOR INTERNAL +// UNIQUENESS. Uniqueness inside unsuccessful_components proves the list does not repeat itself. It +// proves nothing about whether the list IS the unsuccessful set: a row could name a component that +// does not exist, a successful component could be listed as unsuccessful, or a genuinely failing +// component could be missing from the list entirely — and the third is the one that matters, +// because the alert would then report a smaller incident than the one that happened and the +// operator would be told a component is fine while its receipt says otherwise.\n\nSo the law is an +// identity join, in the form DESIGN states it: the decoded unsuccessful identities must equal the +// identities of exactly those complete-population components whose TYPED outcome is unsuccessful. +// Equality of sets, at identity grain, in both directions — an unresolved identity refuses, a +// successful row listed as unsuccessful refuses, an omitted failing row refuses. It is not a count +// comparison; two counts can agree while naming different components, which is the failure mode a +// count check is structurally unable to see.\n\nThis is why the complete components list is decoded +// through the same summary reader rather than left as JSON residue. Deciding whether a row is +// unsuccessful requires its typed outcome, and asking that question of untyped residue would mean +// reading the tag as prose — the exact widening the typed outcome recovery closes one layer down. fn summary_identities(summaries: List) -> List { map(summaries, x => component_alert_summary_identity(s: x)) @@ -622,7 +896,10 @@ fn component_alert_summaries_of(components: List) -> ComponentSummary }) } -data receipt_count_decode_note: String = "component_count is read by the strict numeric reader, the same one the component index uses. It previously admitted a string and compared text, so a receipt declaring \"1\" passed against a decoded count of 1 — the field was checked for AGREEMENT while its type went unchecked, which is half a boundary. The writer emits json_int; a string here means the producer changed." +// component_count is read by the strict numeric reader, the same one the component index uses. It +// previously admitted a string and compared text, so a receipt declaring "1" passed against a +// decoded count of 1 — the field was checked for AGREEMENT while its type went unchecked, which is +// half a boundary. The writer emits json_int; a string here means the producer changed. fn floor_component_receipt_decode_with(text: String, expected_schema: NonEmptyStr) -> FloorComponentReceiptDecoding { match parse_json_document(s: text) { @@ -706,7 +983,21 @@ fn floor_component_receipt_decode_with(text: String, expected_schema: NonEmptySt } } -data floor_component_receipt_subject_match_note: String = "THE ALERT REFUSES A RECEIPT WHOSE SUBJECT DOES NOT MATCH THE EVENT IT IS REACTING TO. This is the check the whole subject exists for, and each coordinate catches a different real failure. A run-id mismatch means the artifact came from a different run than the event describes — the download was misparameterized, or an artifact was resolved from a cached or retried run. A workflow mismatch means the receipt is from a different workflow entirely, which the shared artifact name makes possible. A head-SHA mismatch means the receipt is about a different tree, which is the one an operator would act on wrongly without ever suspecting it, because the run id would look right.\n\nMISMATCH IS A REFUSAL, NOT A DEGRADED RENDER. The tempting arm — report the components but note the subject is uncertain — is the absorbing fallback: it produces a plausible issue body from data of unknown provenance, and the uncertainty note is exactly the part a reader skims. An unmatched receipt is reported as an explicit unknown about the run, which is what the alert already does for a missing receipt, so the two blind cases behave the same way rather than one of them being quietly worse." +// THE ALERT REFUSES A RECEIPT WHOSE SUBJECT DOES NOT MATCH THE EVENT IT IS REACTING TO. This is the +// check the whole subject exists for, and each coordinate catches a different real failure. A +// run-id mismatch means the artifact came from a different run than the event describes — the +// download was misparameterized, or an artifact was resolved from a cached or retried run. A +// workflow mismatch means the receipt is from a different workflow entirely, which the shared +// artifact name makes possible. A head-SHA mismatch means the receipt is about a different tree, +// which is the one an operator would act on wrongly without ever suspecting it, because the run id +// would look right. +// +// MISMATCH IS A REFUSAL, NOT A DEGRADED RENDER. The tempting arm — report the components but note +// the subject is uncertain — is the absorbing fallback: it produces a plausible issue body from +// data of unknown provenance, and the uncertainty note is exactly the part a reader skims. An +// unmatched receipt is reported as an explicit unknown about the run, which is what the alert +// already does for a missing receipt, so the two blind cases behave the same way rather than one of +// them being quietly worse. type ReceiptSubjectMatch = ReceiptSubjectMatches @@ -714,6 +1005,17 @@ type ReceiptSubjectMatch | ReceiptSubjectWorkflowMismatch { receipt: NonEmptyStr, event: String } | ReceiptSubjectHeadShaMismatch { receipt: CommitSha, event: CommitSha } +// THERE IS DELIBERATELY NO floor_component_receipt_subject_admitted PREDICATE. One was added here +// and then removed in the same change that removed the admission and notification Bool projections +// (review 49414 caught that this one survived the sweep, correctly — an exception in the module +// that binds the receipt to the run it describes is the worst place to keep one). The three +// mismatch arms are three different incidents: a run-id mismatch is a misparameterized or retried +// download, a workflow mismatch is a receipt from another workflow sharing the artifact name, and a +// head-SHA mismatch is a receipt about a different TREE — the one an operator would act on wrongly +// while the run id looked right. A Bool answers `is this the right receipt` and discards which of +// those it was, at exactly the boundary where the alert is deciding what to say about a run. +// Callers match. + fn floor_component_receipt_subject_matches_event( subject: FloorComponentReceiptSubject, event: WorkflowRunEvent @@ -728,5 +1030,3 @@ fn floor_component_receipt_subject_matches_event( ReceiptSubjectMatches } } - -data floor_component_receipt_no_bool_projection_note: String = "THERE IS DELIBERATELY NO floor_component_receipt_subject_admitted PREDICATE. One was added here and then removed in the same change that removed the admission and notification Bool projections (review 49414 caught that this one survived the sweep, correctly — an exception in the module that binds the receipt to the run it describes is the worst place to keep one). The three mismatch arms are three different incidents: a run-id mismatch is a misparameterized or retried download, a workflow mismatch is a receipt from another workflow sharing the artifact name, and a head-SHA mismatch is a receipt about a different TREE — the one an operator would act on wrongly while the run id looked right. A Bool answers `is this the right receipt` and discards which of those it was, at exactly the boundary where the alert is deciding what to say about a run. Callers match." diff --git a/dag/gunbc/floor/floor_discovery_scaffold.dag b/dag/gunbc/floor/floor_discovery_scaffold.dag index 5e75041499d..d2678146b19 100644 --- a/dag/gunbc/floor/floor_discovery_scaffold.dag +++ b/dag/gunbc/floor/floor_discovery_scaffold.dag @@ -27,6 +27,14 @@ data floor_discovery_equivalence_test_sidecar_misplaced: String = "misplaced_wir data floor_discovery_equivalence_test_scan_wire_contract: String = "scan_detects_coproduct_wire_contract_data" -data floor_discovery_equivalence_test_narrowed_corpus_control: String = "clean_dag_file_leaves_narrowed_discovery_ok" +// discovery_scope_dirs semantics (intentional, review 41826): the producer walk +// (floor_discovery_walk_dir over all source_roots) applies tree-wide hygiene — filename __ refusal, +// test-marked-decl sidecar, wire-contract sidecar — before finalize. discover_floor_witness_roster +// applies discovery_scope_dirs only as a post-walk row filter (cli_run.rs +// apply_discovery_scope_dirs_filter), not as a pre-walk skip. Legacy build_floor_lens_hygiene_graph +// short-circuited sidecar checks outside scope; the new shape refuses on any hygiene violation +// anywhere under source_roots even when witness enrollment is scoped. Today ci_floor_plan +// discovery_scope_dirs is always [] so production CI is unchanged; non-empty scope is for narrowed +// roster projection only, not narrowed hygiene. -data floor_discovery_scope_semantics_note: String = "discovery_scope_dirs semantics (intentional, review 41826): the producer walk (floor_discovery_walk_dir over all source_roots) applies tree-wide hygiene — filename __ refusal, test-marked-decl sidecar, wire-contract sidecar — before finalize. discover_floor_witness_roster applies discovery_scope_dirs only as a post-walk row filter (cli_run.rs apply_discovery_scope_dirs_filter), not as a pre-walk skip. Legacy build_floor_lens_hygiene_graph short-circuited sidecar checks outside scope; the new shape refuses on any hygiene violation anywhere under source_roots even when witness enrollment is scoped. Today ci_floor_plan discovery_scope_dirs is always [] so production CI is unchanged; non-empty scope is for narrowed roster projection only, not narrowed hygiene." +data floor_discovery_equivalence_test_narrowed_corpus_control: String = "clean_dag_file_leaves_narrowed_discovery_ok" diff --git a/dag/gunbc/floor/floor_materialization.dag b/dag/gunbc/floor/floor_materialization.dag index 7289702f6b9..1488b9475cf 100644 --- a/dag/gunbc/floor/floor_materialization.dag +++ b/dag/gunbc/floor/floor_materialization.dag @@ -1,8 +1,27 @@ module gunbc.floor_materialization -data floor_materialization_note: String = "P1 — the in-process INDEX-BUILD share, declared. Every floor consumer (spec gates + witness entries + the floor-runner/entry-selection prelude) resolves against ONE shared MultiEntryIndex — union-resolve S1 (#6234) materializes the parse + typecheck index once per (thread, source_roots) via process_shared_index (cli_run.rs:1737); each consumer then assembles its own view over that shared index. N consumers demanding one index-build with no Share is the ladder's rule-1 AuthoredDuplication at a SharedStateFrame (one claim_executor process); S1 already did the rule-1 REWIRE (the index is readonly, so the plural reads are legal at the VALUE tier). This module DECLARES that correct-but-undeclared share as a ReferenceTier provider row so it is ladder-visible and a regression (a consumer that builds a private index instead of reading the share) is countable, not re-discovered per lens/gate." - -data floor_three_quantities_note: String = "GRAIN (merry-moth-539, 2026-07-09) — three DISTINCT quantities, kept apart so the share is not mislabeled a lie: (1) index-build(dag,src/v2) = 1 materialization, K readers = THIS module's P1 (identity union-INDEX-build, deliberately NOT 'union-resolve' so it cannot collide with the entry-resolve grain); (2) per-entry CLOSURE resolves = K today = gunbc.ci_materialization's resolves_total receipt (resolve_entry_graph still fires once per distinct entry — S1 shares the INDEX, not the per-entry closures; memo_deduplicates_resolve_count proves once-PER-ENTRY), the ratchet target that the graph-major node-store rewire collapses K->~1; (3) readers of the share = K = this module's reader_count. Case study: S1 already bought index=1 (declared here); the remaining duplication is the K closure resolves (counted there); the graph-major node-store rewire kills those. Declaring the share moves nothing at runtime, so this lane does NOT touch ci_floor_declared_resolve_count." +// P1 — the in-process INDEX-BUILD share, declared. Every floor consumer (spec gates + witness +// entries + the floor-runner/entry-selection prelude) resolves against ONE shared MultiEntryIndex — +// union-resolve S1 (#6234) materializes the parse + typecheck index once per (thread, source_roots) +// via process_shared_index (cli_run.rs:1737); each consumer then assembles its own view over that +// shared index. N consumers demanding one index-build with no Share is the ladder's rule-1 +// AuthoredDuplication at a SharedStateFrame (one claim_executor process); S1 already did the rule-1 +// REWIRE (the index is readonly, so the plural reads are legal at the VALUE tier). This module +// DECLARES that correct-but-undeclared share as a ReferenceTier provider row so it is +// ladder-visible and a regression (a consumer that builds a private index instead of reading the +// share) is countable, not re-discovered per lens/gate. + +// GRAIN (merry-moth-539, 2026-07-09) — three DISTINCT quantities, kept apart so the share is not +// mislabeled a lie: (1) index-build(dag,src/v2) = 1 materialization, K readers = THIS module's P1 +// (identity union-INDEX-build, deliberately NOT 'union-resolve' so it cannot collide with the +// entry-resolve grain); (2) per-entry CLOSURE resolves = K today = gunbc.ci_materialization's +// resolves_total receipt (resolve_entry_graph still fires once per distinct entry — S1 shares the +// INDEX, not the per-entry closures; memo_deduplicates_resolve_count proves once-PER-ENTRY), the +// ratchet target that the graph-major node-store rewire collapses K->~1; (3) readers of the share = +// K = this module's reader_count. Case study: S1 already bought index=1 (declared here); the +// remaining duplication is the K closure resolves (counted there); the graph-major node-store +// rewire kills those. Declaring the share moves nothing at runtime, so this lane does NOT touch +// ci_floor_declared_resolve_count. data floor_index_build_share_provider_id: String = "process_shared_index" @@ -24,7 +43,13 @@ data floor_index_build_share_provider: CacheProvider = provider_row( data floor_index_build_share_providers: List = [floor_index_build_share_provider] -data floor_index_build_share_tier_note: String = "ReferenceTier declared DIRECTLY via provider_row, deliberately NOT provider_from_catalog: the catalog door births STORE tiers from cited upstream facts (that is P2 = extdeps.realization.resolved_graph.resolved_graph_cache, the cross-process content-keyed store graph-major S2b adopts). ReferenceTier is keyless by shape (the dependency edge IS the address) and enters only via provider_row; forcing an in-process name-keyed share through the catalog would project ExistenceKeyed and be refused/mislabeled (#6352). The share's legitimacy is the value tier — the product of the rule-1 rewire S1 already did — not obligation discharge." +// ReferenceTier declared DIRECTLY via provider_row, deliberately NOT provider_from_catalog: the +// catalog door births STORE tiers from cited upstream facts (that is P2 = +// extdeps.realization.resolved_graph.resolved_graph_cache, the cross-process content-keyed store +// graph-major S2b adopts). ReferenceTier is keyless by shape (the dependency edge IS the address) +// and enters only via provider_row; forcing an in-process name-keyed share through the catalog +// would project ExistenceKeyed and be refused/mislabeled (#6352). The share's legitimacy is the +// value tier — the product of the rule-1 rewire S1 already did — not obligation discharge. fn gate_consumer_site(g: Gate) -> String { match g { @@ -39,7 +64,19 @@ fn gate_consumer_site(g: Gate) -> String { } } -data floor_prelude_consumer_sites_note: String = "EMPTY, and that is a measured state rather than an unfilled roster. The floor once resolved two fixed prelude entries against the shared index before any batch ran — the affected-set floor runner and the entry-selection lens — mirroring the host's FLOOR_RUNNER_ENTRY / ENTRY_SELECTION_ENTRY constants. Both existed only to answer the selection question, and both were deleted with affected-set selection (2026-08-15), so the floor now has no prelude entry at all and reader_count over this list is zero by construction rather than by omission.\n\nThe drift this row was written to record still stands as history: entry #2 was once v2.workflow.affected_set_selection rather than the host's v2.lens.affected_set.entry_selection, corrected in #6432 review — a hand-copy of a host constant disagreeing with the constant. 🟡 dissolve-on: the list stays empty through the floor rebuild, at which point this carrier and its witness delete together rather than being repopulated; if a prelude entry is ever needed again it is declared once in .dag and consumed by BOTH the host and this row, never hand-copied." +// EMPTY, and that is a measured state rather than an unfilled roster. The floor once resolved two +// fixed prelude entries against the shared index before any batch ran — the affected-set floor +// runner and the entry-selection lens — mirroring the host's FLOOR_RUNNER_ENTRY / +// ENTRY_SELECTION_ENTRY constants. Both existed only to answer the selection question, and both +// were deleted with affected-set selection (2026-08-15), so the floor now has no prelude entry at +// all and reader_count over this list is zero by construction rather than by omission. +// +// The drift this row was written to record still stands as history: entry #2 was once +// v2.workflow.affected_set_selection rather than the host's v2.lens.affected_set.entry_selection, +// corrected in #6432 review — a hand-copy of a host constant disagreeing with the constant. 🟡 +// dissolve-on: the list stays empty through the floor rebuild, at which point this carrier and its +// witness delete together rather than being repopulated; if a prelude entry is ever needed again it +// is declared once in .dag and consumed by BOTH the host and this row, never hand-copied. data floor_prelude_consumer_sites: List = [] @@ -81,11 +118,39 @@ fn floor_index_build_materialization_without_provider(gates: List, witness ) } -data floor_index_build_p1_p2_convergence_note: String = "P1 (this module's in-process ReferenceTier share = process_shared_index) and P2 (extdeps.realization.resolved_graph.resolved_graph_cache, PerHostFilesystem/ContentAddressed, released never with an exact 10 GiB byte ceiling and provider-native replacement, projecting through provider_from_catalog to a content-keyed ArtifactTier) are two providers of ONE computation identity (the resolved graph), never two unrelated stores (the fnv1a64 dual-surface discipline). P1 discharges same-process index-build plurality (rule-1 rewire, value tier); P2 discharges the cross-process/cross-run isolation-LCA obligation (compile subprocess vs claim_executor frontier), which a reference cannot cross. DISSOLVE-ON: when graph-major S2b is default-on, the cross-run (UnboundedSiblingsFrame) frame enrolls and P2's provider row goes live beside this one; the per-shard W-x same-process residual (Rc !Send) collapses to whole-run once under the Rc->Arc migration (graph-major S3)." - -data floor_materialization_status_note: String = "LIVE — std.materialization_ladder landed on main (#6375). The enrolled witness dag/test/claim/floor_materialization_witness_test.dag IS the authority (green-by-execution on the CI floor, GithubActionsCiJob surface of gunbc.commit_workflow.commit_gate_roster): floor_index_build_materialization -> ValueSharedByReference (provider_id process_shared_index, reader_count == live consumer count) and the RED control floor_index_build_materialization_without_provider -> ValueRefusedNoCarrierProvider (providers empty), so the share is green because DECLARED and covering, not because nothing looked. reader_count folds the live roster (project_ci_floor_gates + project_ci_floor_witness_entries + the 2 prelude entries), so enrolling this very witness grows the consumer set it counts — self-consistent, never circular. CI-MEASURED (run 29066096492): enrolling this witness left the floor's resolves_total UNCHANGED at 3 — the witness POOLS warm against the shared index (its import closure is already materialized by the batch-1 compile-clean whole-tree pass, so its per-entry resolve is a walk_memo hit, resolve_nanos=0, excluded from the count), so the declaration adds ZERO cold-resolve debt and this lane does NOT touch ci_floor_declared_resolve_count. An earlier predicted 3->4 bump was retracted after measurement: the third-std.materialization_ladder-importer-pays hypothesis was wrong — enrollment does not always pay (merry-moth-539's caution, confirmed by the fail-closed gate that redded the mispredicted 4)." - -data floor_typecheck_store_section_note: String = "S2a increment C (cross-worker-typecheck-share-design.md §4.5) — process-scoped typecheck store share, declared alongside P1 index-build share." +// P1 (this module's in-process ReferenceTier share = process_shared_index) and P2 +// (extdeps.realization.resolved_graph.resolved_graph_cache, PerHostFilesystem/ContentAddressed, +// released never with an exact 10 GiB byte ceiling and provider-native replacement, projecting +// through provider_from_catalog to a content-keyed ArtifactTier) are two providers of ONE +// computation identity (the resolved graph), never two unrelated stores (the fnv1a64 dual-surface +// discipline). P1 discharges same-process index-build plurality (rule-1 rewire, value tier); P2 +// discharges the cross-process/cross-run isolation-LCA obligation (compile subprocess vs +// claim_executor frontier), which a reference cannot cross. DISSOLVE-ON: when graph-major S2b is +// default-on, the cross-run (UnboundedSiblingsFrame) frame enrolls and P2's provider row goes live +// beside this one; the per-shard W-x same-process residual (Rc !Send) collapses to +// whole-run once under the Rc->Arc migration (graph-major S3). + +// LIVE — std.materialization_ladder landed on main (#6375). The enrolled witness +// dag/test/claim/floor_materialization_witness_test.dag IS the authority (green-by-execution on the +// CI floor, GithubActionsCiJob surface of gunbc.commit_workflow.commit_gate_roster): +// floor_index_build_materialization -> ValueSharedByReference (provider_id process_shared_index, +// reader_count == live consumer count) and the RED control +// floor_index_build_materialization_without_provider -> ValueRefusedNoCarrierProvider (providers +// empty), so the share is green because DECLARED and covering, not because nothing looked. +// reader_count folds the live roster (project_ci_floor_gates + project_ci_floor_witness_entries + +// the 2 prelude entries), so enrolling this very witness grows the consumer set it counts — +// self-consistent, never circular. CI-MEASURED (run 29066096492): enrolling this witness left the +// floor's resolves_total UNCHANGED at 3 — the witness POOLS warm against the shared index (its +// import closure is already materialized by the batch-1 compile-clean whole-tree pass, so its +// per-entry resolve is a walk_memo hit, resolve_nanos=0, excluded from the count), so the +// declaration adds ZERO cold-resolve debt and this lane does NOT touch +// ci_floor_declared_resolve_count. An earlier predicted 3->4 bump was retracted after measurement: +// the third-std.materialization_ladder-importer-pays hypothesis was wrong — enrollment does not +// always pay (merry-moth-539's caution, confirmed by the fail-closed gate that redded the +// mispredicted 4). + +// S2a increment C (cross-worker-typecheck-share-design.md §4.5) — process-scoped typecheck store +// share, declared alongside P1 index-build share. data floor_typecheck_store_share_note: String = "C2 — the in-process TYPECHECK-STORE share, declared. Adaptive floor workers arm ONE process-scoped typed_module_cache via SharedTypecheckCaches when the memory governor's target_width > 1 (cli_run.rs cross_worker_store + shared_typecheck_store.rs serde byte transport); at width=1 workers stay on private per-index Rc (serde retention without cross-worker benefit breaks CI budget — design §7). The pump thread keeps process_shared_index (private Rc). Identity union-typecheck-store(dag,src/v2) is deliberately distinct from P1's union-index-build. 🟡 dissolve-on: serde byte transport retires when store-path Rc→Arc lands (design §4.2)." @@ -103,7 +168,11 @@ data floor_typecheck_store_share_provider: CacheProvider = provider_row( data floor_typecheck_store_share_providers: List = [floor_typecheck_store_share_provider] -data floor_typecheck_store_consumer_sites_note: String = "The two minimum co-readers of the adaptive worker pool at W>=2 (cross-worker-typecheck-share-design.md §4.4). Runtime width may exceed 2 under the memory governor; the ladder counts declared readonly sites, not live W. The pump thread uses private process_shared_index (P1); only admitted worker shards clone Arc> and read the shared typed store." +// The two minimum co-readers of the adaptive worker pool at W>=2 +// (cross-worker-typecheck-share-design.md §4.4). Runtime width may exceed 2 under the memory +// governor; the ladder counts declared readonly sites, not live W. The pump thread uses private +// process_shared_index (P1); only admitted worker shards clone Arc> +// and read the shared typed store. data floor_typecheck_store_consumer_sites: List = [ "cli_run.rs:run_discovery_corpus_with_options:adaptive-worker-shard", diff --git a/dag/gunbc/floor/floor_non_verdict_classification.dag b/dag/gunbc/floor/floor_non_verdict_classification.dag index a55d2ace27c..ca2de3821ce 100644 --- a/dag/gunbc/floor/floor_non_verdict_classification.dag +++ b/dag/gunbc/floor/floor_non_verdict_classification.dag @@ -63,29 +63,108 @@ fn non_verdict_row_identity(module_path: String, function: String) -> NonVerdict NonVerdictIdentity { module_path: module_path, function: function } } -data closure_ab_note: String = "THE DISCRIMINATOR, AND IT IS THE WHOLE METHOD: run the witness under claim_batch --hermetic (the floor's own execution mode) with a narrow --entry closure, and compare against its behaviour on the floor's whole-corpus prepared subject. A row that PASSES narrow and throws on the floor is closure-dependent: nothing about the witness or its subject changed, only which names were in scope. A row that throws under both is closure-independent and its cause is in the code it names." +// THE DISCRIMINATOR, AND IT IS THE WHOLE METHOD: run the witness under claim_batch --hermetic (the +// floor's own execution mode) with a narrow --entry closure, and compare against its behaviour on +// the floor's whole-corpus prepared subject. A row that PASSES narrow and throws on the floor is +// closure-dependent: nothing about the witness or its subject changed, only which names were in +// scope. A row that throws under both is closure-independent and its cause is in the code it names. data local_binding_shadow_probe_note: String = "THE DEFECT BEHIND THE CLOSURE-DEPENDENT ROWS, established on a complete grid by two lanes independently (calm-boar-150 and silent-bear-842) and enrolled as v2.test.claim.local_binding_shadow local_named_function_binding_is_what_the_call_reaches: a call site spelling a name held by BOTH a local binding and a module-level free function reaches the FREE FUNCTION when the local's value is a reference to a named top-level function. let, parameter and pattern bindings behave identically; a local bound to a LAMBDA reaches the local. The collision is decided at the CALL SITE by the spelling used there, which is why a rename repairs it. Where the shadowing symbol's arity DIFFERS the call refuses loudly; where it AGREES a plausible wrong value returns with no diagnostic. It is closure-sensitive by construction, which is exactly why these rows only lose their verdict once the pool is big enough to contain the colliding name." -data unjoined_inference_note: String = "WHAT IS NOT CLAIMED, and it is the join rather than either half. Closure-dependence is MEASURED for the nine rows below. The shadowing defect is MEASURED, on its own grid. That the shadowing defect is what makes THESE NINE lose their verdict is INFERENCE, not measurement -- no specific colliding name has been identified for the six srv3 rows or the two staging rows, and a check for duplicate free functions among the names those witnesses call found none across dag and src/v2. The cheapest instrument for the join is the repair itself: fix the resolver and re-run, at which point recovery measures the join and non-recovery isolates a second cause with no ambiguity." - -data host_phase_unmeasured_note: String = "THE TWO host_phase_status ROWS ARE UNMEASURED AND STAY THAT WAY IN THIS CARRIER. Their witness file carries ONE import and resolves everything else from the pool, so a narrow closure cannot even resolve them -- claim_batch refuses with `no declaration named host_phase_reconcile in this execution's loaded index`. There is therefore no small-closure arm to compare, and assigning them the neighbouring cause because they share an error message would be exactly the by-error-text grouping this carrier exists to replace." - -data gating_answer_note: String = "DOES known_red_runtime_errored GATE, AND SHOULD IT? It does not, and that is a DELIBERATE BOUNDARY rather than an omission. claim_executor required_floor_outcome_is_clean excludes both non-verdict arms, and v2.workflow.floor_non_verdict's header argues why: the arms are honest observations, and gating on them directly would red every lane holding a row of an unrepaired population. What gates instead is GROWTH at identity grain (non_verdict_unenrolled: an observed non-verdict identity the roster does not carry) and STALENESS (stale_non_verdict: a rostered identity the run did not observe), which together pin observed == roster. So a green main alongside fifteen unanswered enrolments is the debt being enrolled at identity grain, not something overlooked. THE ONE LIVE AXIS is already declared on that carrier and is not a finding of this pass: nothing compares the roster at HEAD against the roster at the BASE, so an author whose change makes a NEW identity stop producing a verdict may add its row in the SAME change -- the row becomes the permission. The carrier rates itself mitigatable there and names its next-rung trigger." +// WHAT IS NOT CLAIMED, and it is the join rather than either half. Closure-dependence is MEASURED +// for the nine rows below. The shadowing defect is MEASURED, on its own grid. That the shadowing +// defect is what makes THESE NINE lose their verdict is INFERENCE, not measurement -- no specific +// colliding name has been identified for the six srv3 rows or the two staging rows, and a check for +// duplicate free functions among the names those witnesses call found none across dag and src/v2. +// The cheapest instrument for the join is the repair itself: fix the resolver and re-run, at which +// point recovery measures the join and non-recovery isolates a second cause with no ambiguity. + +// THE TWO host_phase_status ROWS ARE UNMEASURED AND STAY THAT WAY IN THIS CARRIER. Their witness +// file carries ONE import and resolves everything else from the pool, so a narrow closure cannot +// even resolve them -- claim_batch refuses with `no declaration named host_phase_reconcile in this +// execution's loaded index`. There is therefore no small-closure arm to compare, and assigning them +// the neighbouring cause because they share an error message would be exactly the by-error-text +// grouping this carrier exists to replace. + +// DOES known_red_runtime_errored GATE, AND SHOULD IT? It does not, and that is a DELIBERATE +// BOUNDARY rather than an omission. claim_executor required_floor_outcome_is_clean excludes both +// non-verdict arms, and v2.workflow.floor_non_verdict's header argues why: the arms are honest +// observations, and gating on them directly would red every lane holding a row of an unrepaired +// population. What gates instead is GROWTH at identity grain (non_verdict_unenrolled: an observed +// non-verdict identity the roster does not carry) and STALENESS (stale_non_verdict: a rostered +// identity the run did not observe), which together pin observed == roster. So a green main +// alongside fifteen unanswered enrolments is the debt being enrolled at identity grain, not +// something overlooked. THE ONE LIVE AXIS is already declared on that carrier and is not a finding +// of this pass: nothing compares the roster at HEAD against the roster at the BASE, so an author +// whose change makes a NEW identity stop producing a verdict may add its row in the SAME change -- +// the row becomes the permission. The carrier rates itself mitigatable there and names its +// next-rung trigger. data closure_dependent_evidence: String = "PASSES under a narrow claim_batch --hermetic closure; produces no verdict on the floor's whole-corpus subject" -data closure_dependent_repaid_note: String = "EMPTY, AND THAT IS A REPAYMENT RECEIPT AND A MEASURED JOIN RATHER THAN A MISSING MEASUREMENT. This list held NINE ClosureDependentResolution / AssertsARedThatDoesNotExist rows -- six test.claim.srv3_subsumption, two v2.test.claim.staging, and v2.test.emit.produced_decl_support_preserved. All nine now ANSWER and PASS on the required floor, so they left this carrier and left floor_expected_red in the same change. THE JOIN THIS CARRIER DECLARED AS INFERENCE IS NOW MEASURED, and that is the substantive result rather than the bookkeeping: unjoined_inference_note stated that closure-dependence was measured for these rows, that the local-binding-shadow defect was measured on its own grid, and that the claim THESE NINE lose their verdict TO THAT DEFECT was inference with no colliding name identified for the six srv3 rows or the two staging rows. It named its own instrument -- fix the resolver and re-run, at which point recovery measures the join and non-recovery isolates a second cause. The resolver is fixed (a lexical binding holding a named top-level function now shadows the free function sharing its spelling; the gate named Value::Closure only and so missed Value::Fn) and ALL NINE RECOVERED. Non-recovery would have isolated a second cause; there is none. The one row that already carried a named colliding symbol, produced_decl_support_preserved, is the confirmation rather than the whole result: its `render` is pattern-bound out of ProducedDeclWired against std.layout's free `fn render(doc, proto)`, the loud different-arity face, and it recovered alongside the eight whose colliding name was never identified. The list stays declared rather than deleted because the CAUSE remains constructible -- a lexical binding holding a NON-CALLABLE value still falls through to a same-named free function, enrolled as its own expected-red row with a census-then-widen trigger -- and an emptied arm with its reason recorded is a different fact from an arm that never existed." +// EMPTY, AND THAT IS A REPAYMENT RECEIPT AND A MEASURED JOIN RATHER THAN A MISSING MEASUREMENT. +// This list held NINE ClosureDependentResolution / AssertsARedThatDoesNotExist rows -- six +// test.claim.srv3_subsumption, two v2.test.claim.staging, and +// v2.test.emit.produced_decl_support_preserved. All nine now ANSWER and PASS on the required floor, +// so they left this carrier and left floor_expected_red in the same change. THE JOIN THIS CARRIER +// DECLARED AS INFERENCE IS NOW MEASURED, and that is the substantive result rather than the +// bookkeeping: unjoined_inference_note stated that closure-dependence was measured for these rows, +// that the local-binding-shadow defect was measured on its own grid, and that the claim THESE NINE +// lose their verdict TO THAT DEFECT was inference with no colliding name identified for the six +// srv3 rows or the two staging rows. It named its own instrument -- fix the resolver and re-run, at +// which point recovery measures the join and non-recovery isolates a second cause. The resolver is +// fixed (a lexical binding holding a named top-level function now shadows the free function sharing +// its spelling; the gate named Value::Closure only and so missed Value::Fn) and ALL NINE RECOVERED. +// Non-recovery would have isolated a second cause; there is none. The one row that already carried +// a named colliding symbol, produced_decl_support_preserved, is the confirmation rather than the +// whole result: its `render` is pattern-bound out of ProducedDeclWired against std.layout's free +// `fn render(doc, proto)`, the loud different-arity face, and it recovered alongside the eight +// whose colliding name was never identified. The list stays declared rather than deleted because +// the CAUSE remains constructible -- a lexical binding holding a NON-CALLABLE value still falls +// through to a same-named free function, enrolled as its own expected-red row with a +// census-then-widen trigger -- and an emptied arm with its reason recorded is a different fact from +// an arm that never existed. data closure_dependent_rows: List = [] -data realization_attempt_branch_repaid_note: String = "THE COMPOSED #9106 BRANCH CONTRIBUTED A TENTH ClosureDependentResolution specimen: gunbc.test.claim.realization_attempt_keystone_test.witness_planted_minimal_module_emits passed in its 179-module entry closure but the old floor union shadowed emit_produced's pattern-bound render with std.layout.render(doc, proto), producing the same missing-proto error as produced_decl_support_preserved. #9259 makes the lexical Value::Fn binding win, so this branch-only non-verdict and its expected-red row retire while the witness remains an ordinary regression control." - -data closure_independent_repaid_note: String = "EMPTY, AND THAT IS A REPAYMENT RECEIPT RATHER THAN A MISSING MEASUREMENT. This list held the four v2.test.manual.bootstrap_footprint_anchor identities, the only ClosureIndependent / HonestDebtWithStatedTrigger rows in the population. Their cause was a Symbol-declared field holding Int code points: semantic_decl_string_to_bundle_node folded a String into character atoms carrying raw code points, and canonical_hash_of_connective routed that field into content_hash_atom -> atom_identity_hash, which requires a string, so the hash threw before any assertion ran. Repaired at the PRODUCER by giving character atoms a string identity through from_code_point, with code_point as its inverse on the two decoders. All four now ANSWER: three PASS and bootstrap_footprint_canonical_holds FAILS honestly, so the four left this roster and the three passing ones left floor_expected_red in the same change. The list stays declared rather than deleted because the CAUSE remains constructible -- nothing prevents another producer putting a non-Symbol into Atom.identity -- and an emptied arm with its reason recorded is a different fact from an arm that never existed." +// THE COMPOSED #9106 BRANCH CONTRIBUTED A TENTH ClosureDependentResolution specimen: +// gunbc.test.claim.realization_attempt_keystone_test.witness_planted_minimal_module_emits passed in +// its 179-module entry closure but the old floor union shadowed emit_produced's pattern-bound +// render with std.layout.render(doc, proto), producing the same missing-proto error as +// produced_decl_support_preserved. #9259 makes the lexical Value::Fn binding win, so this +// branch-only non-verdict and its expected-red row retire while the witness remains an ordinary +// regression control. + +// EMPTY, AND THAT IS A REPAYMENT RECEIPT RATHER THAN A MISSING MEASUREMENT. This list held the four +// v2.test.manual.bootstrap_footprint_anchor identities, the only ClosureIndependent / +// HonestDebtWithStatedTrigger rows in the population. Their cause was a Symbol-declared field +// holding Int code points: semantic_decl_string_to_bundle_node folded a String into character atoms +// carrying raw code points, and canonical_hash_of_connective routed that field into +// content_hash_atom -> atom_identity_hash, which requires a string, so the hash threw before any +// assertion ran. Repaired at the PRODUCER by giving character atoms a string identity through +// from_code_point, with code_point as its inverse on the two decoders. All four now ANSWER: three +// PASS and bootstrap_footprint_canonical_holds FAILS honestly, so the four left this roster and the +// three passing ones left floor_expected_red in the same change. The list stays declared rather +// than deleted because the CAUSE remains constructible -- nothing prevents another producer putting +// a non-Symbol into Atom.identity -- and an emptied arm with its reason recorded is a different +// fact from an arm that never existed. data closure_independent_rows: List = [] -data unmeasured_repaid_note: String = "EMPTY, AND IT WAS REPAID WITHOUT EVER BEING MEASURED, which is a different receipt from the two above and is worth keeping distinct. This list held the two test.claim.host_phase_status identities. They were classified StandingUnmeasured on an honest obstacle: their witness carries ONE import and resolves everything else from the pool, so no narrow closure can even resolve them and there was no small-closure arm to compare against -- host_phase_unmeasured_note records that, and records the refusal to assign them the neighbouring cause merely because they shared an error message. THAT RESTRAINT WAS CORRECT AND IT WAS ALSO NOT NEEDED IN THE END: both now ANSWER and PASS on the required floor after the local-binding-shadow repair, alongside the nine rows that WERE measured as closure-dependent. So the shared cause was real; what was missing was an instrument able to show it, and the repair supplied one that the A/B could not. This is NOT a retroactive classification -- the rows are gone and nothing here claims they were ClosureDependentResolution -- it is the weaker and true statement that a repair aimed elsewhere recovered them too. The list stays declared because the obstacle remains constructible: a witness resolving from the pool rather than from declared imports still has no narrow arm." +// EMPTY, AND IT WAS REPAID WITHOUT EVER BEING MEASURED, which is a different receipt from the two +// above and is worth keeping distinct. This list held the two test.claim.host_phase_status +// identities. They were classified StandingUnmeasured on an honest obstacle: their witness carries +// ONE import and resolves everything else from the pool, so no narrow closure can even resolve them +// and there was no small-closure arm to compare against -- host_phase_unmeasured_note records that, +// and records the refusal to assign them the neighbouring cause merely because they shared an error +// message. THAT RESTRAINT WAS CORRECT AND IT WAS ALSO NOT NEEDED IN THE END: both now ANSWER and +// PASS on the required floor after the local-binding-shadow repair, alongside the nine rows that +// WERE measured as closure-dependent. So the shared cause was real; what was missing was an +// instrument able to show it, and the repair supplied one that the A/B could not. This is NOT a +// retroactive classification -- the rows are gone and nothing here claims they were +// ClosureDependentResolution -- it is the weaker and true statement that a repair aimed elsewhere +// recovered them too. The list stays declared because the obstacle remains constructible: a witness +// resolving from the pool rather than from declared imports still has no narrow arm. data unmeasured_rows: List = [] diff --git a/dag/gunbc/generated_artifact.dag b/dag/gunbc/generated_artifact.dag index d5b6e22900a..8bf7f691fcd 100644 --- a/dag/gunbc/generated_artifact.dag +++ b/dag/gunbc/generated_artifact.dag @@ -54,7 +54,22 @@ type CommitPolicy = CommitRequired { consumer: RepoConsumer } | NotCommitted -data artifact_location_note: String = "THE PATH IS A PROJECTION OF ITS PARTS, NOT THE OTHER WAY ROUND. Every row below CONSTRUCTS its path from a directory and an entry name -- some literal, some computed from a spec host or a plan slug -- and the joined string was the only thing this module published. A consumer that must LIST the directory and look for the entry inside that listing therefore had to split the string on its last separator, re-deriving by parsing exactly what the construction already knew. That is decompose-after-compose: redundant by DESIGN section 2, and asymmetric in failure, because a parse has error cases a construction does not. So artifact_location is the single match and artifact_path is derived from it. THE PATH BYTES ARE UNCHANGED and that is load-bearing rather than incidental: artifact_path feeds the drift gate's committed-file read, so a change here would silently repoint every adjudicated artifact.\n\nWHY THE ROOT DIRECTORY IS `.` AND NOT THE EMPTY STRING. A root-level artifact's directory is the repository root, which is a real directory that can be listed; the empty string is not a directory at all and would have to be special-cased at every consumer that wants to observe one. Carrying `.` means a listing consumer needs no arm for `this artifact has no directory`, and the join is the only place that knows the root renders without a prefix." +// THE PATH IS A PROJECTION OF ITS PARTS, NOT THE OTHER WAY ROUND. Every row below CONSTRUCTS its +// path from a directory and an entry name -- some literal, some computed from a spec host or a plan +// slug -- and the joined string was the only thing this module published. A consumer that must LIST +// the directory and look for the entry inside that listing therefore had to split the string on its +// last separator, re-deriving by parsing exactly what the construction already knew. That is +// decompose-after-compose: redundant by DESIGN section 2, and asymmetric in failure, because a +// parse has error cases a construction does not. So artifact_location is the single match and +// artifact_path is derived from it. THE PATH BYTES ARE UNCHANGED and that is load-bearing rather +// than incidental: artifact_path feeds the drift gate's committed-file read, so a change here would +// silently repoint every adjudicated artifact. +// +// WHY THE ROOT DIRECTORY IS `.` AND NOT THE EMPTY STRING. A root-level artifact's directory is the +// repository root, which is a real directory that can be listed; the empty string is not a +// directory at all and would have to be special-cased at every consumer that wants to observe one. +// Carrying `.` means a listing consumer needs no arm for `this artifact has no directory`, and the +// join is the only place that knows the root renders without a prefix. // THE ROW IS KEYED BY HOST IDENTITY, NOT BY THE DEPLOY ROW, AND THAT IS AN ACYCLICITY FACT RATHER // THAN A PREFERENCE. gunbc.runner_host_deploy reaches this module transitively @@ -175,7 +190,13 @@ fn committed_generated_artifact_paths() -> List { map(committed_generated_artifacts(), a => artifact_path(a: a)) } -data artifact_eq_disposition_reference_note: String = "Disposition authority (reference only; do not fork the lifecycle here): gunbc.non_fold_residue.non_fold_residue_frontier enrolls dag/gunbc/generated_artifact.dag::artifact_eq with the typed nfr_reason_land_red_era reason and nfr_dissolve_owning_fold trigger. The live non_fold_residue_clean_holds witness refuses both an unrostered predicate and a stale roster row; its synthetic malformed-row RED witnesses that reason without a dissolution trigger is invalid. This equality residue therefore grows only under that one bounded census row and deletes when the owning fold derives equality from inhabitance." +// Disposition authority (reference only; do not fork the lifecycle here): +// gunbc.non_fold_residue.non_fold_residue_frontier enrolls +// dag/gunbc/generated_artifact.dag::artifact_eq with the typed nfr_reason_land_red_era reason and +// nfr_dissolve_owning_fold trigger. The live non_fold_residue_clean_holds witness refuses both an +// unrostered predicate and a stale roster row; its synthetic malformed-row RED witnesses that +// reason without a dissolution trigger is invalid. This equality residue therefore grows only under +// that one bounded census row and deletes when the owning fold derives equality from inhabitance. fn artifact_eq(a: GeneratedArtifact, b: GeneratedArtifact) -> Bool { match a { diff --git a/dag/gunbc/generated_artifact_assessment.dag b/dag/gunbc/generated_artifact_assessment.dag index 5dd4e9dff81..6cc2d3a29dd 100644 --- a/dag/gunbc/generated_artifact_assessment.dag +++ b/dag/gunbc/generated_artifact_assessment.dag @@ -2,7 +2,25 @@ module gunbc.generated_artifact_assessment import gunbc.generated_artifact { GeneratedArtifact, artifact_eq, artifact_path } -data generated_artifact_assessment_note: String = "P0 (operator-directed, 2026-08-05; module-split per loyal-ram-550 review of #7823): purity BY CONSTRUCTION, not by convention — but the construction is the ABSENCE of an import path to the producer, not yet a compiler-enforced wall. This module carries the GeneratedArtifactObservation type, its accessors, and every leaf/group/diagnostic predicate over an already-retained observation population. It imports gunbc.generated_artifact ONLY (for the identity type and artifact_eq); extdeps.filesystem.filesystem_io and gunbc.generated_artifact_emit's artifact_generate/artifact_extra_valid appear nowhere in it. The live producer (observe_generated_artifact / observe_generated_artifact_population) lives in the sibling module gunbc.generated_artifact_observation, which imports this module — never the other direction. Correction (loyal-ram-550 review, 2026-08-05): an earlier revision of this note claimed a bare artifact_generate/Filesystem reference written inside this module without an import 'fails to compile' — that overstates the rung. DESIGN §4b Class B (open thread, #6985) means a bare cross-module reference can still resolve by pool-membership coincidence when an unrelated import elsewhere in the same closure has already dragged the target module in, which is close to guaranteed here since gunbc.generated_artifact_observation imports both this module and the generator. So today the guarantee this split buys is mechanically-preventable-by-convention (no import path exists; a reviewer sees any new import immediately), not structurally-impossible; the stronger claim dissolves on the namespace lane's closure-independent binding fix (Class B), at which point a bare in-module reference to the producer genuinely refuses to resolve." +// P0 (operator-directed, 2026-08-05; module-split per loyal-ram-550 review of #7823): purity BY +// CONSTRUCTION, not by convention — but the construction is the ABSENCE of an import path to the +// producer, not yet a compiler-enforced wall. This module carries the GeneratedArtifactObservation +// type, its accessors, and every leaf/group/diagnostic predicate over an already-retained +// observation population. It imports gunbc.generated_artifact ONLY (for the identity type and +// artifact_eq); extdeps.filesystem.filesystem_io and gunbc.generated_artifact_emit's +// artifact_generate/artifact_extra_valid appear nowhere in it. The live producer +// (observe_generated_artifact / observe_generated_artifact_population) lives in the sibling module +// gunbc.generated_artifact_observation, which imports this module — never the other direction. +// Correction (loyal-ram-550 review, 2026-08-05): an earlier revision of this note claimed a bare +// artifact_generate/Filesystem reference written inside this module without an import 'fails to +// compile' — that overstates the rung. DESIGN §4b Class B (open thread, #6985) means a bare +// cross-module reference can still resolve by pool-membership coincidence when an unrelated import +// elsewhere in the same closure has already dragged the target module in, which is close to +// guaranteed here since gunbc.generated_artifact_observation imports both this module and the +// generator. So today the guarantee this split buys is mechanically-preventable-by-convention (no +// import path exists; a reviewer sees any new import immediately), not structurally-impossible; the +// stronger claim dissolves on the namespace lane's closure-independent binding fix (Class B), at +// which point a bare in-module reference to the producer genuinely refuses to resolve. type GeneratedArtifactObservation = GeneratedArtifactObserved { @@ -23,7 +41,25 @@ type GeneratedArtifactObservation cause: String } -data committed_absent_arm_note: String = "ABSENT IS NOT A REFUSAL, AND THE DISTINCTION IS THE WHOLE REASON THIS ARM EXISTS. The producer previously mapped EVERY unsuccessful read to GeneratedArtifactObservationRefused, so `the observation could not be made` and `the observation was made and the file is not there` shared one arm. They have opposite owners: a refusal names an authority to repair and reached NO verdict, while an absence IS a verdict about the repository tree and asks whether the artifact was ever meant to be committed. DESIGN carries the receipt for getting this wrong -- this gate's first red included a markdown file deleted ON PURPOSE, and the regenerate remedy would have silently resurrected it.\n\nIT IS ALSO DISTINCT FROM ExpectedArtifactObservationMissing, which is the adjacent confusion. That finding says the POPULATION has no row for an expected identity -- a completeness defect in the observer. This arm says the observer ran, produced a row, and the row reports the committed path is not on disk. Different execution provenance, different remedy.\n\nTHE ABSENCE IS ESTABLISHED, NOT INFERRED. It is produced only where a directory LISTING succeeded and did not name the entry, never from a read failing -- because a read's success channel is exactly the channel that cannot separate absent from unreadable. `detail` carries the established absence's own rendering (which directory was listed, which name was not in it), so a consumer can say what it did not find rather than only that it found nothing." +// ABSENT IS NOT A REFUSAL, AND THE DISTINCTION IS THE WHOLE REASON THIS ARM EXISTS. The producer +// previously mapped EVERY unsuccessful read to GeneratedArtifactObservationRefused, so `the +// observation could not be made` and `the observation was made and the file is not there` shared +// one arm. They have opposite owners: a refusal names an authority to repair and reached NO +// verdict, while an absence IS a verdict about the repository tree and asks whether the artifact +// was ever meant to be committed. DESIGN carries the receipt for getting this wrong -- this gate's +// first red included a markdown file deleted ON PURPOSE, and the regenerate remedy would have +// silently resurrected it. +// +// IT IS ALSO DISTINCT FROM ExpectedArtifactObservationMissing, which is the adjacent confusion. +// That finding says the POPULATION has no row for an expected identity -- a completeness defect in +// the observer. This arm says the observer ran, produced a row, and the row reports the committed +// path is not on disk. Different execution provenance, different remedy. +// +// THE ABSENCE IS ESTABLISHED, NOT INFERRED. It is produced only where a directory LISTING succeeded +// and did not name the entry, never from a read failing -- because a read's success channel is +// exactly the channel that cannot separate absent from unreadable. `detail` carries the established +// absence's own rendering (which directory was listed, which name was not in it), so a consumer can +// say what it did not find rather than only that it found nothing. fn observation_artifact(o: GeneratedArtifactObservation) -> GeneratedArtifact { match o { @@ -41,7 +77,33 @@ fn observation_path(o: GeneratedArtifactObservation) -> String { } } -data generated_artifact_population_findings_note: String = "THE COMPLETE FAILURE POPULATION, AS A PRODUCT RATHER THAN A CHOICE, AND IT IS THE SOLE ASSESSMENT AUTHORITY. The three top-level conditions the population verdict conjoins can fail SIMULTANEOUSLY, so they are FIELDS of one result and not arms of a coproduct: an expected identity can be missing while an unrelated observation is unknown while a third artifact's bytes differ, and any shape that made these alternatives would report one and hide two.\n\nWHAT THIS REPLACES. The module previously answered one question at TWO INCOMPATIBLE GRAINS: assess_generated_artifact_population folded the whole population to a Bool, while first_failing_diagnostic_path returned ONE path -- priority-ordered across the three classes, each class itself a fold guarded by `if acc == \"\"` keeping only its first hit. Two collapses composed, one WITHIN a class and one ACROSS classes, so a tree carrying both a content drift and an unknown observation reported only the drift and the unknown stayed invisible until the drift was repaired and the gate re-run. At the measured ~187s per run that made a required gate an iterative discovery protocol -- run, repair one named artifact, run, discover the next -- while the gate already HELD the complete evidence and discarded all but one identity at the boundary where an operator needs it. The classes are not variants of one fault: a content difference means regenerate, a missing expected observation points at the observer or the population producer, a duplicate points at population construction or identity aliasing, and an unknown observation means a committed artifact no registry row claims. Different owners, different repairs.\n\nWHY THE BOOLEAN IS SAFE HERE. assess_generated_artifact_population survives as a PROJECTION of this carrier, not as a second computation beside it, so there is no fork that could disagree about which population failed. Findings retain GeneratedArtifact IDENTITY and not only a path, which is what the duplicate and unknown arms need -- a path-only roster is weakest exactly where the identity join is the thing that broke. Nothing here rereads or regenerates: every finding is derived from fields the producer already retained, which is what keeps the gate's measured read-only boundary intact." +// THE COMPLETE FAILURE POPULATION, AS A PRODUCT RATHER THAN A CHOICE, AND IT IS THE SOLE ASSESSMENT +// AUTHORITY. The three top-level conditions the population verdict conjoins can fail +// SIMULTANEOUSLY, so they are FIELDS of one result and not arms of a coproduct: an expected +// identity can be missing while an unrelated observation is unknown while a third artifact's bytes +// differ, and any shape that made these alternatives would report one and hide two. +// +// WHAT THIS REPLACES. The module previously answered one question at TWO INCOMPATIBLE GRAINS: +// assess_generated_artifact_population folded the whole population to a Bool, while +// first_failing_diagnostic_path returned ONE path -- priority-ordered across the three classes, +// each class itself a fold guarded by `if acc == ""` keeping only its first hit. Two collapses +// composed, one WITHIN a class and one ACROSS classes, so a tree carrying both a content drift and +// an unknown observation reported only the drift and the unknown stayed invisible until the drift +// was repaired and the gate re-run. At the measured ~187s per run that made a required gate an +// iterative discovery protocol -- run, repair one named artifact, run, discover the next -- while +// the gate already HELD the complete evidence and discarded all but one identity at the boundary +// where an operator needs it. The classes are not variants of one fault: a content difference means +// regenerate, a missing expected observation points at the observer or the population producer, a +// duplicate points at population construction or identity aliasing, and an unknown observation +// means a committed artifact no registry row claims. Different owners, different repairs. +// +// WHY THE BOOLEAN IS SAFE HERE. assess_generated_artifact_population survives as a PROJECTION of +// this carrier, not as a second computation beside it, so there is no fork that could disagree +// about which population failed. Findings retain GeneratedArtifact IDENTITY and not only a path, +// which is what the duplicate and unknown arms need -- a path-only roster is weakest exactly where +// the identity join is the thing that broke. Nothing here rereads or regenerates: every finding is +// derived from fields the producer already retained, which is what keeps the gate's measured +// read-only boundary intact. type ExpectedArtifactCardinalityFinding = ExpectedArtifactObservationMissing { artifact: GeneratedArtifact, path: String } @@ -84,7 +146,11 @@ type GeneratedArtifactPopulationFindings { observation_failures: List } -data observation_findings_note: String = "THE LEAF, DESCENDED. The former assess_generated_artifact_observation collapsed a refusal, a byte difference and a failed extra validation into one `false`; each is its own arm here. ONE OBSERVATION MAY YIELD MORE THAN ONE FINDING -- bytes differing and extra validation failing are independent facts about one artifact -- so this returns a list and never stops at the first, which would reintroduce the within-class collapse this cut removes one level down." +// THE LEAF, DESCENDED. The former assess_generated_artifact_observation collapsed a refusal, a byte +// difference and a failed extra validation into one `false`; each is its own arm here. ONE +// OBSERVATION MAY YIELD MORE THAN ONE FINDING -- bytes differing and extra validation failing are +// independent facts about one artifact -- so this returns a list and never stops at the first, +// which would reintroduce the within-class collapse this cut removes one level down. fn observation_findings(o: GeneratedArtifactObservation) -> List { match o { @@ -156,7 +222,15 @@ fn generated_artifact_population_findings_empty(f: GeneratedArtifactPopulationFi generated_artifact_population_findings_count(f: f) == 0 } -data assess_generated_artifact_population_note: String = "THE BOOLEAN PROJECTION, DERIVED. `expected` is the identity roster (committed_generated_artifacts(), passed in by the caller -- this module never calls it); `observations` is the retained receipt population. This is ONE CARRIER WITH A BOOLEAN PROJECTION rather than a second representation: it reads the finding population and asks whether it is empty, so pass/fail cannot disagree with the diagnostic about which population failed. The join underneath is by artifact_eq at identity grain, never a bare count equality (DESIGN section 5 oracle-independence ruling) -- a population that lost one artifact and duplicated another would count-match while missing the defect, which is exactly why Missing and Duplicated are separate arms rather than one ExpectedCardinalityWrong." +// THE BOOLEAN PROJECTION, DERIVED. `expected` is the identity roster +// (committed_generated_artifacts(), passed in by the caller -- this module never calls it); +// `observations` is the retained receipt population. This is ONE CARRIER WITH A BOOLEAN PROJECTION +// rather than a second representation: it reads the finding population and asks whether it is +// empty, so pass/fail cannot disagree with the diagnostic about which population failed. The join +// underneath is by artifact_eq at identity grain, never a bare count equality (DESIGN section 5 +// oracle-independence ruling) -- a population that lost one artifact and duplicated another would +// count-match while missing the defect, which is exactly why Missing and Duplicated are separate +// arms rather than one ExpectedCardinalityWrong. fn assess_generated_artifact_population(expected: List, observations: List) -> Bool { generated_artifact_population_findings_empty( @@ -203,7 +277,10 @@ fn render_section(title: String, lines: List) -> String { } } -data generated_artifact_population_findings_rendered_note: String = "RENDERS EVERY NONEMPTY CLASS IN ONE PASS, IN SECTIONS, because the classes carry different remedies and a flat roster would merge three owners into one list. The count prefix is derived from the same carrier the sections are, so a summary that disagrees with the body is unrepresentable rather than merely unlikely." +// RENDERS EVERY NONEMPTY CLASS IN ONE PASS, IN SECTIONS, because the classes carry different +// remedies and a flat roster would merge three owners into one list. The count prefix is derived +// from the same carrier the sections are, so a summary that disagrees with the body is +// unrepresentable rather than merely unlikely. fn generated_artifact_population_findings_rendered(f: GeneratedArtifactPopulationFindings) -> String { if generated_artifact_population_findings_empty(f: f) { "" } else { diff --git a/dag/gunbc/generated_artifact_boundary_seed_growth.dag b/dag/gunbc/generated_artifact_boundary_seed_growth.dag index 7b90efdf6aa..aa0202e3f04 100644 --- a/dag/gunbc/generated_artifact_boundary_seed_growth.dag +++ b/dag/gunbc/generated_artifact_boundary_seed_growth.dag @@ -5,7 +5,95 @@ import std.types { String } import gunbc.roadmap_model { RoadmapNodeId } import gunbc.seed_growth { SeedGrowthJustification } -data generated_artifact_boundary_seed_growth_note: String = "Seed-growth obligation for the host that adjudicates the committed generated-artifact population against its authority. Homed here, beside the obligation it declares, rather than inside gunbc.seed_growth_admission, which owns the ROSTER and the join.\n\nWHAT THE CHANGE IS. A required-CI phase, generated-artifact, asks gunbc.generated_artifact committed_generated_artifact_paths for the roster and gunbc.generated_artifact_emit generated_artifact_body_for_path for each body, reads what the tree holds at each path, and refuses on any divergence. This file is the HOST REALIZATION of that route: it resolves the .dag closure once, marshals the projection's three-variant answer into Rust, and adjudicates.\n\nWHY THE PRODUCER IS NOT WHAT GREW. Nothing about generation is authored here. generated_artifact_body_for_path is a pure .dag projection over the three authorities main_wet already folds -- the committed-artifact roster, artifact_path, and the single artifact_generate dispatch -- and it existed before this change with a real writer beside it (tools.generated_artifact_gate main_wet). What did not exist was a CALLER that asked it about the whole population. The growth is the asking, not the answering, and that is the whole reason this obligation is small enough to state: no generator, no roster, no per-artifact knowledge is duplicated in Rust. This file names no individual artifact, and adding an artifact to generated_artifact_registry enrols it with zero edits here.\n\nWHY RUST IS NEEDED AT ALL, which is the question the roster below owes an answer to. The phase must compare the projection's bytes against WHAT IS ON DISK, and reading the committed file is a host effect the required floor's hermetic envelope refuses by construction -- a .dag witness whose subject is that read is counted executed while its assertion never runs, which is the boundary DESIGN's CI paragraph already names beside the floor's counters. So the leaf is Rust for the same reason ingestion is. What was REJECTED, so the choice is visible rather than implied: running the existing drift gate (tools.generated_artifact_gate main) through the interpreter in Wet mode would need no host adjudicator, and it is refused for two independent reasons. It makes the interpreter load-bearing for a NEW required phase at the moment two lanes are deleting it; and the gate body it would run is the one that also reaches artifact_extra_valid's ci_yml_parses, so the required phase would acquire a second subject nobody asked it to check. The Hermetic context this file installs is the construction that forecloses that: a generator reaching a host effect during the projection REFUSES here rather than being carried out on its behalf.\n\nRUNG, STATED HONESTLY AND NOT INFLATED. The CLASS this phase guards -- a committed projection that no longer equals what its authority generates -- moves from OUTSIDE THE LADDER to MECHANICALLY PREVENTABLE. Outside rather than mitigatable because nothing could be attempted: from the 2026-08-15 floor cut until this phase, no route compared any non-Rust-mirror projection to anything, so there was no degradation to contain. It is NOT structurally guaranteed and this row does not claim it: a hand edit to DESIGN.md remains perfectly writable and safety depends on the phase executing. THIS FILE ITSELF sits at MITIGATABLE, for the reason v1_compiler.required_regen_host does: the marshaling of the projection's three variants is hand-written beside the .dag type rather than derived from it, so a fourth variant added to GeneratedArtifactPathBody would be met by this file's unknown-variant refusal rather than by a compile error. That refusal is the mitigation and it is deliberate -- an unknown shape is ignorance and refuses -- but it is a runtime wall, not construction.\n\nADMITTED AGAINST THE v1 FREEZE BY PURPOSE, on the test gunbc.v1_maintenance_standing v1_seed_standing states: does the change serve the v2 self-host program. It does, by the same route gunbc.parse_refusal_location_seed_growth and gunbc.floor_non_verdict_enrollment were admitted -- DESIGN.md is the authority every lane in that program plans against, and a DESIGN.md that has silently diverged from gunbc.design_document is premise contamination for all of them. That is measured rather than argued: this phase's first execution found docs/plans/realization-measurement-loop.md wrong since #9394, when the ContainerRuntime nickname was dissolved in the authority and the projection kept naming it, and gunbc#9392 had already found 2198 characters standing in DESIGN.md that no authority held. Classified against all five refused classes. NewLanguageBehavior: no -- the compile pipeline is byte-untouched; no diagnostic is added, moved or removed, and resolve, infer and emit are not edited. NewCompatibilityObligation: no -- nothing is kept working for an old caller, and the direction is the opposite: three declarations that lived privately in bin/claim_executor.rs are DELETED there and hoisted here, so the projection has one host caller and not two. NewEscapeHatchOrAdmissionRow: no, and the direction is opposite -- this adds a wall and removes nothing. There is no flag, env var or mode that skips it, and there is no write path in this file at all, which is the construction that keeps a checker from becoming a healer. SeedFeatureCompletion: no -- the seed COMPILER gains no capability; it compiles exactly what it compiled before. PublicSurfaceGrowth: this is the nearest, and it is checked rather than asserted -- generated_artifact_boundary_host.rs is wired by #[path] mod inside cli_run.rs, exactly as required_regen_host.rs and partition_crate_boundary_host.rs are, so it contributes no pub mod line to the emitted lib.rs and its SeedRetainedIntrinsicRegistration carries has_pub_mod: false. What grows is the SeedRetained roster, enumerated below.\n\nHAND-ITEM DELTA: +20, all enumerated below rather than counted, and ZERO of them uncitable -- there is not one impl block in the file, which is deliberate and cost something. The first cut carried four inherent methods, and every impl method is uncitable as a DeclarationRef because std.decl_ref offers WholeDeclaration or NamedField and neither names a method on an impl block, so they would have grown the class gunbc.seed_growth_admission reports as seed_growth_uncitable_item_keys. They are free functions instead. THE DELTA IS A NET FIGURE AND THE NET IS NOT +20 ACROSS THE CHANGE: bin/claim_executor.rs LOSES three declarations (GeneratedArtifactPathBody, generated_artifact_ctx, generated_artifact_body_for_path) which are hoisted rather than added, so the corpus-wide hand-item delta for those three is ZERO and only their new home changed. They are listed below because the roster names where an obligation LIVES, not what a diff invented. src/v1/stage0/src/cli_run.rs adds no declaration -- one #[path] mod line and one pub use -- so its disposition is ExistingSeedItemModified and listing it would net a modification into an addition census.\n\nWHAT THIS ROSTER DOES NOT GUARANTEE is what gunbc.seed_growth already discloses of every such roster: it is AUTHORED, not derived, so a later helper added to this same file escapes it silently. That completeness join belongs to the honest-frontier lane. What is claimed here is enumeration, not a denominator." +// Seed-growth obligation for the host that adjudicates the committed generated-artifact population +// against its authority. Homed here, beside the obligation it declares, rather than inside +// gunbc.seed_growth_admission, which owns the ROSTER and the join. +// +// WHAT THE CHANGE IS. A required-CI phase, generated-artifact, asks gunbc.generated_artifact +// committed_generated_artifact_paths for the roster and gunbc.generated_artifact_emit +// generated_artifact_body_for_path for each body, reads what the tree holds at each path, and +// refuses on any divergence. This file is the HOST REALIZATION of that route: it resolves the .dag +// closure once, marshals the projection's three-variant answer into Rust, and adjudicates. +// +// WHY THE PRODUCER IS NOT WHAT GREW. Nothing about generation is authored here. +// generated_artifact_body_for_path is a pure .dag projection over the three authorities main_wet +// already folds -- the committed-artifact roster, artifact_path, and the single artifact_generate +// dispatch -- and it existed before this change with a real writer beside it +// (tools.generated_artifact_gate main_wet). What did not exist was a CALLER that asked it about the +// whole population. The growth is the asking, not the answering, and that is the whole reason this +// obligation is small enough to state: no generator, no roster, no per-artifact knowledge is +// duplicated in Rust. This file names no individual artifact, and adding an artifact to +// generated_artifact_registry enrols it with zero edits here. +// +// WHY RUST IS NEEDED AT ALL, which is the question the roster below owes an answer to. The phase +// must compare the projection's bytes against WHAT IS ON DISK, and reading the committed file is a +// host effect the required floor's hermetic envelope refuses by construction -- a .dag witness +// whose subject is that read is counted executed while its assertion never runs, which is the +// boundary DESIGN's CI paragraph already names beside the floor's counters. So the leaf is Rust for +// the same reason ingestion is. What was REJECTED, so the choice is visible rather than implied: +// running the existing drift gate (tools.generated_artifact_gate main) through the interpreter in +// Wet mode would need no host adjudicator, and it is refused for two independent reasons. It makes +// the interpreter load-bearing for a NEW required phase at the moment two lanes are deleting it; +// and the gate body it would run is the one that also reaches artifact_extra_valid's ci_yml_parses, +// so the required phase would acquire a second subject nobody asked it to check. The Hermetic +// context this file installs is the construction that forecloses that: a generator reaching a host +// effect during the projection REFUSES here rather than being carried out on its behalf. +// +// RUNG, STATED HONESTLY AND NOT INFLATED. The CLASS this phase guards -- a committed projection +// that no longer equals what its authority generates -- moves from OUTSIDE THE LADDER to +// MECHANICALLY PREVENTABLE. Outside rather than mitigatable because nothing could be attempted: +// from the 2026-08-15 floor cut until this phase, no route compared any non-Rust-mirror projection +// to anything, so there was no degradation to contain. It is NOT structurally guaranteed and this +// row does not claim it: a hand edit to DESIGN.md remains perfectly writable and safety depends on +// the phase executing. THIS FILE ITSELF sits at MITIGATABLE, for the reason +// v1_compiler.required_regen_host does: the marshaling of the projection's three variants is +// hand-written beside the .dag type rather than derived from it, so a fourth variant added to +// GeneratedArtifactPathBody would be met by this file's unknown-variant refusal rather than by a +// compile error. That refusal is the mitigation and it is deliberate -- an unknown shape is +// ignorance and refuses -- but it is a runtime wall, not construction. +// +// ADMITTED AGAINST THE v1 FREEZE BY PURPOSE, on the test gunbc.v1_maintenance_standing +// v1_seed_standing states: does the change serve the v2 self-host program. It does, by the same +// route gunbc.parse_refusal_location_seed_growth and gunbc.floor_non_verdict_enrollment were +// admitted -- DESIGN.md is the authority every lane in that program plans against, and a DESIGN.md +// that has silently diverged from gunbc.design_document is premise contamination for all of them. +// That is measured rather than argued: this phase's first execution found +// docs/plans/realization-measurement-loop.md wrong since #9394, when the ContainerRuntime nickname +// was dissolved in the authority and the projection kept naming it, and gunbc#9392 had already +// found 2198 characters standing in DESIGN.md that no authority held. Classified against all five +// refused classes. NewLanguageBehavior: no -- the compile pipeline is byte-untouched; no diagnostic +// is added, moved or removed, and resolve, infer and emit are not edited. +// NewCompatibilityObligation: no -- nothing is kept working for an old caller, and the direction is +// the opposite: three declarations that lived privately in bin/claim_executor.rs are DELETED there +// and hoisted here, so the projection has one host caller and not two. +// NewEscapeHatchOrAdmissionRow: no, and the direction is opposite -- this adds a wall and removes +// nothing. There is no flag, env var or mode that skips it, and there is no write path in this file +// at all, which is the construction that keeps a checker from becoming a healer. +// SeedFeatureCompletion: no -- the seed COMPILER gains no capability; it compiles exactly what it +// compiled before. PublicSurfaceGrowth: this is the nearest, and it is checked rather than asserted +// -- generated_artifact_boundary_host.rs is wired by #[path] mod inside cli_run.rs, exactly as +// required_regen_host.rs and partition_crate_boundary_host.rs are, so it contributes no pub mod +// line to the emitted lib.rs and its SeedRetainedIntrinsicRegistration carries has_pub_mod: false. +// What grows is the SeedRetained roster, enumerated below. +// +// HAND-ITEM DELTA: +20, all enumerated below rather than counted, and ZERO of them uncitable -- +// there is not one impl block in the file, which is deliberate and cost something. The first cut +// carried four inherent methods, and every impl method is uncitable as a DeclarationRef because +// std.decl_ref offers WholeDeclaration or NamedField and neither names a method on an impl block, +// so they would have grown the class gunbc.seed_growth_admission reports as +// seed_growth_uncitable_item_keys. They are free functions instead. THE DELTA IS A NET FIGURE AND +// THE NET IS NOT +20 ACROSS THE CHANGE: bin/claim_executor.rs LOSES three declarations +// (GeneratedArtifactPathBody, generated_artifact_ctx, generated_artifact_body_for_path) which are +// hoisted rather than added, so the corpus-wide hand-item delta for those three is ZERO and only +// their new home changed. They are listed below because the roster names where an obligation LIVES, +// not what a diff invented. src/v1/stage0/src/cli_run.rs adds no declaration -- one #[path] mod +// line and one pub use -- so its disposition is ExistingSeedItemModified and listing it would net a +// modification into an addition census. +// +// WHAT THIS ROSTER DOES NOT GUARANTEE is what gunbc.seed_growth already discloses of every such +// roster: it is AUTHORED, not derived, so a later helper added to this same file escapes it +// silently. That completeness join belongs to the honest-frontier lane. What is claimed here is +// enumeration, not a denominator. data generated_artifact_boundary_seed_growth_justification: SeedGrowthJustification = SeedGrowthJustification { hand_authored_declarations: [ diff --git a/dag/gunbc/generated_artifact_emit.dag b/dag/gunbc/generated_artifact_emit.dag index 3d49754c59f..6fbc318b379 100644 --- a/dag/gunbc/generated_artifact_emit.dag +++ b/dag/gunbc/generated_artifact_emit.dag @@ -108,7 +108,15 @@ fn artifact_generate(a: GeneratedArtifact) -> ArtifactGenerationOutcome { } } -data artifact_extra_valid_note: String = "P0 (operator-directed, 2026-08-05; loyal-ram-550 review of #7823): takes the ALREADY-COMPUTED generation outcome rather than calling artifact_generate itself. The two variants whose extra check depends on generation success (Stage0EmitPlanGeneratedDagArtifact, V1InterpreterDispatchGeneratedRsArtifact) used to call artifact_generate(a: a) a second time here, discarding the content and keeping only success/failure — one Filesystem.Read, but TWO artifact_generate calls per artifact for those two variants, contradicting gunbc.generated_artifact_observation's 'one artifact_generate per artifact' claim. Every caller now supplies the outcome it already produced (observe_generated_artifact reuses its own match result); no second entry point to generation remains." +// P0 (operator-directed, 2026-08-05; loyal-ram-550 review of #7823): takes the ALREADY-COMPUTED +// generation outcome rather than calling artifact_generate itself. The two variants whose extra +// check depends on generation success (Stage0EmitPlanGeneratedDagArtifact, +// V1InterpreterDispatchGeneratedRsArtifact) used to call artifact_generate(a: a) a second time +// here, discarding the content and keeping only success/failure — one Filesystem.Read, but TWO +// artifact_generate calls per artifact for those two variants, contradicting +// gunbc.generated_artifact_observation's 'one artifact_generate per artifact' claim. Every caller +// now supplies the outcome it already produced (observe_generated_artifact reuses its own match +// result); no second entry point to generation remains. fn artifact_extra_valid(a: GeneratedArtifact, generated: ArtifactGenerationOutcome) -> Bool { match a { diff --git a/dag/gunbc/generated_artifact_merge_driver.dag b/dag/gunbc/generated_artifact_merge_driver.dag index ec0a97f1bac..93301b9aea0 100644 --- a/dag/gunbc/generated_artifact_merge_driver.dag +++ b/dag/gunbc/generated_artifact_merge_driver.dag @@ -1,12 +1,50 @@ module gunbc.generated_artifact_merge_driver -data generated_artifact_merge_driver_note: String = "THE MERGE DRIVER FOR merge=generated-artifact PATHS, AND WHY IT REFUSES INSTEAD OF ANSWERING `true`. The binding was `merge.generated-artifact.driver=true` — the shell builtin, which leaves the ours side in place and exits 0. Git then records the merge as CLEAN and the other side's bytes are gone with no signal. That is the absorbing fallback of DESIGN section 5 at the integration boundary: the driver's failure arm answered instead of refusing, and it destroyed the one fact worth keeping — that a provisional choice was made at all — so the deficit's frequency was zero by construction and nothing ranked it for fixing. MEASURED CLASS: PR #7836, twice in one session. Merging main into a branch that also touched src/v1/stage0/src/v1_compiler_infer.rs resolved the SEED to the branch side and dropped main's emit changes (103 lines, then 130 lines) while src/v1/04_infer.dag — the AUTHORITY — merged correctly and carried them. Git reported no conflict either time; the tree was left authority-ahead-of-seed, and the class surfaced a full CI cycle later as a red regen job, or not at all until someone happened to run regen_stage0 --verify. THE REFUSAL IS PRECISE, NOT A WIDEN, and this is measured rather than argued: git invokes a low-level merge driver ONLY when both sides changed the path relative to the merge base. Executed against git 2.x with a probe driver on a four-case matrix — one-sided theirs, one-sided ours, both sides identical, both sides different — the driver was invoked in exactly the fourth case; the first three merged clean with the correct content and never reached it. So driver invocation holds exactly when taking either side drops the other side's authority-derived content, and refusing there adds no stop to any merge that was previously correct. WHAT THE REFUSAL LEAVES BEHIND: the script does not write to the result file, so git keeps the ours side verbatim — clean bytes, NO conflict markers — and marks the path unmerged. The regenerators can therefore run against the worktree immediately, and a writer that would sweep the tree cannot commit marker text (there is none to sweep); the unmerged index stage is what the landed commit-writer admission already refuses on. RUNG (DESIGN section 4b), stated at its honest ceiling: this class moves from mitigatable — the regen gate detecting the bad transition a CI cycle after it exists — to MECHANICALLY PREVENTABLE at the merge boundary. It is NOT structural: the invalid state stays writable, because a resolver can still `git add` the provisional bytes without regenerating, and a clone that never converged the repo-local binding gets vanilla text merge. NEXT TRIGGER for the remaining climb, already a countable row rather than a new program: gunbc.commit_workflow commit_writer_binding_work — the DashboardAutocommit and heal-arm writers calling admit_commit_writer at their actual write boundary, whose unmerged-stage refusal is what turns this stop into one no writer can pass. Until those bindings land, an out-of-repository autocommit that stages and commits through the unmerged path defeats this refusal, and that is a stated residue, not an unnoticed one." +// THE MERGE DRIVER FOR merge=generated-artifact PATHS, AND WHY IT REFUSES INSTEAD OF ANSWERING +// `true`. The binding was `merge.generated-artifact.driver=true` — the shell builtin, which leaves +// the ours side in place and exits 0. Git then records the merge as CLEAN and the other side's +// bytes are gone with no signal. That is the absorbing fallback of DESIGN section 5 at the +// integration boundary: the driver's failure arm answered instead of refusing, and it destroyed the +// one fact worth keeping — that a provisional choice was made at all — so the deficit's frequency +// was zero by construction and nothing ranked it for fixing. MEASURED CLASS: PR #7836, twice in one +// session. Merging main into a branch that also touched src/v1/stage0/src/v1_compiler_infer.rs +// resolved the SEED to the branch side and dropped main's emit changes (103 lines, then 130 lines) +// while src/v1/04_infer.dag — the AUTHORITY — merged correctly and carried them. Git reported no +// conflict either time; the tree was left authority-ahead-of-seed, and the class surfaced a full CI +// cycle later as a red regen job, or not at all until someone happened to run regen_stage0 +// --verify. THE REFUSAL IS PRECISE, NOT A WIDEN, and this is measured rather than argued: git +// invokes a low-level merge driver ONLY when both sides changed the path relative to the merge +// base. Executed against git 2.x with a probe driver on a four-case matrix — one-sided theirs, +// one-sided ours, both sides identical, both sides different — the driver was invoked in exactly +// the fourth case; the first three merged clean with the correct content and never reached it. So +// driver invocation holds exactly when taking either side drops the other side's authority-derived +// content, and refusing there adds no stop to any merge that was previously correct. WHAT THE +// REFUSAL LEAVES BEHIND: the script does not write to the result file, so git keeps the ours side +// verbatim — clean bytes, NO conflict markers — and marks the path unmerged. The regenerators can +// therefore run against the worktree immediately, and a writer that would sweep the tree cannot +// commit marker text (there is none to sweep); the unmerged index stage is what the landed +// commit-writer admission already refuses on. RUNG (DESIGN section 4b), stated at its honest +// ceiling: this class moves from mitigatable — the regen gate detecting the bad transition a CI +// cycle after it exists — to MECHANICALLY PREVENTABLE at the merge boundary. It is NOT structural: +// the invalid state stays writable, because a resolver can still `git add` the provisional bytes +// without regenerating, and a clone that never converged the repo-local binding gets vanilla text +// merge. NEXT TRIGGER for the remaining climb, already a countable row rather than a new program: +// gunbc.commit_workflow commit_writer_binding_work — the DashboardAutocommit and heal-arm writers +// calling admit_commit_writer at their actual write boundary, whose unmerged-stage refusal is what +// turns this stop into one no writer can pass. Until those bindings land, an out-of-repository +// autocommit that stages and commits through the unmerged path defeats this refusal, and that is a +// stated residue, not an unnoticed one. data generated_artifact_merge_driver_script_directory: String = ".githooks" data generated_artifact_merge_driver_script_name: String = "generated-artifact-merge" -data generated_artifact_merge_driver_script_path_note: String = "THE PATH IS DERIVED FROM ITS PARTS, NOT PARSED BACK OUT OF ITSELF. A consumer that needs the directory to LIST it, and the entry name to look for inside that listing, would otherwise have to split this string on its last separator -- re-deriving by parsing something this module already knew structurally, where the parse can fail and the construction cannot. So the parts are the authority and the joined path is the projection. The path string is unchanged, which is the point: every existing consumer reads the same bytes." +// THE PATH IS DERIVED FROM ITS PARTS, NOT PARSED BACK OUT OF ITSELF. A consumer that needs the +// directory to LIST it, and the entry name to look for inside that listing, would otherwise have to +// split this string on its last separator -- re-deriving by parsing something this module already +// knew structurally, where the parse can fail and the construction cannot. So the parts are the +// authority and the joined path is the projection. The path string is unchanged, which is the +// point: every existing consumer reads the same bytes. data generated_artifact_merge_driver_script_path: String = concat( concat(generated_artifact_merge_driver_script_directory, "/"), @@ -15,7 +53,14 @@ data generated_artifact_merge_driver_script_path: String = concat( data generated_artifact_merge_driver_shell_program: String = "bash" -data generated_artifact_merge_driver_invocation_note: String = "The configured driver value invokes the script through `bash ` rather than executing it directly, so the binding does not depend on a file mode. A generated artifact is written by Filesystem.Write, which carries no executable-bit fact, and a driver that git could not spawn would fail the merge for a reason unrelated to the divergence it exists to report. The home is .githooks/ because that directory is already this repository's home for emitted repo-local Git integration scripts (the pre-commit hook there converges these same bindings); git invokes hooks by exact name, so a non-hook file beside them is inert. Placeholders are git's own: %O base, %A ours AND the result path git reads back, %B theirs, %P the pathname in the worktree." +// The configured driver value invokes the script through `bash ` rather than executing it +// directly, so the binding does not depend on a file mode. A generated artifact is written by +// Filesystem.Write, which carries no executable-bit fact, and a driver that git could not spawn +// would fail the merge for a reason unrelated to the divergence it exists to report. The home is +// .githooks/ because that directory is already this repository's home for emitted repo-local Git +// integration scripts (the pre-commit hook there converges these same bindings); git invokes hooks +// by exact name, so a non-hook file beside them is inert. Placeholders are git's own: %O base, %A +// ours AND the result path git reads back, %B theirs, %P the pathname in the worktree. fn generated_artifact_merge_driver_config_value_at(script_path: String) -> String { concat( @@ -28,7 +73,14 @@ fn generated_artifact_merge_driver_config_value() -> String { generated_artifact_merge_driver_config_value_at(script_path: generated_artifact_merge_driver_script_path) } -data generated_artifact_merge_driver_regen_command_parameter_note: String = "THE REGENERATION COMMAND IS A PARAMETER, NOT AN IMPORT, AND THE REASON IS THE ACYCLICITY LAW RATHER THAN TASTE. Its single authority is gunbc.ci_spec ci_heal_author_commit_regen_command, and importing it here would close a real cycle: gunbc.generated_artifact reads this module for the artifact path, and ci_spec reaches gunbc.generated_artifact through the heal roster. So the consumer that already sits downstream of both — gunbc.generated_artifact_emit — supplies it, and the string is still authored in exactly one place. Restating it here as a literal would have been the second representation this document forbids; taking it as a parameter keeps one authority and an acyclic graph." +// THE REGENERATION COMMAND IS A PARAMETER, NOT AN IMPORT, AND THE REASON IS THE ACYCLICITY LAW +// RATHER THAN TASTE. Its single authority is gunbc.ci_spec ci_heal_author_commit_regen_command, and +// importing it here would close a real cycle: gunbc.generated_artifact reads this module for the +// artifact path, and ci_spec reaches gunbc.generated_artifact through the heal roster. So the +// consumer that already sits downstream of both — gunbc.generated_artifact_emit — supplies it, and +// the string is still authored in exactly one place. Restating it here as a literal would have been +// the second representation this document forbids; taking it as a parameter keeps one authority and +// an acyclic graph. // STEP 3 IS NOT REDUNDANT WITH STEP 2. The regen route emits the seed by running a binary built FROM the seed, so a single pass can self-verify for the wrong reason: the binary that emitted the output predates the change it was emitting. The second build-and-verify is what makes the fixed point a claim about the merged authorities rather than about the stale binary. // THE RECIPE NAMED regen_stage0 UNTIL 2026-08-20, AND THAT BINARY WAS DELETED AT THE ROOT BY THE REGEN CUT. A repair recipe is printed at exactly the moment an author is blocked, so a recipe naming a deleted producer is the same stranding this repository just closed inside required-regen itself: the stop is real, and the only move it offers does not exist. Measured cost: it was followed, verbatim, by the session that landed the producer repair. The steps now name the surviving route, whose first pass WRITES the candidate tree before adjudicating it (v2.workflow.required_regen required_regen_run), which is what makes step 2 a producer instruction rather than a request to re-run a check. NOT SWEPT HERE, and named rather than left to be rediscovered: other regen_stage0 citations survive in the corpus (v1_source_audit_witness_test, stage0_regen_convergence_real_execution_witness_test, stage0_crates and workspace_members generated-file headers, several plan carriers). They are a census of their own; this fix covers the one recipe that strands an author mid-merge. This recipe is the author-facing repair for an unmerged generated projection; the CI transport recipe is gunbc.proactive_verification_ledger spine_regen_recipe and is deliberately not restated here. diff --git a/dag/gunbc/generated_artifact_observation.dag b/dag/gunbc/generated_artifact_observation.dag index 1078ed3971a..6ae6c9029d8 100644 --- a/dag/gunbc/generated_artifact_observation.dag +++ b/dag/gunbc/generated_artifact_observation.dag @@ -22,9 +22,27 @@ import v2.std.optional { Present, Absent } data live_tree_disposition: LiveTreeDisposition = ReadsLiveTree -data generated_artifact_observation_note: String = "P0 (operator-directed, 2026-08-05; module-split per loyal-ram-550 review of #7823): the prior shape called artifact_generate up to four times and re-read the committed file a second time per artifact across gate_ok/red_receipt_ok, and the fast .dag witness independently called artifact_generate three more times per artifact to fake a drift with a live perturb — the semantic graph asking the same expensive fact repeatedly, cost and correctness riding on incidental memoization (operator ruling). This module is the ONLY live producer: one Filesystem.Read, one artifact_generate, one artifact_extra_valid per artifact, captured once as a typed receipt. The carrier type and every pure leaf/group/diagnostic predicate live in the sibling module gunbc.generated_artifact_assessment, which cannot import Filesystem or the generator — purity by construction, not by convention. This module depends on that one (never the reverse), so nothing here can be reached by an assessment." +// P0 (operator-directed, 2026-08-05; module-split per loyal-ram-550 review of #7823): the prior +// shape called artifact_generate up to four times and re-read the committed file a second time per +// artifact across gate_ok/red_receipt_ok, and the fast .dag witness independently called +// artifact_generate three more times per artifact to fake a drift with a live perturb — the +// semantic graph asking the same expensive fact repeatedly, cost and correctness riding on +// incidental memoization (operator ruling). This module is the ONLY live producer: one +// Filesystem.Read, one artifact_generate, one artifact_extra_valid per artifact, captured once as a +// typed receipt. The carrier type and every pure leaf/group/diagnostic predicate live in the +// sibling module gunbc.generated_artifact_assessment, which cannot import Filesystem or the +// generator — purity by construction, not by convention. This module depends on that one (never the +// reverse), so nothing here can be reached by an assessment. -data directory_listing_row_note: String = "ONE LISTING PER DIRECTORY, NOT PER ARTIFACT, AND THAT IS A COST DECISION WITH A RULING BEHIND IT. Establishing that a committed file is ABSENT requires listing its directory -- a read cannot do it, because a read's success channel cannot separate absent from unreadable. But the roster puts many artifacts in the same directory (four in .github/workflows, three in .githooks, two each in dag/gunbc and src/v1/stage0/src), so listing per artifact would ask the host the same question repeatedly. This module's standing operator ruling is one Read, one artifact_generate, one artifact_extra_valid per artifact, with cost and correctness never riding on incidental memoization; a per-artifact listing would have violated its spirit while technically obeying its letter. So the distinct directories are listed once each and every artifact joins its own row." +// ONE LISTING PER DIRECTORY, NOT PER ARTIFACT, AND THAT IS A COST DECISION WITH A RULING BEHIND IT. +// Establishing that a committed file is ABSENT requires listing its directory -- a read cannot do +// it, because a read's success channel cannot separate absent from unreadable. But the roster puts +// many artifacts in the same directory (four in .github/workflows, three in .githooks, two each in +// dag/gunbc and src/v1/stage0/src), so listing per artifact would ask the host the same question +// repeatedly. This module's standing operator ruling is one Read, one artifact_generate, one +// artifact_extra_valid per artifact, with cost and correctness never riding on incidental +// memoization; a per-artifact listing would have violated its spirit while technically obeying its +// letter. So the distinct directories are listed once each and every artifact joins its own row. type DirectoryListingRow { directory: String @@ -55,7 +73,11 @@ fn observe_directory_listings(directories: List) -> List) -> GeneratedArtifactObservation { let loc = artifact_location(a: a) diff --git a/dag/gunbc/generated_projection_paths.dag b/dag/gunbc/generated_projection_paths.dag index 24bd7eb7e95..908b3b60834 100644 --- a/dag/gunbc/generated_projection_paths.dag +++ b/dag/gunbc/generated_projection_paths.dag @@ -105,7 +105,18 @@ fn deterministic_generated_projection_paths() -> GeneratedProjectionPathsOutcome // unset row for either would REMOVE working coverage. derived_hand_maintained_disposition_repo_paths() // is the existing authority for exactly that difference; this module joins it rather than // recomputing it. -data stage0_generated_source_complement_note: String = "Cross-authority receipt for the complement, measured at d311b1ceab: the stage0 source root holds 173 direct children and hand_maintained_stage0_filenames() claims 37, so the complement is 136; the emitter's own declaration (src/v1/stage0/src/emitted_population.rs) names 137 direct children. The two agree exactly except main.rs, which is the single emitter_produced_divergent_registrations row — a file the emitter emits and whose committed bytes deliberately diverge from it, so regeneration is NOT its recovery and the unset row is correct. Two independently derived populations agreeing on 136 of 137 is the evidence that the layout claim is the right complement basis. THE FILE HEADER IS NOT THE POPULATION and was tested and rejected as one: 135 files carry '// Generated by v1 compiler -- do not edit.' at that ref, and the header disagrees with the model in BOTH directions — main.rs and std_lens_verdict.rs carry it and are hand-maintained, while compiler_tests.rs, emitted_population.rs and v1_rt.rs lack it and are in the emitted-population manifest." +// Cross-authority receipt for the complement, measured at d311b1ceab: the stage0 source root holds +// 173 direct children and hand_maintained_stage0_filenames() claims 37, so the complement is 136; +// the emitter's own declaration (src/v1/stage0/src/emitted_population.rs) names 137 direct +// children. The two agree exactly except main.rs, which is the single +// emitter_produced_divergent_registrations row — a file the emitter emits and whose committed bytes +// deliberately diverge from it, so regeneration is NOT its recovery and the unset row is correct. +// Two independently derived populations agreeing on 136 of 137 is the evidence that the layout +// claim is the right complement basis. THE FILE HEADER IS NOT THE POPULATION and was tested and +// rejected as one: 135 files carry '// Generated by v1 compiler -- do not edit.' at that ref, and +// the header disagrees with the model in BOTH directions — main.rs and std_lens_verdict.rs carry it +// and are hand-maintained, while compiler_tests.rs, emitted_population.rs and v1_rt.rs lack it and +// are in the emitted-population manifest. type GeneratedProjectionMergeSetting = MergeGeneratedArtifact @@ -122,7 +133,12 @@ type GeneratedProjectionMergeRowsOutcome data stage0_generated_source_pattern: String = concat(stage0_src_repo_prefix, "*.rs") -data stage0_generated_source_pattern_note: String = "A slash-bearing Git attributes pattern does not match across '/', so this claims the DIRECT CHILDREN of the stage0 source root and leaves bin/, cli_run/ and module_path_index/ untouched. That is the correct scope and not a convenient one: every emitted stage0 source is a direct child (measured at d311b1ceab — zero of the emitter-declared sources sit in a subdirectory), and the three subdirectories are hand-maintained Rust the crate layout claim does not enumerate at file grain, so a wider glob would over-claim a population no authority here can take back out." +// A slash-bearing Git attributes pattern does not match across '/', so this claims the DIRECT +// CHILDREN of the stage0 source root and leaves bin/, cli_run/ and module_path_index/ untouched. +// That is the correct scope and not a convenient one: every emitted stage0 source is a direct child +// (measured at d311b1ceab — zero of the emitter-declared sources sit in a subdirectory), and the +// three subdirectories are hand-maintained Rust the crate layout claim does not enumerate at file +// grain, so a wider glob would over-claim a population no authority here can take back out. fn stage0_generated_projection_merge_rows() -> List { concat( diff --git a/dag/gunbc/generic_binder_field_projection_deficit.dag b/dag/gunbc/generic_binder_field_projection_deficit.dag index cd6c23b6210..fbc223d68d4 100644 --- a/dag/gunbc/generic_binder_field_projection_deficit.dag +++ b/dag/gunbc/generic_binder_field_projection_deficit.dag @@ -11,68 +11,60 @@ import std.dissolution { DissolutionCondition, unbound_dissolution } // A V2 INFERENCE DEFICIT, MEASURED BY EXECUTION, WITH ITS LOCATED POPULATION. // -// INVALID STATE: a binder introduced by destructuring a GENERIC coproduct -- -// `Present { value: l }` of `Optional`, `Accepted { value: l }` of -// `Outcome` -- does not carry a resolved element type at the point a FIELD -// is projected off it. `l.children` fails with `error type cascade` located at -// the projection's own offset. The SAME binder used in ARGUMENT position resolves -// correctly and the call typechecks: four claims in the located module pass doing -// exactly that, in the same file, against the same producers. +// INVALID STATE: a binder introduced by destructuring a GENERIC coproduct -- `Present { value: l }` +// of `Optional`, `Accepted { value: l }` of `Outcome` -- does not carry a resolved +// element type where a FIELD is projected off it. `l.children` fails with `error type cascade` +// located at the projection's own offset. The SAME binder in ARGUMENT position resolves correctly +// and the call typechecks: four claims in the located module do exactly that, in the same file, +// against the same producers. // -// HOW IT WAS FOUND: gunbc#8818, on the first execution the fold-lowering -// end-to-end witnesses had ever had. It is recorded here rather than left in that -// PR's prose because two live sites now carry a shape this deficit dictates, and a -// shape dictated by a deficit that nothing names is indistinguishable from a shape +// HOW IT WAS FOUND: gunbc#8818, on the first execution the fold-lowering end-to-end witnesses ever +// had. Recorded here rather than in that PR's prose because two live sites now carry a shape this +// deficit dictates, and a shape dictated by an unnamed deficit is indistinguishable from one // someone preferred. // -// HARM: the author's remedy is to move the projection into a function whose -// parameter is concretely typed, which is what both sites do. That is sound and it -// costs a named indirection per site. The real harm is not the indirection -- it is -// that the failure is attributed to the PROJECTION rather than to the binder whose -// type was never resolved, so the class presents as unrelated one-off type noise in -// whichever file hits it, exactly as gunbc.list_membership_operation_gap records -// for its own class. That is why this carrier states the attribution, not just the -// symptom. +// HARM: the author's remedy is to move the projection into a function whose parameter is concretely +// typed, which both sites do -- sound, at the cost of a named indirection per site. The real harm +// is that the failure is attributed to the PROJECTION rather than to the binder whose type was +// never resolved, so the class presents as unrelated one-off type noise in whichever file hits it, +// as gunbc.list_membership_operation_gap records for its own class. Hence this carrier states the +// attribution, not just the symptom. // -// RUNG FOUND AT: mitigatable. The failure is loud, typed and located -- it refuses -// rather than accepting a wrong projection -- so nothing silently goes wrong; what -// is unavailable is writing the direct form at all. +// RUNG FOUND AT: mitigatable. The failure is loud, typed and located -- it refuses rather than +// accepting a wrong projection -- so nothing silently goes wrong; what is unavailable is writing +// the direct form at all. // -// CEILING: structurally impossible, and the reason is that membership is decidable. -// The element type is present in the scrutinee's type at the match; nothing about -// resolving it at the binder is undecidable or external. This is a decidable class -// whose authority already exists, so it is neither a wall-after-grounding nor a -// ratchet -- it is unbuilt. +// CEILING: structurally impossible, because membership is decidable. The element type is present in +// the scrutinee's type at the match; nothing about resolving it at the binder is undecidable or +// external. A decidable class whose authority already exists: neither wall-after-grounding nor +// ratchet -- unbuilt. // -// NEXT-RUNG TRIGGER: v2 inference resolving a generic coproduct's type argument -// into the arm binder, so that a field projection off that binder is checked -// against the instantiated type. This carrier's dissolution is stated below and its -// located sites are the population that dissolves with it. +// NEXT-RUNG TRIGGER: v2 inference resolving a generic coproduct's type argument into the arm +// binder, so a field projection off that binder is checked against the instantiated type. This +// carrier's dissolution is stated below and its located sites are the population that dissolves +// with it. // -// WHY THERE IS NO WITNESS BESIDE THIS CARRIER, stated rather than left as an -// absence for a reader to read as an oversight. The first cut of this module -// carried a located-ness predicate and a fixture witness folding it. The predicate -// was validation restating what the carrier's own shape already guarantees: both -// `projected_field` and the `DeclarationRef` components are `NonEmptyStr`, and the -// attempt to author the discriminating RED row -- a site with an empty -// `projected_field` -- was REFUSED at resolve with a type mismatch. The invalid -// state has no constructor, so the check had nothing left to catch (DESIGN.md §5, -// construction over validation), and both the predicate and its witness are deleted -// rather than kept as a green that cannot go red. +// WHY THERE IS NO WITNESS BESIDE THIS CARRIER, stated so it is not read as an oversight. The first +// cut carried a located-ness predicate and a fixture witness folding it. The predicate was +// validation restating what the carrier's shape already guarantees: both `projected_field` and the +// `DeclarationRef` components are `NonEmptyStr`, and the attempt to author the discriminating RED +// row -- a site with an empty `projected_field` -- was REFUSED at resolve with a type mismatch. The +// invalid state has no constructor, so the check had nothing to catch (DESIGN.md §5, construction +// over validation), and both predicate and witness are deleted rather than kept as a green that +// cannot go red. // // Nor were these rows registered on the citation census's production pool (that census was -// `v2.lens.cited_symbol_resolution` until 2026-08-26 and is `v1_compiler.declaration_index` -// now; the reasoning below is unchanged by the move and is why neither one reaches them). That pool consumes only CITABLE refs, and both sites below are -// `OutsideDeclIndexTestWitnessModule` -- `v2.std.decl_index` `decl_facts` skips test -// `.dag` files by design -- so registering them would add exactly zero refs to a -// lens that is itself inert. The coverage disposition is carried on each row where -// it can be read, which is what `CitationIndexCoverage` exists for. +// `v2.lens.cited_symbol_resolution` until 2026-08-26 and is `v1_compiler.declaration_index` now; +// the reasoning is unchanged by the move and is why neither reaches them). That pool consumes only +// CITABLE refs, and both sites below are `OutsideDeclIndexTestWitnessModule` -- `v2.std.decl_index` +// `decl_facts` skips test `.dag` files by design -- so registering them would add zero refs to a +// lens that is itself inert. The coverage disposition is carried on each row where it can be read, +// which is what `CitationIndexCoverage` exists for. // -// WHAT THIS CARRIER DOES NOT ASSERT: it does not propose the repair's shape, and it -// does not claim the two sites below are the whole population. They are the sites -// this measurement located. A census over the corpus would be a different piece of -// work and reading two located sites as a total is the failure -// gunbc.list_membership_operation_gap's own header records for its first attempt. +// WHAT THIS CARRIER DOES NOT ASSERT: the repair's shape, or that the two sites below are the whole +// population. They are the sites this measurement located. A corpus census would be different work, +// and reading two located sites as a total is the failure gunbc.list_membership_operation_gap's own +// header records for its first attempt. type GenericBinderProjectionSite { site: DeclarationRef diff --git a/dag/gunbc/git_diff_change_window.dag b/dag/gunbc/git_diff_change_window.dag index 237590c38c7..7f703917f95 100644 --- a/dag/gunbc/git_diff_change_window.dag +++ b/dag/gunbc/git_diff_change_window.dag @@ -39,7 +39,11 @@ import std.content_hash { content_hash_combine_structural, content_hash_eq_structural, } -data git_diff_change_window_note: String = "Git realization of an exact affected-set comparison window. Producers observe merge-base(head, target_generation) in a Git worktree and persist a head- and window-identity-addressed receipt; consumers (gunbc.diff_baseline, floor_diff_observe) read the path named by GUNBC_DIFF_WINDOW_PATH. This module is Git-specific — other SCM realizations carry their own native receipts beneath a future compatibility envelope." +// Git realization of an exact affected-set comparison window. Producers observe merge-base(head, +// target_generation) in a Git worktree and persist a head- and window-identity-addressed receipt; +// consumers (gunbc.diff_baseline, floor_diff_observe) read the path named by +// GUNBC_DIFF_WINDOW_PATH. This module is Git-specific — other SCM realizations carry their own +// native receipts beneath a future compatibility envelope. data gunbc_diff_window_path_env: NonEmptyStr = "GUNBC_DIFF_WINDOW_PATH" diff --git a/dag/gunbc/gitattributes_emit.dag b/dag/gunbc/gitattributes_emit.dag index 7d2e30477d6..eec43afa1bc 100644 --- a/dag/gunbc/gitattributes_emit.dag +++ b/dag/gunbc/gitattributes_emit.dag @@ -14,11 +14,35 @@ type GitattributesGenerationOutcome = GitattributesGenerated { content: String } | GitattributesGenerationRefused -data gitattributes_emit_authority_note: String = "THE committed byte projection through which Git receives the relevant subset of the modeled repository-path policy (generated-file conflict policy charter, lane 2, operator-ruled 2026-08-01). Presentation-policy rows (linguist) and integration-policy rows (merge driver attribute) are orthogonal facts derived from separate authorities — generated provenance does not imply linguist-hidden, and neither row is hand-authored in the committed file. The merge-attribute population is gunbc.generated_projection_paths deterministic_generated_projection_merge_rows(), which joins TWO arms: the committed generated-artifact registry, at exact-path grain with duplicate paths refused before row emission; and the stage0 seed mirror, as a directory glob over the stage0 source root's direct children plus one unset row per hand-maintained member, ordered glob-first because the last matching attributes line wins. THIS SENTENCE PREVIOUSLY DESCRIBED AN ARM THAT DID NOT EXIST — it claimed a join with plan-derived stage0 Rust for months after the #8406 regen cut deleted gunbc.stage0_emit_plan at the root, while the population was in fact the registry alone and every emitted stage0 source carried no merge attribute at all. The prose was the only place the arm survived, which is why it is corrected here rather than quietly widened: a note asserting an arm nobody can execute is what let the gap stand. committed_generated_artifact_paths() remains the heal/registry authority. .gitattributes classifies itself through the same derivation with no handwritten self-row. The merge-driver DEFINITION lives in repository-local Git config (gunbc.repo_local_git_config), not in this file." - - -data generated_projection_paths_join_seam_note: String = "Seam history for the two-arm population. #7609 (operator sequence step 4, 2026-08-01) established deterministic_generated_projection_paths() as the registry roster joined with a stage0 arm derived from gunbc.stage0_emit_plan. The #8406 regen cut deleted that plan at the root and the arm went with it silently, because this consumer sat outside the deletion census. The arm is restored as a COMPLEMENT rather than a path list, for reasons carried at its construction site in gunbc.generated_projection_paths: a path list — hand-authored or derived — leaves the invalid state writable, and the only surviving derived population takes live git observations as inputs, which a committed byte projection may not depend on. Duplicate refusal remains fail-closed inside the registry arm (counted diagnostic), not silent dedup." - +// THE committed byte projection through which Git receives the relevant subset of the modeled +// repository-path policy (generated-file conflict policy charter, lane 2, operator-ruled +// 2026-08-01). Presentation-policy rows (linguist) and integration-policy rows (merge driver +// attribute) are orthogonal facts derived from separate authorities — generated provenance does not +// imply linguist-hidden, and neither row is hand-authored in the committed file. The +// merge-attribute population is gunbc.generated_projection_paths +// deterministic_generated_projection_merge_rows(), which joins TWO arms: the committed +// generated-artifact registry, at exact-path grain with duplicate paths refused before row +// emission; and the stage0 seed mirror, as a directory glob over the stage0 source root's direct +// children plus one unset row per hand-maintained member, ordered glob-first because the last +// matching attributes line wins. THIS SENTENCE PREVIOUSLY DESCRIBED AN ARM THAT DID NOT EXIST — it +// claimed a join with plan-derived stage0 Rust for months after the #8406 regen cut deleted +// gunbc.stage0_emit_plan at the root, while the population was in fact the registry alone and every +// emitted stage0 source carried no merge attribute at all. The prose was the only place the arm +// survived, which is why it is corrected here rather than quietly widened: a note asserting an arm +// nobody can execute is what let the gap stand. committed_generated_artifact_paths() remains the +// heal/registry authority. .gitattributes classifies itself through the same derivation with no +// handwritten self-row. The merge-driver DEFINITION lives in repository-local Git config +// (gunbc.repo_local_git_config), not in this file. + +// Seam history for the two-arm population. #7609 (operator sequence step 4, 2026-08-01) established +// deterministic_generated_projection_paths() as the registry roster joined with a stage0 arm +// derived from gunbc.stage0_emit_plan. The #8406 regen cut deleted that plan at the root and the +// arm went with it silently, because this consumer sat outside the deletion census. The arm is +// restored as a COMPLEMENT rather than a path list, for reasons carried at its construction site in +// gunbc.generated_projection_paths: a path list — hand-authored or derived — leaves the invalid +// state writable, and the only surviving derived population takes live git observations as inputs, +// which a committed byte projection may not depend on. Duplicate refusal remains fail-closed inside +// the registry arm (counted diagnostic), not silent dedup. data dag_linguist_presentation_policy_pattern: String = "**/*.dag" data dag_linguist_presentation_policy_attribute: String = "linguist-language=Rust" diff --git a/dag/gunbc/githooks/githooks_pre_commit_emit.dag b/dag/gunbc/githooks/githooks_pre_commit_emit.dag index 40e86a68589..ef813a83b09 100644 --- a/dag/gunbc/githooks/githooks_pre_commit_emit.dag +++ b/dag/gunbc/githooks/githooks_pre_commit_emit.dag @@ -24,7 +24,18 @@ fn emit_freshness_path_fn(label: String, globs: List) -> String { ) } -data githooks_pre_commit_note: String = "Commit-time fmt gate (operator request 2026-07-12: add fmt to the commit hook). The staged path set is matched against the same LocalTidyCargoFmtCheck trigger globs the pre-push hook and the CI rust gate consume — one fmt-relevance authority, three surfaces. Both commit-time and push-time hooks are CHECK-ONLY: on drift they refuse with the fix recipe instead of mutating the index or auto-committing (git add -u under a partially-staged tree would sweep unstaged edits into the commit — a silent widen; push-time auto-commit was retired in #6706 non-invasive gates, and #6720 carries the same check-only policy into the typed plan/Rust transport). dissolve-on: shell emission via the grammar path (the bash medium modeled as GrammarRelationRow rows read forward, superseding these concat-built shell string literals — same trigger as the githooks_pre_push_emit sibling; the medium-as-string debt is tracked once for the hook-emit family, here explicitly rather than implicitly via the sibling roster). Follow-on (not modeled here): a GitPreCommitHook CommitWorkflowSurface row binding this artifact into commit_workflow_realization_bindings." +// Commit-time fmt gate (operator request 2026-07-12: add fmt to the commit hook). The staged path +// set is matched against the same LocalTidyCargoFmtCheck trigger globs the pre-push hook and the CI +// rust gate consume — one fmt-relevance authority, three surfaces. Both commit-time and push-time +// hooks are CHECK-ONLY: on drift they refuse with the fix recipe instead of mutating the index or +// auto-committing (git add -u under a partially-staged tree would sweep unstaged edits into the +// commit — a silent widen; push-time auto-commit was retired in #6706 non-invasive gates, and #6720 +// carries the same check-only policy into the typed plan/Rust transport). dissolve-on: shell +// emission via the grammar path (the bash medium modeled as GrammarRelationRow rows read forward, +// superseding these concat-built shell string literals — same trigger as the githooks_pre_push_emit +// sibling; the medium-as-string debt is tracked once for the hook-emit family, here explicitly +// rather than implicitly via the sibling roster). Follow-on (not modeled here): a GitPreCommitHook +// CommitWorkflowSurface row binding this artifact into commit_workflow_realization_bindings. fn emit_pre_commit_staged_scan() -> String { concat( @@ -33,7 +44,11 @@ fn emit_pre_commit_staged_scan() -> String { ) } -data pre_commit_fmt_outcome_split_note: String = "Same three-way split as the pre-push sibling, from the one authority (gunbc.local_tidy_spec): exit 1 is the only status that reports drift in staged Rust, and any other non-zero status names cargo as unrunnable rather than blaming the staged work. The branches are spelled as explicit if-blocks instead of an and-list so the arm does not depend on bash errexit's suppression rules inside a && list." +// Same three-way split as the pre-push sibling, from the one authority (gunbc.local_tidy_spec): +// exit 1 is the only status that reports drift in staged Rust, and any other non-zero status names +// cargo as unrunnable rather than blaming the staged work. The branches are spelled as explicit +// if-blocks instead of an and-list so the arm does not depend on bash errexit's suppression rules +// inside a && list. fn emit_pre_commit_fmt_recipe_echo(outcome_recipe: String) -> String { concat(" echo \"[pre-commit] ", concat(outcome_recipe, "\" >&2\n exit 1\nfi\n")) diff --git a/dag/gunbc/githooks/githooks_pre_push_plan.dag b/dag/gunbc/githooks/githooks_pre_push_plan.dag index 8370218ff0a..179dbba9fba 100644 --- a/dag/gunbc/githooks/githooks_pre_push_plan.dag +++ b/dag/gunbc/githooks/githooks_pre_push_plan.dag @@ -32,7 +32,13 @@ type PrePushGateBinding { data pre_push_check_only_note: String = "Pre-push gates refuse with a fix recipe on drift (no auto-commit / auto-stage). Aligns with commit-hook check-only policy: failure arms refuse instead of mutating the tree silently." -data pre_push_fmt_recipe_defork_note: String = "CargoFmtCheck previously carried a pre-baked fail_recipe field holding a byte-identical copy of the row in gunbc.githooks_pre_push_emit — one string, two authorities (DESIGN section 3). The payload is deleted rather than repointed: a failure message is not a property of WHICH gate fired, it is a projection of the outcome that gate OBSERVED, so pre_push_fmt_outcome_recipe derives it from the exit status through the single authority in gunbc.local_tidy_spec. A pre-baked recipe on the binding is precisely what let a could-not-run outcome render a drift remedy." +// CargoFmtCheck previously carried a pre-baked fail_recipe field holding a byte-identical copy of +// the row in gunbc.githooks_pre_push_emit — one string, two authorities (DESIGN section 3). The +// payload is deleted rather than repointed: a failure message is not a property of WHICH gate +// fired, it is a projection of the outcome that gate OBSERVED, so pre_push_fmt_outcome_recipe +// derives it from the exit status through the single authority in gunbc.local_tidy_spec. A +// pre-baked recipe on the binding is precisely what let a could-not-run outcome render a drift +// remedy. fn pre_push_fmt_outcome(status: Int) -> LocalTidyFmtOutcome { local_tidy_fmt_outcome_of_exit_status(program: "cargo", status: status) diff --git a/dag/gunbc/githooks/githooks_repo_local_git_config_emit.dag b/dag/gunbc/githooks/githooks_repo_local_git_config_emit.dag index 0b2cef0e5fc..13ef85b89f5 100644 --- a/dag/gunbc/githooks/githooks_repo_local_git_config_emit.dag +++ b/dag/gunbc/githooks/githooks_repo_local_git_config_emit.dag @@ -24,7 +24,21 @@ data githooks_repo_local_git_config_emit_shell_emit_scaffold: Disposition = Scaf data githooks_repo_local_git_config_emit_dissolution_trigger: DissolutionCondition = unbound_dissolution(description: "🟡 dissolve-on: githooks_repo_local_git_config_emit_shell — hook-emitted git config argv lines are the foreign-executor interim for a runtime-absent site; DISSOLVES WHEN shell→intent bash grammar rows read forward emit the converge block from the same reconcile_install_argv authority without concat-built medium strings (DESIGN §4 one-grammar-both-directions; githooks_pre_push_emit medium disposition family).") -data githooks_repo_local_git_config_emit_quoting_note: String = "THE EMITTED LINE QUOTES EVERY TOKEN, AND THE DEFECT THAT FORCED IT WAS REACHABLE, NOT HYPOTHETICAL. This emitter formerly rendered the install argv through extdeps.git git_shell_join_argv, which joins already-spelled words with single spaces and — by that module's own standing ruling (git_pathspec_exclude_note) — is a join and must never become a quoter, because quoting belongs to the emitting realization, the only layer that knows the target shell. Nothing quoted here, so the rendering was correct only while every binding value happened to be one shell word. The moment merge.generated-artifact.driver stopped being the single word `true` and became a driver invocation with placeholders, the emitted hook line degenerated into `git config --local merge.generated-artifact.driver bash .githooks/generated-artifact-merge %O %A %B %P` — seven arguments where git expects two, so the self-healing converge block would have failed on every hook invocation while looking entirely ordinary in the diff. Rendering through extdeps.exec.command shell_command_render fixes the class rather than this value: every token is single-quoted with embedded apostrophes escaped (witnessed in test.claim.argv_command_render), so a value carrying spaces, quotes, or shell metacharacters is emitted correctly by construction and no future binding can reintroduce the defect by being multi-word." +// THE EMITTED LINE QUOTES EVERY TOKEN, AND THE DEFECT THAT FORCED IT WAS REACHABLE, NOT +// HYPOTHETICAL. This emitter formerly rendered the install argv through extdeps.git +// git_shell_join_argv, which joins already-spelled words with single spaces and — by that module's +// own standing ruling (git_pathspec_exclude_note) — is a join and must never become a quoter, +// because quoting belongs to the emitting realization, the only layer that knows the target shell. +// Nothing quoted here, so the rendering was correct only while every binding value happened to be +// one shell word. The moment merge.generated-artifact.driver stopped being the single word `true` +// and became a driver invocation with placeholders, the emitted hook line degenerated into `git +// config --local merge.generated-artifact.driver bash .githooks/generated-artifact-merge %O %A %B +// %P` — seven arguments where git expects two, so the self-healing converge block would have failed +// on every hook invocation while looking entirely ordinary in the diff. Rendering through +// extdeps.exec.command shell_command_render fixes the class rather than this value: every token is +// single-quoted with embedded apostrophes escaped (witnessed in test.claim.argv_command_render), so +// a value carrying spaces, quotes, or shell metacharacters is emitted correctly by construction and +// no future binding can reintroduce the defect by being multi-word. fn emit_install_binding_line(binding: RepoLocalGitConfigBinding) -> String { concat( diff --git a/dag/gunbc/guarantee_measurement.dag b/dag/gunbc/guarantee_measurement.dag index fb184d6dc12..77b043823fa 100644 --- a/dag/gunbc/guarantee_measurement.dag +++ b/dag/gunbc/guarantee_measurement.dag @@ -4,9 +4,43 @@ import std.types { String, NonEmptyStr, Bool, Int, List, CommitSha } import std.nat { Nat } import std.content_hash { Fnv1a64Structural, content_hash_atom, content_hash_tagged_structural, content_hash_combine_structural } -data guarantee_measurement_charter_note: String = "STAGE 0 OF THE GUARANTEE SPINE: the vocabulary probes and claims meet through, and nothing else (operator spine verdict 2026-08-01; roadmap node ladder-measurement-schema; gap analysis sec 12 Stage 0). Without this stage the probe corpus and the claims carrier each invent the receipt shape and meet in a protocol cycle - probes cannot emit rows the carrier has not defined, the carrier cannot derive dispositions from receipts that do not exist. So this module carries identity brands, the path row, the receipt row, and the joins that make a receipt REFUSE when it names an unknown subject - and deliberately nothing that claims anything about the compiler: no class population, no probe executes here, no disposition, no rung. Those arrive with ladder-probe-corpus and ladder-claims-carrier, which consume this vocabulary instead of minting temporary string keys." - -data guarantee_measurement_axis_discipline_note: String = "The axis enums are CLOSED AND MINIMAL, grounded on the paths real measurements exercise (DESIGN 4b names source-to-interpretation, source-to-each-emission-target, and phase-carrier-to-phase-carrier as independently measured paths): CompileAccept and RuntimeRun are the merged v1 walls' seams - RuntimeRun is deliberately realization-agnostic: the run happens IN the path's realization, so WHICH runtime (the interpreter arm or an emitted binary) is the realization axis's fact, never the boundary's, and an emitted-run path (the divergence probes' subject) is expressible rather than contradictory; InferToEval is the phase boundary gunbc#7485 landed (Eval refuses underived facts) and InferToTranslate is the boundary the containment workstream closes (operator verdict 2026-08-01: the fact stays grounding-was-not-derived, and the phase that consumes it is named by the PATH, never by a second diagnostic). A new variant lands WITH the first real path row that needs it, never speculatively - an earlier draft carried a generic PhaseCarrierAcceptance arm, deleted for exactly that reason: an unused axis variant is vocabulary claiming coverage nothing measures. The same rule deleted the compile_mode axis (review 46308): every landed control determines its pipeline from its boundary (the v1 walls' seams are v1, the phase boundaries are v2), so a stored mode was a second representation of a derivable fact (DESIGN 2) whose only writable novelty was the contradiction, and the axes that remain are fully orthogonal - the two constructible contradictions the review named are now unwritable, one dissolved by the RuntimeRun reading, one by this deletion. The axis returns as a stored fact WITH the first path row measured under two pipelines at one boundary, and enumerating valid families as a coproduct was rejected for that future: family rows restate the orthogonal axes per combination (the N-by-M shape DESIGN 2 folds into axes). GuaranteeTargetName is a receipt-vocabulary brand, not a language authority: it keeps a target name from being passed where a probe id belongs, and its grounding to the real per-language authority is deliberately deferred - dissolve-on the primitive-identity join reaching target-realization rows (v2-target-realization-gate lane), at which point this brand re-grounds on that authority and this sentence deletes." +// STAGE 0 OF THE GUARANTEE SPINE: the vocabulary probes and claims meet through, and nothing else +// (operator spine verdict 2026-08-01; roadmap node ladder-measurement-schema; gap analysis sec 12 +// Stage 0). Without this stage the probe corpus and the claims carrier each invent the receipt +// shape and meet in a protocol cycle - probes cannot emit rows the carrier has not defined, the +// carrier cannot derive dispositions from receipts that do not exist. So this module carries +// identity brands, the path row, the receipt row, and the joins that make a receipt REFUSE when it +// names an unknown subject - and deliberately nothing that claims anything about the compiler: no +// class population, no probe executes here, no disposition, no rung. Those arrive with +// ladder-probe-corpus and ladder-claims-carrier, which consume this vocabulary instead of minting +// temporary string keys. + +// The axis enums are CLOSED AND MINIMAL, grounded on the paths real measurements exercise (DESIGN +// 4b names source-to-interpretation, source-to-each-emission-target, and +// phase-carrier-to-phase-carrier as independently measured paths): CompileAccept and RuntimeRun are +// the merged v1 walls' seams - RuntimeRun is deliberately realization-agnostic: the run happens IN +// the path's realization, so WHICH runtime (the interpreter arm or an emitted binary) is the +// realization axis's fact, never the boundary's, and an emitted-run path (the divergence probes' +// subject) is expressible rather than contradictory; InferToEval is the phase boundary gunbc#7485 +// landed (Eval refuses underived facts) and InferToTranslate is the boundary the containment +// workstream closes (operator verdict 2026-08-01: the fact stays grounding-was-not-derived, and the +// phase that consumes it is named by the PATH, never by a second diagnostic). A new variant lands +// WITH the first real path row that needs it, never speculatively - an earlier draft carried a +// generic PhaseCarrierAcceptance arm, deleted for exactly that reason: an unused axis variant is +// vocabulary claiming coverage nothing measures. The same rule deleted the compile_mode axis +// (review 46308): every landed control determines its pipeline from its boundary (the v1 walls' +// seams are v1, the phase boundaries are v2), so a stored mode was a second representation of a +// derivable fact (DESIGN 2) whose only writable novelty was the contradiction, and the axes that +// remain are fully orthogonal - the two constructible contradictions the review named are now +// unwritable, one dissolved by the RuntimeRun reading, one by this deletion. The axis returns as a +// stored fact WITH the first path row measured under two pipelines at one boundary, and enumerating +// valid families as a coproduct was rejected for that future: family rows restate the orthogonal +// axes per combination (the N-by-M shape DESIGN 2 folds into axes). GuaranteeTargetName is a +// receipt-vocabulary brand, not a language authority: it keeps a target name from being passed +// where a probe id belongs, and its grounding to the real per-language authority is deliberately +// deferred - dissolve-on the primitive-identity join reaching target-realization rows +// (v2-target-realization-gate lane), at which point this brand re-grounds on that authority and +// this sentence deletes. type GuaranteeClassId = NonEmptyStr where brand("GuaranteeClassId") type GuaranteePathId = NonEmptyStr where brand("GuaranteePathId") @@ -35,7 +69,25 @@ type GuaranteePath { realization_target: PathRealization } -data probe_observation_note: String = "The RAW executed observation, never the judgment: dispositions (Unmeasured, BelowFloor, FrontierAccepted, OnLadder, OutsideModeledGuarantee) are DERIVED by the claims carrier from receipts, so storing one here would be the transcribed-rung defect the carrier stage exists to make unrepresentable. ProbeNotRunnable is a separate arm because could-not-measure is not a verdict about the subject (DESIGN 5: top-as-answer conflated with top-as-ignorance is the absorbing fallback) - a probe that failed to execute must never be readable as the subject passing or failing. diagnostic_class names a compiler diagnostic variant as prose at this stage; the diagnostic-taxonomy join is carrier-stage grounding, and a brand here would claim a join this stage does not own. On query shape (review 46075, review 46159): probe_observation_fold is the sum's canonical elimination (the std.change KeyedLeafVerdictFold idiom) and owns the ONLY match over ProbeObservation; observed_is_verdict derives through it, so a per-query hand-match has no form to accumulate - a new query is a ProbeObservationFold row. Review 46075's interim carry had named exactly this fold as its dissolution trigger with the claims carrier as second consumer; a catamorphism's shape is sum-determined, so the trigger executed with review 46159 rather than waiting, and the carrier's disposition derivation consumes the same fold when it lands. Counts are Nat because a diagnostic count is a cardinal fact (review 46287), measured with the same honesty as the brand seam: an executed control shows today's checker still ACCEPTING a negative at the Nat field, so the carrier states the cardinal and the refusal belongs to the numeric-refinement acceptance class the probe corpus carries - not yet a wall here." +// The RAW executed observation, never the judgment: dispositions (Unmeasured, BelowFloor, +// FrontierAccepted, OnLadder, OutsideModeledGuarantee) are DERIVED by the claims carrier from +// receipts, so storing one here would be the transcribed-rung defect the carrier stage exists to +// make unrepresentable. ProbeNotRunnable is a separate arm because could-not-measure is not a +// verdict about the subject (DESIGN 5: top-as-answer conflated with top-as-ignorance is the +// absorbing fallback) - a probe that failed to execute must never be readable as the subject +// passing or failing. diagnostic_class names a compiler diagnostic variant as prose at this stage; +// the diagnostic-taxonomy join is carrier-stage grounding, and a brand here would claim a join this +// stage does not own. On query shape (review 46075, review 46159): probe_observation_fold is the +// sum's canonical elimination (the std.change KeyedLeafVerdictFold idiom) and owns the ONLY match +// over ProbeObservation; observed_is_verdict derives through it, so a per-query hand-match has no +// form to accumulate - a new query is a ProbeObservationFold row. Review 46075's interim carry had +// named exactly this fold as its dissolution trigger with the claims carrier as second consumer; a +// catamorphism's shape is sum-determined, so the trigger executed with review 46159 rather than +// waiting, and the carrier's disposition derivation consumes the same fold when it lands. Counts +// are Nat because a diagnostic count is a cardinal fact (review 46287), measured with the same +// honesty as the brand seam: an executed control shows today's checker still ACCEPTING a negative +// at the Nat field, so the carrier states the cardinal and the refusal belongs to the +// numeric-refinement acceptance class the probe corpus carries - not yet a wall here. type ProbeObservation = RefusedTyped { diagnostic_class: NonEmptyStr, count: Nat } @@ -79,7 +131,11 @@ type MeasurementRevision = GitRevision { commit: CommitSha } | ContentFingerprint { digest: Fnv1a64Structural } -data measurement_revision_note: String = "subject_revision and harness_revision name what was measured, not interchangeable commit-shaped strings. GitRevision carries a cited git commit (the baseline stage's anchor). ContentFingerprint carries an fnv1a64 structural digest of measured subject or harness material — honest for SyntheticProgram witnesses until real revision anchors land; it must never be cast to CommitSha. Downstream consumers discriminate by variant rather than inferring family from hex width." +// subject_revision and harness_revision name what was measured, not interchangeable commit-shaped +// strings. GitRevision carries a cited git commit (the baseline stage's anchor). ContentFingerprint +// carries an fnv1a64 structural digest of measured subject or harness material — honest for +// SyntheticProgram witnesses until real revision anchors land; it must never be cast to CommitSha. +// Downstream consumers discriminate by variant rather than inferring family from hex width. type GuaranteeMeasurementReceipt { class: GuaranteeClassId @@ -93,7 +149,20 @@ type GuaranteeMeasurementReceipt { data receipt_identity_note: String = "Every field is REQUIRED - no Optional arms - so a receipt missing an identity component or a revision is unwritable at construction rather than caught by a check (DESIGN 5 construction over validation; the roadmap node red_control names this). The three revisions answer the three-way ambiguity the baseline stage names: subject_revision names what compiler artifacts were measured, harness_revision names what probe harness ran, probe_set_digest is the content address of the exact probe set - a baseline is the anchor's subject under today's harness and probe set, and without all three 'the baseline at commit X' names three different measurements. Each revision is a MeasurementRevision coproduct (GitRevision | ContentFingerprint) so git commits and content fingerprints are not conflated at the type. Enforcement of required-field construction is itself an open census class (floor-record-construction-wall); this module states the intent by shape and the wall, when it lands, is what makes the shape load-bearing corpus-wide." -data guarantee_registry_mechanism_note: String = "The registry MECHANISM without a registry POPULATION: uniqueness and exactly-one resolution are folds over caller-supplied rows, witnessed here against synthetic rows only. The real class and path populations arrive with the carrier and probe stages; this module supplying them would be Stage 0 claiming compiler coverage it does not have. Resolution mirrors gunbc.roadmap_identity: exactly-one, never first-pick - a duplicate registry answered with a plausible first row is the fabricated-output arm, and it fails at exactly the moment the registry is corrupt. The lookup key is the branded GuaranteePathId end-to-end, through the refusal variants, and join_receipt_to_path passes receipt.path without erasing it to String (review 46179). Measured honestly rather than assumed (DESIGN 4b: a brand is cosmetic until construction and acceptance enforce the distinction): today's checker ACCEPTS a bare String at both a branded parameter and a branded record field - two executed controls, both accepted - so this threading is model-correctness and erasure-removal, not yet a wall. Brand acceptance-enforcement is its own guarantee class for the probe corpus to carry (below floor, with those controls as its discriminating probes), and this seam hardens for free when that class climbs." +// The registry MECHANISM without a registry POPULATION: uniqueness and exactly-one resolution are +// folds over caller-supplied rows, witnessed here against synthetic rows only. The real class and +// path populations arrive with the carrier and probe stages; this module supplying them would be +// Stage 0 claiming compiler coverage it does not have. Resolution mirrors gunbc.roadmap_identity: +// exactly-one, never first-pick - a duplicate registry answered with a plausible first row is the +// fabricated-output arm, and it fails at exactly the moment the registry is corrupt. The lookup key +// is the branded GuaranteePathId end-to-end, through the refusal variants, and join_receipt_to_path +// passes receipt.path without erasing it to String (review 46179). Measured honestly rather than +// assumed (DESIGN 4b: a brand is cosmetic until construction and acceptance enforce the +// distinction): today's checker ACCEPTS a bare String at both a branded parameter and a branded +// record field - two executed controls, both accepted - so this threading is model-correctness and +// erasure-removal, not yet a wall. Brand acceptance-enforcement is its own guarantee class for the +// probe corpus to carry (below floor, with those controls as its discriminating probes), and this +// seam hardens for free when that class climbs. type GuaranteePathResolution = PathResolved { path: GuaranteePath } @@ -145,7 +214,11 @@ fn guarantee_probe_ids_unique(ids: List) -> Bool { distinct_string_count(values: ids |> map(i => i as String)) == count(ids) } -data probe_set_digest_note: String = "Content address of the exact probe set a receipt ran under, folded in the caller's list order: canonical ordering is the PROBE stage's obligation (it owns the probe roster and its canonical enumeration), so this fn is deterministic exactly when the caller's order is - stated rather than silently sorted here, because a sort would need an order authority this stage does not own and a host-order sort would be the determinism leak std.determinism refuses." +// Content address of the exact probe set a receipt ran under, folded in the caller's list order: +// canonical ordering is the PROBE stage's obligation (it owns the probe roster and its canonical +// enumeration), so this fn is deterministic exactly when the caller's order is - stated rather than +// silently sorted here, because a sort would need an order authority this stage does not own and a +// host-order sort would be the determinism leak std.determinism refuses. fn probe_set_digest(probe_ids: List) -> Fnv1a64Structural { fold(probe_ids, init: content_hash_atom(value: "guarantee-probe-set" as NonEmptyStr), f: fn(acc, id) { diff --git a/dag/gunbc/guarantee_probe_corpus.dag b/dag/gunbc/guarantee_probe_corpus.dag index abf810c632e..ffd702caad2 100644 --- a/dag/gunbc/guarantee_probe_corpus.dag +++ b/dag/gunbc/guarantee_probe_corpus.dag @@ -42,13 +42,62 @@ import gunbc.compile_diagnostic_census { data guarantee_probe_corpus_charter_note: String = "STAGE 1a SLICE 1 OF THE GUARANTEE SPINE (roadmap node ladder-probe-corpus): the landed wall controls from the THREE MERGED PRs gunbc#7519 (call-label-and-surplus), gunbc#7484 (method-established-surface + declared-conformance-ground-fragment), and gunbc#7485 (InferToEval self-grounding frontier) migrated onto gunbc.guarantee_measurement identities — MIGRATE, never duplicate beside the dark-suite originals. gunbc#7555 (InferToTranslate underived-refusal) is OPEN on main and introduces NO path/class/probe identities here — that lane lands its own registry rows with executing evidence when it merges. Each migrated row carries a GuaranteeClassId, GuaranteePathId, and GuaranteeProbeId; receipts name subject_revision, harness_revision, and probe_set_digest per the Stage-0 schema. guarantee_probe_dark_suite_dispositions names each Rust dark-suite witness retained until gap-analysis sec 11 item 9 finite classification — enrolled .dag probes are the floor authority; Rust copies are explicitly dispositioned, not silently parallel. This slice owns the identity registry, canonical probe ordering, and the census-to-observation bridge for the v1 compile path. v2 InferToEval executing evidence joins the registry from src/v2/test/claim/infer_self_grounding_wall_test.dag (frontier value-red and derived-green receipt joins). No disposition, no rung, no claims carrier — those arrive with ladder-claims-carrier. gunbc#7847 (call-missing-and-duplicate-wall, roadmap node rn_EXUHLON5V24YU7Z4XLJFWEZO33, duplicate-label slice) added call_duplicate_red_probe/call_duplicate_green_probe to the SAME call_label_surplus_class as #7519's mislabel/surplus rows — one seam (direct_call_shape_diags), a third refusal mode on it, not a new class. Its Rust dark-suite original is call_shape_duplicate_wall_witness, dispositioned below alongside call_shape_wall_witness." -data guarantee_probe_corpus_execution_status_note: String = "THIS REGISTRY'S WITNESSES DO NOT RUN IN CI, AND EVERY RUNG DERIVED FROM THEM MUST BE READ THROUGH THAT (measured 2026-08-22; gap analysis section 11 item 28). The required floor DISCOVERS a witness file declaring live_tree_disposition = ReadsLiveTree and then DECLINES it before the fold — v2.workflow.required_floor RequiredFloorDisposition DeclinedLiveTree, reported as declined_live, 830 identities on run 32553487573. Every carrier that executes this registry declares exactly that, TRUTHFULLY: compile_dag_diagnostic_census resolves its synthetic source against build_module_path_index_from_witness_roots, which walks the live checkout, and the witnesses filesystem_read their harness sources besides. Five carriers and 100 identities of guarantee evidence sit in that arm: test.claim.guarantee_probe_corpus_witness (50), test.claim.sole_constructor_completeness_audit_probe_test (26), v2.test.claim.infer_self_grounding_wall_test (12), test.claim.compile_diagnostic_census_witness (8), test.claim.optional_cast_wall_witness (4). DO NOT REPAIR THIS BY RETYPING A DECLARATION. The declarations are correct; a SubstrateInputsOnly relabel would be a fabricated fact about a live-tree read and would corrupt affected-set selection eligibility, which is the question live_tree_disposition actually owns. The specimen that proves the arm is a stale PREDICTION rather than a capability limit: test.claim.match_exhaustiveness_coproduct_witness calls the SAME builtin, declares SubstrateInputsOnly, and executes and passes on the floor today — same host call, opposite declaration, opposite fate, and that mis-declaration is currently the only reason any census-compiling probe runs at all. THE DIRECT-CALL-ARGUMENT-TYPE ROWS ABOVE DO EXECUTE, and a claim here that they did not is RETRACTED (measured 2026-08-22, run 32586093086: expected-red-roster-join roster=207 still_red=207 not_evaluated=0). Their probes do not run from this registry's declined witness; they run from test.claim.direct_call_argument_type_witness, which this file's witness merely IMPORTS the ids of for a roster assertion — an import is evidence of visibility, never authority for where a name lives. What is true, and sharper, is WHY that file executes: it declares SubstrateInputsOnly while calling compile_dag_diagnostic_census, which is the declaration gunbc.compile_diagnostic_census's own live-tree note already adjudicated as a mis-declaration for transport_script_wall_compile_red_test. Two specimens now, the second authored after the first was adjudicated, and between them they are the only reason any census-compiling probe executes. Either those files are mis-declared or that note is wrong; the answer changes this arm's population and belongs to the arm's owner. DISSOLVES when the DeclinedLiveTree arm is deleted at the root, which required_floor's own header already stages and prices (run 32345970386, 626/783 passing under execution); that deletion is sequenced behind the merge queue and was UNOWNED as of 2026-08-22." - -data guarantee_probe_corpus_path_scope_note: String = "TWO LANDED PATHS IN THE CANONICAL REGISTRY: v1-compile-rust is subject_grain SyntheticProgram, acceptance_boundary CompileAccept, realization_target EmittedTarget { language: rust } — the path compile_diagnostic_census measures. v2-infer-eval is the same subject grain with acceptance_boundary InferToEval and realization_target InterpreterRealization — the path gunbc#7485's GroundingNotDerived frontier diagnostic lives on. A receipt keyed to one path must not claim coverage the other measures (DESIGN 4b minimum-across-paths discipline)." - -data guarantee_probe_corpus_probe_order_note: String = "CANONICAL PROBE ORDERING OBLIGATION (probe_set_digest note): guarantee_probe_ids lists the slice-1 MIGRATED probes in stable lexicographic order by branded id string. Callers must not re-sort; the roster order here IS the probe-set digest authority for this stage." - -data guarantee_probe_corpus_revision_note: String = "subject_revision and harness_revision are REQUIRED on every receipt as MeasurementRevision (GitRevision | ContentFingerprint). build_v1_compile_receipt and build_v2_infer_eval_receipt take a path-typed probe row and derive probe_set_digest from row.probe only (single-probe execution in slice 1). Until the baseline stage supplies anchor git commits, witness fixtures mint ContentFingerprint revisions from measured content: synthetic_program_subject_revision fingerprints compiled source text; synthetic_infer_eval_subject_revision_from_node fingerprints the executed Node fixture via v2.std.node.content_hash; v1_compile_harness_revision and v2_infer_eval_harness_revision fingerprint filesystem_read harness source material (v1_compile_harness_source_paths / v2_infer_eval_harness_source_paths) plus the registry row's ProbeExpectation — never symbol-name spines or prose scope notes. dissolve-on ladder-baseline stage naming real GitRevision subject_revision/harness_revision commits." +// THIS REGISTRY'S WITNESSES DO NOT RUN IN CI, AND EVERY RUNG DERIVED FROM THEM MUST BE READ THROUGH +// THAT (measured 2026-08-22; gap analysis section 11 item 28). The required floor DISCOVERS a +// witness file declaring live_tree_disposition = ReadsLiveTree and then DECLINES it before the fold +// — v2.workflow.required_floor RequiredFloorDisposition DeclinedLiveTree, reported as +// declined_live, 830 identities on run 32553487573. Every carrier that executes this registry +// declares exactly that, TRUTHFULLY: compile_dag_diagnostic_census resolves its synthetic source +// against build_module_path_index_from_witness_roots, which walks the live checkout, and the +// witnesses filesystem_read their harness sources besides. Five carriers and 100 identities of +// guarantee evidence sit in that arm: test.claim.guarantee_probe_corpus_witness (50), +// test.claim.sole_constructor_completeness_audit_probe_test (26), +// v2.test.claim.infer_self_grounding_wall_test (12), test.claim.compile_diagnostic_census_witness +// (8), test.claim.optional_cast_wall_witness (4). DO NOT REPAIR THIS BY RETYPING A DECLARATION. The +// declarations are correct; a SubstrateInputsOnly relabel would be a fabricated fact about a +// live-tree read and would corrupt affected-set selection eligibility, which is the question +// live_tree_disposition actually owns. The specimen that proves the arm is a stale PREDICTION +// rather than a capability limit: test.claim.match_exhaustiveness_coproduct_witness calls the SAME +// builtin, declares SubstrateInputsOnly, and executes and passes on the floor today — same host +// call, opposite declaration, opposite fate, and that mis-declaration is currently the only reason +// any census-compiling probe runs at all. THE DIRECT-CALL-ARGUMENT-TYPE ROWS ABOVE DO EXECUTE, and +// a claim here that they did not is RETRACTED (measured 2026-08-22, run 32586093086: +// expected-red-roster-join roster=207 still_red=207 not_evaluated=0). Their probes do not run from +// this registry's declined witness; they run from test.claim.direct_call_argument_type_witness, +// which this file's witness merely IMPORTS the ids of for a roster assertion — an import is +// evidence of visibility, never authority for where a name lives. What is true, and sharper, is WHY +// that file executes: it declares SubstrateInputsOnly while calling compile_dag_diagnostic_census, +// which is the declaration gunbc.compile_diagnostic_census's own live-tree note already adjudicated +// as a mis-declaration for transport_script_wall_compile_red_test. Two specimens now, the second +// authored after the first was adjudicated, and between them they are the only reason any +// census-compiling probe executes. Either those files are mis-declared or that note is wrong; the +// answer changes this arm's population and belongs to the arm's owner. DISSOLVES when the +// DeclinedLiveTree arm is deleted at the root, which required_floor's own header already stages and +// prices (run 32345970386, 626/783 passing under execution); that deletion is sequenced behind the +// merge queue and was UNOWNED as of 2026-08-22. + +// TWO LANDED PATHS IN THE CANONICAL REGISTRY: v1-compile-rust is subject_grain SyntheticProgram, +// acceptance_boundary CompileAccept, realization_target EmittedTarget { language: rust } — the path +// compile_diagnostic_census measures. v2-infer-eval is the same subject grain with +// acceptance_boundary InferToEval and realization_target InterpreterRealization — the path +// gunbc#7485's GroundingNotDerived frontier diagnostic lives on. A receipt keyed to one path must +// not claim coverage the other measures (DESIGN 4b minimum-across-paths discipline). + +// CANONICAL PROBE ORDERING OBLIGATION (probe_set_digest note): guarantee_probe_ids lists the +// slice-1 MIGRATED probes in stable lexicographic order by branded id string. Callers must not +// re-sort; the roster order here IS the probe-set digest authority for this stage. + +// subject_revision and harness_revision are REQUIRED on every receipt as MeasurementRevision +// (GitRevision | ContentFingerprint). build_v1_compile_receipt and build_v2_infer_eval_receipt take +// a path-typed probe row and derive probe_set_digest from row.probe only (single-probe execution in +// slice 1). Until the baseline stage supplies anchor git commits, witness fixtures mint +// ContentFingerprint revisions from measured content: synthetic_program_subject_revision +// fingerprints compiled source text; synthetic_infer_eval_subject_revision_from_node fingerprints +// the executed Node fixture via v2.std.node.content_hash; v1_compile_harness_revision and +// v2_infer_eval_harness_revision fingerprint filesystem_read harness source material +// (v1_compile_harness_source_paths / v2_infer_eval_harness_source_paths) plus the registry row's +// ProbeExpectation — never symbol-name spines or prose scope notes. dissolve-on ladder-baseline +// stage naming real GitRevision subject_revision/harness_revision commits. fn synthetic_program_subject_revision(source: String) -> MeasurementRevision { ContentFingerprint { digest: content_hash_atom(value: source as NonEmptyStr) } @@ -297,7 +346,11 @@ fn harness_digest_from_material_and_expectation( ) } -data v1_compile_harness_source_paths_note: String = "v1_compile_harness_source_paths is the measured authority for compile_dag_diagnostic_census harness revision: probe-corpus bridge, guarantee_measurement folds, census vocabulary, the host builtin seam, and the v1 CompileAccept→Rust pipeline stages whose edits change which diagnostics a synthetic subject provokes. dissolve-on ladder-baseline stage supplies GitRevision harness anchors." +// v1_compile_harness_source_paths is the measured authority for compile_dag_diagnostic_census +// harness revision: probe-corpus bridge, guarantee_measurement folds, census vocabulary, the host +// builtin seam, and the v1 CompileAccept→Rust pipeline stages whose edits change which diagnostics +// a synthetic subject provokes. dissolve-on ladder-baseline stage supplies GitRevision harness +// anchors. fn v1_compile_harness_source_paths() -> List { [ @@ -410,7 +463,11 @@ type DarkSuiteWitnessDisposition rust_retained_until: NonEmptyStr, } -data guarantee_probe_dark_suite_disposition_note: String = "Slice 1 MIGRATES landed wall REDs into enrolled .dag probes without dissolving the dark Rust suite (gap-analysis sec 11 item 9 operator ruling): each row names the executing .dag probe(s), the retained Rust witness they replace on the floor, and the finite-classification gate that must disposition the Rust copy before deletion. MIGRATE never duplicate means one enrolled measurement authority per class — not silent parallel maintenance forever." +// Slice 1 MIGRATES landed wall REDs into enrolled .dag probes without dissolving the dark Rust +// suite (gap-analysis sec 11 item 9 operator ruling): each row names the executing .dag probe(s), +// the retained Rust witness they replace on the floor, and the finite-classification gate that must +// disposition the Rust copy before deletion. MIGRATE never duplicate means one enrolled measurement +// authority per class — not silent parallel maintenance forever. fn guarantee_probe_dark_suite_dispositions() -> List { [ diff --git a/dag/gunbc/hand_lens_host_bridge_scaffold_index.dag b/dag/gunbc/hand_lens_host_bridge_scaffold_index.dag index 1d39a867660..2d6fa24391b 100644 --- a/dag/gunbc/hand_lens_host_bridge_scaffold_index.dag +++ b/dag/gunbc/hand_lens_host_bridge_scaffold_index.dag @@ -1,6 +1,5 @@ module gunbc.hand_lens_host_bridge_scaffold_index - data transport_script_position_host_bridge_dissolution_trigger: Disposition = Scaffold { dissolves_to: SingleAuthority, bind: DeclarationRef { @@ -10,7 +9,16 @@ data transport_script_position_host_bridge_dissolution_trigger: Disposition = Sc } } -data extdeps_external_authority_host_bridge_alias_walk_coverage_note: String = "SCOPE OF THE ROW BELOW, made explicit because two reviews read the seam as unregistered (reviews 50394, 50418). The registered host seam is cli_run.rs::extdeps_external_authority_module_facts, and the alias-following helpers added beneath it — extdeps_import_home_for_symbol, external_authority_alias_symbol_name, external_authority_anchor_from_data_body, project_external_authority_named_data, try_resolve_extdeps_module_source_path, and the ExternalAuthorityAnchorProjection Refused arm — are PRIVATE HELPERS OF THAT ONE SEAM, not new seams: every production call site is inside its subtree (the only other references are its own unit tests). They dissolve together with it, on the trigger below, so a second HostBridgeScaffoldRow would be a parallel registration of one fact rather than a missing one (DESIGN §3). The capability they add is following an anchor through an import alias instead of requiring a literal body in every extdeps module." +// SCOPE OF THE ROW BELOW, made explicit because two reviews read the seam as unregistered (reviews +// 50394, 50418). The registered host seam is cli_run.rs::extdeps_external_authority_module_facts; +// the alias-following helpers beneath it — extdeps_import_home_for_symbol, +// external_authority_alias_symbol_name, external_authority_anchor_from_data_body, +// project_external_authority_named_data, try_resolve_extdeps_module_source_path, and the +// ExternalAuthorityAnchorProjection Refused arm — are PRIVATE HELPERS OF THAT ONE SEAM, not new +// seams: every production call site is inside its subtree (the only other references are its own +// unit tests). They dissolve with it on the trigger below, so a second HostBridgeScaffoldRow would +// be a parallel registration of one fact (DESIGN §3). The capability they add is following an +// anchor through an import alias instead of requiring a literal body in every extdeps module. data extdeps_external_authority_host_bridge_dissolution_trigger: Disposition = Scaffold { dissolves_to: SingleAuthority, diff --git a/dag/gunbc/host/host_assimilation_program.dag b/dag/gunbc/host/host_assimilation_program.dag index f30db91b417..2b95f5170ab 100644 --- a/dag/gunbc/host/host_assimilation_program.dag +++ b/dag/gunbc/host/host_assimilation_program.dag @@ -44,9 +44,30 @@ import gunbc.host_standup { } import product.placement_supply { HostIdentity } -data host_assimilation_program_note: String = "WHY THIS EXISTS: host_standup_spine is ONE universal list, and gunbc.host_phase_status crossed every enrolled host with all of it. That was correct while every enrolled host was the same kind of thing -- srv1 through srv4 are Ampere Altra CI runner hosts, provisioned through BMC and virtual media, hosting GitHub Actions runner slots and a build cache. It stops being correct the moment a host of a DIFFERENT KIND is enrolled: two NVIDIA DGX Spark inference appliances (srv5, srv6) have no BMC to converge, no virtual-media OS install, no runner user, no Actions slots and no build cache, so crossing them with the universal spine would manufacture obligations that can never be satisfied and can never honestly be marked converged. A fleet-completeness number computed over invented obligations is worse than no number. THE CORRECTION IS PROGRAM SCOPING, NOT AN APPLICABILITY VERDICT: an Applicable-or-NotApplicable arm on the cell would keep the false cells and label them, which is the parallel-representation shape (DESIGN section 3) and leaves every consumer to remember to filter. A host's participation instead SELECTS which obligations exist at all, so an obligation that does not apply is not a cell with a label -- it is not a cell." - -data host_participation_axis_note: String = "Participation is what a host DOES for the fleet, and it is a LIST because the axes are independent rather than exclusive: nothing in the model forbids a host that both runs CI and serves inference, and a coproduct would have forced a third fused variant the first time that happened. It is deliberately NOT a host KIND enum (AltraRunner | SparkAppliance) -- kind is hardware, participation is role, and binding obligations to hardware would mean a future Altra dedicated to serving inherits BMC obligations because of what it is made of rather than what it is for. srv5 and srv6 stay opaque host identities for the same reason: the identity survives a role change or a hardware replacement, and DGX Spark remains a product fact recorded elsewhere, never part of the host's permanent name." +// WHY THIS EXISTS: host_standup_spine is ONE universal list, and gunbc.host_phase_status crossed +// every enrolled host with all of it. That was correct while every enrolled host was the same kind +// of thing -- srv1 through srv4 are Ampere Altra CI runner hosts, provisioned through BMC and +// virtual media, hosting GitHub Actions runner slots and a build cache. It stops being correct the +// moment a host of a DIFFERENT KIND is enrolled: two NVIDIA DGX Spark inference appliances (srv5, +// srv6) have no BMC to converge, no virtual-media OS install, no runner user, no Actions slots and +// no build cache, so crossing them with the universal spine would manufacture obligations that can +// never be satisfied and can never honestly be marked converged. A fleet-completeness number +// computed over invented obligations is worse than no number. THE CORRECTION IS PROGRAM SCOPING, +// NOT AN APPLICABILITY VERDICT: an Applicable-or-NotApplicable arm on the cell would keep the false +// cells and label them, which is the parallel-representation shape (DESIGN section 3) and leaves +// every consumer to remember to filter. A host's participation instead SELECTS which obligations +// exist at all, so an obligation that does not apply is not a cell with a label -- it is not a +// cell. + +// Participation is what a host DOES for the fleet, and it is a LIST because the axes are +// independent rather than exclusive: nothing in the model forbids a host that both runs CI and +// serves inference, and a coproduct would have forced a third fused variant the first time that +// happened. It is deliberately NOT a host KIND enum (AltraRunner | SparkAppliance) -- kind is +// hardware, participation is role, and binding obligations to hardware would mean a future Altra +// dedicated to serving inherits BMC obligations because of what it is made of rather than what it +// is for. srv5 and srv6 stay opaque host identities for the same reason: the identity survives a +// role change or a hardware replacement, and DGX Spark remains a product fact recorded elsewhere, +// never part of the host's permanent name. type FleetParticipation = CiRunnerParticipation @@ -68,9 +89,24 @@ type HostEnrollmentIntent { participation: List } -data host_phase_participation_classification_note: String = "The one place a standup phase is bound to the participations that require it, kept HERE rather than as a field on HostStandupStep on purpose. Adding the field would reshape fourteen rows in host_standup.dag, which is load-bearing, and that module's own gap-ledger note has already ruled that its row-reshaping change stays separable from its status-axis change so the two are reviewable apart -- this is the third change in that neighbourhood and it obeys the same ruling. The match below is EXHAUSTIVE over both closed phase enums, so a newly added phase does not silently default into or out of any program: the compiler refuses until its participations are stated. That is the construction half (DESIGN section 5) -- there is no unclassified phase to detect after the fact, because an unclassified phase does not compile." - -data host_phase_prefix_participation_note: String = "Every PREFIX phase is CI-runner only. BMC credential convergence and virtual-media OS installation are the Ampere Altra provisioning path and have no analogue on an appliance that self-provisions through its own first-boot flow; HostConvergePolicyDerived is grouped with them because the policy it derives is that installation path's policy. A DGX Spark absolutely HAS arrival obligations -- physical binding, network convergence, baseline observation -- but they are its OWN obligations, authored where that arrival is modeled, and inheriting a differently-shaped host's provisioning steps because they sound generic is precisely the false-obligation defect this module exists to prevent." +// The one place a standup phase is bound to the participations that require it, kept HERE rather +// than as a field on HostStandupStep on purpose. Adding the field would reshape fourteen rows in +// host_standup.dag, which is load-bearing, and that module's own gap-ledger note has already ruled +// that its row-reshaping change stays separable from its status-axis change so the two are +// reviewable apart -- this is the third change in that neighbourhood and it obeys the same ruling. +// The match below is EXHAUSTIVE over both closed phase enums, so a newly added phase does not +// silently default into or out of any program: the compiler refuses until its participations are +// stated. That is the construction half (DESIGN section 5) -- there is no unclassified phase to +// detect after the fact, because an unclassified phase does not compile. + +// Every PREFIX phase is CI-runner only. BMC credential convergence and virtual-media OS +// installation are the Ampere Altra provisioning path and have no analogue on an appliance that +// self-provisions through its own first-boot flow; HostConvergePolicyDerived is grouped with them +// because the policy it derives is that installation path's policy. A DGX Spark absolutely HAS +// arrival obligations -- physical binding, network convergence, baseline observation -- but they +// are its OWN obligations, authored where that arrival is modeled, and inheriting a +// differently-shaped host's provisioning steps because they sound generic is precisely the +// false-obligation defect this module exists to prevent. fn prefix_phase_participations(p: HostStandupPrefixPhase) -> List { match p { @@ -81,7 +117,16 @@ fn prefix_phase_participations(p: HostStandupPrefixPhase) -> List List { match p { @@ -129,7 +174,13 @@ type HostAssimilationProgram { steps: List } -data host_assimilation_program_derivation_note: String = "The program is DERIVED from the spine by participation, never a second hand-authored step list. A hand-authored inference program would be a parallel representation of the spine and would drift the first time a phase changed, which is the defect the host_standup gap ledger already records against itself. Deriving also gives the refactor its identity guarantee for free: a host enrolled for CI participation alone selects every step in the spine, because every phase in the spine today exists to serve runner hosts, so srv1 through srv4 keep byte-identical cells rather than merely similar ones. That identity is asserted by an executing witness rather than argued here." +// The program is DERIVED from the spine by participation, never a second hand-authored step list. A +// hand-authored inference program would be a parallel representation of the spine and would drift +// the first time a phase changed, which is the defect the host_standup gap ledger already records +// against itself. Deriving also gives the refactor its identity guarantee for free: a host enrolled +// for CI participation alone selects every step in the spine, because every phase in the spine +// today exists to serve runner hosts, so srv1 through srv4 keep byte-identical cells rather than +// merely similar ones. That identity is asserted by an executing witness rather than argued here. fn assimilation_program_for(intent: HostEnrollmentIntent) -> HostAssimilationProgram { HostAssimilationProgram { @@ -138,9 +189,26 @@ fn assimilation_program_for(intent: HostEnrollmentIntent) -> HostAssimilationPro } } -data host_enrollment_participation_nonempty_note: String = "Enrollment participation is authored as a non-empty list at every fleet enrollment site today. An empty list would yield an empty program identity (invalid NonEmptyStr) and zero completion obligations; a NonEmptyList carrier on HostEnrollmentIntent is deferred until enrollment validation walls land." - -data fleet_participation_canonical_roster_note: String = "Participation has SET semantics but is carried as a List, and identity must not be able to tell the two apart. Step selection already reads the list as membership -- step_serves_any asks only whether SOME participation claims the step -- so order and repetition are invisible to which obligations exist. Deriving the identity from the raw list did let them through: [CiRunner, InferenceServing] and [InferenceServing, CiRunner] and [CiRunner, CiRunner, InferenceServing] select exactly the same steps while producing three different identity strings, which is a second representation of one program (DESIGN section 3) and would fork any consumer that keys on identity. The correction projects through a FIXED roster instead of reading the caller's list order: filtering the roster by membership yields at most one occurrence of each participation, in one order, whatever the caller wrote. That is the construction half -- a duplicate or a reordering cannot reach the identity, so there is no post-hoc canonicalization check to remember to call. The roster's own completeness is the residue this cannot construct away, and it is walled by an EXHAUSTIVE match in fleet_participation_in_canonical_roster: a newly added participation does not compile until it is stated there, and its witness reds if it is stated but left out of the roster." +// Enrollment participation is authored as a non-empty list at every fleet enrollment site today. An +// empty list would yield an empty program identity (invalid NonEmptyStr) and zero completion +// obligations; a NonEmptyList carrier on HostEnrollmentIntent is deferred until enrollment +// validation walls land. + +// Participation has SET semantics but is carried as a List, and identity must not be able to tell +// the two apart. Step selection already reads the list as membership -- step_serves_any asks only +// whether SOME participation claims the step -- so order and repetition are invisible to which +// obligations exist. Deriving the identity from the raw list did let them through: [CiRunner, +// InferenceServing] and [InferenceServing, CiRunner] and [CiRunner, CiRunner, InferenceServing] +// select exactly the same steps while producing three different identity strings, which is a second +// representation of one program (DESIGN section 3) and would fork any consumer that keys on +// identity. The correction projects through a FIXED roster instead of reading the caller's list +// order: filtering the roster by membership yields at most one occurrence of each participation, in +// one order, whatever the caller wrote. That is the construction half -- a duplicate or a +// reordering cannot reach the identity, so there is no post-hoc canonicalization check to remember +// to call. The roster's own completeness is the residue this cannot construct away, and it is +// walled by an EXHAUSTIVE match in fleet_participation_in_canonical_roster: a newly added +// participation does not compile until it is stated there, and its witness reds if it is stated but +// left out of the roster. data fleet_participation_canonical_roster: List = [ CiRunnerParticipation, @@ -171,7 +239,12 @@ fn program_step_count(program: HostAssimilationProgram) -> Int { program.steps.length() } -data ci_runner_program_identity_note: String = "The CI-runner program is the identity anchor for the refactor: it must select the WHOLE spine, because every phase authored to date exists to serve runner hosts. If a later phase is added that no participation claims, or one that CI does not claim, this equality breaks and the witness reds -- which is the intended alarm, not a maintenance burden. It is the discriminating control that distinguishes 'the refactor preserved srv1-srv4 exactly' from 'the refactor produced a plausible-looking matrix'." +// The CI-runner program is the identity anchor for the refactor: it must select the WHOLE spine, +// because every phase authored to date exists to serve runner hosts. If a later phase is added that +// no participation claims, or one that CI does not claim, this equality breaks and the witness reds +// -- which is the intended alarm, not a maintenance burden. It is the discriminating control that +// distinguishes 'the refactor preserved srv1-srv4 exactly' from 'the refactor produced a +// plausible-looking matrix'. data ci_runner_enrollment_participation: List = [CiRunnerParticipation] @@ -182,7 +255,10 @@ data both_participation_enrollment_participation: List = [ CiRunnerParticipation, ] -data assimilation_completion_inputs_union_note: String = "Completion inputs derive as the union over enrolled participations, deduplicated by AssimilationCompleteInput identity, not as a priority chain over participation kinds. A host enrolled for both CI and inference must carry every obligation either role requires; ordering in the participation list must not drop CI-only inputs when inference is listed first." +// Completion inputs derive as the union over enrolled participations, deduplicated by +// AssimilationCompleteInput identity, not as a priority chain over participation kinds. A host +// enrolled for both CI and inference must carry every obligation either role requires; ordering in +// the participation list must not drop CI-only inputs when inference is listed first. fn assimilation_completion_input_list_contains( inputs: List, @@ -225,7 +301,16 @@ fn assimilation_completion_inputs_union( ) } -data assimilation_completion_input_classification_note: String = "Completion obligations are derived from participation beside the phase classification tables above, not as fixed fields on AssimilationCompleteGate. CiRunnerParticipation requires all four AssimilationCompleteInput arms because a CI runner host must be node-registered, resources-present, green-place coherent via the gunbc gate (not merely P3 modeled), and runner-slot healthy. InferenceServingParticipation requires only NodeRegistered and ResourcesPresent: an inference appliance has no GitHub Actions runner slot to health-check and no dashboard green-place pin to coherence-check, so those inputs are not obligations and cannot be supplied without fabricating evidence. GreenPlacePinCoherent and RunnerSlotHealthy are CI-only because they gate facts about executing CI work (dashboard pin coherence, Actions slot enforcement), not generic host readiness." +// Completion obligations are derived from participation beside the phase classification tables +// above, not as fixed fields on AssimilationCompleteGate. CiRunnerParticipation requires all four +// AssimilationCompleteInput arms because a CI runner host must be node-registered, +// resources-present, green-place coherent via the gunbc gate (not merely P3 modeled), and +// runner-slot healthy. InferenceServingParticipation requires only NodeRegistered and +// ResourcesPresent: an inference appliance has no GitHub Actions runner slot to health-check and no +// dashboard green-place pin to coherence-check, so those inputs are not obligations and cannot be +// supplied without fabricating evidence. GreenPlacePinCoherent and RunnerSlotHealthy are CI-only +// because they gate facts about executing CI work (dashboard pin coherence, Actions slot +// enforcement), not generic host readiness. data shared_completion_inputs: List = [ NodeRegistered, diff --git a/dag/gunbc/host/host_authorized_keys_reconcile.dag b/dag/gunbc/host/host_authorized_keys_reconcile.dag index f63b1ff7ef9..a7c0349e813 100644 --- a/dag/gunbc/host/host_authorized_keys_reconcile.dag +++ b/dag/gunbc/host/host_authorized_keys_reconcile.dag @@ -16,7 +16,19 @@ import gunbc.membership_reconcile { type HostAuthorizedKeysMemberAt = NonEmptyStr -data host_authorized_keys_reconcile_authority_note: String = "Instantiates the grain-agnostic gunbc.membership_reconcile spine for one host's ~/.ssh/authorized_keys, keyed by SSH key IDENTITY. This is the durable fix for the srv3 first-contact gap (fleet_subsumption_manual_gaps A1/B2) AND for every future key rotation, both instances of the same silent drift: authorized_keys is materialized once at install and never re-converged against fleet_ssh_access.fleet_authorized_keys, so a key added to the model after a host was installed never reaches that host. srv3 is the live proof: installed via the pre-#7027 lane before fleet_automation_ssh_key existed, so its ubuntu authorized_keys carries the operator macbook key (operator reach) but NOT fleet-automation (workflow reach) — observed 2026-07-24. This module does NOT fork the spine: member = SshPublicKey, and the (key_of, key_eq, value_eq, ownership_of) bundle below is passed to the ONE membership_reconcile fn (a Realization — one fn, N bundles; a forked reconcile would mean the genericity bought nothing). key_of returns the key MATERIAL (the cryptographic identity), never the whole line, so a comment-only drift is Modified (re-upsert the line), not Remove+Add (which would delete then re-add the key)." +// Instantiates the grain-agnostic gunbc.membership_reconcile spine for one host's +// ~/.ssh/authorized_keys, keyed by SSH key IDENTITY. This is the durable fix for the srv3 +// first-contact gap (fleet_subsumption_manual_gaps A1/B2) AND for every future key rotation, both +// instances of the same silent drift: authorized_keys is materialized once at install and never +// re-converged against fleet_ssh_access.fleet_authorized_keys, so a key added to the model after a +// host was installed never reaches that host. srv3 is the live proof: installed via the pre-#7027 +// lane before fleet_automation_ssh_key existed, so its ubuntu authorized_keys carries the operator +// macbook key (operator reach) but NOT fleet-automation (workflow reach) — observed 2026-07-24. +// This module does NOT fork the spine: member = SshPublicKey, and the (key_of, key_eq, value_eq, +// ownership_of) bundle below is passed to the ONE membership_reconcile fn (a Realization — one fn, +// N bundles; a forked reconcile would mean the genericity bought nothing). key_of returns the key +// MATERIAL (the cryptographic identity), never the whole line, so a comment-only drift is Modified +// (re-upsert the line), not Remove+Add (which would delete then re-add the key). fn authorized_key_identity(k: SshPublicKey) -> NonEmptyStr { k.material @@ -36,7 +48,18 @@ fn authorized_key_value_eq(a: SshPublicKey, b: SshPublicKey) -> Bool { && (a.comment == b.comment) } -data fleet_owned_key_materials_note: String = "The registry of key materials the FLEET manages — the ownership authority the R5 teardown-owned-only wall reads for a Removed key. DERIVED from fleet_authorized_keys (single authority, never a hand-copied material list), so today it is exactly the two declared fleet keys. A key NOT in this registry (an out-of-band key like node-orch@srv2 or briansrls@srv1 hand-added in the node-orch bootstrap era) has NO ownership record -> ownership_of returns Absent -> OwnershipUnknown -> the spine REFUSES to tear it down (fail-closed; we never delete a key we did not place). ROTATION EXTENSION (dissolution trigger, not built here): when a fleet key rotates, its retired material must be appended to a retired-owned list so the Removed old key resolves Owned -> teardown-eligible; until that lands, a rotated-out fleet key drops out of this derived registry and is REFUSED rather than removed — a safe leak, not a silent delete. That extension is what makes the Owned teardown arm non-degenerate; today every teardown-refusal is OwnershipUnknown by construction, which is honest for the srv3 case (zero teardowns needed)." +// The registry of key materials the FLEET manages — the ownership authority the R5 +// teardown-owned-only wall reads for a Removed key. DERIVED from fleet_authorized_keys (single +// authority, never a hand-copied material list), so today it is exactly the two declared fleet +// keys. A key NOT in this registry (an out-of-band key like node-orch@srv2 or briansrls@srv1 +// hand-added in the node-orch bootstrap era) has NO ownership record -> ownership_of returns Absent +// -> OwnershipUnknown -> the spine REFUSES to tear it down (fail-closed; we never delete a key we +// did not place). ROTATION EXTENSION (dissolution trigger, not built here): when a fleet key +// rotates, its retired material must be appended to a retired-owned list so the Removed old key +// resolves Owned -> teardown-eligible; until that lands, a rotated-out fleet key drops out of this +// derived registry and is REFUSED rather than removed — a safe leak, not a silent delete. That +// extension is what makes the Owned teardown arm non-degenerate; today every teardown-refusal is +// OwnershipUnknown by construction, which is honest for the srv3 case (zero teardowns needed). fn fleet_owned_key_materials() -> List { map(fleet_authorized_keys, k => k.material) @@ -50,7 +73,15 @@ fn authorized_key_ownership(k: SshPublicKey) -> Ownership? { } } -data host_authorized_keys_reconcile_note: String = "desired = fleet_authorized_keys (the model's declared key set); observed = a live read of the host's authorized_keys (an OBSERVATION effect, staged — the fixtures below are point-in-time reads, not the live provider). The apply-grain POLICY is deliberately NOT resolved in this module: authorized_keys is an EVERYTHING-ON-HOST observed scope (foreign keys are normal, not anomalies), which is exactly the scope where membership_reconcile's wholesale-refuse policy (membership_effects) would be wrong — a foreign key's refusal would block our owned upsert. That is the conscious wholesale-vs-siblings-continue call the spine reserves to a human (membership_effects_wholesale_refuse_policy_note); this module produces the PLAN grain only and leaves the apply projection to that decision." +// desired = fleet_authorized_keys (the model's declared key set); observed = a live read of the +// host's authorized_keys (an OBSERVATION effect, staged — the fixtures below are point-in-time +// reads, not the live provider). The apply-grain POLICY is deliberately NOT resolved in this +// module: authorized_keys is an EVERYTHING-ON-HOST observed scope (foreign keys are normal, not +// anomalies), which is exactly the scope where membership_reconcile's wholesale-refuse policy +// (membership_effects) would be wrong — a foreign key's refusal would block our owned upsert. That +// is the conscious wholesale-vs-siblings-continue call the spine reserves to a human +// (membership_effects_wholesale_refuse_policy_note); this module produces the PLAN grain only and +// leaves the apply projection to that decision. fn host_authorized_keys_reconcile(observed: List) -> MembershipPlan { host_authorized_keys_reconcile_with_ownership( @@ -75,7 +106,12 @@ fn host_authorized_keys_reconcile_with_ownership( ) } -data srv3_observed_authorized_keys_note: String = "OBSERVED, not intended: a point-in-time read of ubuntu@192.168.1.221 ~/.ssh/authorized_keys, run by the operator from the macbook 2026-07-24 (the macbook key is the one credential that reaches srv3 today). node-orch@srv2 and briansrls@srv1 are out-of-band keys the fleet does not own; macbook-m4 is reused from fleet_ssh_access (single authority) since it is byte-identical to the declared operator key. This is a fixture standing in for the staged live observation provider, marked so it is never mistaken for a desired/intent row." +// OBSERVED, not intended: a point-in-time read of ubuntu@192.168.1.221 ~/.ssh/authorized_keys, run +// by the operator from the macbook 2026-07-24 (the macbook key is the one credential that reaches +// srv3 today). node-orch@srv2 and briansrls@srv1 are out-of-band keys the fleet does not own; +// macbook-m4 is reused from fleet_ssh_access (single authority) since it is byte-identical to the +// declared operator key. This is a fixture standing in for the staged live observation provider, +// marked so it is never mistaken for a desired/intent row. data srv3_observed_node_orch_srv2_key: SshPublicKey = SshPublicKey { algorithm: Ed25519, @@ -95,7 +131,11 @@ data srv3_observed_authorized_keys: List = [ operator_macbook_ssh_key, ] -data srv4_observed_authorized_keys_note: String = "OBSERVED read of ubuntu@192.168.1.196 ~/.ssh/authorized_keys (operator, macbook, 2026-07-24). srv4 was installed via #7027 with fleet_authorized_keys in the seed, so it carries EXACTLY the two declared fleet keys and nothing foreign — the CONVERGED control: reconciling it against desired yields an empty plan (0 upsert / 0 teardown / 0 refusal). Both keys reused from fleet_ssh_access (single authority)." +// OBSERVED read of ubuntu@192.168.1.196 ~/.ssh/authorized_keys (operator, macbook, 2026-07-24). +// srv4 was installed via #7027 with fleet_authorized_keys in the seed, so it carries EXACTLY the +// two declared fleet keys and nothing foreign — the CONVERGED control: reconciling it against +// desired yields an empty plan (0 upsert / 0 teardown / 0 refusal). Both keys reused from +// fleet_ssh_access (single authority). data srv4_observed_authorized_keys: List = [ operator_macbook_ssh_key, diff --git a/dag/gunbc/host/host_axis_caps.dag b/dag/gunbc/host/host_axis_caps.dag index 20f4dad146a..9bb0cdf2835 100644 --- a/dag/gunbc/host/host_axis_caps.dag +++ b/dag/gunbc/host/host_axis_caps.dag @@ -268,7 +268,10 @@ fn runner_memory_cap_author_ready_for_shadow_revert(observed: List) - ) } -data memory_cap_author_before_shadow_revert_ordering_note: String = "Deploy drop-ins (20-fleet-width MemoryMax, 40-fleet-high MemoryHigh) must be authored from gunbc.runner_slot_allocation BEFORE systemctl revert MemoryMax MemoryHigh on any unit. On-disk deploy files may still carry superseded 16/15GiB while effective caps match declared 14/13 via system.control shadows; revert alone drops shadows and falls back to stale disk." +// Deploy drop-ins (20-fleet-width MemoryMax, 40-fleet-high MemoryHigh) must be authored from +// gunbc.runner_slot_allocation BEFORE systemctl revert MemoryMax MemoryHigh on any unit. On-disk +// deploy files may still carry superseded 16/15GiB while effective caps match declared 14/13 via +// system.control shadows; revert alone drops shadows and falls back to stale disk. data host_axis_caps_author_before_revert_enforced: Bool = true @@ -365,7 +368,9 @@ fn deploy_memory_cap_script_author_precedes_shadow_revert(script: String, units: } // Enrolled: live_deploy apply emits deploy_memory_cap_apply_script_for_host after sudoers preamble. -data deploy_memory_cap_live_deploy_enrollment_note: String = "deploy_memory_cap_apply_script_for_host is emitted into live_deploy apply (gunbc.live_deploy.emit apply_intent_from_effects) after deploy_apply_preamble_steps — author derived drop-ins then systemctl revert per runner_slot_service_units_for_host." +// deploy_memory_cap_apply_script_for_host is emitted into live_deploy apply (gunbc.live_deploy.emit +// apply_intent_from_effects) after deploy_apply_preamble_steps — author derived drop-ins then +// systemctl revert per runner_slot_service_units_for_host. fn control_shadow_reconcile( desired: List, diff --git a/dag/gunbc/host/host_budget_source.dag b/dag/gunbc/host/host_budget_source.dag index 12033f1dbc6..0bf909a74a2 100644 --- a/dag/gunbc/host/host_budget_source.dag +++ b/dag/gunbc/host/host_budget_source.dag @@ -22,11 +22,66 @@ data host_budget_source_authority_disposition: Disposition = Terminal { reason: "Single authority for the SOURCE half of the host memory budget the v1 memory governor arms against. The BUDGET VALUE stays where it is measured (a cgroup read, a sysctl read, an operator env override); what this module owns is the closed set of places that value can come from, whether each bounds this process, whether each is degraded, and how each is rendered. It exists because those three facts were carried by a free-text label string and re-derived from it by substring scan." } -data host_budget_source_string_scan_note: String = "THE DEFECT THIS MODULE DISSOLVES, quoted from the code that carries it. v1's read_host_budget_bytes returns (Option, String) and cli_run's typed_module_cache_cap_derivation decides whether the budget is degraded by scanning that string: source_label.contains(\"memory.max\") || source_label.contains(\"memory.high\"). The comment beside it already names the fix and the rule it breaks — 'if it ever grows a typed source enum, ground this check on the enum instead of re-parsing its display label (section 3, avoid a second representation)'. So the label is simultaneously a human diagnostic and a machine-read discriminant, which is one string doing two jobs and neither well: a rewording of the diagnostic silently changes the degradation verdict, and a new source arm is degraded-by-default only if nobody happens to mention a cgroup file in its prose.\n\nThe coproduct below is that enum. Degradation becomes a total match rather than a substring test, rendering becomes one function rather than format! calls scattered across arms, and a new source cannot be added without answering both questions." +// THE DEFECT THIS MODULE DISSOLVES, quoted from the code that carries it. v1's +// read_host_budget_bytes returns (Option, String) and cli_run's +// typed_module_cache_cap_derivation decides whether the budget is degraded by scanning that string: +// source_label.contains("memory.max") || source_label.contains("memory.high"). The comment beside +// it already names the fix and the rule it breaks — 'if it ever grows a typed source enum, ground +// this check on the enum instead of re-parsing its display label (section 3, avoid a second +// representation)'. So the label is simultaneously a human diagnostic and a machine-read +// discriminant, which is one string doing two jobs and neither well: a rewording of the diagnostic +// silently changes the degradation verdict, and a new source arm is degraded-by-default only if +// nobody happens to mention a cgroup file in its prose. +// +// The coproduct below is that enum. Degradation becomes a total match rather than a substring test, +// rendering becomes one function rather than format! calls scattered across arms, and a new source +// cannot be added without answering both questions. -data host_budget_source_fabricated_label_note: String = "THE macOS BUG, which is what made this worth modeling now rather than later. read_host_budget_bytes ends with a terminal arm that reads, in full: (mem_total_bytes(), \"/proc/meminfo MemTotal\".to_string()). mem_total_bytes() reads /proc/meminfo and returns None when it cannot — and on a Darwin host it ALWAYS returns None, because there is no /proc. The label is composed unconditionally beside it, so the tuple that comes back is (None, \"/proc/meminfo MemTotal\"): a source attribution naming a file that does not exist on the platform, for a read that never happened, next to a value that was never obtained.\n\nObserved live, every local run on the developer's machine: `[floor-drain] degraded_budget_source: cap=4000 source=/proc/meminfo MemTotal (no private cgroup memory.max/memory.high found; falling back to a host-shared signal)`. Nothing is CONCEALED — the degraded flag is correctly true and the line is loud — but the provenance is fabricated, which is the section 5 plausible-output shape applied to a diagnostic rather than to a result. It is also the precise failure that cost a session's worth of confusion: a reader cannot tell from that line whether MemTotal was read and disbelieved, or never read at all, and the cap of 4000 is the ceiling fallback rather than anything derived from a host fact.\n\nBudgetSourceDarwinPhysicalMemory is the arm that had nowhere to go — and note it is a SOURCE, not an absence. The first repair attempted here was an absence arm carrying the kernel family, which described the situation truthfully and left the mechanism intact; the second and correct repair was to notice the platform has the fact and read it (extdeps.darwin.sysctl hw.memsize). A diagnostic that accurately reports having read nothing is still a run with no bound on it." +// THE macOS BUG, which is what made this worth modeling now rather than later. +// read_host_budget_bytes ends with a terminal arm that reads, in full: (mem_total_bytes(), +// "/proc/meminfo MemTotal".to_string()). mem_total_bytes() reads /proc/meminfo and returns None +// when it cannot — and on a Darwin host it ALWAYS returns None, because there is no /proc. The +// label is composed unconditionally beside it, so the tuple that comes back is (None, +// "/proc/meminfo MemTotal"): a source attribution naming a file that does not exist on the +// platform, for a read that never happened, next to a value that was never obtained. +// +// Observed live, every local run on the developer's machine: `[floor-drain] degraded_budget_source: +// cap=4000 source=/proc/meminfo MemTotal (no private cgroup memory.max/memory.high found; falling +// back to a host-shared signal)`. Nothing is CONCEALED — the degraded flag is correctly true and +// the line is loud — but the provenance is fabricated, which is the section 5 plausible-output +// shape applied to a diagnostic rather than to a result. It is also the precise failure that cost a +// session's worth of confusion: a reader cannot tell from that line whether MemTotal was read and +// disbelieved, or never read at all, and the cap of 4000 is the ceiling fallback rather than +// anything derived from a host fact. +// +// BudgetSourceDarwinPhysicalMemory is the arm that had nowhere to go — and note it is a SOURCE, not +// an absence. The first repair attempted here was an absence arm carrying the kernel family, which +// described the situation truthfully and left the mechanism intact; the second and correct repair +// was to notice the platform has the fact and read it (extdeps.darwin.sysctl hw.memsize). A +// diagnostic that accurately reports having read nothing is still a run with no bound on it. -data host_budget_source_no_fallback_arm_note: String = "THERE IS NO FALLBACK ARM, and removing it is the correction this module exists to carry (operator ruling 2026-08-05: 'we can't do fallbacks'). An earlier revision of this file modeled the macOS path as BudgetSourceUnavailable { kernel } and described the consumer as falling back to the declared ceiling. That was honest LABELLING of a dishonest MECHANISM: cli_run's typed_module_cache_cap_derivation ends in .unwrap_or(TYPED_MODULE_CACHE_MAX_ENTRIES_CEIL), so a budget it cannot compute becomes the MOST PERMISSIVE value it could have returned — top-as-answer conflated with top-as-ignorance, the absorbing fallback DESIGN section 5 forbids by name. Naming the arm accurately did not stop it widening.\n\nIT HAS A RECEIPT, paid during this very change: the full witness corpus was OOM-killed twice on the operator's Mac (exit 137, at ~6800 and ~3300 witnesses). No host signal was readable, the cap defaulted to the ceiling, nothing bounded the resolve, and the OS ended the process. That is section 5's prediction verbatim — the deficit's frequency is zeroed by construction, so it never ranks for fixing, and the corpus-denominated cost breaks the budget later and at interest instead of breaking the build.\n\nTHE STRATEGY CHANGED RATHER THAN THE LABEL. macOS is not a platform without memory facts; it is a platform whose memory facts were never asked for. extdeps.darwin.sysctl models hw.memsize (verified live: 17179869184), so BudgetSourceDarwinPhysicalMemory is a REAL READ and the Darwin path now produces a budget like any other. What remains is BudgetSourceUnreadable, which carries the reason and is NOT a source at all — it is the refusal, and no consumer may turn it into a number." +// THERE IS NO FALLBACK ARM, and removing it is the correction this module exists to carry (operator +// ruling 2026-08-05: 'we can't do fallbacks'). An earlier revision of this file modeled the macOS +// path as BudgetSourceUnavailable { kernel } and described the consumer as falling back to the +// declared ceiling. That was honest LABELLING of a dishonest MECHANISM: cli_run's +// typed_module_cache_cap_derivation ends in .unwrap_or(TYPED_MODULE_CACHE_MAX_ENTRIES_CEIL), so a +// budget it cannot compute becomes the MOST PERMISSIVE value it could have returned — top-as-answer +// conflated with top-as-ignorance, the absorbing fallback DESIGN section 5 forbids by name. Naming +// the arm accurately did not stop it widening. +// +// IT HAS A RECEIPT, paid during this very change: the full witness corpus was OOM-killed twice on +// the operator's Mac (exit 137, at ~6800 and ~3300 witnesses). No host signal was readable, the cap +// defaulted to the ceiling, nothing bounded the resolve, and the OS ended the process. That is +// section 5's prediction verbatim — the deficit's frequency is zeroed by construction, so it never +// ranks for fixing, and the corpus-denominated cost breaks the budget later and at interest instead +// of breaking the build. +// +// THE STRATEGY CHANGED RATHER THAN THE LABEL. macOS is not a platform without memory facts; it is a +// platform whose memory facts were never asked for. extdeps.darwin.sysctl models hw.memsize +// (verified live: 17179869184), so BudgetSourceDarwinPhysicalMemory is a REAL READ and the Darwin +// path now produces a budget like any other. What remains is BudgetSourceUnreadable, which carries +// the reason and is NOT a source at all — it is the refusal, and no consumer may turn it into a +// number. type HostBudgetSource = BudgetSourceEnvOverride @@ -57,7 +112,24 @@ fn host_budget_unreadable_on_kernel(kernel: KernelFamily) -> HostBudgetResolutio } } -data host_budget_source_degraded_meaning_note: String = "DEGRADED MEANS 'NOT A PRIVATE LIMIT ON THIS PROCESS', not 'bad' and not 'unreadable'. Only the two cgroup arms and an explicit operator override describe a bound that belongs to this process alone; the one remaining arm that does not, BudgetSourceDarwinPhysicalMemory, reports a HOST-SHARED signal — hw.memsize is total physical RAM, a fact about the machine rather than about this process.\n\nWHY THAT ARM IS STILL ADMISSIBLE WHILE THE MEMINFO ARMS ARE NOT, since both are host-shared and the difference is not the reading but the kernel. Darwin offers no private-limit mechanism at all — there are no cgroups, so on a Mac there is nothing stronger to want and degraded-with-a-real-value is the honest ceiling. Linux HAS cgroups, so a host-shared reading there is a SUBSTITUTE for a bound the kernel can express and this process failed to read, which is a different act entirely; host_budget_source_admissible_as_bound_on_kernel is exactly that distinction, and the meminfo arms are deleted rather than merely judged by it because with them gone the substitution has no constructor.\n\nUnreadable is categorically different again: it is not a source at all and appears in HostBudgetResolution rather than in this coproduct, and it carries no number for any consumer to admit against." +// DEGRADED MEANS 'NOT A PRIVATE LIMIT ON THIS PROCESS', not 'bad' and not 'unreadable'. Only the +// two cgroup arms and an explicit operator override describe a bound that belongs to this process +// alone; the one remaining arm that does not, BudgetSourceDarwinPhysicalMemory, reports a +// HOST-SHARED signal — hw.memsize is total physical RAM, a fact about the machine rather than about +// this process. +// +// WHY THAT ARM IS STILL ADMISSIBLE WHILE THE MEMINFO ARMS ARE NOT, since both are host-shared and +// the difference is not the reading but the kernel. Darwin offers no private-limit mechanism at all +// — there are no cgroups, so on a Mac there is nothing stronger to want and +// degraded-with-a-real-value is the honest ceiling. Linux HAS cgroups, so a host-shared reading +// there is a SUBSTITUTE for a bound the kernel can express and this process failed to read, which +// is a different act entirely; host_budget_source_admissible_as_bound_on_kernel is exactly that +// distinction, and the meminfo arms are deleted rather than merely judged by it because with them +// gone the substitution has no constructor. +// +// Unreadable is categorically different again: it is not a source at all and appears in +// HostBudgetResolution rather than in this coproduct, and it carries no number for any consumer to +// admit against. fn host_budget_source_is_degraded(s: HostBudgetSource) -> Bool { match s { @@ -107,7 +179,21 @@ fn host_budget_source_label(s: HostBudgetSource) -> NonEmptyStr { } } -data host_budget_source_platform_wall_note: String = "THE WALL THIS MODULE EXISTS TO HOLD, stated as a relation between two things that were previously unrelated: a source that READS procfs may not be reported on a kernel that does not PROVIDE procfs. That is exactly the sentence the live macOS diagnostic violates, and with both halves modeled it is now decidable — extdeps.linux.procfs answers which kernels synthesize /proc, this module answers which sources read it, and host_budget_source_admissible_on_kernel is their conjunction.\n\nIt is a validating predicate rather than a construction wall, and that is an honest limitation rather than a preferred design (DESIGN section 5 prefers construction). Making the bad state unwritable would mean parameterizing HostBudgetSource by the kernel it was observed on so a procfs arm could not be constructed for Darwin at all; that is buildable, and it is deliberately not attempted here because the constructor's only caller today is hand-Rust in the v1 seed, which cannot consume a .dag type at the point it runs. The predicate plus its RED control is the strongest wall reachable while the seed still composes these values itself; the dissolution is the same emit path that retires the seed's other budget mirrors." +// THE WALL THIS MODULE EXISTS TO HOLD, stated as a relation between two things that were previously +// unrelated: a source that READS procfs may not be reported on a kernel that does not PROVIDE +// procfs. That is exactly the sentence the live macOS diagnostic violates, and with both halves +// modeled it is now decidable — extdeps.linux.procfs answers which kernels synthesize /proc, this +// module answers which sources read it, and host_budget_source_admissible_on_kernel is their +// conjunction. +// +// It is a validating predicate rather than a construction wall, and that is an honest limitation +// rather than a preferred design (DESIGN section 5 prefers construction). Making the bad state +// unwritable would mean parameterizing HostBudgetSource by the kernel it was observed on so a +// procfs arm could not be constructed for Darwin at all; that is buildable, and it is deliberately +// not attempted here because the constructor's only caller today is hand-Rust in the v1 seed, which +// cannot consume a .dag type at the point it runs. The predicate plus its RED control is the +// strongest wall reachable while the seed still composes these values itself; the dissolution is +// the same emit path that retires the seed's other budget mirrors. fn host_budget_source_admissible_on_kernel(s: HostBudgetSource, kernel: KernelFamily) -> Bool { if host_budget_source_reads_procfs(s: s) { @@ -117,7 +203,15 @@ fn host_budget_source_admissible_on_kernel(s: HostBudgetSource, kernel: KernelFa } } -data kernel_budget_sourcing_absent_conflation_note: String = "WHY THIS IS A COPRODUCT AND NOT AN OPTION. host_budget_source_for_kernel returned HostBudgetSource? and answered BudgetSourceMemTotal for Linux — a STATIC promise that a Linux host always has a budget, discharged by reading the machine. Deleting that arm leaves Linux with no static answer, and returning none for it would collapse two different states into one absence: WindowsNt has no modeled memory facts at all, while Linux has the strongest facts of any platform here and simply cannot say at declaration time WHICH of them applies, because a cgroup limit and an operator override are runtime facts. An unmodeled platform and a runtime-determined one need opposite remedies — model the platform, versus read the host and refuse if it is silent — so they get different arms." +// WHY THIS IS A COPRODUCT AND NOT AN OPTION. host_budget_source_for_kernel returned +// HostBudgetSource? and answered BudgetSourceMemTotal for Linux — a STATIC promise that a Linux +// host always has a budget, discharged by reading the machine. Deleting that arm leaves Linux with +// no static answer, and returning none for it would collapse two different states into one absence: +// WindowsNt has no modeled memory facts at all, while Linux has the strongest facts of any platform +// here and simply cannot say at declaration time WHICH of them applies, because a cgroup limit and +// an operator override are runtime facts. An unmodeled platform and a runtime-determined one need +// opposite remedies — model the platform, versus read the host and refuse if it is silent — so they +// get different arms. type KernelBudgetSourcing = SourcedStatically { source: HostBudgetSource } @@ -142,9 +236,37 @@ fn kernel_budget_sourcing_static_source(g: KernelBudgetSourcing) -> HostBudgetSo } } -data host_budget_unreadable_cgroup_receipt_note: String = "THE BUILDBUDDY RECEIPT, measured 2026-08-30 on a remote-execution runner from this repository's own dispatch path, and the reason the meminfo arms are gone rather than narrowed. The runner's /proc/self/cgroup reads `0::/` and /sys/fs/cgroup exposes NO memory.max and NO memory.high at ANY depth — the controller files a limit would live in are simply not present in the namespace. The slot is nevertheless capped by the executor, so a limit exists and this process cannot see it.\n\nWHAT THE OLD ARMS DID WITH THAT. Both cgroup reads returned nothing, so read_host_budget_bytes fell to MemAvailable — a number about a MACHINE that co-hosts other tenants, not about this slot — and then capped it at DECLARED_RUNNER_SLOT_MEMORY_HIGH_BYTES, a mirror of gunbc.runner_slot_allocation, which is a declaration about THIS FLEET'S self-hosted runners and says nothing whatever about a third party's executor. Two substitutions stacked: a host-shared reading standing in for a private bound, then one fleet's slot declaration standing in for another's. The corpus regen (main_wet) was SIGKILLed rc=137 under the budget that produced, with no diagnostic; the same run completes under an explicit GUNBC_MEMORY_BUDGET_BYTES of 10 GiB at a peak RSS of 7.7 GiB.\n\nTHIS IS DESIGN SECTION 5'S ABSORBING FALLBACK EXACTLY, in the one code path whose whole job is to bound memory: the mechanism could not compute its answer, so it substituted a superset and was labelled safe by the cap it applied to it. The cap is what made it look bounded. A failure arm must refuse, never widen — so an unreadable cgroup limit on a kernel that HAS cgroups is now HostBudgetUnreadable, which carries no number, and the operator supplies the bound the executor knows and the process cannot see." +// THE BUILDBUDDY RECEIPT, measured 2026-08-30 on a remote-execution runner from this repository's +// own dispatch path, and the reason the meminfo arms are gone rather than narrowed. The runner's +// /proc/self/cgroup reads `0::/` and /sys/fs/cgroup exposes NO memory.max and NO memory.high at ANY +// depth — the controller files a limit would live in are simply not present in the namespace. The +// slot is nevertheless capped by the executor, so a limit exists and this process cannot see it. +// +// WHAT THE OLD ARMS DID WITH THAT. Both cgroup reads returned nothing, so read_host_budget_bytes +// fell to MemAvailable — a number about a MACHINE that co-hosts other tenants, not about this slot +// — and then capped it at DECLARED_RUNNER_SLOT_MEMORY_HIGH_BYTES, a mirror of +// gunbc.runner_slot_allocation, which is a declaration about THIS FLEET'S self-hosted runners and +// says nothing whatever about a third party's executor. Two substitutions stacked: a host-shared +// reading standing in for a private bound, then one fleet's slot declaration standing in for +// another's. The corpus regen (main_wet) was SIGKILLed rc=137 under the budget that produced, with +// no diagnostic; the same run completes under an explicit GUNBC_MEMORY_BUDGET_BYTES of 10 GiB at a +// peak RSS of 7.7 GiB. +// +// THIS IS DESIGN SECTION 5'S ABSORBING FALLBACK EXACTLY, in the one code path whose whole job is to +// bound memory: the mechanism could not compute its answer, so it substituted a superset and was +// labelled safe by the cap it applied to it. The cap is what made it look bounded. A failure arm +// must refuse, never widen — so an unreadable cgroup limit on a kernel that HAS cgroups is now +// HostBudgetUnreadable, which carries no number, and the operator supplies the bound the executor +// knows and the process cannot see. -data host_budget_declared_slot_is_not_a_reading_note: String = "DECLARED_RUNNER_SLOT_MEMORY_HIGH_BYTES SURVIVES IN THE SEED AND IS NO LONGER A BUDGET SOURCE, which is the distinction the deleted arm erased. It mirrors gunbc.runner_slot_allocation gunbc_runner_slot_desired memory_high: a declaration of what THIS fleet configures its own runner slots to, true of the machines this repository provisions and of nothing else. As a fixture for reasoning about those machines it is sound, and the whole-corpus admission witnesses still use it that way. As a CEILING APPLIED TO A READING FROM AN UNKNOWN HOST it was a claim about a host nobody had measured, and it is that use, not the constant, that is deleted. A declared constant may bound a refusal; it may not stand in for a reading." +// DECLARED_RUNNER_SLOT_MEMORY_HIGH_BYTES SURVIVES IN THE SEED AND IS NO LONGER A BUDGET SOURCE, +// which is the distinction the deleted arm erased. It mirrors gunbc.runner_slot_allocation +// gunbc_runner_slot_desired memory_high: a declaration of what THIS fleet configures its own runner +// slots to, true of the machines this repository provisions and of nothing else. As a fixture for +// reasoning about those machines it is sound, and the whole-corpus admission witnesses still use it +// that way. As a CEILING APPLIED TO A READING FROM AN UNKNOWN HOST it was a claim about a host +// nobody had measured, and it is that use, not the constant, that is deleted. A declared constant +// may bound a refusal; it may not stand in for a reading. data host_budget_source_seed_mirror_disposition: Disposition = Scaffold { dissolves_to: SingleAuthority, diff --git a/dag/gunbc/host/host_build_cache_provision.dag b/dag/gunbc/host/host_build_cache_provision.dag index 6a75ec79a15..410d1076cf1 100644 --- a/dag/gunbc/host/host_build_cache_provision.dag +++ b/dag/gunbc/host/host_build_cache_provision.dag @@ -17,7 +17,30 @@ import gunbc.host_toolchain_ensure { host_toolchain_ensure, BuildCache } import gunbc.build_cache_instance { ci_cache_instance } import product.placement_supply { HostIdentity } -data host_build_cache_provision_naming_hazard_note: String = "NAMING HAZARD, RECORD BEFORE TOUCHING EITHER FILE. This module (host_build_cache_provision.dag) and the now-deleted host_build_cache_provision_script.dag differed by one suffix, held unrelated concepts (this one a catalog-gate/verdict fold; that one a concat-shell body builder), and one was edited while the other was deleted in the SAME PR (#8598, the A5 EnsureBuildCacheInstance arm swap) -- a trap the tree itself set, not a naming choice either file made carelessly. It produced a real false-negative: a dependents-census grep answering 'does a disconnected verdict fold exist' checked the deleted file's own leftover state and reported that as though it answered the question about THIS file's fold, which was not re-grepped. The next person doing anything near either name: grep the exact module path, not the shared prefix. The catalog-gate/verdict fold this module used to hold (BuildCacheDaemonObservation / provision_build_cache_verdict / placement_verdict / build_cache_provision_gate_accepts) was a separate finding: zero non-test, non-self-file callers tree-wide, confirmed 2026-08-20, filed in docs/plans/shell-to-dag-residual-census-and-arc-completion.md (C-note) first as a deletion candidate and then, the same day, corrected to the opposite disposition -- an unconsumed authority naming itself as the fix for a live incident class, which must be wired rather than deleted. IT IS WIRED AS OF 2026-08-21, AND IT NO LONGER LIVES HERE -- it moved to gunbc.build_cache_provision_verdict, because wiring it meant gunbc.host_effect_realize had to call it and this module's import of gunbc.host_toolchain_ensure would have closed a cycle through that module. The zero no longer reproduces: gunbc.host_effect_realize build_cache_ensure_step folds provision_build_cache_verdict over the endpoint re-observed AFTER its ensure shell ran, so the fold is the POSTCONDITION on the provisioning path while gunbc.build_cache_ensure admit_ensure_build_cache_instance is the precondition. The sentence is rewritten rather than annotated because a stale 'zero callers' claim in a naming-hazard warning is the exact false-negative this note exists to prevent, pointed the other way." +// NAMING HAZARD, RECORD BEFORE TOUCHING EITHER FILE. This module (host_build_cache_provision.dag) +// and the now-deleted host_build_cache_provision_script.dag differed by one suffix, held unrelated +// concepts (this one a catalog-gate/verdict fold; that one a concat-shell body builder), and one +// was edited while the other was deleted in the SAME PR (#8598, the A5 EnsureBuildCacheInstance arm +// swap) -- a trap the tree itself set, not a naming choice either file made carelessly. It produced +// a real false-negative: a dependents-census grep answering 'does a disconnected verdict fold +// exist' checked the deleted file's own leftover state and reported that as though it answered the +// question about THIS file's fold, which was not re-grepped. The next person doing anything near +// either name: grep the exact module path, not the shared prefix. The catalog-gate/verdict fold +// this module used to hold (BuildCacheDaemonObservation / provision_build_cache_verdict / +// placement_verdict / build_cache_provision_gate_accepts) was a separate finding: zero non-test, +// non-self-file callers tree-wide, confirmed 2026-08-20, filed in +// docs/plans/shell-to-dag-residual-census-and-arc-completion.md (C-note) first as a deletion +// candidate and then, the same day, corrected to the opposite disposition -- an unconsumed +// authority naming itself as the fix for a live incident class, which must be wired rather than +// deleted. IT IS WIRED AS OF 2026-08-21, AND IT NO LONGER LIVES HERE -- it moved to +// gunbc.build_cache_provision_verdict, because wiring it meant gunbc.host_effect_realize had to +// call it and this module's import of gunbc.host_toolchain_ensure would have closed a cycle through +// that module. The zero no longer reproduces: gunbc.host_effect_realize build_cache_ensure_step +// folds provision_build_cache_verdict over the endpoint re-observed AFTER its ensure shell ran, so +// the fold is the POSTCONDITION on the provisioning path while gunbc.build_cache_ensure +// admit_ensure_build_cache_instance is the precondition. The sentence is rewritten rather than +// annotated because a stale 'zero callers' claim in a naming-hazard warning is the exact +// false-negative this note exists to prevent, pointed the other way. data lane_e_srv1_ci_noncompletion_root_cause_receipt: String = "LANE E ROOT-CAUSE RECEIPT (2026-08-06 investigation; quarantine mechanism RETIRED 2026-08-07 — see retirement paragraph). SYMPTOM CLASS INVESTIGATED: cargo check -p v1-compiler-tests --tests killed at the 15m compile-gate ceiling while still compiling v1-compiler. HARM MEASURE: count long cancelled jobs as capacity absorption, not only conclusion=failure — a 14m cancelled build costs the same slot-hours as a 15m killed build. EXECUTED EVIDENCE (historical failure): runs 31060400858 (srv1-05) and 31060543168 (srv1-01), killed at 15m mid-Compiling v1-compiler; srv1-02 build cancelled at 14m (still-bat-561 sample). SLOT DISCRIMINANT (Lane E measurement): srv1 is NOT uniformly broken — still-bat-561 measured srv1-04 build success in ~1m and srv1-03 regen success in the same window srv1-01/05 cold-killed at 15m; warm-cache fleet runs complete the compile gate in ~50s–1m. MECHANISM (load-bearing): one CI sccache endpoint per host; cache-miss compiles execute as CHILDREN OF THE SERVER PROCESS (extdeps.cache.sccache sccache_server_process_facts), so the server's cgroup bounds every compile on the host — not the slot that issued the compile. A server stranded in a retired slot's cgroup (pid 3975946 in srv1-10, 2026-08-01 receipt) exported one dead slot's memory ceiling to all live slots on cache miss. FIX AUTHORITY: ServerManagedUnitOwned on the managed compile-pool slice — ServerRunnerOwned on ANY lifecycle (active or retired) must refuse ProvisionConverged (placement_verdict, build_cache_placement_observation_test witness_both_runner_lifecycles_refuse_at_the_verdict). 2026-08-07 STATUS: INCIDENT CLEARED, NOT REPAIRED — pid 3975946 gone; surviving server on srv1 is runner-owned on ACTIVE slot srv1-02 (~90s old at observation), same ServerRunnerOwned class srv2/srv3 already carried per 2026-08-01 receipt; ephemeral runner recycle is the only reason it has not stranded again — the class can strand again the next time a slot retires while holding the server. POST-INCIDENT MEASUREMENT (still-bat-561, 45 completed runs sampled 2026-08-07, kill-shape = non-success with duration 13–18m): srv1 16 jobs / 5 non-success / 0 kill-shape; srv2 26 / 6 / 2; srv3 29 / 12 / 0; srv4 49 / 14 / 1 — srv1 has zero kill-shape failures while srv2 and srv4 still show them; host-scoped CI quarantine would route work toward hosts currently exhibiting the defect. QUARANTINE RETIREMENT: ci-fleet-admit host-label gate (PR #7889) retired rather than merged — premise expired and measurement inverted the remedy; this receipt is the durable home for the investigation." @@ -59,8 +82,6 @@ fn provision_build_cache_routes_through_host_effect_apply() -> Bool { } } - - func provision_build_cache(host: HostIdentity, catalog_id: CacheInterfaceId) -> ProcessExit uses net: Network { diff --git a/dag/gunbc/host/host_cli_dependency.dag b/dag/gunbc/host/host_cli_dependency.dag index 16ce964b612..d85f227305e 100644 --- a/dag/gunbc/host/host_cli_dependency.dag +++ b/dag/gunbc/host/host_cli_dependency.dag @@ -6,11 +6,31 @@ import extdeps.shell import std.types { String, NonEmptyStr } import v2.std.optional { Optional, Present, Absent, optional_absent, optional_present } -data host_cli_dependency_authority_note: String = "Host CLI placement for wet witnesses: observe whether an executable is on PATH before attempting shell transport. A missing npm on the falsifier runner surfaced as runtime error: failed to execute npm (os error 2) and classified as WitnessRed — indistinguishable from a subject failure. This module refuses BEFORE execution with a typed wire HostDependencyAbsent with tool and hint fields that claim_executor reads structurally (falsifier recovery PR D, run 31685755058 component 6). ENROLLMENT ANSWER: codex_wet_materialization_host_cli_requirements on this module; absent npm stops as HostDependencyAbsent, never auto-install inside the witness." +// Host CLI placement for wet witnesses: observe whether an executable is on PATH before attempting +// shell transport. A missing npm on the falsifier runner surfaced as runtime error: failed to +// execute npm (os error 2) and classified as WitnessRed — indistinguishable from a subject failure. +// This module refuses BEFORE execution with a typed wire HostDependencyAbsent with tool and hint +// fields that claim_executor reads structurally (falsifier recovery PR D, run 31685755058 component +// 6). ENROLLMENT ANSWER: codex_wet_materialization_host_cli_requirements on this module; absent npm +// stops as HostDependencyAbsent, never auto-install inside the witness. -data which_path_realization_limit_note: String = "observe_host_cli_dependency keys on shell.PosixCommandV.Check.exists (Bool from exit_success), NOT .path: under WET execution .path is a bare stdout string, not Optional, and matching it raises non-exhaustive pattern match on the healthy path — the Value::Null / Optional realization fork (DESIGN open thread). Same limit as gunbc.roadmap_belt_actuate belt_program_available_note and falsifier-recovery PR A. Resolved path is blocked until the split lands; locality is unestablished. (This module migrated off shell.Which.Check to shell.PosixCommandV.Check -- crisp-wren-896, PR #8590 -- because gunbc.ci_fleet's own CI/falsifier runner carries the identical distro_or_product: none as srv1-4, so the operational apt-install evidence that once justified `which` here does not establish it as a fact the typed model carries; command -v's weaker, dominant assumption holds everywhere `which` did and does not depend on that evidence.)" +// observe_host_cli_dependency keys on shell.PosixCommandV.Check.exists (Bool from exit_success), +// NOT .path: under WET execution .path is a bare stdout string, not Optional, and matching it +// raises non-exhaustive pattern match on the healthy path — the Value::Null / Optional realization +// fork (DESIGN open thread). Same limit as gunbc.roadmap_belt_actuate belt_program_available_note +// and falsifier-recovery PR A. Resolved path is blocked until the split lands; locality is +// unestablished. (This module migrated off shell.Which.Check to shell.PosixCommandV.Check -- +// crisp-wren-896, PR #8590 -- because gunbc.ci_fleet's own CI/falsifier runner carries the +// identical distro_or_product: none as srv1-4, so the operational apt-install evidence that once +// justified `which` here does not establish it as a fact the typed model carries; command -v's +// weaker, dominant assumption holds everywhere `which` did and does not depend on that evidence.) -data falsifier_run_31685755058_wall_receipt_note: String = "Part-two wall arithmetic CONFIRMED from floor-component-receipt run 31685755058 (not inherited): component 1 cold control wall_ms=3480830 (~58.0 min, 9016 witnesses); component 6 wall_ms=2946579 (~49.1 min, 16 witnesses, npm absent WitnessRed before this PR); components 7-9 outcome=skipped failure_mode=run_incomplete wall_ms=0 witnesses=0 — never executed before the 170-minute step cap. Batch-1 cold-control cost is a separate lane; this PR does not raise deadline/swap/max-parallel." +// Part-two wall arithmetic CONFIRMED from floor-component-receipt run 31685755058 (not inherited): +// component 1 cold control wall_ms=3480830 (~58.0 min, 9016 witnesses); component 6 wall_ms=2946579 +// (~49.1 min, 16 witnesses, npm absent WitnessRed before this PR); components 7-9 outcome=skipped +// failure_mode=run_incomplete wall_ms=0 witnesses=0 — never executed before the 170-minute step +// cap. Batch-1 cold-control cost is a separate lane; this PR does not raise +// deadline/swap/max-parallel. type HostCliEnrollmentRequirement { tool: NonEmptyStr diff --git a/dag/gunbc/host/host_codex_runtime_provision.dag b/dag/gunbc/host/host_codex_runtime_provision.dag index 245c59473e5..264c7d0f8da 100644 --- a/dag/gunbc/host/host_codex_runtime_provision.dag +++ b/dag/gunbc/host/host_codex_runtime_provision.dag @@ -25,7 +25,12 @@ import gunbc.package_delivery { data host_codex_runtime_provision_scope_disposition: Disposition = SingleAuthority -data host_codex_runtime_provision_converge_cadence_note: String = "Assimilation (host_standup P1c) plus recurring reconcile: fleet_converge_policy registers codex_runtime_bundle on every fleet compute host, and gunbc.fleet_converge_cli converge_provider_runtime_codex_receipt runs ProvisionCodexRuntime on every `gunbc converge` for srv1–srv4 after slice1/knob-frontier work (converge_cli_run_host_knobs) and before converge-receipt publication. Standup alone would not heal already-assimilated hosts; the converge adjunct closes that hole." +// Assimilation (host_standup P1c) plus recurring reconcile: fleet_converge_policy registers +// codex_runtime_bundle on every fleet compute host, and gunbc.fleet_converge_cli +// converge_provider_runtime_codex_receipt runs ProvisionCodexRuntime on every `gunbc converge` for +// srv1–srv4 after slice1/knob-frontier work (converge_cli_run_host_knobs) and before +// converge-receipt publication. Standup alone would not heal already-assimilated hosts; the +// converge adjunct closes that hole. data host_codex_runtime_provision_realize_scaffold: Disposition = Scaffold { dissolves_to: RealizationDispatch, @@ -40,7 +45,23 @@ fn provision_codex_runtime_effect() -> HostEffect { ProvisionCodexRuntime {} } -data codex_runtime_provision_argv_note: String = "The observe/read-back argv builder (codex_runtime_observe_argv) lives in gunbc.host_codex_runtime_provision_verdict, not here — this module imports gunbc.host_toolchain_ensure, and host_toolchain_ensure imports gunbc.host_effect_realize, so an argv builder consumed by host_effect_realize cannot live in this module without a gunbc.host_effect_realize -> gunbc.host_codex_runtime_provision -> gunbc.host_toolchain_ensure -> gunbc.host_effect_realize cycle. gunbc.host_effect_realize runs observe, materialize, then read-back as three typed single-command steps: LocalShell dispatches through the existing typed argv services gunbc.WitnessBin.Run (observe/read-back) and gunbc.Cli.Run (materialize, argv built inline from gunbc.cli_invoke gunbc_run_transport_argv against materialize_codex_runtime_on_local_host below); SshShell/FleetSsh dispatch codex_runtime_observe_argv (and the equivalent materialize argv) through run_typed_argv_transport. Branching (does the version match? did materialize fail?) is decided in .dag from the typed exec result (exit code, stdout) by gunbc.host_effect_realize / gunbc.host_codex_runtime_provision_verdict, not by shell if/grep/printf receipt markers. There is no standalone mkdir-release-parent argv: materialize_codex_runtime_bundle's own call chain (release_codex_runtime_bundle_immutable) already runs shell.Mkdir.Parents on release_parent, so a duplicate pre-step would be redundant work." +// The observe/read-back argv builder (codex_runtime_observe_argv) lives in +// gunbc.host_codex_runtime_provision_verdict, not here — this module imports +// gunbc.host_toolchain_ensure, and host_toolchain_ensure imports gunbc.host_effect_realize, so an +// argv builder consumed by host_effect_realize cannot live in this module without a +// gunbc.host_effect_realize -> gunbc.host_codex_runtime_provision -> gunbc.host_toolchain_ensure -> +// gunbc.host_effect_realize cycle. gunbc.host_effect_realize runs observe, materialize, then +// read-back as three typed single-command steps: LocalShell dispatches through the existing typed +// argv services gunbc.WitnessBin.Run (observe/read-back) and gunbc.Cli.Run (materialize, argv built +// inline from gunbc.cli_invoke gunbc_run_transport_argv against +// materialize_codex_runtime_on_local_host below); SshShell/FleetSsh dispatch +// codex_runtime_observe_argv (and the equivalent materialize argv) through +// run_typed_argv_transport. Branching (does the version match? did materialize fail?) is decided in +// .dag from the typed exec result (exit code, stdout) by gunbc.host_effect_realize / +// gunbc.host_codex_runtime_provision_verdict, not by shell if/grep/printf receipt markers. There is +// no standalone mkdir-release-parent argv: materialize_codex_runtime_bundle's own call chain +// (release_codex_runtime_bundle_immutable) already runs shell.Mkdir.Parents on release_parent, so a +// duplicate pre-step would be redundant work. fn provision_codex_runtime_routes_through_host_effect_apply() -> Bool { match host_codex_runtime_provision_realize_scaffold { diff --git a/dag/gunbc/host/host_codex_runtime_provision_verdict.dag b/dag/gunbc/host/host_codex_runtime_provision_verdict.dag index 39bc670aaf9..4041f5d9465 100644 --- a/dag/gunbc/host/host_codex_runtime_provision_verdict.dag +++ b/dag/gunbc/host/host_codex_runtime_provision_verdict.dag @@ -7,7 +7,22 @@ import gunbc.package_delivery { codex_runtime_version_identity_marker, } -data host_codex_runtime_provision_verdict_authority_note: String = "Verdict fold lives here — not in gunbc.host_codex_runtime_provision — so gunbc.host_effect_realize can consume provision_codex_runtime_verdict without importing host_toolchain_ensure (host_codex_runtime_provision -> host_toolchain_ensure -> host_effect_realize would cycle). Mirrors host_build_cache_catalog_gate beside host_build_cache_provision.\n\nOBSERVATION IS TYPED, NOT SHELL TEXT: this module used to classify a combined_output String against printf'd CodexRuntimeProvisionReceipt markers (observe=absent / ensure=FAILED / read_back=FAILED) produced by a hand-rolled bash if/grep/printf body in the now-deleted host_codex_runtime_provision_script.dag. gunbc.host_effect_realize now runs the observe/materialize/read-back sequence as three typed single-command argv executions (observe/read-back via this module's own codex_runtime_observe_argv, materialize via gunbc.cli_invoke gunbc_run_transport_argv or gunbc.Cli.Run) and constructs CodexRuntimeObservation directly from each step's typed exit code / stdout — there is no receipt-marker string to parse or fail to recognize, so the former CodexRuntimeShellOutputUnrecognized / RefusalShellOutputUnrecognized fallback class has no remaining reachable cause and is retired with the shell body it existed to classify." +// Verdict fold lives here — not in gunbc.host_codex_runtime_provision — so +// gunbc.host_effect_realize can consume provision_codex_runtime_verdict without importing +// host_toolchain_ensure (host_codex_runtime_provision -> host_toolchain_ensure -> +// host_effect_realize would cycle). Mirrors host_build_cache_catalog_gate beside +// host_build_cache_provision. +// +// OBSERVATION IS TYPED, NOT SHELL TEXT: this module used to classify a combined_output String +// against printf'd CodexRuntimeProvisionReceipt markers (observe=absent / ensure=FAILED / +// read_back=FAILED) from a hand-rolled bash if/grep/printf body in the deleted +// host_codex_runtime_provision_script.dag. gunbc.host_effect_realize now runs +// observe/materialize/read-back as three typed single-command argv executions (observe/read-back +// via this module's codex_runtime_observe_argv, materialize via gunbc.cli_invoke +// gunbc_run_transport_argv or gunbc.Cli.Run) and builds CodexRuntimeObservation from each step's +// typed exit code / stdout. No receipt-marker string remains to parse, so the former +// CodexRuntimeShellOutputUnrecognized / RefusalShellOutputUnrecognized fallback class has no +// reachable cause and is retired with the shell body it classified. fn codex_runtime_observe_argv(executable: String) -> List { [executable, "--version"] diff --git a/dag/gunbc/host/host_compile_pool.dag b/dag/gunbc/host/host_compile_pool.dag index 7ae2ef71ad1..fd009734d58 100644 --- a/dag/gunbc/host/host_compile_pool.dag +++ b/dag/gunbc/host/host_compile_pool.dag @@ -16,7 +16,27 @@ import product.placement_supply { HostIdentity } data host_compile_pool_scope_disposition: Disposition = SingleAuthority -data host_compile_pool_producer_note: String = "THE PRODUCER FOR HostCompilePoolReady, AND DELIBERATELY NOT A SECOND COMPILE-POOL MODEL. Everything about how large the pool is and where compile RSS is charged already has an authority: gunbc.fleet_host_budget owns CompilePoolPlacement and derives the size through compile_pool_bytes_for from extdeps.jobserver's jobserver_build_pool_bytes, which is the same cited knob that bounds build concurrency. Minting a HostCompilePool record with its own memory_high, memory_max and token_count fields beside that would fork the sizing authority and give a host two answers for how big its pool is — the DESIGN section 3 nickname defect, at the carrier that decides whether a runner may take work.\n\nSo this module adds no sizing and no topology. It adds exactly the missing half: gunbc.runner_activation's HostCompilePoolReady had no producer, so every construction of it in the tree was hand-authored, which is precisely the forgeable-receipt residual that carrier's own note names. derive_host_compile_pool_ready is the one derivation, taking the declared topology and a host observation and returning the receipt.\n\nWHAT IT ANSWERS TODAY, stated so this is not read as a description of a working fleet: gunbc_compile_pool_placement is CompilePoolInRunnerSlots, so the derivation returns not-ready for EVERY host, and activation therefore refuses fleet-wide. That is the correct answer, not a gap in this module — no observed host has the managed slice, and the 2026-08-01 placement receipt found every CI cache endpoint owned by a runner-slot process. The receipt going green is gated on the same flip fleet_host_budget's dissolution trigger already names." +// THE PRODUCER FOR HostCompilePoolReady, AND DELIBERATELY NOT A SECOND COMPILE-POOL MODEL. +// Everything about how large the pool is and where compile RSS is charged already has an authority: +// gunbc.fleet_host_budget owns CompilePoolPlacement and derives the size through +// compile_pool_bytes_for from extdeps.jobserver's jobserver_build_pool_bytes, which is the same +// cited knob that bounds build concurrency. Minting a HostCompilePool record with its own +// memory_high, memory_max and token_count fields beside that would fork the sizing authority and +// give a host two answers for how big its pool is — the DESIGN section 3 nickname defect, at the +// carrier that decides whether a runner may take work. +// +// So this module adds no sizing and no topology. It adds exactly the missing half: +// gunbc.runner_activation's HostCompilePoolReady had no producer, so every construction of it in +// the tree was hand-authored, which is precisely the forgeable-receipt residual that carrier's own +// note names. derive_host_compile_pool_ready is the one derivation, taking the declared topology +// and a host observation and returning the receipt. +// +// WHAT IT ANSWERS TODAY, stated so this is not read as a description of a working fleet: +// gunbc_compile_pool_placement is CompilePoolInRunnerSlots, so the derivation returns not-ready for +// EVERY host, and activation therefore refuses fleet-wide. That is the correct answer, not a gap in +// this module — no observed host has the managed slice, and the 2026-08-01 placement receipt found +// every CI cache endpoint owned by a runner-slot process. The receipt going green is gated on the +// same flip fleet_host_budget's dissolution trigger already names. type CompilePoolObservation = CompilePoolSliceObserved { slice_unit: NonEmptyStr, limits: CgroupMemoryLimits } @@ -31,10 +51,44 @@ type CompilePoolNotReadyReason | PoolSliceNamedDoesNotMatchIntent { declared: NonEmptyStr, observed: NonEmptyStr } | PoolSliceCannotHoldDerivedSize { required: ByteSize, observed_ceiling: ByteSize } -data pool_slice_undersized_note: String = "AN OBSERVED SLICE THAT CANNOT HOLD THE DERIVED POOL IS NOT READY, AND THE FIRST VERSION OF THIS DERIVATION SAID IT WAS (review 46655). It matched CompilePoolSliceObserved and bound `limits: _`, discarding the observed cgroup ceilings, then reported CompilePoolReady carrying the size derived from the JOBSERVER — so the receipt asserted a sized pool on the strength of a number nobody had compared to the host. A slice observed with a zero memory.max was declared ready against a nonzero requirement.\n\nThat is fabricated readiness rather than a missing check, and the distinction matters for where the fix goes: the observation was already in hand and was thrown away, so this is not a gap awaiting a producer. It is the fail-closed rule inverted at the one carrier whose entire job is to decide whether compiles have a bounded parent, and the consequence on a real host is the exact defect the lane exists to close -- an unbounded or wrongly-bounded ceiling parenting every cache-miss compiler, which is what srv1 was doing on 2026-08-01 under slot srv1-10, whose unit was inactive and disabled while its cgroup still parented the server. THE DATE IS ON THAT CLAUSE BECAUSE THE CLAUSE HAS SINCE EXPIRED IN BOTH ITS PARTS: measured 2026-08-25T18:43Z, srv1-10 is ACTIVE and ENABLED and is one of the 21 slots srv1 serves, and srv1 runs no sccache process at all. The derivation this note defends is unaffected -- it is about comparing a declared pool against an observed ceiling, not about which cgroup happened to hold the server on one night.\n\nThe comparison is against memory_max rather than memory_high because max is the ceiling that actually bounds the cgroup; high is a throttling watermark the kernel allows to be exceeded, so a pool sized against high would admit a slice whose hard limit is below the compiles it is meant to hold.\n\nThe defect was DEMONSTRATED BY THIS MODULE'S OWN FIXTURE and that is worth recording rather than quietly deleting: witness_a_declared_and_observed_slice_is_ready built its observation with all-zero limits and passed, so the acceptance set was certifying the hole. The fixture now carries a ceiling that can hold the derived size, and a sibling witness pins the undersized case." +// AN OBSERVED SLICE THAT CANNOT HOLD THE DERIVED POOL IS NOT READY, AND THE FIRST VERSION OF THIS +// DERIVATION SAID IT WAS (review 46655). It matched CompilePoolSliceObserved and bound `limits: _`, +// discarding the observed cgroup ceilings, then reported CompilePoolReady carrying the size derived +// from the JOBSERVER — so the receipt asserted a sized pool on the strength of a number nobody had +// compared to the host. A slice observed with a zero memory.max was declared ready against a +// nonzero requirement. +// +// That is fabricated readiness rather than a missing check, and the distinction matters for where +// the fix goes: the observation was already in hand and was thrown away, so this is not a gap +// awaiting a producer. It is the fail-closed rule inverted at the one carrier whose entire job is +// to decide whether compiles have a bounded parent, and the consequence on a real host is the exact +// defect the lane exists to close -- an unbounded or wrongly-bounded ceiling parenting every +// cache-miss compiler, which is what srv1 was doing on 2026-08-01 under slot srv1-10, whose unit +// was inactive and disabled while its cgroup still parented the server. THE DATE IS ON THAT CLAUSE +// BECAUSE THE CLAUSE HAS SINCE EXPIRED IN BOTH ITS PARTS: measured 2026-08-25T18:43Z, srv1-10 is +// ACTIVE and ENABLED and is one of the 21 slots srv1 serves, and srv1 runs no sccache process at +// all. The derivation this note defends is unaffected -- it is about comparing a declared pool +// against an observed ceiling, not about which cgroup happened to hold the server on one night. +// +// The comparison is against memory_max rather than memory_high because max is the ceiling that +// actually bounds the cgroup; high is a throttling watermark the kernel allows to be exceeded, so a +// pool sized against high would admit a slice whose hard limit is below the compiles it is meant to +// hold. +// +// The defect was DEMONSTRATED BY THIS MODULE'S OWN FIXTURE and that is worth recording rather than +// quietly deleting: witness_a_declared_and_observed_slice_is_ready built its observation with +// all-zero limits and passed, so the acceptance set was certifying the hole. The fixture now +// carries a ceiling that can hold the derived size, and a sibling witness pins the undersized case. - -data pool_not_declared_is_not_pool_absent_note: String = "THE TWO NOT-READY REASONS THAT LOOK ALIKE AND ARE NOT. PoolNotDeclaredInTopology means gunbc.fleet_host_budget's placement row still says CompilePoolInRunnerSlots — nobody has decided this fleet HAS a managed pool, so there is nothing to look for on the host and an observation would be meaningless. PoolSliceAbsentOnHost means the topology declares one and this particular host does not have it — a convergence gap on a decided intent. Their remedies are different and so are their owners: the first is a fleet-wide topology change that must shrink runner_pool in the same motion to keep the host allocation conserving, the second is provisioning one host. Collapsing them into a single not-ready would make the fleet-wide decision look like a per-host chore, which is the reading that gets it done four times by hand instead of once in the model." +// THE TWO NOT-READY REASONS THAT LOOK ALIKE AND ARE NOT. PoolNotDeclaredInTopology means +// gunbc.fleet_host_budget's placement row still says CompilePoolInRunnerSlots — nobody has decided +// this fleet HAS a managed pool, so there is nothing to look for on the host and an observation +// would be meaningless. PoolSliceAbsentOnHost means the topology declares one and this particular +// host does not have it — a convergence gap on a decided intent. Their remedies are different and +// so are their owners: the first is a fleet-wide topology change that must shrink runner_pool in +// the same motion to keep the host allocation conserving, the second is provisioning one host. +// Collapsing them into a single not-ready would make the fleet-wide decision look like a per-host +// chore, which is the reading that gets it done four times by hand instead of once in the model. type CompilePoolReadiness = CompilePoolReady { slice_unit: NonEmptyStr, sized_bytes: ByteSize } @@ -118,7 +172,20 @@ fn pool_is_declared(reason: CompilePoolNotReadyReason) -> Bool { } } -data receipt_projection_is_lossy_note: String = "THE RECEIPT IS A LOSSY PROJECTION OF THE READINESS, AND THAT IS THE HONEST DIRECTION FOR THE LOSS. HostCompilePoolReady carries two Bools because that is the shape gunbc.runner_activation reviewed and consumes; CompilePoolReadiness carries the located reason. Projecting readiness DOWN to the receipt discards the reason, which is acceptable because the receipt's only job is to gate, and every not-ready projection sets pool_observed false so no not-ready state can produce an activating receipt. Projecting the other way would not be acceptable and is not offered: there is no fn from a receipt back to a readiness, because the Bools cannot reconstruct which of the five reasons applied, and a function that guessed would fabricate a located diagnostic.\n\nThis is the interim shape, not the resting one. The dissolve-on is the one runner_activation's own note already declares: when each *_verified flag becomes a typed observed-vs-intended pair, this projection stops discarding the reason and CompilePoolReadiness travels into the receipt directly. Until then the derivation existing at all is the improvement — before it, every HostCompilePoolReady in the tree was hand-written." +// THE RECEIPT IS A LOSSY PROJECTION OF THE READINESS, AND THAT IS THE HONEST DIRECTION FOR THE +// LOSS. HostCompilePoolReady carries two Bools because that is the shape gunbc.runner_activation +// reviewed and consumes; CompilePoolReadiness carries the located reason. Projecting readiness DOWN +// to the receipt discards the reason, which is acceptable because the receipt's only job is to +// gate, and every not-ready projection sets pool_observed false so no not-ready state can produce +// an activating receipt. Projecting the other way would not be acceptable and is not offered: there +// is no fn from a receipt back to a readiness, because the Bools cannot reconstruct which of the +// five reasons applied, and a function that guessed would fabricate a located diagnostic. +// +// This is the interim shape, not the resting one. The dissolve-on is the one runner_activation's +// own note already declares: when each *_verified flag becomes a typed observed-vs-intended pair, +// this projection stops discarding the reason and CompilePoolReadiness travels into the receipt +// directly. Until then the derivation existing at all is the improvement — before it, every +// HostCompilePoolReady in the tree was hand-written. fn receipt_admits_pool(receipt: HostCompilePoolReady) -> Bool { receipt.pool_declared && receipt.pool_observed diff --git a/dag/gunbc/host/host_compile_pool_provision.dag b/dag/gunbc/host/host_compile_pool_provision.dag index 8dae5efcb53..5c3122ac844 100644 --- a/dag/gunbc/host/host_compile_pool_provision.dag +++ b/dag/gunbc/host/host_compile_pool_provision.dag @@ -12,18 +12,18 @@ import product.placement_supply { HostIdentity } data host_compile_pool_provision_scope_disposition: Disposition = SingleAuthority // THE ENTRY POINT, WITHOUT WHICH THE WIRING IS NOT WIRING. gunbc.host_toolchain_ensure gained a -// CompilePool kind, but every other kind on that selector is reached through a named `func` of its own -// -- provision_build_cache for BuildCache, provision_codex_runtime for CodexRuntime, -// srv3_os_install_actuator_toolchain_ensure for OsInstallActuator -- and the compile pool had none. A -// kind nothing selects is the same unreachability this lane exists to close, moved one layer out: the -// admission would have been reachable from the dispatcher and the dispatcher reachable from nobody. -// Found by review (codex/gpt-5.6-sol, review 55280) rather than by me, and the finding was correct. +// CompilePool kind, but every other kind on that selector is reached through a named `func` -- +// provision_build_cache for BuildCache, provision_codex_runtime for CodexRuntime, +// srv3_os_install_actuator_toolchain_ensure for OsInstallActuator -- and the compile pool had none. +// A kind nothing selects is the unreachability this lane exists to close, moved one layer out: the +// admission reachable from the dispatcher and the dispatcher reachable from nobody. Found by review +// (codex/gpt-5.6-sol, review 55280), and the finding was correct. // -// IT IS A THIN CALLER AND CARRIES NO POLICY. Unlike provision_build_cache there is no catalog gate to -// consult first: the compile pool has no cited third-party artifact to admit, so every decision this -// effect makes -- topology declared, size derived, cache placement readable, slice observed -- belongs -// to gunbc.compile_pool_ensure's admission and is taken inside the realizer against the live host. -// Putting any of it here would be a second decision site for one question. +// IT IS A THIN CALLER AND CARRIES NO POLICY. Unlike provision_build_cache there is no catalog gate +// to consult first: the compile pool has no cited third-party artifact to admit, so every decision +// this effect makes -- topology declared, size derived, cache placement readable, slice observed -- +// belongs to gunbc.compile_pool_ensure's admission, taken inside the realizer against the live +// host. Any of it here would be a second decision site for one question. data host_compile_pool_provision_realize_scaffold: Disposition = Scaffold { dissolves_to: RealizationDispatch, bind: DeclarationRef { diff --git a/dag/gunbc/host/host_converge.dag b/dag/gunbc/host/host_converge.dag index 14166272c9d..13dd1c9cbfd 100644 --- a/dag/gunbc/host/host_converge.dag +++ b/dag/gunbc/host/host_converge.dag @@ -47,17 +47,16 @@ import extdeps.systemd.oomd { managed_oom_pressure_limit_show_value, } - data gunbc_cgroup_v2_root: String = "/sys/fs/cgroup" data gunbc_sessions_legacy_parent_slice: String = "system.slice" // THE UNIT NAME AND ITS DROP-IN DIRECTORY COME FROM gunbc.runner_unit AND ARE NO LONGER SPELLED -// HERE. Each of these six rows carried the template name as a literal -- four of them by embedding -// it inside a path -- so the drop-in directory agreed with the unit it configures by convention -// across six independent strings. Renaming the unit would have left four drop-ins writing into a -// directory systemd no longer reads, with nothing to refuse it: a drop-in in the wrong directory is -// not an error, it is simply never applied, so the caps would silently stop being enforced while -// every row here still read correctly. +// HERE. Each of these six rows carried the template name as a literal -- four by embedding it in a +// path -- so the drop-in directory agreed with its unit by convention across six independent +// strings. Renaming the unit would have left four drop-ins writing into a directory systemd no +// longer reads, with nothing to refuse it: a drop-in in the wrong directory is not an error, it is +// simply never applied, so the caps would silently stop being enforced while every row here still +// read correctly. // // The ROW NAMES stay, because the drop-in FILE names -- which knob is 20, which is 30 -- are this // module's own fact and not the unit contract's. @@ -75,15 +74,15 @@ data gunbc_runner_jobserver_service: String = "ctrl-jobserver.service" // THE DAEMON'S FIFO PATH, WHICH THE RUNNER UNIT HAS TO NAME AND NOTHING HERE PREVIOUSLY DID. // // The jobserver's token count already lives beside the env file above; the FIFO those tokens are -// handed out over did not, because no consumer in this repository had to name it until a runner unit -// was rendered from gunbc. It sits here rather than in `extdeps.jobserver` for the reason that -// module's own placement caveat states: the protocol shape is upstream's, this fleet's installed +// handed out over did not, because no consumer here had to name it until a runner unit was +// rendered from gunbc. It sits here rather than in `extdeps.jobserver` for the reason that +// module's placement caveat states: the protocol shape is upstream's, this fleet's installed // daemon is ours. // // It is the DEFAULT. The daemon reads `/etc/default/ctrl-jobserver` and a host may override it -// there, so a render that assumes this path on a host that overrode it would point MAKEFLAGS at a -// FIFO nobody owns. That is why the unit render takes the path as a parameter and this row is what -// the fleet passes, rather than the render reaching for a constant it cannot check. +// there, so a render assuming this path on a host that overrode it would point MAKEFLAGS at a FIFO +// nobody owns. Hence the unit render takes the path as a parameter and this row is what the fleet +// passes, rather than the render reaching for a constant it cannot check. data gunbc_runner_jobserver_default_fifo: String = "/var/lib/ctrl/jobserver/host.fifo" data gunbc_runner_slot_unit_placeholder: NonEmptyStr = "system-actions-runner.slice" @@ -151,7 +150,6 @@ type ConvergeVerdict | Drifted | Absent - fn converge_verdict(effective: String, expected: String) -> ConvergeVerdict { if effective == "" { Absent } else { if effective == expected { VerdictConverged } else { Drifted } } @@ -504,7 +502,12 @@ fn gunbc_pinned_tree_knobs(host: HostIdentity) -> List { [gunbc_pinned_tree_knob(host: host)] } -data codex_runtime_bundle_converge_knob_note: String = "RECURRING reconcile for the pinned Codex provider-runtime bundle — absent materialize, wrong identity replace, present noop. Registered on every fleet compute host in fleet_converge_policy; EXECUTION AUTHORITY is gunbc.fleet_converge_cli converge_provider_runtime_codex_receipt (ProvisionCodexRuntime via realize_provision_codex_runtime), not host_converge_realize interpret_converge_knob — slice1/knob-frontier routes bypass converge_apply today. host_standup P1c remains assimilation-time ensure." +// RECURRING reconcile for the pinned Codex provider-runtime bundle — absent materialize, wrong +// identity replace, present noop. Registered on every fleet compute host in fleet_converge_policy; +// EXECUTION AUTHORITY is gunbc.fleet_converge_cli converge_provider_runtime_codex_receipt +// (ProvisionCodexRuntime via realize_provision_codex_runtime), not host_converge_realize +// interpret_converge_knob — slice1/knob-frontier routes bypass converge_apply today. host_standup +// P1c remains assimilation-time ensure. fn codex_runtime_bundle_knob(host: HostIdentity) -> ConvergeKnob { legacy_converge_knob( @@ -655,9 +658,9 @@ fn session_host_knobs(slice_max: ByteSize, per_session: ByteSize) -> List List } | SessionKnobsNotOwed { cause: String } @@ -738,8 +741,8 @@ fn host_converge_of(h: RunnerHostDeployment, session_knobs: List) // PlanUnsound into ConvergePolicyUnsound before any host was chosen -- so srv2's unestablished // reservation would have blocked converging srv1, srv3 and srv4, none of which depend on it. // -// A refused host contributes NO converge target, which is the point: it is not converged, and it is -// not silently converged with a guessed size either. The refused roster stays readable through +// A refused host contributes NO converge target: it is not converged, and not silently converged +// with a guessed size either. The refused roster stays readable through // fleet_deployment_completion for a consumer that needs to know the fleet is incomplete. fn fleet_converge_policy_from(plan: RunnerDeploymentPlan) -> ConvergePolicy { ConvergePolicyDerived { diff --git a/dag/gunbc/host/host_converge_slice1.dag b/dag/gunbc/host/host_converge_slice1.dag index 295d28f9579..980c9ee68cd 100644 --- a/dag/gunbc/host/host_converge_slice1.dag +++ b/dag/gunbc/host/host_converge_slice1.dag @@ -60,17 +60,23 @@ data gunbc_host_converge_slice1_scope_disposition: Disposition = Terminal { // Operator ruling 2026-08-13: MemoryMax single authority is deploy-time drop-in authoring // (gunbc.host_axis_caps). Convergence must not re-apply it. // -// TWO `data ...: Bool = true` FLAGS USED TO SIT HERE and they are deleted rather than moved. -// They claimed the converge-side apply was suppressed; their only readers were two witnesses -// asserting the literals, so what executed was `true && true`. Nothing in any production path -// consulted them, and the arm they described stayed wired the whole time -// (gunbc.host_converge_realize interpreted a PerSlotMemoryCap knob into a shell body and -// host_effect_realize ran it). The hold is now carried by the absence of that code path, which -// is the construction move the flags were standing in for — see -// gunbc.host_effect_realize converge_in_process_apply_withdrawn_reason. -data host_converge_slice1_memory_max_deploy_authority_note: String = "MemoryMax is authored at deploy through gunbc.host_axis_caps cap membership reconcile and drop-in upsert — not re-applied by gunbc converge. Dual authority (deploy drop-in vs converge set-property) is forbidden." - -data host_converge_slice1_control_shadow_converge_note: String = "Aug-11 root-era set-property wrote /etc/systemd/system.control/.d/50-MemoryMax.conf and 50-MemoryHigh.conf; those drop-ins shadow deploy-authored files until removed. Teardown ordering is author derived drop-ins from gunbc.runner_slot_allocation THEN systemctl revert MemoryMax MemoryHigh — modeled in gunbc.host_axis_caps for the deploy apply path (ghrunner sudo), not gunbc converge on briansrls. Revert-before-author on stale 16/15GiB on-disk deploy drop-ins raises effective caps to the superseded regime." +// TWO `data ...: Bool = true` FLAGS USED TO SIT HERE and are deleted rather than moved. They +// claimed the converge-side apply was suppressed; their only readers were two witnesses +// asserting the literals, so what executed was `true && true`. No production path consulted +// them, and the arm they described stayed wired the whole time (gunbc.host_converge_realize +// interpreted a PerSlotMemoryCap knob into a shell body and host_effect_realize ran it). The +// hold is now carried by the absence of that code path, the construction move the flags stood +// in for — see gunbc.host_effect_realize converge_in_process_apply_withdrawn_reason. +// MemoryMax is authored at deploy through gunbc.host_axis_caps cap membership reconcile and drop-in +// upsert — not re-applied by gunbc converge. Dual authority (deploy drop-in vs converge +// set-property) is forbidden. + +// Aug-11 root-era set-property wrote /etc/systemd/system.control/.d/50-MemoryMax.conf and +// 50-MemoryHigh.conf; those drop-ins shadow deploy-authored files until removed. Teardown ordering +// is author derived drop-ins from gunbc.runner_slot_allocation THEN systemctl revert MemoryMax +// MemoryHigh — modeled in gunbc.host_axis_caps for the deploy apply path (ghrunner sudo), not gunbc +// converge on briansrls. Revert-before-author on stale 16/15GiB on-disk deploy drop-ins raises +// effective caps to the superseded regime. data host_converge_slice1_cap_author_before_shadow_revert_enforced: Bool = true @@ -86,16 +92,15 @@ data host_converge_slice1_knob_name: String = "per_slot_memory_max_bytes" data host_converge_slice1_tasks_knob_name: String = "per_slot_tasks_max" -// TasksMax (gunbc.runner_slot_allocation gunbc_runner_slot_desired, 16384 — the -// 2026-06-21 incident REPAIR value; 512 was the diagnosed EAGAIN outage value, per the -// operator ruling on PR 8045) is the second knob realized on this circuit: same typed -// systemctl show/set-property transports, same fail-closed order (live read -> Drifted -// verdict -> pre-apply TasksCurrent guard -> apply -> independent read-back -> -// reapply-noop), a pids-controller count instead of a byte size. The guard refuses when -// live TasksCurrent meets or exceeds the target cap (>=: equality means zero headroom, -// so the next clone() would EAGAIN) — lowering TasksMax below live usage would not kill -// tasks but would starve every new clone(), the compute_envelope_model failure class — -// typed and retryable, never applied past it. +// TasksMax (gunbc.runner_slot_allocation gunbc_runner_slot_desired, 16384 — the 2026-06-21 +// incident REPAIR value; 512 was the diagnosed EAGAIN outage value, per the operator ruling on +// PR 8045) is the second knob realized on this circuit: same typed systemctl show/set-property +// transports, same fail-closed order (live read -> Drifted verdict -> pre-apply TasksCurrent +// guard -> apply -> independent read-back -> reapply-noop), a pids-controller count instead of +// a byte size. The guard refuses when live TasksCurrent meets or exceeds the target cap (>=: +// equality means zero headroom, so the next clone() would EAGAIN) — lowering TasksMax below +// live usage would not kill tasks but would starve every new clone(), the +// compute_envelope_model failure class — typed and retryable, never applied past it. type HostConvergeSlice1TasksMaxReceipt { host: HostIdentity unit: NonEmptyStr @@ -267,7 +272,8 @@ fn host_converge_slice1_enumerate_units(instance_glob: String) -> List, @@ -439,14 +445,14 @@ fn host_converge_slice1_tasks_read_limit(unit: NonEmptyStr) -> SystemdCgroupTask } } -// The reapply-noop second write intentionally bypasses the TasksCurrent guard: it is only -// reachable after an independent read proved the effective TasksMax already equals the -// desired value, and re-applying the effective value cannot lower headroom (TasksCurrent -// is always <= the effective cap at the moment of the read). The read-back and this -// second write are NOT atomic — concurrent task creation can raise TasksCurrent between -// them — but the EAGAIN-starvation class the guard exists for requires setting the cap -// BELOW current, which re-applying the already-effective value cannot cause regardless -// of concurrent spawns (review 50754 wording). Mirrors the memory circuit's reapply-noop. +// The reapply-noop second write intentionally bypasses the TasksCurrent guard: it is reachable +// only after an independent read proved the effective TasksMax already equals the desired value, +// and re-applying the effective value cannot lower headroom (TasksCurrent is always <= the +// effective cap at the moment of the read). The read-back and this write are NOT atomic — +// concurrent task creation can raise TasksCurrent between them — but the EAGAIN-starvation class +// the guard exists for requires setting the cap BELOW current, which re-applying the +// already-effective value cannot cause regardless of concurrent spawns (review 50754 wording). +// Mirrors the memory circuit's reapply-noop. fn host_converge_slice1_prove_tasks_reapply_noop(unit: NonEmptyStr, knob: ConvergeKnob) -> String? { if !host_converge_slice1_apply_tasks_max(unit: unit, value: knob.desired) { Present { diff --git a/dag/gunbc/host/host_diagnostic_channel.dag b/dag/gunbc/host/host_diagnostic_channel.dag index bf8fcf7474c..7f94ebbeea3 100644 --- a/dag/gunbc/host/host_diagnostic_channel.dag +++ b/dag/gunbc/host/host_diagnostic_channel.dag @@ -149,6 +149,29 @@ fn projection_absence_establishes_source_absence() -> Bool { false } +// Executed 2026-08-11 against srv3 (192.168.1.192) and srv4 (192.168.1.195), ALTRAD8UD-1L2T / +// Ampere Altra Max M128-30. FIVE authorities were read, and the three most diagnostic ones were +// unreachable from Redfish. (1) RedfishMessageRegistry: 'Base.1.16.0.InternalError' from +// Bios.ResetBios, the only identifier of the five that names its own registry and version. (2) +// OpenBmcPhosphorNamespace: 'xyz.openbmc_project.State.Shutdown.Power.Error.Blackout' in the +// Redfish EventLog. (3) AmpereHostFirmwarePostCode: the repeating signature P012 -> E588 -> P029, +// read ONLY from /var/log/obmc-console.log over authenticated BMC SSH — the Redfish HostLogger +// LogService returned Members@odata.count=0 for the same period, so a Redfish-only consumer saw an +// empty console while the disk file held every cycle. (4) AmpereDramReferenceCode: 'MC3:R0: WRLVL +// Err=0 Stat=0x0 NibbleMsk=0xfc0', 'SK[0]: 001200ff', 'MC[0..7]: 00f100b4', 'DDR initialization +// failure. Can't fall back!' — same console-only transport; this is the evidence that identified +// the BIOS 2.06 memory reference code as the fault and it was invisible to Redfish. (5) +// VendorScriptFreeText: firmware_update.sh's 'Invalid ExtendedVersion: primary-all. Please check +// MANIFEST file!' in the BMC journal, which was the ONLY statement of why a firmware push failed — +// Redfish reported TaskState=Exception with the message 'The task with Id 0 has completed with +// errors' and no cause whatsoever. THE STANDING CONSEQUENCE: any convergence that consumes only +// Redfish can observe THAT something failed and never WHY, so BMC SSH is a required diagnostic +// transport for this fleet rather than a debugging convenience. E588 and P012 remain +// OpaqueStableIdentifier: no ASRock or Ampere postcode table has been obtained (both published +// manuals are PDFs whose text this repository could not extract, and the vendor support site is +// behind bot protection), so their meaning is UNCLAIMED. They may be compared and counted; they may +// not be interpreted. + // A convergence run that reads ONLY Redfish and finds nothing has not // established that the host is healthy. Both hosts in this incident sat in // deterministic reset loops while Redfish reported BootProgress=None, an @@ -157,5 +180,3 @@ fn projection_absence_establishes_source_absence() -> Bool { fn redfish_only_observation_is_complete() -> Bool { false } - -data host_diagnostic_channel_incident_receipt: String = "Executed 2026-08-11 against srv3 (192.168.1.192) and srv4 (192.168.1.195), ALTRAD8UD-1L2T / Ampere Altra Max M128-30. FIVE authorities were read, and the three most diagnostic ones were unreachable from Redfish. (1) RedfishMessageRegistry: 'Base.1.16.0.InternalError' from Bios.ResetBios, the only identifier of the five that names its own registry and version. (2) OpenBmcPhosphorNamespace: 'xyz.openbmc_project.State.Shutdown.Power.Error.Blackout' in the Redfish EventLog. (3) AmpereHostFirmwarePostCode: the repeating signature P012 -> E588 -> P029, read ONLY from /var/log/obmc-console.log over authenticated BMC SSH — the Redfish HostLogger LogService returned Members@odata.count=0 for the same period, so a Redfish-only consumer saw an empty console while the disk file held every cycle. (4) AmpereDramReferenceCode: 'MC3:R0: WRLVL Err=0 Stat=0x0 NibbleMsk=0xfc0', 'SK[0]: 001200ff', 'MC[0..7]: 00f100b4', 'DDR initialization failure. Can't fall back!' — same console-only transport; this is the evidence that identified the BIOS 2.06 memory reference code as the fault and it was invisible to Redfish. (5) VendorScriptFreeText: firmware_update.sh's 'Invalid ExtendedVersion: primary-all. Please check MANIFEST file!' in the BMC journal, which was the ONLY statement of why a firmware push failed — Redfish reported TaskState=Exception with the message 'The task with Id 0 has completed with errors' and no cause whatsoever. THE STANDING CONSEQUENCE: any convergence that consumes only Redfish can observe THAT something failed and never WHY, so BMC SSH is a required diagnostic transport for this fleet rather than a debugging convenience. E588 and P012 remain OpaqueStableIdentifier: no ASRock or Ampere postcode table has been obtained (both published manuals are PDFs whose text this repository could not extract, and the vendor support site is behind bot protection), so their meaning is UNCLAIMED. They may be compared and counted; they may not be interpreted." diff --git a/dag/gunbc/host/host_disk_reclaim.dag b/dag/gunbc/host/host_disk_reclaim.dag index c20a1649981..1cb7148b9c8 100644 --- a/dag/gunbc/host/host_disk_reclaim.dag +++ b/dag/gunbc/host/host_disk_reclaim.dag @@ -4,41 +4,44 @@ import std.types { String, NonEmptyStr, Bool, Int, List } import std.measure { ByteSize, byte_size, byte_size_count, Second, second, seconds_per_day } import gunbc.build_cache_instance { RunnerSlotIdentity } -// THE FLEET'S DISK CAPACITY MECHANISM, SUBSUMED. This module models what -// ctrl-runner-reclaim.timer has been doing on every runner host since 2026-07-23, read from the -// script itself (scripts/session-dashboard/host/reclaim-runner-disk.sh and its .service/.timer) -// rather than from a description of it. It follows the precedent gunbc.runner_lifecycle -// runner_lifecycle_ctrl_void_note sets for exactly this situation: READ the ctrl artifact as ground -// truth for what the host actually does, subsume it here, and never invoke ctrl from gunbc. +// THE FLEET'S DISK CAPACITY MECHANISM, SUBSUMED. This module models what ctrl-runner-reclaim.timer +// has done on every runner host since 2026-07-23, read from the script itself +// (scripts/session-dashboard/host/reclaim-runner-disk.sh and its .service/.timer), not from a +// description. It follows the precedent gunbc.runner_lifecycle runner_lifecycle_ctrl_void_note +// sets: READ the ctrl artifact as ground truth for what the host does, subsume it here, never +// invoke ctrl from gunbc. // -// WHY THIS IS A FABRIC CONCERN AND NOT HOUSEKEEPING. The runners are ephemeral in IDENTITY -- a +// WHY THIS IS A FABRIC CONCERN AND NOT HOUSEKEEPING. The runners are ephemeral in IDENTITY -- // one-shot JIT registration per job -- but their WORKSPACE is deliberately persistent: each slot // keeps a fixed _work checkout so CI does not re-clone the ~10G repo and rebuild cold every job. -// That trade was taken on purpose and nothing bounded its cost side, so a slot's own growth is a -// standing claim on the host's disk. A fabric that allocates slots without modeling that claim is -// allocating against a number that drifts underneath it. +// Nothing bounded that trade's cost side, so a slot's own growth is a standing claim on the host's +// disk, and a fabric allocating slots without modeling it allocates against a number that drifts +// underneath it. // // AND IT IS ALREADY LOAD-BEARING, WHICH IS THE REASON TO MODEL IT RATHER THAN REPLACE IT. Measured -// on srv4 (operator, 2026-08-22): packs are 15-46 per checkout against the ~370 the script's own -// header describes, and slots are 2.5G against the 13G it describes. The slots are small BECAUSE -// this runs. Deleting it re-opens a 5x disk regression on every host, so this model exists to give -// the mechanism a home in the substrate, not to supersede a working thing with an unproven one. +// on srv4 (operator, 2026-08-22): packs are 15-46 per checkout against the ~370 the script's header +// describes, slots 2.5G against the 13G it describes. The slots are small BECAUSE this runs. +// Deleting it re-opens a 5x disk regression on every host, so this model gives the mechanism a home +// in the substrate rather than superseding a working thing with an unproven one. // -// NOT A SECOND HYGIENE AUTHORITY. gunbc.host_hygiene_reaper is a DIFFERENT mechanism that shares -// the word: it reaps residual SLOTS -- stale cgroups, inactive units, control-override dirs -- and -// carries zero coverage of packs, repack, tmp_pack, _diag or docker (verified by grep against both -// it and its _remediate half, all six terms zero). Treating one as the other's successor is the -// one-name-two-concepts error, so this module states the boundary rather than assuming a reader -// infers it. -data host_disk_reclaim_subsumption_note: String = "Subsumes ctrl-runner-reclaim.timer / .service / reclaim-runner-disk.sh, read as ground truth per gunbc.runner_lifecycle runner_lifecycle_ctrl_void_note. The script is the authority for WHAT the host does today; this module is the authority for what the fabric believes about it. Disjoint from gunbc.host_hygiene_reaper, which reaps residual slots and models no disk reclamation." - -// THE FIVE GROWTH PATHS, each a separate arm because each has a different owner and a different -// repair. Four are the accretion the script's header names; the fifth is a WEDGE rather than -// growth, and it is the one a summary of this mechanism is most likely to drop: a review killed -// mid-run leaks a refs/heads/review/* ref, and once its target object goes missing `git gc` dies -// with a bad-object error AND writes a .git/gc.log that DISABLES ALL FUTURE AUTO-GC. So the failure -// is self-perpetuating: the mechanism that would reclaim the disk is exactly what the wedge turns -// off, and every subsequent review on that runner stays wedged until a human intervenes. +// NOT A SECOND HYGIENE AUTHORITY. gunbc.host_hygiene_reaper is a DIFFERENT mechanism sharing the +// word: it reaps residual SLOTS -- stale cgroups, inactive units, control-override dirs -- and +// covers none of packs, repack, tmp_pack, _diag or docker (verified by grep against it and its +// _remediate half, all six terms zero). Treating one as the other's successor is the +// one-name-two-concepts error, so the boundary is stated here. +// +// Subsumes ctrl-runner-reclaim.timer / .service / reclaim-runner-disk.sh, read as ground truth per +// gunbc.runner_lifecycle runner_lifecycle_ctrl_void_note. The script is the authority for WHAT the +// host does today; this module for what the fabric believes about it. Disjoint from +// gunbc.host_hygiene_reaper, which reaps residual slots and models no disk reclamation. + +// THE FIVE GROWTH PATHS, each a separate arm because each has a different owner and repair. Four +// are the accretion the script's header names; the fifth is a WEDGE, not growth, and the one a +// summary is most likely to drop: a review killed mid-run leaks a refs/heads/review/* ref, and once +// its target object goes missing `git gc` dies with a bad-object error AND writes a .git/gc.log +// that DISABLES ALL FUTURE AUTO-GC. The failure is self-perpetuating: the mechanism that would +// reclaim the disk is exactly what the wedge turns off, and every subsequent review on that runner +// stays wedged until a human intervenes. type DiskReclaimConcern = UnrepackedPackAccretion { pack_count: Int } | InterruptedFetchResidue { tmp_pack_count: Int } @@ -50,23 +53,22 @@ type StoreWedgeCause = BrokenReviewRef { ref_name: NonEmptyStr } | PoisonedGcLog -// A SLOT RUNNING A JOB IS NOT A SLOT WHOSE STATE IS UNKNOWN, and collapsing the two is what would -// make this mechanism unsafe. The script decides busy-ness by cgroup -- every Runner.Worker lives -// in its slot's actions-runner@.service cgroup -- which is an observation that can itself -// fail. Busy means "do not touch git here"; unobservable means "we could not tell", and both refuse -// git work while being different facts with different repairs (DESIGN section 5: a failure arm -// refuses, it never widens, and could-not-observe must never render as a verdict about the subject). -// WHY THIS IS NOT std.upsert_decision.ObservationVerdict, asked by review 54879 and worth answering -// at the declaration because the two genuinely are adjacent. ObservationVerdict answers "does this -// subject AGREE WITH ITS DESIRED STATE" -- Converged, Drifted, Conflict, Inaccessible, UnknownRefused -// -- and it is the right authority wherever a fabric cell is being reconciled. SlotJobState answers -// something else entirely: what is this slot DOING RIGHT NOW, as an INPUT to a decision about whether -// we may touch it. A busy slot is not drifted and not unconverged; it is working exactly as intended, -// and reporting it as a convergence verdict would make correct operation read as a fault. The overlap -// is real but partial -- the unobservable arm alone rhymes with Inaccessible -- and grounding a -// whole three-arm operational state on a six-arm convergence vocabulary to capture one arm would be -// the hollow alias, not single authority. The import that prompted the question was simply dead and -// is deleted. +// A SLOT RUNNING A JOB IS NOT A SLOT WHOSE STATE IS UNKNOWN, and collapsing the two would make this +// mechanism unsafe. The script decides busy-ness by cgroup -- every Runner.Worker lives in its +// slot's actions-runner@.service cgroup -- an observation that can itself fail. Busy means +// "do not touch git here"; unobservable means "we could not tell"; both refuse git work while being +// different facts with different repairs (DESIGN section 5: a failure arm refuses, never widens, +// and could-not-observe must never render as a verdict about the subject). +// +// WHY THIS IS NOT std.upsert_decision.ObservationVerdict, asked by review 54879; the two are +// genuinely adjacent. ObservationVerdict answers "does this subject AGREE WITH ITS DESIRED STATE" +// -- Converged, Drifted, Conflict, Inaccessible, UnknownRefused -- and is the right authority +// wherever a fabric cell is reconciled. SlotJobState answers what this slot is DOING RIGHT NOW, as +// an INPUT to whether we may touch it. A busy slot is not drifted or unconverged; it is working as +// intended, and a convergence verdict would make correct operation read as a fault. The overlap is +// partial -- only the unobservable arm rhymes with Inaccessible -- and grounding a three-arm +// operational state on a six-arm convergence vocabulary to capture one arm would be the hollow +// alias, not single authority. The import that prompted the question was dead and is deleted. type SlotJobState = SlotRunningJob { worker_cgroup_unit: NonEmptyStr } | SlotIdle @@ -88,9 +90,9 @@ type SlotDiskObservation { } // THE PARAMETERS ARE THE SCRIPT'S DEFAULTS, CARRIED AS DATA RATHER THAN RESTATED IN PROSE, so a -// consumer reads the same number the host runs on. Each is an operator override in the real unit -// (EnvironmentFile=-/etc/default/ctrl-runner-reclaim), which is why they are declarations here and -// not literals inlined at their use sites. +// consumer reads the number the host runs on. Each is an operator override in the real unit +// (EnvironmentFile=-/etc/default/ctrl-runner-reclaim), hence declarations rather than literals at +// use sites. data reclaim_fragmentation_gate_packs: Int = 8 data reclaim_diag_max_age: Second = second(count: 3 * seconds_per_day()) @@ -99,21 +101,24 @@ data reclaim_tmp_pack_min_age: Second = second(count: 1 * seconds_per_day()) data reclaim_per_repo_timeout: Second = second(count: 1800) -// THE TWO AGE FLOORS ARE DURATIONS AND NOW CARRY ONE (review 54879). They first landed as -// `_days: Int`, a flat scalar with the unit in the identifier -- which is the unit-modeling trigger, -// and it sat directly beside `reclaim_per_repo_timeout: Second` doing the same job correctly, so -// the file disagreed with itself about whether a duration has a carrier. The day grain the script -// authors in is preserved by CONSTRUCTION rather than by a hand-computed literal: 3 * seconds_per_day() -// is legible as "three days" and cannot drift from the second count the way a transcribed 259200 -// could. seconds_per_day is composed in std.measure from the ISO 8601 authority that already grounds -// seconds-per-minute and minutes-per-hour there; hours-per-day was missing from that citation and is -// added to it, so no new constant is minted here or anywhere. -data reclaim_parameters_note: String = "RECLAIM_GIT_GC_MIN_PACKS=8, RECLAIM_DIAG_MAX_AGE_DAYS=3, RECLAIM_GIT_GC_TIMEOUT_S=1800, and the tmp_pack sweep's -mtime +1. The pack gate is a FRAGMENTATION gate, not a size gate: a repo at or below it is skipped because the I/O is not worth it, which is what makes a re-run cheap and idempotent. The tmp_pack age floor exists so the sweep can never race a live index-pack." - -// THE DECISION IS FOUR-WAY AND THE TWO REFUSALS ARE NOT ONE ARM. Warranted and below-gate are both +// THE TWO AGE FLOORS ARE DURATIONS AND NOW CARRY ONE (review 54879). They first landed as `_days: +// Int`, a flat scalar with the unit in the identifier -- the unit-modeling trigger -- directly +// beside `reclaim_per_repo_timeout: Second` doing the same job correctly, so the file disagreed +// with itself about whether a duration has a carrier. The script's day grain is preserved by +// CONSTRUCTION: 3 * seconds_per_day() is legible as "three days" and cannot drift from the second +// count the way a transcribed 259200 could. seconds_per_day is composed in std.measure from the ISO +// 8601 authority that already grounds seconds-per-minute and minutes-per-hour; hours-per-day was +// missing from that citation and is added to it, so no new constant is minted. +// +// RECLAIM_GIT_GC_MIN_PACKS=8, RECLAIM_DIAG_MAX_AGE_DAYS=3, RECLAIM_GIT_GC_TIMEOUT_S=1800, and the +// tmp_pack sweep's -mtime +1. The pack gate is a FRAGMENTATION gate, not a size gate: a repo at or +// below it is skipped because the I/O is not worth it, which makes a re-run cheap and idempotent. +// The tmp_pack age floor exists so the sweep can never race a live index-pack. + +// THE DECISION IS FOUR-WAY AND THE TWO REFUSALS ARE NOT ONE ARM. Warranted and below-gate are // answers about the repo; busy and unobservable are answers about whether we may ask at all. An -// earlier shape that returned a Bool could not say which, and a consumer reading false could not -// tell "already packed" from "we did not look". +// earlier Bool shape could not say which, and a consumer reading false could not tell "already +// packed" from "we did not look". type RepackDecision = RepackWarranted { git_dir: NonEmptyStr, owner: NonEmptyStr, pack_count: Int } | RepackBelowFragmentationGate { git_dir: NonEmptyStr, pack_count: Int } @@ -143,10 +148,10 @@ fn slot_repack_decisions(obs: SlotDiskObservation) -> List { map(obs.checkouts, c => repack_decision_for(job_state: obs.job_state, checkout: c)) } -// THE TMP_PACK SWEEP AND THE DIAG ROTATION RUN REGARDLESS OF BUSY-NESS, and that is a property of -// the subjects rather than an exception to the safety rule: neither is job state. A leftover -// tmp_pack_* is pure garbage from an interrupted index-pack, and _diag holds logs, so removing -// either cannot disturb a running job. Only the git object store is serialised against live work. +// THE TMP_PACK SWEEP AND THE DIAG ROTATION RUN REGARDLESS OF BUSY-NESS — a property of the +// subjects, not an exception to the safety rule: neither is job state. A leftover tmp_pack_* is +// garbage from an interrupted index-pack and _diag holds logs, so removing either cannot disturb a +// running job. Only the git object store is serialised against live work. fn slot_has_interrupted_fetch_residue(obs: SlotDiskObservation) -> Bool { fold_list( xs: obs.checkouts, @@ -155,20 +160,31 @@ fn slot_has_interrupted_fetch_residue(obs: SlotDiskObservation) -> Bool { ) } -data reclaim_unconditional_arms_note: String = "tmp_pack sweep and _diag rotation are not gated on slot busy-ness because their subjects are not job state -- an interrupted index-pack's leftover and a log file are safe to remove under a running job. The git object store is the only subject serialised against live work, and it is serialised twice over: skipped entirely for a busy slot, and left to git's own repo lock otherwise." - -// WHAT THE FABRIC ACTUALLY WANTS FROM THIS: a per-slot disk claim derived from OBSERVATION rather -// than from arithmetic. gunbc.runner_lifecycle runner_slot_disk_budget_per_slot is today a single -// literal, byte_size(40960000000), whose own annotation derives it as "~38GB per slot at width 7 on -// 500GB class disk" -- that is the disk divided by the width you want, then used to preflight -// whether that width fits, so the charge and the question are the same number. Measured reality on -// srv4 is ~2.5G per slot, about sixteen times smaller, and the literal post-dates this reclaimer by -// two weeks -- so it was never a stale measurement, it was never a measurement at all. +// tmp_pack sweep and _diag rotation are not gated on slot busy-ness because their subjects are not +// job state -- an interrupted index-pack's leftover and a log file are safe to remove under a +// running job. The git object store is the only subject serialised against live work, twice over: +// skipped entirely for a busy slot, and left to git's own repo lock otherwise. + +// THE INTENDED CONSUMER, named so this fold is not mistaken for a report. gunbc.runner_lifecycle +// runner_slot_disk_budget_per_slot is circular -- disk divided by desired width, used to preflight +// that width -- and measures ~16x the observed per-slot occupancy. slot_observed_bytes is the +// observed quantity that replaces it. NOT DONE IN THIS MODULE, by operator sequencing 2026-08-22: +// the reclaimer is subsumed first, because the width literals are downstream of whether reclamation +// runs at all; a width charge derived from observation while the mechanism holding that observation +// down is unmodeled would rest on an unmodeled dependency. + +// WHAT THE FABRIC ACTUALLY WANTS FROM THIS: a per-slot disk claim derived from OBSERVATION, not +// arithmetic. gunbc.runner_lifecycle runner_slot_disk_budget_per_slot is today a single literal, +// byte_size(40960000000), whose annotation derives it as "~38GB per slot at width 7 on 500GB class +// disk" -- disk divided by the width you want, then used to preflight whether that width fits, so +// charge and question are the same number. Measured reality on srv4 is ~2.5G per slot, about +// sixteen times smaller, and the literal post-dates this reclaimer by two weeks -- never a stale +// measurement, never a measurement at all. // -// This fold is the replacement's input, not the replacement: it says what a slot currently occupies, -// which is the quantity a width preflight should charge. Retiring the literal is deliberately NOT -// done here, because the operator's sequencing is that the reclaimer is subsumed before anything -// touches the width numbers -- those numbers are downstream of whether this mechanism runs. +// This fold is the replacement's input, not the replacement: it says what a slot currently +// occupies, the quantity a width preflight should charge. Retiring the literal is deliberately NOT +// done here: the operator's sequencing is that the reclaimer is subsumed before anything touches +// the width numbers, which are downstream of whether this mechanism runs. fn slot_observed_bytes(obs: SlotDiskObservation) -> ByteSize { byte_size( count: fold_list( @@ -178,5 +194,3 @@ fn slot_observed_bytes(obs: SlotDiskObservation) -> ByteSize { ), ) } - -data reclaim_budget_supersession_note: String = "THE INTENDED CONSUMER, named so this fold is not mistaken for a report. gunbc.runner_lifecycle runner_slot_disk_budget_per_slot is circular -- disk divided by desired width, used to preflight that width -- and measures ~16x the observed per-slot occupancy. slot_observed_bytes is the observed quantity that replaces it. NOT DONE IN THIS MODULE, by operator sequencing 2026-08-22: the reclaimer is subsumed first, because the width literals are downstream of whether reclamation runs at all. A width charge derived from observation while the mechanism holding that observation down is unmodeled would be a number resting on an unmodeled dependency." diff --git a/dag/gunbc/host/host_disk_reclaim_units.dag b/dag/gunbc/host/host_disk_reclaim_units.dag index ebb6cbba387..cffd0ee566e 100644 --- a/dag/gunbc/host/host_disk_reclaim_units.dag +++ b/dag/gunbc/host/host_disk_reclaim_units.dag @@ -27,7 +27,8 @@ import gunbc.host_disk_reclaim { reclaim_per_repo_timeout } // directives this needs -- Nice, IOSchedulingClass, TimeoutStartSec -- did not exist on // SystemdServiceDirective and were added there, because a renderer that cannot express them still // produces a unit that runs and simply competes with live CI while doing so. -data host_disk_reclaim_units_note: String = "Service and timer for the fleet-wide runner disk reclaimer, rendered from extdeps.systemd.unit_file. Cadence and refusal semantics are gunbc.host_disk_reclaim." +// Service and timer for the fleet-wide runner disk reclaimer, rendered from +// extdeps.systemd.unit_file. Cadence and refusal semantics are gunbc.host_disk_reclaim. // TWO DIRECTIVES ON THE INCUMBENT UNIT ARE DELIBERATELY NOT RENDERED, AND THE SECOND IS THE POINT. // diff --git a/dag/gunbc/host/host_effect_realize.dag b/dag/gunbc/host/host_effect_realize.dag index 5e8c76a9c76..dc852682995 100644 --- a/dag/gunbc/host/host_effect_realize.dag +++ b/dag/gunbc/host/host_effect_realize.dag @@ -1156,7 +1156,20 @@ fn provision_build_cache_reconcile( ) } -data provision_codex_runtime_typed_step_note: String = "ProvisionCodexRuntime observe/materialize/read-back replaced the concat-built bash body of the now-deleted host_codex_runtime_provision_script.dag with three typed single-command steps: LocalShell dispatches through the existing typed argv services gunbc.WitnessBin.Run (observe/read-back) and gunbc.Cli.Run (materialize) — argv arrays, not a shell string, so there is no branching or receipt-marker text to construct or parse; SshShell/FleetSsh dispatch through run_typed_argv_transport with the same argv shape built by gunbc.host_codex_runtime_provision_verdict codex_runtime_observe_argv and gunbc.cli_invoke gunbc_run_transport_argv. The mkdir-release-parent step the old shell body ran before materialize is dropped: gunbc.package_delivery release_codex_runtime_bundle_immutable already runs shell.Mkdir.Parents on release_parent inside materialize_codex_runtime_bundle's own call chain, so a second mkdir ahead of it was redundant work (DESIGN §2), not preserved behavior. Read-back reuses the observe step verbatim — 'we ran materialize' is never treated as 'it is installed'; only a second successful --version probe that satisfies codex_runtime_version_identity_satisfied converges." +// ProvisionCodexRuntime observe/materialize/read-back replaced the concat-built bash body of the +// now-deleted host_codex_runtime_provision_script.dag with three typed single-command steps: +// LocalShell dispatches through the existing typed argv services gunbc.WitnessBin.Run +// (observe/read-back) and gunbc.Cli.Run (materialize) — argv arrays, not a shell string, so there +// is no branching or receipt-marker text to construct or parse; SshShell/FleetSsh dispatch through +// run_typed_argv_transport with the same argv shape built by +// gunbc.host_codex_runtime_provision_verdict codex_runtime_observe_argv and gunbc.cli_invoke +// gunbc_run_transport_argv. The mkdir-release-parent step the old shell body ran before materialize +// is dropped: gunbc.package_delivery release_codex_runtime_bundle_immutable already runs +// shell.Mkdir.Parents on release_parent inside materialize_codex_runtime_bundle's own call chain, +// so a second mkdir ahead of it was redundant work (DESIGN §2), not preserved behavior. Read-back +// reuses the observe step verbatim — 'we ran materialize' is never treated as 'it is installed'; +// only a second successful --version probe that satisfies codex_runtime_version_identity_satisfied +// converges. type CodexRuntimeStepResult = CodexRuntimeStepRan { observed: ShellExecObserved } @@ -1436,8 +1449,27 @@ fn realize_provision_codex_runtime( } } -data srv3_actuator_toolchain_emit_artifact_transport_helper_note: String = "realize_os_install_actuator_toolchain_ensure refuses EmitArtifactThenThinRun at entry; srv3_actuator_toolchain_emit_artifact_transport_helper_refused false arms are fail-closed backstop only — not a silent widen (DESIGN §5). EmitArtifactThenThinRun defers execution to a materialized artifact run elsewhere, so an inline bin-witness probe has no host to observe; refusing here is a permanent boundary of that transport, not unmigrated debt.\\n\\nThe five probe leaves that used to build a concatenated shell command string (bin-witness success, test -x, which-based tool presence, which-based tool bin path, chown) now carry typed operation_argv rows and route SshShell and FleetSsh through typed_argv_exec_over_ssh / typed_argv_exec_over_fleet_ssh (gunbc.typed_argv_exec), the same wall the principal, sudo-probe and grant-list leaves already used. FleetSsh is admitted at realize_os_install_actuator_toolchain_ensure's entry gate.\\n\\nRUNG, STATED HONESTLY BECAUSE THE OBVIOUS READING OVERSTATES IT. The FleetSsh arms are compile-verified only: the match is exhaustive and each arm demonstrably reaches typed dispatch, but NO EXECUTING WITNESS DISCRIMINATES THEM, and the reason is structural rather than an omission. A FleetSshExecutionContext is reachable only through materialize_fleet_known_hosts_anchor, which is a `func` using Network, and the KnownHostsAnchor seal admits that one declaration alone -- so a pure `test fn` cannot construct the transport these arms match on. THE SEAL THAT MAKES A FLEET CONTEXT UNFORGEABLE ALSO MAKES IT UNFIXTURABLE. That is a real cost of the wall and it is recorded here rather than paid down by widening the wall, because admitting a test declaration to the seal to make one's own change testable is the trade a reviewer should refuse. NEXT-RUNG TRIGGER: a fixture route to a context that does not weaken the production seal -- an effect-level test harness for the materializer, not a second minter." - +// realize_os_install_actuator_toolchain_ensure refuses EmitArtifactThenThinRun at entry; +// srv3_actuator_toolchain_emit_artifact_transport_helper_refused false arms are fail-closed +// backstop only — not a silent widen (DESIGN §5). EmitArtifactThenThinRun defers execution to a +// materialized artifact run elsewhere, so an inline bin-witness probe has no host to observe; +// refusing here is a permanent boundary of that transport, not unmigrated debt.\n\nThe five probe +// leaves that used to build a concatenated shell command string (bin-witness success, test -x, +// which-based tool presence, which-based tool bin path, chown) now carry typed operation_argv rows +// and route SshShell and FleetSsh through typed_argv_exec_over_ssh / typed_argv_exec_over_fleet_ssh +// (gunbc.typed_argv_exec), the same wall the principal, sudo-probe and grant-list leaves already +// used. FleetSsh is admitted at realize_os_install_actuator_toolchain_ensure's entry gate.\n\nRUNG, +// STATED HONESTLY BECAUSE THE OBVIOUS READING OVERSTATES IT. The FleetSsh arms are compile-verified +// only: the match is exhaustive and each arm demonstrably reaches typed dispatch, but NO EXECUTING +// WITNESS DISCRIMINATES THEM, and the reason is structural rather than an omission. A +// FleetSshExecutionContext is reachable only through materialize_fleet_known_hosts_anchor, which is +// a `func` using Network, and the KnownHostsAnchor seal admits that one declaration alone -- so a +// pure `test fn` cannot construct the transport these arms match on. THE SEAL THAT MAKES A FLEET +// CONTEXT UNFORGEABLE ALSO MAKES IT UNFIXTURABLE. That is a real cost of the wall and it is +// recorded here rather than paid down by widening the wall, because admitting a test declaration to +// the seal to make one's own change testable is the trade a reviewer should refuse. NEXT-RUNG +// TRIGGER: a fixture route to a context that does not weaken the production seal -- an effect-level +// test harness for the materializer, not a second minter. // THE THREE-VALUED COLLAPSE IS CLOSED. This is where the roster of five collapse sites and six // consumers stood, together with its dissolution condition: "DISSOLVES WHEN the sites return an @@ -1451,7 +1483,6 @@ data srv3_actuator_toolchain_emit_artifact_transport_helper_note: String = "real // typechecker at a call site. The projection is gone; the consumers match all three arms, and // exhaustiveness is what keeps them matching them. - // A host probe answers one of three things, and the third is not a shade of the second: // the predicate held, the predicate failed, or the transport could not carry the probe at all. // Collapsing Unobserved into Fails is what let a refused probe read as "the tool is absent" and @@ -1503,8 +1534,13 @@ fn srv3_argv_with_bin(bin_path: String, args: List) -> List { list_append(left: [bin_path], right: args) } - -data srv3_transport_witness_bin_nonroot_workdir_note: String = "typed_argv_exec_over_\{ssh,fleet_ssh} carries a bare argv with no shell to `cd` first, so a non-root workdir has no typed form to hand it. Every in-tree caller passes workdir: \"/\" (checked at review time), so this arm is unreachable dead debt today -- named rather than folded into the workdir == \"/\" condition so a non-root workdir renders as a located refusal instead of an indistinguishable-from-measured `false` (DESIGN §5, fabricated negative). DISSOLVES WHEN a typed remote cd/chdir primitive lands and this reroutes to it, or when workdir is dropped from the signature because no caller ever varies it." +// typed_argv_exec_over_{ssh,fleet_ssh} carries a bare argv with no shell to `cd` first, so a +// non-root workdir has no typed form to hand it. Every in-tree caller passes workdir: "/" (checked +// at review time), so this arm is unreachable dead debt today -- named rather than folded into the +// workdir == "/" condition so a non-root workdir renders as a located refusal instead of an +// indistinguishable-from-measured `false` (DESIGN §5, fabricated negative). DISSOLVES WHEN a typed +// remote cd/chdir primitive lands and this reroutes to it, or when workdir is dropped from the +// signature because no caller ever varies it. fn srv3_transport_witness_bin_success( transport: HostEffectTransport, @@ -2194,7 +2230,6 @@ fn srv3_apt_install_then_reprobe( } } - // THE SEQUENCE IS A VALUE, SO THE EVIDENCE CAN ASSERT WHAT RAN. // // The defect this replaces: srv3_ensure_websocat bound mkdir, chown, curl and chmod as four lets @@ -2733,9 +2768,32 @@ fn realize_os_install_actuator_toolchain_ensure( } } -data build_cache_install_authority_note: String = "The ensure arm installs sccache by calling the cited authority extdeps.cache.sccache.sccache_install_script (pinned release, per-arch sha256 verified, landing at sccache_installed_binary_path) — it does NOT hand-roll a package-manager install. Until 2026-07-24 this module ran `sudo -n apt-get install -y sccache` and wrote a unit with ExecStart=/usr/bin/sccache, a §3 fork of the same concept: P1:runner-deploy-slot installs the pinned tarball to /usr/local/bin and then P1b:build-cache-provision pointed its unit at a path that install never creates, so the daemon could not start and the read-back refused. Both the install and the unit now read ONE path row, so the two phases cannot disagree." - -data build_cache_ensure_scope_swap_note: String = "This ensure body writes a SYSTEM-scope unit (/etc/systemd/system/, WantedBy=multi-user.target, per render_build_cache_unit) rather than the user-scope unit (~/.config/systemd/user, enable-linger) the deleted gunbc.host_build_cache_provision_script builder used. That earlier form needed `sudo -n loginctl enable-linger \"$USER\"` to survive the provisioning ssh connection closing, which is real shell this render's system scope makes unnecessary — a `systemd --user` manager has no login to outlive when the unit is system-scoped. This is a consequence of the A5 grain swap (EnsureBuildCacheInstance { desired: BuildCacheInstance }), not an independently decided handler choice: BuildCacheInstance.intended_unit is a single row (\"gunbc-build-cache-ci.service\", WantedBy=multi-user.target) already grounded on a live srv1 capture, and the render is the single authority for the unit body, so writing it anywhere but where the render says WantedBy points is a second representation of the same fact. The install/enable/restart sequence reuses gunbc.live_deploy.operations' existing typed-argv-backed command builders (systemctl_daemon_reload_command / systemctl_enable_command / systemctl_restart_command, each privileged: true) rather than hand-spelling new sudo -n systemctl strings, matching the idiom already established in gunbc.live_deploy.emit and gunbc.host_axis_caps — a further §3 de-fork consistent with why this swap exists." +// The ensure arm installs sccache by calling the cited authority +// extdeps.cache.sccache.sccache_install_script (pinned release, per-arch sha256 verified, landing +// at sccache_installed_binary_path) — it does NOT hand-roll a package-manager install. Until +// 2026-07-24 this module ran `sudo -n apt-get install -y sccache` and wrote a unit with +// ExecStart=/usr/bin/sccache, a §3 fork of the same concept: P1:runner-deploy-slot installs the +// pinned tarball to /usr/local/bin and then P1b:build-cache-provision pointed its unit at a path +// that install never creates, so the daemon could not start and the read-back refused. Both the +// install and the unit now read ONE path row, so the two phases cannot disagree. + +// This ensure body writes a SYSTEM-scope unit (/etc/systemd/system/, +// WantedBy=multi-user.target, per render_build_cache_unit) rather than the user-scope unit +// (~/.config/systemd/user, enable-linger) the deleted gunbc.host_build_cache_provision_script +// builder used. That earlier form needed `sudo -n loginctl enable-linger "$USER"` to survive the +// provisioning ssh connection closing, which is real shell this render's system scope makes +// unnecessary — a `systemd --user` manager has no login to outlive when the unit is system-scoped. +// This is a consequence of the A5 grain swap (EnsureBuildCacheInstance { desired: +// BuildCacheInstance }), not an independently decided handler choice: +// BuildCacheInstance.intended_unit is a single row ("gunbc-build-cache-ci.service", +// WantedBy=multi-user.target) already grounded on a live srv1 capture, and the render is the single +// authority for the unit body, so writing it anywhere but where the render says WantedBy points is +// a second representation of the same fact. The install/enable/restart sequence reuses +// gunbc.live_deploy.operations' existing typed-argv-backed command builders +// (systemctl_daemon_reload_command / systemctl_enable_command / systemctl_restart_command, each +// privileged: true) rather than hand-spelling new sudo -n systemctl strings, matching the idiom +// already established in gunbc.live_deploy.emit and gunbc.host_axis_caps — a further §3 de-fork +// consistent with why this swap exists. fn build_cache_unit_system_path(instance: BuildCacheInstance) -> String { concat("/etc/systemd/system/", instance.intended_unit as String) @@ -2771,7 +2829,12 @@ fn build_cache_fragment_words() -> String { ) } -data build_cache_classify_fold_note: String = "The misconfigured-fragment classifier is a FOLD over the cited roster extdeps.cache.sccache.sccache_misconfigured_log_fragments, not one emitted branch per fragment. The roster is data the loop iterates: adding a fragment upstream changes the emitted word list and nothing else. Fragments are single-quoted as shell words, which is total only while no fragment contains a single quote — build_cache_fragment_words_are_shell_safe() is the construction check that keeps it so." +// The misconfigured-fragment classifier is a FOLD over the cited roster +// extdeps.cache.sccache.sccache_misconfigured_log_fragments, not one emitted branch per fragment. +// The roster is data the loop iterates: adding a fragment upstream changes the emitted word list +// and nothing else. Fragments are single-quoted as shell words, which is total only while no +// fragment contains a single quote — build_cache_fragment_words_are_shell_safe() is the +// construction check that keeps it so. fn build_cache_classify_failure_body() -> String { join([ @@ -2898,7 +2961,14 @@ fn build_cache_classifier_covers_every_cited_fragment() -> Bool { ) } -data provision_build_cache_arch_derivation_note: String = "The provisioning script is a function of the TARGET HOST's architecture, not a constant: the cited release publishes one musl tarball plus one sha256 per arch, so emitting a script at all requires knowing which. Arch comes from the effect target's own ComputeHost (compute_host_primary_cpu -> CpuFacts.architecture) and the token/digest selectors in extdeps.cache.sccache are partial over the Architecture coproduct — a host whose CPU is unreadable, or whose arch the release does not publish, yields NO script and a typed NotConverged. There is no default arch: shipping an aarch64 binary to an unlisted host is precisely the fabricated-plausible-output §5 forbids." +// The provisioning script is a function of the TARGET HOST's architecture, not a constant: the +// cited release publishes one musl tarball plus one sha256 per arch, so emitting a script at all +// requires knowing which. Arch comes from the effect target's own ComputeHost +// (compute_host_primary_cpu -> CpuFacts.architecture) and the token/digest selectors in +// extdeps.cache.sccache are partial over the Architecture coproduct — a host whose CPU is +// unreadable, or whose arch the release does not publish, yields NO script and a typed +// NotConverged. There is no default arch: shipping an aarch64 binary to an unlisted host is +// precisely the fabricated-plausible-output §5 forbids. fn provision_build_cache_host_arch(effect_intent: HostEffectIntent) -> Architecture? { match effect_intent.target { @@ -3049,7 +3119,13 @@ fn provision_build_cache_witness_intent() -> HostEffectIntent { } } -data provision_build_cache_arch_unknown_fixture_note: String = "A host the fleet knows by identity but whose processor inventory is empty — the shape a freshly-racked srvN has before its BMC read lands (srv4 carried exactly this deferral for memory). Realization must REFUSE it, because 'which binary do I install' has no answer. The fixture deliberately does NOT fabricate a CPU catalog row for an arch the cited release omits: minting a machine that does not exist to test a refusal would be its own §3 violation, so the not-published arm is witnessed at the selector (sccache_release_arch_token over Riscv64/Wasm32) instead." +// A host the fleet knows by identity but whose processor inventory is empty — the shape a +// freshly-racked srvN has before its BMC read lands (srv4 carried exactly this deferral for +// memory). Realization must REFUSE it, because 'which binary do I install' has no answer. The +// fixture deliberately does NOT fabricate a CPU catalog row for an arch the cited release omits: +// minting a machine that does not exist to test a refusal would be its own §3 violation, so the +// not-published arm is witnessed at the selector (sccache_release_arch_token over Riscv64/Wasm32) +// instead. fn provision_build_cache_arch_unknown_host_fixture() -> ComputeHost { ComputeHost { @@ -3314,7 +3390,11 @@ fn reconcile_ssh_argv_exec_outcome( } } -data run_typed_argv_transport_authority_note: String = "Executor→target typed remote argv transport binding (codex_runtime_converge_local_shell_locus dissolve-on class; review 51883). SshShell routes through typed_argv_exec_over_ssh on the transport endpoint; FleetSsh routes through typed_argv_exec_over_fleet_ssh with sealed context. LocalShell refuses — remote argv requires a transport that names the target host. Shell script bodies use host_effect_realize run_shell_transport instead." +// Executor→target typed remote argv transport binding (codex_runtime_converge_local_shell_locus +// dissolve-on class; review 51883). SshShell routes through typed_argv_exec_over_ssh on the +// transport endpoint; FleetSsh routes through typed_argv_exec_over_fleet_ssh with sealed context. +// LocalShell refuses — remote argv requires a transport that names the target host. Shell script +// bodies use host_effect_realize run_shell_transport instead. fn run_typed_argv_transport( argv: List, @@ -3656,7 +3736,26 @@ fn realize_nbd_proxy_session_on_host( } } -data candidate_observation_locus_note: String = "THE CANDIDATE WAS OBSERVED IN THE DEPLOYING CHECKOUT, SO IT MAY ONLY LABEL A TREE THAT IS THAT CHECKOUT. observe_candidate_release reads git HEAD where the deploy program runs. On LocalShell the observation locus and the mutation locus are the same filesystem — the CI job installs from its own workspace — so the revision it read is the revision it installs, and baking that value into ExecStart is a true statement about the resulting process.\\n\\nOver SshShell they are two different machines. The revision came from the LOCAL checkout while the unit is written on the REMOTE host, whose tree arrived by some other path and at some other time; nothing in this fold establishes that they agree. Emitting the local sha there would produce a process that CONFIDENTLY REPORTS a release it may not be running, and readiness would then compare that fabricated label against the expectation that produced it and agree — a self-consistent false green, which is worse than no identity at all because the wrongness is invisible to every downstream check.\\n\\nSo the SSH arm REFUSES rather than labels. This is not a transport allow-list standing in for a model; the admission that is genuinely missing is the agreement between candidate observation locus, source transfer locus, and mutation locus, and until a source transfer is modeled that PROVES the remote tree is this candidate, refusing is the only honest arm (DESIGN section 5: a failure arm must refuse, never widen). EmitArtifactThenThinRun refuses for the same reason, one step further removed.\\n\\nRetract is deliberately not restricted here: it installs no release, carries no candidate, and therefore has no locus to agree about. DISSOLVES WHEN a modeled source transfer establishes remote-tree identity, at which point this arm carries the transferred candidate instead of refusing." +// THE CANDIDATE WAS OBSERVED IN THE DEPLOYING CHECKOUT, SO IT MAY ONLY LABEL A TREE THAT IS THAT +// CHECKOUT. observe_candidate_release reads git HEAD where the deploy program runs. On LocalShell +// the observation locus and the mutation locus are the same filesystem — the CI job installs from +// its own workspace — so the revision it read is the revision it installs, and baking that value +// into ExecStart is a true statement about the resulting process.\n\nOver SshShell they are two +// different machines. The revision came from the LOCAL checkout while the unit is written on the +// REMOTE host, whose tree arrived by some other path and at some other time; nothing in this fold +// establishes that they agree. Emitting the local sha there would produce a process that +// CONFIDENTLY REPORTS a release it may not be running, and readiness would then compare that +// fabricated label against the expectation that produced it and agree — a self-consistent false +// green, which is worse than no identity at all because the wrongness is invisible to every +// downstream check.\n\nSo the SSH arm REFUSES rather than labels. This is not a transport +// allow-list standing in for a model; the admission that is genuinely missing is the agreement +// between candidate observation locus, source transfer locus, and mutation locus, and until a +// source transfer is modeled that PROVES the remote tree is this candidate, refusing is the only +// honest arm (DESIGN section 5: a failure arm must refuse, never widen). EmitArtifactThenThinRun +// refuses for the same reason, one step further removed.\n\nRetract is deliberately not restricted +// here: it installs no release, carries no candidate, and therefore has no locus to agree about. +// DISSOLVES WHEN a modeled source transfer establishes remote-tree identity, at which point this +// arm carries the transferred candidate instead of refusing. fn live_deploy_apply_candidate_locus_refusal(transport: HostEffectTransport) -> String { match transport { @@ -4124,7 +4223,13 @@ type BuildCacheReadResult = BuildCacheReadRan { observed: ShellExecObserved } | BuildCacheReadRefused { reason: String } -data every_read_is_one_argv_note: String = "EVERY READ BELOW IS ONE ARGV, NO SHELL. That is not stylistic: this module's build-cache surface is a shell-to-dag census A5 row whose standing constraint is that its emitted shell may shrink and must not grow, so an observation added as a concat-built bash body would have grown precisely the surface #5828 exists to dissolve. Routing the reads through the typed argv seam instead means the observation lands in the direction the dissolve-on points, and the remaining concat-shell body -- the ensure step, still unconverted -- is strictly smaller as a share of what this effect does than it was." +// EVERY READ BELOW IS ONE ARGV, NO SHELL. That is not stylistic: this module's build-cache surface +// is a shell-to-dag census A5 row whose standing constraint is that its emitted shell may shrink +// and must not grow, so an observation added as a concat-built bash body would have grown precisely +// the surface #5828 exists to dissolve. Routing the reads through the typed argv seam instead means +// the observation lands in the direction the dissolve-on points, and the remaining concat-shell +// body -- the ensure step, still unconverted -- is strictly smaller as a share of what this effect +// does than it was. // IT TAKES A COMMAND RATHER THAN A WORD LIST, for the same reason bmc_netboot_run_bmc now does: a // List parameter is a place an argv can be hand-spelled, and one of this function's callers @@ -4194,11 +4299,24 @@ fn build_cache_path_kind_read( } } -data unread_owner_facts_are_failures_not_blanks_note: String = "WHEN NO OWNER RESOLVES, THE OWNER READS ARE NOT PERFORMED AND SAY SO. The alternative -- issuing cat /proc//cgroup against a pid that does not exist and letting it fail -- would reach the same arms by accident and cost four round trips per pass to do it. Filling them with a positively-named HostReadFailed keeps the decode on one path (gunbc.build_cache_endpoint_observe unreachable_is_a_read_state_not_a_short_circuit_note makes the same move for an unreachable host) while making the reason for the absence readable rather than inferred from a contrived errno." +// WHEN NO OWNER RESOLVES, THE OWNER READS ARE NOT PERFORMED AND SAY SO. The alternative -- issuing +// cat /proc//cgroup against a pid that does not exist and letting it fail -- would reach the same +// arms by accident and cost four round trips per pass to do it. Filling them with a +// positively-named HostReadFailed keeps the decode on one path (gunbc.build_cache_endpoint_observe +// unreachable_is_a_read_state_not_a_short_circuit_note makes the same move for an unreachable host) +// while making the reason for the absence readable rather than inferred from a contrived errno. data owner_facts_unread_detail: NonEmptyStr = "no single listening owner resolved at the endpoint, so the owner's cgroup, start time, principal and executable were not read" -data tcp_endpoint_observation_is_unmodeled_note: String = "A TCP-ENDPOINT INSTANCE REFUSES RATHER THAN BEING OBSERVED THROUGH THE UNIX CHAIN. /proc/net/unix answers for unix sockets only; the TCP equivalent is /proc/net/tcp with its own hex-encoded address columns and its own decode, which nothing in this repository has modeled. No BuildCacheInstance in the tree carries a TCP endpoint today -- both ci_cache_instance and session_cache_instance are unix sockets -- so this is an unbuilt arm rather than a live gap, and it is a POSITIVE typed refusal so that the day one is authored it stops the line instead of being silently classified through a table that cannot see it. An earlier revision reached ss for both shapes, which appeared to support TCP and was never exercised against one; an untested arm that answers is worse than one that refuses." +// A TCP-ENDPOINT INSTANCE REFUSES RATHER THAN BEING OBSERVED THROUGH THE UNIX CHAIN. /proc/net/unix +// answers for unix sockets only; the TCP equivalent is /proc/net/tcp with its own hex-encoded +// address columns and its own decode, which nothing in this repository has modeled. No +// BuildCacheInstance in the tree carries a TCP endpoint today -- both ci_cache_instance and +// session_cache_instance are unix sockets -- so this is an unbuilt arm rather than a live gap, and +// it is a POSITIVE typed refusal so that the day one is authored it stops the line instead of being +// silently classified through a table that cannot see it. An earlier revision reached ss for both +// shapes, which appeared to support TCP and was never exercised against one; an untested arm that +// answers is worse than one that refuses. fn build_cache_listen_inode_read( instance: BuildCacheInstance, @@ -4239,7 +4357,12 @@ fn build_cache_fd_holder_read( } } -data holder_walk_runs_on_the_unlistened_socket_too_note: String = "WHEN THERE IS NO LISTEN ENTRY THE WALK STILL RUNS, against the inode read off the FILE rather than off /proc/net/unix, and that is the whole of clause (3). A socket with no LISTEN entry is either stale (nothing holds it) or held open by a process that is not listening (unlinking severs it). Those two states are indistinguishable without asking whether any descriptor resolves to the file's inode, so skipping the walk here because 'nothing is listening' would collapse them -- and collapse them in the direction that unlinks a live process's socket." +// WHEN THERE IS NO LISTEN ENTRY THE WALK STILL RUNS, against the inode read off the FILE rather +// than off /proc/net/unix, and that is the whole of clause (3). A socket with no LISTEN entry is +// either stale (nothing holds it) or held open by a process that is not listening (unlinking severs +// it). Those two states are indistinguishable without asking whether any descriptor resolves to the +// file's inode, so skipping the walk here because 'nothing is listening' would collapse them -- and +// collapse them in the direction that unlinks a live process's socket. fn build_cache_socket_inode_at_path(path: NonEmptyStr, transport: HostEffectTransport) -> NonEmptyStr? { match build_cache_host_read_observed(command: stat_inode_command(path: path as String), transport: transport) { @@ -4338,7 +4461,14 @@ fn build_cache_reads_without_owner( } } -data unlink_is_a_separate_admitted_step_note: String = "THE UNLINK RUNS AS ITS OWN COMMAND BEFORE THE ENSURE, WHICH IS WHAT gunbc.build_cache_endpoint_path no_unconditional_execstartpre_note REQUIRES. Folding it into the unit as ExecStartPre would unlink in every path state rather than the one state where unlinking is correct -- harmless under an absent path, destructive under a conflict, and silently fatal under a live listener. Here it is reached only from EndpointClearedByUnlink, which admit_stale_socket_unlink produces only from an observed stale socket under a current boot id, and a failure to remove the file refuses the whole effect rather than proceeding into a bind that error 98 would fail anyway." +// THE UNLINK RUNS AS ITS OWN COMMAND BEFORE THE ENSURE, WHICH IS WHAT +// gunbc.build_cache_endpoint_path no_unconditional_execstartpre_note REQUIRES. Folding it into the +// unit as ExecStartPre would unlink in every path state rather than the one state where unlinking +// is correct -- harmless under an absent path, destructive under a conflict, and silently fatal +// under a live listener. Here it is reached only from EndpointClearedByUnlink, which +// admit_stale_socket_unlink produces only from an observed stale socket under a current boot id, +// and a failure to remove the file refuses the whole effect rather than proceeding into a bind that +// error 98 would fail anyway. fn build_cache_unlink_then_ensure( instance: BuildCacheInstance, @@ -4400,7 +4530,33 @@ fn build_cache_unlink_then_ensure( } } -data both_authorities_are_consumed_and_neither_is_duplicated_note: String = "TWO AUTHORITIES, TWO DIFFERENT QUESTIONS, ONE EACH SIDE OF THE ACT. gunbc.build_cache_ensure admit_ensure_build_cache_instance is the PRECONDITION -- may this host be written to at all, given who owns the endpoint and what is at the path -- and it runs before the first mutation. gunbc.host_build_cache_provision provision_build_cache_verdict is the POSTCONDITION -- given what the ensure produced, is this host converged -- and it runs after. The shell-to-dag census C-note found both unconsumed and called them one wiring job; they are, and this is that job, but they are not one check applied twice: the precondition can refuse a host the postcondition would never see, and the postcondition catches the case that motivated it, an ensure that reports success while the endpoint ends up owned by something other than the unit just written.\n\nTHE PLACEMENT IN THE POSTCONDITION IS RE-OBSERVED, NOT CARRIED FORWARD FROM THE PREFLIGHT. Reusing the preflight's placement would be asserting a fact measured before the mutation about the state after it, which is the read-back that greens on evidence it produced, one step removed. So the endpoint is surveyed again and the second survey is what the verdict folds -- which is also what makes the check discriminating: the whole point is that the pre-state and the post-state can differ.\n\nONE RESIDUE, NAMED RATHER THAN DISCOVERED IN PRODUCTION: the post-survey races the unit start. systemctl restart returns when the unit is active, and the server binds its endpoint during its own startup, so there is a window in which the endpoint has no listener yet and the post-survey reads ServerPlacementAbsent -- a FALSE RefusalDaemonAbsent. It is bounded in the right direction: the failure is a loud refusal on an idempotent operation, so the next converge pass reads the settled state and converges, and no host is left silently misreported. It is NOT fixed here because fixing it properly means waiting on an observable readiness condition rather than sleeping, and a readiness predicate for this endpoint is its own piece of modeling. DISSOLVE-ON: an endpoint-readiness observation the ensure step can wait on, at which point the post-survey runs after it rather than after systemctl returns." +// TWO AUTHORITIES, TWO DIFFERENT QUESTIONS, ONE EACH SIDE OF THE ACT. gunbc.build_cache_ensure +// admit_ensure_build_cache_instance is the PRECONDITION -- may this host be written to at all, +// given who owns the endpoint and what is at the path -- and it runs before the first mutation. +// gunbc.host_build_cache_provision provision_build_cache_verdict is the POSTCONDITION -- given what +// the ensure produced, is this host converged -- and it runs after. The shell-to-dag census C-note +// found both unconsumed and called them one wiring job; they are, and this is that job, but they +// are not one check applied twice: the precondition can refuse a host the postcondition would never +// see, and the postcondition catches the case that motivated it, an ensure that reports success +// while the endpoint ends up owned by something other than the unit just written. +// +// THE PLACEMENT IN THE POSTCONDITION IS RE-OBSERVED, NOT CARRIED FORWARD FROM THE PREFLIGHT. +// Reusing the preflight's placement would be asserting a fact measured before the mutation about +// the state after it, which is the read-back that greens on evidence it produced, one step removed. +// So the endpoint is surveyed again and the second survey is what the verdict folds -- which is +// also what makes the check discriminating: the whole point is that the pre-state and the +// post-state can differ. +// +// ONE RESIDUE, NAMED RATHER THAN DISCOVERED IN PRODUCTION: the post-survey races the unit start. +// systemctl restart returns when the unit is active, and the server binds its endpoint during its +// own startup, so there is a window in which the endpoint has no listener yet and the post-survey +// reads ServerPlacementAbsent -- a FALSE RefusalDaemonAbsent. It is bounded in the right direction: +// the failure is a loud refusal on an idempotent operation, so the next converge pass reads the +// settled state and converges, and no host is left silently misreported. It is NOT fixed here +// because fixing it properly means waiting on an observable readiness condition rather than +// sleeping, and a readiness predicate for this endpoint is its own piece of modeling. DISSOLVE-ON: +// an endpoint-readiness observation the ensure step can wait on, at which point the post-survey +// runs after it rather than after systemctl returns. fn build_cache_daemon_observation_of_receipt( observed: ShellExecObserved, @@ -4467,7 +4623,15 @@ fn build_cache_ensure_step( } } -data already_converged_evidence_is_the_observation_note: String = "THE ALREADY-CONVERGED ARM RETURNS ShellEffectApplied WITH exit_code 0 AND NO SHELL HAVING RUN, and that is worth stating because it looks like a fabricated receipt and is not. HostEffectEvidence has two arms and neither names an observation, so the evidence carrier cannot yet distinguish 'a command ran and succeeded' from 'a read established that nothing needed to run'. What the receipt carries is the observation itself -- the unit found owning the endpoint -- rather than invented command output, so a reader sees WHY it converged. DISSOLVE-ON: a HostEffectEvidence arm for an observation-grounded convergence, at which point this arm names it and the exit_code 0 goes away. Until then the honest thing is to say so here rather than let the zero read as a process exit." +// THE ALREADY-CONVERGED ARM RETURNS ShellEffectApplied WITH exit_code 0 AND NO SHELL HAVING RUN, +// and that is worth stating because it looks like a fabricated receipt and is not. +// HostEffectEvidence has two arms and neither names an observation, so the evidence carrier cannot +// yet distinguish 'a command ran and succeeded' from 'a read established that nothing needed to +// run'. What the receipt carries is the observation itself -- the unit found owning the endpoint -- +// rather than invented command output, so a reader sees WHY it converged. DISSOLVE-ON: a +// HostEffectEvidence arm for an observation-grounded convergence, at which point this arm names it +// and the exit_code 0 goes away. Until then the honest thing is to say so here rather than let the +// zero read as a process exit. // THE COMPILE-POOL SLICE GETS ITS OWN EFFECT RATHER THAN RIDING THE CACHE ENSURE, AND THE SEAM IS THE POINT. // gunbc.compile_pool_ensure admit_ensure_compile_pool_slice refuses on TOPOLOGY before it observes anything, diff --git a/dag/gunbc/host/host_identity_access.dag b/dag/gunbc/host/host_identity_access.dag index 6d08a8ede3b..8292f020fed 100644 --- a/dag/gunbc/host/host_identity_access.dag +++ b/dag/gunbc/host/host_identity_access.dag @@ -1,6 +1,5 @@ module gunbc.host_identity_access - data host_identity_hostnamectl_path: NonEmptyStr = fleet_hostnamectl_command_path fn host_identity_ssh_access(host: ComputeHost, ssh_host: NonEmptyStr) -> DeployAccess { @@ -28,7 +27,34 @@ data host_identity_srv4_access: DeployAccess = host_identity_ssh_access( ssh_host: operator_host_srv4 as NonEmptyStr, ) -data host_identity_srv3_access_note: String = "srv3 gains the same direct-SSH access shape as srv4 (operator, 2026-07-24: 'same access pattern as srv4'), now that its host OS is a modeled endpoint (gunbc.fleet_intent_network.srv3_host_lan_endpoint, 192.168.1.221) rather than BMC-only. This row is how you reach srv3 ITSELF, and it is NOT interchangeable with ci_deploy_srv1_operator_access: the latter reaches the host an OsInstallActuator run drives srv3's BMC FROM, which is a different machine. Consumed through gunbc.host_toolchain_ensure.host_toolchain_direct_access, which the reach pairing derives from the resolved TARGET host — so the kind-aware split (BuildCache targets srv3, OsInstallActuator targets the selected actuator host) falls out of target resolution instead of being a second parallel lookup that could disagree with it. NETWORK REACH is NOT direct despite the shape mirroring srv4: srv3 is a fabric-only target reached through srv2, modeled as gunbc.fleet_reach.srv3_reach (ProxyJump interim, dissolving to FabricTunnel when the fabric works). This transport's SshShell ssh_host realizes OVER that reach — the network path (how packets get to srv3) is deliberately INDEPENDENT of the shell/auth transport (how a command runs once reached), per the operator rule 2026-07-24; encoding the jump INTO the transport (so SshShell carries a Reach rather than a bare host alias) is the reach-independent-of-shell decomposition (step b, coordinated with the shell→dag transport-wall lane). LIVENESS grounded 2026-07-25 (corrected from an earlier over-broad claim): srv3 IS reachable and a command DOES run today — session -> srv2 (briansrls) -> ubuntu@srv3 authenticated by srv2's local node-orch key (~/.node-orch/id_ed25519 = node-orch@srv2, which ubuntu@srv3's authorized_keys trusts); proven by execution (hostname=srv3, aarch64, 128 cores, Ubuntu 24.04.3). So both the network reach AND a working shell path exist via that bootstrap credential. The NARROWER real gap: the FLEET identity (fleet-automation-ssh-key) is not yet on srv3 — srv3 was autoinstalled Jul 13 with ssh.authorized-keys EMPTY and hand-keyed post-install with node-orch@srv2 / briansrls@srv1 / macbook only, so the modeled DeployAccess principal (FleetAccountOperator) does not yet have a key srv3 trusts. That is a one-time host_authorized_keys_reconcile upsert over the working node-orch hop (fleet_subsumption_manual_gaps A1/B2), NOT an unreachable host. Reach realizable; a bootstrap shell path works; the fleet-identity shell path is pending key distribution — three facts held separate." +// srv3 gains the same direct-SSH access shape as srv4 (operator, 2026-07-24: 'same access pattern +// as srv4'), now that its host OS is a modeled endpoint +// (gunbc.fleet_intent_network.srv3_host_lan_endpoint, 192.168.1.221) rather than BMC-only. This row +// is how you reach srv3 ITSELF, and it is NOT interchangeable with ci_deploy_srv1_operator_access: +// the latter reaches the host an OsInstallActuator run drives srv3's BMC FROM, which is a different +// machine. Consumed through gunbc.host_toolchain_ensure.host_toolchain_direct_access, which the +// reach pairing derives from the resolved TARGET host — so the kind-aware split (BuildCache targets +// srv3, OsInstallActuator targets the selected actuator host) falls out of target resolution +// instead of being a second parallel lookup that could disagree with it. NETWORK REACH is NOT +// direct despite the shape mirroring srv4: srv3 is a fabric-only target reached through srv2, +// modeled as gunbc.fleet_reach.srv3_reach (ProxyJump interim, dissolving to FabricTunnel when the +// fabric works). This transport's SshShell ssh_host realizes OVER that reach — the network path +// (how packets get to srv3) is deliberately INDEPENDENT of the shell/auth transport (how a command +// runs once reached), per the operator rule 2026-07-24; encoding the jump INTO the transport (so +// SshShell carries a Reach rather than a bare host alias) is the reach-independent-of-shell +// decomposition (step b, coordinated with the shell→dag transport-wall lane). LIVENESS grounded +// 2026-07-25 (corrected from an earlier over-broad claim): srv3 IS reachable and a command DOES run +// today — session -> srv2 (briansrls) -> ubuntu@srv3 authenticated by srv2's local node-orch key +// (~/.node-orch/id_ed25519 = node-orch@srv2, which ubuntu@srv3's authorized_keys trusts); proven by +// execution (hostname=srv3, aarch64, 128 cores, Ubuntu 24.04.3). So both the network reach AND a +// working shell path exist via that bootstrap credential. The NARROWER real gap: the FLEET identity +// (fleet-automation-ssh-key) is not yet on srv3 — srv3 was autoinstalled Jul 13 with +// ssh.authorized-keys EMPTY and hand-keyed post-install with node-orch@srv2 / briansrls@srv1 / +// macbook only, so the modeled DeployAccess principal (FleetAccountOperator) does not yet have a +// key srv3 trusts. That is a one-time host_authorized_keys_reconcile upsert over the working +// node-orch hop (fleet_subsumption_manual_gaps A1/B2), NOT an unreachable host. Reach realizable; a +// bootstrap shell path works; the fleet-identity shell path is pending key distribution — three +// facts held separate. data host_identity_srv3_access: DeployAccess = host_identity_ssh_access( host: srv3_host, diff --git a/dag/gunbc/host/host_layout.dag b/dag/gunbc/host/host_layout.dag index 7e085d25f27..0d5e81bf3fe 100644 --- a/dag/gunbc/host/host_layout.dag +++ b/dag/gunbc/host/host_layout.dag @@ -18,9 +18,34 @@ import gunbc.codex_runtime_paths { } import gunbc.package_delivery { codex_linux_arm64_native_executable_rel } -data host_layout_note: String = "The ONE authority for the srv1 gunbc host paths (docs/plans/gunbc-served-dashboard-design.md section 5; lane agreement with royal-carp-451, 2026-07-20). Five facts must agree with their consumers or in-process dispatch breaks: srv1_gunbc_repo_root is BOTH the tree `gunbc serve` loads AND the repo the belt runs `git worktree add` from; srv1_dispatch_worktree_root is where those worktrees land; srv1_dispatch_attempt_state_root is where the belt publishes attempt evidence before it launches a provider; srv1_gunbc_serve_binary is the deployed seed binary; srv1_roadmap_codex_home is the dedicated Codex state root whose auth/config/session state the Codex provider preflights and invokes through CODEX_HOME. gunbc.roadmap_belt_actuate and gunbc.roadmap_dispatch_actuator project these rows, while gunbc.live_deploy.spec owns convergence of the filesystem/runtime membership. Interim: the parent lane hand-provisions srv1 to these exact paths; that hand-provisioning dissolves when live_deploy formally carries every member, including the provider state-root existence/ownership check (credentials themselves remain user-authored secret state, never deployment payload)." - -data attempt_state_root_layout_admission_note: String = "srv1_dispatch_attempt_state_root JOINED THIS AUTHORITY ON AN INCIDENT (2026-07-30), and the incident is the argument for the row existing. #7368 made a published attempt-state directory a hard precondition of every dispatch, but the path was written as a bare literal inside gunbc.roadmap_dashboard_instance's srv1 row and nowhere else. live_deploy's owned-artifact roster therefore never carried it, so no deploy created it, so `install -d /opt/gunbc/attempt-state` ran as the service user against a root-owned parent and refused with EACCES — after `git worktree add` had already succeeded. Every dispatch since produced an orphan branch and worktree, no session, and a progress row that read `legacy attempt`. The defect was NOT the permission: it was that a path load-bearing for two consumers had one spelling and one owner, so adding the requirement to one consumer could not reach the other. The general rule the row restates: a host path the belt WRITES is a deployment member, and a deployment member is projected from here — never spelled at its point of use. Dissolution: when live_deploy takes a GunbcHostLayout row as a parameter, this flat row folds into the record literal with its siblings (gunbc_host_layout_note's trigger)." +// The ONE authority for the srv1 gunbc host paths (docs/plans/gunbc-served-dashboard-design.md +// section 5; lane agreement with royal-carp-451, 2026-07-20). Five facts must agree with their +// consumers or in-process dispatch breaks: srv1_gunbc_repo_root is BOTH the tree `gunbc serve` +// loads AND the repo the belt runs `git worktree add` from; srv1_dispatch_worktree_root is where +// those worktrees land; srv1_dispatch_attempt_state_root is where the belt publishes attempt +// evidence before it launches a provider; srv1_gunbc_serve_binary is the deployed seed binary; +// srv1_roadmap_codex_home is the dedicated Codex state root whose auth/config/session state the +// Codex provider preflights and invokes through CODEX_HOME. gunbc.roadmap_belt_actuate and +// gunbc.roadmap_dispatch_actuator project these rows, while gunbc.live_deploy.spec owns convergence +// of the filesystem/runtime membership. Interim: the parent lane hand-provisions srv1 to these +// exact paths; that hand-provisioning dissolves when live_deploy formally carries every member, +// including the provider state-root existence/ownership check (credentials themselves remain +// user-authored secret state, never deployment payload). + +// srv1_dispatch_attempt_state_root JOINED THIS AUTHORITY ON AN INCIDENT (2026-07-30), and the +// incident is the argument for the row existing. #7368 made a published attempt-state directory a +// hard precondition of every dispatch, but the path was written as a bare literal inside +// gunbc.roadmap_dashboard_instance's srv1 row and nowhere else. live_deploy's owned-artifact roster +// therefore never carried it, so no deploy created it, so `install -d /opt/gunbc/attempt-state` ran +// as the service user against a root-owned parent and refused with EACCES — after `git worktree +// add` had already succeeded. Every dispatch since produced an orphan branch and worktree, no +// session, and a progress row that read `legacy attempt`. The defect was NOT the permission: it was +// that a path load-bearing for two consumers had one spelling and one owner, so adding the +// requirement to one consumer could not reach the other. The general rule the row restates: a host +// path the belt WRITES is a deployment member, and a deployment member is projected from here — +// never spelled at its point of use. Dissolution: when live_deploy takes a GunbcHostLayout row as a +// parameter, this flat row folds into the record literal with its siblings +// (gunbc_host_layout_note's trigger). data srv1_gunbc_repo_root: FilePath = "/opt/gunbc/gunbc" as FilePath @@ -28,23 +53,78 @@ data srv1_gunbc_serve_binary: FilePath = "/opt/gunbc/bin/gunbc" as FilePath data srv1_dispatch_worktree_root: FilePath = "/opt/gunbc/dispatch-worktrees" as FilePath -data roadmap_codex_account_note: String = "THE DASHBOARD'S CODEX HOME IS DERIVED FROM AN ACCOUNT SELECTION, not spelled as a path. It used to be the literal /home/briansrls/.codex@gunbc-roadmap, which encoded three separate facts in one string with no way to read any of them back: the user's home directory, codex's own default-root name, and the @label convention that distinguishes this account from the two others on the same machine.\\n\\nDeriving it makes each fact answerable at its own layer. gunbc.provider_account owns both the suffix convention and the identity of the dashboard's account, extdeps.llm.codex_auth owns the fact that CODEX_HOME selects an isolated store at all, and what THIS module still states is only the machine-local half: which home directory the roots hang under.\\n\\nTHE FIRST VERSION OF THIS DECOMPOSITION FORKED THE ACCOUNT (review 45207). It named the label a second time here, as roadmap_codex_account_label beside a roadmap_codex_account() constructor, while gunbc.provider_account already carried codex_roadmap_account_label and codex_roadmap_account_selection() for the same account — two names for one concept in two modules, each spelling the same literal, which is exactly the nickname §3 forbids. Worse, neither local name had a consumer: the path rows below still inlined the literal a third and fourth time, so the module declared an authority and then ignored it. The fork is deleted rather than wired up, because the account's home is provider_account — this module's business is host paths.\\n\\nThe derived value is byte-identical to the literal it replaces, which the witness asserts — the point is provenance, not a new value." +// THE DASHBOARD'S CODEX HOME IS DERIVED FROM AN ACCOUNT SELECTION, not spelled as a path. It used +// to be the literal /home/briansrls/.codex@gunbc-roadmap, which encoded three separate facts in one +// string with no way to read any of them back: the user's home directory, codex's own default-root +// name, and the @label convention that distinguishes this account from the two others on the same +// machine.\n\nDeriving it makes each fact answerable at its own layer. gunbc.provider_account owns +// both the suffix convention and the identity of the dashboard's account, extdeps.llm.codex_auth +// owns the fact that CODEX_HOME selects an isolated store at all, and what THIS module still states +// is only the machine-local half: which home directory the roots hang under.\n\nTHE FIRST VERSION +// OF THIS DECOMPOSITION FORKED THE ACCOUNT (review 45207). It named the label a second time here, +// as roadmap_codex_account_label beside a roadmap_codex_account() constructor, while +// gunbc.provider_account already carried codex_roadmap_account_label and +// codex_roadmap_account_selection() for the same account — two names for one concept in two +// modules, each spelling the same literal, which is exactly the nickname §3 forbids. Worse, neither +// local name had a consumer: the path rows below still inlined the literal a third and fourth time, +// so the module declared an authority and then ignored it. The fork is deleted rather than wired +// up, because the account's home is provider_account — this module's business is host paths.\n\nThe +// derived value is byte-identical to the literal it replaces, which the witness asserts — the point +// is provenance, not a new value. data operator_home_root: FilePath = "/home/briansrls" as FilePath data srv1_dispatch_attempt_state_root: FilePath = "/opt/gunbc/attempt-state" as FilePath -data devboot_root_layout_admission_note: String = "THE BUILD SERVICE'S HOST ROOT JOINS THIS AUTHORITY BEFORE ITS FIRST CONSUMER EXISTS, which is the whole point of the ordering. srv1_dispatch_attempt_state_root joined it AFTER an incident (attempt_state_root_layout_admission_note): a path load-bearing for two consumers was spelled as a bare literal inside one of them, so live_deploy's owned-artifact roster never carried it, so no deploy created it, and `install -d` refused with EACCES against a root-owned parent AFTER the dispatch had already half-run. This row is the same shape of fact -- a host path the service WRITES -- and it is declared here first so that roster can carry it from the start rather than after the equivalent failure.\\n\\nWHY ONE ROOT AND NOT TWO. An earlier revision admitted a second root to hold request records, the producer lease and terminal outcomes, on the assumption that this service would keep durable state in a database beside the artifacts. Re-observation found no such store realization in the tree, and found that the artifact store already provides everything the second root was for: a bare Git repository IS the content-addressed object database, its refs ARE durable named state, and its exact-old-value ref update IS the atomic compare-and-swap a producer lease needs. The second root would have been a second durable representation of one fact set, so it is retired rather than left standing with nothing writing to it (see devboot_state_root_retirement_note).\\n\\nWHAT THIS ROW DOES NOT SAY. It is a declaration, not an observation -- the same distinction srv2_host_layout_note draws. The path is not asserted to exist; the deployment creates it and a preflight refuses on the difference. A service that found no store and quietly built into a temp directory would be the absorbing fallback DESIGN section 5 forbids, so the refusal carries the exact path." +// THE BUILD SERVICE'S HOST ROOT JOINS THIS AUTHORITY BEFORE ITS FIRST CONSUMER EXISTS, which is the +// whole point of the ordering. srv1_dispatch_attempt_state_root joined it AFTER an incident +// (attempt_state_root_layout_admission_note): a path load-bearing for two consumers was spelled as +// a bare literal inside one of them, so live_deploy's owned-artifact roster never carried it, so no +// deploy created it, and `install -d` refused with EACCES against a root-owned parent AFTER the +// dispatch had already half-run. This row is the same shape of fact -- a host path the service +// WRITES -- and it is declared here first so that roster can carry it from the start rather than +// after the equivalent failure.\n\nWHY ONE ROOT AND NOT TWO. An earlier revision admitted a second +// root to hold request records, the producer lease and terminal outcomes, on the assumption that +// this service would keep durable state in a database beside the artifacts. Re-observation found no +// such store realization in the tree, and found that the artifact store already provides everything +// the second root was for: a bare Git repository IS the content-addressed object database, its refs +// ARE durable named state, and its exact-old-value ref update IS the atomic compare-and-swap a +// producer lease needs. The second root would have been a second durable representation of one fact +// set, so it is retired rather than left standing with nothing writing to it (see +// devboot_state_root_retirement_note).\n\nWHAT THIS ROW DOES NOT SAY. It is a declaration, not an +// observation -- the same distinction srv2_host_layout_note draws. The path is not asserted to +// exist; the deployment creates it and a preflight refuses on the difference. A service that found +// no store and quietly built into a temp directory would be the absorbing fallback DESIGN section 5 +// forbids, so the refusal carries the exact path. data srv1_devboot_artifact_store_root: FilePath = "/var/lib/gunbc/artifacts.git" as FilePath - data srv1_roadmap_codex_home: FilePath = codex_account_state_root( home: operator_home_root, selection: codex_roadmap_account_selection(), ) as String as FilePath -data gunbc_host_layout_note: String = "THE HOST FACTS AN INSTANCE CANNOT DERIVE FROM ITS OWN ROOT, carried as one row per host instead of one name per host. An isolated dashboard instance derives most of its paths from its instance root — source, attempts, attempt-state and local origin are all children of it — but three belong to the machine rather than the instance: which seed binary serves, where the provider's authenticated state root lives, and which local worktree seeds the source bundle. Those cannot come from the root, so before this row they were written as srv1 literals inside the shared constructor.\\n\\nBE PRECISE ABOUT WHAT THAT DEFECT WAS, because overstating it would be its own false claim. No value was wrong. srv1 and srv2 run the same layout convention, so both rows below carry byte-identical paths, and a home-directory path denotes a different file on each machine anyway. The defect was representational: the constructor took host_identity as a parameter and then pinned the other three facts, so a host whose layout DIVERGED could not be expressed at all. A second host was nameable without being describable, and the name silently carried the first host's body.\\n\\nThat is precisely why it survived review — there was no wrong output to notice. It is a latent fabrication, not a live one: the first host to install its binary elsewhere would have gotten srv1's path under its own name, with the mismatch appearing as an unexplained refusal against a path nobody wrote for it.\\n\\nOne row per host beats a second set of srv2_-prefixed names, because a prefix bakes the instance into the concept and the next host repeats the exercise. The flat srv1_ rows above stay as the field values of srv1's row — they have live consumers in live_deploy and the actuators — so each path is still written exactly once. Dissolution trigger: when live_deploy takes a layout row as a parameter, those flat rows fold into the record literal and the prefix disappears." +// THE HOST FACTS AN INSTANCE CANNOT DERIVE FROM ITS OWN ROOT, carried as one row per host instead +// of one name per host. An isolated dashboard instance derives most of its paths from its instance +// root — source, attempts, attempt-state and local origin are all children of it — but three belong +// to the machine rather than the instance: which seed binary serves, where the provider's +// authenticated state root lives, and which local worktree seeds the source bundle. Those cannot +// come from the root, so before this row they were written as srv1 literals inside the shared +// constructor.\n\nBE PRECISE ABOUT WHAT THAT DEFECT WAS, because overstating it would be its own +// false claim. No value was wrong. srv1 and srv2 run the same layout convention, so both rows below +// carry byte-identical paths, and a home-directory path denotes a different file on each machine +// anyway. The defect was representational: the constructor took host_identity as a parameter and +// then pinned the other three facts, so a host whose layout DIVERGED could not be expressed at all. +// A second host was nameable without being describable, and the name silently carried the first +// host's body.\n\nThat is precisely why it survived review — there was no wrong output to notice. +// It is a latent fabrication, not a live one: the first host to install its binary elsewhere would +// have gotten srv1's path under its own name, with the mismatch appearing as an unexplained refusal +// against a path nobody wrote for it.\n\nOne row per host beats a second set of srv2_-prefixed +// names, because a prefix bakes the instance into the concept and the next host repeats the +// exercise. The flat srv1_ rows above stay as the field values of srv1's row — they have live +// consumers in live_deploy and the actuators — so each path is still written exactly once. +// Dissolution trigger: when live_deploy takes a layout row as a parameter, those flat rows fold +// into the record literal and the prefix disappears. type GunbcHostLayout { host_identity: NonEmptyStr @@ -56,7 +136,10 @@ type GunbcHostLayout { data srv1_dashboard_controller_repo: FilePath = "/home/briansrls/.worktrees/gunbc/codex-provider-feedback-v0" as FilePath -data gunbc_host_layout_codex_executable_note: String = "The bundled Codex executable path is derived through codex_bundled_executable_for_host, not stored as an independent layout literal. GunbcHostLayout.codex_bundled_executable is FilePath? so Absent has no fabricated path constructor — executors refuse when the committed-lock placement is unavailable. Witness: srv1_committed_lock_bundled_executable_is_present." +// The bundled Codex executable path is derived through codex_bundled_executable_for_host, not +// stored as an independent layout literal. GunbcHostLayout.codex_bundled_executable is FilePath? so +// Absent has no fabricated path constructor — executors refuse when the committed-lock placement is +// unavailable. Witness: srv1_committed_lock_bundled_executable_is_present. fn srv1_codex_bundled_executable() -> FilePath? { codex_bundled_executable_for_host(host: "srv1" as HostIdentity) @@ -76,7 +159,20 @@ fn srv1_host_layout() -> GunbcHostLayout { } } -data srv2_host_layout_note: String = "srv2's own row, where a DECLARATION and an OBSERVATION are deliberately different things. A preflight exists to compare what this row says against what the machine holds and refuse on the difference, so a path may legitimately be declared before it exists — an apply that found no binary and quietly served something else, or found no auth root and dispatched unauthenticated, is the absorbing fallback; refusing with the exact path is the whole mechanism.\\n\\nSTATE AS OF 2026-07-30, and this row is the wrong place to keep tracking it: /opt/gunbc/bin/gunbc does not exist on srv2 (only /opt/gunbc/gunbc, a hand-made symlink into a session worktree), while the Codex state root now DOES exist and is authenticated — an earlier version of this note said it had never been created, and that sentence was already false within the hour. Prose describing mutable host state goes stale silently, which is why liveness belongs to the readiness probe that observes it and this row states only intent.\\n\\nThe codex home derives from the same account selection srv1 uses, so the two hosts share the convention rather than each spelling it. The controller repo exists because the preview instance these rows grew out of was authored on this machine." +// srv2's own row, where a DECLARATION and an OBSERVATION are deliberately different things. A +// preflight exists to compare what this row says against what the machine holds and refuse on the +// difference, so a path may legitimately be declared before it exists — an apply that found no +// binary and quietly served something else, or found no auth root and dispatched unauthenticated, +// is the absorbing fallback; refusing with the exact path is the whole mechanism.\n\nSTATE AS OF +// 2026-07-30, and this row is the wrong place to keep tracking it: /opt/gunbc/bin/gunbc does not +// exist on srv2 (only /opt/gunbc/gunbc, a hand-made symlink into a session worktree), while the +// Codex state root now DOES exist and is authenticated — an earlier version of this note said it +// had never been created, and that sentence was already false within the hour. Prose describing +// mutable host state goes stale silently, which is why liveness belongs to the readiness probe that +// observes it and this row states only intent.\n\nThe codex home derives from the same account +// selection srv1 uses, so the two hosts share the convention rather than each spelling it. The +// controller repo exists because the preview instance these rows grew out of was authored on this +// machine. data srv2_gunbc_serve_binary: FilePath = "/opt/gunbc/bin/gunbc" as FilePath @@ -96,13 +192,67 @@ fn srv2_host_layout() -> GunbcHostLayout { codex_bundled_executable: srv2_codex_bundled_executable(), } } -data managed_compile_pool_slice_note: String = "The ONE authority for the managed compile-pool slice unit name, homed here for the same reason as the paths above: it is a fact about how a HOST is laid out, not about any one consumer of it. Its live consumers are gunbc.build_cache_instance `ci_cache_instance` and `session_cache_instance`, which carry it as each deployed instance's `intended_compile_pool`, plus the `managed_compile_pool_slice_fixture` row in gunbc.host_build_cache_provision. STATE, stated so this row is not read as a description of the fleet: NOTHING PROVISIONS THIS SLICE YET. No observed host has it — the 2026-08-01 placement receipt found every CI cache endpoint owned by a runner-slot process, never by a managed unit inside a compile pool — so the name is declared intent that an observation is checked against, and gunbc.fleet_host_budget's `CompilePoolInRunnerSlots` correspondingly derives a ZERO pool today. CORRECTED 2026-08-01 (review 46182, and the review understated it — all three of this note's claims had gone stale in one edit, which is the §3 citation class this repo keeps paying for): it previously cited `ServerUnitOwned.slice_unit` in gunbc.host_build_cache_provision, said the emitted shell in gunbc.host_build_cache_provision_script greps the observed cgroup for this value, and justified its homing by an import cycle between those two consumers. `ServerUnitOwned` no longer exists (it is `ServerManagedUnitOwned { unit }` in gunbc.build_cache_instance, and placement is decided against the instance's `intended_unit` — a service, not this slice); the script carries no compile-pool reference at all, because the branch's emitted placement probe was deliberately not landed (that module is an A5 census row whose emitted shell may shrink but not grow); and with that consumer absent the two-consumer drift hazard and the cycle rationale describe a mechanism that is not in the tree." +// The ONE authority for the managed compile-pool slice unit name, homed here for the same reason as +// the paths above: it is a fact about how a HOST is laid out, not about any one consumer of it. Its +// live consumers are gunbc.build_cache_instance `ci_cache_instance` and `session_cache_instance`, +// which carry it as each deployed instance's `intended_compile_pool`, plus the +// `managed_compile_pool_slice_fixture` row in gunbc.host_build_cache_provision. STATE, stated so +// this row is not read as a description of the fleet: NOTHING PROVISIONS THIS SLICE YET. No +// observed host has it — the 2026-08-01 placement receipt found every CI cache endpoint owned by a +// runner-slot process, never by a managed unit inside a compile pool — so the name is declared +// intent that an observation is checked against, and gunbc.fleet_host_budget's +// `CompilePoolInRunnerSlots` correspondingly derives a ZERO pool today. CORRECTED 2026-08-01 +// (review 46182, and the review understated it — all three of this note's claims had gone stale in +// one edit, which is the §3 citation class this repo keeps paying for): it previously cited +// `ServerUnitOwned.slice_unit` in gunbc.host_build_cache_provision, said the emitted shell in +// gunbc.host_build_cache_provision_script greps the observed cgroup for this value, and justified +// its homing by an import cycle between those two consumers. `ServerUnitOwned` no longer exists (it +// is `ServerManagedUnitOwned { unit }` in gunbc.build_cache_instance, and placement is decided +// against the instance's `intended_unit` — a service, not this slice); the script carries no +// compile-pool reference at all, because the branch's emitted placement probe was deliberately not +// landed (that module is an A5 census row whose emitted shell may shrink but not grow); and with +// that consumer absent the two-consumer drift hazard and the cycle rationale describe a mechanism +// that is not in the tree. data gunbc_managed_compile_pool_slice: NonEmptyStr = "gunbc-compile-pool.slice" as NonEmptyStr -data compile_pool_placement_home_note: String = "THE PLACEMENT ROW LIVES BESIDE THE SLICE NAME BECAUSE THEY ARE ONE QUESTION, AND IT USED TO LIVE IN TWO HOMES. gunbc_managed_compile_pool_slice names the managed slice; gunbc_compile_pool_placement says whether this fleet HAS one. Split across two modules, both could be read independently and they disagreed: every BuildCacheInstance carried the slice name as intended_compile_pool unconditionally, while the placement row said CompilePoolInRunnerSlots, so the rendered cache unit named a Slice= that nothing provisions. Applying it would have had systemd create that cgroup implicitly WITH NO LIMITS -- the same unbounded compile charge gunbc_compile_pool_doc measured, now wearing the managed topology name, with the budget model believing a bounded pool existed.\n\nWHY HERE AND NOT IN gunbc.fleet_host_budget, where the row was declared. Measured, not preferred: fleet_host_budget transitively REACHES build_cache_instance, so the instance importing it would close a cycle, and acyclicity is the one structural law the import graph has. host_layout reaches neither consumer and both already import it, so the single authority costs no new edge. The semantic argument agrees with the measurement -- where compile RSS is charged is a fact about how a HOST is laid out, which is this module's whole subject, and the budget DERIVES from it rather than owning it.\n\nWHAT THIS DOES NOT DO: it does not flip the row. The fleet still declares CompilePoolInRunnerSlots and every live tripwire keyed on that value stays green. What changed is that a disagreement no longer has anywhere to live." - -data gunbc_compile_pool_placement_doc: String = "WHERE COMPILE RSS IS CHARGED IS A TOPOLOGY FACT, AND THE BUDGET IS DERIVED FROM IT — not a fudge factor. CompilePoolInRunnerSlots: no managed compile-pool slice exists, so cache-miss compiles are charged to whichever cgroup the sccache server sits in; the budget's compile_pool child is therefore ZERO because the pool is not a separate claim on host RAM — it is (nominally) already inside runner_pool. That is the honest description of today's topology, NOT an absorbing fallback: it is level-triggered on a declared row, it widens no failure arm, and it fabricates no converged verdict. Note it is also the topology in which a mis-placed server silently exports one slot's caps onto every compile on the host — that deficit is refused by gunbc.host_build_cache_provision's placement verdict, not papered over here. CompilePoolSliceDeclared: the managed unit owns the compiles in its own slice, so the pool becomes a first-class sibling child sized by the admitted jobserver tokens and runner_pool must shrink to keep the host conserving. DISSOLUTION TRIGGER: flip this row to CompilePoolSliceDeclared in the same change that converges the managed gunbc-build-cache-ci.service unit and its slice; conservation then refuses by construction if the slots were not shrunk." +// THE PLACEMENT ROW LIVES BESIDE THE SLICE NAME BECAUSE THEY ARE ONE QUESTION, AND IT USED TO LIVE +// IN TWO HOMES. gunbc_managed_compile_pool_slice names the managed slice; +// gunbc_compile_pool_placement says whether this fleet HAS one. Split across two modules, both +// could be read independently and they disagreed: every BuildCacheInstance carried the slice name +// as intended_compile_pool unconditionally, while the placement row said CompilePoolInRunnerSlots, +// so the rendered cache unit named a Slice= that nothing provisions. Applying it would have had +// systemd create that cgroup implicitly WITH NO LIMITS -- the same unbounded compile charge +// gunbc_compile_pool_doc measured, now wearing the managed topology name, with the budget model +// believing a bounded pool existed. +// +// WHY HERE AND NOT IN gunbc.fleet_host_budget, where the row was declared. Measured, not preferred: +// fleet_host_budget transitively REACHES build_cache_instance, so the instance importing it would +// close a cycle, and acyclicity is the one structural law the import graph has. host_layout reaches +// neither consumer and both already import it, so the single authority costs no new edge. The +// semantic argument agrees with the measurement -- where compile RSS is charged is a fact about how +// a HOST is laid out, which is this module's whole subject, and the budget DERIVES from it rather +// than owning it. +// +// WHAT THIS DOES NOT DO: it does not flip the row. The fleet still declares +// CompilePoolInRunnerSlots and every live tripwire keyed on that value stays green. What changed is +// that a disagreement no longer has anywhere to live. + +// WHERE COMPILE RSS IS CHARGED IS A TOPOLOGY FACT, AND THE BUDGET IS DERIVED FROM IT — not a fudge +// factor. CompilePoolInRunnerSlots: no managed compile-pool slice exists, so cache-miss compiles +// are charged to whichever cgroup the sccache server sits in; the budget's compile_pool child is +// therefore ZERO because the pool is not a separate claim on host RAM — it is (nominally) already +// inside runner_pool. That is the honest description of today's topology, NOT an absorbing +// fallback: it is level-triggered on a declared row, it widens no failure arm, and it fabricates no +// converged verdict. Note it is also the topology in which a mis-placed server silently exports one +// slot's caps onto every compile on the host — that deficit is refused by +// gunbc.host_build_cache_provision's placement verdict, not papered over here. +// CompilePoolSliceDeclared: the managed unit owns the compiles in its own slice, so the pool +// becomes a first-class sibling child sized by the admitted jobserver tokens and runner_pool must +// shrink to keep the host conserving. DISSOLUTION TRIGGER: flip this row to +// CompilePoolSliceDeclared in the same change that converges the managed +// gunbc-build-cache-ci.service unit and its slice; conservation then refuses by construction if the +// slots were not shrunk. type CompilePoolPlacement = CompilePoolSliceDeclared { slice_unit: String } @@ -110,7 +260,18 @@ type CompilePoolPlacement data gunbc_compile_pool_placement: CompilePoolPlacement = CompilePoolInRunnerSlots -data belt_spawn_workdir_env_var_note: String = "The ONE authority for the environment variable that carries a belt tick's spawn root, homed here for the same reason as the compile-pool slice name above: it is a fact about how a HOST is laid out and it has TWO consumers on opposite sides of a boundary, so neither may own it. gunbc.roadmap_belt_actuate READS it in belt_run_once_cli; gunbc.live_deploy.emit WRITES it as the Environment= line of the belt unit it installs. A literal in either place is the same string in two files, and the failure it produces is silent rather than loud: the writer and the reader would simply stop meeting, the reader would refuse for a missing variable, and nothing would say which of the two spellings was wrong.\\n\\nHoming it in either consumer would also drag one into the other's closure. The deploy emitter needs exactly one string from the belt; importing gunbc.roadmap_belt_actuate to get it would put the belt's whole implementation — tmux, git worktrees, the Claude provider preflight — behind every emission of a deploy script, for a name. That is the coupling this module exists to prevent." +// The ONE authority for the environment variable that carries a belt tick's spawn root, homed here +// for the same reason as the compile-pool slice name above: it is a fact about how a HOST is laid +// out and it has TWO consumers on opposite sides of a boundary, so neither may own it. +// gunbc.roadmap_belt_actuate READS it in belt_run_once_cli; gunbc.live_deploy.emit WRITES it as the +// Environment= line of the belt unit it installs. A literal in either place is the same string in +// two files, and the failure it produces is silent rather than loud: the writer and the reader +// would simply stop meeting, the reader would refuse for a missing variable, and nothing would say +// which of the two spellings was wrong.\n\nHoming it in either consumer would also drag one into +// the other's closure. The deploy emitter needs exactly one string from the belt; importing +// gunbc.roadmap_belt_actuate to get it would put the belt's whole implementation — tmux, git +// worktrees, the Claude provider preflight — behind every emission of a deploy script, for a name. +// That is the coupling this module exists to prevent. data belt_spawn_workdir_env_var: NonEmptyStr = "GUNBC_BELT_SPAWN_WORKDIR" as NonEmptyStr @@ -175,7 +336,11 @@ fn publication_answer_dir(instance_root: String) -> String { // install-d on this path and NOT per-run elevation. data fleet_converge_receipt_atomic_publish_note: String = "Receipt publication is gunbc.fleet_converge_cli converge_cli_atomic_receipt_write: Filesystem.Write to a same-directory *.staging file, then shell.Move.File over the final path. rename(2) takes permission from the containing directory, not the file being replaced, so a root-owned stale receipt is superseded without elevation when the service principal owns the directory. In-place Filesystem.Write on the target path fails EACCES on stale root-owned files and is crash-unsafe (truncated receipts). Deploy install-d ensures the directory exists and is owned by the service principal on fresh hosts; that bootstrap is orthogonal to publication. CORRECTED 2026-08-19: this clause named live_deploy emit FleetConvergeTimerUnit apply as the bootstrap's home, and that member is now a retired refusal poison (cadence moved to on-demand plan/apply, sleek-heron-218), so NOTHING had bootstrapped a receipts directory on any fresh instance root since that retirement — srv1's green slot has no receipts directory at all, and production's exists only because it predates it. The bootstrap now rides live_deploy emit BeltTimerUnit apply, on the consumer that needs the directory wherever it is installed — per-run sudo install on individual receipt files is not the publication fix when the directory is already service-user-owned." -data provider_runtime_placement_note: String = "ONE host-parameterized placement for provider package/exec release trees, mutable provider state, and private wire-evidence roots. Parallel to GunbcHostLayout: a second host must be describable without minting a second set of path concepts. Claude/Cursor/Codex lanes consume this record; they do not re-author /opt/... roots. The flat srv1_* path data below are the once-written field values of srv1's row — the same dissolution pattern as gunbc_host_layout_note." +// ONE host-parameterized placement for provider package/exec release trees, mutable provider state, +// and private wire-evidence roots. Parallel to GunbcHostLayout: a second host must be describable +// without minting a second set of path concepts. Claude/Cursor/Codex lanes consume this record; +// they do not re-author /opt/... roots. The flat srv1_* path data below are the once-written field +// values of srv1's row — the same dissolution pattern as gunbc_host_layout_note. type ProviderRuntimePlacement { runtime_release_root: FilePath @@ -207,7 +372,9 @@ fn provider_runtime_placement_for_host(host: HostIdentity) -> ProviderRuntimePla } } -data provider_runtime_placement_shared_row_note: String = "srv1..srv4 currently project srv1_provider_runtime_placement() — paths are convention-uniform today, not derived per-host. dissolve-on: each host's GunbcHostLayout row carries its own ProviderRuntimePlacement when a fleet host diverges from the srv1-shaped paths." +// srv1..srv4 currently project srv1_provider_runtime_placement() — paths are convention-uniform +// today, not derived per-host. dissolve-on: each host's GunbcHostLayout row carries its own +// ProviderRuntimePlacement when a fleet host diverges from the srv1-shaped paths. fn codex_bundled_executable_for_host(host: HostIdentity) -> FilePath? { match provider_runtime_placement_for_host(host: host) { diff --git a/dag/gunbc/host/host_memory_qualification.dag b/dag/gunbc/host/host_memory_qualification.dag index 2d03cb88dad..f6765f4d6e9 100644 --- a/dag/gunbc/host/host_memory_qualification.dag +++ b/dag/gunbc/host/host_memory_qualification.dag @@ -89,7 +89,20 @@ fn host_boot_is_established(standing: HostMemoryStanding) -> Bool { } } -data host_memory_qualification_note: String = "Written after the 2026-08-11 srv3/srv4 incident, where the collapse of these four axes into one 'does the RAM work' question cost three refuted hypotheses. The four axes stood as follows for Samsung M386A4G40DM0-CPB (32GB, 4Rx4, DDR4-2133 LRDIMM) in an eight-module population on ALTRAD8UD-1L2T: class support TRUE (the board states generic RDIMM and LRDIMM support); specific part qualification NOT LISTED in the Ampere Altra Family AVL issue 1.15 of December 2025, which contains neither this part number nor the M386A4G40 family nor any 2133 entry, and whose qualified Samsung 32GB rows are 1Rx4 or 2Rx4 at 2666/2933/3200 while its 4Rx4 rows are 128GB parts tested at 3200; training OPERATIONAL with mask 0x5555 on both hosts after BIOS 3.10; boot REPEATING FIRMWARE RESET at token E588. Absence from an AVL is not a proof of incompatibility and this module does not encode it as one — it is the absence of a positive qualification result, which is why NotListedInVendorAvl is a distinct variant from an unobserved qualification and from a refutation. The documented ALTRAD8UD populations are A1; A1+E1; A1+B1+E1+F1; and A1 through H1: note that the four-module population is NOT one-per-channel, so the diagnostic plan this module's admission check refuses is a real plan that was about to be executed." +// Written after the 2026-08-11 srv3/srv4 incident, where the collapse of these four axes into one +// 'does the RAM work' question cost three refuted hypotheses. The four axes stood as follows for +// Samsung M386A4G40DM0-CPB (32GB, 4Rx4, DDR4-2133 LRDIMM) in an eight-module population on +// ALTRAD8UD-1L2T: class support TRUE (the board states generic RDIMM and LRDIMM support); specific +// part qualification NOT LISTED in the Ampere Altra Family AVL issue 1.15 of December 2025, which +// contains neither this part number nor the M386A4G40 family nor any 2133 entry, and whose +// qualified Samsung 32GB rows are 1Rx4 or 2Rx4 at 2666/2933/3200 while its 4Rx4 rows are 128GB +// parts tested at 3200; training OPERATIONAL with mask 0x5555 on both hosts after BIOS 3.10; boot +// REPEATING FIRMWARE RESET at token E588. Absence from an AVL is not a proof of incompatibility and +// this module does not encode it as one — it is the absence of a positive qualification result, +// which is why NotListedInVendorAvl is a distinct variant from an unobserved qualification and from +// a refutation. The documented ALTRAD8UD populations are A1; A1+E1; A1+B1+E1+F1; and A1 through H1: +// note that the four-module population is NOT one-per-channel, so the diagnostic plan this module's +// admission check refuses is a real plan that was about to be executed. // CAUSAL EXPERIMENTS ARE RECEIPTS, NOT PROSE. Each records what was actually // done, so a later reader can re-judge the verdict against the intervention @@ -146,7 +159,6 @@ data srv3_incident_experiments: List = [ }, ] - // --- PLATFORM TRAINING FAILURES AND WHAT THEY IMPLICATE ------------------------------------------ // // RELOCATED HERE 2026-08-19 (operator ruling) when gunbc.memory_part_selection @@ -293,7 +305,6 @@ fn topology_pressure( } } - data altrad8ud_mrep_failure_observation: PlatformTrainingFailure = PlatformTrainingFailure { board: AsrockRackAltrad8ud1l2t, step: LrdimmDataBufferMrep, @@ -305,7 +316,6 @@ data altrad8ud_mrep_failure_observation: PlatformTrainingFailure = PlatformTrain observed_at: 1786590000000, } - // --- THE 2026-08-22 REPLACEMENT, GRADED ON THE SAME FOUR AXES ----------------------------------- // // WHY THESE ROWS EXIST AT ALL, given that both hosts are up and serving. The 2026-08-11 incident's @@ -372,7 +382,17 @@ data srv4_new_population_standing: HostMemoryStanding = HostMemoryStanding { observed_at: 1787427958000, } -data srv4_observed_at_is_a_window_lower_bound_note: String = "srv4's observed_at is the LOWER BOUND OF A WINDOW, NOT A KNOWN INSTANT, and the difference is recorded because the carrier cannot express it. The srv4 Redfish population read was taken between 19:45Z and 20:15Z on 2026-08-22 and the exact minute was not recorded; 1787427958000 is 19:45:58Z, the timestamp of the correctable-ECC entry this session read first, so it is the earliest instant the read can have occurred at. srv3's value IS an instant, because that read was taken against a live boot transition where the clock mattered. EpochMs admits only a point, so a bounded observation must either fabricate a second or pick a bound and say which -- this picks the lower bound. A consumer computing an age from this row gets an OVERESTIMATE of elapsed time, never an underestimate, which is the safe direction for a staleness question. DISSOLVE-ON: an observed-at carrier that can express a bounded window, at which point this row moves into it and this note goes." +// srv4's observed_at is the LOWER BOUND OF A WINDOW, NOT A KNOWN INSTANT, and the difference is +// recorded because the carrier cannot express it. The srv4 Redfish population read was taken +// between 19:45Z and 20:15Z on 2026-08-22 and the exact minute was not recorded; 1787427958000 is +// 19:45:58Z, the timestamp of the correctable-ECC entry this session read first, so it is the +// earliest instant the read can have occurred at. srv3's value IS an instant, because that read was +// taken against a live boot transition where the clock mattered. EpochMs admits only a point, so a +// bounded observation must either fabricate a second or pick a bound and say which -- this picks +// the lower bound. A consumer computing an age from this row gets an OVERESTIMATE of elapsed time, +// never an underestimate, which is the safe direction for a staleness question. DISSOLVE-ON: an +// observed-at carrier that can express a bounded window, at which point this row moves into it and +// this note goes. // srv2 FAILED THE SAME PART THAT THREE SIBLINGS TRAINED, AND THAT IS THE MOST INFORMATIVE ROW // IN THIS MODULE (2026-08-22). srv1, srv3 and srv4 all POSTed on the 64 GiB Samsung population; @@ -419,7 +439,15 @@ data srv2_failed_64gib_population_standing: HostMemoryStanding = HostMemoryStand observed_at: 1787436400000, } -data srv2_boot_serial_unobservable_note: String = "srv2 HostLogger held ZERO entries across both the failed 64 GiB cycles AND the successful 8 x 16 GiB boot that followed. The successful boot is the control: it proves the absence is a missing recorder, not a mute SoC. Consequence for any retry: the training axis cannot be observed on this fleet until host serial capture works (the obmc-console server configs for ttyS1/ttyS2/ttyS3 exist -- ttyS2 is the SCP, which runs DDR training -- but nothing is captured), so the highest-value action before re-testing 64 GiB on srv2 is fixing serial capture or attaching a physical cable. Failing that, the per-DIMM thermal sensors are the working instrument: the baseline is all eight at 67-75 C during a FAILED run, so a future attempt showing one channel cool fingers that channel with no firmware log needed." +// srv2 HostLogger held ZERO entries across both the failed 64 GiB cycles AND the successful 8 x 16 +// GiB boot that followed. The successful boot is the control: it proves the absence is a missing +// recorder, not a mute SoC. Consequence for any retry: the training axis cannot be observed on this +// fleet until host serial capture works (the obmc-console server configs for ttyS1/ttyS2/ttyS3 +// exist -- ttyS2 is the SCP, which runs DDR training -- but nothing is captured), so the +// highest-value action before re-testing 64 GiB on srv2 is fixing serial capture or attaching a +// physical cable. Failing that, the per-DIMM thermal sensors are the working instrument: the +// baseline is all eight at 67-75 C during a FAILED run, so a future attempt showing one channel +// cool fingers that channel with no firmware log needed. // THE ONE OPEN ITEM ON THIS POPULATION, kept as data rather than left in a chat log. // srv4 logged a single CORRECTABLE ECC error at 2026-08-22T19:45:57Z -- Socket 0 / Channel 0 / @@ -430,6 +458,9 @@ data srv2_boot_serial_unobservable_note: String = "srv2 HostLogger held ZERO ent // It is recorded because the distinguishing fact is a REPEAT at the same bank/row/column, which // separates a marginal device from a single upset, and nobody can look for a repeat against an // event that was never written down. -data srv4_correctable_ecc_watch_note: String = "srv4 dimm0 (Bank 1 DDR4_A1, serial 026B6F84): one MemoryECCCorrectable at 2026-08-22T19:45:57Z, bank 13 row 166 column 96, decoded through the BMC's own OpenBMC message registry rather than guessed. Watch for recurrence at the same coordinates. A second event there is a device-level finding; scattered single events across different addresses are not." +// srv4 dimm0 (Bank 1 DDR4_A1, serial 026B6F84): one MemoryECCCorrectable at 2026-08-22T19:45:57Z, +// bank 13 row 166 column 96, decoded through the BMC's own OpenBMC message registry rather than +// guessed. Watch for recurrence at the same coordinates. A second event there is a device-level +// finding; scattered single events across different addresses are not. data platform_training_implication_note: String = "RELOCATED 2026-08-19 from the deleted gunbc.memory_part_selection, whose candidate/frontier half was a selection island; this evidence half is qualification, so it lives beside the four standings it grades. The grading rule is mechanical and derived: a candidate is graded by whether it must execute a training step THIS platform has been observed to fail, and by how strongly that failure is evidenced. For the ALTRAD8UD-1L2T the step is the DDR4 data-buffer read-phase calibration (MREP), which only load-reduced modules execute, so the grading is independent of vendor, part number, capacity, speed grade, and presence on any approved-vendor list. THE SEVERITY IS A FUNCTION OF EVIDENCE, and an earlier revision of this module got that wrong in a way worth recording: it refused every module executing the failing step, on any board, at any standing including EvidenceUnobserved. Because capacities near a generation's die-density ceiling are disproportionately load-reduced, that blanket rule removed most of the 64GB DDR4 market by construction — the exact blocklist this module's prose claimed to avoid, reached by a different route, and the opposite of letting a buyer shop the whole market. Now: a decode of this platform's own syndrome register refuses; an association carrying byte-identical controller status words, or an inference from sequence position, requires a SAMPLE before committing to a full population and stays on the Pareto frontier; an unobserved standing implicates nothing. gunbc holds MREP at AssociatedByIdenticalSignature, so the honest verdict for another ALTRAD8UD LRDIMM today is buy-one-and-test, not reject. Failures are scoped to the board they were observed on, so an ALTRAD8UD receipt cannot speak for a different platform. KNOWN GAP, stated rather than papered over: BaseboardModel currently has exactly one variant, so the board scoping is real in the code but has no executing cross-board control — minting a second board purely to test it would be a fabricated entity, and the control lands when a second real board is modeled. The second axis, topology pressure, is derived rather than observed: when a capacity cannot be reached as a dual-rank registered module within the generation's monolithic die-density ceiling, that capacity forces extra ranks or stacking, which is why 64GB DDR4 at older speed grades is disproportionately load-reduced while 32GB is not. A buyer economizing on speed grade at 64GB is therefore not buying the same module more slowly, it is a different topology — which is a reason to sample it, not a reason to refuse it." diff --git a/dag/gunbc/host/host_network_diagnosis.dag b/dag/gunbc/host/host_network_diagnosis.dag index 1a8012942db..ecc2c1c0feb 100644 --- a/dag/gunbc/host/host_network_diagnosis.dag +++ b/dag/gunbc/host/host_network_diagnosis.dag @@ -39,7 +39,16 @@ import extdeps.netplan.netplan { interface_has_link, } -data host_network_diagnosis_authority_note: String = "The fold that turns three independently-observable facts — is a netplan config present, is the interface configured, does it have carrier — into a located diagnosis naming the LAYER at fault. It exists because those three were collapsed by hand during the srv4 outage of 2026-07-25 and the collapse produced a confident wrong answer: cloud-init was found disabled, that was read as the config never being applied, and a whole causal story was built on it while the machine's netplan was in fact present, correct and applied, and the real fault was a dead physical link. The generator's state is not an input to this fold AT ALL, and that is the point — it cannot be, because it does not bear on whether the config is in effect (extdeps.netplan.netplan netplan_file_is_applied). What the generator's state DOES determine is who owns the file going forward, which is a separate question this module answers separately." +// The fold that turns three independently-observable facts — is a netplan config present, is the +// interface configured, does it have carrier — into a located diagnosis naming the LAYER at fault. +// It exists because those three were collapsed by hand during the srv4 outage of 2026-07-25 and the +// collapse produced a confident wrong answer: cloud-init was found disabled, that was read as the +// config never being applied, and a whole causal story was built on it while the machine's netplan +// was in fact present, correct and applied, and the real fault was a dead physical link. The +// generator's state is not an input to this fold AT ALL, and that is the point — it cannot be, +// because it does not bear on whether the config is in effect (extdeps.netplan.netplan +// netplan_file_is_applied). What the generator's state DOES determine is who owns the file going +// forward, which is a separate question this module answers separately. type HostNetworkDiagnosis = NetworkConfigAbsent { detail: NonEmptyStr } @@ -80,7 +89,14 @@ fn netplan_file_ownership(d: CloudInitDisposition) -> CloudInitGeneratedFileStat cloud_init_generated_file_state(d: d) } -data srv4_incident_note: String = "The srv4 readings of 2026-07-25, kept as cited fixtures because they are the discriminating input for this whole module and because a synthetic pair could be made to satisfy the fold by accident. Observed live over the BMC serial console after the host dropped off the network: cloud-init disabled by the Ubuntu live installer's marker file; /etc/netplan/50-cloud-init.yaml present and correct, naming enP3p3s0f1 with dhcp4 true and critical true; enP3p3s0f1 administratively UP with qdisc mq and carrier 0; the machine's unused sibling ports at qdisc noop. The fault survived a graceful power-down, a twenty-second de-energised hold and a power-up, which is what rules out NIC firmware or driver state and leaves the cable, the switch port or the PHY." +// The srv4 readings of 2026-07-25, kept as cited fixtures because they are the discriminating input +// for this whole module and because a synthetic pair could be made to satisfy the fold by accident. +// Observed live over the BMC serial console after the host dropped off the network: cloud-init +// disabled by the Ubuntu live installer's marker file; /etc/netplan/50-cloud-init.yaml present and +// correct, naming enP3p3s0f1 with dhcp4 true and critical true; enP3p3s0f1 administratively UP with +// qdisc mq and carrier 0; the machine's unused sibling ports at qdisc noop. The fault survived a +// graceful power-down, a twenty-second de-energised hold and a power-up, which is what rules out +// NIC firmware or driver state and leaves the cable, the switch port or the PHY. data srv4_cloud_init_disposition: CloudInitDisposition = CloudInitDisabledByMarkerFile { path: cloud_init_disabled_marker_path, @@ -106,7 +122,12 @@ data srv4_unused_port: InterfaceObservation = InterfaceObservation { carrier: CarrierUnobserved, } -data disabled_generator_widen_red_control_note: String = "THE forbidden inference, written as an executable declaration rather than a warning: if the generator is disabled, report the configuration as absent. This is not a strawman — it is the reasoning actually performed during the srv4 outage, and it is seductive because it is nearly right in a neighbouring case (a generator disabled BEFORE it ever ran does leave no config). The gate rejects it on srv4's own inputs and accepts the authority on the same inputs, so the separation is proven to be a decision rather than a coincidence." +// THE forbidden inference, written as an executable declaration rather than a warning: if the +// generator is disabled, report the configuration as absent. This is not a strawman — it is the +// reasoning actually performed during the srv4 outage, and it is seductive because it is nearly +// right in a neighbouring case (a generator disabled BEFORE it ever ran does leave no config). The +// gate rejects it on srv4's own inputs and accepts the authority on the same inputs, so the +// separation is proven to be a decision rather than a coincidence. fn diagnose_via_generator_state_widen_sketch( generator: CloudInitDisposition, @@ -146,7 +167,16 @@ fn diagnosis_gate_accepts(d: HostNetworkDiagnosis, observed: InterfaceObservatio } } -data network_axis_cell_note: String = "What makes this fold a MECHANISM rather than a description of a past incident: the network becomes a per-host axis on the same status matrix the swap-backing and toolchain-completeness axes sit on, with the same three parts — a per-host verdict, routed through the read-back independence criterion, and a COUNTED fleet tally of the hosts it cannot see. The count is the load-bearing part. There is no live producer for interface observations today, so every host answers UnknownRefused and the tally is the full fleet; that is a typed, located, countable deficit which RANKS for fixing, and it is the opposite of the absorbing fallback this lane keeps refusing (DESIGN §5 — a masked cost displaces nothing, so a deficit whose frequency is zero by construction never gets fixed). When a producer lands, the tally falls on its own and the fall is the receipt." +// What makes this fold a MECHANISM rather than a description of a past incident: the network +// becomes a per-host axis on the same status matrix the swap-backing and toolchain-completeness +// axes sit on, with the same three parts — a per-host verdict, routed through the read-back +// independence criterion, and a COUNTED fleet tally of the hosts it cannot see. The count is the +// load-bearing part. There is no live producer for interface observations today, so every host +// answers UnknownRefused and the tally is the full fleet; that is a typed, located, countable +// deficit which RANKS for fixing, and it is the opposite of the absorbing fallback this lane keeps +// refusing (DESIGN §5 — a masked cost displaces nothing, so a deficit whose frequency is zero by +// construction never gets fixed). When a producer lands, the tally falls on its own and the fall is +// the receipt. // Both decisive failures map to Drifted, and the layer they name is deliberately NOT carried on the verdict. The status cell answers one question — is this host's network converged — and the answer for a missing config and for a dead cable is the same 'no'; what differs is the remedy, and the remedy lives on HostNetworkDiagnosis, which the fold returns and which every consumer that needs to act reads instead of the cell. Collapsing them on the CELL is a projection; collapsing them in the FOLD would be the state-space conflation, and that is why diagnose_host_network keeps them apart. LinkStateUnobserved maps to UnknownRefused rather than to either decisive verdict, because 'I could not see the link' and 'the link is down' are the ⊤-as-ignorance / ⊤-as-answer pair §5 names. fn network_diagnosis_verdict(d: HostNetworkDiagnosis) -> ObservationVerdict { @@ -158,7 +188,12 @@ fn network_diagnosis_verdict(d: HostNetworkDiagnosis) -> ObservationVerdict { } } -data host_network_producer_absent_note: String = "No live producer exists yet, and these two return none rather than reaching for the srv4 incident fixtures a few declarations above. Answering a HOST query with an INCIDENT fixture would be the fabricated plausible output §5 forbids — it would report srv4 as observed-and-broken on a reading taken once, by hand, over a serial console, and it would report the other three hosts as whatever the fixture happened to say. The fixtures stay where they belong: inputs to the witness, which replays the incident deliberately and says so." +// No live producer exists yet, and these two return none rather than reaching for the srv4 incident +// fixtures a few declarations above. Answering a HOST query with an INCIDENT fixture would be the +// fabricated plausible output §5 forbids — it would report srv4 as observed-and-broken on a reading +// taken once, by hand, over a serial console, and it would report the other three hosts as whatever +// the fixture happened to say. The fixtures stay where they belong: inputs to the witness, which +// replays the incident deliberately and says so. fn host_network_config(host: HostIdentity) -> NetplanConfigFile? { none @@ -181,8 +216,16 @@ fn host_network_verdict(host: HostIdentity) -> ObservationVerdict { } } - -data subsumption_persistence_bar_note: String = "The acceptance bar this feeds, stated so the modelling has a consumer rather than being a description of a past incident. A host is not subsumed because its network works NOW; it is subsumed when its network configuration is present, applied, and PROVEN to survive a power cycle — the configuration is a file the applier reads at boot, so the only honest proof is a boot. That is a dry-provable property here (config present and applied implies the same diagnosis after a restart, since no input to the fold is runtime state) and a wet one on a live host. Note what this bar would NOT have caught: srv4's fault was physical, so a persistence proof would have passed on it right up until the link died. The two mechanisms are complementary and neither substitutes for the other — persistence catches applied-but-not-persisted, a liveness cell catches works-then-stops." +// The acceptance bar this feeds, stated so the modelling has a consumer rather than being a +// description of a past incident. A host is not subsumed because its network works NOW; it is +// subsumed when its network configuration is present, applied, and PROVEN to survive a power cycle +// — the configuration is a file the applier reads at boot, so the only honest proof is a boot. That +// is a dry-provable property here (config present and applied implies the same diagnosis after a +// restart, since no input to the fold is runtime state) and a wet one on a live host. Note what +// this bar would NOT have caught: srv4's fault was physical, so a persistence proof would have +// passed on it right up until the link died. The two mechanisms are complementary and neither +// substitutes for the other — persistence catches applied-but-not-persisted, a liveness cell +// catches works-then-stops. fn diagnosis_is_reboot_stable(config: NetplanConfigFile?, observed: InterfaceObservation) -> Bool { let before = diagnose_host_network(config: config, observed: observed) diff --git a/dag/gunbc/host/host_phase_status.dag b/dag/gunbc/host/host_phase_status.dag index 5562529363a..8218aaa4457 100644 --- a/dag/gunbc/host/host_phase_status.dag +++ b/dag/gunbc/host/host_phase_status.dag @@ -41,9 +41,43 @@ import std.measure { millisecond } type HostPhaseCellMemberAt = String -data host_phase_status_authority_note: String = "THE per-(host x phase) status axis for the host-standup spine. It exists because the spine could describe itself but not its RELATIONSHIP TO THE LIVE FLEET: every phase carried a PhaseDisposition (is there a modeled authority?) and nothing carried an observation (does this host actually agree with it?). Answering 'where does subsumption stand' therefore meant a session reconstruction — hand ssh probes, prose transcription — and the answer went stale the moment it was written. Two axes, kept ORTHOGONAL on purpose, because collapsing them is the state-space conflation this lane keeps paying for: the MODELED axis is PhaseDisposition, a property of the phase, the same for every host; the LIVE axis is std.upsert_decision.ObservationVerdict, a property of the (host, phase) CELL. A phase can be fully modeled AND Inaccessible on a given host — that is exactly srv1/srv2 for the build-cache phase today — and a single fused enum could not say it. Note what is NOT minted here: no Converged|Diverged|Unreachable|Unmodeled verdict of our own. ObservationVerdict already carries Converged/Absent/Drifted/Conflict/Inaccessible/UnknownRefused, which is STRICTLY FINER on the diverged and unreachable axes, and it is already consumed by twelve modules including host_standup itself; a parallel four-arm verdict beside it would be the §3 nickname, duplicated again in everything derived from it. 'Unmodeled' is likewise not an observation and does not become a verdict arm — it stays on PhaseDisposition, carried on the cell beside the observation and derived from the spine, never authored. CORRECTION, found by execution 2026-07-25: the cell was FIRST written as a SUM (StatusUnmodeled | StatusObserved), which forced the two axes to be mutually exclusive and made the fold discard the observation on any DeclaredGap phase. The witness caught it immediately, because the single cell that CARRIES AN OBSERVATION today is (srv3, prefix:os-install-actuated) — a DeclaredGap phase — so live-observation coverage computed as 0 while a real Converged verdict sat one branch away. The sum was the very conflation this note claims to avoid, written into the type by the same hand that wrote the warning. The cell is now a PRODUCT of both axes, which also gives the lane a state it could not previously say: observed Converged on an unmodeled phase means the host is in the desired condition by means the model cannot reproduce — true of srv3's OS install, and exactly the kind of fact that should never round to either 'modeled' or 'gap'." - -data host_phase_cell_grain_note: String = "The cell is keyed by (host, phase_label) and NOT by (host, phase) structurally, because phase_label is already the spine's own stable identity for a step (it is what the gap ledger keyed on, what the refusal strings name, and what an operator reads). Keying on the label keeps ONE identity for a phase across the spine, this matrix, and every refusal message, rather than introducing a second notion of phase identity that could disagree with the first. key_of returns identity, never content (membership_reconcile's R2 contract): a cell whose STATUS changes is Modified — the same cell re-derived — not Removed+Added, which would read as a host leaving and rejoining the fleet." +// THE per-(host x phase) status axis for the host-standup spine. It exists because the spine could +// describe itself but not its RELATIONSHIP TO THE LIVE FLEET: every phase carried a +// PhaseDisposition (is there a modeled authority?) and nothing carried an observation (does this +// host actually agree with it?). Answering 'where does subsumption stand' therefore meant a session +// reconstruction — hand ssh probes, prose transcription — and the answer went stale the moment it +// was written. Two axes, kept ORTHOGONAL on purpose, because collapsing them is the state-space +// conflation this lane keeps paying for: the MODELED axis is PhaseDisposition, a property of the +// phase, the same for every host; the LIVE axis is std.upsert_decision.ObservationVerdict, a +// property of the (host, phase) CELL. A phase can be fully modeled AND Inaccessible on a given host +// — that is exactly srv1/srv2 for the build-cache phase today — and a single fused enum could not +// say it. Note what is NOT minted here: no Converged|Diverged|Unreachable|Unmodeled verdict of our +// own. ObservationVerdict already carries +// Converged/Absent/Drifted/Conflict/Inaccessible/UnknownRefused, which is STRICTLY FINER on the +// diverged and unreachable axes, and it is already consumed by twelve modules including +// host_standup itself; a parallel four-arm verdict beside it would be the §3 nickname, duplicated +// again in everything derived from it. 'Unmodeled' is likewise not an observation and does not +// become a verdict arm — it stays on PhaseDisposition, carried on the cell beside the observation +// and derived from the spine, never authored. CORRECTION, found by execution 2026-07-25: the cell +// was FIRST written as a SUM (StatusUnmodeled | StatusObserved), which forced the two axes to be +// mutually exclusive and made the fold discard the observation on any DeclaredGap phase. The +// witness caught it immediately, because the single cell that CARRIES AN OBSERVATION today is +// (srv3, prefix:os-install-actuated) — a DeclaredGap phase — so live-observation coverage computed +// as 0 while a real Converged verdict sat one branch away. The sum was the very conflation this +// note claims to avoid, written into the type by the same hand that wrote the warning. The cell is +// now a PRODUCT of both axes, which also gives the lane a state it could not previously say: +// observed Converged on an unmodeled phase means the host is in the desired condition by means the +// model cannot reproduce — true of srv3's OS install, and exactly the kind of fact that should +// never round to either 'modeled' or 'gap'. + +// The cell is keyed by (host, phase_label) and NOT by (host, phase) structurally, because +// phase_label is already the spine's own stable identity for a step (it is what the gap ledger +// keyed on, what the refusal strings name, and what an operator reads). Keying on the label keeps +// ONE identity for a phase across the spine, this matrix, and every refusal message, rather than +// introducing a second notion of phase identity that could disagree with the first. key_of returns +// identity, never content (membership_reconcile's R2 contract): a cell whose STATUS changes is +// Modified — the same cell re-derived — not Removed+Added, which would read as a host leaving and +// rejoining the fleet. type HostPhaseCell { host: HostIdentity @@ -56,7 +90,23 @@ type HostPhaseCellStatus { observation: ObservationVerdict? } -data host_phase_no_producer_is_unknown_not_converged_note: String = "THE fail-closed hinge of this module (DESIGN §5). When a modeled phase has NO live observation producer for a host, the derived verdict is UnknownRefused — never Converged, and never silently omitted from the matrix. The tempting arm is the absorbing fallback in its purest form: nothing is known to be wrong, so report converged; the matrix goes green, the coverage deficit's frequency drops to zero by construction, and the status read becomes a fabricated plausible output exactly like a stats probe that auto-starts the server it is measuring (the DaemonUnsupervised finding, #7213). It is also why UnknownRefused and Inaccessible must stay DISTINCT arms rather than one 'not converged': Inaccessible means we asked the host and could not reach it (remedy: fix reach — the A1 materialized-secret lane); UnknownRefused means we have no way to ask at all (remedy: build the producer). Different states, different owners, different work — and the count of each is the honest measure of how far subsumption actually is. host_phase_verdict_absorbing_fallback_widen_sketch models the forbidden arm as a RED control so the fold cannot drift back into it unnoticed. Note the separation this correction forced: 'has a live producer' is now read from the OBSERVATION being Present, not from the verdict being something other than UnknownRefused — a producer that runs and honestly refuses (an unreadable lease table) is coverage, while no producer at all is not, and the earlier version conflated the two." +// THE fail-closed hinge of this module (DESIGN §5). When a modeled phase has NO live observation +// producer for a host, the derived verdict is UnknownRefused — never Converged, and never silently +// omitted from the matrix. The tempting arm is the absorbing fallback in its purest form: nothing +// is known to be wrong, so report converged; the matrix goes green, the coverage deficit's +// frequency drops to zero by construction, and the status read becomes a fabricated plausible +// output exactly like a stats probe that auto-starts the server it is measuring (the +// DaemonUnsupervised finding, #7213). It is also why UnknownRefused and Inaccessible must stay +// DISTINCT arms rather than one 'not converged': Inaccessible means we asked the host and could not +// reach it (remedy: fix reach — the A1 materialized-secret lane); UnknownRefused means we have no +// way to ask at all (remedy: build the producer). Different states, different owners, different +// work — and the count of each is the honest measure of how far subsumption actually is. +// host_phase_verdict_absorbing_fallback_widen_sketch models the forbidden arm as a RED control so +// the fold cannot drift back into it unnoticed. Note the separation this correction forced: 'has a +// live producer' is now read from the OBSERVATION being Present, not from the verdict being +// something other than UnknownRefused — a producer that runs and honestly refuses (an unreadable +// lease table) is coverage, while no producer at all is not, and the earlier version conflated the +// two. fn host_phase_verdict_from(observation: ObservationVerdict?) -> ObservationVerdict { match observation { @@ -110,7 +160,35 @@ fn host_phase_cell_has_live_producer(c: HostPhaseCellStatus) -> Bool { } } -data host_phase_observation_frontier_note: String = "The live-observation producer registry, and deliberately a TYPED, COUNTABLE FRONTIER rather than a table of hand-written facts (the self-host frontier shape, DESIGN §7: each cell is either produced or it is a declared absence, never a silent assumption). Exactly one cell carries an observation today — (srv3, prefix:os-install-actuated), which deduces its verdict through gunbc.host_standup_assimilation_deduction; that deduction was ALREADY host-parameterized (deduce_os_install_actuation_verdict takes fleet_slot: HostIdentity), so generalizing it was reading the parameter it already had, not writing new machinery. CORRECTED 2026-08-09, by execution: this sentence previously called that cell a REAL PRODUCER, and it is not one. Its DHCP lease table is srv3_post_install_lease_table_fixture, an authored data constant in gunbc.network_identity_subsumption whose one dynamic row carries the placeholder MAC 00:00:00:00:00:00 — no effect anywhere in the tree produces a DhcpLeaseTableObservation. So host_phase_observation_coverage_count, which this note designates as the number that measures the debt, counts one covered cell and that cell is authored, overstating coverage by its entire population; and host_phase_cell_has_live_producer tests only that the observation is Present, never that anything executed. The rule below was already written and was simply not enforceable — nothing would have reddened had a second host been wired the same way, which is precisely what Spark collector-fact enrollment for srv5/srv6 was about to do. NEXT-RUNG TRIGGER, named rather than silently stalled (DESIGN §4b): observation provenance becomes a carried fact so authored and executed are different states, and coverage counts only the executed one. Until then the honest reading of the coverage number is ZERO. Controls: test.claim.host_phase_status witness_covered_cell_verdict_is_selected_by_authorship_alone and witness_every_covered_cell_today_is_the_authored_one. Every other cell returns Absent, which host_phase_verdict_from renders UnknownRefused. What must NOT happen here is the shape this module exists to kill: hand-entering an observation because someone ssh'd a host once. A probe run by a person is not a modeled producer — encoding its result as a data row would rebuild the parallel ledger the gap-ledger dissolution just removed, and it would go stale in exactly the same way. A cell earns a row here when an effect produces its verdict by execution; until then UnknownRefused is the honest answer and host_phase_observation_coverage_count is the number that measures the debt." +// The live-observation producer registry, and deliberately a TYPED, COUNTABLE FRONTIER rather than +// a table of hand-written facts (the self-host frontier shape, DESIGN §7: each cell is either +// produced or it is a declared absence, never a silent assumption). Exactly one cell carries an +// observation today — (srv3, prefix:os-install-actuated), which deduces its verdict through +// gunbc.host_standup_assimilation_deduction; that deduction was ALREADY host-parameterized +// (deduce_os_install_actuation_verdict takes fleet_slot: HostIdentity), so generalizing it was +// reading the parameter it already had, not writing new machinery. CORRECTED 2026-08-09, by +// execution: this sentence previously called that cell a REAL PRODUCER, and it is not one. Its DHCP +// lease table is srv3_post_install_lease_table_fixture, an authored data constant in +// gunbc.network_identity_subsumption whose one dynamic row carries the placeholder MAC +// 00:00:00:00:00:00 — no effect anywhere in the tree produces a DhcpLeaseTableObservation. So +// host_phase_observation_coverage_count, which this note designates as the number that measures the +// debt, counts one covered cell and that cell is authored, overstating coverage by its entire +// population; and host_phase_cell_has_live_producer tests only that the observation is Present, +// never that anything executed. The rule below was already written and was simply not enforceable — +// nothing would have reddened had a second host been wired the same way, which is precisely what +// Spark collector-fact enrollment for srv5/srv6 was about to do. NEXT-RUNG TRIGGER, named rather +// than silently stalled (DESIGN §4b): observation provenance becomes a carried fact so authored and +// executed are different states, and coverage counts only the executed one. Until then the honest +// reading of the coverage number is ZERO. Controls: test.claim.host_phase_status +// witness_covered_cell_verdict_is_selected_by_authorship_alone and +// witness_every_covered_cell_today_is_the_authored_one. Every other cell returns Absent, which +// host_phase_verdict_from renders UnknownRefused. What must NOT happen here is the shape this +// module exists to kill: hand-entering an observation because someone ssh'd a host once. A probe +// run by a person is not a modeled producer — encoding its result as a data row would rebuild the +// parallel ledger the gap-ledger dissolution just removed, and it would go stale in exactly the +// same way. A cell earns a row here when an effect produces its verdict by execution; until then +// UnknownRefused is the honest answer and host_phase_observation_coverage_count is the number that +// measures the debt. // THE canonical observation seam (operator directive 2026-08-08): every lifecycle-phase // observation for every host routes through observe_host_phase — never a parallel @@ -157,7 +235,15 @@ fn host_phase_cell_status(subject: HostEnrollmentIntent, host: HostIdentity, ste } } -data host_phase_program_scoping_note: String = "The cells of a host are the steps of ITS OWN assimilation program, not the universal spine. The previous shape crossed every enrolled host with host_standup_spine, which was right while every enrolled host was the same kind of thing and becomes a fabrication the moment one is not: an inference appliance has no BMC to converge and no Actions runner slot to deploy, so those cells could never be satisfied and could never honestly be marked converged. The global-spine path is DELETED rather than kept beside this one -- a retained cross-product would be an inert second answer that some consumer eventually reads (DESIGN section 6, coverage by illusion). A host enrolled for CI participation alone still selects the entire spine, so srv1 through srv4 are unchanged; that is asserted by an executing witness, not assumed here." +// The cells of a host are the steps of ITS OWN assimilation program, not the universal spine. The +// previous shape crossed every enrolled host with host_standup_spine, which was right while every +// enrolled host was the same kind of thing and becomes a fabrication the moment one is not: an +// inference appliance has no BMC to converge and no Actions runner slot to deploy, so those cells +// could never be satisfied and could never honestly be marked converged. The global-spine path is +// DELETED rather than kept beside this one -- a retained cross-product would be an inert second +// answer that some consumer eventually reads (DESIGN section 6, coverage by illusion). A host +// enrolled for CI participation alone still selects the entire spine, so srv1 through srv4 are +// unchanged; that is asserted by an executing witness, not assumed here. fn host_phase_cells_for_enrollment(intent: HostEnrollmentIntent) -> List { map( @@ -170,7 +256,12 @@ fn host_fleet_phase_matrix(enrollments: List) -> List host_phase_cells_for_enrollment(intent: e)) } -data fleet_subsumption_hosts_note: String = "The enrolled fleet, read from gunbc.fleet_intent_network's host rows rather than re-listed here — the matrix must never be able to disagree with the intent about WHICH hosts exist. This is also the enrollment prerequisite the srv4 OOM incident priced (fleet_subsumption_manual_gaps section C): a host takes no fleet work until it is an intent row, so a host absent from this list has no cells at all, and one that appears gains a full column of UnknownRefused cells — the work becoming visible the moment the host is enrolled, which is the behaviour the incident wanted." +// The enrolled fleet, read from gunbc.fleet_intent_network's host rows rather than re-listed here — +// the matrix must never be able to disagree with the intent about WHICH hosts exist. This is also +// the enrollment prerequisite the srv4 OOM incident priced (fleet_subsumption_manual_gaps section +// C): a host takes no fleet work until it is an intent row, so a host absent from this list has no +// cells at all, and one that appears gains a full column of UnknownRefused cells — the work +// becoming visible the moment the host is enrolled, which is the behaviour the incident wanted. data fleet_subsumption_enrollments: List = [ HostEnrollmentIntent { host: operator_host_srv1, participation: ci_runner_enrollment_participation }, @@ -181,7 +272,11 @@ data fleet_subsumption_enrollments: List = [ HostEnrollmentIntent { host: operator_host_srv6, participation: inference_serving_enrollment_participation }, ] -data fleet_subsumption_hosts_projection_note: String = "The bare host list is DERIVED from the enrollments, never authored beside them. Consumers that legitimately care only about WHICH hosts exist -- toolchain components, network diagnosis, swap backing -- keep reading a List of HostIdentity and are untouched by program scoping, while the enrollment rows stay the single authority for who is in the fleet and what they are in it for. Authoring both lists would rebuild the parallel representation this refactor removes." +// The bare host list is DERIVED from the enrollments, never authored beside them. Consumers that +// legitimately care only about WHICH hosts exist -- toolchain components, network diagnosis, swap +// backing -- keep reading a List of HostIdentity and are untouched by program scoping, while the +// enrollment rows stay the single authority for who is in the fleet and what they are in it for. +// Authoring both lists would rebuild the parallel representation this refactor removes. data fleet_subsumption_hosts: List = map( fleet_subsumption_enrollments, @@ -229,7 +324,18 @@ fn host_phase_cell_ownership(c: HostPhaseCellStatus) -> Ownership? { Present { value: Owned } } -data host_phase_reconcile_instantiation_note: String = "The status matrix reconciled as a MEMBERSHIP DIFF — one more (key_of, key_eq, value_eq, ownership_of) bundle handed to the single generic gunbc.membership_reconcile.membership_reconcile, never a forked diff (a forked reconcile means the genericity bought nothing). Desired is every cell modeled AND observed Converged: that is the end state of the whole subsumption lane — every phase modeled AND every enrolled host converged on it — so a Modified hunk is precisely one unit of remaining work, and membership_write_count is the answer to 'how much is left' as a NUMBER rather than a prose reconstruction. Removed is a real arm, not a degenerate pole: a cell observed for a host no longer in fleet_intent is a host that left the fleet, and cells are Owned, so it tears down rather than refusing. The witness exercises that arm synthetically because the live fleet has no departed host today — an instantiation whose interesting hunks are all unreachable in practice is an instantiation nobody has actually tested." +// The status matrix reconciled as a MEMBERSHIP DIFF — one more (key_of, key_eq, value_eq, +// ownership_of) bundle handed to the single generic +// gunbc.membership_reconcile.membership_reconcile, never a forked diff (a forked reconcile means +// the genericity bought nothing). Desired is every cell modeled AND observed Converged: that is the +// end state of the whole subsumption lane — every phase modeled AND every enrolled host converged +// on it — so a Modified hunk is precisely one unit of remaining work, and membership_write_count is +// the answer to 'how much is left' as a NUMBER rather than a prose reconstruction. Removed is a +// real arm, not a degenerate pole: a cell observed for a host no longer in fleet_intent is a host +// that left the fleet, and cells are Owned, so it tears down rather than refusing. The witness +// exercises that arm synthetically because the live fleet has no departed host today — an +// instantiation whose interesting hunks are all unreachable in practice is an instantiation nobody +// has actually tested. fn host_phase_desired(enrollments: List) -> List { map( @@ -242,7 +348,12 @@ fn host_phase_desired(enrollments: List) -> List PhaseDisposition { ModeledCompose { @@ -302,7 +413,12 @@ fn host_phase_converged_cell_count(enrollments: List) -> I filter(host_fleet_phase_matrix(enrollments: enrollments), c => host_phase_cell_is_converged(c: c)).length() } -data host_phase_finished_note: String = "FINISHED is both axes at their end state — a modeled authority AND an observed Converged — and it is deliberately stricter than converged. A host that is in the desired condition by unmodeled means is not finished: nothing can rebuild it, so the next host repeats the toil, which is the entire cost this lane exists to remove. srv3's OS install is that cell today. Counting it as done would make the lane's own progress number flatter than the truth in exactly the direction that feels good." +// FINISHED is both axes at their end state — a modeled authority AND an observed Converged — and it +// is deliberately stricter than converged. A host that is in the desired condition by unmodeled +// means is not finished: nothing can rebuild it, so the next host repeats the toil, which is the +// entire cost this lane exists to remove. srv3's OS install is that cell today. Counting it as done +// would make the lane's own progress number flatter than the truth in exactly the direction that +// feels good. fn host_phase_cell_is_finished(c: HostPhaseCellStatus) -> Bool { host_phase_cell_is_modeled(c: c) && host_phase_cell_is_converged(c: c) diff --git a/dag/gunbc/host/host_realization.dag b/dag/gunbc/host/host_realization.dag index e1b17d249c7..b58dc49ead7 100644 --- a/dag/gunbc/host/host_realization.dag +++ b/dag/gunbc/host/host_realization.dag @@ -27,7 +27,11 @@ fn container_kind_key(c: ApplicationContainerKind) -> String { } } -data host_role_note: String = "WHAT THIS HOST IS DOING IN THE TOPOLOGY, which is orthogonal to where its process lives. A phone can be the instrument itself or a deliberate physical controller for an instrument running elsewhere, and in both cases it is a native mobile process or a browser document. Keeping the role separate is what lets a companion input binding be modelled without inventing a container kind for it." +// WHAT THIS HOST IS DOING IN THE TOPOLOGY, which is orthogonal to where its process lives. A phone +// can be the instrument itself or a deliberate physical controller for an instrument running +// elsewhere, and in both cases it is a native mobile process or a browser document. Keeping the +// role separate is what lets a companion input binding be modelled without inventing a container +// kind for it. type HostRole = PrimaryInstrument @@ -40,7 +44,13 @@ fn host_role_key(r: HostRole) -> String { } } -data process_lifetime_boolean_removed_note: String = "A PROCESS-LIFETIME BOOLEAN DERIVED FROM CONTAINER TYPE WAS WRONG TWICE OVER (review finding, 2026-08-06), and it is deleted rather than corrected. It claimed a native mobile application owns its process lifetime, which no mobile application does — the operating system suspends and kills it on its own policy. And it answered the wrong question: what the product actually needs is CONTINUITY of the instrument and its state, which is a capability with several independent parts, not a property assigned to a container by type. A native shell makes some of those parts easier to realize; it does not make them different requirements." +// A PROCESS-LIFETIME BOOLEAN DERIVED FROM CONTAINER TYPE WAS WRONG TWICE OVER (review finding, +// 2026-08-06), and it is deleted rather than corrected. It claimed a native mobile application owns +// its process lifetime, which no mobile application does — the operating system suspends and kills +// it on its own policy. And it answered the wrong question: what the product actually needs is +// CONTINUITY of the instrument and its state, which is a capability with several independent parts, +// not a property assigned to a container by type. A native shell makes some of those parts easier +// to realize; it does not make them different requirements. data continuity_polarity_note: String = "THE FOURTH FIELD WAS NAMED FOR THE FAILURE AND REQUIRED TO BE TRUE (review finding, 2026-08-06). The accepted law is that internal navigation PRESERVES the instrument and does not replay a cold start — three fields named the preservation and the fourth named the thing to avoid, and completeness then demanded all four be true, so the law read as 'preserve everything, and also throw it away'. A witness pinned false as incomplete, which made the inversion load-bearing rather than a slip someone would notice. @@ -60,7 +70,12 @@ fn continuity_is_complete(c: InstrumentContinuity) -> Bool { && c.avoid_cold_start_replay } -data renderer_kind_note: String = "HOW A DECIDED PROJECTION BECOMES PIXELS AND ACCESSIBILITY STRUCTURE, and the product model must not know which one was chosen. This is a structural axis rather than a product one — there is no vendor here to enumerate — and it is orthogonal to the container above it: a native desktop process may render DOM inside a web view, and a browser document may render a WebGPU scene. Keeping the two apart is what leaves the route open from today's generated DOM instrument to a native or engine realization of the SAME projection, without touching roadmap semantics." +// HOW A DECIDED PROJECTION BECOMES PIXELS AND ACCESSIBILITY STRUCTURE, and the product model must +// not know which one was chosen. This is a structural axis rather than a product one — there is no +// vendor here to enumerate — and it is orthogonal to the container above it: a native desktop +// process may render DOM inside a web view, and a browser document may render a WebGPU scene. +// Keeping the two apart is what leaves the route open from today's generated DOM instrument to a +// native or engine realization of the SAME projection, without touching roadmap semantics. type RendererKind = DomCssRenderer @@ -81,7 +96,14 @@ data product_is_not_engine_note: String = "A BROWSER PRODUCT IS NOT ITS ENGINE, The two are separate fields. The product is what a person installed and what a receipt panel names; the runtime engine is what actually implements the contract and is frequently what a platform limitation belongs to. They resolve independently: a Chrome observation can name Chrome exactly while its Chromium revision stays unresolved, which is the common case under a reduced User-Agent." -data host_realization_identity_note: String = "Every identity here is a symbolic reference to the authority that owns it, never a name string, so a host is a join over real modules rather than over spellings (DESIGN §3 cite-the-symbol). The unresolved arm is a NAMED variant carrying its own cause rather than a bare optional, because there are several distinct reasons an identity goes unresolved and collapsing them into one absent value is the state-space conflation DESIGN's failure-mode list names first: a reduced User-Agent that reports a frozen literal, a probe that never asked, and a platform that publishes no such identity are three different situations with three different remedies, and only the first is fixed by reading UA Client Hints." +// Every identity here is a symbolic reference to the authority that owns it, never a name string, +// so a host is a join over real modules rather than over spellings (DESIGN §3 cite-the-symbol). The +// unresolved arm is a NAMED variant carrying its own cause rather than a bare optional, because +// there are several distinct reasons an identity goes unresolved and collapsing them into one +// absent value is the state-space conflation DESIGN's failure-mode list names first: a reduced +// User-Agent that reports a frozen literal, a probe that never asked, and a platform that publishes +// no such identity are three different situations with three different remedies, and only the first +// is fixed by reading UA Client Hints. type ResolvedRef = RefResolved { declaration: DeclarationRef } @@ -120,10 +142,13 @@ type HostRealization { renderer: RendererKind } +// A host whose revision, device and build are all resolved can carry a claim about THAT machine. A +// host missing any of them can still carry an observation — the observation is real — but a support +// statement derived from it is scoped to whatever was actually resolved, which in practice is +// usually the container kind and nothing more. + fn host_identity_is_exact(h: HostRealization) -> Bool { ref_is_resolved(r: h.platform_revision) && ref_is_resolved(r: h.device_identity) && ref_is_resolved(r: h.container_product_build) } - -data host_identity_exactness_note: String = "A host whose revision, device and build are all resolved can carry a claim about THAT machine. A host missing any of them can still carry an observation — the observation is real — but a support statement derived from it is scoped to whatever was actually resolved, which in practice is usually the container kind and nothing more." diff --git a/dag/gunbc/host/host_resource.dag b/dag/gunbc/host/host_resource.dag index 1f90793aa7a..17a5f7aad84 100644 --- a/dag/gunbc/host/host_resource.dag +++ b/dag/gunbc/host/host_resource.dag @@ -4,7 +4,25 @@ import std.types { String, Int, Bool } import product.placement_supply { HostIdentity } import std.minted_identity { MintedId, MintedIdAllocation, MintedIdAllocator, mint_id, minted_id_eq } -data resource_occurrence_subject_decision_note: String = "WHY THIS IS A DISTINCT CARRIER AND NOT std.occurrence_identity (DESIGN §3, decided by reading rather than by assumption). std.occurrence_identity is specifically about SOURCE occurrences: its OccurrenceCategory enumerates CallableOccurrence/TypeOccurrence/ConstructorOccurrence/FieldOccurrence/MethodOccurrence/NamespaceSegmentOccurrence/LexicalValueOccurrence, its carriers are DeclarationOccurrence/ReferenceOccurrence over OccurrenceContainmentPath, and its allocator is coupled through occurrence_id_allocator_advance_to to AuthoredTokenOrdinalSpace — the authored-source token ordering that gates ModuleExposure. A host resource has no authored token ordinal and no containment path, so a fleet allocator sharing that surface would inherit a watermark constraint that means nothing for it. The subject-agnostic residue in that module is only a monotone Int counter, and sharing ONE Int space across two unrelated subjects is not single authority — it is the cross-family comparison DESIGN closed for ContentHash: occurrence_id_eq would answer true on a numeric coincidence between a host resource and a source occurrence, which is exactly the state-space conflation the family grounding row forbids. So a distinct SUBJECT is correct here — and as of this change it is a distinct subject rather than a distinct mechanism: the genuinely shared concept, an opaque allocator-minted identity in a NAMED subject space, is now std.minted_identity MintedId, and ResourceOccurrenceId is its HostResourceSubject instantiation. The type argument is what makes the numeric-coincidence comparison unwritable rather than merely unlikely: minted_id_eq admits two ids of the SAME subject only." +// WHY THIS IS A DISTINCT CARRIER AND NOT std.occurrence_identity (DESIGN §3, decided by reading +// rather than by assumption). std.occurrence_identity is specifically about SOURCE occurrences: its +// OccurrenceCategory enumerates +// CallableOccurrence/TypeOccurrence/ConstructorOccurrence/FieldOccurrence/MethodOccurrence/NamespaceSegmentOccurrence/LexicalValueOccurrence, +// its carriers are DeclarationOccurrence/ReferenceOccurrence over OccurrenceContainmentPath, and +// its allocator is coupled through occurrence_id_allocator_advance_to to AuthoredTokenOrdinalSpace +// — the authored-source token ordering that gates ModuleExposure. A host resource has no authored +// token ordinal and no containment path, so a fleet allocator sharing that surface would inherit a +// watermark constraint that means nothing for it. The subject-agnostic residue in that module is +// only a monotone Int counter, and sharing ONE Int space across two unrelated subjects is not +// single authority — it is the cross-family comparison DESIGN closed for ContentHash: +// occurrence_id_eq would answer true on a numeric coincidence between a host resource and a source +// occurrence, which is exactly the state-space conflation the family grounding row forbids. So a +// distinct SUBJECT is correct here — and as of this change it is a distinct subject rather than a +// distinct mechanism: the genuinely shared concept, an opaque allocator-minted identity in a NAMED +// subject space, is now std.minted_identity MintedId, and ResourceOccurrenceId is its +// HostResourceSubject instantiation. The type argument is what makes the numeric-coincidence +// comparison unwritable rather than merely unlikely: minted_id_eq admits two ids of the SAME +// subject only. data minted_not_derived_note: String = "Identity is MINTED BY THE DECLARATION, never derived from content, path, or unit name (operator ruling). This is the whole point: a derived key makes a rename indistinguishable from a delete-plus-create unless some key function is clever enough to notice the two are the same thing — which is inference, and DESIGN §4 rules a heuristic never necessary in a closed system. Git does not track renames; it INFERS them from content similarity after the fact, and that inference is precisely what a content-or-path-derived key_of reproduces. Minting the occurrence at declaration time closes it: the occurrence is stable across every value change, so a rename is ONE MemberChanged whose from and to share an identity, and nothing has to be guessed. A consequence worth stating because it is easy to lose: the identity is NOT the role and NOT the address — role is a separate typed field, and unit name, FIFO path, storage path and configuration are all mutable VALUE fields that may change under a fixed identity." @@ -38,7 +56,12 @@ fn host_resource_identity_eq(left: HostResourceIdentity, right: HostResourceIden && resource_occurrence_eq(left: left.occurrence, right: right.occurrence) } -data host_resource_role_parameter_note: String = "role is a TYPE PARAMETER R, not a closed HostRole enum (operator ruling: 'not a closed operator-maintained HostRole enum'). Jobserver, BuildCache and RunnerSlot are typed roles supplied by whichever module owns them, so a new role is a new instantiation rather than an edit to a central coproduct — the same rule DESIGN §3 states for browser products ('adding Opera must not widen a generic browser-product enum'). A central enum would also make the role list an operator maintenance duty, which is the governance redundancy §2 prices." +// role is a TYPE PARAMETER R, not a closed HostRole enum (operator ruling: 'not a closed +// operator-maintained HostRole enum'). Jobserver, BuildCache and RunnerSlot are typed roles +// supplied by whichever module owns them, so a new role is a new instantiation rather than an edit +// to a central coproduct — the same rule DESIGN §3 states for browser products ('adding Opera must +// not widen a generic browser-product enum'). A central enum would also make the role list an +// operator maintenance duty, which is the governance redundancy §2 prices. type HostResource { identity: HostResourceIdentity @@ -50,11 +73,23 @@ fn host_resource_identity_of(resource: HostResource) -> HostResource resource.identity } +// DISCHARGED. This row previously carried an OPEN trigger: extract the subject-parameterized +// minted-identity carrier when a THIRD minted-identity subject appears, or when the +// std.occurrence_identity stage0 projection settles, whichever comes first. The third subject +// arrived (product.inventory procurement lots), so the carrier was extracted to std.minted_identity +// and this module re-grounded on it — the local ResourceOccurrenceId record, its allocator, and +// resource_occurrence_eq are gone in favor of aliases and calls, so there is exactly one allocator +// authority for this shape. What is deliberately NOT done, and remains open under its ORIGINAL +// reason rather than a new one: std.occurrence_identity is still un-migrated, because it is +// load-bearing compiler substrate with staged work in flight (the stage0 roster and +// ContainmentPath projection), and migrating it under an inventory brief would be +// improvising past doubt. DISSOLVE-ON for that residue is unchanged: the std.occurrence_identity +// stage0 projection settles, at which point OccurrenceId re-grounds on +// MintedId and this note retires. + fn host_resource_same_occurrence( left: HostResource, right: HostResource, ) -> Bool { host_resource_identity_eq(left: left.identity, right: right.identity) } - -data resource_occurrence_generalization_discharged_note: String = "DISCHARGED. This row previously carried an OPEN trigger: extract the subject-parameterized minted-identity carrier when a THIRD minted-identity subject appears, or when the std.occurrence_identity stage0 projection settles, whichever comes first. The third subject arrived (product.inventory procurement lots), so the carrier was extracted to std.minted_identity and this module re-grounded on it — the local ResourceOccurrenceId record, its allocator, and resource_occurrence_eq are gone in favor of aliases and calls, so there is exactly one allocator authority for this shape. What is deliberately NOT done, and remains open under its ORIGINAL reason rather than a new one: std.occurrence_identity is still un-migrated, because it is load-bearing compiler substrate with staged work in flight (the stage0 roster and ContainmentPath projection), and migrating it under an inventory brief would be improvising past doubt. DISSOLVE-ON for that residue is unchanged: the std.occurrence_identity stage0 projection settles, at which point OccurrenceId re-grounds on MintedId and this note retires." diff --git a/dag/gunbc/host/host_runner_memory_cap_plan_emit.dag b/dag/gunbc/host/host_runner_memory_cap_plan_emit.dag index 352ff1b8bdf..6408c85ed2c 100644 --- a/dag/gunbc/host/host_runner_memory_cap_plan_emit.dag +++ b/dag/gunbc/host/host_runner_memory_cap_plan_emit.dag @@ -29,7 +29,10 @@ data runner_memory_cap_plan_record_knobs: SerializationKnobs = { data runner_memory_cap_plan_emit_disclaimer: String = "RUNNER-MEMORY-CAP APPLY SHEET — EMIT CLAIMS NOTHING: typed projection of fleet_converge_policy memory-cap knobs (per_slot_memory_max_bytes, per_slot_memory_high_bytes, per_slot_memory_swap_max_bytes) for operator hand-apply only. Modeled-but-not-applied until a separate verify-only receipt reads Converged on the runner instance glob. This emission does not invoke host_effect_apply, systemctl, or any host mutation." -data memory_cap_plan_row_kind_guard_note: String = "memory_cap_plan_row_is_per_slot_memory_cap is a structural ConvergeTarget coproduct guard for plan-emit projection only — PerSlotMemoryCap rows serialize; any other arm emits an UNSOUND line (fail-closed, not silent skip). Dissolve-on: host_converge_realize PerSlotMemoryCap interpreter retires the hand-apply sheet." +// memory_cap_plan_row_is_per_slot_memory_cap is a structural ConvergeTarget coproduct guard for +// plan-emit projection only — PerSlotMemoryCap rows serialize; any other arm emits an UNSOUND line +// (fail-closed, not silent skip). Dissolve-on: host_converge_realize PerSlotMemoryCap interpreter +// retires the hand-apply sheet. fn memory_cap_plan_row_is_per_slot_memory_cap(knob: ConvergeKnob) -> Bool { match knob.target { diff --git a/dag/gunbc/host/host_runner_memory_cap_verify.dag b/dag/gunbc/host/host_runner_memory_cap_verify.dag index 36b07895229..1396b0c54c6 100644 --- a/dag/gunbc/host/host_runner_memory_cap_verify.dag +++ b/dag/gunbc/host/host_runner_memory_cap_verify.dag @@ -181,7 +181,12 @@ type RunnerMemoryCapVerifyRunResult = RunnerMemoryCapVerifyObserved { receipt: RunnerMemoryCapVerifyReceipt } | RunnerMemoryCapVerifyRefused { host: HostIdentity, reason: String } -data runner_memory_cap_verify_read_absent_refusal_note: String = "The live-read arm of runner_memory_cap_verify_result_from_live_read is the fail-closed seam (DESIGN section 5): an absent typed read — clock_now_probed_at absent, or any of the three systemd.Systemctl.ShowProperty reads absent — refuses with a located reason and NEVER widens to an Observed receipt with a fabricated tally. Taking the Optional read as a parameter is what makes that arm executable hermetically: runner_memory_cap_verify_run supplies the live read, while the RED control supplies none and asserts a refusal." +// The live-read arm of runner_memory_cap_verify_result_from_live_read is the fail-closed seam +// (DESIGN section 5): an absent typed read — clock_now_probed_at absent, or any of the three +// systemd.Systemctl.ShowProperty reads absent — refuses with a located reason and NEVER widens to +// an Observed receipt with a fabricated tally. Taking the Optional read as a parameter is what +// makes that arm executable hermetically: runner_memory_cap_verify_run supplies the live read, +// while the RED control supplies none and asserts a refusal. fn runner_memory_cap_verify_result_from_live_read( host: HostIdentity, diff --git a/dag/gunbc/host/host_runner_memory_provision.dag b/dag/gunbc/host/host_runner_memory_provision.dag index 397ba36e32b..64b92399359 100644 --- a/dag/gunbc/host/host_runner_memory_provision.dag +++ b/dag/gunbc/host/host_runner_memory_provision.dag @@ -27,7 +27,19 @@ import gunbc.fleet_show_effective_read { per_slot_memory_cap_verdict_from_unit_r import gunbc.runner_unit_live_read { RunnerUnitMemoryLiveRead } import std.realization_reconcile { Converged, NotConverged } -data host_runner_memory_provision_scope_note: String = "Per-slot MemoryMax/MemoryHigh/MemorySwapMax knobs from gunbc.runner_slot_allocation, selected here as a caps-only HostConverge. THIS MODULE NO LONGER HAS A LIVE APPLY, and the previous sentence of this note described one: it said live apply interprets PerSlotMemoryCap into a derived drop-in write plus daemon-reload executed through host_effect_realize run_shell_transport and grounded on the real exit code. That arm is DELETED (2026-08-21, operator decision) because the 2026-08-13 privilege hold reserves drop-in authoring to the deploy principal, and the hold was enforced by nothing but two self-asserting Bool flags while the arm stayed wired. provision_runner_memory_caps therefore reaches a ConvergePlan realization that refuses by construction; it is retained as the typed entry that produces that refusal rather than deleted, so an operator invoking it is told why instead of finding nothing. The read-back half was always the correct one and is untouched: per_slot_memory_cap_verdict_from_unit_read on the verify path (gunbc.host_runner_memory_cap_verify). What used to be named here as dissolve-on debt — apply-path read-back — is not debt any more, because there is no apply path to read back." +// Per-slot MemoryMax/MemoryHigh/MemorySwapMax knobs from gunbc.runner_slot_allocation, selected +// here as a caps-only HostConverge. THIS MODULE NO LONGER HAS A LIVE APPLY, and the previous +// sentence of this note described one: it said live apply interprets PerSlotMemoryCap into a +// derived drop-in write plus daemon-reload executed through host_effect_realize run_shell_transport +// and grounded on the real exit code. That arm is DELETED (2026-08-21, operator decision) because +// the 2026-08-13 privilege hold reserves drop-in authoring to the deploy principal, and the hold +// was enforced by nothing but two self-asserting Bool flags while the arm stayed wired. +// provision_runner_memory_caps therefore reaches a ConvergePlan realization that refuses by +// construction; it is retained as the typed entry that produces that refusal rather than deleted, +// so an operator invoking it is told why instead of finding nothing. The read-back half was always +// the correct one and is untouched: per_slot_memory_cap_verdict_from_unit_read on the verify path +// (gunbc.host_runner_memory_cap_verify). What used to be named here as dissolve-on debt — +// apply-path read-back — is not debt any more, because there is no apply path to read back. data host_runner_memory_provision_realize_scaffold: Disposition = Scaffold { dissolves_to: RealizationDispatch, diff --git a/dag/gunbc/host/host_standup.dag b/dag/gunbc/host/host_standup.dag index d2dc0f05e97..87903cb6142 100644 --- a/dag/gunbc/host/host_standup.dag +++ b/dag/gunbc/host/host_standup.dag @@ -1,6 +1,5 @@ module gunbc.host_standup - type HostStandupPrefixPhase = BmcCredentialConverge | OsInstallMechanismResolved @@ -316,7 +315,26 @@ data host_standup_spine: List = [ ), ] -data host_standup_gap_ledger_parallel_representation_note: String = "🟡 dissolve-on: this ledger is a PARALLEL REPRESENTATION of host_standup_spine and is declared as one rather than left ambient (DESIGN §3; an honestly-marked scaffold duplicating a canonical fact is still a violation). Twelve rows are hand-written beside the twelve spine steps, and `modeling` is a String carrying four values — modeled / modeled-new-authority / partial / gap — where PhaseDisposition has two arms. Nothing derives one from the other, so they are free to drift, and the drift is invisible: p0_gap_ledger_matches_spine_partial_authorities exists only because the ledger CAN disagree with the spine, which is validation standing where construction was available (§5). It was also the source consulted to answer 'where does subsumption stand', so a stale string became a status report. WHY IT CANNOT SIMPLY BE DERIVED TODAY, which is the real finding: PhaseDisposition is a SUM, so authorities and interim_realization are mutually exclusive — and two live phases need BOTH. P0:reach-secrets-network has modeled ACL authorities AND an unmodeled live upsert; P3:green-place-readiness has a landed gunbc authority AND a ctrl writer (green_place.dag, read-gunbc-pin.sh). Neither fact is representable, so it was pushed into this ledger's prose, which is precisely what the ledger is FOR. That is the same sum-where-a-product-belongs defect that gunbc.host_phase_status.HostPhaseCellStatus was first written with and corrected. DISSOLVES WHEN PhaseDisposition becomes a product — authorities: List alongside interim: PhaseInterim? — at which point 'modeled' is authorities-nonempty-and-no-interim, 'partial' is both-present, 'gap' is interim-only, every row here becomes a projection of the spine, and this table is deleted. Deliberately NOT bundled into the status-axis change: it re-shapes fourteen spine rows in a load-bearing file, and the two changes should be reviewable apart." +// 🟡 dissolve-on: this ledger is a PARALLEL REPRESENTATION of host_standup_spine, declared rather +// than ambient (DESIGN §3; an honestly-marked scaffold duplicating a canonical fact is still a +// violation). Twelve rows are hand-written beside the twelve spine steps, and `modeling` is a +// String carrying four values — modeled / modeled-new-authority / partial / gap — where +// PhaseDisposition has two arms. Nothing derives one from the other, so they drift invisibly: +// p0_gap_ledger_matches_spine_partial_authorities exists only because the ledger CAN disagree with +// the spine — validation standing where construction was available (§5). It was also the source +// consulted for 'where does subsumption stand', so a stale string became a status report. WHY IT +// CANNOT SIMPLY BE DERIVED TODAY, the real finding: PhaseDisposition is a SUM, so authorities and +// interim_realization are mutually exclusive — and two live phases need BOTH. +// P0:reach-secrets-network has modeled ACL authorities AND an unmodeled live upsert; +// P3:green-place-readiness has a landed gunbc authority AND a ctrl writer (green_place.dag, +// read-gunbc-pin.sh). Neither is representable, so it was pushed into this ledger's prose — the +// same sum-where-a-product-belongs defect gunbc.host_phase_status.HostPhaseCellStatus was first +// written with and corrected. DISSOLVES WHEN PhaseDisposition becomes a product — authorities: +// List alongside interim: PhaseInterim? — at which point 'modeled' is +// authorities-nonempty-and-no-interim, 'partial' is both-present, 'gap' is interim-only, every row +// here becomes a projection of the spine, and this table is deleted. Deliberately NOT bundled into +// the status-axis change: it re-shapes fourteen spine rows in a load-bearing file, and the two +// changes should be reviewable apart. data host_standup_gap_ledger: List = [ GapLedgerRow { @@ -666,11 +684,10 @@ fn assimilation_complete_all_satisfied( } } -// NO SILENT FALLBACK — the same claim as before, now carried by the refusal. -// This read "is nbd-proxy, no fallback" while srv3 ran 2.07.00; the load-bearing -// half was always the second one, that a solver finding no virtual media must not -// quietly answer PXE. srv3 on 3.22.00 has no catalogued delivery mechanism, so -// the fallback arm is exactly where a defective solver would land — and refusing +// NO SILENT FALLBACK — the same claim as before, now carried by the refusal. This read "is +// nbd-proxy, no fallback" while srv3 ran 2.07.00; the load-bearing half was always the second: a +// solver finding no virtual media must not quietly answer PXE. srv3 on 3.22.00 has no catalogued +// delivery mechanism, so the fallback arm is exactly where a defective solver would land — refusing // is the proof it does not. A Selected of any kind here means the fallback fired. fn srv3_install_mechanism_refuses_rather_than_falling_back() -> Bool { match srv3_install_mechanism_solution { diff --git a/dag/gunbc/host/host_swap_backing.dag b/dag/gunbc/host/host_swap_backing.dag index ec8ef9b6281..ed7103238ab 100644 --- a/dag/gunbc/host/host_swap_backing.dag +++ b/dag/gunbc/host/host_swap_backing.dag @@ -11,9 +11,34 @@ import gunbc.host_phase_status { observation_verdict_eq } import gunbc.readback_independence { readback_evidence_verdict, proc_swaps_read_probe } import product.placement_supply { HostIdentity } -data host_swap_backing_authority_note: String = "THE host swap-BACKING axis, and it exists because the corpus already converges a swap CAP over a resource whose existence nothing models. gunbc.runner_slot_allocation declares memory_swap_max = 34359738368 (32 GiB) per runner slot; gunbc.host_converge drives it onto every host as MemorySwapMax through the per-slot drop-in at gunbc_runner_per_slot_swap_dropin_path, and gunbc.runner_unit_live_read reads it back and reports Converged when the cgroup property matches. Every one of those steps is correct in isolation and the composition still proves nothing, because MemorySwapMax is a CEILING: systemd accepts it, reports it, and matches it byte-for-byte on a host with zero swap devices. The knob converges green over a resource that does not exist. That is the presupposition failure this module makes representable — a cap is evidence about a resource only once the resource is observed present — and it is the shape DESIGN §5 calls a fabricated plausible output: not a wrong value, a value whose meaning was assumed. What is NOT minted here: no swap-specific verdict enum (std.upsert_decision.ObservationVerdict already spans Converged/Absent/Drifted/Conflict/Inaccessible/UnknownRefused) and no swap observation carrier (extdeps.tools.free.SwapStats is the cited one, grounded on the free(1) man page, and it is reused verbatim rather than re-decomposed here)." - -data host_swap_backing_requirement_is_derived_note: String = "The requirement is DERIVED from the declared cap, never authored per host, and that is the whole reason this module can exist without fabricating anything. Nobody in this session can read /proc/swaps on srv1..srv4 — the reach gap is the A1 lane — so any table of per-host swap sizes written here would be invention dressed as a model. The derivation needs no such reading: declaring a nonzero MemorySwapMax IS declaring that swap backing must exist, so the requirement falls out of gunbc_runner_slot_desired().memory_swap_max, and it moves automatically when that number moves. DECLARED LIMIT, stated rather than papered over: min_total is the PER-SLOT cap, the amount a single runner alone can demand, and not slots-per-host x cap. Whether five concurrent slots can each reach their ceiling is an aggregate-scheduling question this module does not answer, and answering it by multiplication would assert a worst case nobody has measured. So the requirement is a floor below which the declared caps are provably unsatisfiable, and it is honest about being a floor." +// THE host swap-BACKING axis, and it exists because the corpus already converges a swap CAP over a +// resource whose existence nothing models. gunbc.runner_slot_allocation declares memory_swap_max = +// 34359738368 (32 GiB) per runner slot; gunbc.host_converge drives it onto every host as +// MemorySwapMax through the per-slot drop-in at gunbc_runner_per_slot_swap_dropin_path, and +// gunbc.runner_unit_live_read reads it back and reports Converged when the cgroup property matches. +// Every one of those steps is correct in isolation and the composition still proves nothing, +// because MemorySwapMax is a CEILING: systemd accepts it, reports it, and matches it byte-for-byte +// on a host with zero swap devices. The knob converges green over a resource that does not exist. +// That is the presupposition failure this module makes representable — a cap is evidence about a +// resource only once the resource is observed present — and it is the shape DESIGN §5 calls a +// fabricated plausible output: not a wrong value, a value whose meaning was assumed. What is NOT +// minted here: no swap-specific verdict enum (std.upsert_decision.ObservationVerdict already spans +// Converged/Absent/Drifted/Conflict/Inaccessible/UnknownRefused) and no swap observation carrier +// (extdeps.tools.free.SwapStats is the cited one, grounded on the free(1) man page, and it is +// reused verbatim rather than re-decomposed here). + +// The requirement is DERIVED from the declared cap, never authored per host, and that is the whole +// reason this module can exist without fabricating anything. Nobody in this session can read +// /proc/swaps on srv1..srv4 — the reach gap is the A1 lane — so any table of per-host swap sizes +// written here would be invention dressed as a model. The derivation needs no such reading: +// declaring a nonzero MemorySwapMax IS declaring that swap backing must exist, so the requirement +// falls out of gunbc_runner_slot_desired().memory_swap_max, and it moves automatically when that +// number moves. DECLARED LIMIT, stated rather than papered over: min_total is the PER-SLOT cap, the +// amount a single runner alone can demand, and not slots-per-host x cap. Whether five concurrent +// slots can each reach their ceiling is an aggregate-scheduling question this module does not +// answer, and answering it by multiplication would assert a worst case nobody has measured. So the +// requirement is a floor below which the declared caps are provably unsatisfiable, and it is honest +// about being a floor. type SwapBackingRequirement = SwapBackingRequired { min_total: ByteSize, requiring_declaration: DeclarationRef } @@ -35,7 +60,18 @@ fn host_swap_backing_requirement() -> SwapBackingRequirement { } } -data host_swap_backing_verdict_note: String = "The fail-closed fold, with each arm answering a different question so the remedies stay separable. No observation at all is UnknownRefused: nothing on this host can be asked about swap today, and the fix is to BUILD the producer. An observed total of zero is Absent: the host answered, and the answer is that the backing the caps presuppose is not there — the fix is to PROVISION it. An observed total below the requirement is Drifted: backing exists and is too small, so the fix is to RESIZE. Collapsing any two of those into one arm is the state-space conflation this lane keeps paying for; they have different owners and their counts measure different things. Note the arm that is deliberately absent: there is no 'assume the distro default gave us swap'. Ubuntu server images ship with swap configured sometimes and not others depending on installer path, and srv1..srv4 were installed across at least two paths (the srv3/srv4 BMC virtual-media route and whatever preceded it), so a default-assuming arm would be a guess whose error rate is unmeasurable by construction." +// The fail-closed fold, with each arm answering a different question so the remedies stay +// separable. No observation at all is UnknownRefused: nothing on this host can be asked about swap +// today, and the fix is to BUILD the producer. An observed total of zero is Absent: the host +// answered, and the answer is that the backing the caps presuppose is not there — the fix is to +// PROVISION it. An observed total below the requirement is Drifted: backing exists and is too +// small, so the fix is to RESIZE. Collapsing any two of those into one arm is the state-space +// conflation this lane keeps paying for; they have different owners and their counts measure +// different things. Note the arm that is deliberately absent: there is no 'assume the distro +// default gave us swap'. Ubuntu server images ship with swap configured sometimes and not others +// depending on installer path, and srv1..srv4 were installed across at least two paths (the +// srv3/srv4 BMC virtual-media route and whatever preceded it), so a default-assuming arm would be a +// guess whose error rate is unmeasurable by construction. fn swap_stats_total_bytes(s: SwapStats) -> Int { byte_size_count(b: s.total) @@ -64,7 +100,19 @@ fn host_swap_backing_verdict( } } -data host_swap_cap_qualification_note: String = "THE load-bearing conjunct of this module, and the reason it is not a free-floating swap model: it changes what an EXISTING converged knob is allowed to claim. gunbc.runner_unit_live_read already produces a verdict for the per-slot MemorySwapMax cap. That verdict is a fact about the cgroup property and is imported here unchanged; what this fold adds is the qualification that a cap verdict carries information only when its backing is observed Converged. Every other backing state DOMINATES the cap: unobserved backing yields UnknownRefused however green the property read was, absent backing yields Absent, undersized backing yields Drifted. This is not a min() over some verdict lattice and must not become one — it is a presupposition check, and the asymmetry is the point. A green cap over unknown backing is precisely the failure mode #7213 found in another dress: sccache --show-stats reported a healthy server because the probe auto-started the very thing it was measuring, and a MemorySwapMax read reports a healthy ceiling because systemd will happily store a ceiling for a resource the host does not have. Both manufacture their own evidence, and both must refuse instead." +// THE load-bearing conjunct of this module, and the reason it is not a free-floating swap model: it +// changes what an EXISTING converged knob is allowed to claim. gunbc.runner_unit_live_read already +// produces a verdict for the per-slot MemorySwapMax cap. That verdict is a fact about the cgroup +// property and is imported here unchanged; what this fold adds is the qualification that a cap +// verdict carries information only when its backing is observed Converged. Every other backing +// state DOMINATES the cap: unobserved backing yields UnknownRefused however green the property read +// was, absent backing yields Absent, undersized backing yields Drifted. This is not a min() over +// some verdict lattice and must not become one — it is a presupposition check, and the asymmetry is +// the point. A green cap over unknown backing is precisely the failure mode #7213 found in another +// dress: sccache --show-stats reported a healthy server because the probe auto-started the very +// thing it was measuring, and a MemorySwapMax read reports a healthy ceiling because systemd will +// happily store a ceiling for a resource the host does not have. Both manufacture their own +// evidence, and both must refuse instead. fn swap_cap_verdict_qualified_by_backing( cap: ObservationVerdict, @@ -80,7 +128,12 @@ fn swap_cap_verdict_qualified_by_backing( } } -data host_swap_cap_widen_red_control_note: String = "The forbidden arm, modeled as its own declaration so it can be executed against the gate rather than described in prose. Backing-blind qualification is what the corpus does TODAY — the cap verdict is reported as-is, with no backing term anywhere in the fold — so this sketch is not a hypothetical: it is the current behaviour written down and made falsifiable. The gate rejects it and accepts the authority on the same inputs, and the perturbation is independent of the property checked, so the pair cannot pass tautologically." +// The forbidden arm, modeled as its own declaration so it can be executed against the gate rather +// than described in prose. Backing-blind qualification is what the corpus does TODAY — the cap +// verdict is reported as-is, with no backing term anywhere in the fold — so this sketch is not a +// hypothetical: it is the current behaviour written down and made falsifiable. The gate rejects it +// and accepts the authority on the same inputs, and the perturbation is independent of the property +// checked, so the pair cannot pass tautologically. fn swap_cap_verdict_backing_blind_widen_sketch( cap: ObservationVerdict, @@ -111,7 +164,13 @@ fn swap_cap_verdict_gate_accepts( } } -data host_swap_backing_observation_frontier_note: String = "The live producer returns none for every host on purpose — there is no /proc/swaps reader and no free(1) transport in the tree — so the honest observation for all four hosts is absence, and the derived verdict for all four is UnknownRefused. host_swap_backing_observed_producer is an explicit scaffold no-op (not a transport fold that discards success); realization binds host_swap_backing_observed_producer_scaffold until argv materialization can express swap ops. The count of unobserved hosts is exported so the deficit is a NUMBER that must change when a producer lands." +// The live producer returns none for every host on purpose — there is no /proc/swaps reader and no +// free(1) transport in the tree — so the honest observation for all four hosts is absence, and the +// derived verdict for all four is UnknownRefused. host_swap_backing_observed_producer is an +// explicit scaffold no-op (not a transport fold that discards success); realization binds +// host_swap_backing_observed_producer_scaffold until argv materialization can express swap ops. The +// count of unobserved hosts is exported so the deficit is a NUMBER that must change when a producer +// lands. data host_swap_backing_observed_producer_scaffold: Disposition = Scaffold { dissolves_to: SingleAuthority, @@ -130,7 +189,15 @@ fn host_swap_backing_observation(host: HostIdentity) -> SwapStats? { host_swap_backing_observed_producer(host: host) } -data host_swap_backing_probe_independence_note: String = "The host verdict is routed through the read-back independence criterion rather than trusting the observation directly, and it is worth being precise about why, because /proc/swaps IS inert and the routing is therefore a no-op TODAY. The routing is what makes the classification load-bearing instead of decorative: the probe row travels with the verdict, so a future producer that swapped the transport for an ensure-shaped one — `swapon --show` after an activation attempt, an agent that mounts what it fails to find — would have to change proc_swaps_read_probe's classification to keep the same green, and changing it is a visible edit to a cited row rather than a silent change of meaning inside a transport. This is the same reason the swap CAP is qualified by its backing: a value is evidence only together with the conditions under which it was obtained." +// The host verdict is routed through the read-back independence criterion rather than trusting the +// observation directly, and it is worth being precise about why, because /proc/swaps IS inert and +// the routing is therefore a no-op TODAY. The routing is what makes the classification load-bearing +// instead of decorative: the probe row travels with the verdict, so a future producer that swapped +// the transport for an ensure-shaped one — `swapon --show` after an activation attempt, an agent +// that mounts what it fails to find — would have to change proc_swaps_read_probe's classification +// to keep the same green, and changing it is a visible edit to a cited row rather than a silent +// change of meaning inside a transport. This is the same reason the swap CAP is qualified by its +// backing: a value is evidence only together with the conditions under which it was obtained. fn host_swap_backing_host_verdict(host: HostIdentity) -> ObservationVerdict { readback_evidence_verdict( diff --git a/dag/gunbc/host/host_toolchain_components.dag b/dag/gunbc/host/host_toolchain_components.dag index c968c97a96a..9cd81579833 100644 --- a/dag/gunbc/host/host_toolchain_components.dag +++ b/dag/gunbc/host/host_toolchain_components.dag @@ -18,7 +18,12 @@ import product.placement_supply { HostIdentity } data toolchain_completeness_authority_note: String = "B3's toolchain-completeness axis, and it is keyed on a RESOLVED TOOLCHAIN rather than on a host, because the host-shaped question is the one that produces a true-sounding wrong answer. The incident that grounds it, read live on srv2 2026-07-25: floor gates died with `spawn rustfmt: No such file or directory` across two hosts in two days, and the natural reading — rustfmt is missing from the machine — was wrong. rustfmt WAS on srv2. The job principal ghrunner carried two rustup toolchains, 1.93.0 with rustfmt and stable WITHOUT it, ~/.cargo/bin/rustfmt was a shim, and ghrunner's default was stable. So a completeness check keyed on 'is rustfmt installed on this host' answers yes, converges green, and the gate keeps failing — the same presupposition failure gunbc.host_swap_backing found in a MemorySwapMax over absent swap, and gunbc.auth.materialized_secret found in an SmEnabled version upstream had revoked. Three incidents, one shape: a value reported green because the condition under which it would MEAN something was assumed instead of observed. The cell therefore names the toolchain it resolved and the component set THAT toolchain carries, and a requirement is satisfied only by the components of the ACTIVE toolchain." -data toolchain_requirement_derivation_note: String = "The requirement is derived from the gate that needs it, carried as a DeclarationRef rather than a label, so a component nothing requires cannot accumulate here and the reason a component is required stays pointable. Today there is exactly one row: the required CI fmt step invokes rustfmt, so it needs rustfmt on the job principal's resolved toolchain. Clippy is deliberately NOT a row — it was removed from CI 2026-07-08 as zero-signal under a crate-wide allow, and adding it here because it is a familiar component would be requiring something no consumer needs." +// The requirement is derived from the gate that needs it, carried as a DeclarationRef rather than a +// label, so a component nothing requires cannot accumulate here and the reason a component is +// required stays pointable. Today there is exactly one row: the required CI fmt step invokes +// rustfmt, so it needs rustfmt on the job principal's resolved toolchain. Clippy is deliberately +// NOT a row — it was removed from CI 2026-07-08 as zero-signal under a crate-wide allow, and adding +// it here because it is a familiar component would be requiring something no consumer needs. type ToolchainComponentRequirement { component: RustupComponent @@ -54,7 +59,14 @@ fn resolved_toolchain_missing( filter(reqs, r => !resolved_toolchain_has(t: t, c: r.component)) } -data toolchain_completeness_verdict_note: String = "The fail-closed fold, with the arms separated by the work each names. No observation at all is UnknownRefused — nothing has asked this host's job principal what its toolchain resolves, and the remedy is to build the probe. A resolved toolchain missing a required component is Absent — the question was asked and the answer is that the component the gate needs is not in the toolchain the shim will reach, and the remedy is to add it to THAT toolchain. A complete resolution is Converged. There is deliberately no arm for 'the component exists in some other toolchain on the box': that state is exactly the srv2 incident, and it is not a partial success — under it the gate fails every time, so it must not read as anything other than Absent." +// The fail-closed fold, with the arms separated by the work each names. No observation at all is +// UnknownRefused — nothing has asked this host's job principal what its toolchain resolves, and the +// remedy is to build the probe. A resolved toolchain missing a required component is Absent — the +// question was asked and the answer is that the component the gate needs is not in the toolchain +// the shim will reach, and the remedy is to add it to THAT toolchain. A complete resolution is +// Converged. There is deliberately no arm for 'the component exists in some other toolchain on the +// box': that state is exactly the srv2 incident, and it is not a partial success — under it the +// gate fails every time, so it must not read as anything other than Absent. fn toolchain_completeness_verdict( reqs: List, @@ -71,7 +83,13 @@ fn toolchain_completeness_verdict( } } -data toolchain_probe_note: String = "The probe, classified. Listing a toolchain's components reads a directory and installs nothing, so it is ProbeInert and its Converged survives. The sketch beside it is the shape an ensure lane reaches for by reflex and must not: a check that runs `rustup component add` when the component is missing reports success it caused, so it can never witness the deficit it exists to find — and the deficit's frequency would drop to zero by construction, which is the absorbing fallback in its purest form. That matters here beyond principle: the two-hosts-in-two-days recurrence is only visible BECAUSE the checks so far have refused rather than repaired." +// The probe, classified. Listing a toolchain's components reads a directory and installs nothing, +// so it is ProbeInert and its Converged survives. The sketch beside it is the shape an ensure lane +// reaches for by reflex and must not: a check that runs `rustup component add` when the component +// is missing reports success it caused, so it can never witness the deficit it exists to find — and +// the deficit's frequency would drop to zero by construction, which is the absorbing fallback in +// its purest form. That matters here beyond principle: the two-hosts-in-two-days recurrence is only +// visible BECAUSE the checks so far have refused rather than repaired. data rustup_component_list_probe: ReadBackProbe = ReadBackProbe { label: "rustup component list --installed, as the job principal, on the active toolchain", @@ -102,7 +120,19 @@ fn toolchain_completeness_observed_verdict( } } -data srv2_live_reading_note: String = "A CITED LIVE READING, not a fixture, and the distinction is the whole reason this row is allowed to exist. Taken over an ssh hop to srv2 on 2026-07-25 as part of diagnosing the recurring floor failure: ghrunner's default toolchain is stable-aarch64-unknown-linux-gnu, and BEFORE the operator-directed hand-install that toolchain carried cargo/rustc/rustdoc and the rust-gdb and rust-lldb wrappers, with NO rustfmt — while 1.93.0-aarch64-unknown-linux-gnu carried rustfmt the whole time. The hand-install then added rustfmt to stable, verified by execution through the shim on the job's PATH shape (rustfmt 1.9.0-stable). Both readings are recorded because the BEFORE row is what makes the axis discriminating — a module that only ever saw the fixed state could not show that its verdict would have caught the incident. The reading covers srv2's nine slots at once, since they share one ghrunner principal and therefore one toolchain. srv1, srv3 and srv4 have no reading: srv1 refuses every credential available to this session, and no probe runs anywhere in the tree, so their honest verdict is UnknownRefused and this module does not invent one." +// A CITED LIVE READING, not a fixture, and the distinction is the whole reason this row is allowed +// to exist. Taken over an ssh hop to srv2 on 2026-07-25 as part of diagnosing the recurring floor +// failure: ghrunner's default toolchain is stable-aarch64-unknown-linux-gnu, and BEFORE the +// operator-directed hand-install that toolchain carried cargo/rustc/rustdoc and the rust-gdb and +// rust-lldb wrappers, with NO rustfmt — while 1.93.0-aarch64-unknown-linux-gnu carried rustfmt the +// whole time. The hand-install then added rustfmt to stable, verified by execution through the shim +// on the job's PATH shape (rustfmt 1.9.0-stable). Both readings are recorded because the BEFORE row +// is what makes the axis discriminating — a module that only ever saw the fixed state could not +// show that its verdict would have caught the incident. The reading covers srv2's nine slots at +// once, since they share one ghrunner principal and therefore one toolchain. srv1, srv3 and srv4 +// have no reading: srv1 refuses every credential available to this session, and no probe runs +// anywhere in the tree, so their honest verdict is UnknownRefused and this module does not invent +// one. data srv2_ghrunner_toolchain_before_fix: ResolvedToolchain = ResolvedToolchain { principal: "ghrunner", @@ -138,7 +168,6 @@ fn host_toolchain_completeness_verdict(host: HostIdentity) -> ObservationVerdict ) } - data toolchain_ensure_frontier_note: String = "What is NOT here, named rather than half-built. The operator's routing puts the component on the modeled toolchain ensure (B3), which means a HostToolchainKind variant for the runner floor toolchain and a HostEffect arm that applies `rustup component add` to the RESOLVED toolchain of the job principal. That effect cannot be realized today: it hits the argv materializer's hardcoded five-parameter vocabulary, the same blocker gunbc.host_swap_backing and gunbc.auth.materialized_secret both name, now dispatched as its own work item. Landing the variant before the materializer is generic would put an unreachable arm into gunbc.host_effect — a load-bearing type with six match sites and no exhaustiveness checking in the seed, so a variant nothing can apply would fall through silently rather than refuse. The requirement rows, the resolution shape, the verdict and its probe classification are all live now and are what the ensure will consume; the apply arm lands when the materializer child unblocks it. srv2's components were brought into the desired state BY HAND on operator instruction in the meantime, and that hand action is exactly what this axis makes visible and counted rather than invisible." data toolchain_ensure_apply_scaffold: Disposition = Scaffold { @@ -150,7 +179,24 @@ data toolchain_ensure_apply_scaffold: Disposition = Scaffold { } } -data spawn_reachability_is_a_second_condition_note: String = "A CORRECTION to this module, found by execution after the hand-install and before any of it was believed. Installing the component into the resolving toolchain is NECESSARY and is NOT SUFFICIENT, and the two conditions must not be fused into one cell. Read live on srv2 2026-07-25: the runner unit declares no PATH, so it inherits systemd's default (/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/snap/bin), which does not contain /home/ghrunner/.cargo/bin at all. A child spawned with that environment cannot find rustfmt no matter which components are installed, and `spawn rustfmt: No such file or directory` is precisely a PATH-lookup failure rather than a component-missing failure — a component-missing shim produces a rustup diagnostic instead. A ghrunner LOGIN shell does resolve it, because the profile adds the cargo bin directory, so the observed outcome depends on which of those two environments the gate's spawn inherits. That is not observable from a host read at all; it is a property of the job step, and answering it needs a probe INSIDE a job. So the honest state is: the component condition is now Converged on srv2 and the reachability condition is UNOBSERVED, and 'will the gate find rustfmt' is therefore UnknownRefused. Recording this as a second axis rather than letting completeness stand in for it is the same discipline the module was built to enforce, applied to the module itself — a completeness cell reported as the answer would be a value green because the condition under which it MEANS the gate works was assumed, which is the exact error this file opens by describing." +// A CORRECTION to this module, found by execution after the hand-install and before any of it was +// believed. Installing the component into the resolving toolchain is NECESSARY and is NOT +// SUFFICIENT, and the two conditions must not be fused into one cell. Read live on srv2 2026-07-25: +// the runner unit declares no PATH, so it inherits systemd's default +// (/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/snap/bin), which does not contain +// /home/ghrunner/.cargo/bin at all. A child spawned with that environment cannot find rustfmt no +// matter which components are installed, and `spawn rustfmt: No such file or directory` is +// precisely a PATH-lookup failure rather than a component-missing failure — a component-missing +// shim produces a rustup diagnostic instead. A ghrunner LOGIN shell does resolve it, because the +// profile adds the cargo bin directory, so the observed outcome depends on which of those two +// environments the gate's spawn inherits. That is not observable from a host read at all; it is a +// property of the job step, and answering it needs a probe INSIDE a job. So the honest state is: +// the component condition is now Converged on srv2 and the reachability condition is UNOBSERVED, +// and 'will the gate find rustfmt' is therefore UnknownRefused. Recording this as a second axis +// rather than letting completeness stand in for it is the same discipline the module was built to +// enforce, applied to the module itself — a completeness cell reported as the answer would be a +// value green because the condition under which it MEANS the gate works was assumed, which is the +// exact error this file opens by describing. type ComponentSpawnReachability = ReachableOnSpawnPath { path_source: NonEmptyStr } @@ -174,11 +220,19 @@ fn host_toolchain_spawn_reachability(host: HostIdentity) -> ComponentSpawnReacha none } +// No host on the fleet has a reachability observation, srv2 included, and the reason is structural +// rather than incidental: the question is what PATH the gate's spawn inherits, which only a probe +// running INSIDE a job step can answer. A host read cannot see it — I tried, and what a host read +// CAN show (the unit's inherited PATH, and that a login shell differs from it) is exactly enough to +// prove the question is open and not enough to close it. The probe is a step emitting its own +// resolved PATH and the resolution of each required component, and it is the same shape as the +// read-back independence criterion demands: it must observe, never repair. Until it exists, +// host_gate_finds_component_verdict answers UnknownRefused for every host, which is the honest +// reading of a fleet where the same failure has now recurred on two hosts in two days. + fn host_gate_finds_component_verdict(host: HostIdentity) -> ObservationVerdict { gate_finds_component_verdict( completeness: host_toolchain_completeness_verdict(host: host), reachability: host_toolchain_spawn_reachability(host: host), ) } - -data spawn_reachability_probe_frontier_note: String = "No host on the fleet has a reachability observation, srv2 included, and the reason is structural rather than incidental: the question is what PATH the gate's spawn inherits, which only a probe running INSIDE a job step can answer. A host read cannot see it — I tried, and what a host read CAN show (the unit's inherited PATH, and that a login shell differs from it) is exactly enough to prove the question is open and not enough to close it. The probe is a step emitting its own resolved PATH and the resolution of each required component, and it is the same shape as the read-back independence criterion demands: it must observe, never repair. Until it exists, host_gate_finds_component_verdict answers UnknownRefused for every host, which is the honest reading of a fleet where the same failure has now recurred on two hosts in two days." diff --git a/dag/gunbc/host/host_toolchain_ensure.dag b/dag/gunbc/host/host_toolchain_ensure.dag index 04c20fc22dd..f4cfa8e9931 100644 --- a/dag/gunbc/host/host_toolchain_ensure.dag +++ b/dag/gunbc/host/host_toolchain_ensure.dag @@ -32,16 +32,18 @@ import std.realization_reconcile { Converged, NotConverged } type HostToolchainKind = BuildCache | CodexRuntime | OsInstallActuator | CompilePool -// Compatibility specialization, not the Ensure abstraction: HostToolchainKind is the closed selector for the -// existing HostEffect adapters reachable from this entrypoint. Generic convergence semantics live in -// gunbc.ensure and dispatch requirements live in gunbc.roadmap_execution_contract. Cargo, rustc, provider auth, -// and future task dependencies must not be added to this enum merely because their callers say ensure; they -// require their own typed observation/plan/readback adapters and may then reuse generic Ensure. +// Compatibility specialization, not the Ensure abstraction: HostToolchainKind is the closed +// selector for the existing HostEffect adapters reachable from this entrypoint. Generic convergence +// semantics live in gunbc.ensure; dispatch requirements in gunbc.roadmap_execution_contract. Cargo, +// rustc, provider auth, and future task dependencies must not join this enum merely because their +// callers say ensure; they need their own typed observation/plan/readback adapters and may then +// reuse generic Ensure. // -// CompilePool was admitted against that bar rather than beside it, and the three parts are nameable: the -// observation is gunbc.compile_pool_observe observe_compile_pool_slice, the plan is gunbc.compile_pool_ensure -// admit_ensure_compile_pool_slice, and the read-back is that same admission re-derived from a fresh -// observation after the install. It is listed here because the bar was met, not because its caller says ensure. +// CompilePool was admitted against that bar, and the three parts are nameable: observation +// gunbc.compile_pool_observe observe_compile_pool_slice, plan gunbc.compile_pool_ensure +// admit_ensure_compile_pool_slice, read-back that same admission re-derived from a fresh +// observation after the install. Listed because the bar was met, not because its caller says +// ensure. data host_toolchain_ensure_srv3_fork_scaffold: Disposition = Terminal { reason: "srv3_os_install_actuator_toolchain_ensure thinned to host_toolchain_ensure(host: srv3, kind: OsInstallActuator) — no behavioral fork remains." } @@ -55,7 +57,20 @@ data host_toolchain_ensure_apply_dispatch_scaffold: Disposition = Scaffold { } } -data host_toolchain_reach_pairing_note: String = "Access is DERIVED FROM THE TARGET HOST, never looked up independently from the requested host. Before 2026-07-24 these were two parallel decisions: host_toolchain_ensure_target_host was kind-aware (srv3 + OsInstallActuator resolves to the SELECTED actuator host) while the access lookup keyed on the requested host alone and pinned srv3 to ci_deploy_srv1_operator_access. Two defects followed, both silent. (1) A BuildCache request for srv3 targeted srv3 but authenticated to srv1, so the provisioning effect would have landed on the WRONG MACHINE and reported success — a §5 fail-open, and the exact kind this lane adds. (2) The srv1 pin is only correct in the actuator selector's UNSATISFIABLE FALLBACK arm; whenever ActuatorSelected names a different host the access still pointed at srv1. Pairing target and access in one return value makes the disagreement unwritable (§5 construction over validation) rather than a check that could flag it afterwards. Consequence: this module no longer consumes ci_deploy_srv1_operator_access, which is construction-identical to host_identity_srv1_access (same principal, host, transport, privileged commands — witnessed by host_toolchain_srv1_access_rows_are_one_concept); that §3 nickname still has consumers elsewhere and its corpus-wide dissolution is a separate lane, not smuggled in here." +// Access is DERIVED FROM THE TARGET HOST, never looked up independently from the requested host. +// Before 2026-07-24 these were two parallel decisions: host_toolchain_ensure_target_host was +// kind-aware (srv3 + OsInstallActuator resolves to the SELECTED actuator host) while the access +// lookup keyed on the requested host alone and pinned srv3 to ci_deploy_srv1_operator_access. Two +// silent defects followed. (1) A BuildCache request for srv3 targeted srv3 but authenticated to +// srv1, so the provisioning effect would have landed on the WRONG MACHINE and reported success — a +// §5 fail-open, the exact kind this lane adds. (2) The srv1 pin is correct only in the actuator +// selector's UNSATISFIABLE FALLBACK arm; whenever ActuatorSelected names a different host the +// access still pointed at srv1. Pairing target and access in one return value makes the +// disagreement unwritable (§5 construction over validation) rather than flagged afterwards. +// Consequence: this module no longer consumes ci_deploy_srv1_operator_access, which is +// construction-identical to host_identity_srv1_access (same principal, host, transport, privileged +// commands — witnessed by host_toolchain_srv1_access_rows_are_one_concept); that §3 nickname still +// has consumers elsewhere and its corpus-wide dissolution is a separate lane, not smuggled in here. type HostToolchainReach = ResolvedHostToolchainReach { target: ComputeHost, diff --git a/dag/gunbc/hostname_read.dag b/dag/gunbc/hostname_read.dag index ce9fd2ecb6e..efc3d8da5fc 100644 --- a/dag/gunbc/hostname_read.dag +++ b/dag/gunbc/hostname_read.dag @@ -14,7 +14,11 @@ import gunbc.host_operation_exec { HostnameReadShort, } -data hostname_short_capture_note: String = "The read answers a two-armed capture, never a bare String?: an argv materialization that REFUSES is a typed, located fact about the declaration, and collapsing it into the same `none` that means empty stdout, a failed exec, or an unsupported transport is the state-space conflation DESIGN 5 names (review 42946). Every non-value arm carries WHY, mirroring gunbc.systemctl_status_read's UnitStatusCapture." +// The read answers a two-armed capture, never a bare String?: an argv materialization that REFUSES +// is a typed, located fact about the declaration, and collapsing it into the same `none` that means +// empty stdout, a failed exec, or an unsupported transport is the state-space conflation DESIGN 5 +// names (review 42946). Every non-value arm carries WHY, mirroring gunbc.systemctl_status_read's +// UnitStatusCapture. type HostnameShortCapture = HostnameShortCaptured { value: String } diff --git a/dag/gunbc/instruments/body_lowering_population_probe.dag b/dag/gunbc/instruments/body_lowering_population_probe.dag index 40cdd450667..5875ddce3c0 100644 --- a/dag/gunbc/instruments/body_lowering_population_probe.dag +++ b/dag/gunbc/instruments/body_lowering_population_probe.dag @@ -27,13 +27,118 @@ import v2.std.text { String } data live_tree_disposition: LiveTreeDisposition = ReadsLiveTree -data body_lowering_population_probe_note: String = "BL-2 REPRODUCTION INSTRUMENT, homed outside a *_test.dag module for the same reason gunbc.tools.frontier_ingestion_probe is: what it reports is a revision-scoped OBSERVATION of the present frontier, and enrolling that as a test declaration would make an assertion that goes RED on the repair and GREEN on a regression. It reuses take_census_over and classify_source rather than reproducing them, so there is one stage classifier in the repository and not two.\n\nWHY IT EXISTS. The body-lowering wall cannot be scheduled against an unmeasured population. normalize today returns Accepted for a tree whose diagnostics carry wrapper-retention, and resolve consumes it, so retention is kept out of resolve by propagation rather than by construction. Making retention unconstructible is the right wall and it CHANGES WHICH MODULES NORMALIZE ACCEPTS — so the population that would newly refuse has to be known before the wall lands, not discovered from a red tree afterwards.\n\nWHAT THE SUBJECTS ARE is body_lowering_subject_roster's to say, and this note deliberately does not restate it — an earlier revision carried the population as a prose sentence beside a shorter roster, and the two disagreeing is the defect that produced this rewrite. What holds here is only the scope: the subjects are deliberately NOT the whole corpus — a per-file census over every module is a different and much larger instrument — so anything read off this module is a reading over THOSE subjects and carries no claim about the rest.\n\nWHAT A READER MAY NOT DO WITH IT. The per-member stage is the observation. There is no aggregate pass/fail, because the population is the finding and a Boolean over it would be a verdict on subjects that have not all been separately established." - -data body_lowering_population_first_reading_note: String = "FIRST EXECUTED READING (2026-08-13, three-member execution batch): use_site_verdict reached NormalizeAccepted, materialization_carriers NormalizeOther, optional NormalizeRetained. Stages are named by their VARIANT here rather than by a rendered label, because the label is frontier_ingestion_probe frontend_stage_label's to decide and a note that pins its spelling would go stale the moment that renderer is edited — which is the same reason counting may not read labels either.\n\nWHAT IT CHANGES, and it is the shard rows rather than the finding. Neither SH-D shard RETAINS as a file. use_site_verdict normalizes clean; materialization_carriers refuses at normalize for something that is neither retention nor a graft refusal. So the wrapper-retained residue measured in those shards' door chains does not come from the shard modules themselves — it comes from OTHER modules in their import closures, of which optional is one confirmed instance. Saying a shard is body-lowering blocked was too coarse: the shard is blocked because its CLOSURE contains retaining modules, and the subjects to repair are those, not it.\n\nWHAT THIS IS NOT. Three members are not a population, and NORM_OTHER is a cause this instrument does not identify — it names the stage, not the reason. materialization_carriers refusing at normalize for an unidentified reason is an open question, not a measured cause, and nothing here establishes which closure members of either shard retain beyond the one measured.\n\nA COST OBSERVATION, recorded because it bounds what can be asked next: a ten-member run of this census ran 2h51m without a verdict; over three it returns in about two minutes. That is nonlinear in the member count, so one or more subjects outside the batch is pathological rather than merely larger. WHICH member is NOT established — no per-member timing was taken — and attributing it to the largest file would be a guess dressed as a finding." - -data body_lowering_subject_roster_note: String = "THE SUBJECT ROSTER AND THE EXECUTION BATCH ARE TWO AUTHORITIES, and merging them is the planning error this instrument exists to detect. A previous revision declared three rows under the name body_lowering_population_roster while the module's own prose said the subjects were ten. That makes the error representable in the exact shape we are trying to catch: seven unmeasured subjects erased, one retained member observed, and a bare count licensing the sentence 'the retained population is one'. The denominator has to be the SUBJECTS; what a given run could afford to execute is a separate and smaller fact.\n\nSo body_lowering_subject_roster is the denominator and body_lowering_execution_batch is what one run selected from it. A subject not in the batch is SubjectUnmeasured with a cause, never absent and never silently folded into an observed population.\n\nWHERE THE SUBJECT IDENTITIES COME FROM, each cited rather than asserted. The two SH-D shards are the members with measured door receipts. The five std members come from docs/plans/v2-frontend-std-ingestion-frontier-exact-head.md's executed table: logic, optional, diagnostic and occurrence_identity are its four NORM_RETAINED rows, and node is its NOT RE-OBSERVED row — the member whose probe exceeded a 900s budget, which is an interruption plus a lower bound on cost and never a verdict about the subject.\n\nTHE SPECIMEN CLASS IS ONE SUBJECT, NOT THREE, and the reasoning matters more than the number. The prose population also names 'the bare-expression specimens v2.lens.vacuity found rejecting at this seam'. Those are not committed subjects: vacuity_bool_witness_body_lowering_ceiling_note describes them as inline probe configurations executed across 'many isolated configurations' — it never enumerates them, no fixture file carries them, and its byte-for-byte copy of optional_absent's body is the SAME subject as the optional member already rostered rather than a distinct one. The class grounds as a class; its members do not. Minting three identities would satisfy a sentence by fabricating two subjects, which is precisely what a roster exists to prevent, so the class enters as one subject carrying UnauthoredSpecimenClass and stands at SubjectUnmeasured\{no_committed_specimen_identity}. Dissolve-on: the specimens are committed as fixtures, at which point this placeholder expands into its real members and disappears.\n\nTHE PROSE 'TEN' IS RETIRED AS AN UNFOUNDED COUNT (operator ruling, 2026-08-13). It came from reading the population sentence as two shards plus five std members plus three specimens, where the three was our arithmetic over a note that enumerates nothing. Recorded here rather than silently corrected, because a competing number left standing in prose is what makes the next reader reconcile eight against ten and reopen a settled question — the same reason the aggregate helpers were deleted rather than fixed.\n\nAND EIGHT IS NOT AN AUTHORITY EITHER. The denominator is not a number that was chosen; it is the set of exact identities that could be named, and its cardinality is whatever falls out — completeness is an identity join, not a count equality (DESIGN §5). A ninth grounded identity joins without ceremony; the specimen class expands into its real members when committed. The roster is the join, and any count over it is a reading rather than a fact about the population." - -data body_lowering_program_receipt_execution_note: String = "EXECUTED, NOT ASSERTED (2026-08-13, 73s): body_lowering_program_receipt was run against the live tree and returned eight standings — use_site_verdict SubjectObserved\{NormalizeAccepted}, materialization_carriers SubjectObserved\{NormalizeOther}, optional SubjectObserved\{NormalizeRetained}, logic and diagnostic and occurrence_identity and node SubjectUnmeasured\{not_selected_into_execution_batch}, and vacuity_bare_expression_specimens SubjectUnmeasured\{no_committed_specimen_identity}.\n\nWHAT THAT ESTABLISHES beyond the module compiling. The denominator survives the join: eight subjects in, eight standings out, so no subject is silently dropped between roster and receipt. The three observed rows reproduce the earlier batch reading, so deriving the batch from the roster did not change what runs. And the specimen class routes to its own structural cause rather than to not_selected_into_execution_batch — the distinction the realization coproduct exists to make, which a run would collapse if the two causes were reachable from one arm.\n\nWHAT IT DOES NOT ESTABLISH. Nothing here observes the four unmeasured subjects; their standing is the honest report that this batch did not run them, and it may not be read as a stage. node in particular carries a prior interruption with a cost bound, recorded separately in body_lowering_prior_standing, and this run neither confirms nor refutes it." +// BL-2 REPRODUCTION INSTRUMENT, homed outside a *_test.dag module for the same reason +// gunbc.tools.frontier_ingestion_probe is: what it reports is a revision-scoped OBSERVATION of the +// present frontier, and enrolling that as a test declaration would make an assertion that goes RED +// on the repair and GREEN on a regression. It reuses take_census_over and classify_source rather +// than reproducing them, so there is one stage classifier in the repository and not two. +// +// WHY IT EXISTS. The body-lowering wall cannot be scheduled against an unmeasured population. +// normalize today returns Accepted for a tree whose diagnostics carry wrapper-retention, and +// resolve consumes it, so retention is kept out of resolve by propagation rather than by +// construction. Making retention unconstructible is the right wall and it CHANGES WHICH MODULES +// NORMALIZE ACCEPTS — so the population that would newly refuse has to be known before the wall +// lands, not discovered from a red tree afterwards. +// +// WHAT THE SUBJECTS ARE is body_lowering_subject_roster's to say, and this note deliberately does +// not restate it — an earlier revision carried the population as a prose sentence beside a shorter +// roster, and the two disagreeing is the defect that produced this rewrite. What holds here is only +// the scope: the subjects are deliberately NOT the whole corpus — a per-file census over every +// module is a different and much larger instrument — so anything read off this module is a reading +// over THOSE subjects and carries no claim about the rest. +// +// WHAT A READER MAY NOT DO WITH IT. The per-member stage is the observation. There is no aggregate +// pass/fail, because the population is the finding and a Boolean over it would be a verdict on +// subjects that have not all been separately established. + +// FIRST EXECUTED READING (2026-08-13, three-member execution batch): use_site_verdict reached +// NormalizeAccepted, materialization_carriers NormalizeOther, optional NormalizeRetained. Stages +// are named by their VARIANT here rather than by a rendered label, because the label is +// frontier_ingestion_probe frontend_stage_label's to decide and a note that pins its spelling would +// go stale the moment that renderer is edited — which is the same reason counting may not read +// labels either. +// +// WHAT IT CHANGES, and it is the shard rows rather than the finding. Neither SH-D shard RETAINS as +// a file. use_site_verdict normalizes clean; materialization_carriers refuses at normalize for +// something that is neither retention nor a graft refusal. So the wrapper-retained residue measured +// in those shards' door chains does not come from the shard modules themselves — it comes from +// OTHER modules in their import closures, of which optional is one confirmed instance. Saying a +// shard is body-lowering blocked was too coarse: the shard is blocked because its CLOSURE contains +// retaining modules, and the subjects to repair are those, not it. +// +// WHAT THIS IS NOT. Three members are not a population, and NORM_OTHER is a cause this instrument +// does not identify — it names the stage, not the reason. materialization_carriers refusing at +// normalize for an unidentified reason is an open question, not a measured cause, and nothing here +// establishes which closure members of either shard retain beyond the one measured. +// +// A COST OBSERVATION, recorded because it bounds what can be asked next: a ten-member run of this +// census ran 2h51m without a verdict; over three it returns in about two minutes. That is nonlinear +// in the member count, so one or more subjects outside the batch is pathological rather than merely +// larger. WHICH member is NOT established — no per-member timing was taken — and attributing it to +// the largest file would be a guess dressed as a finding. + +// THE SUBJECT ROSTER AND THE EXECUTION BATCH ARE TWO AUTHORITIES, and merging them is the planning +// error this instrument exists to detect. A previous revision declared three rows under the name +// body_lowering_population_roster while the module's own prose said the subjects were ten. That +// makes the error representable in the exact shape we are trying to catch: seven unmeasured +// subjects erased, one retained member observed, and a bare count licensing the sentence 'the +// retained population is one'. The denominator has to be the SUBJECTS; what a given run could +// afford to execute is a separate and smaller fact. +// +// So body_lowering_subject_roster is the denominator and body_lowering_execution_batch is what one +// run selected from it. A subject not in the batch is SubjectUnmeasured with a cause, never absent +// and never silently folded into an observed population. +// +// WHERE THE SUBJECT IDENTITIES COME FROM, each cited rather than asserted. The two SH-D shards are +// the members with measured door receipts. The five std members come from +// docs/plans/v2-frontend-std-ingestion-frontier-exact-head.md's executed table: logic, optional, +// diagnostic and occurrence_identity are its four NORM_RETAINED rows, and node is its NOT +// RE-OBSERVED row — the member whose probe exceeded a 900s budget, which is an interruption plus a +// lower bound on cost and never a verdict about the subject. +// +// THE SPECIMEN CLASS IS ONE SUBJECT, NOT THREE, and the reasoning matters more than the number. The +// prose population also names 'the bare-expression specimens v2.lens.vacuity found rejecting at +// this seam'. Those are not committed subjects: vacuity_bool_witness_body_lowering_ceiling_note +// describes them as inline probe configurations executed across 'many isolated configurations' — it +// never enumerates them, no fixture file carries them, and its byte-for-byte copy of +// optional_absent's body is the SAME subject as the optional member already rostered rather than a +// distinct one. The class grounds as a class; its members do not. Minting three identities would +// satisfy a sentence by fabricating two subjects, which is precisely what a roster exists to +// prevent, so the class enters as one subject carrying UnauthoredSpecimenClass and stands at +// SubjectUnmeasured{no_committed_specimen_identity}. Dissolve-on: the specimens are committed as +// fixtures, at which point this placeholder expands into its real members and disappears. +// +// THE PROSE 'TEN' IS RETIRED AS AN UNFOUNDED COUNT (operator ruling, 2026-08-13). It came from +// reading the population sentence as two shards plus five std members plus three specimens, where +// the three was our arithmetic over a note that enumerates nothing. Recorded here rather than +// silently corrected, because a competing number left standing in prose is what makes the next +// reader reconcile eight against ten and reopen a settled question — the same reason the aggregate +// helpers were deleted rather than fixed. +// +// AND EIGHT IS NOT AN AUTHORITY EITHER. The denominator is not a number that was chosen; it is the +// set of exact identities that could be named, and its cardinality is whatever falls out — +// completeness is an identity join, not a count equality (DESIGN §5). A ninth grounded identity +// joins without ceremony; the specimen class expands into its real members when committed. The +// roster is the join, and any count over it is a reading rather than a fact about the population. + +// EXECUTED, NOT ASSERTED (2026-08-13, 73s): body_lowering_program_receipt was run against the live +// tree and returned eight standings — use_site_verdict SubjectObserved{NormalizeAccepted}, +// materialization_carriers SubjectObserved{NormalizeOther}, optional +// SubjectObserved{NormalizeRetained}, logic and diagnostic and occurrence_identity and node +// SubjectUnmeasured{not_selected_into_execution_batch}, and vacuity_bare_expression_specimens +// SubjectUnmeasured{no_committed_specimen_identity}. +// +// WHAT THAT ESTABLISHES beyond the module compiling. The denominator survives the join: eight +// subjects in, eight standings out, so no subject is silently dropped between roster and receipt. +// The three observed rows reproduce the earlier batch reading, so deriving the batch from the +// roster did not change what runs. And the specimen class routes to its own structural cause rather +// than to not_selected_into_execution_batch — the distinction the realization coproduct exists to +// make, which a run would collapse if the two causes were reachable from one arm. +// +// WHAT IT DOES NOT ESTABLISH. Nothing here observes the four unmeasured subjects; their standing is +// the honest report that this batch did not run them, and it may not be read as a stage. node in +// particular carries a prior interruption with a cost bound, recorded separately in +// body_lowering_prior_standing, and this run neither confirms nor refutes it. type BodyLoweringSubjectRealization = LiveFileSubject { path: String } @@ -55,7 +160,17 @@ data body_lowering_subject_roster: List = [ BodyLoweringSubject { label: ^vacuity_bare_expression_specimens, realization: UnauthoredSpecimenClass }, ] -data body_lowering_observation_standing_note: String = "STANDING IS THREE STATES BECAUSE THE TWO-STATE READING IS THE DEFECT. Observed and absent cannot carry this population: a subject dropped for cost, a subject whose run was interrupted by a budget, and a subject genuinely measured are three different epistemic positions with three different remedies, and collapsing the first two into absence is the empty-observation narrow — it renders 'we did not look' as 'there is nothing there'.\n\nSubjectInterrupted is separate from SubjectUnmeasured rather than a cause of it, and node is why: its probe exceeded 900s. That is an interruption plus a measured lower bound on cost, so it says something real about the subject's expense while saying nothing about its stage. An unmeasured subject carries no such bound. Merging them would discard the only quantitative fact the interrupted row has." +// STANDING IS THREE STATES BECAUSE THE TWO-STATE READING IS THE DEFECT. Observed and absent cannot +// carry this population: a subject dropped for cost, a subject whose run was interrupted by a +// budget, and a subject genuinely measured are three different epistemic positions with three +// different remedies, and collapsing the first two into absence is the empty-observation narrow — +// it renders 'we did not look' as 'there is nothing there'. +// +// SubjectInterrupted is separate from SubjectUnmeasured rather than a cause of it, and node is why: +// its probe exceeded 900s. That is an interruption plus a measured lower bound on cost, so it says +// something real about the subject's expense while saying nothing about its stage. An unmeasured +// subject carries no such bound. Merging them would discard the only quantitative fact the +// interrupted row has. type BodyLoweringObservationStanding = SubjectObserved { stage: FrontendStage } @@ -67,7 +182,35 @@ type BodyLoweringSubjectStanding { standing: BodyLoweringObservationStanding, } -data body_lowering_execution_batch_note: String = "THE BATCH IS THREE, AND THE REASON IS A MEASUREMENT RATHER THAN A PREFERENCE. A ten-member run was executed and ABANDONED at 2h51m wall and 8.3 GiB resident without producing a verdict — on a host whose shared slice is 20 GiB and whose kernel kills the LARGEST task in it, so that run was a hazard to every other session before it was a slow measurement. The instrument this extends already declared the cost: its own roster note records the accepted-classification path measuring over the five-second fast-lane budget for fifteen files, and states that the batched or realized execution which would make a live census affordable is still owed.\n\nSo the batch is sized to what the present instrument can deliver, and that gap is a deficit in the instrument rather than a property of the subjects. THE FOUR SUBJECTS NOT IN IT ARE NOT OBSERVED-AND-EXCLUDED — they are unmeasured, and the program receipt says so per subject rather than leaving them out.\n\nTHE BATCH IS DERIVED FROM THE ROSTER, NEVER RE-AUTHORED BESIDE IT. An intermediate revision of this repair declared the three batch rows as their own literal list carrying their own paths, which put the same path in two places and let them drift — the second representation this whole change exists to remove, reintroduced one declaration below the note forbidding it. body_lowering_batch_selection now carries LABELS only, and the batch is a fold over the roster selecting those labels and reading each path from the subject that owns it. Two things follow by construction rather than by check: a batch row cannot name a path the roster does not, and a subject with no committed realization cannot enter a batch at all, because the specimen arm has no path to contribute and is skipped where it would otherwise have to invent one.\n\nHOW THE NEXT RUN SHOULD BE SHAPED, because the 2h51m failure already bounds it: ONE INDEPENDENT JOB PER SUBJECT, each emitting its own subject identity, stage receipt, runtime and terminal standing. The cost was nonlinear in member count, so one or more subjects is pathological rather than merely larger — a per-subject run identifies WHICH, where another monolithic evaluation would only reproduce the interruption and tempt an attribution by file size, which would be a guess dressed as a finding." +// THE BATCH IS THREE, AND THE REASON IS A MEASUREMENT RATHER THAN A PREFERENCE. A ten-member run +// was executed and ABANDONED at 2h51m wall and 8.3 GiB resident without producing a verdict — on a +// host whose shared slice is 20 GiB and whose kernel kills the LARGEST task in it, so that run was +// a hazard to every other session before it was a slow measurement. The instrument this extends +// already declared the cost: its own roster note records the accepted-classification path measuring +// over the five-second fast-lane budget for fifteen files, and states that the batched or realized +// execution which would make a live census affordable is still owed. +// +// So the batch is sized to what the present instrument can deliver, and that gap is a deficit in +// the instrument rather than a property of the subjects. THE FOUR SUBJECTS NOT IN IT ARE NOT +// OBSERVED-AND-EXCLUDED — they are unmeasured, and the program receipt says so per subject rather +// than leaving them out. +// +// THE BATCH IS DERIVED FROM THE ROSTER, NEVER RE-AUTHORED BESIDE IT. An intermediate revision of +// this repair declared the three batch rows as their own literal list carrying their own paths, +// which put the same path in two places and let them drift — the second representation this whole +// change exists to remove, reintroduced one declaration below the note forbidding it. +// body_lowering_batch_selection now carries LABELS only, and the batch is a fold over the roster +// selecting those labels and reading each path from the subject that owns it. Two things follow by +// construction rather than by check: a batch row cannot name a path the roster does not, and a +// subject with no committed realization cannot enter a batch at all, because the specimen arm has +// no path to contribute and is skipped where it would otherwise have to invent one. +// +// HOW THE NEXT RUN SHOULD BE SHAPED, because the 2h51m failure already bounds it: ONE INDEPENDENT +// JOB PER SUBJECT, each emitting its own subject identity, stage receipt, runtime and terminal +// standing. The cost was nonlinear in member count, so one or more subjects is pathological rather +// than merely larger — a per-subject run identifies WHICH, where another monolithic evaluation +// would only reproduce the interruption and tempt an attribution by file size, which would be a +// guess dressed as a finding. data body_lowering_batch_selection: List = [ ^use_site_verdict, @@ -119,9 +262,58 @@ fn standings_from(obs: List) -> List ObservedReading { } } -data body_lowering_standing_reading_note: String = "A READING EXISTS ONLY WHERE SOMETHING WAS OBSERVED, which is why this returns its own coproduct rather than an ObservedReading. A first draft of this function mapped SubjectUnmeasured and SubjectInterrupted onto ObservedRefusedCauseUnclassified, on the reasoning that all three leave the cause unknown. That is the census-refusal conflation one level up, and in the same module that just repaired it: an unclassified refusal is a fact about a subject that WAS put through the door, while an unmeasured subject was never attempted and an interrupted one was abandoned mid-flight. Collapsing them makes a subject nobody looked at indistinguishable from one the compiler refused for a reason we could not name, and the remedies differ — run it, versus classify the refusal.\n\nThe shapes rhyme, which is exactly why the collapse is tempting: unknown-because-unclassified and unknown-because-unattempted are both unknowns. They are not the same unknown." +// A READING EXISTS ONLY WHERE SOMETHING WAS OBSERVED, which is why this returns its own coproduct +// rather than an ObservedReading. A first draft of this function mapped SubjectUnmeasured and +// SubjectInterrupted onto ObservedRefusedCauseUnclassified, on the reasoning that all three leave +// the cause unknown. That is the census-refusal conflation one level up, and in the same module +// that just repaired it: an unclassified refusal is a fact about a subject that WAS put through the +// door, while an unmeasured subject was never attempted and an interrupted one was abandoned +// mid-flight. Collapsing them makes a subject nobody looked at indistinguishable from one the +// compiler refused for a reason we could not name, and the remedies differ — run it, versus +// classify the refusal. +// +// The shapes rhyme, which is exactly why the collapse is tempting: unknown-because-unclassified and +// unknown-because-unattempted are both unknowns. They are not the same unknown. type StandingReading = StandingObserved { reading: ObservedReading } @@ -153,7 +356,26 @@ fn reading_of_standing(st: BodyLoweringObservationStanding) -> StandingReading { } } -data body_lowering_refusal_scope_note: String = "A CENSUS REFUSAL IS SCOPED TO THE BATCH, NEVER TO THE ROSTER, and an earlier revision of this repair got it wrong in the exact way the repair exists to prevent. It routed EVERY live-file subject through the refusal cause, so a census abort rewrote logic, diagnostic, occurrence_identity and node — subjects this run was never going to execute — from 'not selected' into 'the census refused'. Those are different epistemic positions with different remedies: a not-selected subject needs a bigger batch or a cheaper instrument, while a refused census needs the infrastructure fixed, and a reader deciding what to do next is misdirected by the conflation. It also destroyed the roster/batch split on precisely the path where the distinction is least visible and therefore least likely to be questioned.\n\nSo the refusal cause reaches ONLY the subjects that were actually selected. A subject outside the batch keeps not_selected_into_execution_batch whether the census succeeded or refused, because its standing does not depend on the census at all — nothing about it was attempted. The specimen class keeps its own structural cause under both, for the same reason one rung further out: it could not have been attempted under any batch.\n\nThe general form, worth stating because it is easy to reintroduce: a failure at one scope may not be attributed to subjects outside that scope. Widening a refusal to everything it did not touch is the absorbing fallback wearing an error's name — it looks conservative and it manufactures false information about subjects nobody looked at." +// A CENSUS REFUSAL IS SCOPED TO THE BATCH, NEVER TO THE ROSTER, and an earlier revision of this +// repair got it wrong in the exact way the repair exists to prevent. It routed EVERY live-file +// subject through the refusal cause, so a census abort rewrote logic, diagnostic, +// occurrence_identity and node — subjects this run was never going to execute — from 'not selected' +// into 'the census refused'. Those are different epistemic positions with different remedies: a +// not-selected subject needs a bigger batch or a cheaper instrument, while a refused census needs +// the infrastructure fixed, and a reader deciding what to do next is misdirected by the conflation. +// It also destroyed the roster/batch split on precisely the path where the distinction is least +// visible and therefore least likely to be questioned. +// +// So the refusal cause reaches ONLY the subjects that were actually selected. A subject outside the +// batch keeps not_selected_into_execution_batch whether the census succeeded or refused, because +// its standing does not depend on the census at all — nothing about it was attempted. The specimen +// class keeps its own structural cause under both, for the same reason one rung further out: it +// could not have been attempted under any batch. +// +// The general form, worth stating because it is easy to reintroduce: a failure at one scope may not +// be attributed to subjects outside that scope. Widening a refusal to everything it did not touch +// is the absorbing fallback wearing an error's name — it looks conservative and it manufactures +// false information about subjects nobody looked at. fn refusal_standing_of(s: BodyLoweringSubject, cause: Symbol) -> BodyLoweringObservationStanding { match s.realization { @@ -185,7 +407,17 @@ fn body_lowering_program_receipt() -> List { } } -data body_lowering_prior_standing_note: String = "THE PRIOR STANDINGS ARE A CITED HISTORICAL RECEIPT, not this run's observation, and they are declared separately so the two can never be read as one measurement. Every row here comes from the executed table in docs/plans/v2-frontend-std-ingestion-frontier-exact-head.md at head c03be069687. This is also where SubjectInterrupted has its producer: node is the NOT RE-OBSERVED row, and recording it as interrupted-with-a-bound rather than unmeasured is what keeps its 900s lower bound attached to the subject it was measured on.\n\nA READER MAY NOT TRANSFER THESE TO THE PRESENT HEAD. They were taken on a different tree; the first executed reading of this instrument already contradicted one of them at the shard grain, which is exactly why a historical value may not be filled in for a subject this run did not execute." +// THE PRIOR STANDINGS ARE A CITED HISTORICAL RECEIPT, not this run's observation, and they are +// declared separately so the two can never be read as one measurement. Every row here comes from +// the executed table in docs/plans/v2-frontend-std-ingestion-frontier-exact-head.md at head +// c03be069687. This is also where SubjectInterrupted has its producer: node is the NOT RE-OBSERVED +// row, and recording it as interrupted-with-a-bound rather than unmeasured is what keeps its 900s +// lower bound attached to the subject it was measured on. +// +// A READER MAY NOT TRANSFER THESE TO THE PRESENT HEAD. They were taken on a different tree; the +// first executed reading of this instrument already contradicted one of them at the shard grain, +// which is exactly why a historical value may not be filled in for a subject this run did not +// execute. data body_lowering_prior_standing: List = [ BodyLoweringSubjectStanding { label: ^logic, standing: SubjectObserved { stage: NormalizeRetained } }, @@ -199,6 +431,29 @@ fn observation_line(o: MemberObservation) -> String { concat(symbol_lexeme(sym: o.label), concat(" ", frontend_stage_label(s: o.stage))) } +// THERE IS NO AGGREGATE COUNT HERE, and its absence is the module's own stated scope rather than an +// omission. An earlier revision carried count_stage_matching and retained_member_count, and both +// were deleted for defects worth recording so they are not reintroduced. +// +// A REFUSAL BECAME AN INTEGER. count_stage_matching answered 0 - 1 for CensusRefusedAtWorld and 0 - +// 2 for CensusRefusedAtReconciliation, so a typed refusal and a genuine population count inhabited +// one type. A caller could add, compare or display a failure as a measurement, and -1 is a +// plausible-looking number rather than a loud one. The census already carries typed refusal arms; +// collapsing them into the counting type discarded exactly the distinction those arms exist to +// preserve. +// +// PRESENTATION BECAME SEMANTIC AUTHORITY. It counted by comparing a rendered stage label against a +// target String. FrontendStage is a closed coproduct, so a label rename would have changed the +// count with no stage changing, and a label collision would have merged distinct stages — the +// renderer deciding a semantic question. Counting belongs to the structural variant or to +// frontend_stage_eq, never to rendered text. +// +// Both were reachable because an aggregate was authored at all. This module's note already says the +// per-member observation IS the finding and that no aggregate verdict is warranted over a roster +// whose members have not each been separately established; the helpers contradicted that. A reader +// wanting a count over these members reads the per-member observations and counts the subjects it +// can name. + fn census_report() -> String { match take_body_lowering_population_census() { CensusRefusedAtWorld => "REFUSED_AT_WORLD" @@ -213,5 +468,3 @@ fn census_report() -> String { ) } } - -data body_lowering_no_aggregate_note: String = "THERE IS NO AGGREGATE COUNT HERE, and its absence is the module's own stated scope rather than an omission. An earlier revision carried count_stage_matching and retained_member_count, and both were deleted for defects worth recording so they are not reintroduced.\n\nA REFUSAL BECAME AN INTEGER. count_stage_matching answered 0 - 1 for CensusRefusedAtWorld and 0 - 2 for CensusRefusedAtReconciliation, so a typed refusal and a genuine population count inhabited one type. A caller could add, compare or display a failure as a measurement, and -1 is a plausible-looking number rather than a loud one. The census already carries typed refusal arms; collapsing them into the counting type discarded exactly the distinction those arms exist to preserve.\n\nPRESENTATION BECAME SEMANTIC AUTHORITY. It counted by comparing a rendered stage label against a target String. FrontendStage is a closed coproduct, so a label rename would have changed the count with no stage changing, and a label collision would have merged distinct stages — the renderer deciding a semantic question. Counting belongs to the structural variant or to frontend_stage_eq, never to rendered text.\n\nBoth were reachable because an aggregate was authored at all. This module's note already says the per-member observation IS the finding and that no aggregate verdict is warranted over a roster whose members have not each been separately established; the helpers contradicted that. A reader wanting a count over these members reads the per-member observations and counts the subjects it can name." diff --git a/dag/gunbc/instruments/cas_store_live_probe.dag b/dag/gunbc/instruments/cas_store_live_probe.dag index 433bcb8694a..7224eda2b12 100644 --- a/dag/gunbc/instruments/cas_store_live_probe.dag +++ b/dag/gunbc/instruments/cas_store_live_probe.dag @@ -5,30 +5,25 @@ import std.content_hash { content_hash_of_value } import std.durable_compare_and_set { CasAttempt, CasOutcome, CasCommitted, CasPreconditionFailed, CasStoreRefused, CasExpectation, ExpectSlotAbsent, ExpectSlotGeneration, cas_attempt, cas_generation_count } import gunbc.durable_cas_file_store { CasAttemptAdmission, CasAttemptAdmitted, CasAttemptDigestMismatch, CasAttemptDigestIncomparable, CasAttemptKeyNotSlotAddressable, admit_cas_attempt, file_compare_and_set } -// The live receipt for the EFFECTFUL half of the file-backed CAS store, which -// the hermetic witness cannot reach. +// The live receipt for the EFFECTFUL half of the file-backed CAS store, which the hermetic witness +// cannot reach. // -// It is an entry point rather than a shell script because DESIGN rules an ad-hoc -// .sh in this repository to be unmodeled realization: if a measurement is worth -// re-deriving it is worth an entry point. Run it as +// An entry point rather than a shell script because DESIGN rules an ad-hoc .sh in this repository +// unmodeled realization: a measurement worth re-deriving is worth an entry point. Run it as // // gunbc run --source-root dag --source-root src/v2 \ // --entry dag/gunbc/instruments/cas_store_live_probe.dag --function cas_store_live_probe \ // --arg root= // -// WHY IT COVERS WHAT IT COVERS. An earlier revision of this probe asserted only -// that a first write commits and a second loses. That passed against a store -// carrying a live precondition defect, because both of its attempts expected an -// ABSENT slot -- the one expectation the old derivation got right. The probe was -// strongest exactly where the design was already sound and silent where the -// defect lived, which is the more useful half of the lesson. +// WHY IT COVERS WHAT IT COVERS. An earlier revision asserted only that a first write commits and a +// second loses. That passed against a store carrying a live precondition defect, because both +// attempts expected an ABSENT slot -- the one expectation the old derivation got right. The probe +// was strongest where the design was already sound and silent where the defect lived. // -// The two rows that would have caught it are the last two: an attempt expecting -// generation 7 must be REFUSED against a slot at generation 2, and refused again -// against a slot that does not exist at all. Under the old derivation the target -// was computed from the expectation, so both found generation 8 free, both won -// the exclusive create, and both reported a commit on a precondition that was -// never true. +// The two rows that would have caught it are the last two: an attempt expecting generation 7 must +// be REFUSED against a slot at generation 2, and again against a slot that does not exist. Under +// the old derivation the target was computed from the expectation, so both found generation 8 +// free, both won the exclusive create, and both reported a commit on a precondition never true. fn cas_probe_attempt(key: NonEmptyStr, payload: NonEmptyStr, expected: CasExpectation) -> CasAttempt { cas_attempt(key: key, expected: expected, proposed: payload, proposed_content: content_hash_of_value(value: payload)) diff --git a/dag/gunbc/instruments/cheap_gate_pool.dag b/dag/gunbc/instruments/cheap_gate_pool.dag index 0b29b450cbf..2e4b42fca6a 100644 --- a/dag/gunbc/instruments/cheap_gate_pool.dag +++ b/dag/gunbc/instruments/cheap_gate_pool.dag @@ -5,9 +5,32 @@ import std.logic { Bool } import tools.host_prelude { ClaimRun, run_gunbc_claims } import tools.extdeps_external_authority_transport { extdeps_external_authority_claims } -data cheap_gate_pool_note: String = "ONE pooled claim_batch child for the whole cheap batch (CI floor endgame D1, 2026-07-23; the cold-child class, attribution doc section 9/9.2): after #7122's per-gate pooling, batch 0 still spawned one child per claim-backed gate (layering ~88s + extdeps ~64s on run 30009199696), each paying its own corpus-denominated MultiEntryIndex build over the SAME source roots. The union pool runs every batch-0 claim in one child (srv1 counterfactual: all 12 claims in ONE process 93.4s vs 480s serial), so the shared std/spec closure typechecks once per batch instead of once per gate. The pool membership is DERIVED — the concern authorities' claim lists (today extdeps_external_authority_claims; the layering direction concern was deleted by operator ruling 2026-07-24, its claims with it) — never a third hand-kept roster (roster_registry: ByDerivation). Per-claim verdicts stay named by claim_batch's own PASS-per-function / FAIL-per-function loop with no short-circuit, and the per-gate concern rows survive as pure ENROLLMENT WALLS asserting chunk-transform totality for their claims (see chunk_totality_wall_note — bare pool membership would be vacuous since the pool is their union by construction). Gate-grain attribution deliberately moves to the pool gate: a failing claim names ITSELF in the child's FAIL line, which is the finer grain." +// ONE pooled claim_batch child for the whole cheap batch (CI floor endgame D1, 2026-07-23; the +// cold-child class, attribution doc section 9/9.2): after #7122's per-gate pooling, batch 0 still +// spawned one child per claim-backed gate (layering ~88s + extdeps ~64s on run 30009199696), each +// paying its own corpus-denominated MultiEntryIndex build over the SAME source roots. The union +// pool runs every batch-0 claim in one child (srv1 counterfactual: all 12 claims in ONE process +// 93.4s vs 480s serial), so the shared std/spec closure typechecks once per batch instead of once +// per gate. The pool membership is DERIVED — the concern authorities' claim lists (today +// extdeps_external_authority_claims; the layering direction concern was deleted by operator ruling +// 2026-07-24, its claims with it) — never a third hand-kept roster (roster_registry: ByDerivation). +// Per-claim verdicts stay named by claim_batch's own PASS-per-function / FAIL-per-function loop +// with no short-circuit, and the per-gate concern rows survive as pure ENROLLMENT WALLS asserting +// chunk-transform totality for their claims (see chunk_totality_wall_note — bare pool membership +// would be vacuous since the pool is their union by construction). Gate-grain attribution +// deliberately moves to the pool gate: a failing claim names ITSELF in the child's FAIL line, which +// is the finer grain. -data cheap_gate_pool_slot_sizing_note: String = "K sub-pools sized to the slot (attribution doc section 9.1, the pooling memory cliff): on the Pi bench a pooled process whose UNIONED closure thrashes under the memory envelope loses the whole pooling win (pooled 73m vs spawn-sum 66m), and the same cliff exists inside 16GiB slot cgroups under fleet pressure. So the pool is chunked: at most cheap_gate_pool_max_claims_per_child claims per child, K children serial when the union exceeds it — never one unbounded union. Today's batch-0 union is the extdeps-authority claims over small fixture closures (K=1); the cap is the declared wall that keeps a future enrollment from silently growing one child past the slot. The pooled child is a SEPARATE process from the executor by design (never in-executor evaluation: the executor is the 16GiB-pinned process of the 2026-07-23 crawl; the child dies and frees). Raising the cap is a conscious data edit here, priced against the slot budget." +// K sub-pools sized to the slot (attribution doc section 9.1, the pooling memory cliff): on the Pi +// bench a pooled process whose UNIONED closure thrashes under the memory envelope loses the whole +// pooling win (pooled 73m vs spawn-sum 66m), and the same cliff exists inside 16GiB slot cgroups +// under fleet pressure. So the pool is chunked: at most cheap_gate_pool_max_claims_per_child claims +// per child, K children serial when the union exceeds it — never one unbounded union. Today's +// batch-0 union is the extdeps-authority claims over small fixture closures (K=1); the cap is the +// declared wall that keeps a future enrollment from silently growing one child past the slot. The +// pooled child is a SEPARATE process from the executor by design (never in-executor evaluation: the +// executor is the 16GiB-pinned process of the 2026-07-23 crawl; the child dies and frees). Raising +// the cap is a conscious data edit here, priced against the slot budget. data cheap_gate_pool_max_claims_per_child: Int = 16 @@ -71,7 +94,14 @@ fn claim_in_pool(c: ClaimRun, pool: List) -> Bool { }) } -data chunk_totality_wall_note: String = "The non-vacuous wall (review catch, endgame D1): the derived pool CANNOT drop a concern row — it is the union of the concern lists by construction, so a bare membership check against the pool would be a tautology (an inert gate, the coverage-by-illusion class). What CAN drop a claim is the K-chunking transform between the pool and the spawned children — new fold code sitting between declaration and execution. So each concern gate asserts chunk-transform TOTALITY for its claims: every claim reaches some chunked child's roster (the compile_clean_shard_totality pattern, applied to this partition). The RED direction is real: a chunker edit that loses or fuses rows reds the concern gate before the pool even spawns." +// The non-vacuous wall (review catch, endgame D1): the derived pool CANNOT drop a concern row — it +// is the union of the concern lists by construction, so a bare membership check against the pool +// would be a tautology (an inert gate, the coverage-by-illusion class). What CAN drop a claim is +// the K-chunking transform between the pool and the spawned children — new fold code sitting +// between declaration and execution. So each concern gate asserts chunk-transform TOTALITY for its +// claims: every claim reaches some chunked child's roster (the compile_clean_shard_totality +// pattern, applied to this partition). The RED direction is real: a chunker edit that loses or +// fuses rows reds the concern gate before the pool even spawns. fn cheap_pool_chunked_claims() -> List { fold( diff --git a/dag/gunbc/instruments/ci_heal_dispatch.dag b/dag/gunbc/instruments/ci_heal_dispatch.dag index 9e362276136..f6188b31990 100644 --- a/dag/gunbc/instruments/ci_heal_dispatch.dag +++ b/dag/gunbc/instruments/ci_heal_dispatch.dag @@ -12,7 +12,13 @@ data ci_heal_dispatch_owner: String = gunbc_repository.owner data ci_heal_dispatch_repo: String = gunbc_repository.name data ci_heal_dispatch_workflow_id: String = "ci.yml" -data ci_heal_dispatch_authority_note: String = "The heal job does not mint a gh-api shell call. It invokes the modeled github.Workflows.CreateDispatch operation, whose REST transport is the realization handler. The dispatch ref remains the PR branch because GitHub's endpoint requires a branch/tag ref; expected_healed_sha travels separately as the immutable input and the run refuses unless BOTH its GitHub check subject (github.sha) and checkout name it. GITHUB_TOKEN is resolved solely by the service's auth_source; CreateDispatch has no unused caller-supplied auth_token input and this tool passes no empty sentinel." +// The heal job does not mint a gh-api shell call. It invokes the modeled +// github.Workflows.CreateDispatch operation, whose REST transport is the realization handler. The +// dispatch ref remains the PR branch because GitHub's endpoint requires a branch/tag ref; +// expected_healed_sha travels separately as the immutable input and the run refuses unless BOTH its +// GitHub check subject (github.sha) and checkout name it. GITHUB_TOKEN is resolved solely by the +// service's auth_source; CreateDispatch has no unused caller-supplied auth_token input and this +// tool passes no empty sentinel. fn required_env(name: NonEmptyStr) -> String? { let observed = shell.Env.Get(name: name) diff --git a/dag/gunbc/instruments/dag_compile_clean_gate.dag b/dag/gunbc/instruments/dag_compile_clean_gate.dag index 2d2c3ae4d9e..394512890cc 100644 --- a/dag/gunbc/instruments/dag_compile_clean_gate.dag +++ b/dag/gunbc/instruments/dag_compile_clean_gate.dag @@ -4,7 +4,15 @@ import std.process { ProcessExit, ExitSuccess, exit_failure } import gunbc.ci_gate { gate_receipt_exit, gate_receipt_failure_detail } import tools.dag_compile_clean_transport { run_clean_tree_compile } -data dag_compile_clean_failure_receipt_note: String = "The loudness half: the floor witness collapses this gate's ProcessExit to Bool via tools.ci_gates.exit_ok, so compile-clean reds reported only returned Bool(false). The gate consumes the executor's one in-run compile receipt; the failure-receipt companion projects the same receipt's located diagnostic without a second compile. BOTH HALVES NOW READ ONE RECEIPT. Until this retype the body branched on a Bool and, on the false side, INVENTED its own reason -- the fixed string 'dag compile-clean gate failed: clean-tree compile over witness layer roots' -- while the companion read the located detail from a SECOND builtin over the same process global. So the exit reason a reader saw was a constant that could not distinguish a failed compile from a gate that never ran, and the only text that could was somewhere else. Now the receipt carries its verdict and its detail together and gunbc.ci_gate projects both." +// The loudness half: the floor witness collapses this gate's ProcessExit to Bool via +// tools.ci_gates.exit_ok, so compile-clean reds reported only returned Bool(false). The gate +// consumes the executor's one in-run compile receipt; the failure-receipt companion projects the +// same receipt's located diagnostic without a second compile. BOTH HALVES NOW READ ONE RECEIPT. +// Before this retype the body branched on a Bool and, on the false side, INVENTED its reason -- +// the fixed string 'dag compile-clean gate failed: clean-tree compile over witness layer roots' -- +// while the companion read the located detail from a SECOND builtin over the same process global: +// the exit reason was a constant that could not distinguish a failed compile from a gate that never +// ran. Now the receipt carries verdict and detail together and gunbc.ci_gate projects both. fn dag_compile_clean_failure_reason(exit: ProcessExit) -> String { match exit { diff --git a/dag/gunbc/instruments/dag_compile_clean_partition.dag b/dag/gunbc/instruments/dag_compile_clean_partition.dag index 1a25244e382..bcf54b87950 100644 --- a/dag/gunbc/instruments/dag_compile_clean_partition.dag +++ b/dag/gunbc/instruments/dag_compile_clean_partition.dag @@ -8,7 +8,18 @@ import std.reducible { reduce_verdicts_located, verdict_eq } -data compile_clean_partition_boundary_note: String = "The shard roster's entry roots are ALL of witness_layer_roots — exactly the tree the whole-tree gate compiles — never a subset. The prior boundary took witness_layer_roots.first() ('dag' only), so the roster was a strict subset of the compiled tree: a diff touching only src/v2 .dag files intersected no roster entry and fell to the 'no shard intersection' whole-tree baseline where compiling its own entry closures was admissible (the 2026-07-23 floor redundant-work ledger, lever 8; run 29976989996), and — the fail-open direction of the same asymmetry — a dag-rooted touch could select only dag importers while affected src/v2 importers went uncompiled on a scoped run, covered only by the falsifier's whole-tree cold control. Deriving the boundary from the same ci_layer_roots authority the gate compiles closes both directions at once: roster ⊇ every compiled module entry, so scoped selection is closure-complete over the compiled universe, and 'no shard intersection' shrinks to its honest residue (touched .dag files that declare no module and are imported by none)." +// The shard roster's entry roots are ALL of witness_layer_roots — exactly the tree the whole-tree +// gate compiles — never a subset. The prior boundary took witness_layer_roots.first() ('dag' only), +// so the roster was a strict subset of the compiled tree: a diff touching only src/v2 .dag files +// intersected no roster entry and fell to the 'no shard intersection' whole-tree baseline where +// compiling its own entry closures was admissible (the 2026-07-23 floor redundant-work ledger, +// lever 8; run 29976989996), and — the fail-open direction of the same asymmetry — a dag-rooted +// touch could select only dag importers while affected src/v2 importers went uncompiled on a scoped +// run, covered only by the falsifier's whole-tree cold control. Deriving the boundary from the same +// ci_layer_roots authority the gate compiles closes both directions at once: roster ⊇ every +// compiled module entry, so scoped selection is closure-complete over the compiled universe, and +// 'no shard intersection' shrinks to its honest residue (touched .dag files that declare no module +// and are imported by none). type CompileCleanPartitionBoundary { entry_roots: List diff --git a/dag/gunbc/instruments/dag_compile_clean_scope.dag b/dag/gunbc/instruments/dag_compile_clean_scope.dag index aca7e7f24a2..287ff788831 100644 --- a/dag/gunbc/instruments/dag_compile_clean_scope.dag +++ b/dag/gunbc/instruments/dag_compile_clean_scope.dag @@ -28,7 +28,16 @@ type CompileCleanScopeDisposition | RequireWholeTree { reason: String } | RefuseShardRosterDuplicate { path: String, reason: String } -data compile_clean_scope_import_closure_reground_note: String = "2026-07-16 reground (channel 2 of operator fork (c), 2026-07-10): compile-clean per-PR selection consumes v2.lens.module_graph.entry_affected_by_touched_paths (import-closure grain, edge-source-agnostic) — the same authority the discovery-corpus entry_file_touched channel regrounded onto. The fn-arrow DependencyView chain (entry_affected_by_dependency_view, gated on corpus_dependency_view_per_pr_substrate_ready / #6239) is no longer a consumer here; it remains in tree as the decl-level candidate only. Two guards keep the whole-tree baseline where import-closure selection cannot answer: (a) any touched path outside the selectable universe (docs/** or *.dag) is a compiler/infra change — whole-tree; (b) any non-docs departed path (deletion/rename-from) is invisible to current-tree adjacency (a dangling import drops the edge, so the broken importer would not be selected) — whole-tree." +// 2026-07-16 reground (channel 2 of operator fork (c), 2026-07-10): compile-clean per-PR selection +// consumes v2.lens.module_graph.entry_affected_by_touched_paths (import-closure grain, +// edge-source-agnostic) — the same authority the discovery-corpus entry_file_touched channel +// regrounded onto. The fn-arrow DependencyView chain (entry_affected_by_dependency_view, gated on +// corpus_dependency_view_per_pr_substrate_ready / #6239) is no longer a consumer here; it remains +// in tree as the decl-level candidate only. Two guards keep the whole-tree baseline where +// import-closure selection cannot answer: (a) any touched path outside the selectable universe +// (docs/** or *.dag) is a compiler/infra change — whole-tree; (b) any non-docs departed path +// (deletion/rename-from) is invisible to current-tree adjacency (a dangling import drops the edge, +// so the broken importer would not be selected) — whole-tree. data compile_clean_docs_universe_path_grain_scaffold: Disposition = Scaffold { dissolves_to: SingleAuthority, diff --git a/dag/gunbc/instruments/dag_compile_clean_seam_transport.dag b/dag/gunbc/instruments/dag_compile_clean_seam_transport.dag index c7c4fd3884c..8450c570ded 100644 --- a/dag/gunbc/instruments/dag_compile_clean_seam_transport.dag +++ b/dag/gunbc/instruments/dag_compile_clean_seam_transport.dag @@ -8,7 +8,11 @@ import extdeps.gunbc import std.types { String, Bool } import tools.dag_compile_clean_transport { dcc_gunbc_path, dcc_gunbc_ready } -data cross_shard_seam_fixture_note: String = "Dedicated fixture pair (2026-07-25 falsifier green): the prior live-corpus seam (dag/std/bit.dag importing Classical from dag/std/logic.dag) went stale when bit.dag stopped importing std.logic — importer shard passed after the Classical rename, so shard_x_reds_with_seam_attribution went false. Fixture is owned by this witness; dissolve-on when a live import edge is re-selected with a RED proving the importer actually depends on the broken symbol." +// Dedicated fixture pair (2026-07-25 falsifier green): the prior live-corpus seam (dag/std/bit.dag +// importing Classical from dag/std/logic.dag) went stale when bit.dag stopped importing std.logic — +// importer shard passed after the Classical rename, so shard_x_reds_with_seam_attribution went +// false. Fixture is owned by this witness; dissolve-on when a live import edge is re-selected with +// a RED proving the importer actually depends on the broken symbol. data cross_shard_seam_importer_entry_path: String = "dag/test/fixture/cross_shard_seam/importer.dag" data cross_shard_seam_importee_entry_path: String = "dag/test/fixture/cross_shard_seam/importee.dag" data cross_shard_seam_break_needle: String = "type SeamMarker = SeamAlpha | SeamBeta" diff --git a/dag/gunbc/instruments/dag_compile_clean_shard_roster.dag b/dag/gunbc/instruments/dag_compile_clean_shard_roster.dag index 059778a90e6..59d2c48c9d0 100644 --- a/dag/gunbc/instruments/dag_compile_clean_shard_roster.dag +++ b/dag/gunbc/instruments/dag_compile_clean_shard_roster.dag @@ -172,7 +172,13 @@ data compile_clean_shard_roster_mechanism_fixture_duplicate_facts: List Bool { match compile_clean_module_shard_roster_from( diff --git a/dag/gunbc/instruments/dag_compile_clean_transport.dag b/dag/gunbc/instruments/dag_compile_clean_transport.dag index be203ffcc5f..42ea6cb8d19 100644 --- a/dag/gunbc/instruments/dag_compile_clean_transport.dag +++ b/dag/gunbc/instruments/dag_compile_clean_transport.dag @@ -11,9 +11,23 @@ import gunbc.output_policy { ExpectFailure, ExpectSuccess } import std.logic { Bool } import tools.host_prelude { witness_bin, witness_bin_ready, witness_bin_release_path } -data dcc_typed_transport_doc: String = "Compile-clean gate transports, typed. Each receipt is a composition of typed service operations (shell.Mktemp.Dir, Filesystem.Write, gunbc.WitnessBin.Run on the gunbc binary, shell.Remove.RecursiveForce) - no bash is serialized or executed. The former ShellProgram builders, their serialize-shape witnesses (shell_serializes_dcc_common_compile_invocation and the marker rows), and the two RealizationDispatch scaffold rows dissolved with the flip: the invocation facts are now carried by the typed args construction itself, so the spelling-drift class those witnesses validated is unwritable (DESIGN 5, construction over validation)." - -data dcc_perturb_class_c_attribute_note: String = "Class C attribution (test-decomposition-wcf-cut.md, session nimble-ibex-483, 2026-07-25): run 30009199696 measured dag_compile_clean_perturb_receipts_holds at 53s; measured head ~141s — both are the SAME shape (four serial gunbc compile invocations each pulling compile_clean_source_roots: dag + src/v2 + src/v1). Per-compile wall grew with corpus size and the namespace-flip compile wall, NOT the #7205 per-entry fixture pipeline (Class F: fixture-shaped + slow by defect). Disposition Class C: mechanism RED arms (optional_skew, unresolved_import) need only a single-module fixture closure (dcc_fixture_only_compile_args); cross_tree_import and dag_shell_primary_precedence need the live-tree pool and ride falsifier_rehomed_bin_wet_entries." +// Compile-clean gate transports, typed. Each receipt is a composition of typed service operations +// (shell.Mktemp.Dir, Filesystem.Write, gunbc.WitnessBin.Run on the gunbc binary, +// shell.Remove.RecursiveForce) - no bash is serialized or executed. The former ShellProgram +// builders, their serialize-shape witnesses (shell_serializes_dcc_common_compile_invocation and the +// marker rows), and the two RealizationDispatch scaffold rows dissolved with the flip: the +// invocation facts are now carried by the typed args construction itself, so the spelling-drift +// class those witnesses validated is unwritable (DESIGN 5, construction over validation). + +// Class C attribution (test-decomposition-wcf-cut.md, session nimble-ibex-483, 2026-07-25): run +// 30009199696 measured dag_compile_clean_perturb_receipts_holds at 53s; measured head ~141s — both +// are the SAME shape (four serial gunbc compile invocations each pulling +// compile_clean_source_roots: dag + src/v2 + src/v1). Per-compile wall grew with corpus size and +// the namespace-flip compile wall, NOT the #7205 per-entry fixture pipeline (Class F: +// fixture-shaped + slow by defect). Disposition Class C: mechanism RED arms (optional_skew, +// unresolved_import) need only a single-module fixture closure (dcc_fixture_only_compile_args); +// cross_tree_import and dag_shell_primary_precedence need the live-tree pool and ride +// falsifier_rehomed_bin_wet_entries. fn dcc_source_root_args() -> List { fold(compile_clean_source_roots(), init: [], f: (acc, r) => concat(acc, ["--source-root", r])) @@ -79,7 +93,13 @@ fn run_clean_tree_compile_typed() -> Bool { } } -data run_clean_tree_compile_receipt_note: String = "The name is unchanged and the return type is not: this used to answer Bool via consume_floor_compile_clean_gate_verdict, which folded five host states into false and the scope disposition's skip into true. The builtin behind it is now spelled install_or_consume_floor_compile_clean_gate_receipt, and that spelling carries a fact the old one hid -- under the executor's lazy-install arm the FIRST call here RUNS THE WHOLE-TREE COMPILE. The predecessor was spelled consume_ and documented as reading the receipt only, so nothing at this call site said that a boolean read could cost a whole-tree compile." +// The name is unchanged and the return type is not: this used to answer Bool via +// consume_floor_compile_clean_gate_verdict, which folded five host states into false and the scope +// disposition's skip into true. The builtin behind it is now spelled +// install_or_consume_floor_compile_clean_gate_receipt, a spelling carrying a fact the old one hid +// -- under the executor's lazy-install arm the FIRST call here RUNS THE WHOLE-TREE COMPILE. The +// predecessor was spelled consume_ and documented as reading the receipt only, so nothing at this +// call site said a boolean read could cost a whole-tree compile. fn run_clean_tree_compile() -> GateReceipt { install_or_consume_floor_compile_clean_gate_receipt() diff --git a/dag/gunbc/instruments/direct_rust_door_transport.dag b/dag/gunbc/instruments/direct_rust_door_transport.dag index 69c863ec1d6..cb7bcdf07d1 100644 --- a/dag/gunbc/instruments/direct_rust_door_transport.dag +++ b/dag/gunbc/instruments/direct_rust_door_transport.dag @@ -20,7 +20,10 @@ import v2.std.text { String } data direct_rust_door_fixture_lane_tag: String = "direct_rust_door_fixture_containment" -data direct_rust_door_transport_note: String = "Lane B wet transport realization (wise-ram-22 PR1 containment): mint_written_synthetic_emitted_artifact_from_fixture_inferred is the sole fixture write authority; FixtureCargoGreen only after cargo.Build.BuildManifest executes. No mint_written_provenanced_rust_artifact and no ProducerEmissionReceipt on this path." +// Lane B wet transport realization (wise-ram-22 PR1 containment): +// mint_written_synthetic_emitted_artifact_from_fixture_inferred is the sole fixture write +// authority; FixtureCargoGreen only after cargo.Build.BuildManifest executes. No +// mint_written_provenanced_rust_artifact and no ProducerEmissionReceipt on this path. fn direct_rust_door_compile_workspace(workspace_dir: String) -> Bool { let build = cargo.Build.BuildManifest( diff --git a/dag/gunbc/instruments/e0599_emitter_decision_census.dag b/dag/gunbc/instruments/e0599_emitter_decision_census.dag index 7583e160e2f..7bb2d0f7fae 100644 --- a/dag/gunbc/instruments/e0599_emitter_decision_census.dag +++ b/dag/gunbc/instruments/e0599_emitter_decision_census.dag @@ -16,11 +16,79 @@ import tools.e0599_probe_census { data e0599_measured_producer: String = "V1SeedEmitter" -data e0599_measured_producer_note: String = "PRODUCER TAG (operator design ruling, 2026-07-29, relayed by calm-badger-682). Every row and count in this census characterizes what the V1 SEED EMITTER (src/v1/05_emit_rust.dag, executed through gunbc) synthesizes when it emits v2 modules. CompilerFixedPoint now recenters on the V2 emitter: the v1 stage0 runtime may temporarily EXECUTE the v2 emitter as .dag, but the v1 emitter stops DECIDING the generated Rust, and emitter authority exits v1 before interpreter execution does. CONSEQUENCE, stated so this census cannot later be misread: the measured sites and occurrences are DIAGNOSTIC COMPARISON ONLY and are NOT CompilerFixedPoint progress, and NOT a v2 sizing authority. COUNTS ARE DELIBERATELY NOT RESTATED HERE (review 44386, codex/codex-default): this note previously restated the brief-sha site and occurrence counts, which went stale the moment #7324 moved the corpus and then contradicted the receipt — a §3 dual representation of a fact whose home is the measurement, not this module. The single authority for every count was the stamped receipt TSV, deleted 2026-08-16 (operator ruling: delete anything not actively derived), which carries its own base_sha and measured_corpus_sha; the audit docs/probes/e0599_phase_b0_emitter_decision_2026-07-29.md reports both the brief-sha and post-merge measurements and attributes the delta. Re-syncing a copy here would only re-arm the same drift. The v2 emitter is a different producer, built from target-model rows consumed by the shared translate/serialize path, so no row here transfers to it by assumption — some may, some are artifacts of seed lowering templates v2 does not have. WHAT IT IS LOAD-BEARING FOR: seed-emitter changes are a finite bootstrap adapter queue, and this census is what makes that queue enumerable — it names which seed lowering decisions exist and exactly where each is made." - -data e0599_emitter_decision_census_note: String = "Single authority for the P-fn Phase B0 emitter-decision census: the typed requirement causes, the v1 lowering operations that produce the Rust operations rustc reports as E0599, the cited external impl bounds those operations land on, and the operation->cause decision. Phase A (#7389) proved the requirement does not originate in a .dag body; this module names where it DOES originate — an emitter lowering decision — at the grain of one row per lowering operation. Realization: docs/probes/e0599_emitter_decision_census.sh (SCAFFOLD) joins these rows against structured rustc diagnostics from the real compilation path. CENSUS VOCABULARY, NOT A MODEL CARRIER: B0 is explicitly forbidden from pre-committing the sharing carrier (the B1 authority question WAS whether the requirement rides RequiredTraitWitness in v2.std.compilers.target_model or a capability-keyed contract that subsumes it; RULED 2026-08-19 (operator, relayed by smart-ram-730): option (b), the capability-keyed contract. RequiredTraitWitness is DELETED at the root and replaced by TargetCapabilityRequirement keyed on TargetCapabilityKey, with per-target eligibility rows authored in extdeps. The ruling rests on DESIGN section 4 and section 3 and NOT on any diagnostic count: the three variant names were Rust trait names in a std interface, so a non-Rust target could only express its sharing requirements by editing the coproduct, which is the N-times-M adapter trap. SIZING CORRECTION recorded with the ruling, stated WITHOUT restating any count, per the COUNTS ARE DELIBERATELY NOT RESTATED HERE clause above (review 44386) which this module already learned the hard way: this census's occurrence figures do NOT size a carrier migration -- they count emitted-Rust occurrences under a lowering-operation partition, a different population from carrier sites. This document additionally contradicts itself on one of those figures; the defect is marked at docs/probes/e0599_phase_b0_emitter_decision_2026-07-29.md section 3.2b, which is the measurement's home. ATTRIBUTION SEPARATELY REFUTED: the claim that this carrier was the root of the diagnostics carried by modules importing target_model is FALSE, and it was refuted structurally rather than by counting -- RequiredTraitWitness was monomorphic (type_param was a Symbol FIELD, not a type parameter), so it could not originate a trait-bound class at all, and an executed measurement recorded in the probe documents agreed. Same file, different defect. The ruling never rested on that attribution and does not weaken with it). These types exist to make the measurement typed and fail-closed, and they dissolve into whatever B1 rules. dissolve-on (two, independent): (1) B1 lands the sharing model on the materialization axis, at which point this module's rows migrate to it and the census becomes a projection of that authority; (2) PARTIALLY FIRED 2026-07-29 when #7399 merged: the Clone-impl row now cites extdeps.languages.rust.derive_contracts im_15_1_0_vector_clone_impl_authority through cited_upstream instead of restating its locator. The im::vector inherent impl Vector (carrying Vector::is_empty and Vector::iter) and impl Index for Vector rows STAY local and carry cited_upstream Absent, because derive_contracts is scoped to DERIVE traits and has no row for either; their narrowed trigger is an extdeps home for non-derive im impl citations, which does not exist yet. This is a declared residue, not an oversight — vivid-swift-837 confirmed (2026-07-29) that the std::Vec keying this census flagged was a real authority mismatch, not a deliberate lane, and #7399 adds exact im 15.1.0 impl authorities there. P-fn sequences AFTER #7399 and must not mint a parallel Vec authority: when it lands, delete e0599_im_vector_inherent_impl / _index_impl / _clone_impl and import the derive_contracts rows instead." - -data e0599_cause_semantics_note: String = "WHAT CloneSharedRequirement MEANS, fixed by operator verdict 2026-07-29: the seed emitter INSERTED a clone at this seam. It does NOT mean the clone can safely be deleted. This census does not execute the per-site ownership verdict - it records which emitter predicate or alternative arm is ASSOCIATED with the site, never whether that site is SoleOwner, at last use, movable, or safely borrowable. The supported claim is that these occurrences are produced at seams owned by the ownership/lowering lane and their actual removability REMAINS TO BE PROVED. EmitterArmPresent means a move arm exists, not that the ownership verdict should select it; NoEmitterArm means no alternative arm exists, and a new lowering may still require Clone under sharing. Consequently the occurrence count is NOT a predicted burn-down and must not be used as one." +// PRODUCER TAG (operator design ruling, 2026-07-29, relayed by calm-badger-682). Every row and +// count in this census characterizes what the V1 SEED EMITTER (src/v1/05_emit_rust.dag, executed +// through gunbc) synthesizes when it emits v2 modules. CompilerFixedPoint now recenters on the V2 +// emitter: the v1 stage0 runtime may temporarily EXECUTE the v2 emitter as .dag, but the v1 emitter +// stops DECIDING the generated Rust, and emitter authority exits v1 before interpreter execution +// does. CONSEQUENCE, stated so this census cannot later be misread: the measured sites and +// occurrences are DIAGNOSTIC COMPARISON ONLY and are NOT CompilerFixedPoint progress, and NOT a v2 +// sizing authority. COUNTS ARE DELIBERATELY NOT RESTATED HERE (review 44386, codex/codex-default): +// this note previously restated the brief-sha site and occurrence counts, which went stale the +// moment #7324 moved the corpus and then contradicted the receipt — a §3 dual representation of a +// fact whose home is the measurement, not this module. The single authority for every count was the +// stamped receipt TSV, deleted 2026-08-16 (operator ruling: delete anything not actively derived), +// which carries its own base_sha and measured_corpus_sha; the audit +// docs/probes/e0599_phase_b0_emitter_decision_2026-07-29.md reports both the brief-sha and +// post-merge measurements and attributes the delta. Re-syncing a copy here would only re-arm the +// same drift. The v2 emitter is a different producer, built from target-model rows consumed by the +// shared translate/serialize path, so no row here transfers to it by assumption — some may, some +// are artifacts of seed lowering templates v2 does not have. WHAT IT IS LOAD-BEARING FOR: +// seed-emitter changes are a finite bootstrap adapter queue, and this census is what makes that +// queue enumerable — it names which seed lowering decisions exist and exactly where each is made. + +// Single authority for the P-fn Phase B0 emitter-decision census: the typed requirement causes, the +// v1 lowering operations that produce the Rust operations rustc reports as E0599, the cited +// external impl bounds those operations land on, and the operation->cause decision. Phase A (#7389) +// proved the requirement does not originate in a .dag body; this module names where it DOES +// originate — an emitter lowering decision — at the grain of one row per lowering operation. +// Realization: docs/probes/e0599_emitter_decision_census.sh (SCAFFOLD) joins these rows against +// structured rustc diagnostics from the real compilation path. CENSUS VOCABULARY, NOT A MODEL +// CARRIER: B0 is explicitly forbidden from pre-committing the sharing carrier (the B1 authority +// question WAS whether the requirement rides RequiredTraitWitness in v2.std.compilers.target_model +// or a capability-keyed contract that subsumes it; RULED 2026-08-19 (operator, relayed by +// smart-ram-730): option (b), the capability-keyed contract. RequiredTraitWitness is DELETED at the +// root and replaced by TargetCapabilityRequirement keyed on TargetCapabilityKey, with per-target +// eligibility rows authored in extdeps. The ruling rests on DESIGN section 4 and section 3 and NOT +// on any diagnostic count: the three variant names were Rust trait names in a std interface, so a +// non-Rust target could only express its sharing requirements by editing the coproduct, which is +// the N-times-M adapter trap. SIZING CORRECTION recorded with the ruling, stated WITHOUT restating +// any count, per the COUNTS ARE DELIBERATELY NOT RESTATED HERE clause above (review 44386) which +// this module already learned the hard way: this census's occurrence figures do NOT size a carrier +// migration -- they count emitted-Rust occurrences under a lowering-operation partition, a +// different population from carrier sites. This document additionally contradicts itself on one of +// those figures; the defect is marked at docs/probes/e0599_phase_b0_emitter_decision_2026-07-29.md +// section 3.2b, which is the measurement's home. ATTRIBUTION SEPARATELY REFUTED: the claim that +// this carrier was the root of the diagnostics carried by modules importing target_model is FALSE, +// and it was refuted structurally rather than by counting -- RequiredTraitWitness was monomorphic +// (type_param was a Symbol FIELD, not a type parameter), so it could not originate a trait-bound +// class at all, and an executed measurement recorded in the probe documents agreed. Same file, +// different defect. The ruling never rested on that attribution and does not weaken with it). These +// types exist to make the measurement typed and fail-closed, and they dissolve into whatever B1 +// rules. dissolve-on (two, independent): (1) B1 lands the sharing model on the materialization +// axis, at which point this module's rows migrate to it and the census becomes a projection of that +// authority; (2) PARTIALLY FIRED 2026-07-29 when #7399 merged: the Clone-impl row now cites +// extdeps.languages.rust.derive_contracts im_15_1_0_vector_clone_impl_authority through +// cited_upstream instead of restating its locator. The im::vector inherent impl Vector +// (carrying Vector::is_empty and Vector::iter) and impl Index for Vector rows +// STAY local and carry cited_upstream Absent, because derive_contracts is scoped to DERIVE traits +// and has no row for either; their narrowed trigger is an extdeps home for non-derive im impl +// citations, which does not exist yet. This is a declared residue, not an oversight — +// vivid-swift-837 confirmed (2026-07-29) that the std::Vec keying this census flagged was a real +// authority mismatch, not a deliberate lane, and #7399 adds exact im 15.1.0 impl authorities there. +// P-fn sequences AFTER #7399 and must not mint a parallel Vec authority: when it lands, delete +// e0599_im_vector_inherent_impl / _index_impl / _clone_impl and import the derive_contracts rows +// instead. + +// WHAT CloneSharedRequirement MEANS, fixed by operator verdict 2026-07-29: the seed emitter +// INSERTED a clone at this seam. It does NOT mean the clone can safely be deleted. This census does +// not execute the per-site ownership verdict - it records which emitter predicate or alternative +// arm is ASSOCIATED with the site, never whether that site is SoleOwner, at last use, movable, or +// safely borrowable. The supported claim is that these occurrences are produced at seams owned by +// the ownership/lowering lane and their actual removability REMAINS TO BE PROVED. EmitterArmPresent +// means a move arm exists, not that the ownership verdict should select it; NoEmitterArm means no +// alternative arm exists, and a new lowering may still require Clone under sharing. Consequently +// the occurrence count is NOT a predicted burn-down and must not be used as one. type E0599RequirementCause = TargetApiRequirement diff --git a/dag/gunbc/instruments/emit_host_gate.dag b/dag/gunbc/instruments/emit_host_gate.dag index aa342f5793f..38878691a53 100644 --- a/dag/gunbc/instruments/emit_host_gate.dag +++ b/dag/gunbc/instruments/emit_host_gate.dag @@ -16,7 +16,18 @@ import tools.emit_host_transport { run_c_smoke } -data per_smoke_verdict_note: String = "The gate previously folded five smoke Bools into one refusal whose reason named no smoke — and the claim wrapper (emit_host_gate_passes = exit_ok(run_spec_gate)) discards the reason entirely, so a floor failure surfaced as bare Bool(false) and locating the failing language took log archaeology (receipt: the 2026-07-13 srv3 reprovisioning reds — five runs of forensics to name the ts/go/node toolchain gap, operator-signed fix same day). The failure arm now narrates the per-smoke verdict line through shell_printf_line (the sanctioned printf-with-escaping builder in gunbc.shell_bash_runner — never naive echo concat), visible in the claim executor's shell narration adjacent to the FAIL row, AND carries it as the refusal reason for the ProcessExit path. Narration is failure-arm-only (the floor log stays quiet on green, matching the typecheck-attribution threshold precedent); a refused narration is disclosed inside the reason, never silently dropped. The narration op itself is a marked scaffold — see emit_host_verdict_narration_dissolution_trigger." +// The gate previously folded five smoke Bools into one refusal whose reason named no smoke — and +// the claim wrapper (emit_host_gate_passes = exit_ok(run_spec_gate)) discards the reason entirely, +// so a floor failure surfaced as bare Bool(false) and locating the failing language took log +// archaeology (receipt: the 2026-07-13 srv3 reprovisioning reds — five runs of forensics to name +// the ts/go/node toolchain gap, operator-signed fix same day). The failure arm now narrates the +// per-smoke verdict line through shell_printf_line (the sanctioned printf-with-escaping builder in +// gunbc.shell_bash_runner — never naive echo concat), visible in the claim executor's shell +// narration adjacent to the FAIL row, AND carries it as the refusal reason for the ProcessExit +// path. Narration is failure-arm-only (the floor log stays quiet on green, matching the +// typecheck-attribution threshold precedent); a refused narration is disclosed inside the reason, +// never silently dropped. The narration op itself is a marked scaffold — see +// emit_host_verdict_narration_dissolution_trigger. data emit_host_verdict_narration_scaffold: Disposition = Scaffold { dissolves_to: SingleAuthority, diff --git a/dag/gunbc/instruments/emit_host_transport.dag b/dag/gunbc/instruments/emit_host_transport.dag index 7c459b3c9aa..c5c86ef7985 100644 --- a/dag/gunbc/instruments/emit_host_transport.dag +++ b/dag/gunbc/instruments/emit_host_transport.dag @@ -44,7 +44,18 @@ import gunbc.retained_shell_script { retained_foreign } data fixture_cargo_toml: String = "[package]\nname = \"emit_host_fixture\"\nversion = \"0.0.0\"\nedition = \"2021\"\n\n[[bin]]\nname = \"fixture\"\npath = \"src/main.rs\"" -data typed_smoke_doc: String = "run_rust_smoke and run_python_smoke are runtime-present sites: gunbc invokes each step, so per the shell-emission-model rule they are typed service operations (shell.Mktemp/Mkdir/Remove, Filesystem.Write for the heredoc bodies, cargo.Build.BuildManifest, python.Interpreter.RunFile, gunbc.WitnessBin.Run), never serialized bash. The former wc -c byte-count check strengthened to CONTENT equality: the fixtures emit NUL bytes precisely because printable output could pass a count check with wrong content; the expected string is built in-substrate from the same stdout_byte_count authority (repeat_string of from_code_point(0)), and NULs survive the transport's trailing-whitespace trim. Cleanup runs unconditionally (the old set -e script leaked the tempdir on mid-script failure). run_go_smoke, run_ts_smoke, and run_node_http_serve_smoke provision missing toolchains under WORKDIR before the smoke body (go.dev tarball; nodejs.org official linux tarball — npx ships in the same bin/) so self-hosted runners without a fleet-wide node install still pass emit_host_gate_passes." +// run_rust_smoke and run_python_smoke are runtime-present sites: gunbc invokes each step, so per +// the shell-emission-model rule they are typed service operations (shell.Mktemp/Mkdir/Remove, +// Filesystem.Write for the heredoc bodies, cargo.Build.BuildManifest, python.Interpreter.RunFile, +// gunbc.WitnessBin.Run), never serialized bash. The former wc -c byte-count check strengthened to +// CONTENT equality: the fixtures emit NUL bytes precisely because printable output could pass a +// count check with wrong content; the expected string is built in-substrate from the same +// stdout_byte_count authority (repeat_string of from_code_point(0)), and NULs survive the +// transport's trailing-whitespace trim. Cleanup runs unconditionally (the old set -e script leaked +// the tempdir on mid-script failure). run_go_smoke, run_ts_smoke, and run_node_http_serve_smoke +// provision missing toolchains under WORKDIR before the smoke body (go.dev tarball; nodejs.org +// official linux tarball — npx ships in the same bin/) so self-hosted runners without a fleet-wide +// node install still pass emit_host_gate_passes. data emit_host_node_bootstrap_shell_dissolution_trigger: DissolutionCondition = unbound_dissolution(description: "🟡 dissolve-on: emit_host_node_bootstrap_script — concat-interpolated bash fragment (curl/tar nodejs.org linux tarball into WORKDIR/nodeboot) spliced into run_ts_smoke and run_node_http_serve_smoke via toolchain_provision_shell_exec; transport scaffold parallel to run_go_smoke's inline go.dev tarball provision. DISSOLVES WHEN emit_host TS/node smokes route through typed extdeps ops like run_rust_smoke/run_python_smoke (shell.Mktemp/Mkdir, modeled curl/tar or node runtime provision row) and this string carrier retires from dag/gunbc/instruments/emit_host_transport.dag.") @@ -87,37 +98,37 @@ fn run_python_smoke() -> Bool { // WHY THE OCTET COMPARISON LEFT THE SHELL SCRIPT. // -// These three smokes used to end in a shell `test` of the byte count against the expected one (and, for the http probe, a bare -// `process.exit(1)`), so the script's exit status was the only thing that crossed into .dag. That -// status is true for a missing toolchain, a failed download, a compile error AND a fixture that -// emitted the wrong number of octets -- four states with four different owners, aliased below the -// substrate where no carrier can reach them. A typed verdict at the .dag layer cannot separate what -// the transport already collapsed. +// These three smokes used to end in a shell `test` of the byte count against the expected one (and, +// for the http probe, a bare `process.exit(1)`), so the script's exit status was the only thing +// that crossed into .dag. That status is true for a missing toolchain, a failed download, a compile +// error AND a fixture emitting the wrong number of octets — four states with four owners, aliased +// below the substrate where no carrier can reach them. A typed verdict at the .dag layer cannot +// separate what the transport already collapsed. // -// So the script now REPORTS rather than adjudicates: it prints the byte count (or the probe's status -// marker) and exits, and the comparison is performed here against the same in-substrate authority the -// fixtures are built from. The script's own exit status keeps exactly one meaning -- the harness -// could not pose the question -- and the comparison keeps the other -- the subject answered wrongly. -// Both are still folded into one Bool at the return type; that fold is the class-B migration and is -// deliberately not done here, because splitting the two states has to happen before a carrier can -// name them, and this change is the half that no carrier shape depends on. +// So the script now REPORTS rather than adjudicates: it prints the byte count (or the probe's +// status marker) and exits, and the comparison is performed here against the same in-substrate +// authority the fixtures are built from. The script's exit status keeps exactly one meaning — the +// harness could not pose the question — and the comparison the other — the subject answered +// wrongly. Both still fold into one Bool at the return type; that fold is the class-B migration, +// deliberately not done here: splitting the two states must precede a carrier naming them, and this +// change is the half no carrier shape depends on. // THE DECISION, TAKEN OVER SCALARS SO A HERMETIC CONTROL CAN AUTHOR ITS INPUTS. // // These take the reported stdout and the harness bit as plain values rather than the run record, -// because shell.Exec.Run returns an anonymous output record no fixture can construct. Passing scalars -// is what makes the cause-swap control in test.claim.emit_host_reported_comparison_cause_swap_test -// authorable with no host effect at all: the two failure causes it distinguishes are supplied -// directly, so the control measures THE PREDICATE rather than the toolchain. +// because shell.Exec.Run returns an anonymous output record no fixture can construct. Scalars make +// the cause-swap control in test.claim.emit_host_reported_comparison_cause_swap_test authorable +// with no host effect: the two failure causes it distinguishes are supplied directly, so the +// control measures THE PREDICATE rather than the toolchain. fn emit_host_reported_octets_match(reported_stdout: String, expected_text: String) -> Bool { reported_stdout == expected_text } // The fold that is still a fold. harness_ok false means the script could not pose the question; -// octets_match false means the fixture answered with the wrong number of octets. Both still collapse -// into one Bool HERE, and that is the class-B migration's job, not this change's -- what this change -// establishes is that the two are separately computable at all, which they were not while the -// comparison lived inside the script. +// octets_match false means the fixture answered with the wrong number of octets. Both still +// collapse into one Bool HERE — the class-B migration's job, not this change's, which establishes +// only that the two are separately computable, as they were not while the comparison lived inside +// the script. fn emit_host_smoke_holds(harness_ok: Bool, reported_stdout: String, expected_text: String) -> Bool { harness_ok && emit_host_reported_octets_match(reported_stdout: reported_stdout, expected_text: expected_text) } @@ -235,7 +246,10 @@ fn c_emit_host_inject_fault_refuses_holds() -> Bool { } } -data c_emit_host_smoke_red_doc: String = "Discriminating control for run_c_smoke (wet — exercised by emit_host_gate_passes, not hermetic discovery): the inject-fault arm deliberately calls add(2,2) while the green oracle expects add(2,3)=5 in the fixed-width stdout codec — a regression that accepts the fault path or drops the RED half goes RED here." +// Discriminating control for run_c_smoke (wet — exercised by emit_host_gate_passes, not hermetic +// discovery): the inject-fault arm deliberately calls add(2,2) while the green oracle expects +// add(2,3)=5 in the fixed-width stdout codec — a regression that accepts the fault path or drops +// the RED half goes RED here. fn run_c_smoke() -> Bool { c_emit_host_green_holds() && c_emit_host_inject_fault_refuses_holds() diff --git a/dag/gunbc/instruments/extdeps_external_authority_gate.dag b/dag/gunbc/instruments/extdeps_external_authority_gate.dag index f0f889c0bd6..21c0773fb56 100644 --- a/dag/gunbc/instruments/extdeps_external_authority_gate.dag +++ b/dag/gunbc/instruments/extdeps_external_authority_gate.dag @@ -4,7 +4,15 @@ import std.process { ProcessExit, ExitSuccess, exit_failure } import tools.cheap_gate_pool { claims_covered_by_cheap_pool_chunks } import tools.extdeps_external_authority_transport { extdeps_external_authority_claims } -data extdeps_enrollment_wall_note: String = "ENROLLMENT WALL (CI floor endgame D1, 2026-07-23): the extdeps external-authority claims EXECUTE in the batch-0 pooled claim_batch child (tools.cheap_gate_pool — one child per batch, per-claim verdicts named in its PASS/FAIL lines); this gate is the pure construction wall that the concern's every claim survives the K-chunking transform into some pooled child's roster (chunk-transform totality — the pool itself is the union of the concern lists by construction, so membership alone would be vacuous; the chunker between pool and children is the code that could drop a claim, and that drop reds here before the pool even runs). Concern authority stays tools.extdeps_external_authority_transport.extdeps_external_authority_claims; execution authority is the pool." +// ENROLLMENT WALL (CI floor endgame D1, 2026-07-23): the extdeps external-authority claims EXECUTE +// in the batch-0 pooled claim_batch child (tools.cheap_gate_pool — one child per batch, per-claim +// verdicts named in its PASS/FAIL lines); this gate is the pure construction wall that the +// concern's every claim survives the K-chunking transform into some pooled child's roster +// (chunk-transform totality — the pool itself is the union of the concern lists by construction, so +// membership alone would be vacuous; the chunker between pool and children is the code that could +// drop a claim, and that drop reds here before the pool even runs). Concern authority stays +// tools.extdeps_external_authority_transport.extdeps_external_authority_claims; execution authority +// is the pool. func run_extdeps_external_authority_gate_body() -> ProcessExit { return if claims_covered_by_cheap_pool_chunks(claims: extdeps_external_authority_claims()) { diff --git a/dag/gunbc/instruments/extdeps_external_authority_transport.dag b/dag/gunbc/instruments/extdeps_external_authority_transport.dag index 1534547beea..e4aeb23d6b6 100644 --- a/dag/gunbc/instruments/extdeps_external_authority_transport.dag +++ b/dag/gunbc/instruments/extdeps_external_authority_transport.dag @@ -2,7 +2,20 @@ module tools.extdeps_external_authority_transport import tools.host_prelude { ClaimRun } -data transport_note: String = "Witness transport for the extdeps external-authority gate, rewired 2026-07-22 onto the mandatory-tag dissolution: the verdict authority is v2.lens.mandatory_tag (compile-gate fold, red/green-controlled at the v2 parse grain in v2.lens.mandatory_tag.gate_witness_test, discovery-enrolled); this transport runs the CORPUS-cadence rows - the retained host anchor projection (v2.lens.mandatory_tag.corpus_scan, a declared shrunken scaffold whose dissolution trigger is witness realization of the parse-grain corpus witness) plus the uri modeling witnesses. The old per-module lens_unit rows retired with v2.lens.extdeps_external_authority; their grain is covered by the gate witnesses. Pooled 2026-07-23 (the cold-child class, attribution doc section 9): the three per-concern fns' calls collapse into ONE claim_batch child; SUPERSEDED same day by the batch-0 pool (CI floor endgame D1, tools.cheap_gate_pool — one child for the whole cheap batch, so this concern's claims no longer pay their own index build). This module is the concern authority only (the claim list below); the gate module beside it is the enrollment wall. The former extdeps_external_authority_source_roots datum was a nickname of witness_layer_roots and dissolved into it." +// Witness transport for the extdeps external-authority gate, rewired 2026-07-22 onto the +// mandatory-tag dissolution: the verdict authority is v2.lens.mandatory_tag (compile-gate fold, +// red/green-controlled at the v2 parse grain in v2.lens.mandatory_tag.gate_witness_test, +// discovery-enrolled); this transport runs the CORPUS-cadence rows - the retained host anchor +// projection (v2.lens.mandatory_tag.corpus_scan, a declared shrunken scaffold whose dissolution +// trigger is witness realization of the parse-grain corpus witness) plus the uri modeling +// witnesses. The old per-module lens_unit rows retired with v2.lens.extdeps_external_authority; +// their grain is covered by the gate witnesses. Pooled 2026-07-23 (the cold-child class, +// attribution doc section 9): the three per-concern fns' calls collapse into ONE claim_batch child; +// SUPERSEDED same day by the batch-0 pool (CI floor endgame D1, tools.cheap_gate_pool — one child +// for the whole cheap batch, so this concern's claims no longer pay their own index build). This +// module is the concern authority only (the claim list below); the gate module beside it is the +// enrollment wall. The former extdeps_external_authority_source_roots datum was a nickname of +// witness_layer_roots and dissolved into it. data uri_witness_entry: String = "dag/test/claim/uri_witness_test.dag" diff --git a/dag/gunbc/instruments/extdeps_scope_placement_gate.dag b/dag/gunbc/instruments/extdeps_scope_placement_gate.dag index 9a51f9d9a19..28b568d089e 100644 --- a/dag/gunbc/instruments/extdeps_scope_placement_gate.dag +++ b/dag/gunbc/instruments/extdeps_scope_placement_gate.dag @@ -29,7 +29,27 @@ import gunbc.extdeps_scope_frontier { // corrected. The durable rule this is an instance of is stated in full in // tools.prose_row_introduction_gate: the .dag call graph is not the execution graph, and "runs in // CI" is decided by .github/workflows/ and the fold it invokes, never by who calls whom in .dag. -data extdeps_scope_placement_gate_note: String = "Server-side per-PR wall for the ExternalModelScope disk-to-roster direction (codex review 46281; modeled on the publication placement gate pattern, whose own gate was deleted by operator ruling in #7591 — this gate is independent and remains). It collects Added/Copied/Renamed paths with git.Core.DiffNameStatus at TWO bases, one per question (scope_placement_base_split_note), and refuses when (a) a dag/extdeps .dag file added by THIS CHANGE — the merge base origin/main...HEAD — is in neither scope_carrier_paths nor scope_machinery_exempt_paths (scope_placement_refused_paths — the frozen manifest is deliberately NOT an admission channel for a new file), (b) any frozen-manifest row names a file added after the freeze anchor, read over the whole post-freeze population at legacy_manifest_freeze_sha..HEAD (manifest_freeze_holds — the smuggling channel), or (c) the change itself ADDS a line to the manifest (manifest_line_observation folded to a verdict by manifest_remove_only_exit, over git.Core.DiffUnified0 against origin/main merge-base — the remove-only lifecycle made mechanical, codex review 46378; the sole admitted arm is the manifest file absent at the merge base, i.e. the PR that first introduces it, unforgeable once it exists on main). Diff failure and parse truncation are refusals, never skips. Merge-base grain on the membership arm is what makes the refusal attributable: only paths the change itself introduces are judged there, so this gate neither retroactively reds the frozen manifest population nor blocks a lane for a file some other lane landed. The wet live-cover witness (dag/test/claim/external_model_scope_live_cover_witness_test.dag) remains the whole-population re-verification. This gate dissolves with the whole storage-grain frontier into the v2.lens.mandatory_tag region promotion (compile-grain, module-name grain)." +// Server-side per-PR wall for the ExternalModelScope disk-to-roster direction (codex review 46281; +// modeled on the publication placement gate pattern, whose own gate was deleted by operator ruling +// in #7591 — this gate is independent and remains). It collects Added/Copied/Renamed paths with +// git.Core.DiffNameStatus at TWO bases, one per question (scope_placement_base_split_note), and +// refuses when (a) a dag/extdeps .dag file added by THIS CHANGE — the merge base origin/main...HEAD +// — is in neither scope_carrier_paths nor scope_machinery_exempt_paths +// (scope_placement_refused_paths — the frozen manifest is deliberately NOT an admission channel for +// a new file), (b) any frozen-manifest row names a file added after the freeze anchor, read over +// the whole post-freeze population at legacy_manifest_freeze_sha..HEAD (manifest_freeze_holds — the +// smuggling channel), or (c) the change itself ADDS a line to the manifest +// (manifest_line_observation folded to a verdict by manifest_remove_only_exit, over +// git.Core.DiffUnified0 against origin/main merge-base — the remove-only lifecycle made mechanical, +// codex review 46378; the sole admitted arm is the manifest file absent at the merge base, i.e. the +// PR that first introduces it, unforgeable once it exists on main). Diff failure and parse +// truncation are refusals, never skips. Merge-base grain on the membership arm is what makes the +// refusal attributable: only paths the change itself introduces are judged there, so this gate +// neither retroactively reds the frozen manifest population nor blocks a lane for a file some other +// lane landed. The wet live-cover witness +// (dag/test/claim/external_model_scope_live_cover_witness_test.dag) remains the whole-population +// re-verification. This gate dissolves with the whole storage-grain frontier into the +// v2.lens.mandatory_tag region promotion (compile-grain, module-name grain). // ONE fold over a name-status diff, read at two different bases (DESIGN section 2 horizontal: one concept, every breadth). The base is the caller's parameter because WHICH base answers WHICH question is a workflow fact, not a property of the git operation (DESIGN section 3: business policy must not fuse into the extdeps interface). fn diff_added_paths_observation(success: Bool, raw: String) -> DiffAddedObservation { @@ -71,7 +91,9 @@ fn scope_placement_change_added_paths() -> DiffAddedObservation { diff_added_paths_observation(success: diff.success, raw: diff.raw) } -data manifest_remove_only_exit_note: String = "THE one verdict surface over ManifestLineObservation (codex review 46387: a parallel Bool predicate beside this match was two verdict authorities — deleted; the hermetic witness controls and the wet gate both route through THIS fn)." +// THE one verdict surface over ManifestLineObservation (codex review 46387: a parallel Bool +// predicate beside this match was two verdict authorities — deleted; the hermetic witness controls +// and the wet gate both route through THIS fn). fn manifest_remove_only_exit(observation: ManifestLineObservation) -> ProcessExit { match observation { @@ -157,7 +179,22 @@ fn scope_placement_freeze_verdict(observation: DiffAddedObservation) -> ProcessE } } -data language_module_home_arm_note: String = "The language-module homing arm (operator requirement, 2026-08-05). It reuses the SAME merge-base added-path observation the membership arm already reads — one diff, two questions — rather than taking a second reading of git, because which base answers which question is already settled above and a second observation would be a second authority for one fact. It refuses on the same DiffObserveFailed and DiffParseTruncated arms for the same reason: an unreadable diff must never report a clean tree. WHAT THIS ARM DOES NOT DECIDE, stated here because a refusal message is where a reader forms their model of the wall (cursor review 48573): it checks that an added path sits inside SOME declared target directory, never that it sits under the home of the target the module actually declares — the observation carries paths and no subject. An earlier revision of this message said a module 'must live under its target's declared canonical_prefix', which promised the relational check the predicate does not perform, and it also named an unregistered-target-directory roster that no longer exists. WHY IT LIVES IN THIS GATE rather than beside the rows it enforces: gunbc.language_module_home holds the decision functions and this file holds the per-PR execution, exactly the split gunbc.extdeps_scope_frontier and this gate already use. Without this arm the homing rosters would have been a predicate nothing calls — the coverage-by-illusion tier DESIGN section 6 names, where an inert lens is itself a lie." +// The language-module homing arm (operator requirement, 2026-08-05). It reuses the SAME merge-base +// added-path observation the membership arm already reads — one diff, two questions — rather than +// taking a second reading of git, because which base answers which question is already settled +// above and a second observation would be a second authority for one fact. It refuses on the same +// DiffObserveFailed and DiffParseTruncated arms for the same reason: an unreadable diff must never +// report a clean tree. WHAT THIS ARM DOES NOT DECIDE, stated here because a refusal message is +// where a reader forms their model of the wall (cursor review 48573): it checks that an added path +// sits inside SOME declared target directory, never that it sits under the home of the target the +// module actually declares — the observation carries paths and no subject. An earlier revision of +// this message said a module 'must live under its target's declared canonical_prefix', which +// promised the relational check the predicate does not perform, and it also named an +// unregistered-target-directory roster that no longer exists. WHY IT LIVES IN THIS GATE rather than +// beside the rows it enforces: gunbc.language_module_home holds the decision functions and this +// file holds the per-PR execution, exactly the split gunbc.extdeps_scope_frontier and this gate +// already use. Without this arm the homing rosters would have been a predicate nothing calls — the +// coverage-by-illusion tier DESIGN section 6 names, where an inert lens is itself a lie. fn language_module_home_verdict(observation: DiffAddedObservation) -> ProcessExit { match observation { @@ -213,7 +250,13 @@ func run_extdeps_scope_placement_gate_body() -> ProcessExit { ) } -data scope_placement_failure_receipt_note: String = "The loudness half (gunbc#7644 diagnosis): the floor witness collapses this gate's ProcessExit to Bool via tools.ci_gates.exit_ok, so ten consecutive main runs reported the bare string 'returned Bool(false)' and named neither the refusing arm nor the offending path — the located diagnostic scope_placement_gate_verdict already builds was discarded one frame above it. This projects the SAME verdict (one authority, never a second decision path) into the claim_executor failure-receipt companion channel, the mechanism v2.std.native_agreement established for exactly this class." +// The loudness half (gunbc#7644 diagnosis): the floor witness collapses this gate's ProcessExit to +// Bool via tools.ci_gates.exit_ok, so ten consecutive main runs reported the bare string 'returned +// Bool(false)' and named neither the refusing arm nor the offending path — the located diagnostic +// scope_placement_gate_verdict already builds was discarded one frame above it. This projects the +// SAME verdict (one authority, never a second decision path) into the claim_executor +// failure-receipt companion channel, the mechanism v2.std.native_agreement established for exactly +// this class. fn scope_placement_failure_reason(exit: ProcessExit) -> String { match exit { diff --git a/dag/gunbc/instruments/fabric_control_plane_live_probe.dag b/dag/gunbc/instruments/fabric_control_plane_live_probe.dag index 805c80530be..1fb7a3394d7 100644 --- a/dag/gunbc/instruments/fabric_control_plane_live_probe.dag +++ b/dag/gunbc/instruments/fabric_control_plane_live_probe.dag @@ -40,21 +40,19 @@ import gunbc.fabric_control_plane { bind_fabric_cell_candidate, } -// The live receipt for the EFFECTFUL half of the broker, which the hermetic -// witness cannot reach. An entry point rather than a shell script, for the -// reason DESIGN gives: if a measurement is worth re-deriving it is worth an -// entry point. +// The live receipt for the EFFECTFUL half of the broker, which the hermetic witness cannot +// reach. An entry point rather than a shell script, for the reason DESIGN gives: if a +// measurement is worth re-deriving it is worth an entry point. // // gunbc run --source-root dag --source-root src/v2 \ // --entry dag/gunbc/instruments/fabric_control_plane_live_probe.dag \ // --function cp_live_reservation_is_exclusive --arg root= // // THE SECOND FUNCTION'S ASSERTION IS NOT ITS RETURN VALUE. Run -// cp_live_nothing_admissible_touches_no_store against a FRESH empty root and -// then list that directory: it must still be empty. A broker that reserved a -// cell when selection admitted nothing would be fabricating a decision the -// market refused to make, and it would still return a plausible-looking -// outcome -- the only thing that catches it is that no file appeared. +// cp_live_nothing_admissible_touches_no_store against a FRESH empty root and then list that +// directory: it must still be empty. A broker that reserved a cell when selection admitted +// nothing would fabricate a decision the market refused to make, and would still return a +// plausible-looking outcome -- only the absence of a file catches it. fn cp_probe_offer_id(key: OfferKey) -> FabricIdentity { FabricIdentity { principal: "gunbc", key: key } @@ -68,24 +66,21 @@ fn cp_probe_slot() -> RunnerSlotIdentity { RunnerSlotIdentity { host: "srv3", slot_index: 6 } } -// NOTHING OFFERED AND EVERYTHING REJECTED ARE DIFFERENT STATES WITH DIFFERENT -// REMEDIES, so NoCellAdmissible splits into 50 and 51 rather than one code. -// `considered` is empty only when the roster was empty -- a binding that -// refused -- and populated when real offers failed a screen. The first is a -// broken fixture, the second is the market answering. Read once as a single -// code, a probe offer declaring 1 thread against the floor's required 8 looked -// exactly like a correct market refusal, and the fixture defect was invisible. +// NOTHING OFFERED AND EVERYTHING REJECTED ARE DIFFERENT STATES WITH DIFFERENT REMEDIES, so +// NoCellAdmissible splits into 50 and 51 rather than one code. `considered` is empty only when +// the roster was empty -- a binding that refused -- and populated when real offers failed a +// screen. The first is a broken fixture, the second is the market answering. Read as a single +// code, a probe offer declaring 1 thread against the floor's required 8 looked exactly like a +// correct market refusal, and the fixture defect was invisible. // -// A THIRD STATE JOINS THAT SPLIT for the same reason it exists. 52 is "the -// roster was not empty and admission removed every member across the -// capacity-class line" -- read as 50 it would look like nothing was offered, -// which sends a reader to find capacity when the capacity was there and was -// correctly refused. It is checked FIRST because a refused roster can leave the -// market with nothing to consider, so 50 and 52 are both true of that state and -// only 52 says which happened. +// A THIRD STATE JOINS THAT SPLIT for the same reason. 52 is "the roster was not empty and +// admission removed every member across the capacity-class line" -- read as 50 it would look +// like nothing was offered, sending a reader to find capacity that was there and correctly +// refused. It is checked FIRST because a refused roster can leave the market with nothing to +// consider, so 50 and 52 are both true of that state and only 52 says which happened. // -// A commit returns 100 + its generation so it can never be confused with a -// refusal code; the four refusals stay distinct because their remedies differ. +// A commit returns 100 + its generation so it can never be confused with a refusal code; the +// four refusals stay distinct because their remedies differ. fn cp_probe_code(r: CellReservation) -> Int { match r { CellReserved { offer: _, slot_key: _, generation: g, account: _ } => 100 + g @@ -174,13 +169,12 @@ fn cp_live_nothing_admissible_touches_no_store(root: NonEmptyStr) -> Int { ) } -// THE END-TO-END ROW, AND IT IS THE ONE THE HERMETIC ROWS STRUCTURALLY CANNOT -// REACH. The mint row proves a mismatched pair refuses; the exclusive row proves -// the store arbitrates. Neither proves the BROKER carries the market's choice -// into the store -- that is the join, and the join is exactly what stayed green -// through the defect this arrow repairs. So this row runs the whole path with a -// real roster and then reads the FILESYSTEM: the slot that appears must be the -// cell whose offer won, spelled by its canonical name and nothing else. +// THE END-TO-END ROW, AND IT IS THE ONE THE HERMETIC ROWS STRUCTURALLY CANNOT REACH. The mint row +// proves a mismatched pair refuses; the exclusive row proves the store arbitrates. Neither proves +// the BROKER carries the market's choice into the store -- that join is exactly what stayed green +// through the defect this arrow repairs. So this row runs the whole path with a real roster and +// then reads the FILESYSTEM: the slot that appears must be the cell whose offer won, spelled by +// its canonical name and nothing else. fn cp_probe_offer_for(executor: NonEmptyStr) -> SupplierOffer { SupplierOffer { id: cp_probe_offer_id(key: "offer-1"), @@ -251,15 +245,14 @@ fn cp_live_broker_reserves_the_cell_the_market_chose(root: NonEmptyStr) -> Int { } // THE RELEASE COMMIT IS THE ONE ARM NO HERMETIC ROW CAN REACH. Every refusal in -// release_reserved_cell is decided from the slot observation, which is a value, -// so the witness file covers all of them. The successful free-write is a host -// effect, refused under the floor's hermetic envelope, so a live run is its -// only possible evidence -- the same split the reserve path carries, and the -// reason this file exists. +// release_reserved_cell is decided from the slot observation, a value, so the witness file covers +// all of them. The successful free-write is a host effect, refused under the floor's hermetic +// envelope, so a live run is its only possible evidence -- the same split the reserve path +// carries, and the reason this file exists. // -// The codes are disjoint from cp_probe_code's so a reader cannot confuse a -// release outcome with a reservation one, and each refusal keeps its own number -// because their remedies differ exactly as the reservation's do. +// The codes are disjoint from cp_probe_code's so a release outcome cannot be confused with a +// reservation one, and each refusal keeps its own number because their remedies differ exactly +// as the reservation's do. fn cp_probe_release_code(r: CellHoldEnd) -> Int { match r { CellHoldEnded { slot_key: _, generation: g, account: _ } => 200 + g diff --git a/dag/gunbc/instruments/floor_effect_gate_witness.dag b/dag/gunbc/instruments/floor_effect_gate_witness.dag index 933b9d137e1..32e7d9b5a44 100644 --- a/dag/gunbc/instruments/floor_effect_gate_witness.dag +++ b/dag/gunbc/instruments/floor_effect_gate_witness.dag @@ -11,7 +11,30 @@ import tools.prose_row_introduction_gate { prose_row_gate_failure_receipt } import tools.dag_compile_clean_gate { dag_compile_clean_failure_receipt } import tools.generated_artifact_gate { generated_artifact_drift_failure_receipt } -data floor_gate_failure_receipt_note: String = "claim_executor derives a failure-receipt companion from a Bool witness's own name and evaluates it in THIS entry's context, so the companion must be declared here beside the witness it explains — not only in the gate module that builds the reason. Naming is mechanical: _passes or _holds yields _failure_receipt, delegated to gunbc.test_module_hygiene.failure_receipt_companion (the single authority; cli_run::failure_receipt_companion routes through the same module). The scope_placement_gate_loudness_seed_probe_* pair — a hermetic always-false _passes witness plus its failure_receipt companion — USED to live here solely so dag/test/claim/long/extdeps_scope_placement_gate_loudness_witness_test.dag could execute production seed_runner_bool_false_failure_detail against a located receipt. It is deleted from this entry and declared IN that witness entry instead (seed_runner_loudness_probe_*), because the companion is evaluated in the WITNESS entry's context and importing this module to reach the probe pulled this entry's whole gate closure — 718 modules against the witness's own 62 — into a witness that needs none of it. Mute frontier (identity join on this entry, 2026-08-18): seven _passes witnesses, four _failure_receipt companions (extdeps_scope_placement_gate_failure_receipt, prose_row_introduction_gate_failure_receipt, dag_compile_clean_gate_failure_receipt, generated_artifact_drift_gate_failure_receipt; the _companion_name helper that used to sit here is deleted with the probe pair — its one reader was the same witness, which now asks gunbc.test_module_hygiene failure_receipt_companion directly). Three production gates still return bare returned Bool(false) on refusal with no located receipt: cheap_claim_pool_gate, emit_host_gate, extdeps_external_authority_gate. Dissolution trigger: a gate leaves this frontier when this entry declares its _failure_receipt companion beside the _passes witness. Frontier counts are hand-authored prose debt — dissolve-on: derived projection from this entry's declarations (see loudness_witness_note sequencing note)." +// claim_executor derives a failure-receipt companion from a Bool witness's own name and evaluates +// it in THIS entry's context, so the companion must be declared here beside the witness it explains +// — not only in the gate module that builds the reason. Naming is mechanical: _passes or +// _holds yields _failure_receipt, delegated to +// gunbc.test_module_hygiene.failure_receipt_companion (the single authority; +// cli_run::failure_receipt_companion routes through the same module). The +// scope_placement_gate_loudness_seed_probe_* pair — a hermetic always-false _passes witness plus +// its failure_receipt companion — USED to live here solely so +// dag/test/claim/long/extdeps_scope_placement_gate_loudness_witness_test.dag could execute +// production seed_runner_bool_false_failure_detail against a located receipt. It is deleted from +// this entry and declared IN that witness entry instead (seed_runner_loudness_probe_*), because the +// companion is evaluated in the WITNESS entry's context and importing this module to reach the +// probe pulled this entry's whole gate closure — 718 modules against the witness's own 62 — into a +// witness that needs none of it. Mute frontier (identity join on this entry, 2026-08-18): seven +// _passes witnesses, four _failure_receipt companions +// (extdeps_scope_placement_gate_failure_receipt, prose_row_introduction_gate_failure_receipt, +// dag_compile_clean_gate_failure_receipt, generated_artifact_drift_gate_failure_receipt; the +// _companion_name helper that used to sit here is deleted with the probe pair — its one reader was +// the same witness, which now asks gunbc.test_module_hygiene failure_receipt_companion directly). +// Three production gates still return bare returned Bool(false) on refusal with no located receipt: +// cheap_claim_pool_gate, emit_host_gate, extdeps_external_authority_gate. Dissolution trigger: a +// gate leaves this frontier when this entry declares its _failure_receipt companion beside +// the _passes witness. Frontier counts are hand-authored prose debt — dissolve-on: derived +// projection from this entry's declarations (see loudness_witness_note sequencing note). func emit_host_gate_passes() -> Bool { tools.ci_gates.exit_ok(e: run_spec_gate(g: EmitHostGate)) diff --git a/dag/gunbc/instruments/frontier_ingestion_probe.dag b/dag/gunbc/instruments/frontier_ingestion_probe.dag index 61aa2c36480..cc0e9ed9605 100644 --- a/dag/gunbc/instruments/frontier_ingestion_probe.dag +++ b/dag/gunbc/instruments/frontier_ingestion_probe.dag @@ -17,9 +17,46 @@ import v2.compiler.normalize { normalize } import v2.extdeps.languages.dag { dag_lex_rules, dag_grammar } import extdeps.filesystem.filesystem_io -data frontier_read_refusal_disposition_note: String = "WHY THERE IS NO READ-REFUSAL ARM HERE, and what is owed instead. A read refusal collapsing into a frontend verdict would be the empty-observation conflation one layer earlier than the one this instrument guards, so it was worth checking. It is not reachable through this primitive: the seed's filesystem_read returns FilesystemReadResult, declared in v1_method filesystem_read_result_type and emitted by v1.runtime_rust rt_filesystem, and that record carries EXACTLY ONE field, content. There is no success or error field to consult — the service-level Filesystem.Read operation is a different carrier with a different shape, and reading its three-field contract onto this primitive would be a citation to the wrong authority.\n\nWhat the primitive does on failure is panic: rt_filesystem calls read_to_string with unwrap_or_else(|e| panic!). So a missing or unreadable member aborts the run loudly rather than presenting as LEX, PARSE or ACCEPTED, and a guard branching on a success flag this record does not carry would be machinery with no producer — a scaffold defending against a state the type cannot express.\n\nWHAT IS OWED: the primitive has no TYPED refusal, only an abort. A caller cannot distinguish absent from unreadable from empty, and cannot continue past either. That is a real modeling gap in the primitive rather than in this probe, and it dissolves when filesystem_read returns an outcome instead of panicking — at which point this fn gains a refusal arm and a discriminating control, both of which would be untestable today." - -data frontier_ingestion_probe_note: String = "THE REPRODUCTION INSTRUMENT for docs/plans/v2-frontend-std-ingestion-frontier-exact-head.md, deliberately homed OUTSIDE a *_test.dag module. take_frontend_census reads the live tree and reports, for each rostered member, which frontend stage it currently reaches. That reading is a revision-scoped OBSERVATION of the present frontier, not a claim that must hold: promoting it to a test declaration would enroll an assertion whose success depends on the current defects continuing to exist, so it would go RED on the repair and GREEN on a regression. The durable claims — that the stage classifier separates its states, that reasons are read by containment, and that census reconciliation is a two-directional join over the roster — live in gunbc.test.claim.normalize_retention_contract_probe_test and import classify_source from here, so there is one classifier authority and not two.\n\nReason reads are CONTAINMENT reads, never head reads: rejected_with_pending PREPENDS accepted-carrier diagnostics (the wrapper-retention carriers, which are not failures) to a later rejection, so the head of the list is systematically the retention carrier rather than the cause.\n\nStages are DISTINCT STATES. An earlier draft returned an empty reason list for both 'normalize accepted' and 'lex/parse rejected', which is the empty-observation narrow: an unlexable member read as an accepted one. classify_source matches each stage's Outcome in sequence so that collapse is unrepresentable." +// WHY THERE IS NO READ-REFUSAL ARM HERE, and what is owed instead. A read refusal collapsing into a +// frontend verdict would be the empty-observation conflation one layer earlier than the one this +// instrument guards, so it was worth checking. It is not reachable through this primitive: the +// seed's filesystem_read returns FilesystemReadResult, declared in v1_method +// filesystem_read_result_type and emitted by v1.runtime_rust rt_filesystem, and that record carries +// EXACTLY ONE field, content. There is no success or error field to consult — the service-level +// Filesystem.Read operation is a different carrier with a different shape, and reading its +// three-field contract onto this primitive would be a citation to the wrong authority. +// +// What the primitive does on failure is panic: rt_filesystem calls read_to_string with +// unwrap_or_else(|e| panic!). So a missing or unreadable member aborts the run loudly rather than +// presenting as LEX, PARSE or ACCEPTED, and a guard branching on a success flag this record does +// not carry would be machinery with no producer — a scaffold defending against a state the type +// cannot express. +// +// WHAT IS OWED: the primitive has no TYPED refusal, only an abort. A caller cannot distinguish +// absent from unreadable from empty, and cannot continue past either. That is a real modeling gap +// in the primitive rather than in this probe, and it dissolves when filesystem_read returns an +// outcome instead of panicking — at which point this fn gains a refusal arm and a discriminating +// control, both of which would be untestable today. + +// THE REPRODUCTION INSTRUMENT for docs/plans/v2-frontend-std-ingestion-frontier-exact-head.md, +// deliberately homed OUTSIDE a *_test.dag module. take_frontend_census reads the live tree and +// reports, for each rostered member, which frontend stage it currently reaches. That reading is a +// revision-scoped OBSERVATION of the present frontier, not a claim that must hold: promoting it to +// a test declaration would enroll an assertion whose success depends on the current defects +// continuing to exist, so it would go RED on the repair and GREEN on a regression. The durable +// claims — that the stage classifier separates its states, that reasons are read by containment, +// and that census reconciliation is a two-directional join over the roster — live in +// gunbc.test.claim.normalize_retention_contract_probe_test and import classify_source from here, so +// there is one classifier authority and not two. +// +// Reason reads are CONTAINMENT reads, never head reads: rejected_with_pending PREPENDS +// accepted-carrier diagnostics (the wrapper-retention carriers, which are not failures) to a later +// rejection, so the head of the list is systematically the retention carrier rather than the cause. +// +// Stages are DISTINCT STATES. An earlier draft returned an empty reason list for both 'normalize +// accepted' and 'lex/parse rejected', which is the empty-observation narrow: an unlexable member +// read as an accepted one. classify_source matches each stage's Outcome in sequence so that +// collapse is unrepresentable. fn artifact_tree(artifact: ParseArtifact) -> ParseTree { artifact.tree @@ -41,7 +78,24 @@ fn reasons_contain(reasons: List, sym: Symbol) -> Bool { }) } -data frontend_stage_result_space_note: String = "THE RESULT SPACE IS CLOSED, and two things about its membership are deliberate.\n\nIt no longer carries an invalid-empty arm. The previous classifier took List and had to answer NORM_REASON_SET_INVALID_EMPTY for the empty list, because that input was constructible and mapping it to any real stage would have rendered a missing observation as an observed one. It now takes NonEmptyDiagnostics, whose head field is not optional, so the empty reason set has no representation and the arm that defended against it has no subject. That is a climb from mechanically-preventable to structurally-impossible for this one class, and per the dissolution rule the production arm is deleted while the discriminating controls stay — the controls that survive are the ones that still have a writable input, and the RED that fed an empty list retires with the state it named, not with the wall.\n\nIt does NOT carry a ReadRefused arm. filesystem_read panics rather than returning an outcome (frontier_read_refusal_disposition_note records the exact contract), so a read failure aborts the run and never reaches a verdict. Adding the arm would be a constructor no producer can reach — machinery defending a state the primitive cannot express — and it would make the space look more total than the observation actually is. It enters when the primitive gains a typed refusal, and not before." +// THE RESULT SPACE IS CLOSED, and two things about its membership are deliberate. +// +// It no longer carries an invalid-empty arm. The previous classifier took List and had to +// answer NORM_REASON_SET_INVALID_EMPTY for the empty list, because that input was constructible and +// mapping it to any real stage would have rendered a missing observation as an observed one. It now +// takes NonEmptyDiagnostics, whose head field is not optional, so the empty reason set has no +// representation and the arm that defended against it has no subject. That is a climb from +// mechanically-preventable to structurally-impossible for this one class, and per the dissolution +// rule the production arm is deleted while the discriminating controls stay — the controls that +// survive are the ones that still have a writable input, and the RED that fed an empty list retires +// with the state it named, not with the wall. +// +// It does NOT carry a ReadRefused arm. filesystem_read panics rather than returning an outcome +// (frontier_read_refusal_disposition_note records the exact contract), so a read failure aborts the +// run and never reaches a verdict. Adding the arm would be a constructor no producer can reach — +// machinery defending a state the primitive cannot express — and it would make the space look more +// total than the observation actually is. It enters when the primitive gains a typed refusal, and +// not before. type FrontendStage = LexRefused @@ -145,7 +199,20 @@ fn classify_normalize_diagnostics(d: NonEmptyDiagnostics) -> FrontendStage { } } -data prepared_frontend_world_note: String = "ONE PREPARED WORLD, because the expensive part of a fifteen-member census is not the fifteen members. classify_source used to call dag_lex_rules() and dag_grammar() itself, so every member rebuilt the rule set, revalidated the grammar and recomputed the FIRST/nullable analysis — fifteen times over, for a grammar that does not vary between members.\n\nThe seam already existed and this reuses it rather than inventing one: 02_parse's prepared_grammar_carrier_note records that prepare_grammar exists precisely so a K-module walk validates once instead of K times, and parse_module_prepared is the per-module half. parse_module is prepare_grammar composed with it, which is the right shape for a single call and the wrong one for a census.\n\nPreparation can REFUSE — a malformed grammar rejects — and that refusal is a fact about the world rather than about any member, so it is not a FrontendStage. Reporting it as one would say fifteen members failed to parse when what actually happened is that no member was ever examined." +// ONE PREPARED WORLD, because the expensive part of a fifteen-member census is not the fifteen +// members. classify_source used to call dag_lex_rules() and dag_grammar() itself, so every member +// rebuilt the rule set, revalidated the grammar and recomputed the FIRST/nullable analysis — +// fifteen times over, for a grammar that does not vary between members. +// +// The seam already existed and this reuses it rather than inventing one: 02_parse's +// prepared_grammar_carrier_note records that prepare_grammar exists precisely so a K-module walk +// validates once instead of K times, and parse_module_prepared is the per-module half. parse_module +// is prepare_grammar composed with it, which is the right shape for a single call and the wrong one +// for a census. +// +// Preparation can REFUSE — a malformed grammar rejects — and that refusal is a fact about the world +// rather than about any member, so it is not a FrontendStage. Reporting it as one would say fifteen +// members failed to parse when what actually happened is that no member was ever examined. type FrontendWorld { lex_rules: LexRuleSet, @@ -195,7 +262,18 @@ fn classify_source_in(world: FrontendWorld, source: String) -> FrontendStage { } } -data single_source_laziness_note: String = "classify_source PREPARES LAZILY, and that is a budget fact rather than a style choice. A lex refusal is decided before any grammar is consulted, which is the only reason the LEX claim in the durable witness executes inside the five-second fast lane at all; preparing the world first would build dag_grammar() for a source that never reaches the parser and push that claim over budget on its own.\n\nIt returns an Outcome rather than a bare FrontendStage because preparation can refuse, and a refused grammar is a fact about the world, not about the source. Reporting it as ParseRefused would say this source failed to parse when no parse was ever attempted — the state-space conflation the stage vocabulary exists to prevent, one level up. The census path does not have this shape: classify_source_in takes an already-prepared world, so every member it classifies has a stage by construction." +// classify_source PREPARES LAZILY, and that is a budget fact rather than a style choice. A lex +// refusal is decided before any grammar is consulted, which is the only reason the LEX claim in the +// durable witness executes inside the five-second fast lane at all; preparing the world first would +// build dag_grammar() for a source that never reaches the parser and push that claim over budget on +// its own. +// +// It returns an Outcome rather than a bare FrontendStage because preparation can refuse, and a +// refused grammar is a fact about the world, not about the source. Reporting it as ParseRefused +// would say this source failed to parse when no parse was ever attempted — the state-space +// conflation the stage vocabulary exists to prevent, one level up. The census path does not have +// this shape: classify_source_in takes an already-prepared world, so every member it classifies has +// a stage by construction. fn classify_source(source: String) -> v2.std.diagnostic.Outcome { match lex_walk_artifact(source: source, file: ^frontier_probe, rules: dag_lex_rules()) { @@ -213,7 +291,23 @@ fn classify_source(source: String) -> v2.std.diagnostic.Outcome { } } -data frontier_roster_note: String = "THE ROSTER IS THE DENOMINATOR, and it is authored here so that the census can be reconciled against a denominator rather than counted. The fifteen hand-written m_* fns this replaces were fifteen independent facts with no denominator at all: a member could be dropped from the file and every remaining fn would still answer, so 'all fifteen were observed' was only ever 'fifteen fns happened to be present'. reconcile_census now joins observations against the roster in both directions and at multiplicity, so a dropped member, a stray one and a doubled one are three distinct located verdicts rather than a smaller number that nothing compares against.\n\nWhat this does NOT do is make the live census executable per PR. The accepted-classification path measured over the five-second fast-lane budget, and the roster reads fifteen live files on top of that. Which stage of that path owns the cost is NOT established — the figure is an aggregate over lex, grammar construction, preparation and validation, parse and normalize, and no nested span separated them. Preparing the world once removes the per-member multiplier but not that floor, so take_frontend_census stays in this non-test module as a reproduction instrument, and the durable claims over it are the classifier, identity and join laws exercised on synthetic inputs. The batched or realized execution that would let the live fifteen gate a PR is still owed, and is where the two stage-separation claims deleted in the previous increment return." +// THE ROSTER IS THE DENOMINATOR, and it is authored here so that the census can be reconciled +// against a denominator rather than counted. The fifteen hand-written m_* fns this replaces were +// fifteen independent facts with no denominator at all: a member could be dropped from the file and +// every remaining fn would still answer, so 'all fifteen were observed' was only ever 'fifteen fns +// happened to be present'. reconcile_census now joins observations against the roster in both +// directions and at multiplicity, so a dropped member, a stray one and a doubled one are three +// distinct located verdicts rather than a smaller number that nothing compares against. +// +// What this does NOT do is make the live census executable per PR. The accepted-classification path +// measured over the five-second fast-lane budget, and the roster reads fifteen live files on top of +// that. Which stage of that path owns the cost is NOT established — the figure is an aggregate over +// lex, grammar construction, preparation and validation, parse and normalize, and no nested span +// separated them. Preparing the world once removes the per-member multiplier but not that floor, so +// take_frontend_census stays in this non-test module as a reproduction instrument, and the durable +// claims over it are the classifier, identity and join laws exercised on synthetic inputs. The +// batched or realized execution that would let the live fifteen gate a PR is still owed, and is +// where the two stage-separation claims deleted in the previous increment return. type FrontierMember { label: Symbol, @@ -248,7 +342,25 @@ fn classify_member_in(world: FrontendWorld, m: FrontierMember) -> MemberObservat MemberObservation { label: m.label, stage: classify_source_in(world: world, source: r.content) } } -data census_receipt_provenance_note: String = "WHAT THE RECEIPT BINDS, and what it deliberately does not. A census is a claim about a POPULATION measured by a MECHANISM at a REVISION, and a bare list of stages carries none of those three. So the receipt carries the roster identity it was measured against and the reconciliation verdict over its own observations, which makes two specific misreadings impossible: a census cannot be read against a roster it was not taken over, and a census that never reconciled cannot present as one that did, because CensusTaken is reachable only through the exact arm.\n\nNOT BOUND, stated rather than implied: the source tree revision and the frontend implementation identity. Neither is reachable from here — nothing in this module can observe the git tree, and the world is a value rather than a content-addressed subject — so binding them would mean inventing an identity rather than observing one, which is the failure this instrument was built to stop making. Until they are bound, a receipt establishes WHICH members were measured and that the population reconciles; it does not establish WHICH tree or WHICH compiler produced the stages. That is the difference between this and the provenance-bound receipt the executing census still owes.\n\nRoster identity is the roster's own content, not a count and not a name: two rosters with the same fifteen labels over different paths are different denominators and must not share an identity." +// WHAT THE RECEIPT BINDS, and what it deliberately does not. A census is a claim about a POPULATION +// measured by a MECHANISM at a REVISION, and a bare list of stages carries none of those three. So +// the receipt carries the roster identity it was measured against and the reconciliation verdict +// over its own observations, which makes two specific misreadings impossible: a census cannot be +// read against a roster it was not taken over, and a census that never reconciled cannot present as +// one that did, because CensusTaken is reachable only through the exact arm. +// +// NOT BOUND, stated rather than implied: the source tree revision and the frontend implementation +// identity. Neither is reachable from here — nothing in this module can observe the git tree, and +// the world is a value rather than a content-addressed subject — so binding them would mean +// inventing an identity rather than observing one, which is the failure this instrument was built +// to stop making. Until they are bound, a receipt establishes WHICH members were measured and that +// the population reconciles; it does not establish WHICH tree or WHICH compiler produced the +// stages. That is the difference between this and the provenance-bound receipt the executing census +// still owes. +// +// Roster identity is the roster's own content, not a count and not a name: two rosters with the +// same fifteen labels over different paths are different denominators and must not share an +// identity. type FrontendCensusReceipt = CensusTaken { @@ -301,7 +413,13 @@ fn take_frontend_census() -> FrontendCensusReceipt { take_census_over(rows: frontier_roster) } -data census_over_rows_note: String = "take_census_over takes the roster as an argument for the same reason verify_roster does: a census that could only ever be taken over the one live roster cannot be reconciled against a different denominator, so a second population needs a second copy of the mechanism, and two copies of a classifier are two authorities that can disagree. The roster identity is derived from the rows PASSED, never from the module-level roster, so a receipt cannot claim a denominator it was not measured over — that is the exact misreading census_receipt_provenance_note exists to prevent, and it would be reintroduced by a parameterised census that kept stamping the default identity." +// take_census_over takes the roster as an argument for the same reason verify_roster does: a census +// that could only ever be taken over the one live roster cannot be reconciled against a different +// denominator, so a second population needs a second copy of the mechanism, and two copies of a +// classifier are two authorities that can disagree. The roster identity is derived from the rows +// PASSED, never from the module-level roster, so a receipt cannot claim a denominator it was not +// measured over — that is the exact misreading census_receipt_provenance_note exists to prevent, +// and it would be reintroduced by a parameterised census that kept stamping the default identity. fn take_census_over(rows: List) -> FrontendCensusReceipt { match prepare_frontend_world() { @@ -318,7 +436,25 @@ fn take_census_over(rows: List) -> FrontendCensusReceipt { } } -data census_reconciliation_note: String = "COVERAGE IS NOT RECONCILIATION, and the first version of this census only had the first half. It asked, per roster label, whether SOME observation carried it — which is the superset direction alone. A census carrying all fifteen members plus a sixteenth stray passed it, and so did one carrying a member twice, because neither condition can make a per-label existence check fail. Calling that an identity join was wrong: it is the join's covering half.\n\nReconciliation is both directions plus multiplicity, and the three failures are genuinely different states with different remedies. A MISSING member means the instrument did not observe something the denominator requires — coverage is short. An UNROSTERED observation means the instrument observed something the denominator does not contain, which usually means the roster and the instrument have drifted apart rather than that the run went wrong. A DUPLICATE means one member was observed twice, which silently doubles that member's contribution to every per-stage count derived from the census. Collapsing all three into one Bool would have made any figure derived from the census unfalsifiable in exactly the way a stray or doubled row corrupts it — which is why the per-stage count that used to sit beside this is gone (stage_count_absence_note).\n\nSo the verdict is a coproduct carrying the offending label rather than a Bool, and census_is_complete is derived from it rather than computed beside it — a second Bool fold would be a second authority that could disagree with the first." +// COVERAGE IS NOT RECONCILIATION, and the first version of this census only had the first half. It +// asked, per roster label, whether SOME observation carried it — which is the superset direction +// alone. A census carrying all fifteen members plus a sixteenth stray passed it, and so did one +// carrying a member twice, because neither condition can make a per-label existence check fail. +// Calling that an identity join was wrong: it is the join's covering half. +// +// Reconciliation is both directions plus multiplicity, and the three failures are genuinely +// different states with different remedies. A MISSING member means the instrument did not observe +// something the denominator requires — coverage is short. An UNROSTERED observation means the +// instrument observed something the denominator does not contain, which usually means the roster +// and the instrument have drifted apart rather than that the run went wrong. A DUPLICATE means one +// member was observed twice, which silently doubles that member's contribution to every per-stage +// count derived from the census. Collapsing all three into one Bool would have made any figure +// derived from the census unfalsifiable in exactly the way a stray or doubled row corrupts it — +// which is why the per-stage count that used to sit beside this is gone (stage_count_absence_note). +// +// So the verdict is a coproduct carrying the offending label rather than a Bool, and +// census_is_complete is derived from it rather than computed beside it — a second Bool fold would +// be a second authority that could disagree with the first. type CensusReconciliation = CensusExact @@ -327,7 +463,25 @@ type CensusReconciliation | CensusDuplicateMember { label: Symbol } | CensusUnrosteredObservation { label: Symbol } -data frontier_roster_validity_note: String = "THE DENOMINATOR IS ITSELF AN OBSERVATION, and reconciling against an unvalidated one proves less than it appears. reconcile_census checks the observations against the roster; it assumed the roster was internally sound, and two malformed rosters defeat it in ways that leave CensusExact reachable.\n\nA DUPLICATE LABEL is invisible to the observation-side check by construction: two roster rows carrying one label are each satisfied by the SAME single observation, because count_label finds exactly one for both. So a roster of fifteen rows and fourteen distinct labels reconciles exactly against fourteen observations, and the fifteenth member is silently not a member.\n\nA DUPLICATE PATH under two labels is worse, because nothing anywhere is wrong at the label grain: the census reads one file twice, reports it as two members, and every derived per-stage figure counts that member's stage twice. The roster is the only place this is decidable, since by observation time the path has already been read.\n\nSo roster validity is established BEFORE the observation join and CensusExact is unreachable without it. The two verdicts stay separate rather than fusing into one refusal list because they have different remedies and different owners: a bad roster is an authoring error in this file, while a bad census is a fact about the run." +// THE DENOMINATOR IS ITSELF AN OBSERVATION, and reconciling against an unvalidated one proves less +// than it appears. reconcile_census checks the observations against the roster; it assumed the +// roster was internally sound, and two malformed rosters defeat it in ways that leave CensusExact +// reachable. +// +// A DUPLICATE LABEL is invisible to the observation-side check by construction: two roster rows +// carrying one label are each satisfied by the SAME single observation, because count_label finds +// exactly one for both. So a roster of fifteen rows and fourteen distinct labels reconciles exactly +// against fourteen observations, and the fifteenth member is silently not a member. +// +// A DUPLICATE PATH under two labels is worse, because nothing anywhere is wrong at the label grain: +// the census reads one file twice, reports it as two members, and every derived per-stage figure +// counts that member's stage twice. The roster is the only place this is decidable, since by +// observation time the path has already been read. +// +// So roster validity is established BEFORE the observation join and CensusExact is unreachable +// without it. The two verdicts stay separate rather than fusing into one refusal list because they +// have different remedies and different owners: a bad roster is an authoring error in this file, +// while a bad census is a fact about the run. type FrontierRosterVerdict = FrontierRosterValid @@ -445,7 +599,18 @@ fn census_is_complete(obs: List) -> Bool { } } -data stage_count_absence_note: String = "THERE IS NO PER-STAGE COUNT HERE, deliberately. A count_stage over a raw List existed in the first version of this increment and was deleted rather than kept, because it took the unreconciled population: it would happily total a census missing a member, carrying a duplicate, holding an unrostered observation, or built on an invalid roster. That directly contradicts the finding recorded beside it — that a duplicate silently doubles one member's contribution to every derived figure — so shipping both would have been the mechanism and its own counterexample in one file.\n\nIt had no consumer either: nothing counts stages yet, because the live census does not execute as a gate. When counting is genuinely needed it belongs on a carrier that only a reconciled census can construct, so that no total is reachable from observations that were never joined against a validated roster. That carrier lands with the executing census, where it will have a producer." +// THERE IS NO PER-STAGE COUNT HERE, deliberately. A count_stage over a raw List +// existed in the first version of this increment and was deleted rather than kept, because it took +// the unreconciled population: it would happily total a census missing a member, carrying a +// duplicate, holding an unrostered observation, or built on an invalid roster. That directly +// contradicts the finding recorded beside it — that a duplicate silently doubles one member's +// contribution to every derived figure — so shipping both would have been the mechanism and its own +// counterexample in one file. +// +// It had no consumer either: nothing counts stages yet, because the live census does not execute as +// a gate. When counting is genuinely needed it belongs on a carrier that only a reconciled census +// can construct, so that no total is reachable from observations that were never joined against a +// validated roster. That carrier lands with the executing census, where it will have a producer. fn render_classified(source_outcome: v2.std.diagnostic.Outcome) -> String { match source_outcome { @@ -454,7 +619,14 @@ fn render_classified(source_outcome: v2.std.diagnostic.Outcome) - } } -data wall_attribution_probe_note: String = "MINIMAL-PAIR PROBES for the two frontend walls that are still hypotheses rather than findings. Neither may become a finding from a description: the roadmap row's own RED control refuses a repair asserted separable without execution, and the em-dash misattribution earlier in this lane is the standing receipt for why. Each fn below is one construct in an otherwise-minimal module, so a refusal names the construct rather than the file. They render to label text rather than returning a typed value, because their only consumer is a person invoking one by hand and reading the answer; the typed vocabulary is for structured consumers. A refused world renders as its own word rather than as any stage, since no stage was reached." +// MINIMAL-PAIR PROBES for the two frontend walls that are still hypotheses rather than findings. +// Neither may become a finding from a description: the roadmap row's own RED control refuses a +// repair asserted separable without execution, and the em-dash misattribution earlier in this lane +// is the standing receipt for why. Each fn below is one construct in an otherwise-minimal module, +// so a refusal names the construct rather than the file. They render to label text rather than +// returning a typed value, because their only consumer is a person invoking one by hand and reading +// the answer; the typed vocabulary is for structured consumers. A refused world renders as its own +// word rather than as any stage, since no stage was reached. fn p_hex_escape() -> String { render_classified(source_outcome: classify_source(source: "module frontier_probe\n\ndata d: String = \"a\\x0db\"\n")) } fn p_hex_escape_control() -> String { render_classified(source_outcome: classify_source(source: "module frontier_probe\n\ndata d: String = \"ab\"\n")) } diff --git a/dag/gunbc/instruments/generated_artifact_gate.dag b/dag/gunbc/instruments/generated_artifact_gate.dag index 927625b2f0c..bf6861c357a 100644 --- a/dag/gunbc/instruments/generated_artifact_gate.dag +++ b/dag/gunbc/instruments/generated_artifact_gate.dag @@ -27,9 +27,29 @@ fn exit_ok(e: ProcessExit) -> Bool { match e { ExitSuccess => true _ => false } } -data generated_artifact_gate_remedy_grain_note: String = "THE REMEDY IS PER FINDING, NOT PER RUN, AND REMOVING THE GLOBAL SENTENCE IS THE POINT. This body used to append one closing instruction to every nonempty population -- do not hand-edit, regenerate via main_wet -- which is correct for a content difference and WRONG for every other class the carrier distinguishes. An expected observation that is missing means the observer or the population producer is incomplete and NOTHING was adjudicated for that artifact; regenerating cannot fix it and greening it that way would hide the completeness defect. An unknown observation means the registry and the tree disagree about whether the artifact should be committed at all, and DESIGN records the receipt for taking that instruction literally: this gate's first red included a markdown file DELETED ON PURPOSE by a commit whose entire subject was that the .dag carrier is the authority, and regenerating would have enforced the reverse of a decision already taken, permanently and silently. An extra-validation failure is a defect in the generated bytes themselves, so regeneration reproduces it. A carrier or generator refusal names an authority to repair and reached no verdict at all. One sentence sending all of them to main_wet is the absorbing fallback in remediation form -- a single widening arm standing where a typed, per-class answer was already available in the findings carrier -- so each rendered finding now carries its own remedy and the run-level sentence is gone. The producer is still named where it IS the remedy; it is simply no longer the default for classes it cannot repair." +// THE REMEDY IS PER FINDING, NOT PER RUN, AND REMOVING THE GLOBAL SENTENCE IS THE POINT. This body +// used to append one closing instruction to every nonempty population -- do not hand-edit, +// regenerate via main_wet -- which is correct for a content difference and WRONG for every other +// class the carrier distinguishes. An expected observation that is missing means the observer or +// the population producer is incomplete and NOTHING was adjudicated for that artifact; regenerating +// cannot fix it and greening it that way would hide the completeness defect. An unknown observation +// means the registry and the tree disagree about whether the artifact should be committed at all, +// and DESIGN records the receipt for taking that instruction literally: this gate's first red +// included a markdown file DELETED ON PURPOSE by a commit whose entire subject was that the .dag +// carrier is the authority, and regenerating would have enforced the reverse of a decision already +// taken, permanently and silently. An extra-validation failure is a defect in the generated bytes +// themselves, so regeneration reproduces it. A carrier or generator refusal names an authority to +// repair and reached no verdict at all. One sentence sending all of them to main_wet is the +// absorbing fallback in remediation form -- a single widening arm standing where a typed, per-class +// answer was already available in the findings carrier -- so each rendered finding now carries its +// own remedy and the run-level sentence is gone. The producer is still named where it IS the +// remedy; it is simply no longer the default for classes it cannot repair. -data run_generated_artifact_drift_gate_body_note: String = "One live pass: observe_generated_artifact_population() reads each committed file and generates each artifact exactly once (gunbc.generated_artifact_observation). The pure group and leaf assessments, and the failure diagnostic, all read that SAME retained population — no re-invocation of the producer (operator ruling: cost and correctness must not ride on incidental memoization of a repeated producer call)." +// One live pass: observe_generated_artifact_population() reads each committed file and generates +// each artifact exactly once (gunbc.generated_artifact_observation). The pure group and leaf +// assessments, and the failure diagnostic, all read that SAME retained population — no +// re-invocation of the producer (operator ruling: cost and correctness must not ride on incidental +// memoization of a repeated producer call). func run_generated_artifact_drift_gate_body() -> ProcessExit { let observations = observe_generated_artifact_population() @@ -60,7 +80,19 @@ func run_generated_artifact_drift_gate() -> ProcessExit { run_generated_artifact_drift_gate_body() } -data generated_artifact_drift_failure_receipt_note: String = "The loudness half: the floor witness collapses this gate's ProcessExit to Bool via tools.ci_gates.exit_ok, so a generated-artifact drift red reported only returned Bool(false) without the located path. The gate body records the reason from its single observe_generated_artifact_population pass; the failure-receipt companion consumes that retained detail instead of re-observing. THE BODY NOW RECORDS THE CLEAN SIDE TOO, and that is the whole point of the change rather than a tidy-up. Before it, only the FAILING side wrote, so the global's unset state carried two facts at once -- the body ran and found no drift, and the body never ran in this process -- and the companion papered the second over with the fabricated prose 'gate body did not run in this process' handed back as if it were a drift detail. Nothing downstream could tell an unguarded run from a clean one, which is DESIGN's execution-provenance rule exactly: a stage that can prevent an observation from executing must participate in the observation carrier, so refused-before-observing cannot inhabit the same state as observed-nothing. Recording the clean side is what makes GateNotRun mean only the thing it says." +// The loudness half: the floor witness collapses this gate's ProcessExit to Bool via +// tools.ci_gates.exit_ok, so a generated-artifact drift red reported only returned Bool(false) +// without the located path. The gate body records the reason from its single +// observe_generated_artifact_population pass; the failure-receipt companion consumes that retained +// detail instead of re-observing. THE BODY NOW RECORDS THE CLEAN SIDE TOO, and that is the whole +// point of the change rather than a tidy-up. Before it, only the FAILING side wrote, so the +// global's unset state carried two facts at once -- the body ran and found no drift, and the body +// never ran in this process -- and the companion papered the second over with the fabricated prose +// 'gate body did not run in this process' handed back as if it were a drift detail. Nothing +// downstream could tell an unguarded run from a clean one, which is DESIGN's execution-provenance +// rule exactly: a stage that can prevent an observation from executing must participate in the +// observation carrier, so refused-before-observing cannot inhabit the same state as +// observed-nothing. Recording the clean side is what makes GateNotRun mean only the thing it says. fn generated_artifact_drift_failure_reason(exit: ProcessExit) -> String { match exit { diff --git a/dag/gunbc/instruments/host_prelude.dag b/dag/gunbc/instruments/host_prelude.dag index c2d89350e79..b52bcee5ce2 100644 --- a/dag/gunbc/instruments/host_prelude.dag +++ b/dag/gunbc/instruments/host_prelude.dag @@ -52,7 +52,16 @@ type WitnessBinReadiness = WitnessBinReady { path: String } | WitnessBinRefused { bin_name: String, path: String, reason: WitnessBinRefusalReason } -data witness_bin_readiness_outcome_is_data_note: String = "The three probes below are annotated OutcomeIsData (operator ruling 2026-07-25). A non-zero exit from `test -f`/`-x`/`-s` here means SUBJECT ABSENT — an answer, not a fault — and it is ambient in BOTH of this helper's contexts: the production readiness path (witness_bin_run_if_ready, below) responds by building, and the build-artifact red controls respond by asserting the refusal arm. Because the arm is correct for every caller, it is annotatable HERE, once, with no expectation threaded through this production signature and no ambient frame left open. ADMISSIBILITY (the §5 guard in gunbc.output_policy outcome_is_data_note): this fn returns WitnessBinReadiness — a typed verdict whose arms ARE the adjudication — never unit, so the exit provably feeds a typed consumer. What stays anomalous is dispatch failure: if the probe itself cannot run, that is not an exit code and does not travel this arm." +// The three probes below are annotated OutcomeIsData (operator ruling 2026-07-25). A non-zero exit +// from `test -f`/`-x`/`-s` here means SUBJECT ABSENT — an answer, not a fault — and it is ambient +// in BOTH of this helper's contexts: the production readiness path (witness_bin_run_if_ready, +// below) responds by building, and the build-artifact red controls respond by asserting the refusal +// arm. Because the arm is correct for every caller, it is annotatable HERE, once, with no +// expectation threaded through this production signature and no ambient frame left open. +// ADMISSIBILITY (the §5 guard in gunbc.output_policy outcome_is_data_note): this fn returns +// WitnessBinReadiness — a typed verdict whose arms ARE the adjudication — never unit, so the exit +// provably feeds a typed consumer. What stays anomalous is dispatch failure: if the probe itself +// cannot run, that is not an exit code and does not travel this arm. fn witness_bin_artifact_readiness(bin_name: String, path: String) -> WitnessBinReadiness { let is_file = shell.Test.IsFile(path: path, expect: OutcomeIsData) @@ -80,7 +89,23 @@ fn witness_bin_readiness_is_ready(readiness: WitnessBinReadiness) -> Bool { } } -data witness_bin_ensure_built_probe_note: String = "Enrollment wall first (gunbc.ci_release_bins.release_bin_is_enrolled): an unenrolled bin_name refuses as WitnessBinNotEnrolled and never reaches cargo.Build — that is the construction close of the mid-floor cold-monolith absorption the release-bins pack exists to bound (roster dissolve_on for the former ci_floor_required_artifact_names hand list). The presence probe below is OutcomeIsData for the same reason as witness_bin_artifact_readiness: a non-zero `test -x` means NOT BUILT YET, which is the question this fn asks, and the production arm answers it by building while the red control answers it by asserting refusal. The cargo.Build.BuildInheritEnv call further down is DELIBERATELY NOT annotated, and the distinction is worth stating because both effects sit in the same typed fn. The §5 guard's LETTER admits both — this fn returns WitnessBinReadiness and the build's exit is folded into WitnessBinRefused. Its SPIRIT does not: a failing `cargo build` in the production path is a genuine fault an engineer wants shouted, not an observation about the subject's state. Silencing it would trade a fake anomaly (which the guard exists to remove) for a hidden real one (which is what the whole arc is about). So it stays loud pending an operator call; the residual ❌ that proves discrimination for no_fake_anomalies_witness_test is carried by typed_witness_bin_run_loud_failure_red_control_holds (unannotated WitnessBin.Run of /bin/false — cheap), not by the unenrolled WitnessBin path which now refuses before cargo." +// Enrollment wall first (gunbc.ci_release_bins.release_bin_is_enrolled): an unenrolled bin_name +// refuses as WitnessBinNotEnrolled and never reaches cargo.Build — that is the construction close +// of the mid-floor cold-monolith absorption the release-bins pack exists to bound (roster +// dissolve_on for the former ci_floor_required_artifact_names hand list). The presence probe below +// is OutcomeIsData for the same reason as witness_bin_artifact_readiness: a non-zero `test -x` +// means NOT BUILT YET, which is the question this fn asks, and the production arm answers it by +// building while the red control answers it by asserting refusal. The cargo.Build.BuildInheritEnv +// call further down is DELIBERATELY NOT annotated, and the distinction is worth stating because +// both effects sit in the same typed fn. The §5 guard's LETTER admits both — this fn returns +// WitnessBinReadiness and the build's exit is folded into WitnessBinRefused. Its SPIRIT does not: a +// failing `cargo build` in the production path is a genuine fault an engineer wants shouted, not an +// observation about the subject's state. Silencing it would trade a fake anomaly (which the guard +// exists to remove) for a hidden real one (which is what the whole arc is about). So it stays loud +// pending an operator call; the residual ❌ that proves discrimination for +// no_fake_anomalies_witness_test is carried by typed_witness_bin_run_loud_failure_red_control_holds +// (unannotated WitnessBin.Run of /bin/false — cheap), not by the unenrolled WitnessBin path which +// now refuses before cargo. fn witness_bin_ensure_built_typed(root: String, bin: WitnessBin) -> WitnessBinReadiness { let path = witness_bin_release_path(root: root, bin_name: bin.bin_name) @@ -113,7 +138,12 @@ fn witness_bin_ready(root: String, bin: WitnessBin) -> Bool { witness_bin_readiness_is_ready(readiness: witness_bin_ensure_built_typed(root: root, bin: bin)) } -data witness_bin_readiness_seed_note: String = "witness_bin_ready_seed is the fold IDENTITY for 'nothing has refused yet', not a claim that a binary at the empty path is ready. witness_bin_readiness_then only ever replaces it — the seed cannot escape a non-empty fold, and an empty prereq list falls straight through to the invoke bin's own check. The fold carries the FIRST refusal and stops probing after it: once a prerequisite has refused, checking the rest would build binaries for a run that cannot happen, and the first refusal is the one that located the fault." +// witness_bin_ready_seed is the fold IDENTITY for 'nothing has refused yet', not a claim that a +// binary at the empty path is ready. witness_bin_readiness_then only ever replaces it — the seed +// cannot escape a non-empty fold, and an empty prereq list falls straight through to the invoke +// bin's own check. The fold carries the FIRST refusal and stops probing after it: once a +// prerequisite has refused, checking the rest would build binaries for a run that cannot happen, +// and the first refusal is the one that located the fault. fn witness_bin_ready_seed() -> WitnessBinReadiness { WitnessBinReady { path: "" } @@ -171,7 +201,22 @@ fn gunbc_claims_readiness() -> WitnessBinReadiness { ) } -data run_gunbc_claims_pooled_note: String = "One child process per CALL, not per claim (2026-07-23, the floor redundant-work ledger's cold-child class — #7106 levers 2/3, corrected mechanism in the attribution doc section 9): the per-claim fold spawned one cold `gunbc run --claim-run` process per row, serial, each re-deriving the module index and closure resolve from scratch — the batch walls were the SUM of those children (~26/run, ~23 of 48 floor minutes), invisible to resolves_total by process boundary. The realization is now ONE `claim_batch` child per call carrying every row (claim_batch's own pre-existing pooled grammar: repeated --entry groups with --function rows, one shared MultiEntryIndex per process — srv1 counterfactual receipts: single cold child 47.2s; 12 claims pooled 93.4s vs 480s serial). The vehicle is deliberately a SEPARATE process, never in-executor evaluation: the executor is the 16GiB-pinned process of the 2026-07-23 crawl, and adding claim closures to its retention trades the time disease for the memory one; the child dies and frees. Verdict semantics preserved by claim_batch's own loop: every row runs (no short-circuit), each failure is NAMED per-function (PASS f / FAIL f lines), exit nonzero iff any failed — the same conjunction the per-process fold computed, with strictly more reporting. Residue — one pool build per call — dissolves on the cross-process content-keyed typed-module store (docs/plans/compile-wall-endgame.md W3)." +// One child process per CALL, not per claim (2026-07-23, the floor redundant-work ledger's +// cold-child class — #7106 levers 2/3, corrected mechanism in the attribution doc section 9): the +// per-claim fold spawned one cold `gunbc run --claim-run` process per row, serial, each re-deriving +// the module index and closure resolve from scratch — the batch walls were the SUM of those +// children (~26/run, ~23 of 48 floor minutes), invisible to resolves_total by process boundary. The +// realization is now ONE `claim_batch` child per call carrying every row (claim_batch's own +// pre-existing pooled grammar: repeated --entry groups with --function rows, one shared +// MultiEntryIndex per process — srv1 counterfactual receipts: single cold child 47.2s; 12 claims +// pooled 93.4s vs 480s serial). The vehicle is deliberately a SEPARATE process, never in-executor +// evaluation: the executor is the 16GiB-pinned process of the 2026-07-23 crawl, and adding claim +// closures to its retention trades the time disease for the memory one; the child dies and frees. +// Verdict semantics preserved by claim_batch's own loop: every row runs (no short-circuit), each +// failure is NAMED per-function (PASS f / FAIL f lines), exit nonzero iff any failed — the same +// conjunction the per-process fold computed, with strictly more reporting. Residue — one pool build +// per call — dissolves on the cross-process content-keyed typed-module store +// (docs/plans/compile-wall-endgame.md W3). fn claim_batch_claim_args(acc: List, last_entry: String, c: ClaimRun) -> List { if c.entry == last_entry { diff --git a/dag/gunbc/instruments/interpreter_dispatch_bijection_real_roster_transport.dag b/dag/gunbc/instruments/interpreter_dispatch_bijection_real_roster_transport.dag index f7f90b0d88f..2e6aee9264f 100644 --- a/dag/gunbc/instruments/interpreter_dispatch_bijection_real_roster_transport.dag +++ b/dag/gunbc/instruments/interpreter_dispatch_bijection_real_roster_transport.dag @@ -23,9 +23,34 @@ import std.decl_ref { DeclarationRef, WholeDeclaration } import std.logic { Bool } import std.types { FilePath, NonEmptyStr, List, String } -data interpreter_dispatch_bijection_real_roster_transport_note: String = "R1 closeout item TWO real-crate bijection receipt (gunbc#7825): executes the two #[ignore] integration tests in src/v1/stage0/tests/interpreter_dispatch_bijection_real_roster_red.rs via two sequential cargo.Build.TestWithCwdEnv calls (env -C RUSTC_WRAPPER= CTRL_BUILD_MODE=local CARGO_BUILD_JOBS=4 test ..., short-circuiting on the first failure) — real git clone, real gunbc run main_wet regen, real cargo build -p v1-compiler on the clone. Migrated off a hand-built retained_foreign multi-line bash script (set -euo pipefail; cd; export; two invocations) onto typed argv (SHELL-DAG dissolution). cargo.Build.Test (the bare, no-cwd operation) routes through ctrl-build remote in this environment and is intentionally not used here. Claim-A compile-red siblings stay in the default local cargo test run; this transport is the falsifier_self_host_wet enrollment path for claim-B." +// R1 closeout item TWO real-crate bijection receipt (gunbc#7825): executes the two #[ignore] +// integration tests in src/v1/stage0/tests/interpreter_dispatch_bijection_real_roster_red.rs via +// two sequential cargo.Build.TestWithCwdEnv calls (env -C RUSTC_WRAPPER= +// CTRL_BUILD_MODE=local CARGO_BUILD_JOBS=4 test ..., short-circuiting on the first +// failure) — real git clone, real gunbc run main_wet regen, real cargo build -p v1-compiler on the +// clone. Migrated off a hand-built retained_foreign multi-line bash script (set -euo pipefail; cd; +// export; two invocations) onto typed argv (SHELL-DAG dissolution). cargo.Build.Test (the bare, +// no-cwd operation) routes through ctrl-build remote in this environment and is intentionally not +// used here. Claim-A compile-red siblings stay in the default local cargo test run; this transport +// is the falsifier_self_host_wet enrollment path for claim-B. -data interpreter_dispatch_bijection_real_roster_memory_envelope_note: String = "MEMORY PREDICTION (Lane D, 2026-08-06; construction pin still-bat-561 msg_e79bf117). WITNESS NEEDS: per direction — git clone --local (no target/), gunbc run main_wet on the clone (pre-built release gunbc), cold release cargo build -p v1-compiler --lib in the clone; two directions sequential so peak is max(per-direction). DOMINANT PEAK: in-clone cold rustc lib build. CONSTRUCTION: transport shell exports CARGO_BUILD_JOBS=4 before both cargo test invocations so the fleet-measured premise (jobs=4) is the executed premise — not a note beside an unenforced conditional. ANCHORS: gunbc_ci_native_bundle_batch_clamp_basis_note cgroup peak 11940978688 B (~11.1 GiB, cold cargo, jobs=4); v1 width-1 floor 10.7 GiB cgroup under 15 GiB throttle; claim_batch resolve ~2.4 GiB before shell. PREDICTED PEAK on falsifier runner slot: ~10–12 GiB cgroup, ~4–5 GiB headroom under gunbc_runner_slot_desired().memory_max (16 GiB kill / 15 GiB high; live read falsifier 31047744902 srv3-03). VERDICT: fits the declared slot under pinned jobs=4. STACKING RISK (fleet memory-tight tonight): batch-1 discovery co-residency ~5–6 GiB before batch-2 wet rows. LOCAL SIGKILL CONTROL: claim_batch --wet killed host-tree cargo test compile at 97s before clone+build (children-max-rss 2.7 GiB); same host completed direction-1 through clone+build in 346s wall — kill is not clone peak evidence. WALL ROUTED TO OPERATOR (not solved here): ~692s whole transport vs gunbc_falsifier_self_host_wet_receipt_wall_budget 600s — basis and regime below in dashboard routing; witness not shrunk." +// MEMORY PREDICTION (Lane D, 2026-08-06; construction pin still-bat-561 msg_e79bf117). WITNESS +// NEEDS: per direction — git clone --local (no target/), gunbc run main_wet on the clone (pre-built +// release gunbc), cold release cargo build -p v1-compiler --lib in the clone; two directions +// sequential so peak is max(per-direction). DOMINANT PEAK: in-clone cold rustc lib build. +// CONSTRUCTION: transport shell exports CARGO_BUILD_JOBS=4 before both cargo test invocations so +// the fleet-measured premise (jobs=4) is the executed premise — not a note beside an unenforced +// conditional. ANCHORS: gunbc_ci_native_bundle_batch_clamp_basis_note cgroup peak 11940978688 B +// (~11.1 GiB, cold cargo, jobs=4); v1 width-1 floor 10.7 GiB cgroup under 15 GiB throttle; +// claim_batch resolve ~2.4 GiB before shell. PREDICTED PEAK on falsifier runner slot: ~10–12 GiB +// cgroup, ~4–5 GiB headroom under gunbc_runner_slot_desired().memory_max (16 GiB kill / 15 GiB +// high; live read falsifier 31047744902 srv3-03). VERDICT: fits the declared slot under pinned +// jobs=4. STACKING RISK (fleet memory-tight tonight): batch-1 discovery co-residency ~5–6 GiB +// before batch-2 wet rows. LOCAL SIGKILL CONTROL: claim_batch --wet killed host-tree cargo test +// compile at 97s before clone+build (children-max-rss 2.7 GiB); same host completed direction-1 +// through clone+build in 346s wall — kill is not clone peak evidence. WALL ROUTED TO OPERATOR (not +// solved here): ~692s whole transport vs gunbc_falsifier_self_host_wet_receipt_wall_budget 600s — +// basis and regime below in dashboard routing; witness not shrunk. // The subject is bound to the machine holding the workspace: it clones the live tree, // regenerates artifacts in the clone and requires a real compile refusal there. So its diff --git a/dag/gunbc/instruments/merge_admission_capture.dag b/dag/gunbc/instruments/merge_admission_capture.dag index c8f7ebd7355..fa2e4d05d78 100644 --- a/dag/gunbc/instruments/merge_admission_capture.dag +++ b/dag/gunbc/instruments/merge_admission_capture.dag @@ -17,9 +17,21 @@ import gunbc.merge_admission_subject { render_tested_subject_wire, } -data merge_admission_capture_profile_note: String = "This wet capture entry imports only the walk-attempt/tested-subject identity carrier plus typed git/filesystem services. Merge classification, commit-roster hashing, and CI failure taxonomy are deliberately downstream and absent from this closure. The entry runs in the plan's typed pre-walk claim_batch subprocess, so its evaluator arena dies before the ordinary floor and discovery cannot retain it." - -data merge_admission_capture_refusal_wire_note: String = "TYPED CAUSE ACROSS THE CLAIM BOUNDARY (review 47517, §5). The pre-walk transport's channel is a Bool claim result, which cannot carry the refusal cause — so the capture core returns the typed sum below, and the Bool projection durably writes the labeled cause to target/merge-admission-capture-refusal.txt (the floor-population-budget-refusal.txt pattern) BEFORE returning false; claim_executor's run_pre_walk_execution reads that wire into its located PRE-WALK-REFUSED line. The wire is best-effort-cleared at capture start so a stale cause from a prior attempt cannot masquerade as this attempt's; a false result with no wire is itself a distinct reported state (child died before writing), never silently folded into a generic failure." +// This wet capture entry imports only the walk-attempt/tested-subject identity carrier plus typed +// git/filesystem services. Merge classification, commit-roster hashing, and CI failure taxonomy are +// deliberately downstream and absent from this closure. The entry runs in the plan's typed pre-walk +// claim_batch subprocess, so its evaluator arena dies before the ordinary floor and discovery +// cannot retain it. + +// TYPED CAUSE ACROSS THE CLAIM BOUNDARY (review 47517, §5). The pre-walk transport's channel is a +// Bool claim result, which cannot carry the refusal cause — so the capture core returns the typed +// sum below, and the Bool projection durably writes the labeled cause to +// target/merge-admission-capture-refusal.txt (the floor-population-budget-refusal.txt pattern) +// BEFORE returning false; claim_executor's run_pre_walk_execution reads that wire into its located +// PRE-WALK-REFUSED line. The wire is best-effort-cleared at capture start so a stale cause from a +// prior attempt cannot masquerade as this attempt's; a false result with no wire is itself a +// distinct reported state (child died before writing), never silently folded into a generic +// failure. fn env_var(name: NonEmptyStr) -> String? { let env = shell.Env.Get(name: name) @@ -94,7 +106,10 @@ fn tested_subject_capture_refusal_label(cause: TestedSubjectCaptureRefusal) -> S data merge_admission_capture_refusal_wire_relpath: String = "target/merge-admission-capture-refusal.txt" -data merge_admission_capture_refusal_wire_seed_pairing: String = "PAIRED LITERAL (review 47596): claim_executor.rs MERGE_ADMISSION_CAPTURE_REFUSAL_WIRE reads this exact relpath — the seed cannot import a .dag datum, so a rename here lands with the paired seed edit or the parent's read degrades to its loud wire-absent arm. Same pairing class as floor-population-budget-refusal.txt, under walk_plan_run_stage_claim_executor_seed_deferral." +// PAIRED LITERAL (review 47596): claim_executor.rs MERGE_ADMISSION_CAPTURE_REFUSAL_WIRE reads this +// exact relpath — the seed cannot import a .dag datum, so a rename here lands with the paired seed +// edit or the parent's read degrades to its loud wire-absent arm. Same pairing class as +// floor-population-budget-refusal.txt, under walk_plan_run_stage_claim_executor_seed_deferral. func tested_subject_capture() -> TestedSubjectCapture { match capture_walk_attempt_id() { diff --git a/dag/gunbc/instruments/merge_admission_walk.dag b/dag/gunbc/instruments/merge_admission_walk.dag index f5ec0ddf239..9ac39df266f 100644 --- a/dag/gunbc/instruments/merge_admission_walk.dag +++ b/dag/gunbc/instruments/merge_admission_walk.dag @@ -32,17 +32,47 @@ import std.disposition { Disposition, Scaffold, SingleAuthority } import std.decl_ref { DeclarationRef, WholeDeclaration } import tools.merge_admission_capture { capture_walk_attempt_id } -data merge_admission_walk_note: String = "The live merge-admission path is a typed pre-walk capture subprocess followed by stamp_tested_floor and refresh_current_target_and_gate as two singleton success stages in ONE shared stage entry. Capture consumes only walk-attempt/tested-subject identity in a claim_batch child whose address space dies before the ordinary floor; importing the downstream classifier and commit-roster closure before discovery made the authored Negligible profile false, and an in-process capture still retained one worker arena after thread join. The WalkPlan carries two ordering laws instead of shell: stamp is reachable only after the whole ordinary floor and its finalization passed, and stage 2 is unreachable until stage 1 passed. The two non-heavy claims route through population_unit_lane to the MAIN THREAD (corrected per review 47528 — an earlier draft said spawned lanes; claim_executor population_unit_lane and its tests are the placement authority); each stage may still resolve the entry independently, and the disk-visible stage-1 receipt is the intentional barrier, not an in-memory memo promise. The stage-2 claim stays composite because fetch MUST precede classification; two sibling claims would have no ordering guarantee." +// The live merge-admission path is a typed pre-walk capture subprocess followed by +// stamp_tested_floor and refresh_current_target_and_gate as two singleton success stages in ONE +// shared stage entry. Capture consumes only walk-attempt/tested-subject identity in a claim_batch +// child whose address space dies before the ordinary floor; importing the downstream classifier and +// commit-roster closure before discovery made the authored Negligible profile false, and an +// in-process capture still retained one worker arena after thread join. The WalkPlan carries two +// ordering laws instead of shell: stamp is reachable only after the whole ordinary floor and its +// finalization passed, and stage 2 is unreachable until stage 1 passed. The two non-heavy claims +// route through population_unit_lane to the MAIN THREAD (corrected per review 47528 — an earlier +// draft said spawned lanes; claim_executor population_unit_lane and its tests are the placement +// authority); each stage may still resolve the entry independently, and the disk-visible stage-1 +// receipt is the intentional barrier, not an in-memory memo promise. The stage-2 claim stays +// composite because fetch MUST precede classification; two sibling claims would have no ordering +// guarantee. -data merge_admission_checkout_ancestry_note: String = "The tested base is captured from the CHECKOUT'S ancestry, never from the moving target ref. On pull_request and merge_group, GitHub checks out the synthetic merge result: HEAD is the exact tree the floor tests and HEAD^1 is the target-side parent whose tree that result was built on. On push and workflow_dispatch, HEAD itself is the tested target tree. The captured subject persists that object id before substantive floor work. Stamp reads this file and does not re-read HEAD or the target; stage 2 alone fetches the CURRENT target, then compares it with the captured tree. A target advance during the floor therefore produces StaleBase instead of a receipt claiming the new target was tested." +// The tested base is captured from the CHECKOUT'S ancestry, never from the moving target ref. On +// pull_request and merge_group, GitHub checks out the synthetic merge result: HEAD is the exact +// tree the floor tests and HEAD^1 is the target-side parent whose tree that result was built on. On +// push and workflow_dispatch, HEAD itself is the tested target tree. The captured subject persists +// that object id before substantive floor work. Stamp reads this file and does not re-read HEAD or +// the target; stage 2 alone fetches the CURRENT target, then compares it with the captured tree. A +// target advance during the floor therefore produces StaleBase instead of a receipt claiming the +// new target was tested. data merge_admission_pr_number_deferred_note: String = "MergeAdmissionReceiptV2.pr_number is optional and is not an admission key: attempt, head, base tree, roster, and conclusion are the complete classifier inputs. The retired cold stamp process parsed GITHUB_EVENT_PATH through retained jq shell solely to populate this display field. The warm path deliberately writes Absent rather than carry that shell transport into the runtime-present claim. dissolve-on: a typed GitHub event payload projection supplies the optional number; admission behavior is unchanged because no verdict arm reads it." -data merge_admission_current_main_cold_baseline_note: String = "CURRENT-MAIN BEFORE RECEIPT (not the stale pre-unification estimate): GitHub Actions run 30671526012, job 91291716818, head ddbc5fd6aa, 2026-07-31. The cold stamp process occupied 94.26s from the floor group's final receipt at 23:28:39.602Z through ExitSuccess at 23:30:13.861Z. The separate gate step occupied 102.87s from its step start at 23:30:16.977Z through ExitSuccess at 23:31:59.849Z. Combined merge-admission cold-process wall was therefore 197.13s per PR on that current-main receipt. The after number must come from this PR's own on-success receipt; no projected saving is asserted here." +// CURRENT-MAIN BEFORE RECEIPT (not the stale pre-unification estimate): GitHub Actions run +// 30671526012, job 91291716818, head ddbc5fd6aa, 2026-07-31. The cold stamp process occupied 94.26s +// from the floor group's final receipt at 23:28:39.602Z through ExitSuccess at 23:30:13.861Z. The +// separate gate step occupied 102.87s from its step start at 23:30:16.977Z through ExitSuccess at +// 23:31:59.849Z. Combined merge-admission cold-process wall was therefore 197.13s per PR on that +// current-main receipt. The after number must come from this PR's own on-success receipt; no +// projected saving is asserted here. data merge_admission_fetch_stall_deadline_seconds: Seconds = 240 -data merge_admission_fetch_deadline_note: String = "Stage 2 reserves five minutes for the whole postcondition population and gives FetchNoTags a stricter typed 240-second no-progress deadline. The git transport binds that value to http.lowSpeedTime with a one-byte-per-second floor, so a stalled remote read returns through FetchNoTags's located failure result while time remains to write the stage receipt; the 60-minute workflow wrapper is not the fetch watchdog." +// Stage 2 reserves five minutes for the whole postcondition population and gives FetchNoTags a +// stricter typed 240-second no-progress deadline. The git transport binds that value to +// http.lowSpeedTime with a one-byte-per-second floor, so a stalled remote read returns through +// FetchNoTags's located failure result while time remains to write the stage receipt; the 60-minute +// workflow wrapper is not the fetch watchdog. fn read_tested_subject(root: String, attempt_id: WalkAttemptId) -> TestedSubject? { let read = Filesystem.Read( @@ -145,16 +175,35 @@ fn merge_target_refresh_refusal_label(cause: MergeTargetRefreshRefusal) -> Strin data merge_admission_refresh_refusal_wire_relpath: String = "target/merge-admission-refresh-refusal.txt" -data merge_admission_refresh_refusal_wire_seed_pairing: String = "PAIRED LITERAL (review 47596): claim_executor.rs MERGE_ADMISSION_REFRESH_REFUSAL_WIRE reads this exact relpath at the stage-failure line — the seed cannot import a .dag datum, so a rename here lands with the paired seed edit or the stage failure degrades to detail-without-wire, never a silent success. Same pairing class as floor-population-budget-refusal.txt, under walk_plan_run_stage_claim_executor_seed_deferral." +// PAIRED LITERAL (review 47596): claim_executor.rs MERGE_ADMISSION_REFRESH_REFUSAL_WIRE reads this +// exact relpath at the stage-failure line — the seed cannot import a .dag datum, so a rename here +// lands with the paired seed edit or the stage failure degrades to detail-without-wire, never a +// silent success. Same pairing class as floor-population-budget-refusal.txt, under +// walk_plan_run_stage_claim_executor_seed_deferral. -data merge_admission_refresh_refusal_wire_note: String = "TYPED CAUSE FOR THE STAGE-2 CLAIM BOUNDARY (review 47542 finding 3, made blocking by CI run 30764945450: refresh_current_target_and_gate returned bare Bool false on srv4-04 with all seven failure causes conflated, and the red was undiagnosable from the log). Same pattern as merge_admission_capture_refusal_wire_note: the claim channel is a Bool, so the core returns the typed sum and the Bool projection durably writes the labeled cause to target/merge-admission-refresh-refusal.txt before returning false; claim_executor's stage-failure line reads the wire. The fetch and target-tree arms carry the operation's own stderr/cause so a runner-side git failure names itself." +// TYPED CAUSE FOR THE STAGE-2 CLAIM BOUNDARY (review 47542 finding 3, made blocking by CI run +// 30764945450: refresh_current_target_and_gate returned bare Bool false on srv4-04 with all seven +// failure causes conflated, and the red was undiagnosable from the log). Same pattern as +// merge_admission_capture_refusal_wire_note: the claim channel is a Bool, so the core returns the +// typed sum and the Bool projection durably writes the labeled cause to +// target/merge-admission-refresh-refusal.txt before returning false; claim_executor's stage-failure +// line reads the wire. The fetch and target-tree arms carry the operation's own stderr/cause so a +// runner-side git failure names itself. func refresh_wire_path() -> FilePath { let root = git.Inspect.Toplevel().path as String concat(concat(root, "/"), merge_admission_refresh_refusal_wire_relpath) as FilePath } -data merge_admission_refresh_ratchet_off_widening_note: String = "DECLARED SCAFFOLD (review 47596 finding 3): the final else arm of merge_target_refresh returns MergeTargetRefreshed WITHOUT consuming the classifier verdict whenever merge_freshness_verdict_is_consumed_to_block() is false — the shadow-phase ratchet the operator's fleet-gating policy requires before a mechanism may red unrelated PRs. While the ratchet is off, the verdict is still computed and the RefreshVerdictDenied arm stays type-live, but stage 2 cannot deny on staleness; this is a visible, bounded widening, not a silent fail-open. Dissolution trigger: the merge_freshness_verdict_is_consumed_to_block flip in gunbc.ci_failure_class — the same commit that turns the flag on deletes this else arm (verdict match becomes unconditional) and this row with it." +// DECLARED SCAFFOLD (review 47596 finding 3): the final else arm of merge_target_refresh returns +// MergeTargetRefreshed WITHOUT consuming the classifier verdict whenever +// merge_freshness_verdict_is_consumed_to_block() is false — the shadow-phase ratchet the operator's +// fleet-gating policy requires before a mechanism may red unrelated PRs. While the ratchet is off, +// the verdict is still computed and the RefreshVerdictDenied arm stays type-live, but stage 2 +// cannot deny on staleness; this is a visible, bounded widening, not a silent fail-open. +// Dissolution trigger: the merge_freshness_verdict_is_consumed_to_block flip in +// gunbc.ci_failure_class — the same commit that turns the flag on deletes this else arm (verdict +// match becomes unconditional) and this row with it. data merge_admission_refresh_ratchet_off_widening_disposition: Disposition = Scaffold { dissolves_to: SingleAuthority, diff --git a/dag/gunbc/instruments/module_impact_query_front_door.dag b/dag/gunbc/instruments/module_impact_query_front_door.dag index 8188d2bd10b..127d9eff0f1 100644 --- a/dag/gunbc/instruments/module_impact_query_front_door.dag +++ b/dag/gunbc/instruments/module_impact_query_front_door.dag @@ -65,7 +65,12 @@ import v2.lens.module_impact_query { module_impact_reading, } -data front_door_note: String = "The real-corpus front door for the impact reading (operator ruling 2026-08-08, recut C): a person asks 'what depends on this?' about the LIVE tree and gets a rendered answer, so the reading's first consumer is not only its own witness suite. Run it with: gunbc run --source-root dag --source-root src/v2 --entry dag/gunbc/instruments/module_impact_query_front_door.dag --function dependents_of_module_graph_report (or dependents_of_path_report). This is a tools entry, not a new Rust CLI arm - no seed growth." +// The real-corpus front door for the impact reading (operator ruling 2026-08-08, recut C): a person +// asks 'what depends on this?' about the LIVE tree and gets a rendered answer, so the reading's +// first consumer is not only its own witness suite. Run it with: gunbc run --source-root dag +// --source-root src/v2 --entry dag/gunbc/instruments/module_impact_query_front_door.dag --function +// dependents_of_module_graph_report (or dependents_of_path_report). This is a tools entry, not a +// new Rust CLI arm - no seed growth. data no_lines: List = [] @@ -75,9 +80,36 @@ data front_door_importer_roots: List = ["dag", "src/v2"] data front_door_exclude_substrings: List = [] -data front_door_admissions_note: String = "The standing the front door reports is NOT asserted here: these are the two dependency-projection capabilities that gunbc.namespace_reference_derived_closure_admission reports Unavailable, carried as the admission rows the reading consumes, so a live query renders PoolIndependentProjectionUnavailable with the trigger that owns the climb. When the namespace lane establishes pool-independent projection, this roster is what changes and the rendered standing follows automatically rather than this module being edited to say something new. Dissolve-on: the front door reads the production admissions directly once they are projectable under the dag/src-v2 source roots (they live under src/v1 today)." - -data front_door_dogfooding_cost_receipt_note: String = "MEASURED COST OF CONSUMING THIS READING PER PR, and why the per-PR CI consumer was withdrawn (2026-08-09). A PR-close delivery check was built on this front door and run on real pull requests; it is closed unmerged at gunbc#8064, branch head afc52ff39ffc5405e61ede484ceb32a31cd0288c, which is the quarry pointer for the mechanism and its witnesses. Three facts it measured are kept because they price the displaced cost that declaration-grain consumption would remove, and none of them is recoverable by re-reading this module. ONE: on ten ordinary subjects, eight modification-only diffs rendered DeclarationGrainUnavailable, so the module grain named in front_door_disclosure_note is not a rare edge but the common case for ordinary edits. TWO: the delivery step cost five minutes thirty-five seconds of wall on the ordinary PR path, measured as a CI step interval rather than locally (a full front-door resolve exceeds a session container lifetime, so a local timing is not obtainable and any local figure would be a guess). THREE: splitting the fold so that a diff with no added paths takes a declaration census and never builds the corpus-wide edge index is the correct shape and was not sufficient — the resolve of this module's own closure is paid before any census begins. The operator's criterion was that a non-negligible common-path cost closes the consumer rather than levying a permanent CI tax for a mostly-undecidable report, and five minutes thirty-five seconds fired it. What was withdrawn is the per-PR step, NOT the acceptance question it asked, which is whether anything consumes what a change built; that question survives for humans and for the eventual lens suite. Dissolve-on: a cheap exact-subject fact pack makes the reading consumable inside a warm budget, at which point these figures become the before side of its receipt rather than a standing caution." +// The standing the front door reports is NOT asserted here: these are the two dependency-projection +// capabilities that gunbc.namespace_reference_derived_closure_admission reports Unavailable, +// carried as the admission rows the reading consumes, so a live query renders +// PoolIndependentProjectionUnavailable with the trigger that owns the climb. When the namespace +// lane establishes pool-independent projection, this roster is what changes and the rendered +// standing follows automatically rather than this module being edited to say something new. +// Dissolve-on: the front door reads the production admissions directly once they are projectable +// under the dag/src-v2 source roots (they live under src/v1 today). + +// MEASURED COST OF CONSUMING THIS READING PER PR, and why the per-PR CI consumer was withdrawn +// (2026-08-09). A PR-close delivery check was built on this front door and run on real pull +// requests; it is closed unmerged at gunbc#8064, branch head +// afc52ff39ffc5405e61ede484ceb32a31cd0288c, which is the quarry pointer for the mechanism and its +// witnesses. Three facts it measured are kept because they price the displaced cost that +// declaration-grain consumption would remove, and none of them is recoverable by re-reading this +// module. ONE: on ten ordinary subjects, eight modification-only diffs rendered +// DeclarationGrainUnavailable, so the module grain named in front_door_disclosure_note is not a +// rare edge but the common case for ordinary edits. TWO: the delivery step cost five minutes +// thirty-five seconds of wall on the ordinary PR path, measured as a CI step interval rather than +// locally (a full front-door resolve exceeds a session container lifetime, so a local timing is not +// obtainable and any local figure would be a guess). THREE: splitting the fold so that a diff with +// no added paths takes a declaration census and never builds the corpus-wide edge index is the +// correct shape and was not sufficient — the resolve of this module's own closure is paid before +// any census begins. The operator's criterion was that a non-negligible common-path cost closes the +// consumer rather than levying a permanent CI tax for a mostly-undecidable report, and five minutes +// thirty-five seconds fired it. What was withdrawn is the per-PR step, NOT the acceptance question +// it asked, which is whether anything consumes what a change built; that question survives for +// humans and for the eventual lens suite. Dissolve-on: a cheap exact-subject fact pack makes the +// reading consumable inside a warm budget, at which point these figures become the before side of +// its receipt rather than a standing caution. data front_door_admissions: List = [ ReferenceDerivedClosureUnavailable { @@ -257,7 +289,10 @@ fn count_text(xs: List) -> String { data front_door_default_subject: String = "v2.lens.module_graph" -data front_door_render_cost_note: String = "front_door_nodes() is evaluated ONCE per report and threaded into render_reading. An earlier draft called it inside the per-reached-module fold, which recomputed the live module-declaration facts once per reached module - the copied-accumulator cost shape DESIGN 6 forbids regardless of realized n, and on a corpus-sized reading that is hundreds of live fact scans for one report." +// front_door_nodes() is evaluated ONCE per report and threaded into render_reading. An earlier +// draft called it inside the per-reached-module fold, which recomputed the live module-declaration +// facts once per reached module - the copied-accumulator cost shape DESIGN 6 forbids regardless of +// realized n, and on a corpus-sized reading that is hundreds of live fact scans for one report. fn dependents_of_module_graph_report() -> String { render_reading( @@ -275,7 +310,15 @@ fn dependents_of_path_report() -> String { ) } -data changed_surface_front_door_note: String = "CAPSULE 2 applied consumer, reworked under the operator ruling 2026-08-08. Changed paths AND departed paths both come from v2.workflow.floor_diff_observe, the existing single authority for the CI diff observation - this adds no second diff producer. Deletions are no longer discarded: a departed path becomes a ResolveInBase subject, which cannot resolve against the head population and therefore lands as a typed BaseGraphUnavailable unresolved subject, contaminating the outcome to Partial. A diff-observation FAILURE refuses with the observation's own reason and computes NO impact - it never widens to the whole corpus. Run: gunbc run --source-root dag --source-root src/v2 --entry dag/gunbc/instruments/module_impact_query_front_door.dag --function changed_surface_impact_report" +// CAPSULE 2 applied consumer, reworked under the operator ruling 2026-08-08. Changed paths AND +// departed paths both come from v2.workflow.floor_diff_observe, the existing single authority for +// the CI diff observation - this adds no second diff producer. Deletions are no longer discarded: a +// departed path becomes a ResolveInBase subject, which cannot resolve against the head population +// and therefore lands as a typed BaseGraphUnavailable unresolved subject, contaminating the outcome +// to Partial. A diff-observation FAILURE refuses with the observation's own reason and computes NO +// impact - it never widens to the whole corpus. Run: gunbc run --source-root dag --source-root +// src/v2 --entry dag/gunbc/instruments/module_impact_query_front_door.dag --function +// changed_surface_impact_report fn render_changed_path_seeds(row: ChangedPathSeeds) -> String { if any(xs: row.seeds, predicate: fn(_) { true }) { diff --git a/dag/gunbc/instruments/multi_module_compile_fixture.dag b/dag/gunbc/instruments/multi_module_compile_fixture.dag index 1805ca86a1e..0d48f20ded2 100644 --- a/dag/gunbc/instruments/multi_module_compile_fixture.dag +++ b/dag/gunbc/instruments/multi_module_compile_fixture.dag @@ -21,13 +21,51 @@ import gunbc.compile_diagnostic_census { CompileDiagnosticCensusRow } // IT IS AN INSTRUMENT AND NOT A GATE. Its outcome reports what the compile decided; nothing here // judges acceptance, and no workflow invokes it. Callers judge. -data multi_module_compile_fixture_isolation_note: String = "CORPUS ISOLATION IS BY CONSTRUCTION, NOT BY A FLAG, and that is the property the whole instrument rests on. The supplied manifest IS the source vector handed to the pipeline: there is no code path in the host realization that consults a module index, so a fixture module may reuse a corpus module's SPELLING and bind its own declaration. THE OPEN QUESTION THIS ANSWERS, recorded because upstream did not know the answer and asked for it either way: under whole-corpus floor preparation the required floor folds every witness through ONE prepared subject, so a name collision between a witness and the corpus is unconditional there. That preparation is a fact about how WITNESSES are compiled, not about this nested compile - the prepared authority is consulted by unrelated censuses and never by compile_to_resolved - so a fixture compiled through this instrument is isolated even when the caller itself was prepared whole-corpus. Control 5 of the witness measures exactly that rather than assuming it." +// CORPUS ISOLATION IS BY CONSTRUCTION, NOT BY A FLAG, and that is the property the whole instrument +// rests on. The supplied manifest IS the source vector handed to the pipeline: there is no code +// path in the host realization that consults a module index, so a fixture module may reuse a corpus +// module's SPELLING and bind its own declaration. THE OPEN QUESTION THIS ANSWERS, recorded because +// upstream did not know the answer and asked for it either way: under whole-corpus floor +// preparation the required floor folds every witness through ONE prepared subject, so a name +// collision between a witness and the corpus is unconditional there. That preparation is a fact +// about how WITNESSES are compiled, not about this nested compile - the prepared authority is +// consulted by unrelated censuses and never by compile_to_resolved - so a fixture compiled through +// this instrument is isolated even when the caller itself was prepared whole-corpus. Control 5 of +// the witness measures exactly that rather than assuming it. -data multi_module_compile_fixture_phase_standing_note: String = "PHASE-GRAINED STANDING IS THE POINT OF THE COPRODUCT. FixtureInstrumentRefused is the HARNESS's fault - a ragged manifest, a duplicate path, an entry naming no supplied module, a panic. FixtureCompileRefused is the SUBJECT's fault and carries the compiler's own judgment. FixtureCompileCompleted is the subject passing. Collapsing the first two hands a broken harness the compiler's message, which is the not-applicable-versus-malformed conflation DESIGN records as a recurring failure mode, applied to the instrument itself: one reason symbol over 'the input is wrong' and 'this instrument had nothing to say about it', which have opposite owners and opposite repairs. EXECUTION PROVENANCE IS STRUCTURAL AND NOT A FIELD ANYONE MUST REMEMBER TO READ: FixtureCompileCompleted is constructed at exactly one place in the host, after the emit returned and after the blocking population was counted, so an unreached or killed compile HAS NO SPELLING in which it renders as zero diagnostics. That is the positive-artifact rule moved into the type rather than asserted about it." +// PHASE-GRAINED STANDING IS THE POINT OF THE COPRODUCT. FixtureInstrumentRefused is the HARNESS's +// fault - a ragged manifest, a duplicate path, an entry naming no supplied module, a panic. +// FixtureCompileRefused is the SUBJECT's fault and carries the compiler's own judgment. +// FixtureCompileCompleted is the subject passing. Collapsing the first two hands a broken harness +// the compiler's message, which is the not-applicable-versus-malformed conflation DESIGN records as +// a recurring failure mode, applied to the instrument itself: one reason symbol over 'the input is +// wrong' and 'this instrument had nothing to say about it', which have opposite owners and opposite +// repairs. EXECUTION PROVENANCE IS STRUCTURAL AND NOT A FIELD ANYONE MUST REMEMBER TO READ: +// FixtureCompileCompleted is constructed at exactly one place in the host, after the emit returned +// and after the blocking population was counted, so an unreached or killed compile HAS NO SPELLING +// in which it renders as zero diagnostics. That is the positive-artifact rule moved into the type +// rather than asserted about it. -data multi_module_compile_fixture_digest_note: String = "THE TWO DIGESTS TRAVEL WITH THE RESULT so a receipt quoting this instrument names the subject it measured and the compiler that measured it. source_digest is a structural fnv1a64 over the entry spelling and every (path, content) pair in the order supplied - a reordering is a different subject and a whitespace edit is a different subject. compiler_digest is resolved_graph_cache transform_content_digest, the content hash of the running compiler binary, which is already the single authority every identity key in the seed consumes for compiler identity rather than a second executable read. Both are absent from the FixtureInstrumentRefused arm on purpose: that arm reports that no subject was formed, and stamping a digest on it would name a measurement that did not happen." +// THE TWO DIGESTS TRAVEL WITH THE RESULT so a receipt quoting this instrument names the subject it +// measured and the compiler that measured it. source_digest is a structural fnv1a64 over the entry +// spelling and every (path, content) pair in the order supplied - a reordering is a different +// subject and a whitespace edit is a different subject. compiler_digest is resolved_graph_cache +// transform_content_digest, the content hash of the running compiler binary, which is already the +// single authority every identity key in the seed consumes for compiler identity rather than a +// second executable read. Both are absent from the FixtureInstrumentRefused arm on purpose: that +// arm reports that no subject was formed, and stamping a digest on it would name a measurement that +// did not happen. -data multi_module_compile_fixture_scope_note: String = "SCOPE, stated so a receipt cannot claim coverage it does not have (DESIGN 4b: a class's rung is the MINIMUM across its in-scope paths). This is the v1 pipeline to the RUST render target over an authored multi-module subject. It observes NOTHING about the interpreter's disposition of the same program, nothing about other emission targets, and nothing about corpus-grain prevalence. Unlike the single-module census it does NOT arm with_type_ref_hit_ne_bind_measure: the census arms that bracket to sharpen masked type refs against a corpus pool this instrument does not have, so arming it here would be a knob with no subject. WHAT module_count MEANS: the number of modules the RESOLVE stage produced, read from the resolved graph - not the length of the supplied list. A control comparing it against the manifest is therefore a join between two different producers rather than measure() == measure()." +// SCOPE, stated so a receipt cannot claim coverage it does not have (DESIGN 4b: a class's rung is +// the MINIMUM across its in-scope paths). This is the v1 pipeline to the RUST render target over an +// authored multi-module subject. It observes NOTHING about the interpreter's disposition of the +// same program, nothing about other emission targets, and nothing about corpus-grain prevalence. +// Unlike the single-module census it does NOT arm with_type_ref_hit_ne_bind_measure: the census +// arms that bracket to sharpen masked type refs against a corpus pool this instrument does not +// have, so arming it here would be a knob with no subject. WHAT module_count MEANS: the number of +// modules the RESOLVE stage produced, read from the resolved graph - not the length of the supplied +// list. A control comparing it against the manifest is therefore a join between two different +// producers rather than measure() == measure(). type FixtureSource { path: NonEmptyStr diff --git a/dag/gunbc/instruments/namespace_import_closure_behavioral_transport.dag b/dag/gunbc/instruments/namespace_import_closure_behavioral_transport.dag index b73875e0db6..575071c3da6 100644 --- a/dag/gunbc/instruments/namespace_import_closure_behavioral_transport.dag +++ b/dag/gunbc/instruments/namespace_import_closure_behavioral_transport.dag @@ -20,7 +20,29 @@ import tools.host_prelude { witness_bin, witness_bin_ready, witness_bin_release_ data nic_doc: String = "Wet cargo-compiling receipt for the Gate-1 emit import-closure derivation (emit_import_closure_root, PR 6960): a NAMESPACE module — zero import statements, the v2 corpus shape after namespace wave-1 — that references cross-module names emits VALID, cargo-COMPILING Rust. The regression it guards: before the reference_derived_use_lines pass in v1.compiler.emit_rust, the seed emitter derived Rust use-lines ONLY from authored import name-lists, so an import-free module referencing a sibling emitted bare unqualified names with no use-line and no inline qualification — unresolvable Rust (E0425/E0433). Regen does NOT guard this class (the seed corpus is import-based, so its emit is unchanged by the pass), which is why this receipt is REQUIRED coverage rather than redundant with the self-host fixed point. Live host effects (real gunbc emit, cargo build), so it is CLASSIFIED onto the falsifier Wet follow-on batch (falsifier_self_host_wet_entries) rather than per-PR hermetic discovery -- and classification is membership, not execution: falsifier.yml was deleted at 611fd02770 (#8283, 2026-08-15) and std.witness_admission witness_cadence_has_scheduled_route answers FalsifierSelfHostWet false, so nothing runs this receipt today (censused in gunbc.deleted_cadence_reference_census)." -data nic_discriminating_control_note: String = "DESIGN section 5 discriminating control — the receipt is an A/B on exactly one axis, not a lone green. Both arms write the SAME two file paths into the SAME scratch layout and run the SAME emit+build chain; the ONLY difference is whether the provider module actually DEFINES the names the consumer references. The green arm (nic_provider_source) defines PilotColor / pilot_provider_flag / pilot_provider_default and must cargo-build; the red arm (nic_provider_missing_names_source) defines unrelated names instead, so the consumer's cross-module references are unresolvable and the chain MUST refuse. Because the two arms differ in nothing but cross-module name availability, a green red-arm would mean the fixture never exercised cross-module resolution at all — the vacuity this control exists to catch. What this control does NOT prove is the pass itself: proving RED-without-the-pass needs a second gunbc built from the pre-pass emitter, which is not a committable artifact. THAT HALF IS CURRENTLY UNMECHANIZED — say it plainly rather than let a path imply coverage. It was staged by a hand-run dev script (scripts/namespace_import_closure_witness.sh, deleted 2026-07-23 with the rest of scripts/); a discriminating control whose only realization is a shell file nobody executes on any cadence is coverage by illusion, not a red control (DESIGN section 5), so the file's existence was never the proof its citation implied. The method, if reproducing by hand: build the control emitter from origin/main's v1_compiler_emit_rust.rs, emit the same fixture closure with each binary, and require the with-pass closure to cargo-build and the no-pass closure to fail on unresolved names (E0425/E0433). The realization that closes this honestly is the already-declared alternative — a modeled cargo-compiling wet witness enrolled in falsifier_self_host_wet_entries, which runs on a cadence and counts its own red — and the gap dissolves entirely when 04_resolve.dag promotes UnlistedImportUse to is_error_diagnostic=true and the reference_derived_use_lines pass is deleted." +// DESIGN section 5 discriminating control — the receipt is an A/B on exactly one axis, not a lone +// green. Both arms write the SAME two file paths into the SAME scratch layout and run the SAME +// emit+build chain; the ONLY difference is whether the provider module actually DEFINES the names +// the consumer references. The green arm (nic_provider_source) defines PilotColor / +// pilot_provider_flag / pilot_provider_default and must cargo-build; the red arm +// (nic_provider_missing_names_source) defines unrelated names instead, so the consumer's +// cross-module references are unresolvable and the chain MUST refuse. Because the two arms differ +// in nothing but cross-module name availability, a green red-arm would mean the fixture never +// exercised cross-module resolution at all — the vacuity this control exists to catch. What this +// control does NOT prove is the pass itself: proving RED-without-the-pass needs a second gunbc +// built from the pre-pass emitter, which is not a committable artifact. THAT HALF IS CURRENTLY +// UNMECHANIZED — say it plainly rather than let a path imply coverage. It was staged by a hand-run +// dev script (scripts/namespace_import_closure_witness.sh, deleted 2026-07-23 with the rest of +// scripts/); a discriminating control whose only realization is a shell file nobody executes on any +// cadence is coverage by illusion, not a red control (DESIGN section 5), so the file's existence +// was never the proof its citation implied. The method, if reproducing by hand: build the control +// emitter from origin/main's v1_compiler_emit_rust.rs, emit the same fixture closure with each +// binary, and require the with-pass closure to cargo-build and the no-pass closure to fail on +// unresolved names (E0425/E0433). The realization that closes this honestly is the already-declared +// alternative — a modeled cargo-compiling wet witness enrolled in falsifier_self_host_wet_entries, +// which runs on a cadence and counts its own red — and the gap dissolves entirely when +// 04_resolve.dag promotes UnlistedImportUse to is_error_diagnostic=true and the +// reference_derived_use_lines pass is deleted. data nic_scaffold_dissolution_trigger: DissolutionCondition = unbound_dissolution(description: "SCAFFOLD dissolve-on: nic_provider_source / nic_provider_missing_names_source / nic_consumer_source are hand-authored fixture module sources (String carriers), the meta-harness around the receipt rather than a forked emit path — the emit itself is the real gunbc binary on real bytes. They are embedded rather than committed as .dag files on purpose: the consumer deliberately references a sibling WITHOUT importing it, so committing it under a discovered source root would feed a deliberately-unresolved module to the whole-tree compile-clean gate. DISSOLVES WHEN the step-2 typed refusal lands in the resolver (v1 04_resolve: UnlistedImportUse promoted to an error, or the resolver emitting the use-line intent directly), which deletes the reference_derived_use_lines pass and this receipt with it.") @@ -34,11 +56,27 @@ data nic_consumer_partial_import_source: String = "module witness.pilot.emit_con data nic_consumer_partial_import_type_source: String = "module witness.pilot.emit_consumer_partial_type\n\nimport witness.pilot.emit_provider { PilotColor }\n\nfn pilot_consumer_partial_type_default() -> PilotWidget \{ pilot_provider_widget_default() \}\n" -data nic_local_homonym_arm_note: String = "Discriminating arm for the local-decl exclusion in reference_derived_use_lines (the E0255 std_dup emitter root, fixed 2026-07-23 smart-crane-730): the consumer DECLARES a type alias whose name collides with a provider enum (PilotColor — mirroring dag/std types.dag List/Map vs their src/v2/std twins) and references it bare in a signature. The bare-name registry row for PilotColor belongs to the provider, so before the exclusion the pass synthesized `pub use crate::witness_pilot_emit_provider::PilotColor;` INTO a module that also emits `pub type PilotColor = ...` — rustc E0255, cargo refuses. With the exclusion the module's own declaration wins and no use-line is synthesized. Verified by execution in both directions 2026-07-23: post-fix binary emits no PilotColor use-line and the crate builds; the pre-fix binary (origin/main control worktree) emits the self-colliding line. Regressing the exclusion turns this arm red by the same E0255." +// Discriminating arm for the local-decl exclusion in reference_derived_use_lines (the E0255 std_dup +// emitter root, fixed 2026-07-23 smart-crane-730): the consumer DECLARES a type alias whose name +// collides with a provider enum (PilotColor — mirroring dag/std types.dag List/Map vs their +// src/v2/std twins) and references it bare in a signature. The bare-name registry row for +// PilotColor belongs to the provider, so before the exclusion the pass synthesized `pub use +// crate::witness_pilot_emit_provider::PilotColor;` INTO a module that also emits `pub type +// PilotColor = ...` — rustc E0255, cargo refuses. With the exclusion the module's own declaration +// wins and no use-line is synthesized. Verified by execution in both directions 2026-07-23: +// post-fix binary emits no PilotColor use-line and the crate builds; the pre-fix binary +// (origin/main control worktree) emits the self-colliding line. Regressing the exclusion turns this +// arm red by the same E0255. data nic_consumer_local_homonym_source: String = "module witness.pilot.emit_consumer_local_homonym\n\ntype PilotColor = PilotWidget\n\nfn pilot_local_default() -> PilotColor \{ pilot_provider_widget_default() \}\n" -data nic_scratch_under_workspace_root_note: String = "The fixture SOURCE tree is mktemp'd under the workspace root (target/, already gitignored), not under /tmp, because the v1 CLI's repo-path grant refuses a source path outside the process workspace root (cli_run repo_relative_path_normalized, modeled by gunbc.cli_run_repo_grant) — a bare mktemp -d source root panics the emit. The OUTPUT dir has no such constraint and stays a plain mktemp. target/ rather than a fixed path so concurrent runs cannot collide and so a mid-run auto-commit can never capture the fixture (.gitignore is itself a generated artifact — hand-editing it drifts the artifact gate)." +// The fixture SOURCE tree is mktemp'd under the workspace root (target/, already gitignored), not +// under /tmp, because the v1 CLI's repo-path grant refuses a source path outside the process +// workspace root (cli_run repo_relative_path_normalized, modeled by gunbc.cli_run_repo_grant) — a +// bare mktemp -d source root panics the emit. The OUTPUT dir has no such constraint and stays a +// plain mktemp. target/ rather than a fixed path so concurrent runs cannot collide and so a mid-run +// auto-commit can never capture the fixture (.gitignore is itself a generated artifact — +// hand-editing it drifts the artifact gate). // THE FAIL-OPEN THIS MIGRATION CLOSES, AND IT IS THE RED ARM RATHER THAN THE GREEN ONES. // diff --git a/dag/gunbc/instruments/prose_row_introduction_gate.dag b/dag/gunbc/instruments/prose_row_introduction_gate.dag index 40ba82539e2..7242eb347fa 100644 --- a/dag/gunbc/instruments/prose_row_introduction_gate.dag +++ b/dag/gunbc/instruments/prose_row_introduction_gate.dag @@ -9,7 +9,7 @@ import gunbc.prose_row_frontier { ProseRowRef, ProseRowDiffObservation, ProseRowsObserved, ProseRowDiffUnreadable, prose_row_diff_observation, prose_rows_introduced, - prose_rows_refused, prose_rows_out_of_scope, prose_rows_render, + prose_rows_render, } // THIS GATE DOES NOT CURRENTLY RUN, and every present-tense enforcement sentence below describes @@ -59,17 +59,10 @@ fn prose_row_gate_observation() -> ProseRowDiffObservation { } } -fn prose_row_refusal_reason(refused: List, residue: List) -> String { - let residue_count = residue |> count +fn prose_row_refusal_reason(refused: List) -> String { concat( - "prose row introduction gate: THIS CHANGE introduces prose-only data NAME_note: String declaration(s) under a path whose prose has already been migrated to authored // annotations (gunbc.prose_row_frontier prose_row_migration_scope). Move the irreducible rationale into a leading // block above the declaration it explains, and put any date, review id, PR number, SHA, status, count or dissolution condition in a typed carrier (DESIGN section 4c). Refused: ", - concat( - prose_rows_render(rows: refused), - concat( - " | counted out-of-scope residue, reported and NOT enforced, rows: ", - concat(to_string(residue_count), concat(" — ", prose_rows_render(rows: residue))) - ) - ) + "prose row introduction gate: THIS CHANGE introduces prose-only data NAME_note: String declaration(s). Move the irreducible rationale into a leading // block above the declaration it explains, and put any date, review id, PR number, SHA, status, count or dissolution condition in a typed carrier (DESIGN section 4c). Refused: ", + prose_rows_render(rows: refused) ) } @@ -84,15 +77,9 @@ fn prose_row_introduction_verdict(observation: ProseRowDiffObservation) -> Proce reason: concat("prose row introduction gate: ", concat(reason, " — refusing, never skipping (DESIGN section 5)")) ) ProseRowsObserved { added: added, removed: removed } => { - let introduced = prose_rows_introduced(added: added, removed: removed) - let refused = prose_rows_refused(introduced: introduced) + let refused = prose_rows_introduced(added: added, removed: removed) if (refused |> count) > 0 { - exit_failure( - reason: prose_row_refusal_reason( - refused: refused, - residue: prose_rows_out_of_scope(introduced: introduced) - ) - ) + exit_failure(reason: prose_row_refusal_reason(refused: refused)) } else { ExitSuccess } @@ -124,41 +111,22 @@ fn prose_row_receipt_content(observation: ProseRowDiffObservation) -> String { match observation { ProseRowDiffUnreadable { reason: reason } => concat("disposition\tpath\tdecl_name\nunreadable\t\t", reason) - ProseRowsObserved { added: added, removed: removed } => { - let introduced = prose_rows_introduced(added: added, removed: removed) + ProseRowsObserved { added: added, removed: removed } => concat( "disposition\tpath\tdecl_name\n", - concat( - join( - prose_rows_refused(introduced: introduced) - |> map(row => prose_row_receipt_row(row: row, disposition: "refused_in_scope")), - "\n" - ), - concat( - "\n", - join( - prose_rows_out_of_scope(introduced: introduced) - |> map(row => prose_row_receipt_row(row: row, disposition: "counted_out_of_scope")), - "\n" - ) - ) + join( + prose_rows_introduced(added: added, removed: removed) + |> map(row => prose_row_receipt_row(row: row, disposition: "refused")), + "\n" ) ) - } } } -// A FAILED RECEIPT WRITE REFUSES, and finding out why cost a probe run. Filesystem.Write returns -// `success`, and an earlier revision of this body ignored it: in a worktree with no `target/` directory -// the write silently did nothing and the gate went on to report its verdict, so the counted residue — -// the ONLY observability this bounded scope has — could vanish with nothing counting the loss. That is -// the deficit-with-zero-frequency shape DESIGN 5 forbids, authored into the very mechanism added to -// prevent it. -// -// So the write is checked and its failure is a typed, located refusal naming the path and the host's -// own error. The alternative considered and rejected was creating the directory: that turns a missing -// scratch dir into a silent success and puts this gate back where it started, guessing about state it -// did not observe. In CI `target/` always exists because the release build precedes the floor. +// A FAILED RECEIPT WRITE REFUSES. Filesystem.Write returns `success`; an earlier revision ignored it, +// so in a worktree with no `target/` the write silently did nothing and the gate still reported a +// verdict. Creating the directory was rejected: it turns a missing scratch dir into a silent success. +// In CI `target/` always exists because the release build precedes the floor. func run_prose_row_introduction_gate_body() -> ProcessExit { let observation = prose_row_gate_observation() let written = Filesystem.Write( @@ -174,7 +142,7 @@ func run_prose_row_introduction_gate_body() -> ProcessExit { concat( prose_row_receipt_path, concat( - " — the out-of-scope prose population is the only observability this path-scoped wall has, so a run that cannot record it refuses rather than reporting a clean tree (DESIGN section 5). Host error: ", + " — a run that cannot record its receipt refuses rather than reporting a clean tree (DESIGN section 5). Host error: ", written.error ) ) @@ -185,7 +153,7 @@ func run_prose_row_introduction_gate_body() -> ProcessExit { // The loudness half, mirroring tools.extdeps_scope_placement_gate: the floor witness collapses this // gate's ProcessExit to Bool, so without a companion a refusal reads as `returned Bool(false)` and -// names neither the offending row nor the residue. This projects the SAME verdict rather than deciding +// names no offending row. This projects the SAME verdict rather than deciding // again, so there is never a second refusal authority to disagree with the first. fn prose_row_failure_reason(exit: ProcessExit) -> String { match exit { diff --git a/dag/gunbc/instruments/publication_publisher.dag b/dag/gunbc/instruments/publication_publisher.dag index f7b9b66ee2f..829dbdc8399 100644 --- a/dag/gunbc/instruments/publication_publisher.dag +++ b/dag/gunbc/instruments/publication_publisher.dag @@ -22,7 +22,11 @@ import gunbc.publication_decision { publication_decision_authorizes_newly_public, } -data publication_publisher_note: String = "Slice-1 sole modeled publisher (model + shadow only). Ordinary ModeledSessionId values are OrdinarySession and carry no PublicationAuthority. Exactly one sole_modeled_publisher_session may consume a change-locally admitted PublicProjectionPlan derived from PublicationPolicy — never a pathname census or placement-gate roster. Ordinary sessions do NOT yet lose live public-write capability in this slice; credentials and push machinery are unchanged." +// Slice-1 sole modeled publisher (model + shadow only). Ordinary ModeledSessionId values are +// OrdinarySession and carry no PublicationAuthority. Exactly one sole_modeled_publisher_session may +// consume a change-locally admitted PublicProjectionPlan derived from PublicationPolicy — never a +// pathname census or placement-gate roster. Ordinary sessions do NOT yet lose live public-write +// capability in this slice; credentials and push machinery are unchanged. type ModeledSessionId = NonEmptyStr where brand("ModeledSessionId") diff --git a/dag/gunbc/instruments/publication_push_shadow.dag b/dag/gunbc/instruments/publication_push_shadow.dag index 446fdd271ce..afd9e8832de 100644 --- a/dag/gunbc/instruments/publication_push_shadow.dag +++ b/dag/gunbc/instruments/publication_push_shadow.dag @@ -18,7 +18,10 @@ import tools.publication_publisher { admit_public_write, } -data publication_push_shadow_note: String = "Slice-1 shadow witness: read-only stopped-line-audit observer replaying observed public pushes through the modeled admission stack. Reports typed, located, counted refusals; does not green, gate, or mutate live push paths. Ordinary sessions still reach the public remote in this slice — the ledger sizes the enforcement stage." +// Slice-1 shadow witness: read-only stopped-line-audit observer replaying observed public pushes +// through the modeled admission stack. Reports typed, located, counted refusals; does not green, +// gate, or mutate live push paths. Ordinary sessions still reach the public remote in this slice — +// the ledger sizes the enforcement stage. type ObservedPublicPush { session: ModeledSessionId diff --git a/dag/gunbc/instruments/rust_item_census_instrument.dag b/dag/gunbc/instruments/rust_item_census_instrument.dag index 89540110b76..286b8a4227b 100644 --- a/dag/gunbc/instruments/rust_item_census_instrument.dag +++ b/dag/gunbc/instruments/rust_item_census_instrument.dag @@ -48,7 +48,10 @@ import v2.std.algebra { length, list_append, list_map } // of the two happened -- the distinction is carried in the text rather than in the status, because // an admission gate that exited zero on an unobservable change would be the empty-observation // narrow arriving through the exit code. -data rust_item_census_instrument_note: String = "Named instrument for the item-grain Rust census. Writes the merge-base-to-HEAD change population, the live hand-Rust population, and the seed-growth admission verdict computed from them. Exit status is the ADMISSION verdict, not the instrument's own completion: an unjustified addition and an unobservable change both exit nonzero, and the report says which." +// Named instrument for the item-grain Rust census. Writes the merge-base-to-HEAD change population, +// the live hand-Rust population, and the seed-growth admission verdict computed from them. Exit +// status is the ADMISSION verdict, not the instrument's own completion: an unjustified addition and +// an unobservable change both exit nonzero, and the report says which. fn item_row(item: RustItemIdentity) -> String { concat( diff --git a/dag/gunbc/instruments/self_host_03_normalize_declared_source_refs.dag b/dag/gunbc/instruments/self_host_03_normalize_declared_source_refs.dag index 231c313dc2d..a98bafaf40f 100644 --- a/dag/gunbc/instruments/self_host_03_normalize_declared_source_refs.dag +++ b/dag/gunbc/instruments/self_host_03_normalize_declared_source_refs.dag @@ -3,7 +3,16 @@ module tools.self_host_03_normalize_declared_source_refs import v2.compiler.source_authority { SourceRef, source_ref_for_storage_path } import v2.std.collection { List } -data sn_declared_source_refs_doc: String = "declared_source_refs for the 03_normalize behavioral witness only (review 41433 fix): this list opts src/v2/compiler/03_normalize.dag's witness entry into the cli_run.rs affected-set selection axis (docs/plans/declared-source-ref-selection-design.md §4-5, §5.1 flagship). Homed in its OWN module, imported only by dag/test/claim/self_host_03_normalize_behavioral_witness_test.dag — not by tools.self_host_module_behavioral_transport_roster, which all 16 frontier witness entries share. cli_run.rs's declared_source_ref scan is closure-wide (every file in a witness entry's transitive import closure), so a declared_source_refs list living in the shared roster would leak this selection axis onto all 16 entries; a dedicated per-entry module keeps the axis scoped to 03_normalize alone, matching the roster row's module_path/shim_lib_rel/shim_writes/driver_rel exactly." +// declared_source_refs for the 03_normalize behavioral witness only (review 41433 fix): this list +// opts src/v2/compiler/03_normalize.dag's witness entry into the cli_run.rs affected-set selection +// axis (docs/plans/declared-source-ref-selection-design.md §4-5, §5.1 flagship). Homed in its OWN +// module, imported only by dag/test/claim/self_host_03_normalize_behavioral_witness_test.dag — not +// by tools.self_host_module_behavioral_transport_roster, which all 16 frontier witness entries +// share. cli_run.rs's declared_source_ref scan is closure-wide (every file in a witness entry's +// transitive import closure), so a declared_source_refs list living in the shared roster would leak +// this selection axis onto all 16 entries; a dedicated per-entry module keeps the axis scoped to +// 03_normalize alone, matching the roster row's module_path/shim_lib_rel/shim_writes/driver_rel +// exactly. data sn_source_ref: SourceRef = source_ref_for_storage_path(path: "src/v2/compiler/03_normalize.dag") @@ -11,9 +20,23 @@ data sn_shim_lib_ref: SourceRef = source_ref_for_storage_path(path: "dag/gunbc/i data sn_shim_driver_ref: SourceRef = source_ref_for_storage_path(path: "dag/gunbc/instruments/self_host_03_normalize_shims/witness_main.rs") -data sn_declared_source_refs_completeness_note: String = "Hand-verified completeness (docs/plans/declared-source-ref-selection-design.md §5.1 flagship): every storage path this transport reads is listed in declared_source_refs — emitter .dag, shim lib, witness driver, and each curated shim write source. Widening opt-in past this row waits on effect_reach census join admission. The five shared-std shim writes (algebra, collection, diagnostic, node, integer) now resolve to dag/gunbc/instruments/self_host_std_bridge_shims/ rather than this row's own shim dir (the de-fork: one bridge, not one copy per row), so the selection axis follows the file that actually drives the receipt — a declared ref pointing at a no-longer-consumed copy would read as covered while the real input went unselected." - -data sn_declared_source_refs_authority_gap_note: String = "KNOWN INCOMPLETE, and NOT a read-completeness bug: declared_source_refs lists what this transport READS, so it does not list src/v2/std/*.dag — the authority the shared std bridge mirrors. A widening of v2.std.diagnostic therefore does not select this witness even though it can break it, which is exactly how the Locus struct-to-coproduct drift stayed invisible until a wet falsifier run days later. Closing it needs a correctness-dependency axis (authority -> mirror) distinct from the read axis; the shim-surface wall is the intended mechanism and the medium decision (char-fold lens vs seed bin vs declared surface rows) is open. Until then this gap is stated here rather than papered over by adding authority paths to a list whose contract is reads." +// Hand-verified completeness (docs/plans/declared-source-ref-selection-design.md §5.1 flagship): +// every storage path this transport reads is listed in declared_source_refs — emitter .dag, shim +// lib, witness driver, and each curated shim write source. Widening opt-in past this row waits on +// effect_reach census join admission. The five shared-std shim writes (algebra, collection, +// diagnostic, node, integer) now resolve to dag/gunbc/instruments/self_host_std_bridge_shims/ +// rather than this row's own shim dir (the de-fork: one bridge, not one copy per row), so the +// selection axis follows the file that actually drives the receipt — a declared ref pointing at a +// no-longer-consumed copy would read as covered while the real input went unselected. + +// KNOWN INCOMPLETE, and NOT a read-completeness bug: declared_source_refs lists what this transport +// READS, so it does not list src/v2/std/*.dag — the authority the shared std bridge mirrors. A +// widening of v2.std.diagnostic therefore does not select this witness even though it can break it, +// which is exactly how the Locus struct-to-coproduct drift stayed invisible until a wet falsifier +// run days later. Closing it needs a correctness-dependency axis (authority -> mirror) distinct +// from the read axis; the shim-surface wall is the intended mechanism and the medium decision +// (char-fold lens vs seed bin vs declared surface rows) is open. Until then this gap is stated here +// rather than papered over by adding authority paths to a list whose contract is reads. data declared_source_refs: List = [ sn_source_ref, diff --git a/dag/gunbc/instruments/self_host_curated_probe_cargo.dag b/dag/gunbc/instruments/self_host_curated_probe_cargo.dag index f1a3e91f173..5fdca03414a 100644 --- a/dag/gunbc/instruments/self_host_curated_probe_cargo.dag +++ b/dag/gunbc/instruments/self_host_curated_probe_cargo.dag @@ -6,7 +6,11 @@ import extdeps.filesystem.filesystem_io { Filesystem } import std.process { ProcessExit, ExitSuccess, exit_failure } import tools.self_host_curated_seed_linked_harness { cssl_v1_compiled_probe_lib_cargo_toml } -data curated_probe_cargo_scaffold_note: String = "SCAFFOLD — dissolve-on: deletes when cssl exposes probe-lib manifest projection or bash-emit #5828 realizes curated_cargo_probe_one transport. Single authority: cssl_v1_compiled_probe_lib_cargo_toml (tools.self_host_curated_seed_linked_harness). Shell route: curated_probe_cargo_toml_write_from_cssl_authority (ProcessExit + Filesystem.Write) — gunbc run has no value-printing verb (#8286 wall)." +// SCAFFOLD — dissolve-on: deletes when cssl exposes probe-lib manifest projection or bash-emit +// #5828 realizes curated_cargo_probe_one transport. Single authority: +// cssl_v1_compiled_probe_lib_cargo_toml (tools.self_host_curated_seed_linked_harness). Shell route: +// curated_probe_cargo_toml_write_from_cssl_authority (ProcessExit + Filesystem.Write) — gunbc run +// has no value-printing verb (#8286 wall). fn curated_probe_write_cargo_toml_blob(path: String, content: String, label: String) -> ProcessExit { if content == "" { diff --git a/dag/gunbc/instruments/self_host_curated_seed_linked_harness.dag b/dag/gunbc/instruments/self_host_curated_seed_linked_harness.dag index 74ff8955319..a2f7e144bb9 100644 --- a/dag/gunbc/instruments/self_host_curated_seed_linked_harness.dag +++ b/dag/gunbc/instruments/self_host_curated_seed_linked_harness.dag @@ -14,21 +14,72 @@ import v2.std.collection { List } import v2.std.text { String } import tools.host_prelude { witness_bin, witness_bin_ready, witness_bin_release_path } -data cssl_doc: String = "Reusable curated seed-linked self-host harness (Wave 2 easy-flip lane): factors the duplicated emit->shim-assemble->cargo->witness(fault) spine shared by Band A behavioral transports. Probe lane consumes only cssl_v1_compiled_probe_lib_cargo_toml via tools.self_host_curated_probe_cargo. Curated = gunbc emit must succeed, then seed-retained dep shims + shim lib.rs replace the emitted closure so the entry module rustc-links against v1-compiler without waiting for full std self-emit or emitter Track-B body fixes. Each module lane supplies its own shim file paths, witness driver, pass marker, and zero or more supplementary claim-runs. Empty shim_lib_rel = keep gunbc-emitted lib.rs (runtime closure only). Empty claim_runs = skip supplementary claim-runs. LANDED: the per-module copy-paste this note names is dissolved by tools.self_host_module_behavioral_transport_roster — one config row per frontier module, one generic compiler_module_behavioral_receipt_for(module_path) dispatch, this kernel unchanged as the sole executor." - -data cssl_cargo_features_note: String = "Mirrors slb_cargo_features_note: text_lookup_work_counter feature declared for -D warnings consumers." - -data cssl_std_seed_link_followup_note: String = "LANDED (2026-07-17): generic seed-shim closure assembly via cssl_assemble bin + cssl_seed_linked_closure_assembly.rs (typed Refused final arm; explicit dag/std · lens · peripheral emit-retain). Former assembly Refused arms (compiler dep absent from seed lib.rs; v2_std module without shared std-bridge shim) fold into byte-untouched emit-retain — refusal relocates to cargo verdict, not silent green (the sanitize scaffold debt on this path dissolved 2026-07-23 when strip_repeated_use_symbols landed in v1.compiler.emit_rust). Invoked from cssl_seed_linked_behavioral_receipt when shim_lib_rel empty and shim_writes empty. cssl_assemble IS the assembly entrypoint (a modeled bin, invoked directly); the hand probe runners that used to drive it under CSSL_STD_SEED_LINK=1 lived in scripts/ and were deleted 2026-07-23 — their measurement receipts are in docs/probes, and re-measuring goes through cssl_assemble, not a restored shell wrapper." +// Reusable curated seed-linked self-host harness (Wave 2 easy-flip lane): factors the duplicated +// emit->shim-assemble->cargo->witness(fault) spine shared by Band A behavioral transports. Probe +// lane consumes only cssl_v1_compiled_probe_lib_cargo_toml via tools.self_host_curated_probe_cargo. +// Curated = gunbc emit must succeed, then seed-retained dep shims + shim lib.rs replace the emitted +// closure so the entry module rustc-links against v1-compiler without waiting for full std +// self-emit or emitter Track-B body fixes. Each module lane supplies its own shim file paths, +// witness driver, pass marker, and zero or more supplementary claim-runs. Empty shim_lib_rel = keep +// gunbc-emitted lib.rs (runtime closure only). Empty claim_runs = skip supplementary claim-runs. +// LANDED: the per-module copy-paste this note names is dissolved by +// tools.self_host_module_behavioral_transport_roster — one config row per frontier module, one +// generic compiler_module_behavioral_receipt_for(module_path) dispatch, this kernel unchanged as +// the sole executor. + +// Mirrors slb_cargo_features_note: text_lookup_work_counter feature declared for -D warnings +// consumers. + +// LANDED (2026-07-17): generic seed-shim closure assembly via cssl_assemble bin + +// cssl_seed_linked_closure_assembly.rs (typed Refused final arm; explicit dag/std · lens · +// peripheral emit-retain). Former assembly Refused arms (compiler dep absent from seed lib.rs; +// v2_std module without shared std-bridge shim) fold into byte-untouched emit-retain — refusal +// relocates to cargo verdict, not silent green (the sanitize scaffold debt on this path dissolved +// 2026-07-23 when strip_repeated_use_symbols landed in v1.compiler.emit_rust). Invoked from +// cssl_seed_linked_behavioral_receipt when shim_lib_rel empty and shim_writes empty. cssl_assemble +// IS the assembly entrypoint (a modeled bin, invoked directly); the hand probe runners that used to +// drive it under CSSL_STD_SEED_LINK=1 lived in scripts/ and were deleted 2026-07-23 — their +// measurement receipts are in docs/probes, and re-measuring goes through cssl_assemble, not a +// restored shell wrapper. data cssl_std_bridge_shims_rel: String = "dag/gunbc/instruments/self_host_std_bridge_shims" data cssl_std_bridge_scaffold_dissolution_trigger: DissolutionCondition = unbound_dissolution(description: "dissolve-on: dag/gunbc/instruments/self_host_std_bridge_shims/* shared std-bridge (emitted-side ONLY; seed oracle uses v1_compiler real std). DISSOLVES WHEN v2 std self-emits and gunbc emits seed-linked extern imports without hand ABI bridges.") -data cssl_is_not_a_seed_admission_path_note: String = "CSSL DOES NOT ADD MODULES TO THE v1 SEED, AND THE ABSENCE OF THIS SENTENCE HAS A MEASURED COST. cssl_assemble writes into out_dir ONLY -- the gunbc-emitted CANDIDATE crate. AS OF THIS REVISION IT DOES NOT READ src/v1/stage0/src AT ALL, AND THAT IS A STRENGTHENING OF THIS PARAGRAPH, NOT A CHANGE OF SUBJECT. It formerly had exactly one such read: seed_has_pub_mod, an oracle deciding whether a compiler-family module in the CANDIDATE should become a re-export of the seed rather than keep its emitted body. That oracle text-parsed the seed lib.rs for a pub mod line, which made it a SECOND authority over seed mod-tree membership beside the modeled one, v2.compiler.self_host.stage0_crate_layout -- and the two disagree exactly during drift. It is deleted with the arm it fed, which was already UNREACHABLE: the arm required a closure member to be absent from the emitted lib.rs manifest that the loop had just drawn it from, a negation false by construction. Verified by execution before deletion (a panic substituted for the arm body left all nine module tests identical in outcome), with the discriminating control closure_compiler_mod_emit_retained_when_seed_also_has_pub_mod kept enrolled. So the claim below no longer rests on reading which paths the arms write: no arm of assemble_seed_linked_closure reads OR writes any path under src/v1, so running it can never make the seed declare a module it did not already declare. THERE IS EXACTLY ONE WAY A MODULE ENTERS THE SEED: compile_stage0() emits it and the emitted bytes are committed, or v2.compiler.self_host.stage0_crate_layout registers it as seed-retained. claim_executor --required-regen refusing an unregistered committed file (committed_not_emitted) is that single path WORKING; it is not a second mechanism disagreeing with this harness. WHY THIS IS WRITTEN DOWN. cssl_closure_assembly_note beside it describes the assembly RULE correctly and never says what the rule is not, and the gap was read as a fork: an escalation concluded the repository had lost the ability to add a seed module, a lane was dispatched on it, the premise was retracted, and the re-scoped item still carried 'two paths -- cssl_assemble and --required-regen' forward until someone read what assemble_seed_linked_closure actually writes. A refusal doing its job was read as evidence of a fork. The distinction costs one paragraph here and cost a wrong work item without it." +// CSSL DOES NOT ADD MODULES TO THE v1 SEED, AND THE ABSENCE OF THIS SENTENCE HAS A MEASURED COST. +// cssl_assemble writes into out_dir ONLY -- the gunbc-emitted CANDIDATE crate. AS OF THIS REVISION +// IT DOES NOT READ src/v1/stage0/src AT ALL, AND THAT IS A STRENGTHENING OF THIS PARAGRAPH, NOT A +// CHANGE OF SUBJECT. It formerly had exactly one such read: seed_has_pub_mod, an oracle deciding +// whether a compiler-family module in the CANDIDATE should become a re-export of the seed rather +// than keep its emitted body. That oracle text-parsed the seed lib.rs for a pub mod line, which +// made it a SECOND authority over seed mod-tree membership beside the modeled one, +// v2.compiler.self_host.stage0_crate_layout -- and the two disagree exactly during drift. It is +// deleted with the arm it fed, which was already UNREACHABLE: the arm required a closure member to +// be absent from the emitted lib.rs manifest that the loop had just drawn it from, a negation false +// by construction. Verified by execution before deletion (a panic substituted for the arm body left +// all nine module tests identical in outcome), with the discriminating control +// closure_compiler_mod_emit_retained_when_seed_also_has_pub_mod kept enrolled. So the claim below +// no longer rests on reading which paths the arms write: no arm of assemble_seed_linked_closure +// reads OR writes any path under src/v1, so running it can never make the seed declare a module it +// did not already declare. THERE IS EXACTLY ONE WAY A MODULE ENTERS THE SEED: compile_stage0() +// emits it and the emitted bytes are committed, or v2.compiler.self_host.stage0_crate_layout +// registers it as seed-retained. claim_executor --required-regen refusing an unregistered committed +// file (committed_not_emitted) is that single path WORKING; it is not a second mechanism +// disagreeing with this harness. WHY THIS IS WRITTEN DOWN. cssl_closure_assembly_note beside it +// describes the assembly RULE correctly and never says what the rule is not, and the gap was read +// as a fork: an escalation concluded the repository had lost the ability to add a seed module, a +// lane was dispatched on it, the premise was retracted, and the re-scoped item still carried 'two +// paths -- cssl_assemble and --required-regen' forward until someone read what +// assemble_seed_linked_closure actually writes. A refusal doing its job was read as evidence of a +// fork. The distinction costs one paragraph here and cost a wrong work item without it. data cssl_closure_assembly_note: String = "Generic closure assembly: the dep population is the EMITTED-POPULATION MANIFEST the compiler writes into the same out_dir (src/emitted_population.rs), read under the grammar both ends resolve from gunbc.stage0_emitted_population_manifest -- split on the separator, strip the line prefix. Entry module bytes hash-locked. Emit-retain rule: every path the emitter DECLARED it produced must be materialized in out_dir, byte-untouched; a declared path with no file is a typed refusal (RefusedDeclaredMember), and refusal for anything the bytes then say relocates to the cargo verdict. THIS REPLACED THE LAST RUST TEXT-PARSE ON THIS PATH (the prior revision scraped src/lib.rs pub mod lines to recover closure membership), and the swap is a strengthening rather than a rename: lib.rs mod list is rendered FROM the same module file list the writer then writes out, so that check could only agree with itself on the real path and its only reachable red came from a hand-authored lib.rs -- while the manifest declares Cargo.toml, lib.rs, main.rs, every module, every test file and itself, so the declared-versus-materialized question is answerable at the grain that matters. BOOTSTRAP_INLINE_MODS is deleted with it: the manifest never declares NonEmptyVec/NonEmptyBTreeSet because they are inlined structs and not files, so the state that skip tolerated is not representable in the population assembly reads. The seed lib.rs is still NOT consulted: the compiler-seed-re-export arm and its seed_has_pub_mod oracle are deleted (see cssl_is_not_a_seed_admission_path_note), so seed mod-tree membership has one authority, v2.compiler.self_host.stage0_crate_layout, and this harness is not a second reader of it. Hand shim_lib_rel/shim_writes = optional fast-path override (body_producer / behavioral receipts)." -data cssl_medium_as_string_scaffold_debt: String = "SCAFFOLD — tracked debt: Cargo.toml TOML markup authored via concat string literals in cssl_v1_compiled_cargo_* (same class as slb_cargo_toml / ssuv_cargo_toml / peh_cargo_toml). Probe-only throwaway measurement — not gate-side substrate. dissolve-on: extdeps cargo manifest grammar rows + bash-emit #5828 OR harness consolidation; until then one concat producer per variant ([[bin]] witness vs [lib] probe), consumed only via tools.self_host_curated_probe_cargo." +// SCAFFOLD — tracked debt: Cargo.toml TOML markup authored via concat string literals in +// cssl_v1_compiled_cargo_* (same class as slb_cargo_toml / ssuv_cargo_toml / peh_cargo_toml). +// Probe-only throwaway measurement — not gate-side substrate. dissolve-on: extdeps cargo manifest +// grammar rows + bash-emit #5828 OR harness consolidation; until then one concat producer per +// variant ([[bin]] witness vs [lib] probe), consumed only via tools.self_host_curated_probe_cargo. fn cssl_repo_path(root: String, rel: String) -> String { concat(root, concat("/", rel)) @@ -45,7 +96,8 @@ fn cssl_v1_compiled_cargo_package_header() -> String { "[package]\nname = \"v1_compiled\"\nversion = \"0.1.0\"\nedition = \"2021\"\n\n[features]\ntext_lookup_work_counter = []\n\n" } -data cssl_witness_bin_variant_note: String = "Witness-driver [[bin]] layout (cssl_v1_compiled_cargo_toml) for ferret flip lanes. Probe does not consume this variant — probe uses cssl_v1_compiled_probe_lib_cargo_toml only." +// Witness-driver [[bin]] layout (cssl_v1_compiled_cargo_toml) for ferret flip lanes. Probe does not +// consume this variant — probe uses cssl_v1_compiled_probe_lib_cargo_toml only. fn cssl_v1_compiled_cargo_toml(root: String) -> String { concat( diff --git a/dag/gunbc/instruments/self_host_logic_behavioral_transport.dag b/dag/gunbc/instruments/self_host_logic_behavioral_transport.dag index 0d547c8e6d8..6a49adeacb8 100644 --- a/dag/gunbc/instruments/self_host_logic_behavioral_transport.dag +++ b/dag/gunbc/instruments/self_host_logic_behavioral_transport.dag @@ -2,7 +2,6 @@ module tools.self_host_logic_behavioral_transport import std.dissolution { DissolutionCondition, dissolution_description, unbound_dissolution } - import extdeps.shell import extdeps.filesystem.filesystem_io import extdeps.cargo_build @@ -28,9 +27,18 @@ import v2.compiler.self_host.promotion_admission { } import tools.host_prelude { witness_bin, witness_bin_ready, witness_bin_release_path } - - -data slb_doc: String = "Wave 1 Gate 4 (weak self-host behavioral receipt) for dag/std/logic.dag: realize the v2 self-emit -> compile -> run -> equals-seed chain as typed host ops, mirroring run_rust_smoke's shape (shell.Mktemp/Filesystem.Write/cargo.Build.BuildManifest/gunbc.WitnessBin.Run) rather than serialized bash. The module content is read via Filesystem.Read at run time (reads-real-bytes, matching self_host_realized_comparison's provenance pattern) so the receipt cannot drift from the committed dag/std/logic.dag. The driver crate depends on the real v1-compiler crate as a path dependency and imports its std_logic module directly as the seed oracle -- no seed-runtime shim is reconstructed (single authority, DESIGN.md SS3), and the seed crate is not rebuilt in isolation since sccache keys on rustc invocation+source content, not target-dir location. The driver exercises the full Classical truth table for classical_not/classical_and/classical_or and supports --inject-fault, which deliberately calls the emitted side on a mismatched input so the receipt is proven discriminating (DESIGN.md SS5) rather than tautologically green." +// Wave 1 Gate 4 (weak self-host behavioral receipt) for dag/std/logic.dag: realize the v2 self-emit +// -> compile -> run -> equals-seed chain as typed host ops, mirroring run_rust_smoke's shape +// (shell.Mktemp/Filesystem.Write/cargo.Build.BuildManifest/gunbc.WitnessBin.Run) rather than +// serialized bash. The module content is read via Filesystem.Read at run time (reads-real-bytes, +// matching self_host_realized_comparison's provenance pattern) so the receipt cannot drift from the +// committed dag/std/logic.dag. The driver crate depends on the real v1-compiler crate as a path +// dependency and imports its std_logic module directly as the seed oracle -- no seed-runtime shim +// is reconstructed (single authority, DESIGN.md SS3), and the seed crate is not rebuilt in +// isolation since sccache keys on rustc invocation+source content, not target-dir location. The +// driver exercises the full Classical truth table for classical_not/classical_and/classical_or and +// supports --inject-fault, which deliberately calls the emitted side on a mismatched input so the +// receipt is proven discriminating (DESIGN.md SS5) rather than tautologically green. data slb_logic_source_rel: String = "dag/std/logic.dag" @@ -71,7 +79,14 @@ fn slb_repo_path(root: String, rel: String) -> String { concat(root, concat("/", rel)) } -data slb_cargo_features_note: String = "The driver Cargo.toml declares [features] text_lookup_work_counter to mirror the seed crate (src/v1/stage0/Cargo.toml): the gunbc-emitted v1_rt.rs gates on #[cfg(feature = \"text_lookup_work_counter\")], and CI exports RUSTFLAGS=-D warnings, which promotes the unexpected_cfgs lint to a hard error when the consuming crate does not declare the referenced feature. Without this line the emit->compile step fails exit=101 (12 unexpected-cfg errors) under CI while passing a local build that omits -D warnings. Deeper root (separate emit lane): gunbc's own emitted Cargo.toml omits this [features] block, so any -D-warnings consumer of a gunbc-emitted crate breaks the same way." +// The driver Cargo.toml declares [features] text_lookup_work_counter to mirror the seed crate +// (src/v1/stage0/Cargo.toml): the gunbc-emitted v1_rt.rs gates on #[cfg(feature = +// "text_lookup_work_counter")], and CI exports RUSTFLAGS=-D warnings, which promotes the +// unexpected_cfgs lint to a hard error when the consuming crate does not declare the referenced +// feature. Without this line the emit->compile step fails exit=101 (12 unexpected-cfg errors) under +// CI while passing a local build that omits -D warnings. Deeper root (separate emit lane): gunbc's +// own emitted Cargo.toml omits this [features] block, so any -D-warnings consumer of a +// gunbc-emitted crate breaks the same way. fn slb_cargo_toml(root: String) -> String { concat( diff --git a/dag/gunbc/instruments/self_host_module_behavioral_transport_roster.dag b/dag/gunbc/instruments/self_host_module_behavioral_transport_roster.dag index fc86e3f499e..74185933873 100644 --- a/dag/gunbc/instruments/self_host_module_behavioral_transport_roster.dag +++ b/dag/gunbc/instruments/self_host_module_behavioral_transport_roster.dag @@ -25,7 +25,22 @@ import v2.std.subject_evidence { } import v2.std.text { String } -data compiler_module_behavioral_transport_roster_doc: String = "The modeled per-module behavioral-receipt producer (Gate-1 receipt lane B1): one config row per src/v2/compiler/self_host/frontier.dag module, one generic dispatch fn (compiler_module_behavioral_receipt_for) replacing the 16 hand-authored tools.self_host__behavioral_transport.dag files this roster supersedes (00_compile, 01_tokenize, 02_parse, 03_body_producer, 03_ingest, 03_normalize, 03_resolve, 04_infer, 07_target_carriers, discovery_enumeration, materialization_carriers, parse_engine_hooks, program_assembly, program_partition, source_authority, use_site_verdict). Each row is config data only (shim paths, driver path, pass marker, supplementary claim-runs) migrated verbatim from those files; the seed-vs-emitted execution stays entirely in the shared kernel (tools.self_host_curated_seed_linked_harness), so a new frontier row's receipt is one roster row, not a new file. Fail-closed: an unrecognized module_path returns false (never a fabricated pass) — see compiler_module_behavioral_receipt_for. tools.self_host_logic_behavioral_transport and tools.namespace_import_closure_behavioral_transport are deliberately NOT migrated here: neither is keyed by a frontier row (logic predates the frontier; namespace_import_closure proves an emitter CLASS property, not a per-module fact — see falsifier_self_host_wet_emit_class_note in v2.compiler.self_host.wet_receipt_enrollment)." +// The modeled per-module behavioral-receipt producer (Gate-1 receipt lane B1): one config row per +// src/v2/compiler/self_host/frontier.dag module, one generic dispatch fn +// (compiler_module_behavioral_receipt_for) replacing the 16 hand-authored +// tools.self_host__behavioral_transport.dag files this roster supersedes (00_compile, +// 01_tokenize, 02_parse, 03_body_producer, 03_ingest, 03_normalize, 03_resolve, 04_infer, +// 07_target_carriers, discovery_enumeration, materialization_carriers, parse_engine_hooks, +// program_assembly, program_partition, source_authority, use_site_verdict). Each row is config data +// only (shim paths, driver path, pass marker, supplementary claim-runs) migrated verbatim from +// those files; the seed-vs-emitted execution stays entirely in the shared kernel +// (tools.self_host_curated_seed_linked_harness), so a new frontier row's receipt is one roster row, +// not a new file. Fail-closed: an unrecognized module_path returns false (never a fabricated pass) +// — see compiler_module_behavioral_receipt_for. tools.self_host_logic_behavioral_transport and +// tools.namespace_import_closure_behavioral_transport are deliberately NOT migrated here: neither +// is keyed by a frontier row (logic predates the frontier; namespace_import_closure proves an +// emitter CLASS property, not a per-module fact — see falsifier_self_host_wet_emit_class_note in +// v2.compiler.self_host.wet_receipt_enrollment). type ModuleBehavioralTransportConfig { module_path: String diff --git a/dag/gunbc/interpreter_replacement_cut.dag b/dag/gunbc/interpreter_replacement_cut.dag index 985f75a5e19..d036a0e94bc 100644 --- a/dag/gunbc/interpreter_replacement_cut.dag +++ b/dag/gunbc/interpreter_replacement_cut.dag @@ -12,7 +12,167 @@ import gunbc.replacement_cut { CutRegistration, RegisteredCutRemeasureRequired, } -data interpreter_replacement_cut_note: String = "RECAST 2026-08-28 FROM AN EXACT PLAN INTO A LEGACY SURVEY PLUS A REMEASUREMENT OBLIGATION, and everything below this paragraph is the historical record of the plan as it stood. WHAT CHANGED AND WHY: gunbc.replacement_cut re-grounded BoundaryConsumer onto the relation consumer x consumed-authority at DECLARATION grain. This value was authored at MODULE-OR-BINARY grain over a ten-of-125 sample -- it says so itself, several times, and those statements are the reason the recast is mechanical rather than a judgment call. The two are different identity universes, so the old value no longer inhabits the exact carrier. THAT IS THE DESIRED RESULT: the survey was not merely incomplete in count, it was authored in a universe too coarse to satisfy the new contract, and weakening the grain so it would fit is the one move that would launder a sample into an exactness claim. WHAT THE RE-GROUNDING FOUND, mechanically, which no reader of the strings had noticed: four of the ten consumer labels -- v1_compiler.cli_run, v1_compiler.coproduct_reflection, v1_compiler.pre_push, v1_compiler.v1_interpreter_dispatch_generated -- name no .dag module anywhere in the corpus. They are host Rust compilation units. A NonEmptyStr accepted them and a resolvable DeclarationRef does not, which is the argument for the re-grounding stated as a receipt rather than as a prediction. They are NOT residue and NOT unidentifiable: a Rust dependency on X is a real semantic consumer whose removal makes the build refuse, and gunbc.rust_item_identity plus gunbc.rust_item_host_observation item_declaration_ref already project a citable Rust item into the SAME DeclarationRef vocabulary, with typed Uncitable and Refused arms for impl methods and unprojectable paths. So the exact identities exist; what this row never measured is which ITEMS consume X, and one label may expand into many item relations, into an uncitable impl method, or into none at all. A one-for-one translation would have invented ten identities the measurement never established. THE OBLIGATION IS REGISTERED RATHER THAN DISCHARGED: interpreter_cut_registration is RegisteredCutRemeasureRequired, so the identity stays visible -- a descent naming it resolves and refuses as RegistryParentRemeasureRequired rather than as ParentAbsent -- while the coarse survey is structurally unable to answer as an exact plan. Remeasurement belongs to the lane that owns this boundary. ONE SUBSTANTIVE FINDING FOR THAT REMEASUREMENT, visible only at exact grain: v1_interpreter_dispatch_generated appears BOTH as a retired consumer and as a covered-region member, and an X-region member is not automatically an inbound boundary consumer -- so it is very likely a region member that never represented a boundary crossing.\n\nHISTORICAL RECORD OF THE PLAN FOLLOWS, unchanged except where it is quoted above. THE FIRST PRODUCTION ReplacementCut IN THE REPOSITORY. X is the v1 tree-walking interpreter; Y is the INDEPENDENTLY EMITTED v2 evaluator (v2.compiler.eval), not per-subject native emission -- see the Y1/Y2 correction below, which is the most consequential amendment this row has taken. The row is authored from a measured deletion rather than from imagination: the deletion was performed on integration/interpreter-cut and the boundary population read from the far side of it, which is the only side it is visible from.\n\nWHY THIS PLAN REFUSES, and why the refusal is the deliverable: admit_replacement_cut_plan returns PlanRefused with counted causes, and every cause is a TRUE open decision rather than an authoring defect. Two classes. UNDECIDED: the driver region is another lane's boundary and the cut above it is unauthored, so no disposition can be earned here. UNCOVERED SURVIVING: the consumers that must keep working have no coverage authority to name, because Y has no executing realization. CORRECTED (this cause was previously justified from the per-subject native routing roster -- 77 ReadsLiveTree plus 35 host-effect entries ceilinged into a lane that does not exist -- which is Y2 evidence offered for a Y1 contract, the exact conflation the Y1/Y2 paragraph below unpicks). The correct refusal is narrower, nearer, and measured: v2.compiler.eval has not earned a v2-emitter-produced executing receipt. Its frontier row is seed-retained with a RealizationGap located at ProbeStageAssemble, and the compiler-wide emitter_produced_baseline is 0 -- no module in the v2 compiler has a producer-qualified promotion. Naming a coverage authority anyway would be the fallback-wearing-a-disposition move the carrier already refuses; naming Y as covering a consumer Y does not cover is the same lie one level up. So the plan is authored to refuse and the causes are counted.\n\nWHAT THE COUNT IS, STATED AT ITS REAL SCOPE (corrected after review): the causes are the known unresolved dispositions AMONG THE SAMPLED CONSUMERS, UNDER ONE CANDIDATE Y. They are not a complete consumer census, not a semantic-feature census, and not a work estimate. An earlier revision of this note called them the honest size of what remains, which is an overclaim on two axes at once: the census enumerates ten of 125 files by its own declaration, and the refusal is evaluated against native execution alone.\n\nAND WHAT THE REFUSAL DOES NOT ESTABLISH (written when new_root still named native execution through emitted Rust, and kept as the record of that correction rather than rewritten as though it had always read this way): this plan's refusal showed that ONE realization does not presently cover the retained consumers. It does not show that no realization could. Any other candidate -- a fresh, smaller, fail-closed evaluator among them -- is excluded here BY CONSTRUCTION rather than by evidence, and no such candidate has been built, run, or refused. The choice of native execution is a received direction, not a conclusion derived in this carrier; a row that states a received constraint without saying so is indistinguishable from a row that concluded it. CORRECTED: new_root now names the boundary in CONSUMER terms and no longer in one realization's vocabulary. The two are different decisions and only the second is fixed by direction. Section 3 is explicit that the interface shape is what is owned, that a transport is one of N and never a fact about the dependency, and that the dispatch selecting a realization is itself realization and sits peripheral rather than in the interface. A replacement contract naming emitted Rust fused those, which is the mechanical reason every reading of this plan terminated at the native routing roster: that measures ONE REALIZATION'S COVERAGE and it is not the contract's denominator. The contract's denominator is reachable expression forms, reachable primitive identities, required effect kinds and required result shapes.\n\nTHE Y1/Y2 CORRECTION, and it supersedes the selected-realization paragraph this replaces. TWO DIFFERENT REPLACEMENTS were being run as one, and separating them dissolves most of what this row treated as blocking.\n\n Y1 -- the emitted v2 evaluator. v2.compiler.eval, authored in .dag and emitted into an independently executable artifact. Another GENERIC evaluator: it executes arbitrary .dag declarations, exactly as X does.\n Y2 -- direct per-subject native emission. Each .dag declaration compiled into its own Rust implementation, with no generic evaluator involved for that subject.\n\nThese are not the same migration, and the compiler model already carries both: v2.compiler.compile's CompileMode is TranslateTo -- target -- OR Eval -- runtime -- two sibling realizations, so generic evaluation is a modeled arm of the compiler and not a hypothetical.\n\nWHICH ONE DELETES X: Y1. An emitted v2 evaluator could replace the v1 interpreter WHILE EVERY SUBJECT REMAINS DYNAMICALLY EVALUATED. Y2 is a separate, later replacement whose X is generic evaluation itself and which may eventually retire Y1 too -- it is not a prerequisite for deleting v1. Treating it as one is what produced this row's central defect: a Y1-shaped CONTRACT -- evaluate a resolved .dag declaration, or refuse -- proved unavailable by Y2-shaped EVIDENCE, the per-subject native roster. That does not follow. The roster is the denominator of a different replacement.\n\nWHAT THE BOOTSTRAP ACTUALLY LOOKS LIKE, which explains the frozen-X behavior far more directly than any roster: X currently interprets v2.compiler.eval, which MODELS the intended replacement semantics. That is X executing the SPECIFICATION of Y, not Y existing independently. Delete X today and the only general mechanism by which the v2 evaluator's own .dag implementation can execute goes with it.\n\nAND A THIRD EVALUATOR IS NOT THE ANSWER: authoring a fresh minimal interpreter beside these would make three authorities for evaluation behavior -- v1's semantics, v2.compiler.eval's semantics, and the new one's. The move is to make the evaluator that already exists independently real. A minimal EMIT-ABLE SLICE of v2.compiler.eval is a legitimate bootstrap increment, but only if it implements the existing interpretation algebra, shares the same runtime-value and diagnostic authorities, fails closed, never falls back to v1, and carries an explicit dissolution into the complete emitted evaluator -- that is a first increment of Y1, not a third semantic design.\n\nEMISSION IS THE FIRST BLOCKER AND NOT NECESSARILY THE LAST: v2.compiler.eval carries runtime_reject arms today. Even once it builds independently it may refuse real retained roots -- and THOSE REFUSALS ARE THE CENSUS THIS PROJECT HAS NEVER HAD, at the right grain: reachable evaluator forms, required primitive operations, required effects, required value and result representations. Not 744 declaration names and not 1350 Rust reference hits.\n\nA CONSEQUENCE WORTH CARRYING: on a boundary stated in consumer terms, cli_run is a CONSUMER of DagExecution and not automatically the parent cut. The unauthored-parent finding recorded in why_not_higher may therefore be an artifact of drawing the boundary at a FILE rather than at a contract, in which case it dissolves rather than needing to be solved. That arm is left as authored until it is re-measured at this boundary, because replacing a measured finding with a predicted one is the same error in the other direction.\n\nCENSUS GRAIN, declared: one row per distinct consuming module or binary, selected as the measured top of the reference distribution on main at 7f5fa6a94ec1 (1350 line-hits across 125 files; the ten rows below carry 1004 of them). This is NOT proven equal to the real boundary population -- the carrier states that limitation for every plan, and it holds here: an omitted consumer cannot be detected at this grain. The remaining 115 files are the residue this row does not enumerate, and that residue is a known omission rather than a claim of completeness.\n\nWHAT THAT REFERENCE COUNT DOES AND DOES NOT MEASURE: it measures ENTANGLEMENT -- how much code reaches X -- and it does not measure surviving OBLIGATIONS. Reliance establishes migration cost, never correctness, so the line-hit total is a selection instrument for census grain and nothing else. It must not be quoted as the size of the cut: the number that would be is how many of those references survive dispositioning, and that is not knowable until the boundary is settled.\n\nCORRECTION (row disposition, found by review after landing): coproduct_reflection and pre_push were authored Undecided on the stated ground that no disposition could be earned at an unauthored parent boundary. The measured branch DELETES both files, so this carrier reported an open decision for two consumers the artifact had already disposed of -- a false statement in a landed plan that other lanes read. Both are now Retire, because the deletion IS the disposition. cli_run remains Undecided and that is not the same defect: its region belongs to another lane, and parking a decision that is genuinely not this lane's to make is the honest arm rather than an error. Recorded here rather than silently amended, because a plan that corrects itself without saying so is indistinguishable from one that was always right.\n\nAND A DISPUTE THAT RIDES ON THOSE TWO ROWS, recorded because ratifying it silently would be the worse error: Retire here reports WHAT THE MEASURED BRANCH DID, not that deleting those consumers was methodologically sound. Review has since held that deleting consumers and behavioral sensors alongside X damages the experiment -- once a demand signal is removed, the absence of further failure no longer means the behavior was not load-bearing, it may only mean nothing is left to ask. A consumer earns Retire by being dispositioned, not by having been deleted. These two rows are therefore accurate as a record of the branch and are NOT a finding that the behavior had no surviving subject." +// RECAST 2026-08-28 FROM AN EXACT PLAN INTO A LEGACY SURVEY PLUS A REMEASUREMENT OBLIGATION, and +// everything below this paragraph is the historical record of the plan as it stood. WHAT CHANGED +// AND WHY: gunbc.replacement_cut re-grounded BoundaryConsumer onto the relation consumer x +// consumed-authority at DECLARATION grain. This value was authored at MODULE-OR-BINARY grain over a +// ten-of-125 sample -- it says so itself, several times, and those statements are the reason the +// recast is mechanical rather than a judgment call. The two are different identity universes, so +// the old value no longer inhabits the exact carrier. THAT IS THE DESIRED RESULT: the survey was +// not merely incomplete in count, it was authored in a universe too coarse to satisfy the new +// contract, and weakening the grain so it would fit is the one move that would launder a sample +// into an exactness claim. WHAT THE RE-GROUNDING FOUND, mechanically, which no reader of the +// strings had noticed: four of the ten consumer labels -- v1_compiler.cli_run, +// v1_compiler.coproduct_reflection, v1_compiler.pre_push, +// v1_compiler.v1_interpreter_dispatch_generated -- name no .dag module anywhere in the corpus. They +// are host Rust compilation units. A NonEmptyStr accepted them and a resolvable DeclarationRef does +// not, which is the argument for the re-grounding stated as a receipt rather than as a prediction. +// They are NOT residue and NOT unidentifiable: a Rust dependency on X is a real semantic consumer +// whose removal makes the build refuse, and gunbc.rust_item_identity plus +// gunbc.rust_item_host_observation item_declaration_ref already project a citable Rust item into +// the SAME DeclarationRef vocabulary, with typed Uncitable and Refused arms for impl methods and +// unprojectable paths. So the exact identities exist; what this row never measured is which ITEMS +// consume X, and one label may expand into many item relations, into an uncitable impl method, or +// into none at all. A one-for-one translation would have invented ten identities the measurement +// never established. THE OBLIGATION IS REGISTERED RATHER THAN DISCHARGED: +// interpreter_cut_registration is RegisteredCutRemeasureRequired, so the identity stays visible -- +// a descent naming it resolves and refuses as RegistryParentRemeasureRequired rather than as +// ParentAbsent -- while the coarse survey is structurally unable to answer as an exact plan. +// Remeasurement belongs to the lane that owns this boundary. ONE SUBSTANTIVE FINDING FOR THAT +// REMEASUREMENT, visible only at exact grain: v1_interpreter_dispatch_generated appears BOTH as a +// retired consumer and as a covered-region member, and an X-region member is not automatically an +// inbound boundary consumer -- so it is very likely a region member that never represented a +// boundary crossing. +// +// HISTORICAL RECORD OF THE PLAN FOLLOWS, unchanged except where it is quoted above. THE FIRST +// PRODUCTION ReplacementCut IN THE REPOSITORY. X is the v1 tree-walking interpreter; Y is the +// INDEPENDENTLY EMITTED v2 evaluator (v2.compiler.eval), not per-subject native emission -- see the +// Y1/Y2 correction below, which is the most consequential amendment this row has taken. The row is +// authored from a measured deletion rather than from imagination: the deletion was performed on +// integration/interpreter-cut and the boundary population read from the far side of it, which is +// the only side it is visible from. +// +// WHY THIS PLAN REFUSES, and why the refusal is the deliverable: admit_replacement_cut_plan returns +// PlanRefused with counted causes, and every cause is a TRUE open decision rather than an authoring +// defect. Two classes. UNDECIDED: the driver region is another lane's boundary and the cut above it +// is unauthored, so no disposition can be earned here. UNCOVERED SURVIVING: the consumers that must +// keep working have no coverage authority to name, because Y has no executing realization. +// CORRECTED (this cause was previously justified from the per-subject native routing roster -- 77 +// ReadsLiveTree plus 35 host-effect entries ceilinged into a lane that does not exist -- which is +// Y2 evidence offered for a Y1 contract, the exact conflation the Y1/Y2 paragraph below unpicks). +// The correct refusal is narrower, nearer, and measured: v2.compiler.eval has not earned a +// v2-emitter-produced executing receipt. Its frontier row is seed-retained with a RealizationGap +// located at ProbeStageAssemble, and the compiler-wide emitter_produced_baseline is 0 -- no module +// in the v2 compiler has a producer-qualified promotion. Naming a coverage authority anyway would +// be the fallback-wearing-a-disposition move the carrier already refuses; naming Y as covering a +// consumer Y does not cover is the same lie one level up. So the plan is authored to refuse and the +// causes are counted. +// +// WHAT THE COUNT IS, STATED AT ITS REAL SCOPE (corrected after review): the causes are the known +// unresolved dispositions AMONG THE SAMPLED CONSUMERS, UNDER ONE CANDIDATE Y. They are not a +// complete consumer census, not a semantic-feature census, and not a work estimate. An earlier +// revision of this note called them the honest size of what remains, which is an overclaim on two +// axes at once: the census enumerates ten of 125 files by its own declaration, and the refusal is +// evaluated against native execution alone. +// +// AND WHAT THE REFUSAL DOES NOT ESTABLISH (written when new_root still named native execution +// through emitted Rust, and kept as the record of that correction rather than rewritten as though +// it had always read this way): this plan's refusal showed that ONE realization does not presently +// cover the retained consumers. It does not show that no realization could. Any other candidate -- +// a fresh, smaller, fail-closed evaluator among them -- is excluded here BY CONSTRUCTION rather +// than by evidence, and no such candidate has been built, run, or refused. The choice of native +// execution is a received direction, not a conclusion derived in this carrier; a row that states a +// received constraint without saying so is indistinguishable from a row that concluded it. +// CORRECTED: new_root now names the boundary in CONSUMER terms and no longer in one realization's +// vocabulary. The two are different decisions and only the second is fixed by direction. Section 3 +// is explicit that the interface shape is what is owned, that a transport is one of N and never a +// fact about the dependency, and that the dispatch selecting a realization is itself realization +// and sits peripheral rather than in the interface. A replacement contract naming emitted Rust +// fused those, which is the mechanical reason every reading of this plan terminated at the native +// routing roster: that measures ONE REALIZATION'S COVERAGE and it is not the contract's +// denominator. The contract's denominator is reachable expression forms, reachable primitive +// identities, required effect kinds and required result shapes. +// +// THE Y1/Y2 CORRECTION, and it supersedes the selected-realization paragraph this replaces. TWO +// DIFFERENT REPLACEMENTS were being run as one, and separating them dissolves most of what this row +// treated as blocking. +// +// Y1 -- the emitted v2 evaluator. v2.compiler.eval, authored in .dag and emitted into an +// independently executable artifact. Another GENERIC evaluator: it executes arbitrary .dag +// declarations, exactly as X does. +// Y2 -- direct per-subject native emission. Each .dag declaration compiled into its own Rust +// implementation, with no generic evaluator involved for that subject. +// +// These are not the same migration, and the compiler model already carries both: +// v2.compiler.compile's CompileMode is TranslateTo -- target -- OR Eval -- runtime -- two sibling +// realizations, so generic evaluation is a modeled arm of the compiler and not a hypothetical. +// +// WHICH ONE DELETES X: Y1. An emitted v2 evaluator could replace the v1 interpreter WHILE EVERY +// SUBJECT REMAINS DYNAMICALLY EVALUATED. Y2 is a separate, later replacement whose X is generic +// evaluation itself and which may eventually retire Y1 too -- it is not a prerequisite for deleting +// v1. Treating it as one is what produced this row's central defect: a Y1-shaped CONTRACT -- +// evaluate a resolved .dag declaration, or refuse -- proved unavailable by Y2-shaped EVIDENCE, the +// per-subject native roster. That does not follow. The roster is the denominator of a different +// replacement. +// +// WHAT THE BOOTSTRAP ACTUALLY LOOKS LIKE, which explains the frozen-X behavior far more directly +// than any roster: X currently interprets v2.compiler.eval, which MODELS the intended replacement +// semantics. That is X executing the SPECIFICATION of Y, not Y existing independently. Delete X +// today and the only general mechanism by which the v2 evaluator's own .dag implementation can +// execute goes with it. +// +// AND A THIRD EVALUATOR IS NOT THE ANSWER: authoring a fresh minimal interpreter beside these would +// make three authorities for evaluation behavior -- v1's semantics, v2.compiler.eval's semantics, +// and the new one's. The move is to make the evaluator that already exists independently real. A +// minimal EMIT-ABLE SLICE of v2.compiler.eval is a legitimate bootstrap increment, but only if it +// implements the existing interpretation algebra, shares the same runtime-value and diagnostic +// authorities, fails closed, never falls back to v1, and carries an explicit dissolution into the +// complete emitted evaluator -- that is a first increment of Y1, not a third semantic design. +// +// EMISSION IS THE FIRST BLOCKER AND NOT NECESSARILY THE LAST: v2.compiler.eval carries +// runtime_reject arms today. Even once it builds independently it may refuse real retained roots -- +// and THOSE REFUSALS ARE THE CENSUS THIS PROJECT HAS NEVER HAD, at the right grain: reachable +// evaluator forms, required primitive operations, required effects, required value and result +// representations. Not 744 declaration names and not 1350 Rust reference hits. +// +// A CONSEQUENCE WORTH CARRYING: on a boundary stated in consumer terms, cli_run is a CONSUMER of +// DagExecution and not automatically the parent cut. The unauthored-parent finding recorded in +// why_not_higher may therefore be an artifact of drawing the boundary at a FILE rather than at a +// contract, in which case it dissolves rather than needing to be solved. That arm is left as +// authored until it is re-measured at this boundary, because replacing a measured finding with a +// predicted one is the same error in the other direction. +// +// CENSUS GRAIN, declared: one row per distinct consuming module or binary, selected as the measured +// top of the reference distribution on main at 7f5fa6a94ec1 (1350 line-hits across 125 files; the +// ten rows below carry 1004 of them). This is NOT proven equal to the real boundary population -- +// the carrier states that limitation for every plan, and it holds here: an omitted consumer cannot +// be detected at this grain. The remaining 115 files are the residue this row does not enumerate, +// and that residue is a known omission rather than a claim of completeness. +// +// WHAT THAT REFERENCE COUNT DOES AND DOES NOT MEASURE: it measures ENTANGLEMENT -- how much code +// reaches X -- and it does not measure surviving OBLIGATIONS. Reliance establishes migration cost, +// never correctness, so the line-hit total is a selection instrument for census grain and nothing +// else. It must not be quoted as the size of the cut: the number that would be is how many of those +// references survive dispositioning, and that is not knowable until the boundary is settled. +// +// CORRECTION (row disposition, found by review after landing): coproduct_reflection and pre_push +// were authored Undecided on the stated ground that no disposition could be earned at an unauthored +// parent boundary. The measured branch DELETES both files, so this carrier reported an open +// decision for two consumers the artifact had already disposed of -- a false statement in a landed +// plan that other lanes read. Both are now Retire, because the deletion IS the disposition. cli_run +// remains Undecided and that is not the same defect: its region belongs to another lane, and +// parking a decision that is genuinely not this lane's to make is the honest arm rather than an +// error. Recorded here rather than silently amended, because a plan that corrects itself without +// saying so is indistinguishable from one that was always right. +// +// AND A DISPUTE THAT RIDES ON THOSE TWO ROWS, recorded because ratifying it silently would be the +// worse error: Retire here reports WHAT THE MEASURED BRANCH DID, not that deleting those consumers +// was methodologically sound. Review has since held that deleting consumers and behavioral sensors +// alongside X damages the experiment -- once a demand signal is removed, the absence of further +// failure no longer means the behavior was not load-bearing, it may only mean nothing is left to +// ask. A consumer earns Retire by being dispositioned, not by having been deleted. These two rows +// are therefore accurate as a record of the branch and are NOT a finding that the behavior had no +// surviving subject. data interpreter_cut_identity: ReplacementCutId = "cut-v1-interpreter" as ReplacementCutId diff --git a/dag/gunbc/lambda_argument_typing_gate.dag b/dag/gunbc/lambda_argument_typing_gate.dag index 8caf4600d55..3376724f3a5 100644 --- a/dag/gunbc/lambda_argument_typing_gate.dag +++ b/dag/gunbc/lambda_argument_typing_gate.dag @@ -209,7 +209,10 @@ data lambda_typing_gate_second_cause_row: DeclarationRef = decl_ref(module_path: // site came out red. It came out red -- and rustc's caret is on `acc`, never on `row`. The field // access is incidental. Had that observation been made after seeing the red instead of before, "the // only closure doing field access" would have read as an explanation and shipped as one. -data lambda_typing_gate_detection_is_not_causation_note: String = "Both specimens above share one shape: a signal that is TRUE and LOCAL and points at the wrong place. rustc's caret is at the closure because that is where inference ran out, not where the information was lost. A pre-registered hunch is the cheap defence, and it works by being falsifiable before the data arrives rather than by being correct." +// Both specimens above share one shape: a signal that is TRUE and LOCAL and points at the wrong +// place. rustc's caret is at the closure because that is where inference ran out, not where the +// information was lost. A pre-registered hunch is the cheap defence, and it works by being +// falsifiable before the data arrives rather than by being correct. // A WARNING ABOUT COUNTING E0282 ON THESE CRATES, because the next reader will try. The emitted // closure for v2.compiler.parse carries SEVEN E0282 diagnostics and only ONE of them is this row's. diff --git a/dag/gunbc/language_module_home.dag b/dag/gunbc/language_module_home.dag index c490cf4b3b2..22daf4e0e98 100644 --- a/dag/gunbc/language_module_home.dag +++ b/dag/gunbc/language_module_home.dag @@ -31,9 +31,49 @@ import gunbc.language_target_registry { } import std.dissolution { DissolutionCondition, unbound_dissolution } -data language_module_home_law: String = "WHERE A TARGET'S SURFACES LIVE IS ORGANIZATIONAL POLICY, NEVER IDENTITY (operator verdict relayed 2026-08-05). DeclarationRef remains the single identity authority for a language target; a canonical_prefix says where that target's modules SHOULD be stored and decides nothing about what the target IS. The two must not fuse, because a path prefix is exactly the kind of positional fact that would start competing with the symbolic key if it ever became a discriminant — the class DESIGN section 3 rules on when it says paths are discriminators, not gospel. WHAT THIS MODULE ENFORCES, STATED AS NARROWLY AS IT IS TRUE (operator REQUEST_CHANGES, 2026-08-05): a file ADDED under dag/extdeps/languages/ must sit inside a DECLARED TARGET DIRECTORY, or be one of the four exact legacy flat modules. That is a directory-membership ratchet. IT IS NOT A TARGET-PAIRING WALL, and an earlier revision of this module claimed it was — the law read 'a module misfiled under another target's directory is therefore REFUSED', which was false of the mechanism that shipped. The gate observes added PATH STRINGS and nothing ever supplies or resolves a module's declared extdeps_model_scope.subject, so dag/extdeps/languages/html/verilog_emit.dag is ADMITTED — html/ is a declared home — even if that file declares the Verilog subject. Wrong-target placement is precisely what the claim promised and precisely what the predicate never asked. WHY THE OVERCLAIM SURVIVED REVIEW is worth recording with it: a witness did exercise the relational property, but it associated the path with the HTML home by reading html/ OUT OF THE PATH — the path-to-identity fusion this very note forbids, performed by the control meant to prove it never happens. A correct control names verilog_language_subject_ref and demands the Verilog prefix; it cannot be written against a caller that only has a string. THE RELATIONAL WALL IS A SEPARATE CHANGE, and it needs a real join: a placement observation carrying (file_path, target) resolved from each added carrier's own scope, looked up in this roster by THAT target. The predicates that decided the relational case correctly were DELETED here rather than left beside an unwired claim, because machinery kept for a future consumer is the inert-lens tier DESIGN section 6 names and this module already shipped one of those." +// WHERE A TARGET'S SURFACES LIVE IS ORGANIZATIONAL POLICY, NEVER IDENTITY (operator verdict relayed +// 2026-08-05). DeclarationRef remains the single identity authority for a language target; a +// canonical_prefix says where that target's modules SHOULD be stored and decides nothing about what +// the target IS. A path prefix is exactly the positional fact that would compete with the symbolic +// key if it became a discriminant — the class DESIGN section 3 rules on: paths are discriminators, +// not gospel. WHAT THIS MODULE ENFORCES, AS NARROWLY AS IT IS TRUE (operator REQUEST_CHANGES, +// 2026-08-05): a file ADDED under dag/extdeps/languages/ must sit inside a DECLARED TARGET +// DIRECTORY, or be one of the four exact legacy flat modules — a directory-membership ratchet. IT +// IS NOT A TARGET-PAIRING WALL; an earlier revision's law read 'a module misfiled under another +// target's directory is therefore REFUSED', which was false of the shipped mechanism. The gate +// observes added PATH STRINGS and nothing supplies or resolves a module's declared +// extdeps_model_scope.subject, so dag/extdeps/languages/html/verilog_emit.dag is ADMITTED — html/ +// is a declared home — even if it declares the Verilog subject. WHY THE OVERCLAIM SURVIVED REVIEW: +// a witness did exercise the relational property, but associated the path with the HTML home by +// reading html/ OUT OF THE PATH — the path-to-identity fusion this note forbids, performed by the +// control meant to prove it never happens. A correct control names verilog_language_subject_ref +// and demands the Verilog prefix; it cannot be written against a caller holding only a string. THE +// RELATIONAL WALL IS A SEPARATE CHANGE needing a real join: a placement observation carrying +// (file_path, target) resolved from each added carrier's own scope, looked up in this roster by +// THAT target. The predicates that decided the relational case correctly were DELETED rather than +// left beside an unwired claim: machinery kept for a future consumer is the inert-lens tier DESIGN +// section 6 names, and this module already shipped one. -data language_module_home_scope_note: String = "WHAT THIS MODULE DOES AND DELIBERATELY DOES NOT DO. IT DOES: declare a canonical home per registered language target, carry the four legacy flat modules as an EXACT finite frontier with a dissolution trigger each, and supply the admission predicate tools.extdeps_scope_placement_gate calls on the paths a change adds. IT DOES NOT: move any file, edit any importer, touch src/v2, or check that a module sits under ITS OWN target's home rather than someone else's. IT ALSO NO LONGER CARRIES EXCEPTIONS FOR UNREGISTERED-TARGET DIRECTORIES (operator P1, 2026-08-05). An earlier revision declared dag/extdeps/languages/gpu/ and dag/extdeps/languages/simd/ as admitted-because-unregistered. Because the gate observes only NEWLY ADDED paths, existing files under them never needed an exception at all — so the rows bought nothing for the present tree and granted a permanent licence for unlimited FUTURE files under an uncited subtree. An exact roster of two prefixes is bounded in rows and unbounded in effect, which is the distinction the first reading of it missed. New work under a language this repository has not registered should force the registration, which is what the absence of those rows now does. NGSPICE IS ALSO OUT OF THIS WALL'S POPULATION: the roster once declared a simulator home under dag/extdeps/simulators/, while the predicate judges only dag/extdeps/languages/, so that row passed by never being consulted rather than by being satisfied — an unenforced claim, removed. WHY THE FLAT-MODULE MOVES ARE NOT MADE HERE: all four are rows of the FROZEN legacy manifest in gunbc.extdeps_scope_frontier, and an added dag/extdeps path can only land as scope carrier or genuine machinery, never rejoin the manifest — so each move forces its surface module to spell a second literal ExternalAuthority for an upstream whose subject carrier D0 just authored. That question is the one escalated in extdeps.posix.shell_command_language posix_authority_duplication_note." +// WHAT THIS MODULE DOES AND DELIBERATELY DOES NOT DO. IT DOES: declare a canonical home per +// registered language target, carry the four legacy flat modules as an EXACT finite frontier with a +// dissolution trigger each, and supply the admission predicate tools.extdeps_scope_placement_gate +// calls on the paths a change adds. IT DOES NOT: move any file, edit any importer, touch src/v2, or +// check that a module sits under ITS OWN target's home rather than someone else's. IT ALSO NO +// LONGER CARRIES EXCEPTIONS FOR UNREGISTERED-TARGET DIRECTORIES (operator P1, 2026-08-05). An +// earlier revision declared dag/extdeps/languages/gpu/ and dag/extdeps/languages/simd/ as +// admitted-because-unregistered. The gate observes only NEWLY ADDED paths, so existing files under +// them never needed an exception — the rows bought nothing for the present tree and granted a +// permanent licence for unlimited FUTURE files under an uncited subtree: bounded in rows, unbounded +// in effect. New work under an unregistered language should force the registration, which their +// absence now does. NGSPICE IS ALSO OUT OF THIS WALL'S POPULATION: the roster once declared a +// simulator home under dag/extdeps/simulators/, while the predicate judges only +// dag/extdeps/languages/, so that row passed by never being consulted — an unenforced claim, +// removed. WHY THE FLAT-MODULE MOVES ARE NOT MADE HERE: all four are rows of the FROZEN legacy +// manifest in gunbc.extdeps_scope_frontier, and an added dag/extdeps path can only land as scope +// carrier or genuine machinery, never rejoin the manifest — so each move forces its surface module +// to spell a second literal ExternalAuthority for an upstream whose subject carrier D0 just +// authored. That question is escalated in extdeps.posix.shell_command_language +// posix_authority_duplication_note. type LanguageTargetHome { target: DeclarationRef @@ -143,7 +183,13 @@ data language_target_homes: List = [ } ] -data legacy_flat_module_note: String = "THE EXACT FINITE FRONTIER. Four rows, enumerated by walking dag/extdeps/languages for files sitting directly in it rather than in a target directory, verified against the live tree on 2026-08-05 rather than transcribed from a report. Each row names the file as it exists today, the target whose surface it implements, and the address a move should land at. The roster is EXACT and the wall reads it by equality, so this is not a prefix or a pattern: a flat module absent from these four rows refuses. That is what makes the frontier finite rather than a category that can quietly grow — the failure mode the scope frontier's own frozen manifest exists to prevent, applied one directory down." +// THE EXACT FINITE FRONTIER. Four rows, enumerated by walking dag/extdeps/languages for files +// sitting directly in it rather than in a target directory, verified against the live tree on +// 2026-08-05 rather than transcribed from a report. Each row names the file as it exists today, the +// target whose surface it implements, and the address a move should land at. The wall reads the +// roster by equality, not prefix or pattern: a flat module absent from these four rows refuses. +// That keeps the frontier finite rather than a category that can quietly grow — the scope +// frontier's frozen manifest, applied one directory down. type LegacyLanguageModuleHome { file_path: String @@ -188,8 +234,19 @@ type FlatModuleVerdict = FlatModuleOnFrontier { file_path: String, moves_to: String } | FlatModuleRefused { file_path: String } - -data flat_module_refusal_note: String = "THE WALL, AND WHY ITS FAILURE ARM REFUSES RATHER THAN WIDENS. A flat language module is admitted ONLY by exact membership in the frontier above; anything else is FlatModuleRefused carrying the offending path, which is typed and located rather than a Bool. There is no arm that admits an unrecognised flat module, no allowance keyed on a name pattern, and no toggle — the three shapes DESIGN section 5 rules out. THE CONSEQUENCE, ENFORCED RATHER THAN INTENDED: a new target-specific module authored flat beside the target directories cannot land, so the population this frontier freezes can only shrink, because tools.extdeps_scope_placement_gate calls added_language_paths_refused on the paths this change adds and exits failure naming them. AN EARLIER REVISION CLAIMED THAT CONSEQUENCE WHILE NOTHING CALLED THE PREDICATE (cursor review 48516, and the parent lane found it independently). That is the coverage-by-illusion tier DESIGN section 6 names — machinery exists, nothing gates on it, and an inert lens is itself a lie — with the sentence asserting the wall being the only thing that made it look otherwise. Caught in review, not by me. A module inside a target directory is not a flat module and is never consulted here, which is the whole point — the wall constrains where NEW surfaces go without touching the twenty-five directories D0 created." +// THE WALL, AND WHY ITS FAILURE ARM REFUSES RATHER THAN WIDENS. A flat language module is admitted +// ONLY by exact membership in the frontier above; anything else is FlatModuleRefused carrying the +// offending path, typed and located rather than a Bool. No arm admits an unrecognised flat module, +// no allowance is keyed on a name pattern, and there is no toggle — the three shapes DESIGN section +// 5 rules out. THE CONSEQUENCE, ENFORCED RATHER THAN INTENDED: a new target-specific module +// authored flat beside the target directories cannot land, so this population can only shrink, +// because tools.extdeps_scope_placement_gate calls added_language_paths_refused on the paths a +// change adds and exits failure naming them. AN EARLIER REVISION CLAIMED THAT CONSEQUENCE WHILE +// NOTHING CALLED THE PREDICATE (cursor review 48516, and the parent lane found it independently) — +// the coverage-by-illusion tier DESIGN section 6 names: machinery exists, nothing gates on it, an +// inert lens is itself a lie. Caught in review, not by me. A module inside a target directory is +// never consulted here: the wall constrains where NEW surfaces go without touching the twenty-five +// directories D0 created. fn classify_flat_module(file_path: String) -> FlatModuleVerdict { fold( @@ -218,7 +275,17 @@ fn every_flat_module_is_on_the_frontier(walked_flat_paths: List) -> Bool data language_root_prefix: String = "dag/extdeps/languages/" -data added_path_admission_note: String = "THE GATE PREDICATE. A path added under dag/extdeps/languages/ is ADMITTED if it sits inside some declared target directory or is one of the four exact legacy frontier rows; everything else under that root REFUSES. Paths outside that root are not judged here at all, and that is now stated rather than left as an accident of the prefix test. THE SHAPE IS AN ADMISSION AGAINST DECLARED ROSTERS, NOT A FLATNESS TEST, deliberately: deciding whether a path is 'flat' from a string means splitting it into segments and then reasoning about what a segment denotes, which is one step from reading a target off a directory name. Admission reaches the same outcome without the identity question ever being asked. WHAT IT ENFORCES, EXACTLY: a new module authored directly in dag/extdeps/languages/ refuses, and a new directory under that root refuses until a home for its target is declared in the same change. WHAT IT DOES NOT ENFORCE: which of the declared directories a given module belongs in. It admits membership in SOME declared home because a path string is all it receives." +// THE GATE PREDICATE. A path added under dag/extdeps/languages/ is ADMITTED if it sits inside some +// declared target directory or is one of the four exact legacy frontier rows; everything else under +// that root REFUSES. Paths outside that root are not judged here, stated rather than left as an +// accident of the prefix test. THE SHAPE IS AN ADMISSION AGAINST DECLARED ROSTERS, NOT A FLATNESS +// TEST: deciding 'flat' from a string means splitting segments and reasoning about what one +// denotes, one step from reading a target off a directory name. Admission reaches the same outcome +// without asking the identity question. WHAT IT ENFORCES, EXACTLY: a new module directly in +// dag/extdeps/languages/ refuses, and a new directory under that root refuses until a home for its +// target is declared in the same change. WHAT IT DOES NOT ENFORCE: which declared directory a +// given module belongs in — it admits membership in SOME declared home because a path string is +// all it receives. fn path_under_any_declared_home(file_path: String) -> Bool { fold(language_target_homes, init: false, f: fn(acc, h) { @@ -241,8 +308,20 @@ fn added_language_paths_refused(added: List) -> List { }) } +// MAXIMUM CONCLUSION — what a green check establishes and what it cannot. IT ESTABLISHES that every +// path this change adds under dag/extdeps/languages/ lies inside a declared target directory or is +// an exact legacy frontier row, so a new flat module and a new undeclared target directory both +// refuse. IT DOES NOT ESTABLISH THAT A FILE SITS UNDER THE RIGHT TARGET'S DIRECTORY — the gate +// receives path strings and no declared subject, so an emitter for one language placed under +// another's directory passes. That is the wall's edge, not a hidden arm: closing it requires a +// placement observation carrying (file_path, target) resolved from the file's own +// extdeps_model_scope, a separate change. IT DOES NOT ESTABLISH that the four frontier rows are the +// complete flat population of the live tree: the gate reads what a change ADDS at merge-base +// grain, governing what enters and not what already sits there. NOR that any target's surfaces are +// complete or correctly grouped. The construction-side end state is the one the +// module-identity-versus-storage lane names: when a module's home is derived from the containment +// tree rather than declared beside it, this frontier and its predicate both delete. + fn legacy_flat_module_count() -> Int { fold(legacy_language_module_homes, init: 0, f: fn(acc, r) { acc + 1 }) } - -data language_module_home_maximum_conclusion: String = "MAXIMUM CONCLUSION — what a green check here establishes and what it cannot. IT ESTABLISHES that every path this change adds under dag/extdeps/languages/ lies inside a declared target directory or is an exact legacy frontier row, so a new flat module and a new undeclared target directory both refuse. IT DOES NOT ESTABLISH THAT A FILE SITS UNDER THE RIGHT TARGET'S DIRECTORY — the gate receives path strings and no module's declared subject, so an emitter for one language placed under another language's directory passes. That is the wall's edge, not a hidden arm: closing it requires a placement observation carrying (file_path, target) resolved from the file's own extdeps_model_scope, which is a separate change. IT DOES NOT ESTABLISH that the four frontier rows are the complete flat population of the live tree: the gate reads what a change ADDS at merge-base grain, so it governs what enters and says nothing about what already sits there. NOR DOES IT ESTABLISH that any target's surfaces are complete or correctly grouped. The construction-side end state is the one the module-identity-versus-storage lane names: when a module's home is derived from the containment tree rather than declared beside it, this frontier and its predicate both delete." diff --git a/dag/gunbc/language_source_scaffold_index.dag b/dag/gunbc/language_source_scaffold_index.dag index 65fa292ad40..2f9a1cdc83e 100644 --- a/dag/gunbc/language_source_scaffold_index.dag +++ b/dag/gunbc/language_source_scaffold_index.dag @@ -2,9 +2,25 @@ module gunbc.language_source_scaffold_index import std.dissolution { DissolutionCondition, unbound_dissolution } -data language_source_scaffold_index_note: String = "B2 inventory for hand-authored target-language semantics in language files. Every blob of hand-written target source carries a row here whose disposition is a std.disposition Scaffold naming the construction that subsumes it, or a Terminal stating why it is irreducible. No string-content classifier is involved: the mark is a typed value, not prose to be parsed." - -data language_source_scaffold_enforcement_boundary: String = "What is actually enforced, stated precisely because the earlier wording overstated it (review 43129). CONSTRUCTION (real): Disposition has no prose and no absent arm, so a row cannot carry an unmarked or free-text disposition — Terminal-with-reason or Scaffold-with-bind are the only inhabitants, and 'unmarked' is genuinely unwritable. CONSTRUCTION (literal sites only, PR #7438): DeclarationRef.module_path and decl_name are NonEmptyStr (String where non_empty) and the v1 typechecker enforces non_empty at string-literal construction sites — an empty bind is unwritable at compile time. Non-literal string values at those positions emit counted WhereRefinementUnenforced advisory (wall after grounding: decidable whether evidence exists, evidence carrier not yet modeled — dissolve-on feature:refinement-evidence-at-non-literal-positions). language_source_scaffold_row_is_dispositioned still checks bind.decl_name != \"\" && bind.module_path != \"\" for non-literal paths and remains load-bearing until every construction site is covered; the empty-bind hard RED control lives in dag/test/claim/where_refinement_enforcement_witness_test.dag." +// B2 inventory for hand-authored target-language semantics in language files. Every blob of +// hand-written target source carries a row here whose disposition is a std.disposition Scaffold +// naming the construction that subsumes it, or a Terminal stating why it is irreducible. No +// string-content classifier is involved: the mark is a typed value, not prose to be parsed. + +// What is actually enforced, stated precisely because the earlier wording overstated it (review +// 43129). CONSTRUCTION (real): Disposition has no prose and no absent arm, so a row cannot carry an +// unmarked or free-text disposition — Terminal-with-reason or Scaffold-with-bind are the only +// inhabitants, and 'unmarked' is genuinely unwritable. CONSTRUCTION (literal sites only, PR #7438): +// DeclarationRef.module_path and decl_name are NonEmptyStr (String where non_empty) and the v1 +// typechecker enforces non_empty at string-literal construction sites — an empty bind is unwritable +// at compile time. Non-literal string values at those positions emit counted +// WhereRefinementUnenforced advisory (wall after grounding: decidable whether evidence exists, +// evidence carrier not yet modeled — dissolve-on +// feature:refinement-evidence-at-non-literal-positions). +// language_source_scaffold_row_is_dispositioned still checks bind.decl_name != "" && +// bind.module_path != "" for non-literal paths and remains load-bearing until every construction +// site is covered; the empty-bind hard RED control lives in +// dag/test/claim/where_refinement_enforcement_witness_test.dag. type LanguageSourceScaffoldRow { carrier_module: String diff --git a/dag/gunbc/language_target_registry.dag b/dag/gunbc/language_target_registry.dag index 89d860aac9e..0c4414020b1 100644 --- a/dag/gunbc/language_target_registry.dag +++ b/dag/gunbc/language_target_registry.dag @@ -70,23 +70,131 @@ import std.language_target_identity { count_rows, } -data language_target_registry_totality_law: String = "NO INVISIBLE REMAINDER (operator requirement C, 2026-08-04). This registry may not read as complete because two specimen rows exist. Every target-bearing surface this repository currently carries is EITHER joined to a registered subject OR appears here as an exact row with no identity, which the join kernel then reports as SurfaceMissingTargetIdentity. A surface that is simply absent from these lists is the one state the wall cannot see, which is why the population definition below is declared rather than implied, and why the TargetVocabulary rows carry an explicit scaffold note rather than a silent transcription. The registry now registers 26 subjects, so most rows below ARE identified; the ones that are not — machine_code and the legacy frontier prose table — each carry a note stating why joining them to a subject would be WRONG rather than merely undone, and both are deliberate. CORRECTED 2026-08-04 (review 48151): this sentence previously also listed the two runtime checkers as unidentified, which stopped being true in this same change when cargo was bound to Rust and the interpreter to Python. The authority describing the census contradicted the census two hundred lines below it — the stale-prose class this registry has now hit four times in a day, and the reason every correction here quotes what it replaced." - -data language_target_registry_vocabulary_scaffold_note: String = "DECLARED SCAFFOLD, with its dissolution trigger — the TargetVocabulary rows below are HAND-DECLARED, and they were meant to be derived. The intent was to read v2.lens.realization_vocabulary_containment's target_ast_vocab_modules and target_ast_vocab_module_prefixes directly, so a module added to the lens would appear here automatically as an unidentified surface and the second-hardcoded-roster fork would close by construction. THAT IMPORT DOES NOT COMPILE TODAY, and the blocker is not in this module: the lens fails to resolve fold_list and list_snoc_item from v2.std.algebra even when compiled from its OWN source root as the entry — measured 2026-08-04. That is the Class B pool-membership defect DESIGN's import-strip open thread describes: a module's bare cross-module references resolve only when some unrelated import elsewhere in the assembled closure has already dragged the target into the pool, so the lens resolves inside the whole-corpus compile and not in a narrow closure. Transcribing the two lists here would be the workaround DESIGN section 5 forbids — an unmarked second representation of the very roster this registry exists to unfork — so they are declared as a counted scaffold instead. DISSOLVE-ON: the lens resolves under a narrow closure (Class B fix), at which point these rows become the fold over its lists; and ultimately the reverse direction, the lens deriving its confined set FROM this registry, which is the prerequisite edge from the support-registry obligation to the no-new-debt-gate obligation in the governing strategy contract, and is not done here. Those obligations are identified in a private strategy overlay rather than in this repository, so they are described by role and not named — publishing an overlay identifier is an explicit act under that repository's opt-in-public model, and it has not been authorised." - -data language_target_registry_population_definition: String = "Target-bearing surfaces enumerated from six declared authorities: (1) StdLanguageCatalogue — std.languages Language rows; (2) TargetModelDeclaration — committed TargetModel-bearing language modules; (3) EmitHostExecution — tools.emit_host_transport per-language smoke fns; (4) TargetVocabulary — the confined module list and prefix list v2.lens.realization_vocabulary_containment enforces; (5) RuntimeChecker — external toolchains a support claim would execute against; (6) LegacyFrontier — gunbc.plans.language_target_self_host_frontier's prose rows. ALL SIX authorities are HAND-DECLARED at this revision — authority 4 was intended to be derived and is a counted scaffold with a stated trigger (language_target_registry_vocabulary_scaffold_note); nothing in this module is derived from the live tree. GRAIN, because it decides whether a row can be checked: a locus is either a real module path or a declared PREFIX over several (the extdeps.languages family paths cover multiple modules each, and v2.workflow.bash is a prefix over two, matching the containment lens's own grain). Distinguishing a legitimate prefix from a path that names nothing requires testing exact-match and prefix-match separately — the instrument fierce-heron-301 used to find three phantom rows among nine legitimate ones (2026-08-04). AND THE PREFIX MUST BE A STRING PREFIX, NOT A NAMESPACE ONE, which is not a quibble: a full sweep of this census on 2026-08-04 reported v2.workflow.bash as naming nothing when prefix was read as namespace containment, because the two modules under it are v2.workflow.bash_command_fold_serialize and v2.workflow.bash_emit — siblings sharing a name stem, not children of a v2.workflow.bash namespace. The containment lens confines by string prefix, so the census must match that grain or it manufactures phantoms out of the lens's real rows. Under the corrected instrument the census has zero phantoms." - -data language_target_registry_maximum_conclusion: String = "MAXIMUM CONCLUSION — what a green accounting or integrity check here does and does not establish. IT ESTABLISHES: every surface in the declared population above is accounted for, each either joined to a registered subject or reported as an exact unidentified row, and that no malformed join input is present; IT DOES NOT ESTABLISH that the declared population is COMPLETE with respect to the repository. All six authorities are hand-declared, so a target-bearing surface nobody wrote a row for is invisible to every assertion over this module — the failure mode loyal-ant-382 measured at 43 percent under-selection on a text-keyed census and this session reproduced at 100 percent on the ShellCommand sink. NOR DOES IT ESTABLISH THAT A DECLARED ROW NAMES A REAL SURFACE: three census rows were found on 2026-08-04 to be file paths transcribed as module paths, naming modules that do not exist, and no assertion in this module could have caught them because the population is the thing being asserted over. Closing both directions needs a Node-tree observer deriving the population from the real module tree, which is the residue-census obligation and is NOT built here. No count in this module may be quoted as a corpus claim." - -data registry_revision_note: String = "WHY THERE IS NO PINNED REVISION STAMP HERE, removed rather than corrected (2026-08-04). An earlier revision of this module carried language_target_registry_subject_revision as a hand-written commit SHA, on the reasoning that a row set without its revision is a measurement pretending to be a standing fact. The reasoning was right and the mechanism was self-defeating in two ways. FIRST, IT WAS ALREADY FALSE: the stamp named the commit current when it was typed, and the row set then changed in four subsequent commits — including the one taking the registry from one target to three — so the declaration asserted an enumeration that had not held for some time. A review approved it as anchored honestly, which is the tell that a stale stamp reads as provenance rather than as the claim it actually is. SECOND, AND DECISIVELY, IT CANNOT BE FIXED BY UPDATING: pinning the SHA of the commit that contains the pin is self-referential, so any such stamp is stale by at least one commit BY CONSTRUCTION, and each update re-copies a number from the tree — the measure() == measure() collapse DESIGN section 5 names. The rows ARE anchored, by the thing that already anchors them: git records exactly which revision this file held which rows, at line grain, without anyone maintaining it. A second hand-kept representation of that fact is the section 3 duplication this registry exists to remove, applied to the registry itself." - -data subject_ref_derivation_note: String = "EVERY SUBJECT REF BELOW IS A PROJECTION OF ITS SCOPE, never re-authored beside it. An earlier revision declared gnu_bash_subject_ref as a hand-written DeclarationRef repeating the module path and decl name the scope already carries — two writable representations of one identity, which can disagree exactly the way target_grounding_law describes. Reading scope.subject instead makes the disagreeing state unrepresentable: there is only one place the subject is stated, and it is the carrier that also holds the citations. Verified by execution 2026-08-04 that a top-level data declaration may project a field of an imported scope, so this costs nothing." +// NO INVISIBLE REMAINDER (operator requirement C, 2026-08-04). This registry may not read as +// complete because two specimen rows exist. Every target-bearing surface this repository currently +// carries is EITHER joined to a registered subject OR appears here as an exact row with no +// identity, which the join kernel then reports as SurfaceMissingTargetIdentity. A surface that is +// simply absent from these lists is the one state the wall cannot see, which is why the population +// definition below is declared rather than implied, and why the TargetVocabulary rows carry an +// explicit scaffold note rather than a silent transcription. The registry now registers 26 +// subjects, so most rows below ARE identified; the ones that are not — machine_code and the legacy +// frontier prose table — each carry a note stating why joining them to a subject would be WRONG +// rather than merely undone, and both are deliberate. CORRECTED 2026-08-04 (review 48151): this +// sentence previously also listed the two runtime checkers as unidentified, which stopped being +// true in this same change when cargo was bound to Rust and the interpreter to Python. The +// authority describing the census contradicted the census two hundred lines below it — the +// stale-prose class this registry has now hit four times in a day, and the reason every correction +// here quotes what it replaced. + +// DECLARED SCAFFOLD, with its dissolution trigger — the TargetVocabulary rows below are +// HAND-DECLARED, and they were meant to be derived. The intent was to read +// v2.lens.realization_vocabulary_containment's target_ast_vocab_modules and +// target_ast_vocab_module_prefixes directly, so a module added to the lens would appear here +// automatically as an unidentified surface and the second-hardcoded-roster fork would close by +// construction. THAT IMPORT DOES NOT COMPILE TODAY, and the blocker is not in this module: the lens +// fails to resolve fold_list and list_snoc_item from v2.std.algebra even when compiled from its OWN +// source root as the entry — measured 2026-08-04. That is the Class B pool-membership defect +// DESIGN's import-strip open thread describes: a module's bare cross-module references resolve only +// when some unrelated import elsewhere in the assembled closure has already dragged the target into +// the pool, so the lens resolves inside the whole-corpus compile and not in a narrow closure. +// Transcribing the two lists here would be the workaround DESIGN section 5 forbids — an unmarked +// second representation of the very roster this registry exists to unfork — so they are declared as +// a counted scaffold instead. DISSOLVE-ON: the lens resolves under a narrow closure (Class B fix), +// at which point these rows become the fold over its lists; and ultimately the reverse direction, +// the lens deriving its confined set FROM this registry, which is the prerequisite edge from the +// support-registry obligation to the no-new-debt-gate obligation in the governing strategy +// contract, and is not done here. Those obligations are identified in a private strategy overlay +// rather than in this repository, so they are described by role and not named — publishing an +// overlay identifier is an explicit act under that repository's opt-in-public model, and it has not +// been authorised. + +// Target-bearing surfaces enumerated from six declared authorities: (1) StdLanguageCatalogue — +// std.languages Language rows; (2) TargetModelDeclaration — committed TargetModel-bearing language +// modules; (3) EmitHostExecution — tools.emit_host_transport per-language smoke fns; (4) +// TargetVocabulary — the confined module list and prefix list +// v2.lens.realization_vocabulary_containment enforces; (5) RuntimeChecker — external toolchains a +// support claim would execute against; (6) LegacyFrontier — +// gunbc.plans.language_target_self_host_frontier's prose rows. ALL SIX authorities are +// HAND-DECLARED at this revision — authority 4 was intended to be derived and is a counted scaffold +// with a stated trigger (language_target_registry_vocabulary_scaffold_note); nothing in this module +// is derived from the live tree. GRAIN, because it decides whether a row can be checked: a locus is +// either a real module path or a declared PREFIX over several (the extdeps.languages family paths +// cover multiple modules each, and v2.workflow.bash is a prefix over two, matching the containment +// lens's own grain). Distinguishing a legitimate prefix from a path that names nothing requires +// testing exact-match and prefix-match separately — the instrument fierce-heron-301 used to find +// three phantom rows among nine legitimate ones (2026-08-04). AND THE PREFIX MUST BE A STRING +// PREFIX, NOT A NAMESPACE ONE, which is not a quibble: a full sweep of this census on 2026-08-04 +// reported v2.workflow.bash as naming nothing when prefix was read as namespace containment, +// because the two modules under it are v2.workflow.bash_command_fold_serialize and +// v2.workflow.bash_emit — siblings sharing a name stem, not children of a v2.workflow.bash +// namespace. The containment lens confines by string prefix, so the census must match that grain or +// it manufactures phantoms out of the lens's real rows. Under the corrected instrument the census +// has zero phantoms. + +// MAXIMUM CONCLUSION — what a green accounting or integrity check here does and does not establish. +// IT ESTABLISHES: every surface in the declared population above is accounted for, each either +// joined to a registered subject or reported as an exact unidentified row, and that no malformed +// join input is present; IT DOES NOT ESTABLISH that the declared population is COMPLETE with +// respect to the repository. All six authorities are hand-declared, so a target-bearing surface +// nobody wrote a row for is invisible to every assertion over this module — the failure mode +// loyal-ant-382 measured at 43 percent under-selection on a text-keyed census and this session +// reproduced at 100 percent on the ShellCommand sink. NOR DOES IT ESTABLISH THAT A DECLARED ROW +// NAMES A REAL SURFACE: three census rows were found on 2026-08-04 to be file paths transcribed as +// module paths, naming modules that do not exist, and no assertion in this module could have caught +// them because the population is the thing being asserted over. Closing both directions needs a +// Node-tree observer deriving the population from the real module tree, which is the residue-census +// obligation and is NOT built here. No count in this module may be quoted as a corpus claim. + +// WHY THERE IS NO PINNED REVISION STAMP HERE, removed rather than corrected (2026-08-04). An +// earlier revision of this module carried language_target_registry_subject_revision as a +// hand-written commit SHA, on the reasoning that a row set without its revision is a measurement +// pretending to be a standing fact. The reasoning was right and the mechanism was self-defeating in +// two ways. FIRST, IT WAS ALREADY FALSE: the stamp named the commit current when it was typed, and +// the row set then changed in four subsequent commits — including the one taking the registry from +// one target to three — so the declaration asserted an enumeration that had not held for some time. +// A review approved it as anchored honestly, which is the tell that a stale stamp reads as +// provenance rather than as the claim it actually is. SECOND, AND DECISIVELY, IT CANNOT BE FIXED BY +// UPDATING: pinning the SHA of the commit that contains the pin is self-referential, so any such +// stamp is stale by at least one commit BY CONSTRUCTION, and each update re-copies a number from +// the tree — the measure() == measure() collapse DESIGN section 5 names. The rows ARE anchored, by +// the thing that already anchors them: git records exactly which revision this file held which +// rows, at line grain, without anyone maintaining it. A second hand-kept representation of that +// fact is the section 3 duplication this registry exists to remove, applied to the registry itself. + +// EVERY SUBJECT REF BELOW IS A PROJECTION OF ITS SCOPE, never re-authored beside it. An earlier +// revision declared gnu_bash_subject_ref as a hand-written DeclarationRef repeating the module path +// and decl name the scope already carries — two writable representations of one identity, which can +// disagree exactly the way target_grounding_law describes. Reading scope.subject instead makes the +// disagreeing state unrepresentable: there is only one place the subject is stated, and it is the +// carrier that also holds the citations. Verified by execution 2026-08-04 that a top-level data +// declaration may project a field of an imported scope, so this costs nothing. data gnu_bash_subject_ref: DeclarationRef = gnu_bash_model_scope.subject.declaration -data d0_subject_registration_note: String = "D0 IS THE ROWS, AND THIS IS WHAT REPLACED THE BLOCKER NOTE THAT STOOD HERE. The previous revision recorded that registering TypeScript, C, C++, Verilog, SPICE, Python, Go, Java, Kotlin, Swift, LLVM IR, WebAssembly, PTX, machine code, Lean, SQL, HTML, CSS, ECMAScript, JSON, YAML and Markdown was impossible because the construction wall requires a TargetGrounding and NONE of them had a cited subject declaration — Bash was the only one, and it had only just been authored. Twenty-three cited subject carriers now exist under dag/extdeps/languages/*/subject.dag, each enrolled in scope_carrier_paths, and the registry registers 26 targets: 24 external plus the two project-grounded ones. THE CITATION RULE THIS BATCH FOLLOWED, because it is the whole value of the rows: every locator was FETCHED LIVE on 2026-08-04 and its title and publisher confirmed from the response before it was written down. None was recalled. Where an upstream could not be read, the target was NOT registered and the cause was recorded in unregistered_target_findings — three of them — because a plausible URL for an unverified authority would make the wall green while grounding it in nothing, which is worse than the honest absence the blocker note described." - -data unregistered_target_finding_note: String = "TYPED NON-REGISTRATIONS — the residue of verify-or-refuse, carried as rows so it is COUNTED and prioritizable rather than absorbed into silence (the extdeps.pricing.object_storage RateUnavailable precedent, where vendors whose rates were unreadable landed as typed unavailability instead of being filled from memory). Each row names a target that a reader would reasonably expect in the registry, and the exact reason it is absent. Two distinct causes appear and they need different work: an unreadable upstream authority is fixed by obtaining the document, while a fused surface is fixed by decomposing the surface — no amount of citation work registers x86-64 while machine_code still fuses three ISAs into one coproduct. These are NOT parked targets: a parked target is registered and grounded, these are ungrounded and therefore unwritable as registrations by construction." +// D0 IS THE ROWS, AND THIS IS WHAT REPLACED THE BLOCKER NOTE THAT STOOD HERE. The previous revision +// recorded that registering TypeScript, C, C++, Verilog, SPICE, Python, Go, Java, Kotlin, Swift, +// LLVM IR, WebAssembly, PTX, machine code, Lean, SQL, HTML, CSS, ECMAScript, JSON, YAML and +// Markdown was impossible because the construction wall requires a TargetGrounding and NONE of them +// had a cited subject declaration — Bash was the only one, and it had only just been authored. +// Twenty-three cited subject carriers now exist under dag/extdeps/languages/*/subject.dag, each +// enrolled in scope_carrier_paths, and the registry registers 26 targets: 24 external plus the two +// project-grounded ones. THE CITATION RULE THIS BATCH FOLLOWED, because it is the whole value of +// the rows: every locator was FETCHED LIVE on 2026-08-04 and its title and publisher confirmed from +// the response before it was written down. None was recalled. Where an upstream could not be read, +// the target was NOT registered and the cause was recorded in unregistered_target_findings — three +// of them — because a plausible URL for an unverified authority would make the wall green while +// grounding it in nothing, which is worse than the honest absence the blocker note described. + +// TYPED NON-REGISTRATIONS — the residue of verify-or-refuse, carried as rows so it is COUNTED and +// prioritizable rather than absorbed into silence (the extdeps.pricing.object_storage +// RateUnavailable precedent, where vendors whose rates were unreadable landed as typed +// unavailability instead of being filled from memory). Each row names a target that a reader would +// reasonably expect in the registry, and the exact reason it is absent. Two distinct causes appear +// and they need different work: an unreadable upstream authority is fixed by obtaining the +// document, while a fused surface is fixed by decomposing the surface — no amount of citation work +// registers x86-64 while machine_code still fuses three ISAs into one coproduct. These are NOT +// parked targets: a parked target is registered and grounded, these are ungrounded and therefore +// unwritable as registrations by construction. type TargetNonRegistrationCause = UpstreamAuthorityUnreadable { attempted_locator: String, observed: String } @@ -128,7 +236,17 @@ data unregistered_target_findings: List = [ } ] -data legacy_frontier_label_fusion_note: String = "FILED, NOT FIXED (direction from the lane owner, 2026-08-04). These are findings about the PROSE self-host frontier table, whose rows the per-subject carriers are meant to replace. They are carried as typed rows rather than prose because the point of filing them is that they are COUNTABLE and joinable during that migration — a paragraph describing a fused label cannot be enumerated, and this table is exactly what someone migrating the rows will need to consult. WHY THEY MATTER MORE TOGETHER THAN SEPARATELY: the machine_code coproduct fuses three ISAs in the TYPE layer, and the frontier table independently fuses the same targets in its LABEL layer. One defect visible at two layers is stronger evidence than either sighting alone, because it shows the fusion was not an artifact of how one carrier happened to be written. The targets named here ARE registered and correctly grounded — nothing about their subjects is in doubt; what is defective is the legacy label that a migration would otherwise match them by." +// FILED, NOT FIXED (direction from the lane owner, 2026-08-04). These are findings about the PROSE +// self-host frontier table, whose rows the per-subject carriers are meant to replace. They are +// carried as typed rows rather than prose because the point of filing them is that they are +// COUNTABLE and joinable during that migration — a paragraph describing a fused label cannot be +// enumerated, and this table is exactly what someone migrating the rows will need to consult. WHY +// THEY MATTER MORE TOGETHER THAN SEPARATELY: the machine_code coproduct fuses three ISAs in the +// TYPE layer, and the frontier table independently fuses the same targets in its LABEL layer. One +// defect visible at two layers is stronger evidence than either sighting alone, because it shows +// the fusion was not an artifact of how one carrier happened to be written. The targets named here +// ARE registered and correctly grounded — nothing about their subjects is in doubt; what is +// defective is the legacy label that a migration would otherwise match them by. type LegacyLabelFinding { legacy_label: String @@ -157,14 +275,53 @@ fn unregistered_target_finding_count() -> Int { fold(unregistered_target_findings, init: 0, f: fn(acc, r) { acc + 1 }) } -data project_grounded_targets_note: String = "The two PROJECT-grounded targets, and they are here for a reason beyond being cheap: ProjectGrounding had zero inhabitants, so an entire arm of the grounding coproduct was unexercised. These are also the two rows that could not have existed under a universal external subject carrier — the .dag language and the controlled-English medium are this project's OWN targets, with no independently governed upstream to cite, and forcing them through an external scope would have made them claim a governance they do not have. Their authority declarations are real and symbolic: dag_item_forms is the .dag surface-form authority, FormalSentence the controlled-English one. Neither carries an extdeps_model_scope and neither should. They are also now PROJECTABLE, which they were not while the join key was an external subject reference — the neutral DeclarationRef key is what lets one projection roster span both grounding arms instead of silently serving only external targets." +// The two PROJECT-grounded targets, and they are here for a reason beyond being cheap: +// ProjectGrounding had zero inhabitants, so an entire arm of the grounding coproduct was +// unexercised. These are also the two rows that could not have existed under a universal external +// subject carrier — the .dag language and the controlled-English medium are this project's OWN +// targets, with no independently governed upstream to cite, and forcing them through an external +// scope would have made them claim a governance they do not have. Their authority declarations are +// real and symbolic: dag_item_forms is the .dag surface-form authority, FormalSentence the +// controlled-English one. Neither carries an extdeps_model_scope and neither should. They are also +// now PROJECTABLE, which they were not while the join key was an external subject reference — the +// neutral DeclarationRef key is what lets one projection roster span both grounding arms instead of +// silently serving only external targets. data jsx_registry_role_note: String = "JSX'S REGISTRY ROLE IS AN OPEN DECISION, SURFACED RATHER THAN SETTLED BY REGISTRATION (cursor review 48122, P1). This registry registered JSX as a parked P3 target because a live surface is named extdeps.languages.react_jsx and the census must account for it. The reviewer's objection is fair: the web direction treats JSX as an OPTIONAL PROJECTION and React as a projection rather than a second component authority, so quietly adding JSX as an independently supported target enlarges the strategy's target universe by a side effect of census bookkeeping. THE QUESTION, stated so it can be answered rather than inherited: is JSX an independently supported target MEDIUM, or a syntax CAPABILITY of the web/ECMAScript path? WHAT IS NOT IN DOUBT is the subject: the upstream JSX specification states in its own text that JSX is independent of React and has no defined semantics, so citing it separately from React is correct either way and the carrier stands whichever role is chosen. WHAT THE DISPOSITION MEANS TODAY: TargetDecisionPending, not TargetParked. Parked would assert a settled strategy slot awaiting implementation; pending says the slot itself is undecided, which is the honest state. Registering it as pending keeps the surface accounted for — a census that dropped the row would hide a real target-bearing module — without asserting the answer." -data d0_scope_boundary_note: String = "WHAT THIS REGISTRY IS AND IS NOT, so it is not read as closing more than it does (cursor review 48122, P1 — a fair objection to how this work could be presented). This is the D0 IDENTITY AND GROUNDING slice: which targets exist, what upstream authority grounds each, and which surfaces join which subject. IT DOES NOT CLOSE THE SUPPORT REGISTRY. The support contract requires every registered target to carry declared support PROFILES, required CAPABILITY sets, EVIDENCE refs, and checker bindings in one typed registry, and none of those exist here. What is here in their place is deliberately weaker and should be read as such: priority appears only as free text inside parked reasons, and there are no capability or profile rows at all. Anyone treating a registration in this file as a support claim is reading it wrong — grounding says a target has an authority behind it, not that anything about it is supported." - -data governing_contract_id_resolution_note: String = "WHY THIS ROW CARRIES A MODULE NAME AND NOT THE GOVERNING CONTRACT IDENTIFIERS, and the answer is a publication boundary rather than a citation gap. The strategy authority that governs Rust is not in this repository: it lives in a separate private overlay, so its contract identifiers cannot be resolved — or verified — from here, and a sweep of this tree finds nothing no matter how thorough. THAT IS A FIFTH FAILURE SHAPE, worth naming because it is NOT the hand-carried-string class this lane spent the day fixing: those strings named nothing anywhere, while these name something real that is invisible from where the check stands. A measurement that cannot reach an authority must not conclude the authority is absent. WHY THE IDS ARE NOT SIMPLY COPIED IN: the private overlay runs an opt-in-public model in which publishing is an explicit act, so transcribing its identifiers into a public row would be a PUBLICATION DECISION taken by a registry edit — above this lane, and not repaired by the identifiers looking innocuous. The interim carried here is v2.compiler.self_host.emitter_producer_provenance, a real module in this repository that a reader can open, which is weaker than the governing contract but resolvable rather than dangling. It replaced v2.compiler.self_host.frontier when the self-host frontier roster was deleted; the first repoint went to emit_coverage_frontier and was wrong, because that module answers whether a witness body runs native (its axis a) and explicitly disclaims module self-emission (its axis b) — the name resembled the deleted one, which is not a reason. Rust is genuinely governed by MORE THAN ONE contract, which is why the carrier is plural and why this single entry is an interim rather than the truth. NEXT TRIGGER: the operator rules on whether the strategy keys are published; if they are, this field takes the exact governing identifiers and this note deletes. THE ECMASCRIPT AND C++ DECISION IDS ARE A DIFFERENT CASE: they name a document section in THIS repository, which is a positional citation — weaker than a symbol, but openable." - +// WHAT THIS REGISTRY IS AND IS NOT, so it is not read as closing more than it does (cursor review +// 48122, P1 — a fair objection to how this work could be presented). This is the D0 IDENTITY AND +// GROUNDING slice: which targets exist, what upstream authority grounds each, and which surfaces +// join which subject. IT DOES NOT CLOSE THE SUPPORT REGISTRY. The support contract requires every +// registered target to carry declared support PROFILES, required CAPABILITY sets, EVIDENCE refs, +// and checker bindings in one typed registry, and none of those exist here. What is here in their +// place is deliberately weaker and should be read as such: priority appears only as free text +// inside parked reasons, and there are no capability or profile rows at all. Anyone treating a +// registration in this file as a support claim is reading it wrong — grounding says a target has an +// authority behind it, not that anything about it is supported. + +// WHY THIS ROW CARRIES A MODULE NAME AND NOT THE GOVERNING CONTRACT IDENTIFIERS, and the answer is +// a publication boundary rather than a citation gap. The strategy authority that governs Rust is +// not in this repository: it lives in a separate private overlay, so its contract identifiers +// cannot be resolved — or verified — from here, and a sweep of this tree finds nothing no matter +// how thorough. THAT IS A FIFTH FAILURE SHAPE, worth naming because it is NOT the +// hand-carried-string class this lane spent the day fixing: those strings named nothing anywhere, +// while these name something real that is invisible from where the check stands. A measurement that +// cannot reach an authority must not conclude the authority is absent. WHY THE IDS ARE NOT SIMPLY +// COPIED IN: the private overlay runs an opt-in-public model in which publishing is an explicit +// act, so transcribing its identifiers into a public row would be a PUBLICATION DECISION taken by a +// registry edit — above this lane, and not repaired by the identifiers looking innocuous. The +// interim carried here is v2.compiler.self_host.emitter_producer_provenance, a real module in this +// repository that a reader can open, which is weaker than the governing contract but resolvable +// rather than dangling. It replaced v2.compiler.self_host.frontier when the self-host frontier +// roster was deleted; the first repoint went to emit_coverage_frontier and was wrong, because that +// module answers whether a witness body runs native (its axis a) and explicitly disclaims module +// self-emission (its axis b) — the name resembled the deleted one, which is not a reason. Rust is +// genuinely governed by MORE THAN ONE contract, which is why the carrier is plural and why this +// single entry is an interim rather than the truth. NEXT TRIGGER: the operator rules on whether the +// strategy keys are published; if they are, this field takes the exact governing identifiers and +// this note deletes. THE ECMASCRIPT AND C++ DECISION IDS ARE A DIFFERENT CASE: they name a document +// section in THIS repository, which is a positional citation — weaker than a symbol, but openable. data registered_language_targets: List = [ LanguageTargetRegistration { @@ -312,7 +469,23 @@ data ngspice_simulator_subject_ref: DeclarationRef = ngspice_simulator_model_sco data icarus_verilog_simulator_subject_ref: DeclarationRef = icarus_verilog_simulator_model_scope.subject.declaration -data target_runtime_binding_note: String = "WHICH RUNTIME EXECUTES A TARGET IS A SEPARATE FACT FROM WHAT THE TARGET IS (cursor review 48122, P0). Fusing them is what made ngspice look like a language: the registry registered the simulator, gave it the netlist's file extensions, and joined it to a surface that models a generic SpiceNetlist. Split here into two subjects plus this binding, so the medium can gain a second runtime without either subject changing — which is the whole test of whether the split was real. A binding is NOT a support claim: it says which tool WOULD execute this target, not that any execution receipt exists. SPICE's frontier row is bar-b, meaning the witness compares emitted text to a golden file and never runs ngspice at all, so this binding is precisely the thing an execution receipt would later discharge. THIS BINDING ALSO CARRIES THE DIALECT FACT, which belongs here and not on either subject's scope (cursor review 48361). SPICE has no normative netlist specification, so the readable authority for the concrete syntax this repository would emit is the ngspice manual — but that manual is ngspice's own governing document, and citing it on the SPICE medium's scope would claim one upstream grounds another. An earlier revision did exactly that and re-forked the fact this split had just separated. The binding is the honest home: it names both subjects by reference, each keeping its own authority, and says which runtime's dialect an emission would target. That is why a support claim for SPICE must resolve THROUGH this binding to state a dialect, rather than reading one off the medium's citation list." +// WHICH RUNTIME EXECUTES A TARGET IS A SEPARATE FACT FROM WHAT THE TARGET IS (cursor review 48122, +// P0). Fusing them is what made ngspice look like a language: the registry registered the +// simulator, gave it the netlist's file extensions, and joined it to a surface that models a +// generic SpiceNetlist. Split here into two subjects plus this binding, so the medium can gain a +// second runtime without either subject changing — which is the whole test of whether the split was +// real. A binding is NOT a support claim: it says which tool WOULD execute this target, not that +// any execution receipt exists. SPICE's frontier row is bar-b, meaning the witness compares emitted +// text to a golden file and never runs ngspice at all, so this binding is precisely the thing an +// execution receipt would later discharge. THIS BINDING ALSO CARRIES THE DIALECT FACT, which +// belongs here and not on either subject's scope (cursor review 48361). SPICE has no normative +// netlist specification, so the readable authority for the concrete syntax this repository would +// emit is the ngspice manual — but that manual is ngspice's own governing document, and citing it +// on the SPICE medium's scope would claim one upstream grounds another. An earlier revision did +// exactly that and re-forked the fact this split had just separated. The binding is the honest +// home: it names both subjects by reference, each keeping its own authority, and says which +// runtime's dialect an emission would target. That is why a support claim for SPICE must resolve +// THROUGH this binding to state a dialect, rather than reading one off the medium's citation list. type TargetRuntimeBinding { target: DeclarationRef @@ -381,7 +554,16 @@ data std_language_catalogue_rows: List = [ identified_decl_row(authority: StdLanguageCatalogue, module_path: "std.languages", decl_name: "typescript_language", subject: typescript_language_subject_ref), ] -data emit_host_node_smoke_subject_note: String = "run_node_http_serve_smoke joins the ECMASCRIPT subject, not TypeScript, and the distinction is the point. The smoke writes a server.js heredoc and executes it with node; DESIGN describes the same artifact as 'an emitted-JS static server'. So the LANGUAGE OF THE ARTIFACT is JavaScript. Two things this row deliberately does NOT claim: Node.js is the RUNTIME, an independently governed upstream product that would take its own subject if it were ever registered as a target, so it is not fused into this language identity; and this is not evidence that the v2.extdeps.languages.ecmascript MODULE has consumers — measured 2026-08-04, that module still has zero importers anywhere in the tree, exactly as the legacy frontier table says. A surface emitting a language is a different fact from a module being imported, and only the first is what this census row records." +// run_node_http_serve_smoke joins the ECMASCRIPT subject, not TypeScript, and the distinction is +// the point. The smoke writes a server.js heredoc and executes it with node; DESIGN describes the +// same artifact as 'an emitted-JS static server'. So the LANGUAGE OF THE ARTIFACT is JavaScript. +// Two things this row deliberately does NOT claim: Node.js is the RUNTIME, an independently +// governed upstream product that would take its own subject if it were ever registered as a target, +// so it is not fused into this language identity; and this is not evidence that the +// v2.extdeps.languages.ecmascript MODULE has consumers — measured 2026-08-04, that module still has +// zero importers anywhere in the tree, exactly as the legacy frontier table says. A surface +// emitting a language is a different fact from a module being imported, and only the first is what +// this census row records. data emit_host_execution_rows: List = [ identified_decl_row(authority: EmitHostExecution, module_path: "tools.emit_host_transport", decl_name: "run_rust_smoke", subject: rust_language_subject_ref), @@ -392,7 +574,12 @@ data emit_host_execution_rows: List = [ identified_decl_row(authority: EmitHostExecution, module_path: "tools.emit_host_transport", decl_name: "run_node_http_serve_smoke", subject: ecmascript_language_subject_ref), ] -data react_jsx_surface_naming_note: String = "extdeps.languages.react_jsx joins the JSX subject. The module path names two things — React, a library, and JSX, a syntax — but the upstream JSX specification states in its own text that JSX is independent of React and carries no defined semantics, so the syntax this surface models has exactly one upstream and it is JSX. React would be a separate subject if this repository ever modelled the library itself. The fused path name is a DISCRIMINATOR, not the identity (DESIGN section 3), and is left alone here rather than renamed inside a registration change." +// extdeps.languages.react_jsx joins the JSX subject. The module path names two things — React, a +// library, and JSX, a syntax — but the upstream JSX specification states in its own text that JSX +// is independent of React and carries no defined semantics, so the syntax this surface models has +// exactly one upstream and it is JSX. React would be a separate subject if this repository ever +// modelled the library itself. The fused path name is a DISCRIMINATOR, not the identity (DESIGN +// section 3), and is left alone here rather than renamed inside a registration change. data machine_code_fusion_note: String = "v2.extdeps.languages.machine_code is the one TargetModel surface that stays UNIDENTIFIED, and it is a finding rather than an omission. It carries a single Isa coproduct with three arms — X86_64Isa, Aarch64Isa, RiscV64Isa — which places three independently governed upstreams in one carrier. DESIGN section 3's external-upstream decomposition forbids exactly that: 'one concept, one authority unifies a shared interface or formal shape; it does not merge independently governed entities that inhabit that shape', and its worked rule is that adding a product must not widen a generic enum. So there is no single subject this surface could honestly join, and joining it to any one of the three would be the fusion asserting itself as an identity. RISC-V is registered as its own subject because its authority verified; the other two are recorded in unregistered_target_findings with their causes. DECOMPOSING machine_code is deliberately NOT done here — this change registers subjects, it does not restructure target surfaces." @@ -423,17 +610,58 @@ data target_model_declaration_rows: List = [ unidentified_module_row(authority: TargetModelDeclaration, module_path: "v2.extdeps.languages.machine_code"), ] -data census_path_resolution_note: String = "EVERY module_path IN THIS CENSUS WAS VERIFIED AGAINST THE REAL MODULE TREE, after review 48122 caught one that named nothing. The reviewer found the SPICE row pointing at extdeps.formats.spice, which is not a module — the surface is v2.extdeps.formats.spice, as this registry's own SPICE subject carrier already said. Identifying that row made a phantom worse: an unidentified phantom is merely a surface nobody can find, while an IDENTIFIED one asserts a join that can never be exercised, and it hid the real SPICE surface's absence from the census behind an apparently-accounted-for row. SWEEPING THE WHOLE CENSUS RATHER THAN THE ONE CITED ROW FOUND TWO MORE, both older and both the same root cause: extdeps.rust.cargo and extdeps.python.interpreter are FILE PATHS transcribed as module paths (dag/extdeps/rust/cargo.dag and dag/extdeps/python/python.dag), but the modules those files declare are extdeps.cargo and extdeps.python. That is DESIGN section 3's 'paths are discriminators, not gospel' violated in the direction that is hardest to see — a module path that LOOKS derived from a real file. They are corrected to the real modules and, per surface_locus_grain_note, moved to MODULE grain rather than being given invented decl_names: 'cargo' and 'python_interpreter' are not declarations either, and inventing one would be a nickname for a module. THE NINE REMAINING NON-EXACT PATHS ARE LEGITIMATE AND WERE CHECKED INDIVIDUALLY: the nine extdeps.languages family paths (rust, typescript, python, go, c, css, json, yaml, lean) each name a FAMILY of two to six real modules, and v2.workflow.bash is a string prefix covering v2.workflow.bash_command_fold_serialize and v2.workflow.bash_emit — the same prefix grain the containment lens uses. WHAT THIS DOES NOT ESTABLISH, and it is the honest limit: the sweep was done ONCE, by hand, at this revision. Nothing prevents the next phantom, because the census is hand-declared. The construction-side fix is the census being DERIVED from the module tree — already named as the residue-census obligation in language_target_registry_maximum_conclusion — at which point a path that names no module becomes unwritable rather than reviewable." - -data runtime_checker_subject_note: String = "THE CHECKER ROWS BIND TO THE TARGET THEY REALIZE, AND THIS REVERSES THIS FILE'S EARLIER REASONING (cursor review 48122, P0; the lane owner, whose rows these are, agreed and asked that their original intent be overruled). An earlier revision left them unidentified on the argument that a toolchain is not a language. That argument is TRUE AND IRRELEVANT, and mistaking one for the other is the actual defect: LanguageTargetSurfaceRow.target answers WHICH TARGET THIS SURFACE REALIZES, not whether the tool IS the language. This registry already treats run_rust_smoke and run_python_smoke exactly that way — nobody claimed those smoke functions were the Rust and Python languages — so leaving cargo and the interpreter unidentified was inconsistent within one file. Bound: cargo realizes Rust, the Python interpreter realizes Python. Cargo and CPython keep their own separate upstream identities and would take their own cited subject declarations if either is ever registered in its own right; binding a surface to a target does not merge the two. Leaving them unidentified would have permanently conflated tool identity with associated target and stopped the unidentified population reaching zero for a reason that was never real." - +// EVERY module_path IN THIS CENSUS WAS VERIFIED AGAINST THE REAL MODULE TREE, after review 48122 +// caught one that named nothing. The reviewer found the SPICE row pointing at +// extdeps.formats.spice, which is not a module — the surface is v2.extdeps.formats.spice, as this +// registry's own SPICE subject carrier already said. Identifying that row made a phantom worse: an +// unidentified phantom is merely a surface nobody can find, while an IDENTIFIED one asserts a join +// that can never be exercised, and it hid the real SPICE surface's absence from the census behind +// an apparently-accounted-for row. SWEEPING THE WHOLE CENSUS RATHER THAN THE ONE CITED ROW FOUND +// TWO MORE, both older and both the same root cause: extdeps.rust.cargo and +// extdeps.python.interpreter are FILE PATHS transcribed as module paths (dag/extdeps/rust/cargo.dag +// and dag/extdeps/python/python.dag), but the modules those files declare are extdeps.cargo and +// extdeps.python. That is DESIGN section 3's 'paths are discriminators, not gospel' violated in the +// direction that is hardest to see — a module path that LOOKS derived from a real file. They are +// corrected to the real modules and, per surface_locus_grain_note, moved to MODULE grain rather +// than being given invented decl_names: 'cargo' and 'python_interpreter' are not declarations +// either, and inventing one would be a nickname for a module. THE NINE REMAINING NON-EXACT PATHS +// ARE LEGITIMATE AND WERE CHECKED INDIVIDUALLY: the nine extdeps.languages family paths (rust, +// typescript, python, go, c, css, json, yaml, lean) each name a FAMILY of two to six real modules, +// and v2.workflow.bash is a string prefix covering v2.workflow.bash_command_fold_serialize and +// v2.workflow.bash_emit — the same prefix grain the containment lens uses. WHAT THIS DOES NOT +// ESTABLISH, and it is the honest limit: the sweep was done ONCE, by hand, at this revision. +// Nothing prevents the next phantom, because the census is hand-declared. The construction-side fix +// is the census being DERIVED from the module tree — already named as the residue-census obligation +// in language_target_registry_maximum_conclusion — at which point a path that names no module +// becomes unwritable rather than reviewable. + +// THE CHECKER ROWS BIND TO THE TARGET THEY REALIZE, AND THIS REVERSES THIS FILE'S EARLIER REASONING +// (cursor review 48122, P0; the lane owner, whose rows these are, agreed and asked that their +// original intent be overruled). An earlier revision left them unidentified on the argument that a +// toolchain is not a language. That argument is TRUE AND IRRELEVANT, and mistaking one for the +// other is the actual defect: LanguageTargetSurfaceRow.target answers WHICH TARGET THIS SURFACE +// REALIZES, not whether the tool IS the language. This registry already treats run_rust_smoke and +// run_python_smoke exactly that way — nobody claimed those smoke functions were the Rust and Python +// languages — so leaving cargo and the interpreter unidentified was inconsistent within one file. +// Bound: cargo realizes Rust, the Python interpreter realizes Python. Cargo and CPython keep their +// own separate upstream identities and would take their own cited subject declarations if either is +// ever registered in its own right; binding a surface to a target does not merge the two. Leaving +// them unidentified would have permanently conflated tool identity with associated target and +// stopped the unidentified population reaching zero for a reason that was never real. data runtime_checker_rows: List = [ identified_module_row(authority: RuntimeChecker, module_path: "extdeps.cargo", subject: rust_language_subject_ref), identified_module_row(authority: RuntimeChecker, module_path: "extdeps.python", subject: python_language_subject_ref), ] -data legacy_frontier_row_note: String = "The legacy frontier surface stays unidentified because it is a MULTI-TARGET prose table, not a surface belonging to one subject: gunbc.plans.language_target_self_host_frontier carries a row per target for a dozen targets at once. It cannot join a single subject, and the join kernel is right to report it. Its dissolution is the migration of those prose rows onto per-subject carriers — now possible for the first time, because until this change there were no subjects to migrate them ONTO. That migration is not done here; what this change removes is the reason it was blocked. The projections below carry legacy_frontier_label so each prose row can be matched to its canonical subject by name rather than by eye when that work happens." +// The legacy frontier surface stays unidentified because it is a MULTI-TARGET prose table, not a +// surface belonging to one subject: gunbc.plans.language_target_self_host_frontier carries a row +// per target for a dozen targets at once. It cannot join a single subject, and the join kernel is +// right to report it. Its dissolution is the migration of those prose rows onto per-subject +// carriers — now possible for the first time, because until this change there were no subjects to +// migrate them ONTO. That migration is not done here; what this change removes is the reason it was +// blocked. The projections below carry legacy_frontier_label so each prose row can be matched to +// its canonical subject by name rather than by eye when that work happens. data legacy_frontier_rows: List = [ unidentified_module_row(authority: LegacyFrontier, module_path: "gunbc.plans.language_target_self_host_frontier"), @@ -470,7 +698,18 @@ fn identified_module_row( } } -data bash_vocabulary_join_note: String = "The two Bash vocabulary rows below are identified, and their significance CHANGED when the other subjects landed (this note previously said Bash was 'the registry's only live join today' and that 'everything else is unidentified because Bash is the only subject that exists yet' — both true when written, both false the moment D0 registered 26 subjects, and left standing they made this module assert its own emptiness while listing a full registry; corrected after review 48113). What these rows demonstrate now is narrower and still worth stating: they are the join for a target whose surfaces are confined by MODULE PREFIX rather than by a target-model declaration, so they exercise the SurfaceModule locus grain against a real registered subject rather than a fixture. The interesting Bash fact today is not that it is the only join — it is the projection two rows down, where GNU Bash is a fully registered, cited subject with NO legacy language id at all, because std.languages carries rust, go, python and typescript rows and no bash row." +// The two Bash vocabulary rows below are identified, and their significance CHANGED when the other +// subjects landed (this note previously said Bash was 'the registry's only live join today' and +// that 'everything else is unidentified because Bash is the only subject that exists yet' — both +// true when written, both false the moment D0 registered 26 subjects, and left standing they made +// this module assert its own emptiness while listing a full registry; corrected after review +// 48113). What these rows demonstrate now is narrower and still worth stating: they are the join +// for a target whose surfaces are confined by MODULE PREFIX rather than by a target-model +// declaration, so they exercise the SurfaceModule locus grain against a real registered subject +// rather than a fixture. The interesting Bash fact today is not that it is the only join — it is +// the projection two rows down, where GNU Bash is a fully registered, cited subject with NO legacy +// language id at all, because std.languages carries rust, go, python and typescript rows and no +// bash row. data target_vocabulary_rows: List = [ identified_module_row(authority: TargetVocabulary, module_path: "extdeps.languages.typescript.program", subject: typescript_language_subject_ref), @@ -486,7 +725,28 @@ data language_target_surface_census: List = concat( concat(legacy_frontier_rows, target_vocabulary_rows) ) -data language_target_projection_note: String = "Requirement D. Twenty-five projections — one per external subject, PLUS the .dag language, which only became projectable when the neutral DeclarationRef join key landed: under the old ExternalSubjectRef key the .dag language and the controlled-English medium would have had to be wrapped in a carrier documented as a reference to an independently governed upstream subject, which is exactly what it is not. The controlled-English medium is deliberately left UNPROJECTED: a registered subject with no projection is a real state the registry must report as Absent rather than fabricate a name for, and it is the live row that keeps that control exercised instead of hypothetical. Each projection is a set of ATTRIBUTES each a set of ATTRIBUTES hanging off the canonical subject rather than a second identity — the direction rule in language_target_projection_law is what makes them safe: projecting a subject to its LanguageId is total, while looking a LanguageId up REFUSES when no registered subject claims it. THE ABSENCES ARE THE FINDINGS AND THEY ARE LOAD-BEARING. legacy_language_id is Present on exactly four rows — rust, go, python, typescript — because std.languages contains exactly those four Language rows; every other registered subject, INCLUDING BASH, has no legacy id at all, which is why a LanguageId could never have served as the identity key for this registry. legacy_frontier_label is absent for html, css, json, yaml, commonmark and jsx because the prose self-host frontier table never carried rows for them: it is a table about compilation targets, and the structured media were outside its scope. Where a label IS carried it is the frontier's exact spelling, including the two rows whose spelling is itself a finding — C and C++ appear there as 'cpp — Phase-0-C' and 'cpp — full C target', two rows of one prose table for what are two DIFFERENT languages under one fused label, and PTX and RISC-V both map to the single label 'machine_code / ptx'. Those fused labels are precisely what per-subject carriers replace." +// Requirement D. Twenty-five projections — one per external subject, PLUS the .dag language, which +// only became projectable when the neutral DeclarationRef join key landed: under the old +// ExternalSubjectRef key the .dag language and the controlled-English medium would have had to be +// wrapped in a carrier documented as a reference to an independently governed upstream subject, +// which is exactly what it is not. The controlled-English medium is deliberately left UNPROJECTED: +// a registered subject with no projection is a real state the registry must report as Absent rather +// than fabricate a name for, and it is the live row that keeps that control exercised instead of +// hypothetical. Each projection is a set of ATTRIBUTES each a set of ATTRIBUTES hanging off the +// canonical subject rather than a second identity — the direction rule in +// language_target_projection_law is what makes them safe: projecting a subject to its LanguageId is +// total, while looking a LanguageId up REFUSES when no registered subject claims it. THE ABSENCES +// ARE THE FINDINGS AND THEY ARE LOAD-BEARING. legacy_language_id is Present on exactly four rows — +// rust, go, python, typescript — because std.languages contains exactly those four Language rows; +// every other registered subject, INCLUDING BASH, has no legacy id at all, which is why a +// LanguageId could never have served as the identity key for this registry. legacy_frontier_label +// is absent for html, css, json, yaml, commonmark and jsx because the prose self-host frontier +// table never carried rows for them: it is a table about compilation targets, and the structured +// media were outside its scope. Where a label IS carried it is the frontier's exact spelling, +// including the two rows whose spelling is itself a finding — C and C++ appear there as 'cpp — +// Phase-0-C' and 'cpp — full C target', two rows of one prose table for what are two DIFFERENT +// languages under one fused label, and PTX and RISC-V both map to the single label 'machine_code / +// ptx'. Those fused labels are precisely what per-subject carriers replace. data language_target_projections: List = [ LanguageTargetProjection { @@ -714,7 +974,14 @@ fn registry_integrity_violation_count() -> Int { integrity_violation_count(vs: language_target_registry_violations()) } -data accounting_wall_note: String = "THE RATCHET: every census row is accounted for — joined to a registered subject, or reported by name in the missing-identity class — as an EXACT equality with no third state. A row naming an unregistered subject falls in neither class and breaks the sum; a violation detector that regressed to reporting nothing drops the remainder to zero while the census stays put and breaks it too. This is the ratchet direction the operator separated from completion: it makes the remainder VISIBLE and COUNTED, and is explicitly NOT evidence that any target is complete. It is also NOT the integrity wall — that is a separate predicate, because summing the two classes is exactly what made the previous single check undiscriminating." +// THE RATCHET: every census row is accounted for — joined to a registered subject, or reported by +// name in the missing-identity class — as an EXACT equality with no third state. A row naming an +// unregistered subject falls in neither class and breaks the sum; a violation detector that +// regressed to reporting nothing drops the remainder to zero while the census stays put and breaks +// it too. This is the ratchet direction the operator separated from completion: it makes the +// remainder VISIBLE and COUNTED, and is explicitly NOT evidence that any target is complete. It is +// also NOT the integrity wall — that is a separate predicate, because summing the two classes is +// exactly what made the previous single check undiscriminating. fn registry_accounting_total() -> Bool { declared_population_accounting_exact( @@ -723,7 +990,12 @@ fn registry_accounting_total() -> Bool { ) } -data integrity_wall_note: String = "THE ADMISSION WALL, separate from the ratchet above and required to be exactly zero: no census row may name a subject no registration carries, no surface may be joined to two disagreeing targets, no subject may be registered twice, no locus may appear twice in the census, and no subject may carry two projections. Unlike the accounting ratchet — whose nonzero remainder is the honest starting state — every one of these is malformed input rather than unfinished work, so the correct population for all five is zero now and forever." +// THE ADMISSION WALL, separate from the ratchet above and required to be exactly zero: no census +// row may name a subject no registration carries, no surface may be joined to two disagreeing +// targets, no subject may be registered twice, no locus may appear twice in the census, and no +// subject may carry two projections. Unlike the accounting ratchet — whose nonzero remainder is the +// honest starting state — every one of these is malformed input rather than unfinished work, so the +// correct population for all five is zero now and forever. fn registry_integrity_holds() -> Bool { registry_integrity_clean( diff --git a/dag/gunbc/legacy_test_behavior_disposition.dag b/dag/gunbc/legacy_test_behavior_disposition.dag index a5c0c0ff657..a0408028a86 100644 --- a/dag/gunbc/legacy_test_behavior_disposition.dag +++ b/dag/gunbc/legacy_test_behavior_disposition.dag @@ -40,9 +40,28 @@ type LegacyTestBehaviorDisposition dissolution: DissolutionCondition, } -data legacy_test_behavior_disposition_authority_note: String = "BEHAVIOR GRAIN, NOT MODULE GRAIN. The discovered subject is every line-anchored #[test] function under src/v1/tests/src, identified by its source file and function name. A disposition is admitted only for one such identity. CoveredByWitness additionally names a discovered `test fn` in the floor corpus and the failure that distinguishes equivalent coverage from a guard that merely stays quiet. DeleteLowValueBehavior and RetainedNonMigratableBehavior require their own reasons; retention also names its dissolution trigger and never authorizes deletion. Unclassified is deliberately not a disposition someone can author: it is the set difference discovered legacy behaviors minus disposition identities. Completion is that difference at zero together with no stale or duplicate dispositions and no unresolved witness reference. No numeric snapshot is an oracle." - -data legacy_test_behavior_discovery_scaffold_note: String = "DECLARED SEED SCAFFOLD for the missing list-directory effect. cli_run.rs test_migration_behavior_discovery_report walks the legacy Rust test directory and the existing corpus_dag_files floor population, extracting line-anchored #[test] and `test fn` declarations. Discovery errors make test_migration_behavior_discovery_holds false rather than fabricating an empty population. The identities cross the interpreter boundary as canonical source-file::function strings; this module derives the same serialization from typed fields. DEFERRAL OWNER: the self-host lane's v1-hand-queue-drain ROADMAP node ('Work through the remaining hand-written files the products need'); this product-reachable cli_run.rs bridge is one of that node's hand-written files and cannot silently fall outside its derived queue. DISSOLVES WHEN .dag can enumerate repository files and read the parsed Rust/DAG declaration trees directly; the graph and its dispositions stay, only the host observation bridge deletes." +// BEHAVIOR GRAIN, NOT MODULE GRAIN. The discovered subject is every line-anchored #[test] function +// under src/v1/tests/src, identified by its source file and function name. A disposition is +// admitted only for one such identity. CoveredByWitness additionally names a discovered `test fn` +// in the floor corpus and the failure that distinguishes equivalent coverage from a guard that +// merely stays quiet. DeleteLowValueBehavior and RetainedNonMigratableBehavior require their own +// reasons; retention also names its dissolution trigger and never authorizes deletion. Unclassified +// is deliberately not a disposition someone can author: it is the set difference discovered legacy +// behaviors minus disposition identities. Completion is that difference at zero together with no +// stale or duplicate dispositions and no unresolved witness reference. No numeric snapshot is an +// oracle. + +// DECLARED SEED SCAFFOLD for the missing list-directory effect. cli_run.rs +// test_migration_behavior_discovery_report walks the legacy Rust test directory and the existing +// corpus_dag_files floor population, extracting line-anchored #[test] and `test fn` declarations. +// Discovery errors make test_migration_behavior_discovery_holds false rather than fabricating an +// empty population. The identities cross the interpreter boundary as canonical +// source-file::function strings; this module derives the same serialization from typed fields. +// DEFERRAL OWNER: the self-host lane's v1-hand-queue-drain ROADMAP node ('Work through the +// remaining hand-written files the products need'); this product-reachable cli_run.rs bridge is one +// of that node's hand-written files and cannot silently fall outside its derived queue. DISSOLVES +// WHEN .dag can enumerate repository files and read the parsed Rust/DAG declaration trees directly; +// the graph and its dispositions stay, only the host observation bridge deletes. fn legacy_test_behavior_id(behavior: LegacyTestBehavior) -> String { concat(concat(behavior.source_file as String, "::"), behavior.test_fn as String) @@ -132,7 +151,17 @@ fn legacy_test_behavior_unresolved_witness_dispositions( )) } -data legacy_test_behavior_family_note: String = "A FAMILY IS A SCOPE OVER THE ONE GRAPH, NOT A SECOND CLASSIFICATION SCHEME. Migration proceeds family by family while the closing contract stays red on the rest of the corpus, so a family needs its own completion reading — but membership is derived by matching discovered behavior identities against the family's declared source files, never by listing the behaviors, so a #[test] added to a member module lands in the family's unclassified set and reds its completion the same way it reds the global frontier. Two states are refused rather than passed: a family whose declared source files discover no behavior at all is Incomplete, not vacuously complete (a renamed or misspelled path cannot read as finished work), and the family's own dispositions are checked for staleness against the WHOLE discovered population, so moving a behavior out of a member module refuses instead of quietly narrowing. Completion of a family is not completion of the contract and does not authorize deletion; it is the unit in which the frontier shrinks." +// A FAMILY IS A SCOPE OVER THE ONE GRAPH, NOT A SECOND CLASSIFICATION SCHEME. Migration proceeds +// family by family while the closing contract stays red on the rest of the corpus, so a family +// needs its own completion reading — but membership is derived by matching discovered behavior +// identities against the family's declared source files, never by listing the behaviors, so a +// #[test] added to a member module lands in the family's unclassified set and reds its completion +// the same way it reds the global frontier. Two states are refused rather than passed: a family +// whose declared source files discover no behavior at all is Incomplete, not vacuously complete (a +// renamed or misspelled path cannot read as finished work), and the family's own dispositions are +// checked for staleness against the WHOLE discovered population, so moving a behavior out of a +// member module refuses instead of quietly narrowing. Completion of a family is not completion of +// the contract and does not authorize deletion; it is the unit in which the frontier shrinks. type LegacyTestBehaviorFamily { name: NonEmptyStr diff --git a/dag/gunbc/legacy_test_inflow.dag b/dag/gunbc/legacy_test_inflow.dag index 492fb4925cc..90954595f09 100644 --- a/dag/gunbc/legacy_test_inflow.dag +++ b/dag/gunbc/legacy_test_inflow.dag @@ -8,17 +8,89 @@ import gunbc.legacy_test_behavior_disposition { legacy_test_behavior_id, } -data legacy_test_inflow_authority_note: String = "WHY THIS EXISTS: THE MECHANISM IS ABSENT, NOT THE RATIO. The v1-test-migration roadmap node declares in its own out_of_scope that adding a test written only in Rust, with no authority the substrate owns, 'counts against the finish line rather than toward it' because it grows the very population the node exists to drain. Nothing in the repository observes that inflow. The declared rule and the enforced rule differ, and the difference is invisible: the disposition graph measures the population and the frontier, both of which a steady arrival of new legacy tests moves without anyone deciding to move them. This carrier makes arrivals observable at the grain the rule is stated in. It is deliberately NOT justified by any measured arrival rate — a rate is one window's sample, and a mechanism argued from a sample invites deletion the first window the sample inverts. The rate is what this measures, never what establishes it." +// WHY THIS EXISTS: THE MECHANISM IS ABSENT, NOT THE RATIO. The v1-test-migration roadmap node +// declares in its own out_of_scope that adding a test written only in Rust, with no authority the +// substrate owns, 'counts against the finish line rather than toward it' because it grows the very +// population the node exists to drain. Nothing in the repository observes that inflow. The declared +// rule and the enforced rule differ, and the difference is invisible: the disposition graph +// measures the population and the frontier, both of which a steady arrival of new legacy tests +// moves without anyone deciding to move them. This carrier makes arrivals observable at the grain +// the rule is stated in. It is deliberately NOT justified by any measured arrival rate — a rate is +// one window's sample, and a mechanism argued from a sample invites deletion the first window the +// sample inverts. The rate is what this measures, never what establishes it. -data legacy_test_inflow_no_scheduled_consumer_note: String = "DECLARED SCAFFOLD: THIS AUDIT HAS NO SCHEDULED CONSUMER YET, and saying so is the point (operator review, gunbc#7692). v2.workflow.legacy_test_inflow_audit exposes a live observation, but nothing in this repository schedules it, enrolls it in a cadence, persists its receipt, or renders it — so after merge the audit runs only when a person runs it by hand. That is an audit MECHANISM plus a local recipe, not an operating audit, and the distinction is exactly the one this lane raised against CoveredByWitness rows citing witnesses no lane executes: a mechanism that never runs reads as discharged while observing nothing. The claim is narrowed to match rather than the gap being papered over. Local recipe: GUNBC_CI_DIFF_BASE= GUNBC_CI_DIFF_HEAD= claim_batch --wet --source-root src/v2 --source-root dag --entry src/v2/workflow/legacy_test_inflow_audit.dag --functions legacy_test_inflow_live_observation_holds. DISSOLVES WHEN a non-blocking consumer lands — a persisted receipt carrying the subject and the InflowMeasured-or-InflowRefused observation, exposed to a cadence or the dashboard — at which point this note deletes and the promotion question below becomes answerable from measured windows rather than hand runs." +// DECLARED SCAFFOLD: THIS AUDIT HAS NO SCHEDULED CONSUMER YET, and saying so is the point (operator +// review, gunbc#7692). v2.workflow.legacy_test_inflow_audit exposes a live observation, but nothing +// in this repository schedules it, enrolls it in a cadence, persists its receipt, or renders it — +// so after merge the audit runs only when a person runs it by hand. That is an audit MECHANISM plus +// a local recipe, not an operating audit, and the distinction is exactly the one this lane raised +// against CoveredByWitness rows citing witnesses no lane executes: a mechanism that never runs +// reads as discharged while observing nothing. The claim is narrowed to match rather than the gap +// being papered over. Local recipe: GUNBC_CI_DIFF_BASE= GUNBC_CI_DIFF_HEAD= claim_batch +// --wet --source-root src/v2 --source-root dag --entry src/v2/workflow/legacy_test_inflow_audit.dag +// --functions legacy_test_inflow_live_observation_holds. DISSOLVES WHEN a non-blocking consumer +// lands — a persisted receipt carrying the subject and the InflowMeasured-or-InflowRefused +// observation, exposed to a cadence or the dashboard — at which point this note deletes and the +// promotion question below becomes answerable from measured windows rather than hand runs. -data legacy_test_inflow_audit_mode_note: String = "AUDIT MODE, DELIBERATELY NOT A GATE (lane ruling 2026-08-02, following the acceptance-door split precedent: mechanism and audit first, refusal later). Every function here REPORTS; none refuses a change. The reason is throughput, not caution: a blocking rule here lands on every lane in the wave at once, and the decision to spend that throughput should be made against a measurement this carrier does not yet have. THE ONE THING AUDIT MODE DOES NOT RELAX IS FAIL-CLOSED OBSERVATION: a diff that cannot be observed, or an added attribute whose function line never arrives, yields InflowRefused carrying a typed reason — never InflowMeasured with an empty list. Not-observed and nothing-arrived are different states and this carrier keeps them apart, because collapsing them would make the counter silently report zero exactly when it is broken. PROMOTION TRIGGER: when a measured window supports a decision, InflowRefused and a nonempty InflowMeasured become a refusal at the change boundary; that promotion deletes this note, not the kernel." +// AUDIT MODE, DELIBERATELY NOT A GATE (lane ruling 2026-08-02, following the acceptance-door split +// precedent: mechanism and audit first, refusal later). Every function here REPORTS; none refuses a +// change. The reason is throughput, not caution: a blocking rule here lands on every lane in the +// wave at once, and the decision to spend that throughput should be made against a measurement this +// carrier does not yet have. THE ONE THING AUDIT MODE DOES NOT RELAX IS FAIL-CLOSED OBSERVATION: a +// diff that cannot be observed, or an added attribute whose function line never arrives, yields +// InflowRefused carrying a typed reason — never InflowMeasured with an empty list. Not-observed and +// nothing-arrived are different states and this carrier keeps them apart, because collapsing them +// would make the counter silently report zero exactly when it is broken. PROMOTION TRIGGER: when a +// measured window supports a decision, InflowRefused and a nonempty InflowMeasured become a refusal +// at the change boundary; that promotion deletes this note, not the kernel. -data legacy_test_inflow_scope_note: String = "SCOPE IS THE DISPOSITION GRAPH'S DENOMINATOR, EXACTLY. An arrival counts when it adds a line-anchored Rust `#[test]` function under src/v1/tests/src, because that is the population gunbc.legacy_test_behavior_disposition discovers and the frontier is measured against. Rust tests elsewhere in the seed — src/v1/stage0/src and its tests directory — are outside the counted population and are outside this counter too; that is a stated scope, not an oversight, and widening one without the other would make the counter and the frontier disagree about what they are counting. A change that adds a floor .dag `test fn` adds no arrival: it is the migration direction, and counting it would price the cure as the disease." +// SCOPE IS THE DISPOSITION GRAPH'S DENOMINATOR, EXACTLY. An arrival counts when it adds a +// line-anchored Rust `#[test]` function under src/v1/tests/src, because that is the population +// gunbc.legacy_test_behavior_disposition discovers and the frontier is measured against. Rust tests +// elsewhere in the seed — src/v1/stage0/src and its tests directory — are outside the counted +// population and are outside this counter too; that is a stated scope, not an oversight, and +// widening one without the other would make the counter and the frontier disagree about what they +// are counting. A change that adds a floor .dag `test fn` adds no arrival: it is the migration +// direction, and counting it would price the cure as the disease. -data legacy_test_inflow_extraction_fork_note: String = "A DECLARED SECOND READING OF ONE RULE, with the same dissolution trigger as the first. The identity of a legacy behavior — an added `#[test]` line, then the next line that is neither blank nor another attribute, then its function name after any pub/async — is authored twice: once over the live tree in the seed host bridge (cli_run.rs test_migration_named_fn_after_attribute, reached through the discovery report), and once here over unified diff text. The fork is not gratuitous: the host builtin walks the working tree and cannot be pointed at a diff, and .dag cannot yet enumerate a revision's files, so neither reading can call the other today. It carries the SAME dissolve-on as the discovery scaffold it mirrors — when .dag can read parsed declaration trees directly, both readings collapse onto that one authority and this fold deletes. WHAT IS NOT FORKED IS THE IDENTITY ITSELF (codex review 47551, and the correction is worth stating because the first revision of this carrier got it wrong): an arrival IS a gunbc.legacy_test_behavior_disposition LegacyTestBehavior, imported and constructed here, serialized by that module's legacy_test_behavior_id. An earlier revision minted a sibling LegacyTestInflowAddition record with the same two fields and a private id function that happened to produce the same string, and argued in this note that the two could therefore be joined — which is the nickname failure exactly: a second name for one concept, joinable only for as long as nobody edits either serializer. The reading of Rust source text is forked because neither reading can call the other; the thing the readings produce is not." +// A DECLARED SECOND READING OF ONE RULE, with the same dissolution trigger as the first. The +// identity of a legacy behavior — an added `#[test]` line, then the next line that is neither blank +// nor another attribute, then its function name after any pub/async — is authored twice: once over +// the live tree in the seed host bridge (cli_run.rs test_migration_named_fn_after_attribute, +// reached through the discovery report), and once here over unified diff text. The fork is not +// gratuitous: the host builtin walks the working tree and cannot be pointed at a diff, and .dag +// cannot yet enumerate a revision's files, so neither reading can call the other today. It carries +// the SAME dissolve-on as the discovery scaffold it mirrors — when .dag can read parsed declaration +// trees directly, both readings collapse onto that one authority and this fold deletes. WHAT IS NOT +// FORKED IS THE IDENTITY ITSELF (codex review 47551, and the correction is worth stating because +// the first revision of this carrier got it wrong): an arrival IS a +// gunbc.legacy_test_behavior_disposition LegacyTestBehavior, imported and constructed here, +// serialized by that module's legacy_test_behavior_id. An earlier revision minted a sibling +// LegacyTestInflowAddition record with the same two fields and a private id function that happened +// to produce the same string, and argued in this note that the two could therefore be joined — +// which is the nickname failure exactly: a second name for one concept, joinable only for as long +// as nobody edits either serializer. The reading of Rust source text is forked because neither +// reading can call the other; the thing the readings produce is not. -data legacy_test_inflow_arrival_versus_churn_note: String = "AN ARRIVAL IS NOT A POPULATION DELTA, AND THE DIFFERENCE IS RENAMES — measured, not reasoned. Over the first window this counter was run against (the merge of gunbc#7652 to the tree state at 9f978aa8df9), the discovered population gained 21 identities and lost 5, while this counter reported 18 arrivals. The gap is exactly three renamed test functions: renaming a test emits a removed and an added `fn` line with its `#[test]` line UNCHANGED, so an identity join over the two populations counts the rename as one departure plus one arrival, and this counter counts it as neither. The counter's reading is the one the roadmap rule is stated in — renaming a legacy test does not grow the population the migration must drain, so it is churn, not inflow. Anyone comparing the two numbers should expect them to differ by renames and should NOT reconcile them by adjusting either: they answer different questions, and only this one answers 'did a new legacy behavior arrive'.\\n\\nCONSEQUENCE FOR THE ZERO-CONTEXT TRANSPORT, stated because it is the shape most likely to be misread as a miss: the floor observes git diff with -U0, so a change that adds ONLY an attribute line above an already-existing function leaves that function's declaration outside the observed text entirely. That arrival is real and this carrier will not name it, so it REFUSES (InflowRefused, unresolved attribute) rather than reporting a count that silently omits it. Under a context-bearing diff the same fold resolves the declaration and reports it normally. Refusing a shape it cannot name is the fail-closed half of audit mode; quietly reporting one fewer arrival would be the absorbing fallback." +// AN ARRIVAL IS NOT A POPULATION DELTA, AND THE DIFFERENCE IS RENAMES — measured, not reasoned. +// Over the first window this counter was run against (the merge of gunbc#7652 to the tree state at +// 9f978aa8df9), the discovered population gained 21 identities and lost 5, while this counter +// reported 18 arrivals. The gap is exactly three renamed test functions: renaming a test emits a +// removed and an added `fn` line with its `#[test]` line UNCHANGED, so an identity join over the +// two populations counts the rename as one departure plus one arrival, and this counter counts it +// as neither. The counter's reading is the one the roadmap rule is stated in — renaming a legacy +// test does not grow the population the migration must drain, so it is churn, not inflow. Anyone +// comparing the two numbers should expect them to differ by renames and should NOT reconcile them +// by adjusting either: they answer different questions, and only this one answers 'did a new legacy +// behavior arrive'.\n\nCONSEQUENCE FOR THE ZERO-CONTEXT TRANSPORT, stated because it is the shape +// most likely to be misread as a miss: the floor observes git diff with -U0, so a change that adds +// ONLY an attribute line above an already-existing function leaves that function's declaration +// outside the observed text entirely. That arrival is real and this carrier will not name it, so it +// REFUSES (InflowRefused, unresolved attribute) rather than reporting a count that silently omits +// it. Under a context-bearing diff the same fold resolves the declaration and reports it normally. +// Refusing a shape it cannot name is the fail-closed half of audit mode; quietly reporting one +// fewer arrival would be the absorbing fallback. type LegacyTestInflowObservation = InflowMeasured { additions: List } @@ -140,7 +212,14 @@ fn inflow_diff_line_fold(acc: InflowFoldState, line: String) -> InflowFoldState } } -data legacy_test_inflow_context_line_note: String = "CONTEXT LINES ARE READ ONLY WHILE AN ADDED ATTRIBUTE IS PENDING, and that arm is load-bearing rather than defensive: writing `#[test]` above a function that already exists adds ONE line, so the function name arrives as CONTEXT and a fold that looked only at added lines would miss the arrival entirely. Outside a pending attribute, context is ignored — otherwise every unchanged test function in a touched module would be counted as arriving. Removed lines are skipped without clearing the pending attribute, so deleting a doc line between the attribute and its function does not lose the arrival; a removed `#[test]` is never an arrival, which is what makes this a counter of inflow rather than of churn." +// CONTEXT LINES ARE READ ONLY WHILE AN ADDED ATTRIBUTE IS PENDING, and that arm is load-bearing +// rather than defensive: writing `#[test]` above a function that already exists adds ONE line, so +// the function name arrives as CONTEXT and a fold that looked only at added lines would miss the +// arrival entirely. Outside a pending attribute, context is ignored — otherwise every unchanged +// test function in a touched module would be counted as arriving. Removed lines are skipped without +// clearing the pending attribute, so deleting a doc line between the attribute and its function +// does not lose the arrival; a removed `#[test]` is never an arrival, which is what makes this a +// counter of inflow rather than of churn. fn inflow_observation_of_diff(diff_text: String) -> LegacyTestInflowObservation { let final_state = fold( @@ -167,7 +246,15 @@ type LegacyTestInflowTally = InflowCountMeasured { count: Int, ids: List } | InflowCountUnavailable { reason: NonEmptyStr } -data legacy_test_inflow_tally_note: String = "THE PROJECTION CANNOT ERASE THE REFUSAL, because there is no arm on which to erase it (codex review, gunbc#7692). An earlier revision exposed inflow_observation_addition_ids and inflow_observation_count directly: both mapped InflowRefused to an empty list, and count of empty is zero, so an unobservable diff PROJECTED AS ZERO ARRIVALS unless every caller remembered to consult inflow_observation_is_measured first. The typed outcome existed precisely to remove that footgun and the projection put it back, one call-order mistake from the silent widening this carrier's own note forbids. A count and an id list now exist ONLY on InflowCountMeasured; a refusal projects to InflowCountUnavailable carrying its reason, which has no count to read. Fail-closed semantics is a property of the shape rather than of caller discipline." +// THE PROJECTION CANNOT ERASE THE REFUSAL, because there is no arm on which to erase it (codex +// review, gunbc#7692). An earlier revision exposed inflow_observation_addition_ids and +// inflow_observation_count directly: both mapped InflowRefused to an empty list, and count of empty +// is zero, so an unobservable diff PROJECTED AS ZERO ARRIVALS unless every caller remembered to +// consult inflow_observation_is_measured first. The typed outcome existed precisely to remove that +// footgun and the projection put it back, one call-order mistake from the silent widening this +// carrier's own note forbids. A count and an id list now exist ONLY on InflowCountMeasured; a +// refusal projects to InflowCountUnavailable carrying its reason, which has no count to read. +// Fail-closed semantics is a property of the shape rather than of caller discipline. fn inflow_observation_tally(observation: LegacyTestInflowObservation) -> LegacyTestInflowTally { match observation { diff --git a/dag/gunbc/live_deploy/apply.dag b/dag/gunbc/live_deploy/apply.dag index b43d8cdc2bb..c2f7c49f692 100644 --- a/dag/gunbc/live_deploy/apply.dag +++ b/dag/gunbc/live_deploy/apply.dag @@ -73,11 +73,8 @@ import std.realization_reconcile { Reconciliation, reconciliation_converged, Con import extdeps.filesystem.filesystem_io { Filesystem } import std.process { ProcessExit, ExitSuccess, exit_failure } - - data single_deployment_note: String = "ONE DEPLOYMENT, ONE PORT. Blue/green was deleted at the root on 2026-08-19 by operator directive, and the reason is the one its own predecessor note recorded against itself: blue/green is sound for a STATELESS server, and this dashboard is stateful. The compromise it reached -- the slot owns source, binary, unit and port while dispatch worktrees, attempt evidence and the tmux server stay host-shared -- is a two-copy deployment that is only half two copies, and it cost more to reason about than the zero-downtime swap was worth for a single-operator dashboard.\n\nWHAT WENT, AS ONE POPULATION: the slot selection coproduct and its Tailscale-status read, the deploy-idle-then-flip-then-retire fold, the slot/route/retiring spec projections, and the second HostDashboardInstance. What replaces them is this: apply converges deployment_spec_srv1 IN PLACE, members in declared order, and the Tailscale mapping is an ordinary member pointing at the one port rather than a route flipped between two.\n\nWHAT IS HONESTLY LOST is the property blue/green existed to buy: a failed candidate can no longer be discovered on an idle slot while the old process keeps serving. A bad deploy now restarts the one service, and the readiness gate refuses AFTER the restart rather than before a flip -- so the failure mode is downtime, not a silent bad cutover. That trade is the point of the directive, not an oversight, and the mitigation is that every member is idempotent and a retract remains available.\n\nTHE HAZARD THIS ALSO CLOSES: two slots meant two belt timers, both enabled, sharing one dispatch-worktree root and one attempt-state root. Nothing prevented a collision except that both belts happened to be crashing; with one deployment there is one belt and the prepare/activate split it would have needed is no longer owed.\n\nA SECOND ORDERING CONSEQUENCE, named because the loss above is not the only one. Under the deleted cutover fold the route write ran AFTER the candidate slot converged, readiness gate included. The mapping is now an ordinary member, last in deployment_owned_steps and inside the mutation, so the route is asserted BEFORE live_deploy_apply_readiness_gate runs -- which is, in shape, the pre-blue/green ordering that blue_green_spec_partition_note recorded as having recreated a 502 window. It is not that defect here, because with one port the write is an idempotent re-assert of the same endpoint-to-8080 mapping rather than a redirection to an unverified process, so it opens no window beyond the restart downtime already accepted above. The residual is bounded by the realization: if the Tailscale mapping is ever applied as remove-then-add rather than as a replace, the route is briefly ABSENT while the service is unverified. That is a property of extdeps.tailscale.serve, not of this fold, and it is stated here so the ordering is a known quantity rather than something a later reader rediscovers as a surprise." - fn live_deploy_preflight_intent(access: DeployAccess) -> HostEffectIntent { HostEffectIntent { target: HostOs { node: access.host }, @@ -343,7 +340,14 @@ fn live_deploy_apply_srv1_with_access( ) } -data retract_carries_no_candidate_note: String = "A RETRACT OBSERVES NO CANDIDATE, WHICH IS WHY IT CANNOT FAIL FOR WANT OF ONE. It removes what this deployment owns; there is no release being installed, so there is nothing whose identity a retract could state and nothing for readiness to compare. While the revision rode on DeploymentServiceConfig this path had to construct one anyway, and worse, it called observe_candidate_release first — so a retract could refuse because git HEAD was unreadable, a condition with no bearing whatsoever on tearing down a unit.\\n\\nThe fold now passes RevisionNotInstalled, so the arm that would render an ExecStart refuses loudly instead of naming a revision this operation never had." +// A RETRACT OBSERVES NO CANDIDATE, WHICH IS WHY IT CANNOT FAIL FOR WANT OF ONE. It removes what +// this deployment owns; there is no release being installed, so there is nothing whose identity a +// retract could state and nothing for readiness to compare. While the revision rode on +// DeploymentServiceConfig this path had to construct one anyway, and worse, it called +// observe_candidate_release first — so a retract could refuse because git HEAD was unreadable, a +// condition with no bearing whatsoever on tearing down a unit.\n\nThe fold now passes +// RevisionNotInstalled, so the arm that would render an ExecStart refuses loudly instead of naming +// a revision this operation never had. fn live_deploy_retract_srv1_with_access( access: DeployAccess, diff --git a/dag/gunbc/live_deploy/candidate.dag b/dag/gunbc/live_deploy/candidate.dag index 75cf645970c..7187e884711 100644 --- a/dag/gunbc/live_deploy/candidate.dag +++ b/dag/gunbc/live_deploy/candidate.dag @@ -13,9 +13,43 @@ import gunbc.running_release_identity { running_release_surface_equals, } -data candidate_release_note: String = "THE PER-INVOCATION RELEASE, WHICH THE SLOT SPEC IS NOT. DeploymentSpec answers WHERE a deployment is placed — which instance root, which port, which unit name, which host — and every one of those facts is stable across deploys of blue or of green. CandidateRelease answers WHAT is being installed on this invocation, and every one of ITS facts changes on every deploy. Fusing them would put a mutable revision inside the static slot topology, which is why deployment_spec_srv1 takes the revision as a parameter rather than owning it: the slot identity stays a constant of the fleet model and the release travels beside it.\\n\\nTWO MEMBERS BECAUSE THE PROCESS REPORTS TWO. RunningReleaseObservation carries revision and surface_bundle_identity as separate wire members precisely because they fail independently, and a candidate that carried only one of them could not be compared against the half it omitted. So the candidate is the EXPECTATION and the observation is the READING, member for member, and readiness is the equality between them." +// THE PER-INVOCATION RELEASE, WHICH THE SLOT SPEC IS NOT. DeploymentSpec answers WHERE a deployment +// is placed — which instance root, which port, which unit name, which host — and every one of those +// facts is stable across deploys of blue or of green. CandidateRelease answers WHAT is being +// installed on this invocation, and every one of ITS facts changes on every deploy. Fusing them +// would put a mutable revision inside the static slot topology, which is why deployment_spec_srv1 +// takes the revision as a parameter rather than owning it: the slot identity stays a constant of +// the fleet model and the release travels beside it.\n\nTWO MEMBERS BECAUSE THE PROCESS REPORTS +// TWO. RunningReleaseObservation carries revision and surface_bundle_identity as separate wire +// members precisely because they fail independently, and a candidate that carried only one of them +// could not be compared against the half it omitted. So the candidate is the EXPECTATION and the +// observation is the READING, member for member, and readiness is the equality between them. -data candidate_observed_once_note: String = "OBSERVED ONCE, AT THE PUBLIC LAUNCHER, BEFORE ANY HOST MUTATION — and the placement is the whole property. A revision rediscovered deeper in the chain (in the emitter, in readiness, in a retry arm) is a SECOND observation of a tree that the deploy itself may have changed underneath, so the unit could be emitted with one revision and verified against another, and every comparison downstream would still look internally consistent. Observing once means the launcher's reading is the only revision in the invocation and every later use is a reference to that one value, which is what makes 'the checkout's sha, the emitted launch sha, and the process-reported sha are equal' a claim with a single subject rather than three readings that happen to agree.\\n\\nIT IS THE DEPLOYING CHECKOUT, NOT A SLOT'S DISK HEAD. The distinction is the one the operator ruling turns on. This observation runs in the CI workspace that is performing the deploy, and answers 'which candidate am I installing' — a fact the deploying job is the only party that knows. Reading a git HEAD inside a slot tree ON THE HOST would answer a different question ('what does that directory contain'), and using it as the ACTIVE predecessor is exactly the disk-derived ordering the ruling struck down, because the disk diverges from the running process whenever a sync is interrupted or lands out of order. Observing the candidate here is safe for the same reason that one is not: nothing has been mutated yet, and the checkout is the deploying job's own workspace.\\n\\nTHE SURFACE HALF NEEDS NO OBSERVATION. expected_surface_identity is a pure function of this tree, so it is computed rather than read.\\n\\nTHE SURFACE HALF IS CONSUMED, NOT MERELY PUBLISHED. apply.dag carries this whole value — both members — into the readiness poll, and readiness compares the process-reported surface_bundle_identity against expected_surface_identity rather than re-materializing the bundle at comparison time. That distinction is the point: a recomputed expectation would describe the tree AS IT IS WHEN COMPARED, so if the deploying checkout changed after admission the expectation and the verdict would drift together and agree with a process other than the one admitted. An earlier revision of this note said the member was captured and dropped; that was true when written and is false now." +// OBSERVED ONCE, AT THE PUBLIC LAUNCHER, BEFORE ANY HOST MUTATION — and the placement is the whole +// property. A revision rediscovered deeper in the chain (in the emitter, in readiness, in a retry +// arm) is a SECOND observation of a tree that the deploy itself may have changed underneath, so the +// unit could be emitted with one revision and verified against another, and every comparison +// downstream would still look internally consistent. Observing once means the launcher's reading is +// the only revision in the invocation and every later use is a reference to that one value, which +// is what makes 'the checkout's sha, the emitted launch sha, and the process-reported sha are +// equal' a claim with a single subject rather than three readings that happen to agree.\n\nIT IS +// THE DEPLOYING CHECKOUT, NOT A SLOT'S DISK HEAD. The distinction is the one the operator ruling +// turns on. This observation runs in the CI workspace that is performing the deploy, and answers +// 'which candidate am I installing' — a fact the deploying job is the only party that knows. +// Reading a git HEAD inside a slot tree ON THE HOST would answer a different question ('what does +// that directory contain'), and using it as the ACTIVE predecessor is exactly the disk-derived +// ordering the ruling struck down, because the disk diverges from the running process whenever a +// sync is interrupted or lands out of order. Observing the candidate here is safe for the same +// reason that one is not: nothing has been mutated yet, and the checkout is the deploying job's own +// workspace.\n\nTHE SURFACE HALF NEEDS NO OBSERVATION. expected_surface_identity is a pure function +// of this tree, so it is computed rather than read.\n\nTHE SURFACE HALF IS CONSUMED, NOT MERELY +// PUBLISHED. apply.dag carries this whole value — both members — into the readiness poll, and +// readiness compares the process-reported surface_bundle_identity against expected_surface_identity +// rather than re-materializing the bundle at comparison time. That distinction is the point: a +// recomputed expectation would describe the tree AS IT IS WHEN COMPARED, so if the deploying +// checkout changed after admission the expectation and the verdict would drift together and agree +// with a process other than the one admitted. An earlier revision of this note said the member was +// captured and dropped; that was true when written and is false now. type CandidateRelease { revision: CommitSha @@ -85,7 +119,17 @@ type CandidateObservation = CandidateObserved { candidate: CandidateRelease } | CandidateUnobservable { cause: CandidateUnobservableCause } -data candidate_unobservable_causes_note: String = "THREE CAUSES, ALL REFUSALS, NONE OF THEM A DEFAULT. A deploy that cannot name the candidate it is installing has nothing to emit into ExecStart and nothing to verify the routed process against, so there is no arm here that proceeds with a placeholder, an abbreviated sha, or the string that happened to arrive. Refused means the probe itself did not answer (not a repository, git unavailable); Empty means it answered with nothing, which a bare success flag would have read as success; Malformed means it answered with something that is not a 40-digit lowercase object name — a tag, a branch, an abbreviated sha — which is the case a length-free check accepts and an ancestry probe then silently mis-answers.\\n\\nEmpty is kept distinct from Malformed rather than folded into it because their remedies differ: an empty stdout means the observation seam is broken, while a non-sha text means the seam works and the repository is in a state (detached, unborn, or a ref that does not resolve) the deploy must not paper over." +// THREE CAUSES, ALL REFUSALS, NONE OF THEM A DEFAULT. A deploy that cannot name the candidate it is +// installing has nothing to emit into ExecStart and nothing to verify the routed process against, +// so there is no arm here that proceeds with a placeholder, an abbreviated sha, or the string that +// happened to arrive. Refused means the probe itself did not answer (not a repository, git +// unavailable); Empty means it answered with nothing, which a bare success flag would have read as +// success; Malformed means it answered with something that is not a 40-digit lowercase object name +// — a tag, a branch, an abbreviated sha — which is the case a length-free check accepts and an +// ancestry probe then silently mis-answers.\n\nEmpty is kept distinct from Malformed rather than +// folded into it because their remedies differ: an empty stdout means the observation seam is +// broken, while a non-sha text means the seam works and the repository is in a state (detached, +// unborn, or a ref that does not resolve) the deploy must not paper over. // The scan carries `expect_rename_source` because porcelain v1 with -z emits a rename or copy as TWO // NUL-separated fields — the record, then the source path — and the source path is a bare filename. There is @@ -257,7 +301,13 @@ fn observe_candidate_release() -> CandidateObservation { // to trust. Reading the status first means a dirty tree never reaches the renderer at all — nothing derived // from untrusted bytes is built, so there is no intermediate value a later arm could mistakenly use. -data observe_candidate_release_is_a_function_note: String = "A FUNCTION AND NOT A TOP-LEVEL data, FOR THE REASON gunbc.stage0_rust_host_observation ALREADY PAID FOR. A top-level data declaration is evaluated by IMPORTING it, so a module that merely named this symbol in an import list would perform Git I/O as a consequence of loading — and that is not hypothetical: a dead import of exactly that shape reached git.Inspect.HeadCommit under the hermetic envelope and took down a 105-row discovery batch. As a function the effect is reachable only by CALLING it, so every witness in this lane may import CandidateRelease, the cause coproduct, and the comparison predicates without observing any host." +// A FUNCTION AND NOT A TOP-LEVEL data, FOR THE REASON gunbc.stage0_rust_host_observation ALREADY +// PAID FOR. A top-level data declaration is evaluated by IMPORTING it, so a module that merely +// named this symbol in an import list would perform Git I/O as a consequence of loading — and that +// is not hypothetical: a dead import of exactly that shape reached git.Inspect.HeadCommit under the +// hermetic envelope and took down a 105-row discovery batch. As a function the effect is reachable +// only by CALLING it, so every witness in this lane may import CandidateRelease, the cause +// coproduct, and the comparison predicates without observing any host. fn candidate_unobservable_detail(cause: CandidateUnobservableCause) -> String { match cause { @@ -290,6 +340,14 @@ fn render_candidate_paths(paths: List) -> String { if length(paths) == 0 { "(none)" } else { join(paths, ", ") } } +// THE CONJUNCTION EXISTS AS A CONVENIENCE FOR CALLERS THAT NEED BOTH, AND IT IS DELIBERATELY NOT +// THE ONLY SURFACE. running_release_revision_equals and running_release_surface_equals stay +// separately callable because a deploy must be able to say WHICH half failed — right revision with +// a stale surface means the graph did not rebind, right surface at an unexpected revision means +// another candidate won the route, and the two have different remedies. Consumers that must locate +// the failure call the halves and construct a typed cause; only a consumer that genuinely needs the +// single 'is this the release I admitted' bit calls this. + fn candidate_release_matches_identity( candidate: CandidateRelease, identity: RunningReleaseIdentity, @@ -297,5 +355,3 @@ fn candidate_release_matches_identity( running_release_revision_equals(identity: identity, expected: candidate.revision) && running_release_surface_equals(identity: identity, expected: candidate.expected_surface_identity) } - -data candidate_release_matches_identity_note: String = "THE CONJUNCTION EXISTS AS A CONVENIENCE FOR CALLERS THAT NEED BOTH, AND IT IS DELIBERATELY NOT THE ONLY SURFACE. running_release_revision_equals and running_release_surface_equals stay separately callable because a deploy must be able to say WHICH half failed — right revision with a stale surface means the graph did not rebind, right surface at an unexpected revision means another candidate won the route, and the two have different remedies. Consumers that must locate the failure call the halves and construct a typed cause; only a consumer that genuinely needs the single 'is this the release I admitted' bit calls this." diff --git a/dag/gunbc/live_deploy/deployed_tree_scope.dag b/dag/gunbc/live_deploy/deployed_tree_scope.dag index f3223c085cd..abbfa560c1b 100644 --- a/dag/gunbc/live_deploy/deployed_tree_scope.dag +++ b/dag/gunbc/live_deploy/deployed_tree_scope.dag @@ -2,42 +2,43 @@ module gunbc.live_deploy.deployed_tree_scope import std.types { Bool, String, List } -// WHAT THE DEPLOY ACTUALLY INSTALLS, DECLARED ONCE BECAUSE TWO THINGS NEED IT AND THEY MUST NOT DISAGREE. -// The working-tree leg of the source sync rsyncs the runner checkout wholesale except these prefixes, so -// the deployed source is the WORKING TREE, not the commit. Two consumers read that fact: +// WHAT THE DEPLOY ACTUALLY INSTALLS, DECLARED ONCE BECAUSE TWO THINGS NEED IT AND MUST NOT DISAGREE. +// The working-tree leg of the source sync rsyncs the runner checkout wholesale except these +// prefixes, so the deployed source is the WORKING TREE, not the commit. Two consumers read it: // // gunbc.live_deploy.emit renders `--exclude /

` per prefix into the tree-sync ExecStart // gunbc.live_deploy.candidate decides whether an untracked path would actually be shipped // -// Spelling it in both would let them drift, and the drift is the dangerous direction: admission would pass -// on a path the sync then installs. Same fact, one home (DESIGN §3). +// Spelling it in both would let them drift, in the dangerous direction: admission would pass on a +// path the sync then installs. Same fact, one home (DESIGN §3). // -// IT LIVES IN ITS OWN LEAF MODULE FOR A STRUCTURAL REASON, not for tidiness. Putting it beside the emitter -// and importing that from candidate closes a real cycle — candidate is reached from gunbc.hostname_read, -// which gunbc.host_effect reaches, which the emitter's own closure reaches — and acyclicity is the import -// graph's one structural law (DESIGN §3). A fact needed by two modules on opposite sides of a dependency -// chain belongs beneath both, not inside either. +// IT LIVES IN ITS OWN LEAF MODULE FOR A STRUCTURAL REASON. Putting it beside the emitter and +// importing that from candidate closes a real cycle — candidate is reached from +// gunbc.hostname_read, which gunbc.host_effect reaches, which the emitter's own closure reaches — +// and acyclicity is the import graph's one structural law (DESIGN §3). A fact needed by two modules +// on opposite sides of a dependency chain belongs beneath both, not inside either. // WHY THIS LIST IS NOT JUST target AND .git, AND WHY IT IS STILL NOT EVERY IGNORED PATH. -// The working-tree leg is rsync, which does not read .gitignore, so an ignored path outside these prefixes -// is copied to the host under a revision that does not name it. Candidate admission refuses on exactly that -// state (gunbc.live_deploy.candidate, the ignored_deployed arm), and the refusal names two remedies: widen -// these exclusions, or remove the file. The four appended here take the first remedy because they are host -// state by construction -- agent session state and local tool/build caches that the deployed tree must never -// contain under any revision, so shipping one is never the intent and refusing on one is pure friction. +// The working-tree leg is rsync, which does not read .gitignore, so an ignored path outside these +// prefixes is copied to the host under a revision that does not name it. Candidate admission +// refuses on exactly that state (gunbc.live_deploy.candidate, the ignored_deployed arm), naming two +// remedies: widen these exclusions, or remove the file. The four appended here take the first +// because they are host state by construction -- agent session state and local tool/build caches +// the deployed tree must never contain under any revision, so shipping one is never the intent and +// refusing on one is pure friction. // -// THE .env FAMILY IS DELIBERATELY ABSENT AND STAYS REFUSED. The repository ignores `.env`, `.env.local` and -// `.env.*.local`; the third is a wildcard, and both this predicate and rsync's `--exclude /

` match a -// literal segment. Listing the two spellable forms would silence admission on them while `.env.*.local` kept -// shipping -- partial cover on the one class where the shipped bytes are secrets, which is strictly worse -// than the present loud refusal on all three. It stays refused until the exclusion carrier can express a -// pattern, and the same reasoning holds for src/v2/test/claim/manual/sg2_scratch_probe.dag for a different -// reason: it sits inside a source root the graph actually reads, so excluding it would change what the host -// compiles rather than merely what it stores. +// THE .env FAMILY IS DELIBERATELY ABSENT AND STAYS REFUSED. The repository ignores `.env`, +// `.env.local` and `.env.*.local`; the third is a wildcard, and both this predicate and rsync's +// `--exclude /

` match a literal segment. Listing the two spellable forms would silence admission +// on them while `.env.*.local` kept shipping -- partial cover on the one class where the shipped +// bytes are secrets, strictly worse than the present loud refusal on all three. It stays refused +// until the exclusion carrier can express a pattern. src/v2/test/claim/manual/sg2_scratch_probe.dag +// stays refused for a different reason: it sits inside a source root the graph actually reads, so +// excluding it would change what the host compiles rather than merely what it stores. // -// NOT DERIVED FROM gunbc.gitignore_authority, ON PURPOSE. That authority answers "must not be tracked"; this -// one answers "must not be shipped", and the sets genuinely differ -- a tracked file ships and is not -// ignored. Deriving would also let an ordinary .gitignore edit silently change what a deploy installs, and -// would put an import edge into a module kept leaf for the acyclicity reason stated above. +// NOT DERIVED FROM gunbc.gitignore_authority, ON PURPOSE. That authority answers "must not be +// tracked"; this one answers "must not be shipped", and the sets differ -- a tracked file ships and +// is not ignored. Deriving would also let an ordinary .gitignore edit silently change what a deploy +// installs, and would put an import edge into a module kept leaf for the acyclicity reason above. data deployed_tree_excluded_prefixes: List = [ "target", ".git", ".claude", ".gunbc", "target-codex", ".cargo-home", ] @@ -55,21 +56,22 @@ fn path_is_deployed(path: String) -> Bool { // WHICH REPOSITORY TRANSITION THIS DEPLOYMENT IS BOUND TO, AND WHY IT IS A WALL RATHER THAN A NOTE. // // The `.git` leg of the source sync copies `HEAD`, `objects`, `packed-refs` and `refs` as FILES and -// excludes `index` entirely, so the target receives working files from one revision and git metadata -// from another. Measured on srv1 2026-08-27, `/opt/gunbc/gunbc` is in three-way divergence -- -// `git status --porcelain` reports 5650 paths, of which 1964 are tracked-and-staged-present but -// absent from the worktree and 1731 are present but unknown to HEAD -- with the first status column -// (index vs HEAD) and the second (worktree vs index) BOTH wrong. That is one deployment's output, -// not two racing: no concurrency control can make a single execution of this operation valid. +// excludes `index` entirely, so the target receives working files from one revision and git +// metadata from another. Measured on srv1 2026-08-27, `/opt/gunbc/gunbc` is in three-way +// divergence -- `git status --porcelain` reports 5650 paths, of which 1964 are +// tracked-and-staged-present but absent from the worktree and 1731 are present but unknown to +// HEAD -- with the first status column (index vs HEAD) and the second (worktree vs index) BOTH +// wrong. That is one deployment's output, not two racing: no concurrency control can make a +// single execution of this operation valid. // -// THIS EXISTS BECAUSE A PR COMMENT IS NOT A WALL. The change that restored the dashboard deploy mode -// recorded, in prose, that the mode must not be dispatched until this leg is replaced. DESIGN -// section 5 is explicit that construction beats validation and that a state you can still write is a -// state that will be written; an operator memory and a PR body are neither. So the unsafe dispatch -// is refused HERE, before the launcher observes anything, and the prose is demoted to explanation. +// THIS EXISTS BECAUSE A PR COMMENT IS NOT A WALL. The change that restored the dashboard deploy +// mode recorded, in prose, that the mode must not be dispatched until this leg is replaced. DESIGN +// section 5: construction beats validation and a state you can still write will be written; an +// operator memory and a PR body are neither. So the unsafe dispatch is refused HERE, before the +// launcher observes anything, and the prose is demoted to explanation. // -// ITS RUNG, HONESTLY: mechanically preventable, not structural. The wall executes and refuses on the -// real path, but the bound realization is a HAND-DECLARED row -- someone could edit it without +// ITS RUNG, HONESTLY: mechanically preventable, not structural. The wall executes and refuses on +// the real path, but the bound realization is a HAND-DECLARED row -- someone could edit it without // having done the work, and nothing would notice. NEXT-RUNG TRIGGER: derive the bound realization // from the emitted tree-sync unit itself, so the declaration cannot disagree with what is installed. // diff --git a/dag/gunbc/live_deploy/emit.dag b/dag/gunbc/live_deploy/emit.dag index ca9ec822727..bcc03f8bff9 100644 --- a/dag/gunbc/live_deploy/emit.dag +++ b/dag/gunbc/live_deploy/emit.dag @@ -174,7 +174,12 @@ fn systemctl_disable_now_argv_for_deploy(unit: NonEmptyStr, privileged: Bool) -> } } -data systemctl_disable_now_if_present_note: String = "Runtime retirement is idempotent only for the explicit systemd LoadState=not-found observation. The decision is a bash grammar tree at this foreign-executor realization edge: a fail-fast assignment captures LoadState, then a modeled If disables every state other than not-found. A show/read failure therefore stops before the conditional, and a disable/stop failure remains loud. This replaces the former `2>/dev/null || true` without smuggling command substitution or control flow through a String leaf." +// Runtime retirement is idempotent only for the explicit systemd LoadState=not-found observation. +// The decision is a bash grammar tree at this foreign-executor realization edge: a fail-fast +// assignment captures LoadState, then a modeled If disables every state other than not-found. A +// show/read failure therefore stops before the conditional, and a disable/stop failure remains +// loud. This replaces the former `2>/dev/null || true` without smuggling command substitution or +// control flow through a String leaf. fn systemctl_load_state_assignment_intent(unit: NonEmptyStr) -> Node { bash_build_assign( @@ -288,7 +293,27 @@ fn emit_pipeline_or_poison(p: Pipeline, refused: String) -> String { data emit_systemd_unit_serve_note: String = "The belt-B unit: ExecStart is `gunbc serve` (the seed binary answering every request through the ONE .dag entry gunbc.roadmap_serve.roadmap_serve_handle — no emitted JS, no subprocess dispatch). User=briansrls, NOT root: claude auth exists only under briansrls on srv1 (parent lane verification 2026-07-20), so a root-run belt would spawn unauthed dead sessions; port 8080 is non-privileged so briansrls binds it. WorkingDirectory = the repo root — the source roots and entry are relative to it, and the belt's spawn workdir projects the same gunbc.host_layout fact." -data release_revision_binding_note: String = "WHERE THE LAUNCH REVISION COMES FROM IS ITSELF A MODELED FACT, AND THERE ARE EXACTLY TWO ARMS BECAUSE ONLY TWO ARE HONEST. RevisionBoundAtEmission is the production apply: the launcher observed one candidate, the unit installed at the host bakes exactly that value, and readiness compares against the same single subject. RevisionNotInstalled is the retract fold, and it exists so a retract cannot name a revision even accidentally — the arm that would render ExecStart under it REFUSES with a poison marker, the same unreachable-arm discipline emit_deploy_teardown_dependency_refusal uses.\\n\\nA THIRD ARM WAS TRIED AND DELETED, AND THE REASON IS WORTH KEEPING. RevisionBoundAtInvocation rendered a bash parameter expansion into the unit so a committed script could defer its revision to invocation. It could never have worked: the unit body reaches the host as a single-quoted printf argument (it was a quoted-delimiter heredoc when this was found, and the property that mattered survived the change), so bash performs no expansion inside it and the literal dollar-brace text lands in the unit file; systemd does not run ExecStart through bash, implements no :? operator, and this unit sets no such Environment. The chain script-invocation to unit to launch argument did not exist at any link. Proven by execution, not by reading — which is the point: it typechecked, it read plausibly, and it was inert.\\n\\nWhat it was protecting turned out not to need protecting. It existed to keep a committed script as the byte oracle over this ExecStart line, on the belief that deleting the artifact would lose that coverage. A fixture sha is forbidden in a committed PRODUCTION artifact, not in a witness — so the oracle lives in the test corpus against RevisionBoundAtEmission with a fixture, and the artifact was simply dead (no execution consumer, and a HostReconciler consumer row that named a reader which does not exist)." +// WHERE THE LAUNCH REVISION COMES FROM IS ITSELF A MODELED FACT, AND THERE ARE EXACTLY TWO ARMS +// BECAUSE ONLY TWO ARE HONEST. RevisionBoundAtEmission is the production apply: the launcher +// observed one candidate, the unit installed at the host bakes exactly that value, and readiness +// compares against the same single subject. RevisionNotInstalled is the retract fold, and it exists +// so a retract cannot name a revision even accidentally — the arm that would render ExecStart under +// it REFUSES with a poison marker, the same unreachable-arm discipline +// emit_deploy_teardown_dependency_refusal uses.\n\nA THIRD ARM WAS TRIED AND DELETED, AND THE +// REASON IS WORTH KEEPING. RevisionBoundAtInvocation rendered a bash parameter expansion into the +// unit so a committed script could defer its revision to invocation. It could never have worked: +// the unit body reaches the host as a single-quoted printf argument (it was a quoted-delimiter +// heredoc when this was found, and the property that mattered survived the change), so bash +// performs no expansion inside it and the literal dollar-brace text lands in the unit file; systemd +// does not run ExecStart through bash, implements no :? operator, and this unit sets no such +// Environment. The chain script-invocation to unit to launch argument did not exist at any link. +// Proven by execution, not by reading — which is the point: it typechecked, it read plausibly, and +// it was inert.\n\nWhat it was protecting turned out not to need protecting. It existed to keep a +// committed script as the byte oracle over this ExecStart line, on the belief that deleting the +// artifact would lose that coverage. A fixture sha is forbidden in a committed PRODUCTION artifact, +// not in a witness — so the oracle lives in the test corpus against RevisionBoundAtEmission with a +// fixture, and the artifact was simply dead (no execution consumer, and a HostReconciler consumer +// row that named a reader which does not exist). type ReleaseRevisionBinding = RevisionBoundAtEmission { revision: CommitSha } @@ -643,7 +668,17 @@ fn apply_belt_timer_unit_write_step(spec: DeploymentSpec) -> PipelineStep { )) } -data live_deploy_reconcile_binding_note: String = "apply/retract are the two degenerate POLES of the ONE grain-agnostic membership_reconcile (gunbc.membership_reconcile) — no bespoke fold. apply = reconcile(desired = all deploy members, observed = none) => every member Added => Upsert. retract = reconcile(desired = none, observed = the owned artifacts) => every member Removed => Teardown (all Owned, so no refusal). The DeploymentStep (key_of/key_eq/value_eq/ownership_of) bundle is this consumer's Realization of the spine — not a copy of it. ownership_of projects through deployment_step_ownership onto the single gunbc.ownership authority, so it is ALWAYS Present (a deploy member's ownership is known by construction); the OwnershipUnknown/MemberNotOwned refusal arms are the safety backstop for a future non-degenerate observed provider, exercised only by the synthetic spine witness. key_of returns stable member IDENTITY (artifact path / dependency package); value_eq never fires in the degenerate poles (no Modified — one side is always empty)." +// apply/retract are the two degenerate POLES of the ONE grain-agnostic membership_reconcile +// (gunbc.membership_reconcile) — no bespoke fold. apply = reconcile(desired = all deploy members, +// observed = none) => every member Added => Upsert. retract = reconcile(desired = none, observed = +// the owned artifacts) => every member Removed => Teardown (all Owned, so no refusal). The +// DeploymentStep (key_of/key_eq/value_eq/ownership_of) bundle is this consumer's Realization of the +// spine — not a copy of it. ownership_of projects through deployment_step_ownership onto the single +// gunbc.ownership authority, so it is ALWAYS Present (a deploy member's ownership is known by +// construction); the OwnershipUnknown/MemberNotOwned refusal arms are the safety backstop for a +// future non-degenerate observed provider, exercised only by the synthetic spine witness. key_of +// returns stable member IDENTITY (artifact path / dependency package); value_eq never fires in the +// degenerate poles (no Modified — one side is always empty). type DeploymentStepMemberAt = NonEmptyStr @@ -688,7 +723,12 @@ fn deployment_retract_plan(spec: DeploymentSpec) -> MembershipPlan PipelineStep { deploy_raw(command: deploy_stage_write_command( @@ -730,7 +770,18 @@ fn apply_tree_sync_unit_write_step(spec: DeploymentSpec, tree_path: String) -> P )) } -data tree_sync_restart_diagnosis_note: String = "gunbc-tree-sync.service (#7011 realization) failed its control process on every srv1 deploy since landing (receipt 29928424523 and its predecessor 29926422286), and the bare 'sudo -n systemctl restart' stderr — 'Job for gunbc-tree-sync.service failed because the control process exited with error code' — never carries WHY: the actual ExecStart failure (the rsync exit) lives only in the unit's own journal, unreachable from this refusal (DESIGN 5 — a stopped, typed, but unlocated line, the same gap #7038 closed for gunbc-roadmap.service's async ServiceNotReady). This restart is SYNCHRONOUS, ahead of that poll-based unit_diagnosis capture entirely (GunbcSourceTree upsert runs before the SystemdUnit member), so it needs its own capture rather than waiting on one that never runs. Reuses the single unit-status-tail authority (live_deploy_unit_status_tail_command, gunbc.live_deploy.intent) the roadmap-unit capture already owns — no second diagnosis format. On failure the tail is appended to stderr and the step still fails (exit 1): a diagnosis, never an escape hatch (DESIGN 5 forbids proceeding as if the refusal had not fired)." +// gunbc-tree-sync.service (#7011 realization) failed its control process on every srv1 deploy since +// landing (receipt 29928424523 and its predecessor 29926422286), and the bare 'sudo -n systemctl +// restart' stderr — 'Job for gunbc-tree-sync.service failed because the control process exited with +// error code' — never carries WHY: the actual ExecStart failure (the rsync exit) lives only in the +// unit's own journal, unreachable from this refusal (DESIGN 5 — a stopped, typed, but unlocated +// line, the same gap #7038 closed for gunbc-roadmap.service's async ServiceNotReady). This restart +// is SYNCHRONOUS, ahead of that poll-based unit_diagnosis capture entirely (GunbcSourceTree upsert +// runs before the SystemdUnit member), so it needs its own capture rather than waiting on one that +// never runs. Reuses the single unit-status-tail authority (live_deploy_unit_status_tail_command, +// gunbc.live_deploy.intent) the roadmap-unit capture already owns — no second diagnosis format. On +// failure the tail is appended to stderr and the step still fails (exit 1): a diagnosis, never an +// escape hatch (DESIGN 5 forbids proceeding as if the refusal had not fired). fn tree_sync_restart_step_with_diagnosis(spec: DeploymentSpec) -> PipelineStep { deploy_raw(command: join([ @@ -741,7 +792,18 @@ fn tree_sync_restart_step_with_diagnosis(spec: DeploymentSpec) -> PipelineStep { ], "")) } -data tailscale_apply_is_unconditional_note: String = "THE IDEMPOTENCE GUARD IS DELETED, AND ITS REMOVAL IS THE POINT. The apply used to wrap the serve command in `if ! tailscale serve status | grep -q ':'`, which is a pseudo-observation twice over: it greps unstructured status text for a port number that appears nowhere in the configuration key, and a mapping for a DIFFERENT deployment on the same port would satisfy it — so the guard skipped precisely when another deployment held the endpoint, which is the collision it looked like it was preventing.\\n\\nThere is nothing to guard. Configuring an endpoint to a backend it already has is a no-op at the upstream, because the endpoint IS the key; issuing it twice cannot produce a second mapping. So the correct number of observations here is zero, and the replacement for a wrong observation is not a better observation but no observation at all.\\n\\nThe argv is built by extdeps.tailscale.serve rather than concatenated here, so the apply and the teardown cannot disagree about the flags — which is the property the upstream `off` form actually requires." +// THE IDEMPOTENCE GUARD IS DELETED, AND ITS REMOVAL IS THE POINT. The apply used to wrap the serve +// command in `if ! tailscale serve status | grep -q ':'`, which is a pseudo-observation twice +// over: it greps unstructured status text for a port number that appears nowhere in the +// configuration key, and a mapping for a DIFFERENT deployment on the same port would satisfy it — +// so the guard skipped precisely when another deployment held the endpoint, which is the collision +// it looked like it was preventing.\n\nThere is nothing to guard. Configuring an endpoint to a +// backend it already has is a no-op at the upstream, because the endpoint IS the key; issuing it +// twice cannot produce a second mapping. So the correct number of observations here is zero, and +// the replacement for a wrong observation is not a better observation but no observation at +// all.\n\nThe argv is built by extdeps.tailscale.serve rather than concatenated here, so the apply +// and the teardown cannot disagree about the flags — which is the property the upstream `off` form +// actually requires. fn tailscale_serve_apply_step(spec: DeploymentSpec) -> PipelineStep { deploy_raw(command: shell_privileged_command_text_of_argv( @@ -792,7 +854,17 @@ fn install_d_owned_command(spec: DeploymentSpec, path: String) -> String { install_d_owned_for_user(user: spec.service.service_user, path: path) } -data ensure_dependency_steps_note: String = "THE DISPATCH THAT ADMITS A SECOND KIND OF ENSURED RESOURCE, written so the package arm is untouched. A package still lowers to exactly the ensure step it always did, at both privilege settings, so no existing emitted byte moves -- the widening adds a kind, it does not redefine what ensuring a package means.\\n\\nA directory lowers to the same owned install -d the artifact steps use, through the same single authority, with the owner taken from the ensured subject rather than from a deployment spec. That is what lets a host-level resource be stated without an instance: an ensured host directory is a fact about the host, and threading a spec through it would have made it look like a per-instance fact again -- the exact confusion that put this root in the owned roster to begin with.\\n\\nprivileged is consumed only by the package arm because it describes how apt is invoked; install -d is privileged in both contexts and takes the same form either way." +// THE DISPATCH THAT ADMITS A SECOND KIND OF ENSURED RESOURCE, written so the package arm is +// untouched. A package still lowers to exactly the ensure step it always did, at both privilege +// settings, so no existing emitted byte moves -- the widening adds a kind, it does not redefine +// what ensuring a package means.\n\nA directory lowers to the same owned install -d the artifact +// steps use, through the same single authority, with the owner taken from the ensured subject +// rather than from a deployment spec. That is what lets a host-level resource be stated without an +// instance: an ensured host directory is a fact about the host, and threading a spec through it +// would have made it look like a per-instance fact again -- the exact confusion that put this root +// in the owned roster to begin with.\n\nprivileged is consumed only by the package arm because it +// describes how apt is invoked; install -d is privileged in both contexts and takes the same form +// either way. fn ensure_dependency_steps(dep: DeploymentDependencyStep, privileged: Bool) -> List { match dep.subject { @@ -803,7 +875,14 @@ fn ensure_dependency_steps(dep: DeploymentDependencyStep, privileged: Bool) -> L } } -data install_d_managed_group_residue_note: String = "`-g` is spelled with the OWNER'S USER NAME, not a modeled group name, and that is inherited from install_d_owned_command rather than introduced here: PosixUser carries a gid but no group name, and srv1's per-user groups happen to share their user's name so the emitted argv is byte-identical either way. It is written down because the coincidence is load-bearing and silent — a host whose service account sat in a differently-named primary group would emit a `-g` that does not resolve, and the failure would look like a permissions bug rather than a modeling gap. dissolve-on = PosixUser (or a PosixGroup beside it) carries the group name, at which point this projects it instead of reusing the user's." +// `-g` is spelled with the OWNER'S USER NAME, not a modeled group name, and that is inherited from +// install_d_owned_command rather than introduced here: PosixUser carries a gid but no group name, +// and srv1's per-user groups happen to share their user's name so the emitted argv is +// byte-identical either way. It is written down because the coincidence is load-bearing and silent +// — a host whose service account sat in a differently-named primary group would emit a `-g` that +// does not resolve, and the failure would look like a permissions bug rather than a modeling gap. +// dissolve-on = PosixUser (or a PosixGroup beside it) carries the group name, at which point this +// projects it instead of reusing the user's. fn install_d_managed_command(dir: ManagedDirectory) -> String { install_directory_owned_command( @@ -956,7 +1035,24 @@ fn emit_artifact_upsert(art: DeploymentArtifactStep, spec: DeploymentSpec, revis } } -data devboot_root_realization_note: String = "REALIZATION FOR THE BUILD SERVICE'S ARTIFACT STORE, which is an ENSURED host directory rather than an owned artifact, and the difference is the whole safety content. Ensured means converged on every apply and never torn down; owned means removed by retract. Produced artifacts are precisely what a deploy must never delete, so the owned category was wrong -- and it was not wrong in the abstract: with the root on every per-instance spec, a lab twin's retract would have removed the production store, which is what the twin disjointness claims caught.\\n\\nTHE DEPLOY ENSURES THE DIRECTORY; THE SERVICE MAKES IT A REPOSITORY. That split is forced rather than chosen. The deploy principal's sudo roster grants exactly six commands -- apt-get, install, visudo, systemctl, tailscale, rm -- and git is not among them, so a `sudo -n -u git init --bare` step would be refused by sudoers and would red every deploy of this host. An earlier revision emitted precisely that line. `install -d` is granted and idempotent, so the deploy converges ownership and existence; `git init --bare` is idempotent by upstream contract and runs as the account that owns the directory, so no privilege boundary is crossed and no grant needs widening.\\n\\nTHERE IS NO TEARDOWN ARM, and its absence is the point rather than an omission. An ensured resource reaches EffectRemove only through the R5 wall breach poison, so a retract cannot remove this store even by mistake -- the property the previous `rm -d` arm tried to approximate by refusing on a non-empty directory is now structural, and the awkward consequence that arm carried (a full retract could never complete once the store existed) simply does not arise." +// REALIZATION FOR THE BUILD SERVICE'S ARTIFACT STORE, which is an ENSURED host directory rather +// than an owned artifact, and the difference is the whole safety content. Ensured means converged +// on every apply and never torn down; owned means removed by retract. Produced artifacts are +// precisely what a deploy must never delete, so the owned category was wrong -- and it was not +// wrong in the abstract: with the root on every per-instance spec, a lab twin's retract would have +// removed the production store, which is what the twin disjointness claims caught.\n\nTHE DEPLOY +// ENSURES THE DIRECTORY; THE SERVICE MAKES IT A REPOSITORY. That split is forced rather than +// chosen. The deploy principal's sudo roster grants exactly six commands -- apt-get, install, +// visudo, systemctl, tailscale, rm -- and git is not among them, so a `sudo -n -u git +// init --bare` step would be refused by sudoers and would red every deploy of this host. An earlier +// revision emitted precisely that line. `install -d` is granted and idempotent, so the deploy +// converges ownership and existence; `git init --bare` is idempotent by upstream contract and runs +// as the account that owns the directory, so no privilege boundary is crossed and no grant needs +// widening.\n\nTHERE IS NO TEARDOWN ARM, and its absence is the point rather than an omission. An +// ensured resource reaches EffectRemove only through the R5 wall breach poison, so a retract cannot +// remove this store even by mistake -- the property the previous `rm -d` arm tried to approximate +// by refusing on a non-empty directory is now structural, and the awkward consequence that arm +// carried (a full retract could never complete once the store existed) simply does not arise. data belt_receipt_dir_bootstrap_note: String = "THE BELT MEMBER CARRIES THE install -d FOR THE DIRECTORY ITS TICK WRITES INTO, which is the orchestration-is-derived-from-the-member rule emit_deploy_member_effect_note already states (a ServerScript carries the install -d it writes into). The belt tick publishes its receipt to instance_receipts_dir(instance_root) and the workflow UI reads that receipt to answer whether a tick ran; with no directory owned by the service principal the write is EACCES, which is what every instance was in fact doing — belt-last-tick.json was absent from BOTH srv1 roots when read on 2026-08-19, so this receipt has never been written anywhere, not merely on the new slot.\n\nTHIS ALSO RESTORES A BOOTSTRAP THAT WENT DEAD RATHER THAN ADDING A NEW ONE. fleet_converge_receipt_atomic_publish_note FORMERLY named the deploy install-d on fleet_converge_receipt_dir as riding FleetConvergeTimerUnit apply (that clause is itself now corrected in gunbc.host_layout, so this sentence describes the prior revision rather than the current text), and that member is a retired refusal poison (the cadence moved to on-demand plan/apply, sleek-heron-218), so nothing has bootstrapped a receipts directory on any fresh instance root since. Green has no receipts directory at all; production's exists only because it predates the retirement. Riding the belt member is correct rather than convenient: the belt is the consumer that needs the directory on every instance it is installed on, so the bootstrap travels with a member that is unconditionally present wherever a receipt gets written.\n\nTHERE IS DELIBERATELY NO TEARDOWN ARM. Receipts are produced artifacts, and devboot_root_realization_note draws exactly this line — a deploy must never delete what it produced — so the directory is bootstrapped and never removed, and a retract that also destroyed the converge receipts sharing the directory would be the failure that note exists to prevent. install -d is idempotent, so converging an existing correctly-owned directory (production's) emits the same command and changes nothing.\n\nRESIDUE, NAMED: the belt writes its receipt in place rather than through the same-directory staging + rename that gunbc.fleet_converge_cli uses. In-place write is correct here because the directory is service-user-owned and the file is not a stale root-owned artifact, but it is NOT crash-safe — a tick interrupted mid-write leaves a truncated receipt, which renders as BeltPassOutcomeUnreadable rather than as a wrong verdict. Adopting the atomic publish is the strictly better construction and is not done here; it is stated so the weaker one is a decision on the record rather than an oversight." data teardown_worktree_rmdir_note: String = "DispatchWorktreeRoot teardown is rm -d (empty-directory removal via the granted rm — the unprivileged principal has no rmdir grant and no write on the root-owned parent), NEVER rm -rf: a non-empty worktree root means live agent worktrees exist, so the retract FAILS LOUDLY there instead of silently destroying sessions (DESIGN 5 — the refusal is the empty-dir precondition itself, identical to the former rmdir). GunbcSourceTree teardown runs AFTER the worktree rm -d succeeds, so the repo the worktrees are registered in is never deleted from under them." @@ -1030,7 +1126,22 @@ fn emit_artifact_teardown(art: DeploymentArtifactStep, spec: DeploymentSpec) -> } } -data emit_deploy_member_effect_note: String = "The per-member realization HANDLER: share the DIFF (membership_reconcile), dispatch the APPLY (this). One handler for both fold directions — EffectAdd dispatches on member kind to the artifact/dependency install fragments; EffectRemove to the removal fragments. Orchestration is DERIVED from the effect kind, not an always-on envelope: a SystemdUnit upsert carries its own daemon-reload/enable/restart (once, part of the unit's realization); ServerScript carries the /opt/gunbc install -d it writes into. MemberEffect is a bidirectional sum, so each POLE has one structurally-unreachable direction (apply never Remove, retract never Add) — those ARE reached across the two poles combined. TWO arms are unreachable across BOTH poles. EffectRemove of a Dependency: a dependency is Ensured, so a Removed dependency is MemberRemovalRefused, never an EffectRemove. And EffectReplace in either direction: both poles are DEGENERATE (apply observes nothing, retract desires nothing), so neither can produce a Modified hunk at all — a replace here would mean a pole stopped being degenerate, which is a real observed-provider arriving and needs its staged-replacement intermediates declared (gunbc.change_realization) before it may emit anything. That arm REFUSES loudly (emit_deploy_teardown_dependency_refusal — a typed/located poison step), it does NOT fabricate a no-op (DESIGN §5: an unreachable arm refuses, never a silent pass)." +// The per-member realization HANDLER: share the DIFF (membership_reconcile), dispatch the APPLY +// (this). One handler for both fold directions — EffectAdd dispatches on member kind to the +// artifact/dependency install fragments; EffectRemove to the removal fragments. Orchestration is +// DERIVED from the effect kind, not an always-on envelope: a SystemdUnit upsert carries its own +// daemon-reload/enable/restart (once, part of the unit's realization); ServerScript carries the +// /opt/gunbc install -d it writes into. MemberEffect is a bidirectional sum, so each POLE has one +// structurally-unreachable direction (apply never Remove, retract never Add) — those ARE reached +// across the two poles combined. TWO arms are unreachable across BOTH poles. EffectRemove of a +// Dependency: a dependency is Ensured, so a Removed dependency is MemberRemovalRefused, never an +// EffectRemove. And EffectReplace in either direction: both poles are DEGENERATE (apply observes +// nothing, retract desires nothing), so neither can produce a Modified hunk at all — a replace here +// would mean a pole stopped being degenerate, which is a real observed-provider arriving and needs +// its staged-replacement intermediates declared (gunbc.change_realization) before it may emit +// anything. That arm REFUSES loudly (emit_deploy_teardown_dependency_refusal — a typed/located +// poison step), it does NOT fabricate a no-op (DESIGN §5: an unreachable arm refuses, never a +// silent pass). data live_deploy_replace_unreachable_poison: String = "__GUNBC_DEPLOY_UNREACHABLE__ EffectReplace reached the deploy emit dispatch. Both deploy poles are degenerate — apply observes the empty set, retract desires the empty set — so no Modified hunk can arise and no replace can be emitted. Reaching this arm means a NON-degenerate observed provider landed without its realization being modeled: a replace on a host resource is an in-place update only if the address survived, and a STAGED replacement with declared intermediates otherwise (gunbc.change_realization). Emitting an unconditional overwrite here would be the srv4 failure exactly — a transition that believed it had nothing to retire. This marker is not valid deploy shell, so the golden drift gate reds. member=" @@ -1094,7 +1205,20 @@ fn apply_intent_from_effects( } } -data ordering_partition_deleted_note: String = "THE PARTITION THAT PROTECTED SIBLING TEARDOWNS IS DELETED BECAUSE ITS SUBJECT IS GONE. review 45016 found that a refusal arm ending in `false` aborted the whole fail-fast retract at the FIRST teardown, so the unit, worktree root, serve binary, tree-sync files and repo tree were never removed — every retract, production and twin, tore down nothing. The repair sorted refusing teardowns after acting ones, which was correct for the model as it then stood.\\n\\nThat model was wrong one layer up. The only refusing owned artifact was TailscaleServeMapping, and it refused on an uncited claim that the CLI has no scoped removal. With the endpoint modeled, its teardown ACTS, and no owned artifact refuses — so the partition sorts a list by a predicate that is now constant, which is a node whose validation cannot fail.\\n\\nThe class it guarded is closed differently and more strongly: a retract for a host whose client cannot do scoped removal is not EMITTED at all (retract_capability_refusal), so a stop-the-line arm cannot appear among the teardowns in the first place, and there is no ordering left to get right. Keeping the partition would preserve the shape of a fix for a defect that no longer has a way to occur, which is exactly the dead-scaffold class." +// THE PARTITION THAT PROTECTED SIBLING TEARDOWNS IS DELETED BECAUSE ITS SUBJECT IS GONE. review +// 45016 found that a refusal arm ending in `false` aborted the whole fail-fast retract at the FIRST +// teardown, so the unit, worktree root, serve binary, tree-sync files and repo tree were never +// removed — every retract, production and twin, tore down nothing. The repair sorted refusing +// teardowns after acting ones, which was correct for the model as it then stood.\n\nThat model was +// wrong one layer up. The only refusing owned artifact was TailscaleServeMapping, and it refused on +// an uncited claim that the CLI has no scoped removal. With the endpoint modeled, its teardown +// ACTS, and no owned artifact refuses — so the partition sorts a list by a predicate that is now +// constant, which is a node whose validation cannot fail.\n\nThe class it guarded is closed +// differently and more strongly: a retract for a host whose client cannot do scoped removal is not +// EMITTED at all (retract_capability_refusal), so a stop-the-line arm cannot appear among the +// teardowns in the first place, and there is no ordering left to get right. Keeping the partition +// would preserve the shape of a fix for a defect that no longer has a way to occur, which is +// exactly the dead-scaffold class. fn retract_unsupported_client_poison(observed: String) -> String { concat( @@ -1224,7 +1348,25 @@ fn deployment_ensure_dependency_script(dep: DeploymentDependencyStep) -> String emit_pipeline_or_poison(p: deployment_ensure_dependency_intent(dep: dep), refused: live_deploy_ensure_dep_emit_refused_poison) } -data emitted_shell_parseability_oracle_note: String = "THE ORACLE THIS PR'S OWN DIAGNOSIS ARGUES FOR AND DOES NOT YET BUILD, written down rather than left implicit (review, 2026-07-30). The refusal-arm defect established that a byte-equality golden gate and target-language VALIDITY are different oracle families, and only the first exists over these two artifacts. What landed in response was a string-shape claim about one call site — the escape form present, the raw apostrophe absent — which closes that site and says nothing about the next hand-quoted one.\\n\\nSo the class is open and this row names its closure rather than implying the point fix covered it. The repo already has the machinery: a bounded bash grammar and serialize_bash, read forward, ingest the emitted script and refuse on a parse failure — the same one-grammar-both-directions move DESIGN 4 describes, with a bash -n gate over the two committed artifacts as the cheaper interim if the grammar's coverage of this dialect proves short.\\n\\nThe point fix it described is now gone with its subject: the arm that needed quoting was the tailscale teardown refusal, and that refusal was itself built on a false claim about the CLI, so the emitter no longer quotes anything by hand. That REMOVES a call site; it does not close the class, and the bound is now worse rather than better — with no hand-quoted site left in this module, nothing here would red if the next one were introduced.\\n\\nDISSOLVES ON: both generated deploy scripts parsed by an executing consumer on every emit, with a discriminating RED that introduces a hand-concatenated quote at any call site and goes red on the parse rather than on a string shape. The shared authority itself stays independently witnessed at shell_quote_escapes_an_embedded_apostrophe, which does not depend on this module having a caller." +// THE ORACLE THIS PR'S OWN DIAGNOSIS ARGUES FOR AND DOES NOT YET BUILD, written down rather than +// left implicit (review, 2026-07-30). The refusal-arm defect established that a byte-equality +// golden gate and target-language VALIDITY are different oracle families, and only the first exists +// over these two artifacts. What landed in response was a string-shape claim about one call site — +// the escape form present, the raw apostrophe absent — which closes that site and says nothing +// about the next hand-quoted one.\n\nSo the class is open and this row names its closure rather +// than implying the point fix covered it. The repo already has the machinery: a bounded bash +// grammar and serialize_bash, read forward, ingest the emitted script and refuse on a parse failure +// — the same one-grammar-both-directions move DESIGN 4 describes, with a bash -n gate over the two +// committed artifacts as the cheaper interim if the grammar's coverage of this dialect proves +// short.\n\nThe point fix it described is now gone with its subject: the arm that needed quoting +// was the tailscale teardown refusal, and that refusal was itself built on a false claim about the +// CLI, so the emitter no longer quotes anything by hand. That REMOVES a call site; it does not +// close the class, and the bound is now worse rather than better — with no hand-quoted site left in +// this module, nothing here would red if the next one were introduced.\n\nDISSOLVES ON: both +// generated deploy scripts parsed by an executing consumer on every emit, with a discriminating RED +// that introduces a hand-concatenated quote at any call site and goes red on the parse rather than +// on a string shape. The shared authority itself stays independently witnessed at +// shell_quote_escapes_an_embedded_apostrophe, which does not depend on this module having a caller. data live_deploy_emit_shell_dissolution_trigger: Disposition = Scaffold { dissolves_to: RealizationDispatch, @@ -1253,6 +1395,43 @@ data live_deploy_remote_mutation_service_op_realization_dissolution_trigger: Dis } } +// The apply/retract scripts are now ONE orchestration-intent Pipeline each, built from the two +// degenerate POLES of the grain-agnostic gunbc.membership_reconcile (apply = reconcile +// desired=all-members/observed=none => all Upsert; retract = reconcile +// desired=none/observed=owned-artifacts => all Teardown) — apply_intent_from_effects / +// retract_intent_from_effects fold the resulting MemberEffects into the Pipeline via the per-kind +// emit_deploy_member_effect handler (share the diff, dispatch the apply), framing +// (preamble/receipt) wrapping OUTSIDE the reconcile. A refusal (a non-owned/unknown member) refuses +// the WHOLE fold before emit (EffectsOrRefusal sum — un-emittable by type; never reached by the +// degenerate poles). The Pipeline is emitted through the signed +// v2.compiler.emit_orchestration.orch_emit_pipeline path over the bash_orchestration_emit medium — +// NOT assembled by workflow-code string concat. The smuggled-program class (control flow / +// sequencing joins built by concat in this module — the former ensure_apt_package function-def, the +// deployment_ensure_dependency_script nested if/then concat, the tailscale if/fi) is dissolved: +// dependency-ensure is inline If-Not-ExitZero(dpkg -s) then Do(sudo -n apt-get install) intent +// (DESIGN §2 CUT: desugar inline, no minted verb), and the emission JOIN lives in +// extdeps/languages/bash_orchestration_emit, not here (no-smuggled-programs wall: a string literal +// is an ATOM, joins live in extdeps/languages rows). An emission refusal returns a poison marker +// (live_deploy_*_emit_refused_poison) so the marker reaches a consumer instead of a hand-spelled +// fallback (DESIGN §5: refuse, never widen). THE COMMITTED GOLDEN ARTIFACTS ARE DELETED: nothing +// executed .github/live-deploy-srv1-apply.sh or -retract.sh, and their registry rows named a +// HostReconciler consumer that does not exist — the real deploy runs live_deploy_apply_srv1_wet +// through gunbc run. A committed artifact nobody reads is not coverage. The byte oracle those +// goldens provided now lives where a fixture revision is legitimate, in test.claim.live_deploy.emit +// against RevisionBoundAtEmission, and it is strictly stronger there: the goldens compared whole +// files and located nothing, while the witnesses pin the exact ExecStart line with three REDs (flag +// absent, wrong candidate's value, flag duplicated). RESIDUE (named, not this slice): Run.command +// is still a String leaf (v2.std.orchestration.Run), so the heredoc file bodies (server.js, systemd +// unit) and per-command argv interpolation ride inside command: strings as the anemic-leaf residue +// — they dissolve when Do-effect gains a typed file-write leaf (heredoc file bodies as typed +// orchestration intent, live_deploy_intent_dependency_graph_emit_dissolution_trigger binds +// orch_emit_pipeline). Transport-aware remote-mutation service-op realization (live_deploy runs +// shell, not yet a transport-realized service op) is OUT OF SCOPE — +// live_deploy_remote_mutation_service_op_realization_dissolution_trigger. Healthz readiness and +// digest read-back run on the DAG reconcile spine after mutation (live_deploy.readiness transport +// curl poll, then live_deploy.apply digest read-back); the apply script ships mutation only (no +// in-script curl read-back). + data live_deploy_intent_dependency_graph_emit_dissolution_trigger: Disposition = Scaffold { dissolves_to: RealizationDispatch, bind: DeclarationRef { @@ -1261,5 +1440,3 @@ data live_deploy_intent_dependency_graph_emit_dissolution_trigger: Disposition = field: WholeDeclaration, } } - -data live_deploy_golden_freeze_note: String = "The apply/retract scripts are now ONE orchestration-intent Pipeline each, built from the two degenerate POLES of the grain-agnostic gunbc.membership_reconcile (apply = reconcile desired=all-members/observed=none => all Upsert; retract = reconcile desired=none/observed=owned-artifacts => all Teardown) — apply_intent_from_effects / retract_intent_from_effects fold the resulting MemberEffects into the Pipeline via the per-kind emit_deploy_member_effect handler (share the diff, dispatch the apply), framing (preamble/receipt) wrapping OUTSIDE the reconcile. A refusal (a non-owned/unknown member) refuses the WHOLE fold before emit (EffectsOrRefusal sum — un-emittable by type; never reached by the degenerate poles). The Pipeline is emitted through the signed v2.compiler.emit_orchestration.orch_emit_pipeline path over the bash_orchestration_emit medium — NOT assembled by workflow-code string concat. The smuggled-program class (control flow / sequencing joins built by concat in this module — the former ensure_apt_package function-def, the deployment_ensure_dependency_script nested if/then concat, the tailscale if/fi) is dissolved: dependency-ensure is inline If-Not-ExitZero(dpkg -s) then Do(sudo -n apt-get install) intent (DESIGN §2 CUT: desugar inline, no minted verb), and the emission JOIN lives in extdeps/languages/bash_orchestration_emit, not here (no-smuggled-programs wall: a string literal is an ATOM, joins live in extdeps/languages rows). An emission refusal returns a poison marker (live_deploy_*_emit_refused_poison) so the marker reaches a consumer instead of a hand-spelled fallback (DESIGN §5: refuse, never widen). THE COMMITTED GOLDEN ARTIFACTS ARE DELETED: nothing executed .github/live-deploy-srv1-apply.sh or -retract.sh, and their registry rows named a HostReconciler consumer that does not exist — the real deploy runs live_deploy_apply_srv1_wet through gunbc run. A committed artifact nobody reads is not coverage. The byte oracle those goldens provided now lives where a fixture revision is legitimate, in test.claim.live_deploy.emit against RevisionBoundAtEmission, and it is strictly stronger there: the goldens compared whole files and located nothing, while the witnesses pin the exact ExecStart line with three REDs (flag absent, wrong candidate's value, flag duplicated). RESIDUE (named, not this slice): Run.command is still a String leaf (v2.std.orchestration.Run), so the heredoc file bodies (server.js, systemd unit) and per-command argv interpolation ride inside command: strings as the anemic-leaf residue — they dissolve when Do-effect gains a typed file-write leaf (heredoc file bodies as typed orchestration intent, live_deploy_intent_dependency_graph_emit_dissolution_trigger binds orch_emit_pipeline). Transport-aware remote-mutation service-op realization (live_deploy runs shell, not yet a transport-realized service op) is OUT OF SCOPE — live_deploy_remote_mutation_service_op_realization_dissolution_trigger. Healthz readiness and digest read-back run on the DAG reconcile spine after mutation (live_deploy.readiness transport curl poll, then live_deploy.apply digest read-back); the apply script ships mutation only (no in-script curl read-back)." diff --git a/dag/gunbc/live_deploy/intent.dag b/dag/gunbc/live_deploy/intent.dag index 94f2d9ef328..599c6fe625c 100644 --- a/dag/gunbc/live_deploy/intent.dag +++ b/dag/gunbc/live_deploy/intent.dag @@ -63,7 +63,10 @@ data live_deploy_unit_diagnosis_capture_exit_note: String = "DECLARED SCAFFOLD data live_deploy_unit_diagnosis_dissolution_trigger: DissolutionCondition = unbound_dissolution(description: "dissolves-on: unit-diagnosis modeled as a typed systemd.Systemctl.Status observation (exit code recorded as data in UnitStatusCapture, never masked by '| tail' or trailing 'exit 0'); delete live_deploy_unit_status_tail_command concat.") -data live_deploy_unit_status_emit_scaffold_note: String = "Emit-only residue: tree_sync_restart_step_with_diagnosis (gunbc.live_deploy.emit) still shells live_deploy_unit_status_tail_command into foreign-executor bash. Readiness unit_diagnosis now routes through systemd.Systemctl.Status (gunbc.systemctl_status_read); dissolve this concat when tree-sync restart migrates to typed host_effect_apply." +// Emit-only residue: tree_sync_restart_step_with_diagnosis (gunbc.live_deploy.emit) still shells +// live_deploy_unit_status_tail_command into foreign-executor bash. Readiness unit_diagnosis now +// routes through systemd.Systemctl.Status (gunbc.systemctl_status_read); dissolve this concat when +// tree-sync restart migrates to typed host_effect_apply. data live_deploy_unit_status_tail_scaffold: Disposition = Scaffold { dissolves_to: SingleAuthority, diff --git a/dag/gunbc/live_deploy/readiness.dag b/dag/gunbc/live_deploy/readiness.dag index 6bc8e684425..191d2044146 100644 --- a/dag/gunbc/live_deploy/readiness.dag +++ b/dag/gunbc/live_deploy/readiness.dag @@ -188,11 +188,65 @@ fn healthz_body_groundable(body: String) -> Bool { roadmap_site_healthz_json_string_parses(body: body) } -data service_ready_means_serving_this_tree_note: String = "READY was under-specified and the gap was live: it meant 'something answered /healthz', which the PRE-RESTART process satisfies. `gunbc serve` binds its compiled graph ONCE at process start, so between `systemctl restart` and the new process finishing its ~35 s load the OLD process is still answering — a poll that grounds on any answer can therefore green against the very process the deploy is replacing, and the deploy reports success while the dashboard still serves the previous tree. That is not a hypothetical: on 2026-07-24 the live srv1 page was serving the escaped `.node-superseded > .title` selector, the pre-fix `width` chip, no disclosure and no centering, and every check in the system was green. So readiness now means the STRONGER and actually intended thing — the unit is answering AND the surface it answers with is this tree's — which the healthz digest map makes a plain equality (roadmap_site_healthz_surface_is_current). The three refusal causes stay type-distinct because their remedies differ: the probe never landed (network/unit down), the body is not JSON (wrong process on the port), the body is a well-formed fingerprint of ANOTHER tree (graph not rebound / sync incomplete). Each is a typed, located, countable refusal that the poll retries within its bound and that REFUSES at the bound — never a widen, and never a mode that proceeds as if it had not fired. Known and accepted race, named rather than hidden: two main pushes deploying concurrently can have deploy N observe deploy N+1's surface, which refuses honestly ('the surface is not the one I deployed' is true) and self-heals on the next deploy." - -data readiness_revision_half_note: String = "READINESS NOW ASKS BOTH QUESTIONS THE PROCESS ANSWERS, AND KEEPS THEM APART. The surface half ('is this tree's surface what the unit serves') was already here and is unchanged in meaning. The revision half is new and is the one the receipt could not supply: the running process reports which release it was LAUNCHED as, so 'the unit answering on this port is the candidate I just installed' becomes a comparison against an immutable fact of that process rather than an inference from a file, a directory, or the fact that something answered.\\n\\nWHY THE SURFACE CHECK DOES NOT ALREADY COVER IT. Two deploys of the SAME tree at different commits render byte-identical surfaces — a commit touching only Rust, only tests, or only documents changes the revision and not one served artifact — so a surface-only readiness greens against the previous process on every such deploy. That is not an exotic case; it is most of them. Conversely a process can serve a fresh surface at an unexpected revision when another candidate won the route. Neither half implies the other, which is exactly why the wire carries two members.\\n\\nTHE EXPECTED REVISION IS NOT RE-OBSERVED HERE. It is candidate.revision — the same single value the launcher observed once and the emitter wrote into ExecStart — so 'the checkout's sha, the emitted launch sha, and the process-reported sha are equal' is a claim about ONE subject read back, not three readings that happen to agree. A readiness that observed its own expectation could only ever prove the host agrees with whatever the host currently says.\\n\\nProcessIdentityAbsent and ProcessIdentityMalformed stay distinct from ProcessRevisionMismatch because a process that cannot state its release has not disagreed with anything — it is the pre-identity process, or a wrong process on the port, and the remedy is to look at what is listening rather than at which commit won." - -data readiness_verdict_is_flat_note: String = "THESE ARMS ARE PEERS, NOT KINDS OF ONE ANOTHER, WHICH IS WHY THE GROUPING WAS REMOVED HERE AND KEPT ONE LAYER DOWN. Identity refusals used to collapse into a single ReleaseIdentityUnreadable carrying a rendered sentence, so 'the process states no release' and 'the process states a malformed one' arrived as the same verdict distinguished only by prose — and a mismatch, which is not an identity failure at all, sat beside them as if it were a sibling of the same kind.\\n\\nThe distinctions are operational. ProcessIdentityAbsent is the MIGRATION arm: a process launched before this channel existed. It is a refusal, never a synthetic predecessor, but its remedy is 'restart it under a unit that passes --release-revision'. ProcessIdentityMalformed means the channel exists and the process is answering wrongly, which is a defect rather than an old process. ProcessRevisionMismatch means identity was read cleanly and disagrees. ProcessSurfaceMismatch means the reported surface digest is not the one the candidate expected. ServedSurfaceStale means the projected artifact map differs. Four different remedies, four arms.\\n\\nTHE OBSERVATION CARRIER STAYS GROUPED, DELIBERATELY. gunbc.running_release_identity keeps RunningReleaseIdentified | RunningReleaseUnidentified\{cause\} because a real consumer branches on identified-versus-not WITHOUT enumerating causes — this fold's own match. A grouping that some consumer reads is a level; a grouping nobody reads is a nickname. These two carriers are two layers of one model, not two names for one thing, and collapsing either into the other would lose a distinction something depends on." +// READY was under-specified and the gap was live: it meant 'something answered /healthz', which the +// PRE-RESTART process satisfies. `gunbc serve` binds its compiled graph ONCE at process start, so +// between `systemctl restart` and the new process finishing its ~35 s load the OLD process is still +// answering — a poll that grounds on any answer can therefore green against the very process the +// deploy is replacing, and the deploy reports success while the dashboard still serves the previous +// tree. That is not a hypothetical: on 2026-07-24 the live srv1 page was serving the escaped +// `.node-superseded > .title` selector, the pre-fix `width` chip, no disclosure and no +// centering, and every check in the system was green. So readiness now means the STRONGER and +// actually intended thing — the unit is answering AND the surface it answers with is this tree's — +// which the healthz digest map makes a plain equality (roadmap_site_healthz_surface_is_current). +// The three refusal causes stay type-distinct because their remedies differ: the probe never landed +// (network/unit down), the body is not JSON (wrong process on the port), the body is a well-formed +// fingerprint of ANOTHER tree (graph not rebound / sync incomplete). Each is a typed, located, +// countable refusal that the poll retries within its bound and that REFUSES at the bound — never a +// widen, and never a mode that proceeds as if it had not fired. Known and accepted race, named +// rather than hidden: two main pushes deploying concurrently can have deploy N observe deploy N+1's +// surface, which refuses honestly ('the surface is not the one I deployed' is true) and self-heals +// on the next deploy. + +// READINESS NOW ASKS BOTH QUESTIONS THE PROCESS ANSWERS, AND KEEPS THEM APART. The surface half +// ('is this tree's surface what the unit serves') was already here and is unchanged in meaning. The +// revision half is new and is the one the receipt could not supply: the running process reports +// which release it was LAUNCHED as, so 'the unit answering on this port is the candidate I just +// installed' becomes a comparison against an immutable fact of that process rather than an +// inference from a file, a directory, or the fact that something answered.\n\nWHY THE SURFACE CHECK +// DOES NOT ALREADY COVER IT. Two deploys of the SAME tree at different commits render +// byte-identical surfaces — a commit touching only Rust, only tests, or only documents changes the +// revision and not one served artifact — so a surface-only readiness greens against the previous +// process on every such deploy. That is not an exotic case; it is most of them. Conversely a +// process can serve a fresh surface at an unexpected revision when another candidate won the route. +// Neither half implies the other, which is exactly why the wire carries two members.\n\nTHE +// EXPECTED REVISION IS NOT RE-OBSERVED HERE. It is candidate.revision — the same single value the +// launcher observed once and the emitter wrote into ExecStart — so 'the checkout's sha, the emitted +// launch sha, and the process-reported sha are equal' is a claim about ONE subject read back, not +// three readings that happen to agree. A readiness that observed its own expectation could only +// ever prove the host agrees with whatever the host currently says.\n\nProcessIdentityAbsent and +// ProcessIdentityMalformed stay distinct from ProcessRevisionMismatch because a process that cannot +// state its release has not disagreed with anything — it is the pre-identity process, or a wrong +// process on the port, and the remedy is to look at what is listening rather than at which commit +// won. + +// THESE ARMS ARE PEERS, NOT KINDS OF ONE ANOTHER, WHICH IS WHY THE GROUPING WAS REMOVED HERE AND +// KEPT ONE LAYER DOWN. Identity refusals used to collapse into a single ReleaseIdentityUnreadable +// carrying a rendered sentence, so 'the process states no release' and 'the process states a +// malformed one' arrived as the same verdict distinguished only by prose — and a mismatch, which is +// not an identity failure at all, sat beside them as if it were a sibling of the same kind.\n\nThe +// distinctions are operational. ProcessIdentityAbsent is the MIGRATION arm: a process launched +// before this channel existed. It is a refusal, never a synthetic predecessor, but its remedy is +// 'restart it under a unit that passes --release-revision'. ProcessIdentityMalformed means the +// channel exists and the process is answering wrongly, which is a defect rather than an old +// process. ProcessRevisionMismatch means identity was read cleanly and disagrees. +// ProcessSurfaceMismatch means the reported surface digest is not the one the candidate expected. +// ServedSurfaceStale means the projected artifact map differs. Four different remedies, four +// arms.\n\nTHE OBSERVATION CARRIER STAYS GROUPED, DELIBERATELY. gunbc.running_release_identity +// keeps RunningReleaseIdentified | RunningReleaseUnidentified{cause} because a real consumer +// branches on identified-versus-not WITHOUT enumerating causes — this fold's own match. A grouping +// that some consumer reads is a level; a grouping nobody reads is a nickname. These two carriers +// are two layers of one model, not two names for one thing, and collapsing either into the other +// would lose a distinction something depends on. type HealthzRefusalCause = HealthzProbeFailed { detail: String } @@ -374,7 +428,11 @@ fn show_healthz_read_for_deploy( } } -data healthz_healthy_fixture_note: String = "The healthy fixture is the REAL healthz body, not a hand-spelled {\"status\":\"ok\"}. Once readiness means 'answering AND serving this tree', a fixture that merely parses is no longer a healthy read — it is precisely the stale-surface case — so a stand-in body would have made the converges-on-healthy witnesses assert the opposite of the contract. Its counterpart witness_stale_surface_read_does_not_ground keeps the hand-spelled body as the discriminating RED." +// The healthy fixture is the REAL healthz body, not a hand-spelled {"status":"ok"}. Once readiness +// means 'answering AND serving this tree', a fixture that merely parses is no longer a healthy read +// — it is precisely the stale-surface case — so a stand-in body would have made the +// converges-on-healthy witnesses assert the opposite of the contract. Its counterpart +// witness_stale_surface_read_does_not_ground keeps the hand-spelled body as the discriminating RED. fn show_healthz_read_fixture_healthy( bundle: RoadmapSiteSurfaceBundle, @@ -395,7 +453,16 @@ fn show_healthz_read_fixture_healthy( } } -data healthz_healthy_fixture_carries_identity_note: String = "THE HEALTHY FIXTURE MOVED FROM bundle.healthz_body TO THE FULL SERVED DOCUMENT, AND IT HAD TO. bundle.healthz_body is the SURFACE projection — what the deploying tree expects the surface to be — and a running process never serves it, because a running process also states its release identity. Once readiness checks that identity, a fixture spelling the surface alone is no longer a healthy read: it is the pre-identity process, and the converges-on-healthy witnesses would have been asserting the refusal they exist to exclude.\\n\\nThe fixture is rendered by healthz_body_with_release_identity — the SAME producer the live route uses — so the healthy case is a document the process could actually emit rather than a hand-assembled approximation of one. That is the same discipline the original fixture note recorded when it refused a hand-spelled {\\\"status\\\":\\\"ok\\\"}, applied one layer up." +// THE HEALTHY FIXTURE MOVED FROM bundle.healthz_body TO THE FULL SERVED DOCUMENT, AND IT HAD TO. +// bundle.healthz_body is the SURFACE projection — what the deploying tree expects the surface to be +// — and a running process never serves it, because a running process also states its release +// identity. Once readiness checks that identity, a fixture spelling the surface alone is no longer +// a healthy read: it is the pre-identity process, and the converges-on-healthy witnesses would have +// been asserting the refusal they exist to exclude.\n\nThe fixture is rendered by +// healthz_body_with_release_identity — the SAME producer the live route uses — so the healthy case +// is a document the process could actually emit rather than a hand-assembled approximation of one. +// That is the same discipline the original fixture note recorded when it refused a hand-spelled +// {\"status\":\"ok\"}, applied one layer up. fn show_healthz_read_fixture_healthy_live( release_revision: CommitSha, diff --git a/dag/gunbc/live_deploy/revision.dag b/dag/gunbc/live_deploy/revision.dag index cb8b2b882a4..423567cbe89 100644 --- a/dag/gunbc/live_deploy/revision.dag +++ b/dag/gunbc/live_deploy/revision.dag @@ -4,11 +4,45 @@ import std.types { NonEmptyStr, FilePath, CommitSha } import gunbc.fleet_intent { srv1_host } import product.placement_supply { HostIdentity } -data deploy_revision_authority_note: String = "DEPLOY ORDERING IS A SEPARATE DEFECT FROM DEPLOY INTERLEAVING, AND ONLY THIS MODULE ADDRESSES THE FIRST. Serialization (one deploy at a time per target) stops two deploys from rsyncing into the same tree concurrently; it does NOT make the sequence monotonic, because main-push runs are deliberately not cancelled (operator ruling 2026-07-23: the per-commit verdict history is bisection evidence) and GitHub orders queued jobs by when they began waiting, never by commit ancestry. srv1-deploy-serialization-and-false-greens.md §2.3 carries the receipt that arrival order is ALREADY inverted relative to commit order — the earlier commit's deploy started 12 seconds later — and its §6 concludes that serialization ALONE is strictly worse than today's honest red, because an older deploy running second rsyncs its older tree, converges, and greens, silently regressing the live dashboard. The missing half is a freshness precondition, which is what classify_deploy_revision is." - -data deploy_revision_four_cases_note: String = "FOUR CASES, NOT A BINARY DESCENDANT CHECK, and the third is the one that matters. A naive guard refuses everything that is not a strict descendant, which turns the ORDINARY case — an older run finishing after a newer one — into a red. That is expected noise by construction: it fires on every rapid push pair, so it trains the operator to ignore deploy failures, which is the opposite of a safety property (operator ruling 2026-07-30). The cases: SameRevision means the host already carries this exact tree, so there is nothing to do and nothing to report. DeployedIsAncestor is the normal forward deploy. CandidateIsAncestor means a NEWER revision is already live and this run has been superseded — the correct behavior is to do nothing and exit ZERO, because the fleet is in a strictly better state than this run would have produced; treating it as failure would red main for behaving correctly. UnrelatedHistories (neither is an ancestor of the other — a force-push, a rewritten branch, a different repository) and RelationUnverifiable (the ancestry probe itself could not answer: missing object, unreadable state, no persisted revision) are the only refusals, and both are typed and located rather than a generic error. Refusing on unverifiable is the fail-closed direction: 'I could not determine whether this is newer' is NOT 'this is newer', and collapsing the two is the absorbing-fallback shape DESIGN §5 forbids." - -data deploy_revision_probe_note: String = "The relation is OBSERVED, not inferred from timestamps or run numbers. `git merge-base --is-ancestor A B` answers it in one exit code — 0 for yes, 1 for no, 128 for a bad object — and the exit-1 case is a legitimate ANSWER rather than an error, which is precisely what lets UnrelatedHistories and RelationUnverifiable stay separate variants. Two probes are needed because a single direction cannot distinguish 'candidate is older' from 'unrelated': is_ancestor(deployed, candidate) and is_ancestor(candidate, deployed) together give (true,true)=same, (true,false)=advance, (false,true)=superseded, (false,false)=unrelated. Equality is decided by SHA comparison BEFORE any probe, because it is the common no-op case and deserves neither a subprocess nor a chance to misclassify. Timestamps are explicitly NOT an input: wall-clock ordering of CI runs is exactly the signal that is already inverted, and commit dates are attacker- and rebase-controlled." +// DEPLOY ORDERING IS A SEPARATE DEFECT FROM DEPLOY INTERLEAVING, AND ONLY THIS MODULE ADDRESSES THE +// FIRST. Serialization (one deploy at a time per target) stops two deploys from rsyncing into the +// same tree concurrently; it does NOT make the sequence monotonic, because main-push runs are +// deliberately not cancelled (operator ruling 2026-07-23: the per-commit verdict history is +// bisection evidence) and GitHub orders queued jobs by when they began waiting, never by commit +// ancestry. srv1-deploy-serialization-and-false-greens.md §2.3 carries the receipt that arrival +// order is ALREADY inverted relative to commit order — the earlier commit's deploy started 12 +// seconds later — and its §6 concludes that serialization ALONE is strictly worse than today's +// honest red, because an older deploy running second rsyncs its older tree, converges, and greens, +// silently regressing the live dashboard. The missing half is a freshness precondition, which is +// what classify_deploy_revision is. + +// FOUR CASES, NOT A BINARY DESCENDANT CHECK, and the third is the one that matters. A naive guard +// refuses everything that is not a strict descendant, which turns the ORDINARY case — an older run +// finishing after a newer one — into a red. That is expected noise by construction: it fires on +// every rapid push pair, so it trains the operator to ignore deploy failures, which is the opposite +// of a safety property (operator ruling 2026-07-30). The cases: SameRevision means the host already +// carries this exact tree, so there is nothing to do and nothing to report. DeployedIsAncestor is +// the normal forward deploy. CandidateIsAncestor means a NEWER revision is already live and this +// run has been superseded — the correct behavior is to do nothing and exit ZERO, because the fleet +// is in a strictly better state than this run would have produced; treating it as failure would red +// main for behaving correctly. UnrelatedHistories (neither is an ancestor of the other — a +// force-push, a rewritten branch, a different repository) and RelationUnverifiable (the ancestry +// probe itself could not answer: missing object, unreadable state, no persisted revision) are the +// only refusals, and both are typed and located rather than a generic error. Refusing on +// unverifiable is the fail-closed direction: 'I could not determine whether this is newer' is NOT +// 'this is newer', and collapsing the two is the absorbing-fallback shape DESIGN §5 forbids. + +// The relation is OBSERVED, not inferred from timestamps or run numbers. `git merge-base +// --is-ancestor A B` answers it in one exit code — 0 for yes, 1 for no, 128 for a bad object — and +// the exit-1 case is a legitimate ANSWER rather than an error, which is precisely what lets +// UnrelatedHistories and RelationUnverifiable stay separate variants. Two probes are needed because +// a single direction cannot distinguish 'candidate is older' from 'unrelated': +// is_ancestor(deployed, candidate) and is_ancestor(candidate, deployed) together give +// (true,true)=same, (true,false)=advance, (false,true)=superseded, (false,false)=unrelated. +// Equality is decided by SHA comparison BEFORE any probe, because it is the common no-op case and +// deserves neither a subprocess nor a chance to misclassify. Timestamps are explicitly NOT an +// input: wall-clock ordering of CI runs is exactly the signal that is already inverted, and commit +// dates are attacker- and rebase-controlled. type DeployTargetId { host: HostIdentity @@ -16,7 +50,13 @@ type DeployTargetId { service_unit: NonEmptyStr } -data deploy_target_id_note: String = "The mutex identity is the DEPLOYMENT DOMAIN — the host, the directory the deploy rsyncs into, and the service unit it restarts — never the workflow job name. A job id is a CI-side label: renaming the job silently mints a different lock while the same three host resources are still being mutated, and two differently-named stages that touch the same directory would serialize against nothing. Deriving the identity from what is actually mutated makes 'same target' mean 'same bytes at risk', which is the only reading under which a mutex is a safety property rather than a naming convention." +// The mutex identity is the DEPLOYMENT DOMAIN — the host, the directory the deploy rsyncs into, and +// the service unit it restarts — never the workflow job name. A job id is a CI-side label: renaming +// the job silently mints a different lock while the same three host resources are still being +// mutated, and two differently-named stages that touch the same directory would serialize against +// nothing. Deriving the identity from what is actually mutated makes 'same target' mean 'same bytes +// at risk', which is the only reading under which a mutex is a safety property rather than a naming +// convention. data srv1_dashboard_target: DeployTargetId = DeployTargetId { host: srv1_host.identity, @@ -50,7 +90,15 @@ fn classify_deploy_revision(relation: DeployRevisionRelation) -> DeployRevisionV } } -data deploy_revision_disposition_split_note: String = "Three dispositions, deliberately not two. `proceeds` is the ONLY verdict that performs the mutation. `terminal_success` covers the two do-nothing outcomes — already converged and superseded — which must exit ZERO: they are correct states of the world, not failures, and the whole point of the four-case split is that a superseded run is indistinguishable from a healthy fleet. `refuses` is the only red. Collapsing proceeds and terminal_success into one 'ok' predicate would lose the distinction the apply path needs (whether to mutate at all), and collapsing superseded into refuses would reintroduce the expected-red-noise defect this module exists to prevent. Every verdict lands in exactly one of the three, and the witnesses assert that totality rather than trusting it." +// Three dispositions, deliberately not two. `proceeds` is the ONLY verdict that performs the +// mutation. `terminal_success` covers the two do-nothing outcomes — already converged and +// superseded — which must exit ZERO: they are correct states of the world, not failures, and the +// whole point of the four-case split is that a superseded run is indistinguishable from a healthy +// fleet. `refuses` is the only red. Collapsing proceeds and terminal_success into one 'ok' +// predicate would lose the distinction the apply path needs (whether to mutate at all), and +// collapsing superseded into refuses would reintroduce the expected-red-noise defect this module +// exists to prevent. Every verdict lands in exactly one of the three, and the witnesses assert that +// totality rather than trusting it. fn deploy_revision_proceeds(verdict: DeployRevisionVerdict) -> Bool { match verdict { @@ -79,7 +127,11 @@ fn deploy_revision_refuses(verdict: DeployRevisionVerdict) -> Bool { } } -data deploy_revision_probe_relation_note: String = "The (deployed→candidate, candidate→deployed) probe pair folded into a relation. SHA equality is checked first and short-circuits both probes. Note that (true,true) is reachable ONLY when the SHAs are equal — a commit is its own ancestor and ancestry is antisymmetric — so this arm is defensive rather than expected, and it agrees with the equality short-circuit instead of contradicting it." +// The (deployed→candidate, candidate→deployed) probe pair folded into a relation. SHA equality is +// checked first and short-circuits both probes. Note that (true,true) is reachable ONLY when the +// SHAs are equal — a commit is its own ancestor and ancestry is antisymmetric — so this arm is +// defensive rather than expected, and it agrees with the equality short-circuit instead of +// contradicting it. // THE CLASSIFICATION IS ONE AUTHORITY WITH TWO EVIDENCE REALIZATIONS. // @@ -122,7 +174,15 @@ fn deploy_revision_relation_from_probes( ) } -data deploy_revision_persisted_outside_tree_note: String = "The deployed-revision record lives OUTSIDE the directory the deploy rsyncs into, and that placement is load-bearing rather than tidy. A marker inside the deployed tree is destroyed or overwritten by the very rsync whose outcome it is supposed to describe, so after a partial or interrupted deploy the record would describe a tree that was never fully written — the state the guard exists to detect becomes the state it cannot see. Keeping it beside the destination instead means the record is written ONLY after the service has restarted and the deployed surface has been read back, so a crash mid-deploy leaves the previous revision recorded and the next run correctly classifies the incomplete tree as still-at-the-old-revision rather than trusting a marker the failed run had already advanced." +// The deployed-revision record lives OUTSIDE the directory the deploy rsyncs into, and that +// placement is load-bearing rather than tidy. A marker inside the deployed tree is destroyed or +// overwritten by the very rsync whose outcome it is supposed to describe, so after a partial or +// interrupted deploy the record would describe a tree that was never fully written — the state the +// guard exists to detect becomes the state it cannot see. Keeping it beside the destination instead +// means the record is written ONLY after the service has restarted and the deployed surface has +// been read back, so a crash mid-deploy leaves the previous revision recorded and the next run +// correctly classifies the incomplete tree as still-at-the-old-revision rather than trusting a +// marker the failed run had already advanced. fn deploy_revision_state_path(target: DeployTargetId) -> FilePath { concat(concat("/var/lib/gunbc/deploy-revision/", target.service_unit as String), ".sha") as FilePath diff --git a/dag/gunbc/live_deploy/service_ready.dag b/dag/gunbc/live_deploy/service_ready.dag index da05ad98a84..0546bc602bd 100644 --- a/dag/gunbc/live_deploy/service_ready.dag +++ b/dag/gunbc/live_deploy/service_ready.dag @@ -31,7 +31,15 @@ type UnitDiagnosis = UnitDiagnosisUnavailable | UnitDiagnosisCaptured { text: String } -data live_deploy_unit_diagnosis_coproduct_note: String = "UnitDiagnosis is a coproduct, not a String with a reserved 'unavailable' value, and the distinction is the whole point of the field. A sentinel string makes 'nothing was captured' and 'the unit literally emitted the sentinel text' the SAME representation -- an illegal state that is representable, and one an equality test would silently misclassify the day a captured stdout happened to match. That is precisely the state-space conflation this module's diagnosis note invokes against folding unit_diagnosis into last_probe, so encoding absence as a magic string here would have contradicted the field's own reason for existing (caught in review of PR 7038, review 41182). Absence is a variant; rendering matches on it; no comparison against a magic value exists anywhere." +// UnitDiagnosis is a coproduct, not a String with a reserved 'unavailable' value, and the +// distinction is the whole point of the field. A sentinel string makes 'nothing was captured' and +// 'the unit literally emitted the sentinel text' the SAME representation -- an illegal state that +// is representable, and one an equality test would silently misclassify the day a captured stdout +// happened to match. That is precisely the state-space conflation this module's diagnosis note +// invokes against folding unit_diagnosis into last_probe, so encoding absence as a magic string +// here would have contradicted the field's own reason for existing (caught in review of PR 7038, +// review 41182). Absence is a variant; rendering matches on it; no comparison against a magic value +// exists anywhere. type ServiceNotReady { unit: NonEmptyStr @@ -41,7 +49,18 @@ type ServiceNotReady { unit_diagnosis: UnitDiagnosis } -data live_deploy_service_ready_unit_diagnosis_note: String = "unit_diagnosis carries the UNIT's own account of itself (systemctl status: Active state, main-PID exit code, recent journal lines), captured once when the poll bound is exhausted. It is a SEPARATE fact from last_probe and must not be folded into it: last_probe is what the health endpoint said (a curl-level observation from outside), unit_diagnosis is what the service said (a process-level observation from inside). Conflating them is the state-space conflation DESIGN 3 names -- 'the endpoint refused connection' and 'the process exited 1 while compiling' are different states whose remedies differ, and only the second locates the defect. Motivating incident: the srv1 deploy refused with ServiceNotReady on every run from belt B's merge (PR 6940) onward, and because the refusal carried only the curl exit the cause stayed unlocated for the whole window -- the line stopped, typed, but nobody could see WHY. gunbc serve has four distinct exit paths (source load, blocking diagnostics, no graph, bind failure) and the probe alone cannot tell them apart." +// unit_diagnosis carries the UNIT's own account of itself (systemctl status: Active state, main-PID +// exit code, recent journal lines), captured once when the poll bound is exhausted. It is a +// SEPARATE fact from last_probe and must not be folded into it: last_probe is what the health +// endpoint said (a curl-level observation from outside), unit_diagnosis is what the service said (a +// process-level observation from inside). Conflating them is the state-space conflation DESIGN 3 +// names -- 'the endpoint refused connection' and 'the process exited 1 while compiling' are +// different states whose remedies differ, and only the second locates the defect. Motivating +// incident: the srv1 deploy refused with ServiceNotReady on every run from belt B's merge (PR 6940) +// onward, and because the refusal carried only the curl exit the cause stayed unlocated for the +// whole window -- the line stopped, typed, but nobody could see WHY. gunbc serve has four distinct +// exit paths (source load, blocking diagnostics, no graph, bind failure) and the probe alone cannot +// tell them apart. data live_deploy_roadmap_unit_expected_startup: Second = second(count: 40) @@ -74,13 +93,30 @@ data live_deploy_host_sleep_generalization_dissolution_trigger: Disposition = Sc } } -data live_deploy_executor_pacing_sleep_frontier_note: String = "Slice 3 readiness poll paces via extdeps.tools.sleep.Delay.Seconds (executor-local modeled sleep authority). General HostSleep duration host_effect variant is deferred — live_deploy_host_sleep_generalization_dissolution_trigger; Srv3BootOnceReadBackSleep dissolves as a consumer when HostSleep lands." +// Slice 3 readiness poll paces via extdeps.tools.sleep.Delay.Seconds (executor-local modeled sleep +// authority). General HostSleep duration host_effect variant is deferred — +// live_deploy_host_sleep_generalization_dissolution_trigger; Srv3BootOnceReadBackSleep dissolves as +// a consumer when HostSleep lands. data live_deploy_service_ready_unit_authority: NonEmptyStr = gunbc_roadmap_unit_name data live_deploy_service_ready_endpoint_authority: NonEmptyStr = roadmap_site_health_path -data live_deploy_unit_diagnosis_capture_placement_note: String = "The capture is performed by the DEPLOY ENTRY (readiness.live_deploy_poll_until_service_ready_for_deploy), never by the recursive poller, and that placement is load-bearing rather than stylistic. The poller is driven directly by claim witnesses; the deploy access it is handed carries an SshShell transport pointed at srv1, so performing the capture inside the poller's exhaustion branch made a hermetic refusal-shape witness attempt a LIVE cross-host SSH from whatever runner executed the corpus. That is non-hermetic by construction and it duly behaved differently per environment -- green locally, and on srv2-03 it returned a value that blew a downstream match (PR 7038, first CI run). So the recursive poller stays free of the capture effect and builds its refusal with UnitDiagnosisUnavailable, which is the honest statement at that point: nothing has been captured yet. The entry, which only the real deploy reaches, performs the capture once on the refusal and rebuilds it through service_not_ready_with_captured_diagnosis. Rendering matches the variant and emits the segment only for UnitDiagnosisCaptured, so exactly one unit_diagnosis segment ever appears, it is never a placeholder masquerading as an observation, and there is ONE rendering authority rather than a second copy of the format string at the entry." +// The capture is performed by the DEPLOY ENTRY +// (readiness.live_deploy_poll_until_service_ready_for_deploy), never by the recursive poller, and +// that placement is load-bearing rather than stylistic. The poller is driven directly by claim +// witnesses; the deploy access it is handed carries an SshShell transport pointed at srv1, so +// performing the capture inside the poller's exhaustion branch made a hermetic refusal-shape +// witness attempt a LIVE cross-host SSH from whatever runner executed the corpus. That is +// non-hermetic by construction and it duly behaved differently per environment -- green locally, +// and on srv2-03 it returned a value that blew a downstream match (PR 7038, first CI run). So the +// recursive poller stays free of the capture effect and builds its refusal with +// UnitDiagnosisUnavailable, which is the honest statement at that point: nothing has been captured +// yet. The entry, which only the real deploy reaches, performs the capture once on the refusal and +// rebuilds it through service_not_ready_with_captured_diagnosis. Rendering matches the variant and +// emits the segment only for UnitDiagnosisCaptured, so exactly one unit_diagnosis segment ever +// appears, it is never a placeholder masquerading as an observation, and there is ONE rendering +// authority rather than a second copy of the format string at the entry. fn service_not_ready_reason(refusal: ServiceNotReady) -> String { let base = concat( diff --git a/dag/gunbc/live_deploy/spec.dag b/dag/gunbc/live_deploy/spec.dag index 40f02683960..3d1e167391f 100644 --- a/dag/gunbc/live_deploy/spec.dag +++ b/dag/gunbc/live_deploy/spec.dag @@ -48,11 +48,30 @@ import extdeps.http.server { HttpServerListenConfig } import product.network_topology { NetworkEndpoint } import gunbc.ownership { Ownership, Owned, Ensured } -data live_deploy_spec_belt_b_note: String = "Belt B membership (docs/plans/gunbc-served-dashboard-design.md section 5): the service is `gunbc serve` (the seed binary), not an emitted node server.js, so the members flip — ServeBinary + GunbcSourceTree + DispatchWorktreeRoot in (paths from the ONE gunbc.host_layout authority the belt also projects), ServerScript and the NodeJsPackage dependency out (the Ensured nodejs is never torn down — R5; the stale /opt/gunbc/server.js file is left inert on disk and reaped when belt A's interim retires). Deploy principal is ghrunner (DeployRunnerPrincipal.job_user; unit_user=root is NOT the authorization principal). #7011 realization: tree sync AS service user via gunbc-tree-sync.service; six roster sudo grants (Wave C adds visudo for dropin gate). Wave C envelope: in-script preflight + roster-emitted sudoers dropin (gunbc.live_deploy.emit deploy_apply_preamble_shell)." +// Belt B membership (docs/plans/gunbc-served-dashboard-design.md section 5): the service is `gunbc +// serve` (the seed binary), not an emitted node server.js, so the members flip — ServeBinary + +// GunbcSourceTree + DispatchWorktreeRoot in (paths from the ONE gunbc.host_layout authority the +// belt also projects), ServerScript and the NodeJsPackage dependency out (the Ensured nodejs is +// never torn down — R5; the stale /opt/gunbc/server.js file is left inert on disk and reaped when +// belt A's interim retires). Deploy principal is ghrunner (DeployRunnerPrincipal.job_user; +// unit_user=root is NOT the authorization principal). #7011 realization: tree sync AS service user +// via gunbc-tree-sync.service; six roster sudo grants (Wave C adds visudo for dropin gate). Wave C +// envelope: in-script preflight + roster-emitted sudoers dropin (gunbc.live_deploy.emit +// deploy_apply_preamble_shell). data gunbc_service_user: NonEmptyStr = "briansrls" -data attempt_state_root_member_note: String = "AttemptStateRoot is an owned member for the same reason DispatchWorktreeRoot is: the belt WRITES there, under the service user, on every dispatch. It was missing until 2026-07-30, and the gap was not visible as a gap — #7368 made a published attempt state a hard precondition of dispatch while the path lived only in gunbc.roadmap_dashboard_instance's srv1 row, so this roster had nothing to add it to. The result on the live host was EACCES from `install -d` against a root-owned /opt/gunbc, AFTER the worktree had been created: an orphan branch per click and a progress row reading `legacy attempt`. Ownership is Owned, not Ensured, because the deploy creates it; teardown mirrors DispatchWorktreeRoot's empty-directory refusal rather than a recursive remove, since a non-empty attempt-state root means published attempt evidence for sessions that may still be live (teardown_worktree_rmdir_note carries the same argument for the same reason)." +// AttemptStateRoot is an owned member for the same reason DispatchWorktreeRoot is: the belt WRITES +// there, under the service user, on every dispatch. It was missing until 2026-07-30, and the gap +// was not visible as a gap — #7368 made a published attempt state a hard precondition of dispatch +// while the path lived only in gunbc.roadmap_dashboard_instance's srv1 row, so this roster had +// nothing to add it to. The result on the live host was EACCES from `install -d` against a +// root-owned /opt/gunbc, AFTER the worktree had been created: an orphan branch per click and a +// progress row reading `legacy attempt`. Ownership is Owned, not Ensured, because the deploy +// creates it; teardown mirrors DispatchWorktreeRoot's empty-directory refusal rather than a +// recursive remove, since a non-empty attempt-state root means published attempt evidence for +// sessions that may still be live (teardown_worktree_rmdir_note carries the same argument for the +// same reason). type OwnedArtifactKind = ServeBinary @@ -72,7 +91,20 @@ type EnsuredDependencyKind = | DevbootArtifactStoreDirectory | CodexRuntimeReleaseDirectory -data ensured_subject_note: String = "ONE CONCEPT -- AN ENSURED HOST RESOURCE -- WITH N KINDS, not a second ensured mechanism beside the first. What Ensured means was already exactly right for the build service's artifact store: converged on every apply, never torn down, TeardownRefuses{TeardownOutsideOwnership}. What was wrong was that a dependency could only BE a package, so a directory had nowhere to live and got put in the owned roster instead -- where Owned means torn down by retract, and produced artifacts are the one thing a deploy must never delete.\\n\\nTHE HAZARD THAT RULED THIS, recorded because it was caught rather than reasoned: appending a host-level root to every per-instance spec made a lab twin's retract remove the production artifact store. Two existing claims went red on it. The claims were right and the placement was wrong, so the category moved and the claims were not touched.\\n\\nThe package arm keeps its exact prior behaviour -- same ensure step, same emitted bytes -- because this widening is about admitting a second KIND of ensured resource, not about changing what ensuring a package means. The directory arm carries its own owner rather than reading a deployment spec, so an ensured host resource stays a fact about the HOST and needs no instance to be stated." +// ONE CONCEPT -- AN ENSURED HOST RESOURCE -- WITH N KINDS, not a second ensured mechanism beside +// the first. What Ensured means was already exactly right for the build service's artifact store: +// converged on every apply, never torn down, TeardownRefuses{TeardownOutsideOwnership}. What was +// wrong was that a dependency could only BE a package, so a directory had nowhere to live and got +// put in the owned roster instead -- where Owned means torn down by retract, and produced artifacts +// are the one thing a deploy must never delete.\n\nTHE HAZARD THAT RULED THIS, recorded because it +// was caught rather than reasoned: appending a host-level root to every per-instance spec made a +// lab twin's retract remove the production artifact store. Two existing claims went red on it. The +// claims were right and the placement was wrong, so the category moved and the claims were not +// touched.\n\nThe package arm keeps its exact prior behaviour -- same ensure step, same emitted +// bytes -- because this widening is about admitting a second KIND of ensured resource, not about +// changing what ensuring a package means. The directory arm carries its own owner rather than +// reading a deployment spec, so an ensured host resource stays a fact about the HOST and needs no +// instance to be stated. type EnsuredSubject = EnsuredPackage { package: NonEmptyStr } @@ -106,7 +138,25 @@ fn deployment_step_ownership(step: DeploymentStep) -> Ownership { } } -data teardown_disposition_note: String = "WHETHER A MEMBER CAN BE TORN DOWN IS A FACT ABOUT THE MEMBER, so it lives here beside deployment_step_ownership rather than as a predicate in the emitter (review 45217). The emitter previously classified this by hand-walking three coproducts in one Boolean — MemberEffect, then DeploymentStep, then OwnedArtifactKind — which put a fact about the deployment model inside the thing that renders it, and encoded the model's storage shape as recursion in a reusable predicate. Adding an artifact kind then meant remembering to visit the emitter, and the answer was a bare true with no way to ask WHY.\\n\\nEVERY OWNED ARTIFACT NOW ACTS, and the second refusal cause is deleted with the claim that justified it. TailscaleServeMapping used to answer TeardownRefuses of TeardownWouldExceedOwnership on the premise that the CLI offers no per-port removal and only a node-global reset. That premise was false and uncited: the upstream documents scoped removal via `off` (extdeps.tailscale.serve, review 2026-07-30). Once the mapping is modeled as the endpoint tailscale actually keys on, its teardown replays that endpoint with `off` and removes exactly what this deployment created — so the arm is an ordinary teardown, not a refusal, and TeardownWouldExceedOwnership had exactly one inhabitant which was wrong.\\n\\nWhat survives is the refusal that was always correct and is about ownership rather than tooling: an ensured dependency is outside ownership entirely — tailscale and tmux were installed for the host, not for this deployment, so removing them is not this retract's business at any scope.\\n\\nThe disposition is per-layer and total, so a new OwnedArtifactKind cannot inherit a teardown answer by omission — the match fails to compile until the new kind states its own." +// WHETHER A MEMBER CAN BE TORN DOWN IS A FACT ABOUT THE MEMBER, so it lives here beside +// deployment_step_ownership rather than as a predicate in the emitter (review 45217). The emitter +// previously classified this by hand-walking three coproducts in one Boolean — MemberEffect, then +// DeploymentStep, then OwnedArtifactKind — which put a fact about the deployment model inside the +// thing that renders it, and encoded the model's storage shape as recursion in a reusable +// predicate. Adding an artifact kind then meant remembering to visit the emitter, and the answer +// was a bare true with no way to ask WHY.\n\nEVERY OWNED ARTIFACT NOW ACTS, and the second refusal +// cause is deleted with the claim that justified it. TailscaleServeMapping used to answer +// TeardownRefuses of TeardownWouldExceedOwnership on the premise that the CLI offers no per-port +// removal and only a node-global reset. That premise was false and uncited: the upstream documents +// scoped removal via `off` (extdeps.tailscale.serve, review 2026-07-30). Once the mapping is +// modeled as the endpoint tailscale actually keys on, its teardown replays that endpoint with `off` +// and removes exactly what this deployment created — so the arm is an ordinary teardown, not a +// refusal, and TeardownWouldExceedOwnership had exactly one inhabitant which was wrong.\n\nWhat +// survives is the refusal that was always correct and is about ownership rather than tooling: an +// ensured dependency is outside ownership entirely — tailscale and tmux were installed for the +// host, not for this deployment, so removing them is not this retract's business at any +// scope.\n\nThe disposition is per-layer and total, so a new OwnedArtifactKind cannot inherit a +// teardown answer by omission — the match fails to compile until the new kind states its own. type TeardownRefusalCause = | TeardownOutsideOwnership @@ -137,7 +187,22 @@ fn deployment_step_teardown_disposition(step: DeploymentStep) -> TeardownDisposi } } -data target_carries_the_observed_client_note: String = "SCOPED TEARDOWN IS A PROPERTY OF THE CLIENT ON THIS MACHINE, so it is a field of the target rather than a constant in the emitter. The tailscale Serve CLI CHANGED in client 1.52, so `serve off` is available on some hosts and not others, and the previous code answered the question with a data row asserting it for every host at every time.\\n\\nsrv1 was measured at 1.98.3 and its row says so, with the measurement carried as the observed string rather than as a Bool — a verdict that has forgotten what it was computed from cannot be re-checked when the host is upgraded. srv2 and srv3 are ScopedRemovalUnobserved, which is NOT a claim that they lack the capability: it is the statement that nobody looked. Collapsing that to either pole is the two-states-one-label defect this whole change is repairing, and asserting the fleet from one measured host is the same shape as the claim being retracted (operator, 2026-07-30).\\n\\nTHE REFUSAL FIRES AT EMIT, NOT AT RUN. A retract script is simply not produced for a target whose client is unobserved or too old, so there is no arm in the emitted shell that could stop the line halfway through a teardown, and no ordering question about where such an arm would sit. Refusing before mutation is what the operator required, and refusing before EMISSION is the construction-side reading of it: the dangerous artifact does not exist rather than existing and declining to run." +// SCOPED TEARDOWN IS A PROPERTY OF THE CLIENT ON THIS MACHINE, so it is a field of the target +// rather than a constant in the emitter. The tailscale Serve CLI CHANGED in client 1.52, so `serve +// off` is available on some hosts and not others, and the previous code answered the +// question with a data row asserting it for every host at every time.\n\nsrv1 was measured at +// 1.98.3 and its row says so, with the measurement carried as the observed string rather than as a +// Bool — a verdict that has forgotten what it was computed from cannot be re-checked when the host +// is upgraded. srv2 and srv3 are ScopedRemovalUnobserved, which is NOT a claim that they lack the +// capability: it is the statement that nobody looked. Collapsing that to either pole is the +// two-states-one-label defect this whole change is repairing, and asserting the fleet from one +// measured host is the same shape as the claim being retracted (operator, 2026-07-30).\n\nTHE +// REFUSAL FIRES AT EMIT, NOT AT RUN. A retract script is simply not produced for a target whose +// client is unobserved or too old, so there is no arm in the emitted shell that could stop the line +// halfway through a teardown, and no ordering question about where such an arm would sit. Refusing +// before mutation is what the operator required, and refusing before EMISSION is the +// construction-side reading of it: the dangerous artifact does not exist rather than existing and +// declining to run. type DeploymentHostTarget { host: ComputeHost @@ -148,7 +213,17 @@ type DeploymentHostTarget { tailscale_client: TailscaleServeCapability } -data deployment_service_handler_note: String = "serve_function WAS DROPPED HERE AND THE EMITTER FELL BACK TO A LITERAL. HostDashboardInstance carries it — the instance IS the choice of which handler answers — and this record simply did not have the field, so emit_systemd_unit_doc hardcoded --function roadmap_serve_handle. That literal binds srv1_live_dashboard_instance().\\n\\nThe consequence is worse than a wrong flag. A deployed srv1_lab twin would listen on the lab port, work out of the lab tree, own the lab unit — and then IDENTIFY AND OPERATE AS PRODUCTION, because the handler is what selects the instance. Every path-level check passes; the process is simply serving the wrong dashboard. A path-disjointness witness cannot see it, which is exactly why the twin proof had to move from declared members to emitted effects (review, 2026-07-30).\\n\\nCarrying it makes the fallback impossible rather than unlikely: the emitter has no literal to reach for, because the field it needs is only obtainable from the instance." +// serve_function WAS DROPPED HERE AND THE EMITTER FELL BACK TO A LITERAL. HostDashboardInstance +// carries it — the instance IS the choice of which handler answers — and this record simply did not +// have the field, so emit_systemd_unit_doc hardcoded --function roadmap_serve_handle. That literal +// binds srv1_live_dashboard_instance().\n\nThe consequence is worse than a wrong flag. A deployed +// srv1_lab twin would listen on the lab port, work out of the lab tree, own the lab unit — and then +// IDENTIFY AND OPERATE AS PRODUCTION, because the handler is what selects the instance. Every +// path-level check passes; the process is simply serving the wrong dashboard. A path-disjointness +// witness cannot see it, which is exactly why the twin proof had to move from declared members to +// emitted effects (review, 2026-07-30).\n\nCarrying it makes the fallback impossible rather than +// unlikely: the emitter has no literal to reach for, because the field it needs is only obtainable +// from the instance. type DeploymentServiceConfig { unit_name: NonEmptyStr @@ -187,7 +262,18 @@ type DeploymentServiceConfig { // while still holding the listener. The measured serve cost is mostly the SECOND kind: a // jq subprocess over the attempt event files, which is wait rather than CPU and would // never cross a CPU cap. -data dashboard_serve_budget_basis_note: String = "MEASURED BASIS FOR THE DECLARED CAP (session crisp-gull-269, 2026-08-10, replay against a local copy of srv1 attempt-state). Per request on the daily page: render evaluation 840ms self-time; jq provider projection 486ms across 25,744,152 B spanning five current attempts; ~1.5s total. The largest retained attempt (60,257,673 B, the 2026-08-08 wedge artifact) is roughly three times the jq term, putting a realistic worst case near 2.5-3s. The declared caps are an order of magnitude above that so a legitimate render is never refused, while still converting an unbounded listener hold into a bounded typed refusal. THE ASYMMETRY THAT SETS THE DIRECTION: today's failure mode is a silent multi-hour hang observable only to whoever eventually restarts the process; a too-tight cap fails as an immediate, typed, visible 500 on one request. Those are not equally bad, which argues for arming a defensible cap now and refining it against this basis rather than converging on a perfect number first. VALUE IS OPERATOR POLICY: these numbers are a proposal carrying their basis, not a derivation that forecloses the operator's choice." +// MEASURED BASIS FOR THE DECLARED CAP (session crisp-gull-269, 2026-08-10, replay against a local +// copy of srv1 attempt-state). Per request on the daily page: render evaluation 840ms self-time; jq +// provider projection 486ms across 25,744,152 B spanning five current attempts; ~1.5s total. The +// largest retained attempt (60,257,673 B, the 2026-08-08 wedge artifact) is roughly three times the +// jq term, putting a realistic worst case near 2.5-3s. The declared caps are an order of magnitude +// above that so a legitimate render is never refused, while still converting an unbounded listener +// hold into a bounded typed refusal. THE ASYMMETRY THAT SETS THE DIRECTION: today's failure mode is +// a silent multi-hour hang observable only to whoever eventually restarts the process; a too-tight +// cap fails as an immediate, typed, visible 500 on one request. Those are not equally bad, which +// argues for arming a defensible cap now and refining it against this basis rather than converging +// on a perfect number first. VALUE IS OPERATOR POLICY: these numbers are a proposal carrying their +// basis, not a derivation that forecloses the operator's choice. fn dashboard_serve_cpu_limit() -> EvaluationLimit { LimitSet { limit: positive_millisecond(count: positive_measure_count(predecessor: 29999)) } @@ -197,13 +283,57 @@ fn dashboard_serve_wall_limit() -> EvaluationLimit { LimitSet { limit: positive_millisecond(count: positive_measure_count(predecessor: 29999)) } } -data deployment_spec_carries_no_release_note: String = "THE SPEC ANSWERS WHERE, THE CANDIDATE ANSWERS WHAT, AND THE FIELD THAT FUSED THEM IS GONE. DeploymentServiceConfig briefly carried release_revision, which put a per-invocation fact inside the slot topology — and the tell was visible on the retract path: a retract installs no release, yet it had to construct a DeploymentSpec and therefore had to name a revision it never read. A field that is meaningful on one path and merely along for the ride on another is not a model of either.\\n\\nEvery field that remains here is stable across deploys of this slot: which unit name, which binary, which handler, which root, which principal, which port. Deploying blue twice at two different commits produces the SAME DeploymentServiceConfig both times, which is what makes it a topology rather than a log.\\n\\nThe launch identity now travels beside the spec as gunbc.live_deploy.emit ReleaseRevisionBinding, supplied at the apply seam and read only where ExecStart is rendered. The retract fold passes RevisionNotInstalled and can therefore not name a revision even by accident — the arm that would render one refuses loudly instead of emitting a plausible line." - -data deployment_placement_axis_note: String = "A DEPLOYMENT IS (WHAT × WHERE), AND THIS MODULE USED TO FUSE THEM. deployment_owned_steps and deployment_service_config were NULLARY — zero arguments — so they returned THE deployment rather than A deployment, and gunbc_roadmap_unit_name was a single value naming the only unit that could ever exist. The consequence is sharper than 'srv1 is hardcoded': a second deployment was not hard to add, it was UNEXPRESSIBLE. A twin on srv1 would have collided on the unit name, the unit path, the binary path, the repo root and port 8080, every one of them a lone data row.\\n\\nThat framing hid the real axis. Host is not what varies — INSTANCE is, and host is one of its fields. Deploying twice on one machine and once on each of two machines are the same operation with different arguments, and any model where the first is impossible has picked the wrong parameter.\\n\\nThe correct placement model already existed and this module referenced it zero times. HostDashboardInstance carries instance_root, repo_root, serve_binary, dispatch_worktree_root, listen_host, listen_port, serve_function and instance_id, and it already proves the point: srv1_live on 8080 and srv1_lab on 18081 are two instances on ONE host with disjoint roots and distinct named tmux servers. So this is a de-fork onto an existing authority, not new modeling — two placement models existed, one was right, and the deploy path consumed neither.\\n\\nUnit and tailscale-mapping names travel together as DeploymentNames, supplied by the caller. deployment_unit_naming_note explains why they are passed rather than derived, and why that is a deliberate stop short of the stronger model rather than the model itself." +// THE SPEC ANSWERS WHERE, THE CANDIDATE ANSWERS WHAT, AND THE FIELD THAT FUSED THEM IS GONE. +// DeploymentServiceConfig briefly carried release_revision, which put a per-invocation fact inside +// the slot topology — and the tell was visible on the retract path: a retract installs no release, +// yet it had to construct a DeploymentSpec and therefore had to name a revision it never read. A +// field that is meaningful on one path and merely along for the ride on another is not a model of +// either.\n\nEvery field that remains here is stable across deploys of this slot: which unit name, +// which binary, which handler, which root, which principal, which port. Deploying blue twice at two +// different commits produces the SAME DeploymentServiceConfig both times, which is what makes it a +// topology rather than a log.\n\nThe launch identity now travels beside the spec as +// gunbc.live_deploy.emit ReleaseRevisionBinding, supplied at the apply seam and read only where +// ExecStart is rendered. The retract fold passes RevisionNotInstalled and can therefore not name a +// revision even by accident — the arm that would render one refuses loudly instead of emitting a +// plausible line. + +// A DEPLOYMENT IS (WHAT × WHERE), AND THIS MODULE USED TO FUSE THEM. deployment_owned_steps and +// deployment_service_config were NULLARY — zero arguments — so they returned THE deployment rather +// than A deployment, and gunbc_roadmap_unit_name was a single value naming the only unit that could +// ever exist. The consequence is sharper than 'srv1 is hardcoded': a second deployment was not hard +// to add, it was UNEXPRESSIBLE. A twin on srv1 would have collided on the unit name, the unit path, +// the binary path, the repo root and port 8080, every one of them a lone data row.\n\nThat framing +// hid the real axis. Host is not what varies — INSTANCE is, and host is one of its fields. +// Deploying twice on one machine and once on each of two machines are the same operation with +// different arguments, and any model where the first is impossible has picked the wrong +// parameter.\n\nThe correct placement model already existed and this module referenced it zero +// times. HostDashboardInstance carries instance_root, repo_root, serve_binary, +// dispatch_worktree_root, listen_host, listen_port, serve_function and instance_id, and it already +// proves the point: srv1_live on 8080 and srv1_lab on 18081 are two instances on ONE host with +// disjoint roots and distinct named tmux servers. So this is a de-fork onto an existing authority, +// not new modeling — two placement models existed, one was right, and the deploy path consumed +// neither.\n\nUnit and tailscale-mapping names travel together as DeploymentNames, supplied by the +// caller. deployment_unit_naming_note explains why they are passed rather than derived, and why +// that is a deliberate stop short of the stronger model rather than the model itself. data deployment_unit_naming_note: String = "THE UNIT NAME IS PASSED, NOT DERIVED, AND THAT IS A DELIBERATE STOP RATHER THAN THE WEAKER MODEL. Deriving it from instance_id is what this module should eventually do — it is what dashboard_instance_service_session_name already does for the tmux session, and derivation is what makes a collision unwritable instead of merely unlikely. But srv1-live is RUNNING as gunbc-roadmap.service right now, and instance_id srv1-live derives gunbc-srv1-live.service. Deriving here would rename a live unit as a side effect of an abstraction change: the next apply installs the new unit while the old one keeps serving, and retract then targets the new path and orphans the running process.\\n\\nSo the caller states them. srv1_live_deployment_names preserves production byte-identically and carries the reason with it; derived_deployment_names is the shape new instances use, and it is a real function rather than a comment so the second deployment does not re-litigate the question. Nothing inherits a default silently — deployment_spec_for takes identity as a required parameter.\\n\\nDISSOLVES ON: a scheduled unit rename for srv1-live (stop old, install derived, verify health, remove old), after which srv1 passes derived_deployment_names like everyone else and this row deletes. Until then the collision guard is the witness that every declared spec has a distinct unit name, path and port — validation standing where a construction wall belongs, and marked as such rather than presented as the wall." -data tailscale_serve_path_was_not_an_endpoint_note: String = "THIS FIELD USED TO BE A NAME NOTHING CONFIGURED, and that is the whole of the twin-collision defect. tailscale_serve_path held \\\"tailscale-serve\\\" for production and \\\"tailscale-serve-srv1-lab\\\" for the twin — two distinct strings, which is exactly what the member-path disjointness witness checked and exactly why it passed. The emitter referenced the field zero times. It emitted `tailscale serve --bg ` from the port alone, and a bare serve binds the DEFAULT HTTPS listener at the ROOT mount, so both deployments configured the SAME endpoint and the twin's apply silently repointed production's routing at the lab (review, 2026-07-30).\\n\\nSo the disjointness was real and meaningless: the model's identity axis and tailscale's configuration key were unrelated quantities, and no witness over the former could observe a collision in the latter. This is not a check that was too weak — it is a check of the wrong thing, which no strengthening would have fixed.\\n\\nThe field is now the endpoint ITSELF, imported from extdeps.tailscale.serve where the upstream key is modeled. Two consequences follow by construction rather than by witness: the emitter cannot fall back to a port-only spelling because the port-only operation no longer exists, and the member path is a PROJECTION of the endpoint (tailscale_serve_endpoint_key), so member-path disjointness and endpoint disjointness became the same statement instead of two that could disagree." +// THIS FIELD USED TO BE A NAME NOTHING CONFIGURED, and that is the whole of the twin-collision +// defect. tailscale_serve_path held \"tailscale-serve\" for production and +// \"tailscale-serve-srv1-lab\" for the twin — two distinct strings, which is exactly what the +// member-path disjointness witness checked and exactly why it passed. The emitter referenced the +// field zero times. It emitted `tailscale serve --bg ` from the port alone, and a bare serve +// binds the DEFAULT HTTPS listener at the ROOT mount, so both deployments configured the SAME +// endpoint and the twin's apply silently repointed production's routing at the lab (review, +// 2026-07-30).\n\nSo the disjointness was real and meaningless: the model's identity axis and +// tailscale's configuration key were unrelated quantities, and no witness over the former could +// observe a collision in the latter. This is not a check that was too weak — it is a check of the +// wrong thing, which no strengthening would have fixed.\n\nThe field is now the endpoint ITSELF, +// imported from extdeps.tailscale.serve where the upstream key is modeled. Two consequences follow +// by construction rather than by witness: the emitter cannot fall back to a port-only spelling +// because the port-only operation no longer exists, and the member path is a PROJECTION of the +// endpoint (tailscale_serve_endpoint_key), so member-path disjointness and endpoint disjointness +// became the same statement instead of two that could disagree. // THE PUBLICATION PRINCIPAL IS A HOST ALLOCATION THIS GRAPH CARRIES, NEVER ONE IT MINTS. // name_requirements_authority_note in gunbc.fleet_posix_accounts records the reverted attempt to @@ -329,7 +459,6 @@ fn deployment_credential_env_path(names: DeploymentNames) -> NonEmptyStr { join(["/etc/", deployment_belt_unit_stem(names: names), "-publication-credentials.env"], "") as NonEmptyStr } - // THE DEPLOYED TREE'S REMOTE IS A MEMBER, and it needs a unit of its own for an ordering reason // rather than an aesthetic one. The convergence must run AS THE SERVICE USER -- the tree is // service-user-owned and the deploy principal is the unprivileged ci-runner (emit_deploy_principal_note), @@ -396,7 +525,19 @@ fn deployment_fleet_converge_timer_unit_path(names: DeploymentNames) -> NonEmpty data tailscale_default_https_port: Port = 443 -data derived_mount_is_the_instance_namespace_note: String = "THE MOUNT PATH IS NAMESPACED BY instance_id, which is the same axis that already namespaces the instance root, the repo root, the serve binary, the listen port, the worktree root, the unit name and the tmux session. The twin's endpoint is /srv1-lab because the twin IS srv1-lab, so two instances cannot produce one endpoint without sharing an identity the instance registry already forbids — the collision is unwritable rather than merely witnessed (operator: use literal namespacing in the repo, 2026-07-30).\\n\\nHand-picking a mount would have re-created the defect this replaces. tailscale_serve_path was hand-picked too, and being distinct is precisely what it was: distinct, unconsumed, and unrelated to the thing tailscale keys on. A second hand-picked string differs only in being the right KIND of string, and the next instance would re-litigate it.\\n\\nDISSOLVES ON the containment tree becoming the naming authority (namespace-only resolution): instance_id is itself a flat identifier, so this derivation is namespacing by string prefix where namespacing by position is the end state. The mount then projects from the instance's position rather than from its name, and this function stops spelling a separator." +// THE MOUNT PATH IS NAMESPACED BY instance_id, which is the same axis that already namespaces the +// instance root, the repo root, the serve binary, the listen port, the worktree root, the unit name +// and the tmux session. The twin's endpoint is /srv1-lab because the twin IS srv1-lab, so two +// instances cannot produce one endpoint without sharing an identity the instance registry already +// forbids — the collision is unwritable rather than merely witnessed (operator: use literal +// namespacing in the repo, 2026-07-30).\n\nHand-picking a mount would have re-created the defect +// this replaces. tailscale_serve_path was hand-picked too, and being distinct is precisely what it +// was: distinct, unconsumed, and unrelated to the thing tailscale keys on. A second hand-picked +// string differs only in being the right KIND of string, and the next instance would re-litigate +// it.\n\nDISSOLVES ON the containment tree becoming the naming authority (namespace-only +// resolution): instance_id is itself a flat identifier, so this derivation is namespacing by string +// prefix where namespacing by position is the end state. The mount then projects from the +// instance's position rather than from its name, and this function stops spelling a separator. fn derived_deployment_names(instance: HostDashboardInstance) -> DeploymentNames { DeploymentNames { @@ -412,10 +553,32 @@ fn derived_deployment_names(instance: HostDashboardInstance) -> DeploymentNames } } -data srv1_live_deployment_names_note: String = "PRODUCTION'S NAMES AS THEY ACTUALLY ARE ON THE MACHINE, not as the derivation would mint them. srv1-live runs as gunbc-roadmap.service and owns the tailscale mapping named tailscale-serve; the derivation produces gunbc-srv1-live.service and tailscale-serve-srv1-live. Both differences would be silent renames — the apply installs under the new name while the old unit keeps serving, and retract then removes the new one and orphans the running process. Neither belongs in a change whose purpose is to make placement a parameter." +// PRODUCTION'S NAMES AS THEY ACTUALLY ARE ON THE MACHINE, not as the derivation would mint them. +// srv1-live runs as gunbc-roadmap.service and owns the tailscale mapping named tailscale-serve; the +// derivation produces gunbc-srv1-live.service and tailscale-serve-srv1-live. Both differences would +// be silent renames — the apply installs under the new name while the old unit keeps serving, and +// retract then removes the new one and orphans the running process. Neither belongs in a change +// whose purpose is to make placement a parameter. data gunbc_roadmap_unit_name: NonEmptyStr = "gunbc-roadmap.service" -data production_keeps_the_root_mount_note: String = "PRODUCTION STAYS ON THE ROOT MOUNT, and it is the one place a derived mount would be wrong. srv1-live is serving https://srv1.tailecbe08.ts.net/ today; giving it --set-path=/srv1-live would move the dashboard to a different URL as a side effect of an abstraction change, which is the same silent-rename hazard deployment_unit_naming_note refuses for the unit name and refuses here for the same reason.\\n\\nThe root mount is therefore a DECLARED PLACEMENT for production and derived for everyone else — the identical shape, and the identical stop, as gunbc-roadmap.service.\\n\\nThe backend is NOT transcribed. It reads the instance's listen_port, so this row states only what genuinely differs from the derivation (the mount) and inherits everything else; a literal 8080 here would be a live-state transcription of the kind swept in #7464. That is why this became a function of the instance rather than staying a nullary data row.\\n\\nWHAT DOES CHANGE ON THE WIRE: the emitted command gains an explicit --https=443 where it previously passed the port alone. That is not byte-preservation and is not claimed as such. It is required, because the upstream `off` form demands every original flag, so a mapping applied without the listener flag cannot state the listener flag when removing itself. 443 is the listener production already serves on, so the resulting mapping is the one that is live.\\n\\nDISSOLVES ON the same scheduled migration as the unit name: once srv1-live moves to its derived names it takes a derived mount with them, and this function collapses into derived_deployment_names." +// PRODUCTION STAYS ON THE ROOT MOUNT, and it is the one place a derived mount would be wrong. +// srv1-live is serving https://srv1.tailecbe08.ts.net/ today; giving it --set-path=/srv1-live would +// move the dashboard to a different URL as a side effect of an abstraction change, which is the +// same silent-rename hazard deployment_unit_naming_note refuses for the unit name and refuses here +// for the same reason.\n\nThe root mount is therefore a DECLARED PLACEMENT for production and +// derived for everyone else — the identical shape, and the identical stop, as +// gunbc-roadmap.service.\n\nThe backend is NOT transcribed. It reads the instance's listen_port, so +// this row states only what genuinely differs from the derivation (the mount) and inherits +// everything else; a literal 8080 here would be a live-state transcription of the kind swept in +// #7464. That is why this became a function of the instance rather than staying a nullary data +// row.\n\nWHAT DOES CHANGE ON THE WIRE: the emitted command gains an explicit --https=443 where it +// previously passed the port alone. That is not byte-preservation and is not claimed as such. It is +// required, because the upstream `off` form demands every original flag, so a mapping applied +// without the listener flag cannot state the listener flag when removing itself. 443 is the +// listener production already serves on, so the resulting mapping is the one that is +// live.\n\nDISSOLVES ON the same scheduled migration as the unit name: once srv1-live moves to its +// derived names it takes a derived mount with them, and this function collapses into +// derived_deployment_names. fn srv1_live_deployment_names(instance: HostDashboardInstance) -> DeploymentNames { DeploymentNames { @@ -431,9 +594,13 @@ fn srv1_live_deployment_names(instance: HostDashboardInstance) -> DeploymentName } } - - -data gunbc_tree_sync_note: String = "gunbc-tree-sync.service is the GunbcSourceTree member's sync TRANSPORT, not an independent member: a oneshot unit running AS the service user (the tree's owner writes its own tree — ownership by construction, no chown), installed/started by the deploy through the granted install+systemctl channel because the ci-runner principal holds no rsync grant and no write access to the service-user-owned tree (emit_deploy_principal_note). Its EnvironmentFile carries the runner checkout path (varies per runner slot), written by the apply at run time. Both files are owned host state and are torn down with the tree in the GunbcSourceTree teardown arm." +// gunbc-tree-sync.service is the GunbcSourceTree member's sync TRANSPORT, not an independent +// member: a oneshot unit running AS the service user (the tree's owner writes its own tree — +// ownership by construction, no chown), installed/started by the deploy through the granted +// install+systemctl channel because the ci-runner principal holds no rsync grant and no write +// access to the service-user-owned tree (emit_deploy_principal_note). Its EnvironmentFile carries +// the runner checkout path (varies per runner slot), written by the apply at run time. Both files +// are owned host state and are torn down with the tree in the GunbcSourceTree teardown arm. data tree_sync_was_a_host_singleton_note: String = "THE SYNC TRANSPORT WAS ONE SET OF FILES FOR THE WHOLE HOST, and it never appeared in the five-member footprint the twin witness compares. gunbc-tree-sync.service and /etc/gunbc-tree-sync.env were fixed literals, so a twin apply OVERWROTE both with its own tree destination and restarted the shared unit — pointing production's sync at the lab's tree — and a twin retract DELETED both, disabling production's sync entirely. Neither effect is a path in DeploymentArtifactStep, so a claim over declared members could not see either (review, 2026-07-30).\\n\\nThey are derived from the deployment identity now, for the same reason the unit and binary are: the collision becomes unwritable rather than unlikely, because two deployments cannot produce one filename without sharing an identity the registry already forbids. gunbc_tree_sync_note calls these files owned host state torn down with the tree; that description is only true once they are per-deployment, and it was describing an intent the literals contradicted.\\n\\nThe names still derive from the SERVICE unit name rather than from instance_id directly, so srv1-live's migration-debt unit name carries through to its sync files and its emitted paths stay byte-identical." data gunbc_tree_sync_unit_name: NonEmptyStr = "gunbc-tree-sync.service" @@ -459,9 +626,23 @@ type DeploymentSpec { names: DeploymentNames } - - -data deployment_artifact_route_identity_note: String = "TWO CONSUMERS, ONE SCOPE-FREE ROUTE LABEL (KEYING-0 follow-on, node://adhoc-907f1da5-c20): TailscaleServeMapping.path is tailscale_serve_route_member_path — tailscale_serve_route_key_label(tailscale_serve_route_key(endpoint)), the route key WITHOUT its TailscaleServeScope node. deployment_step_key and spec_owned_paths both read art.path, so the serve_route_key Optional field and its emit dispatch are deleted from DeploymentArtifactStep.\\n\\nHONEST PRECONDITION (not a defect today): a DeploymentSpec is bound to one host, so within a spec the node is constant and a node-free route label is sufficient for membership reconcile and twin disjointness. The twin-safety witnesses pin deployment_plan_host_identity(live) == deployment_plan_host_identity(twin), so owned-path disjointness is same-host by construction. This path is NOT node-qualified: the first consumer that compares owned paths ACROSS hosts — fleet-wide reconcile, multi-host teardown — will treat two hosts serving the same listener×mount as one identity and report a collision that is not one. extdeps.tailscale.serve scopes the relation; live_deploy does not yet thread scope into art.path.\\n\\nDELIBERATE DECISION: art.path stays scope-free for this PR. Scoped identity in art.path would require deployment_step_key (and emit's plain membership_reconcile) to carry ScopedKey rather than NonEmptyStr — a wider change than unifying the two projections. Document the precondition now; revisit scoped art.path when a cross-host consumer exists." +// TWO CONSUMERS, ONE SCOPE-FREE ROUTE LABEL (KEYING-0 follow-on, node://adhoc-907f1da5-c20): +// TailscaleServeMapping.path is tailscale_serve_route_member_path — +// tailscale_serve_route_key_label(tailscale_serve_route_key(endpoint)), the route key WITHOUT its +// TailscaleServeScope node. deployment_step_key and spec_owned_paths both read art.path, so the +// serve_route_key Optional field and its emit dispatch are deleted from +// DeploymentArtifactStep.\n\nHONEST PRECONDITION (not a defect today): a DeploymentSpec is bound to +// one host, so within a spec the node is constant and a node-free route label is sufficient for +// membership reconcile and twin disjointness. The twin-safety witnesses pin +// deployment_plan_host_identity(live) == deployment_plan_host_identity(twin), so owned-path +// disjointness is same-host by construction. This path is NOT node-qualified: the first consumer +// that compares owned paths ACROSS hosts — fleet-wide reconcile, multi-host teardown — will treat +// two hosts serving the same listener×mount as one identity and report a collision that is not one. +// extdeps.tailscale.serve scopes the relation; live_deploy does not yet thread scope into +// art.path.\n\nDELIBERATE DECISION: art.path stays scope-free for this PR. Scoped identity in +// art.path would require deployment_step_key (and emit's plain membership_reconcile) to carry +// ScopedKey rather than NonEmptyStr — a wider change than unifying the two projections. Document +// the precondition now; revisit scoped art.path when a cross-host consumer exists. fn deployment_owned_steps( instance: HostDashboardInstance, @@ -489,9 +670,24 @@ fn deployment_owned_steps( ] } - -data devboot_root_membership_note: String = "THE BUILD SERVICE'S ROOTS ARE MEMBERSHIP, PROJECTED, AND SEPARATE FROM THE PER-INSTANCE STEPS -- three claims, and the third is the one that took a correction. Membership: these are directories the service WRITES, which is the same property that makes DispatchWorktreeRoot and AttemptStateRoot owned members, and attempt_state_root_member_note records what it cost to learn that a written path outside the roster is a path nothing creates. Projected: the paths come from the gunbc.host_layout rows, never spelled here, so the belt and the deploy cannot drift to two spellings of one directory.\\n\\nSEPARATE, because the first cut put them in deployment_owned_steps beside the dashboard's own members and that was wrong in a way the srv1-only scope hides. Those steps are built PER INSTANCE, and every instance would then have received srv1's literal paths under its own name -- srv2's preview slot creating /var/lib/gunbc directories for a service that does not run there. gunbc_host_layout_note names that exact defect for the exact reason it is dangerous: no value is visibly wrong, so nothing surfaces, and the first host whose layout genuinely diverged would silently inherit srv1's body.\\n\\nSo the projection is its own function, called for the host that actually runs the build service, rather than folded into a per-instance list that would answer for hosts nobody asked about. Dissolve-on: devboot deployed to a second host. At that point the roots become fields of a host layout resolved through a total host-identity lookup -- the next-rung trigger dispatch_state_scope_carries_its_roots_note already names -- and this srv1-shaped function retires into that derivation." - +// THE BUILD SERVICE'S ROOTS ARE MEMBERSHIP, PROJECTED, AND SEPARATE FROM THE PER-INSTANCE STEPS -- +// three claims, and the third is the one that took a correction. Membership: these are directories +// the service WRITES, which is the same property that makes DispatchWorktreeRoot and +// AttemptStateRoot owned members, and attempt_state_root_member_note records what it cost to learn +// that a written path outside the roster is a path nothing creates. Projected: the paths come from +// the gunbc.host_layout rows, never spelled here, so the belt and the deploy cannot drift to two +// spellings of one directory.\n\nSEPARATE, because the first cut put them in deployment_owned_steps +// beside the dashboard's own members and that was wrong in a way the srv1-only scope hides. Those +// steps are built PER INSTANCE, and every instance would then have received srv1's literal paths +// under its own name -- srv2's preview slot creating /var/lib/gunbc directories for a service that +// does not run there. gunbc_host_layout_note names that exact defect for the exact reason it is +// dangerous: no value is visibly wrong, so nothing surfaces, and the first host whose layout +// genuinely diverged would silently inherit srv1's body.\n\nSo the projection is its own function, +// called for the host that actually runs the build service, rather than folded into a per-instance +// list that would answer for hosts nobody asked about. Dissolve-on: devboot deployed to a second +// host. At that point the roots become fields of a host layout resolved through a total +// host-identity lookup -- the next-rung trigger dispatch_state_scope_carries_its_roots_note already +// names -- and this srv1-shaped function retires into that derivation. fn deployment_ensured_steps() -> List { [ @@ -533,9 +729,35 @@ fn deployment_steps_apply_order( ) } -data devboot_root_apply_membership_note: String = "THE STORE ROOT IS AN ENSURED HOST RESOURCE, NOT AN OWNED ARTIFACT, and the route to that answer is worth keeping because the obvious fix was the dangerous one. Review asked for devboot_owned_steps to be wired into deployment_steps_apply_order. That was done, and two existing claims went red immediately: a_twin_deployment_on_one_host_collides_with_nothing and twin_teardown_removes_nothing_production_owns. The second is not a technicality -- appending a host-level root to every per-instance spec means a lab twin retract would rm the PRODUCTION artifact store.\\n\\nThe claims were right and the placement was wrong. Owned means torn down by retract, and produced artifacts are exactly what a deploy must never delete -- devboot_root_realization_note argued this while the root sat in the owned roster anyway. So the root moved to the ensured side, where converged-on-apply and never-torn-down are what the category already means, and neither claim was edited. The rejected alternative was to narrow those two claims to per-instance ownership so a host-shared resource fell outside them; it was refused because editing a safety claim to accommodate a change is the DESIGN section 5 tell, and a claim that has just caught a real hazard is not the thing to weaken (operator ruling, 2026-08-08).\\n\\nThe service still runs git init --bare itself, which remains correct and is not made redundant: the deploy ensures the DIRECTORY exists and is owned, and only the service can turn a directory into a repository, because git is not on the deploy principal sudo roster." - -data deployment_retract_order_derivation_note: String = "RETRACT IS THE REVERSE OF WHAT THIS SPEC APPLIES, derived from the spec rather than transcribed beside it. It used to be a second hand-written list repeating the same five steps in opposite order — one fact in two places, so editing either alone makes them disagree, and the disagreement surfaces as a teardown that removes the binary while the unit still references it. deployment_apply_order_note states the ordering law in prose; this computes it.\\n\\nDeriving from spec.steps rather than re-deriving from instance and identity matters: it makes the retract order the reverse of THE ORDER THIS SPEC ACTUALLY APPLIED, not the reverse of what a freshly built spec would apply. Those differ exactly when a spec is constructed with something other than the standard order, which is the case a hand-mirrored list gets wrong. Ensured dependencies drop out because they are never torn down (R5) — the filter states that, rather than a comment claiming it." +// THE STORE ROOT IS AN ENSURED HOST RESOURCE, NOT AN OWNED ARTIFACT, and the route to that answer +// is worth keeping because the obvious fix was the dangerous one. Review asked for +// devboot_owned_steps to be wired into deployment_steps_apply_order. That was done, and two +// existing claims went red immediately: a_twin_deployment_on_one_host_collides_with_nothing and +// twin_teardown_removes_nothing_production_owns. The second is not a technicality -- appending a +// host-level root to every per-instance spec means a lab twin retract would rm the PRODUCTION +// artifact store.\n\nThe claims were right and the placement was wrong. Owned means torn down by +// retract, and produced artifacts are exactly what a deploy must never delete -- +// devboot_root_realization_note argued this while the root sat in the owned roster anyway. So the +// root moved to the ensured side, where converged-on-apply and never-torn-down are what the +// category already means, and neither claim was edited. The rejected alternative was to narrow +// those two claims to per-instance ownership so a host-shared resource fell outside them; it was +// refused because editing a safety claim to accommodate a change is the DESIGN section 5 tell, and +// a claim that has just caught a real hazard is not the thing to weaken (operator ruling, +// 2026-08-08).\n\nThe service still runs git init --bare itself, which remains correct and is not +// made redundant: the deploy ensures the DIRECTORY exists and is owned, and only the service can +// turn a directory into a repository, because git is not on the deploy principal sudo roster. + +// RETRACT IS THE REVERSE OF WHAT THIS SPEC APPLIES, derived from the spec rather than transcribed +// beside it. It used to be a second hand-written list repeating the same five steps in opposite +// order — one fact in two places, so editing either alone makes them disagree, and the disagreement +// surfaces as a teardown that removes the binary while the unit still references it. +// deployment_apply_order_note states the ordering law in prose; this computes it.\n\nDeriving from +// spec.steps rather than re-deriving from instance and identity matters: it makes the retract order +// the reverse of THE ORDER THIS SPEC ACTUALLY APPLIED, not the reverse of what a freshly built spec +// would apply. Those differ exactly when a spec is constructed with something other than the +// standard order, which is the case a hand-mirrored list gets wrong. Ensured dependencies drop out +// because they are never torn down (R5) — the filter states that, rather than a comment claiming +// it. fn deployment_owned_steps_retract_order(spec: DeploymentSpec) -> List { reverse( @@ -589,7 +811,17 @@ fn deployment_service_config( } } -data deployment_spec_for_note: String = "THE CONSTRUCTOR THE MODULE WAS MISSING. Everything downstream of DeploymentSpec — deployment_plan_host_identity, deployment_plan_ssh_host, deployment_plan_listen_port, deployment_tailnet_url, and the whole emit/apply chain — was ALREADY parameterized over the spec value. Only construction was fused, which is why the defect survived: reading any consumer shows a clean function of its argument, and the single point where the argument gets invented is three nullary calls in one file.\\n\\nThe three parameters are the three genuinely independent axes. instance says WHAT is placed and at which root and port; target says WHICH machine and how to reach it; identity says which names the deployment owns in the host's unit and tailscale namespaces. Twins on one host share a target and differ in the other two; the same instance on two machines shares instance and identity and differs in target. Neither case needs a new function, which is the property that was missing." +// THE CONSTRUCTOR THE MODULE WAS MISSING. Everything downstream of DeploymentSpec — +// deployment_plan_host_identity, deployment_plan_ssh_host, deployment_plan_listen_port, +// deployment_tailnet_url, and the whole emit/apply chain — was ALREADY parameterized over the spec +// value. Only construction was fused, which is why the defect survived: reading any consumer shows +// a clean function of its argument, and the single point where the argument gets invented is three +// nullary calls in one file.\n\nThe three parameters are the three genuinely independent axes. +// instance says WHAT is placed and at which root and port; target says WHICH machine and how to +// reach it; identity says which names the deployment owns in the host's unit and tailscale +// namespaces. Twins on one host share a target and differ in the other two; the same instance on +// two machines shares instance and identity and differs in target. Neither case needs a new +// function, which is the property that was missing. fn deployment_spec_for( instance: HostDashboardInstance, @@ -615,13 +847,26 @@ fn deployment_spec_srv1() -> DeploymentSpec { ) } - - -data alternating_state_roots_dissolved_note: String = "THE ALTERNATING-STATE-ROOTS SCAFFOLD IS DISSOLVED BY DELETION, WHICH IS THE CONDITION IT NAMED. It read: dissolves when dispatch worktree and attempt-state roots are no longer a second copy provisioned per candidate, leaving two alternating state roots. Blue/green was deleted at the root on 2026-08-19, so there is one deployment, one dispatch-worktree root and one attempt-state root -- not two shared ones, but one, which satisfies the condition more completely than the host-scoped-sharing shape it anticipated. The bounded outage repair it accompanied (deployment_retiring_slot_spec) went with it: retirement of an old slot is not an operation this model has any more.\n\nRecorded rather than silently dropped because a scaffold row that vanishes with its subject leaves no evidence the obligation was DISCHARGED as opposed to forgotten (DESIGN 4b: dissolution on climb deletes the production machinery, never the receipt).\n\nRetirement classification went with it. RetirementDisposition and owned_artifact_retirement_disposition -- a coproduct and the total fold over OwnedArtifactKind that answered which members a retiring slot stops and which it preserves -- had exactly one consumer, deployment_retiring_slot_spec, and no other. With no retiring slot there is nothing to classify, so they are deleted here rather than left as a total fold nobody calls. Review caught them: the first cut of this change claimed zero production residue and this was part of what refuted it." - - - - +// THE ALTERNATING-STATE-ROOTS SCAFFOLD IS DISSOLVED BY DELETION, WHICH IS THE CONDITION IT NAMED. +// It read: dissolves when dispatch worktree and attempt-state roots are no longer a second copy +// provisioned per candidate, leaving two alternating state roots. Blue/green was deleted at the +// root on 2026-08-19, so there is one deployment, one dispatch-worktree root and one attempt-state +// root -- not two shared ones, but one, which satisfies the condition more completely than the +// host-scoped-sharing shape it anticipated. The bounded outage repair it accompanied +// (deployment_retiring_slot_spec) went with it: retirement of an old slot is not an operation this +// model has any more. +// +// Recorded rather than silently dropped because a scaffold row that vanishes with its subject +// leaves no evidence the obligation was DISCHARGED as opposed to forgotten (DESIGN 4b: dissolution +// on climb deletes the production machinery, never the receipt). +// +// Retirement classification went with it. RetirementDisposition and +// owned_artifact_retirement_disposition -- a coproduct and the total fold over OwnedArtifactKind +// that answered which members a retiring slot stops and which it preserves -- had exactly one +// consumer, deployment_retiring_slot_spec, and no other. With no retiring slot there is nothing to +// classify, so they are deleted here rather than left as a total fold nobody calls. Review caught +// them: the first cut of this change claimed zero production residue and this was part of what +// refuted it. fn deployment_plan_host_identity(spec: DeploymentSpec) -> String { spec.target.host.identity diff --git a/dag/gunbc/live_deploy/target_decision.dag b/dag/gunbc/live_deploy/target_decision.dag index 90a7a91dccd..fb4f6fa680b 100644 --- a/dag/gunbc/live_deploy/target_decision.dag +++ b/dag/gunbc/live_deploy/target_decision.dag @@ -38,17 +38,17 @@ import gunbc.live_deploy.spec { DeploymentSpec, deployment_plan_listen_port } // // gunbc.live_deploy.revision has stated this policy since it was written -- four cases, three // dispositions, refuse on unverifiable -- and until this module landed its ONLY caller was its own -// witness test. That is specification-without-execution (DESIGN section 5) in the shape +// witness test: specification-without-execution (DESIGN section 5) in the shape // gunbc.fleet_desired_admission's header already names for the ref-advance half: "the modeled wall -// stood beside an actuator that walked around it". The actuator here is -// gunbc.live_deploy.apply live_deploy_admitted_wet_with_access, which observed a candidate and -// applied it unconditionally, so an older deploy arriving second rsynced its older tree, converged, -// and greened -- exactly the silent regression srv1-deploy-serialization-and-false-greens (deleted) -// section 6 predicted serialization alone would produce. +// stood beside an actuator that walked around it". The actuator here is gunbc.live_deploy.apply +// live_deploy_admitted_wet_with_access, which observed a candidate and applied it unconditionally, +// so an older deploy arriving second rsynced its older tree, converged, and greened -- exactly the +// silent regression srv1-deploy-serialization-and-false-greens (deleted) section 6 predicted +// serialization alone would produce. // // THIS MODULE IS THE CONSUMER, NOT A SECOND POLICY. The four-case relation and its verdict come // from gunbc.live_deploy.revision classify_deploy_revision; the ancestry reading comes from -// gunbc.fleet_desired_observe observe_fleet_revision_relation, which is already the sole production +// gunbc.fleet_desired_observe observe_fleet_revision_relation, already the sole production // constructor of a merge-base-derived relation. Nothing here re-derives either. What is new is the // PREDECESSOR question those authorities take as given: which revision is the target actually at. // THE PREDECESSOR IS THE RUNNING PROCESS, AND THAT IS AN OPERATOR RULING RATHER THAN A PREFERENCE. @@ -58,20 +58,20 @@ import gunbc.live_deploy.spec { DeploymentSpec, deployment_plan_listen_port } // "grounding deployment ordering on either re-admits the backward move the ordering exists to // forbid". The process is the only party that can answer which release is being served. // -// NoRunningRelease IS A POSITIVE STATE AND NOT A DEGRADED READ, and the distinction is what keeps -// this wall from being one an operator cannot get out of. A unit that is not active is not serving -// anything, so there is no release to move backward from and an advance onto an idle target is -// unconditionally forward. That is a FACT the host reported, not an absence of one: it is reached -// only when `systemctl is-active` ANSWERED, and it carries the state word it answered with. The -// arm that would have been dangerous -- reading a failed healthz probe as "nothing is running" -- -// is deliberately not available: a refused probe cannot distinguish a down service from a broken -// credential, and admitting on it would be the empty-observation narrow DESIGN section 5 names, -// at the one seam where the consequence is an unordered production deploy. +// NoRunningRelease IS A POSITIVE STATE AND NOT A DEGRADED READ, which keeps this wall from being +// one an operator cannot get out of. A unit that is not active is not serving anything, so there +// is no release to move backward from and an advance onto an idle target is unconditionally +// forward. That is a FACT the host reported, not an absence of one: reached only when `systemctl +// is-active` ANSWERED, carrying the state word it answered with. The arm that would have been +// dangerous -- reading a failed healthz probe as "nothing is running" -- is deliberately not +// available: a refused probe cannot distinguish a down service from a broken credential, and +// admitting on it would be the empty-observation narrow DESIGN section 5 names, at the one seam +// where the consequence is an unordered production deploy. // // So the discriminator is systemd rather than the health endpoint. An INACTIVE unit is the absent -// predecessor; an ACTIVE unit whose identity is unreadable is a routed process demonstrably serving -// something this system cannot name, which gunbc.running_release_identity's own cause note maps to -// a refusal "never to an advance and never to a synthetic predecessor". +// predecessor; an ACTIVE unit whose identity is unreadable is a routed process demonstrably +// serving something this system cannot name, which gunbc.running_release_identity's own cause +// note maps to a refusal "never to an advance and never to a synthetic predecessor". type DeployTargetPredecessor = NoRunningRelease { unit_state: NonEmptyStr } | RunningRelease { revision: CommitSha } @@ -106,13 +106,13 @@ fn deploy_target_predecessor_from_healthz(read: HealthzEffectiveRead) -> DeployT } } -// THE UNIT READ DECIDES WHETHER A HEALTH PROBE IS EVEN ASKED FOR, and it says so by returning a -// step rather than by taking the probe's result as an argument. An argument would be evaluated -// whichever arm the match then selected, so the idle unit would be probed anyway and the refused -// probe of an idle unit would become indistinguishable from the refused probe of a live one -- the -// exact distinction this fold exists to preserve, defeated by the shape of the signature rather -// than by the logic inside it. Returning ProbeRunningProcess keeps the fold PURE and hands the one -// remaining observation back to the wet composition that owns effects. +// THE UNIT READ DECIDES WHETHER A HEALTH PROBE IS EVEN ASKED FOR, by returning a step rather than +// taking the probe's result as an argument. An argument would be evaluated whichever arm the match +// then selected, so the idle unit would be probed anyway and the refused probe of an idle unit +// would become indistinguishable from the refused probe of a live one -- the exact distinction +// this fold preserves, defeated by the shape of the signature rather than the logic inside it. +// Returning ProbeRunningProcess keeps the fold PURE and hands the one remaining observation back +// to the wet composition that owns effects. type UnitPredecessorStep = PredecessorSettled { predecessor: DeployTargetPredecessor } | ProbeRunningProcess @@ -140,8 +140,8 @@ fn deploy_target_step_from_unit_state(capture: UnitIsActiveCapture) -> UnitPrede // THE DECISION'S INPUT CARRIES THE RELATION ALREADY OBSERVED, rather than an optional revision the // decision would then have to probe. Only one arm has two revisions to relate, so only one arm can -// carry a relation, and a coproduct says that where an Optional beside a predecessor would have -// admitted the meaningless pairs -- a relation with no predecessor, a predecessor with none. +// carry a relation; a coproduct says that where an Optional beside a predecessor would admit the +// meaningless pairs -- a relation with no predecessor, a predecessor with none. type DeployTargetInput = TargetIdle { unit_state: NonEmptyStr } | TargetRelated { running: CommitSha, relation: DeployRevisionRelation } @@ -155,20 +155,20 @@ type DeployTargetDecision | DeployTargetRefused { cause: String } // SameRevision MAPS TO PROCEED HERE, AND THIS CONSUMER DIVERGES FROM -// deploy_revision_terminal_success ON EXACTLY THAT ARM. The divergence is deliberate, it is the -// only one, and it is stated here rather than left for a reader to find by diffing the two. +// deploy_revision_terminal_success ON EXACTLY THAT ARM. The divergence is deliberate, the only +// one, and stated here rather than left for a reader to find by diffing the two. // // That predicate answers for a consumer whose predecessor is the DEPLOYED TREE, where "the host -// already carries this exact tree" really does mean there is nothing to do. This consumer's -// predecessor is the RUNNING PROCESS, and the two are not the same question: a process announcing -// revision R proves only what its launch argument said, and the tree beneath it can be drifted, -// half-synced, or hand-edited while the announcement stays R. Skipping the deploy on that basis -// would convert an operator's repair -- re-running the deploy at the same revision to reconcile a -// damaged host -- into a silent success that changed nothing, which is the fabricated-plausible- -// output failure DESIGN section 5 forbids, arrived at from the direction that looks like thrift. +// already carries this exact tree" does mean there is nothing to do. This consumer's predecessor +// is the RUNNING PROCESS, a different question: a process announcing revision R proves only what +// its launch argument said, and the tree beneath it can be drifted, half-synced, or hand-edited +// while the announcement stays R. Skipping the deploy on that basis would convert an operator's +// repair -- re-running the deploy at the same revision to reconcile a damaged host -- into a +// silent success that changed nothing, the fabricated-plausible-output failure DESIGN section 5 +// forbids, arrived at from the direction that looks like thrift. // -// It also costs nothing to admit. The harm this wall exists to prevent is the BACKWARD move, and -// an identical revision is not one; apply is a reconcile whose members are idempotent, so +// It also costs nothing to admit. The harm this wall prevents is the BACKWARD move, and an +// identical revision is not one; apply is a reconcile whose members are idempotent, so // re-converging the same revision is the ordinary no-change case rather than a second deploy. The // wall therefore refuses backward, unrelated and unverifiable, and admits same-or-forward. fn decide_deploy_target(input: DeployTargetInput, candidate: CommitSha) -> DeployTargetDecision { @@ -249,12 +249,12 @@ fn deploy_target_decision_line(decision: DeployTargetDecision) -> String { } } -// THE RELATION IS READ IN THE DEPLOYING CHECKOUT, which is the only party holding both revisions as -// objects: it was fetched with full depth for the candidate, and the running release is a commit on -// the branch it deployed from. A missing object makes `git merge-base` exit 128, which +// THE RELATION IS READ IN THE DEPLOYING CHECKOUT, the only party holding both revisions as +// objects: it was fetched with full depth for the candidate, and the running release is a commit +// on the branch it deployed from. A missing object makes `git merge-base` exit 128, which // gunbc.fleet_main_revision merge_base_relation folds to RelationUnverifiable and this module -// refuses on -- the fail-closed direction, since "I could not determine whether this is newer" is -// not "this is newer". +// refuses on -- fail-closed, since "I could not determine whether this is newer" is not "this is +// newer". fn deploy_target_input_from_predecessor( predecessor: DeployTargetPredecessor, candidate: CommitSha, @@ -293,10 +293,10 @@ fn deploy_target_input_from_predecessor( } } -// THE WET COMPOSITION, AND IT RUNS BEFORE ANY MUTATION. Every observation it makes is read-only: -// one `systemctl is-active`, and at most one health probe. gunbc.live_deploy.apply calls it after -// the candidate is observed and before the preflight mutation gate, which is the cheapest place a -// backward deploy can be stopped -- nothing has been touched, so there is no half-applied state. +// THE WET COMPOSITION, AND IT RUNS BEFORE ANY MUTATION. Every observation is read-only: one +// `systemctl is-active`, and at most one health probe. gunbc.live_deploy.apply calls it after the +// candidate is observed and before the preflight mutation gate, the cheapest place a backward +// deploy can be stopped -- nothing has been touched, so there is no half-applied state. fn observe_deploy_target( spec: DeploymentSpec, transport: HostEffectTransport, diff --git a/dag/gunbc/live_rust_observation.dag b/dag/gunbc/live_rust_observation.dag index df497b3cca2..bdab8fe4d88 100644 --- a/dag/gunbc/live_rust_observation.dag +++ b/dag/gunbc/live_rust_observation.dag @@ -24,7 +24,53 @@ import std.types { CommitSha, Int, NonEmptyStr, String } import std.content_hash { Fnv1a64Structural } import v2.std.algebra { length } -data live_rust_observation_note: String = "THE CLOSING CONTRACT FOR THE RUST-SOURCE OBSERVATION: proof that a run observed the exact subject it claims to have tested, rather than proof that an observation function is callable.\n\nWHAT MAKES THIS EVIDENCE RATHER THAN A GREEN LIGHT. The expected revision is supplied by the workflow runner (extdeps.github.actions_environment GITHUB_SHA), NOT derived from the repository the observation reads. Deriving both sides with git would compare git to itself -- true by construction, incapable of failing, and exactly the reflection-evidence-for-structural-proof failure DESIGN names. Because the two sides have independent origins, a mismatch is reachable, and a reachable mismatch is what makes the match meaningful.\n\nLAYERING. extdeps.github.actions_environment owns what GITHUB_SHA means; extdeps.shell shell.Env.Get owns reading a process environment variable. This module only projects that existing operation into the GitHub-owned observation type. It adds no GitHub-specific environment transport and no new Rust realization.\n\nWHY THE FOLD TAKES THE OBSERVATION AS A PARAMETER. admit_live_rust_observation is pure: it decides an outcome from a request and an observation it is handed. Only live_rust_observation_wet calls the host. That split is deliberate and load-bearing -- it means every control except the revision match can execute HERMETICALLY against constructed observations, at no host cost and with no Git mock anywhere, while the single genuinely-live claim stays in the one wet entry. A contract whose controls all required the host would have to be trusted; this one is mostly decidable.\n\nNO MOCKS, STATED SO IT IS NOT ADDED LATER AS A CONVENIENCE. Publishing static Git mock responses to make the live witness green would let a witness named for liveness pass against a constant sha. The hermetic controls below use constructed RustSourceHostObservation values, which is a different thing: they exercise the FOLD, and none of them claims to have observed anything.\n\nEVERY REFUSAL IS TYPED AND DISTINCT. The three host refusals stay separable (a Git failure is not a manifest read failure is not a TOML parse failure), missing and present-but-empty runner expectations are different arms, an empty population is its own cause rather than a quiet success, and a revision mismatch names both sides. A failure arm refuses and never widens (DESIGN 5): there is no arm that accepts a partial population, and no arm that treats an unavailable expectation as satisfied.\n\nCOST. The closing contract is enrolled in gunbc.ci_layer_roots bin_witness_wet_entries for the per-PR wet corpora batch (bin_witness_corpus_node), where GitHub supplies GITHUB_SHA. Its per_unit_ms is currently 0 under the declared D2 calibration gap, so this unit is honestly unpriced rather than free; the batch's fixed overhead remains the active backstop until that calibration lands.\n\nACCEPTANCE OBSERVATION. Roadmap node v1-rust-source-observation closes only on a grep-visible executed pass line for live_rust_observation_matches_actions_subject in a real CI job log with independently sourced GITHUB_SHA — not on enrollment alone. Per-row wet-batch execution is currently unobservable in Actions job logs after discovery corpus completes (gunbc.ci_layer_roots bin_witness_wet_per_row_actions_log_observation_gap_note); dissolve-on is coordinator replay of per-row outcomes." +// THE CLOSING CONTRACT FOR THE RUST-SOURCE OBSERVATION: proof that a run observed the exact subject +// it claims to have tested, rather than proof that an observation function is callable. +// +// WHAT MAKES THIS EVIDENCE RATHER THAN A GREEN LIGHT. The expected revision is supplied by the +// workflow runner (extdeps.github.actions_environment GITHUB_SHA), NOT derived from the repository +// the observation reads. Deriving both sides with git would compare git to itself -- true by +// construction, incapable of failing, and exactly the reflection-evidence-for-structural-proof +// failure DESIGN names. Because the two sides have independent origins, a mismatch is reachable, +// and a reachable mismatch is what makes the match meaningful. +// +// LAYERING. extdeps.github.actions_environment owns what GITHUB_SHA means; extdeps.shell +// shell.Env.Get owns reading a process environment variable. This module only projects that +// existing operation into the GitHub-owned observation type. It adds no GitHub-specific environment +// transport and no new Rust realization. +// +// WHY THE FOLD TAKES THE OBSERVATION AS A PARAMETER. admit_live_rust_observation is pure: it +// decides an outcome from a request and an observation it is handed. Only live_rust_observation_wet +// calls the host. That split is deliberate and load-bearing -- it means every control except the +// revision match can execute HERMETICALLY against constructed observations, at no host cost and +// with no Git mock anywhere, while the single genuinely-live claim stays in the one wet entry. A +// contract whose controls all required the host would have to be trusted; this one is mostly +// decidable. +// +// NO MOCKS, STATED SO IT IS NOT ADDED LATER AS A CONVENIENCE. Publishing static Git mock responses +// to make the live witness green would let a witness named for liveness pass against a constant +// sha. The hermetic controls below use constructed RustSourceHostObservation values, which is a +// different thing: they exercise the FOLD, and none of them claims to have observed anything. +// +// EVERY REFUSAL IS TYPED AND DISTINCT. The three host refusals stay separable (a Git failure is not +// a manifest read failure is not a TOML parse failure), missing and present-but-empty runner +// expectations are different arms, an empty population is its own cause rather than a quiet +// success, and a revision mismatch names both sides. A failure arm refuses and never widens (DESIGN +// 5): there is no arm that accepts a partial population, and no arm that treats an unavailable +// expectation as satisfied. +// +// COST. The closing contract is enrolled in gunbc.ci_layer_roots bin_witness_wet_entries for the +// per-PR wet corpora batch (bin_witness_corpus_node), where GitHub supplies GITHUB_SHA. Its +// per_unit_ms is currently 0 under the declared D2 calibration gap, so this unit is honestly +// unpriced rather than free; the batch's fixed overhead remains the active backstop until that +// calibration lands. +// +// ACCEPTANCE OBSERVATION. Roadmap node v1-rust-source-observation closes only on a grep-visible +// executed pass line for live_rust_observation_matches_actions_subject in a real CI job log with +// independently sourced GITHUB_SHA — not on enrollment alone. Per-row wet-batch execution is +// currently unobservable in Actions job logs after discovery corpus completes (gunbc.ci_layer_roots +// bin_witness_wet_per_row_actions_log_observation_gap_note); dissolve-on is coordinator replay of +// per-row outcomes. type LiveRustObservationRequest { expected_repository_revision: CommitSha diff --git a/dag/gunbc/local_tidy_spec.dag b/dag/gunbc/local_tidy_spec.dag index 26d6eb6ddeb..10cb322be45 100644 --- a/dag/gunbc/local_tidy_spec.dag +++ b/dag/gunbc/local_tidy_spec.dag @@ -79,7 +79,21 @@ type LocalTidyFmtOutcome | FmtDrift | FmtToolUnavailable { program: String, cause: String } -data local_tidy_fmt_outcome_note: String = "A tool that COULD NOT RUN is not a negative finding about the work. The pre-commit hook, the pre-push hook, and the pre_push.rs transport each collapsed a non-zero cargo-fmt exit into 'drift', so a missing cargo printed 'rustfmt drift — run: cargo fmt --all' — a remedy that runs the very binary whose absence caused the message (operator receipt 2026-07-30: hit three times in one session, on both hooks). This is the DESIGN section 5 absorbing fallback read on the answer lattice: 'the tree is unformatted' and 'I could not determine whether the tree is formatted' are different states whose remedies differ, and conflating them zeroes the deficit's observable frequency. The split is decidable from the exit status alone, so it is a construction wall rather than a check: only FmtDrift can reach a drift recipe, because local_tidy_fmt_outcome_recipe routes FmtToolUnavailable to a message that names the program and proposes no remedy depending on it. Resolution is a SEPARATE and independent axis owned by ROADMAP toolchain-single-resolver / toolchain-rust-hermetic (seed carrier InterpError::HostToolUnresolved, deleted at that lane's handback): even a perfectly hermetic cargo leaves this conflation wrong whenever the binary is unreadable, mid-install, or the wrong architecture, which is why the split lands here and does not fork a second resolver." +// A tool that COULD NOT RUN is not a negative finding about the work. The pre-commit hook, the +// pre-push hook, and the pre_push.rs transport each collapsed a non-zero cargo-fmt exit into +// 'drift', so a missing cargo printed 'rustfmt drift — run: cargo fmt --all' — a remedy that runs +// the very binary whose absence caused the message (operator receipt 2026-07-30: hit three times in +// one session, on both hooks). This is the DESIGN section 5 absorbing fallback read on the answer +// lattice: 'the tree is unformatted' and 'I could not determine whether the tree is formatted' are +// different states whose remedies differ, and conflating them zeroes the deficit's observable +// frequency. The split is decidable from the exit status alone, so it is a construction wall rather +// than a check: only FmtDrift can reach a drift recipe, because local_tidy_fmt_outcome_recipe +// routes FmtToolUnavailable to a message that names the program and proposes no remedy depending on +// it. Resolution is a SEPARATE and independent axis owned by ROADMAP toolchain-single-resolver / +// toolchain-rust-hermetic (seed carrier InterpError::HostToolUnresolved, deleted at that lane's +// handback): even a perfectly hermetic cargo leaves this conflation wrong whenever the binary is +// unreadable, mid-install, or the wrong architecture, which is why the split lands here and does +// not fork a second resolver. data local_tidy_fmt_exit_partition_note: String = "Grounded in the observed contract of cargo fmt --all --check, pinned by execution 2026-07-30: 0 = clean, 1 = rustfmt reported diffs (the only exit that is a finding about the work), 127 = the shell could not locate cargo, 126 = located but not executable (wrong architecture, lost permission bit), 101 = cargo ran but has no fmt subcommand (rustfmt component absent mid-install). Every status other than 0 and 1 therefore says the check did not produce a verdict, so the partition is exactly clean / drift / could-not-run. The residue arm is fail-closed by refusal, never by widening: an unrecognized status still stops the line with a non-zero exit, and only the MESSAGE changes, so no unverified push is admitted." @@ -87,9 +101,23 @@ data local_tidy_fmt_pre_push_drift_recipe: String = "rustfmt drift — run: carg data local_tidy_fmt_pre_commit_drift_recipe: String = "fmt drift in staged Rust — run: cargo fmt --all && git add -u, then re-commit." -data local_tidy_fmt_drift_recipe_defork_note: String = "local_tidy_fmt_pre_push_drift_recipe was authored byte-identically in BOTH gunbc.githooks_pre_push_emit and gunbc.githooks_pre_push_plan — one string, two authorities, free to drift (DESIGN section 3). Both now import this row. The pre-commit and pre-push drift recipes stay two rows because they are two genuinely different remedies (git add -u then re-commit, versus commit and re-push), not one concept spelled twice." - -data local_tidy_fmt_unavailable_advertises_no_bypass_note: String = "The could-not-run message deliberately advertises NO bypass (review 45175). An earlier draft closed it with 'git's --no-verify skips this hook', which is an escape hatch in exactly the arm that has the least information: a toggle whose only effect is to proceed as if the refusal had not fired, offered at the moment the check could not determine anything (DESIGN section 5, operator ruling 2026-07-05). The only remedy this arm may propose is the one that makes the check able to report — install the toolchain. The DRIFT recipe keeps its pre-existing --no-verify mention untouched, and that asymmetry is the point rather than an oversight: on drift the developer has been told the actual state of the tree and is choosing to skip a known finding, whereas on could-not-run there is no finding to weigh, so advertising the bypass would convert a refusal into a documented default. witness_unavailable_recipe_advertises_no_bypass is the RED control." +// local_tidy_fmt_pre_push_drift_recipe was authored byte-identically in BOTH +// gunbc.githooks_pre_push_emit and gunbc.githooks_pre_push_plan — one string, two authorities, free +// to drift (DESIGN section 3). Both now import this row. The pre-commit and pre-push drift recipes +// stay two rows because they are two genuinely different remedies (git add -u then re-commit, +// versus commit and re-push), not one concept spelled twice. + +// The could-not-run message deliberately advertises NO bypass (review 45175). An earlier draft +// closed it with 'git's --no-verify skips this hook', which is an escape hatch in exactly the arm +// that has the least information: a toggle whose only effect is to proceed as if the refusal had +// not fired, offered at the moment the check could not determine anything (DESIGN section 5, +// operator ruling 2026-07-05). The only remedy this arm may propose is the one that makes the check +// able to report — install the toolchain. The DRIFT recipe keeps its pre-existing --no-verify +// mention untouched, and that asymmetry is the point rather than an oversight: on drift the +// developer has been told the actual state of the tree and is choosing to skip a known finding, +// whereas on could-not-run there is no finding to weigh, so advertising the bypass would convert a +// refusal into a documented default. witness_unavailable_recipe_advertises_no_bypass is the RED +// control. fn local_tidy_fmt_tool_unavailable_recipe(program: String, cause: String) -> String { concat( diff --git a/dag/gunbc/managed_directory.dag b/dag/gunbc/managed_directory.dag index f2fbccf6131..431bd32a7a2 100644 --- a/dag/gunbc/managed_directory.dag +++ b/dag/gunbc/managed_directory.dag @@ -14,7 +14,36 @@ import gunbc.ownership { Ownership, Owned } import gunbc.fleet_posix_accounts { fleet_posix_operator_user } import gunbc.host_layout { srv1_dispatch_attempt_state_root } -data managed_directory_note: String = "A host directory the fleet owns, carried as WHO NEEDS WHAT rather than as a mode. The mode is DERIVED (managed_directory_permissions below), which is the whole point: gunbc.live_deploy.emit's install_d_owned_command hard-codes 0755 for every owned directory, so /opt/gunbc/attempt-state converged world-readable while the per-attempt directories beneath it are 0700 — the leaf could state its requirement and the root could not. Replacing that constant with a tighter constant would have moved the same undeclared number; a member whose dependents genuinely need traversal would then break with no statement of why. So a member declares its dependents and the permission is computed from them (operator direction 2026-07-30: precise, and derived from the needs/dependents).\\n\\nNOTHING HERE IS NEW VOCABULARY, deliberately. The verbs are std.effect_grant.Verb — the same Read/Write/Execute the deploy-preflight already admits against, read here to CONSTRUCT a permission instead of to authorize an action, which is the same relation emit and ingest have to one grammar. The permission carrier is extdeps.access.posix.FilePermissions, cited to Open Group sys/stat.h, which until now was defined, self-tested, and consumed by nothing — it is named on the inert-carrier roster (v2.lens.inert_carrier) for exactly that reason, and this module is the live consumer that takes it off, the same way RbacPolicy came off when extdeps/bmc/access.dag started using it. Principals are PosixUser rows from the closed gunbc.fleet_posix_accounts roster. Lifecycle is gunbc.ownership.Ownership. The only thing this module adds is the JOIN.\\n\\nCLASS ASSIGNMENT IS DERIVED, NOT DECLARED, which is what stops the dependent list from being the answer written twice: a dependent lands in the owner, group, or other class by comparing its uid and gid against the owner's, so a row cannot claim 'group' for a principal that shares no gid. Declaring the class directly would let the mode be whatever the author wanted while still looking computed.\\n\\nSCOPE (a counted frontier, not a sweep): only the attempt-state root consumes this today. The other owned directories keep the shared constant until each one's dependents are established by evidence rather than by assumption — /opt/gunbc/gunbc is written by the tree-sync unit AS the service user and read by the serve process, and getting that wrong tightens a live host out of service on the next deploy. A blanket re-derivation is a host-behaviour change disguised as a refactor. dissolve-on = every member in gunbc.live_deploy.spec deployment_owned_steps carries a ManagedDirectory, at which point install_d_owned_command loses its mode argument entirely and this frontier note is deleted." +// A host directory the fleet owns, carried as WHO NEEDS WHAT rather than as a mode. The mode is +// DERIVED (managed_directory_permissions below), which is the whole point: gunbc.live_deploy.emit's +// install_d_owned_command hard-codes 0755 for every owned directory, so /opt/gunbc/attempt-state +// converged world-readable while the per-attempt directories beneath it are 0700 — the leaf could +// state its requirement and the root could not. Replacing that constant with a tighter constant +// would have moved the same undeclared number; a member whose dependents genuinely need traversal +// would then break with no statement of why. So a member declares its dependents and the permission +// is computed from them (operator direction 2026-07-30: precise, and derived from the +// needs/dependents).\n\nNOTHING HERE IS NEW VOCABULARY, deliberately. The verbs are +// std.effect_grant.Verb — the same Read/Write/Execute the deploy-preflight already admits against, +// read here to CONSTRUCT a permission instead of to authorize an action, which is the same relation +// emit and ingest have to one grammar. The permission carrier is +// extdeps.access.posix.FilePermissions, cited to Open Group sys/stat.h, which until now was +// defined, self-tested, and consumed by nothing — it is named on the inert-carrier roster +// (v2.lens.inert_carrier) for exactly that reason, and this module is the live consumer that takes +// it off, the same way RbacPolicy came off when extdeps/bmc/access.dag started using it. Principals +// are PosixUser rows from the closed gunbc.fleet_posix_accounts roster. Lifecycle is +// gunbc.ownership.Ownership. The only thing this module adds is the JOIN.\n\nCLASS ASSIGNMENT IS +// DERIVED, NOT DECLARED, which is what stops the dependent list from being the answer written +// twice: a dependent lands in the owner, group, or other class by comparing its uid and gid against +// the owner's, so a row cannot claim 'group' for a principal that shares no gid. Declaring the +// class directly would let the mode be whatever the author wanted while still looking +// computed.\n\nSCOPE (a counted frontier, not a sweep): only the attempt-state root consumes this +// today. The other owned directories keep the shared constant until each one's dependents are +// established by evidence rather than by assumption — /opt/gunbc/gunbc is written by the tree-sync +// unit AS the service user and read by the serve process, and getting that wrong tightens a live +// host out of service on the next deploy. A blanket re-derivation is a host-behaviour change +// disguised as a refactor. dissolve-on = every member in gunbc.live_deploy.spec +// deployment_owned_steps carries a ManagedDirectory, at which point install_d_owned_command loses +// its mode argument entirely and this frontier note is deleted. type DirectoryDependent { who: PosixUser @@ -101,13 +130,37 @@ fn managed_directory_mode_octal(d: ManagedDirectory) -> String { file_mode_octal(mode: managed_directory_permissions(d: d).mode) } -data srv1_attempt_state_dependents_note: String = "The attempt-state root's dependents are the whole evidence base for its mode, so they are stated with their reasons rather than assumed. The service user needs all three verbs: WRITE because belt_attempt_state_initialize_for_instance writes attempt-key, the admission receipt, and the initial provider-events file; EXECUTE because creating and traversing the per-attempt subdirectory requires the directory search bit (which is why belt_footprint_root_verdict probes search separately from write); READ because belt_workflow_progress_for_attempt_ref reads back -current/attempt-key on every dashboard poll. NOBODY ELSE APPEARS, and that absence is the claim being made: the CI-runner principal never touches this tree (the deploy creates the directory through sudo and hands it to the service user), and no other account on srv1 has a reason to read attempt evidence. Deriving from exactly that list gives 0700 — a tightening from the inherited 0755, and the tightening is the consequence rather than the goal. If a future consumer genuinely needs to read attempt evidence (a metrics reader, a second service account), it is added HERE as a dependent with the verbs it needs, and the mode widens by derivation with the reason recorded — never by editing a number." +// The attempt-state root's dependents are the whole evidence base for its mode, so they are stated +// with their reasons rather than assumed. The service user needs all three verbs: WRITE because +// belt_attempt_state_initialize_for_instance writes attempt-key, the admission receipt, and the +// initial provider-events file; EXECUTE because creating and traversing the per-attempt +// subdirectory requires the directory search bit (which is why belt_footprint_root_verdict probes +// search separately from write); READ because belt_workflow_progress_for_attempt_ref reads back +// -current/attempt-key on every dashboard poll. NOBODY ELSE APPEARS, and that absence is the +// claim being made: the CI-runner principal never touches this tree (the deploy creates the +// directory through sudo and hands it to the service user), and no other account on srv1 has a +// reason to read attempt evidence. Deriving from exactly that list gives 0700 — a tightening from +// the inherited 0755, and the tightening is the consequence rather than the goal. If a future +// consumer genuinely needs to read attempt evidence (a metrics reader, a second service account), +// it is added HERE as a dependent with the verbs it needs, and the mode widens by derivation with +// the reason recorded — never by editing a number. fn srv1_dashboard_service_principal() -> PosixUser { fleet_posix_operator_user } -data attempt_state_directory_is_placed_note: String = "THE DIRECTORY'S REQUIREMENTS ARE THE SAME EVERYWHERE; ITS LOCATION IS NOT. This arrived as a nullary srv1 singleton — the same nullary shape #7456 removed from deployment_owned_steps — and merging the two lanes made the mismatch live: the owned MEMBER path became instance-derived while the apply command still built its directory at srv1's literal, so a twin deployment's apply would have created and chmodded PRODUCTION's attempt-state root. That is review 45197's defect on a new member: an instance-derived path beside a hardcoded one that used to agree because only one instance existed.\\n\\nOwner and dependents genuinely do not vary — every instance's attempt state is written and read by that instance's own service user, so the derivation of the mode is unchanged and this is not a loosening. Only the placement moves to a parameter, which is the axis #7456 established as the one that varies.\\n\\nsrv1_attempt_state_directory survives as the production placement of the general row rather than as a second definition, so its single consumer keeps working and there is still exactly one statement of what the directory requires." +// THE DIRECTORY'S REQUIREMENTS ARE THE SAME EVERYWHERE; ITS LOCATION IS NOT. This arrived as a +// nullary srv1 singleton — the same nullary shape #7456 removed from deployment_owned_steps — and +// merging the two lanes made the mismatch live: the owned MEMBER path became instance-derived while +// the apply command still built its directory at srv1's literal, so a twin deployment's apply would +// have created and chmodded PRODUCTION's attempt-state root. That is review 45197's defect on a new +// member: an instance-derived path beside a hardcoded one that used to agree because only one +// instance existed.\n\nOwner and dependents genuinely do not vary — every instance's attempt state +// is written and read by that instance's own service user, so the derivation of the mode is +// unchanged and this is not a loosening. Only the placement moves to a parameter, which is the axis +// #7456 established as the one that varies.\n\nsrv1_attempt_state_directory survives as the +// production placement of the general row rather than as a second definition, so its single +// consumer keeps working and there is still exactly one statement of what the directory requires. fn attempt_state_directory_at(path: FilePath) -> ManagedDirectory { let service = srv1_dashboard_service_principal() diff --git a/dag/gunbc/materialization_kernel_closing_frontier_audit.dag b/dag/gunbc/materialization_kernel_closing_frontier_audit.dag index 8b1bda7c5e8..37268f55e8e 100644 --- a/dag/gunbc/materialization_kernel_closing_frontier_audit.dag +++ b/dag/gunbc/materialization_kernel_closing_frontier_audit.dag @@ -21,7 +21,9 @@ type FrontierClause { verdict: ClauseVerdict } -data clause_delivered_note: String = "The one exhaustive match on ClauseVerdict in this module; every other Bool-producing projection on a FrontierClause routes through this (review 47530, DESIGN §3 — an undeclared sum re-matched by a family of free predicates duplicates the same exhaustiveness fact at each call site)." +// The one exhaustive match on ClauseVerdict in this module; every other Bool-producing projection +// on a FrontierClause routes through this (review 47530, DESIGN §3 — an undeclared sum re-matched +// by a family of free predicates duplicates the same exhaustiveness fact at each call site). fn clause_delivered(c: FrontierClause) -> Bool { match c.verdict { @@ -31,7 +33,21 @@ fn clause_delivered(c: FrontierClause) -> Bool { } } -data clause_has_any_evidence_note: String = "DELETED (review 47530 round 2, still-bat-561): this module previously carried clause_has_any_evidence, a Bool-returning fn that always returned true because every ClauseVerdict variant requires a NonEmptyStr reason/evidence field by construction — the bad state (a clause with no evidence) has no constructor, so the check could not fail. A function named has_any_evidence that returns true unconditionally reads as a performed check to any caller who does not open it, which is worse than no function at all (DESIGN §4b(4) — a climb deletes the redundant lower-rung PRODUCTION machinery the construction obsoletes; §5 — a check standing where construction already makes the state unwritable is validation, not verification). materialization_kernel_every_clause_has_evidence, its sole caller, was deleted with it for the same reason, and the witness that asserted it (materialization_kernel_every_clause_carries_evidence) was deleted rather than kept green-by-tautology. This note is the surviving, durable part: every FrontierClause carries evidence because ClauseVerdict's three variants each require a NonEmptyStr field (reason, or reason+evidence, or witness_module+witness_fn+evidence) — read the type in this file if you need to confirm that property holds; there is nothing left to call." +// DELETED (review 47530 round 2, still-bat-561): this module previously carried +// clause_has_any_evidence, a Bool-returning fn that always returned true because every +// ClauseVerdict variant requires a NonEmptyStr reason/evidence field by construction — the bad +// state (a clause with no evidence) has no constructor, so the check could not fail. A function +// named has_any_evidence that returns true unconditionally reads as a performed check to any caller +// who does not open it, which is worse than no function at all (DESIGN §4b(4) — a climb deletes the +// redundant lower-rung PRODUCTION machinery the construction obsoletes; §5 — a check standing where +// construction already makes the state unwritable is validation, not verification). +// materialization_kernel_every_clause_has_evidence, its sole caller, was deleted with it for the +// same reason, and the witness that asserted it +// (materialization_kernel_every_clause_carries_evidence) was deleted rather than kept +// green-by-tautology. This note is the surviving, durable part: every FrontierClause carries +// evidence because ClauseVerdict's three variants each require a NonEmptyStr field (reason, or +// reason+evidence, or witness_module+witness_fn+evidence) — read the type in this file if you need +// to confirm that property holds; there is nothing left to call. fn closing_frontier_clauses() -> List { [ @@ -99,7 +115,12 @@ fn materialization_kernel_clause_count(clauses: List) -> Int { count(clauses) } -data materialization_kernel_all_clauses_delivered_note: String = "RENAMED from materialization_kernel_closing_contract_holds (still-bat-561 operator ruling, 2026-08-02): this is a derived AUDIT SUMMARY over closing_frontier_clauses — true only if every audited clause independently reached Delivered — not a roadmap execution contract and not itself bound to the v1-materialization-kernel roadmap node's acceptance. See materialization_kernel_closing_frontier_audit_note above for why the prior framing (an authored AND-fold presented as 'the contract') was retired." +// RENAMED from materialization_kernel_closing_contract_holds (still-bat-561 operator ruling, +// 2026-08-02): this is a derived AUDIT SUMMARY over closing_frontier_clauses — true only if every +// audited clause independently reached Delivered — not a roadmap execution contract and not itself +// bound to the v1-materialization-kernel roadmap node's acceptance. See +// materialization_kernel_closing_frontier_audit_note above for why the prior framing (an authored +// AND-fold presented as 'the contract') was retired. fn materialization_kernel_all_clauses_delivered(clauses: List) -> Bool { clauses |> fold(init: true, f: (acc, c) => acc && clause_delivered(c: c)) diff --git a/dag/gunbc/materialization_provider_targets.dag b/dag/gunbc/materialization_provider_targets.dag index 458fe5dd056..836bdbb6abd 100644 --- a/dag/gunbc/materialization_provider_targets.dag +++ b/dag/gunbc/materialization_provider_targets.dag @@ -11,9 +11,31 @@ import extdeps.realization.eval_memo { eval_memo_id } import extdeps.realization.native_artifact_cache { native_artifact_cache_id } import std.dissolution { DissolutionCondition, unbound_dissolution } -data materialization_provider_targets_note: String = "The consumer-target frontier of std.materialization_provider (parent mandate 6B, model-only; homed in gunbc/ per operator rework ruling PR 7385 — this roster is INTERNAL planning data about our own tree, so it carries no ExternalAuthority anchor and does not live in extdeps): each row names an EXISTING cited cache row (by its CacheInterfaceId — grounding, never a second description of the cache) that today serves lookups through an ad-hoc carrier and later consumes the contract's provider_serve / provider_admit instead. Every row carries TYPED binds (blocker 4): cache_row points at the cited catalog-facts declaration that describes the cache, and consumer_seam points at the declaration governing the consumer surface that retires on the flip; prose reason/dissolve_on stay beside the binds as context, but the binds are the machine-followable edges. The rows are a DeclaredFrontier (registered in gunbc.roster_registry): countable, prioritizable; a consumer flip retires its row, and the roster deletes when empty. This module declares TARGETS only — no consumer flips, no Rust, per the mandate." +// The consumer-target frontier of std.materialization_provider (parent mandate 6B, model-only; +// homed in gunbc/ per operator rework ruling PR 7385 — this roster is INTERNAL planning data about +// our own tree, so it carries no ExternalAuthority anchor and does not live in extdeps): each row +// names an EXISTING cited cache row (by its CacheInterfaceId — grounding, never a second +// description of the cache) that today serves lookups through an ad-hoc carrier and later consumes +// the contract's provider_serve / provider_admit instead. Every row carries TYPED binds (blocker +// 4): cache_row points at the cited catalog-facts declaration that describes the cache, and +// consumer_seam points at the declaration governing the consumer surface that retires on the flip; +// prose reason/dissolve_on stay beside the binds as context, but the binds are the +// machine-followable edges. The rows are a DeclaredFrontier (registered in gunbc.roster_registry): +// countable, prioritizable; a consumer flip retires its row, and the roster deletes when empty. +// This module declares TARGETS only — no consumer flips, no Rust, per the mandate. -data materialization_provider_non_target_note: String = "DECLARED EXCLUSION, so the roster's coverage claim is checkable rather than merely asserted: extdeps.realization.artifact_store_fs is named in the 6B mandate's grounding list but is deliberately NOT a row here, and the reason is directional. Every row below is a cache that will one day CONSUME the contract; artifact_store_fs sits UNDER it — it is the filesystem transport handler bound to std.artifact_store's persistent tier (put/get with typed grant refusals), and std.materialization_provider already consumes that store directly via store_put. It also has no CacheInterfaceId, because it describes a transport rather than a cache system, so it cannot inhabit ProviderContractTarget's cache field at all. Its scope and remaining gaps are tracked on that module and deliberately not restated here — an earlier version of this sentence copied a claim from that module's prose that its own functions refuted (operator-flagged on PR 7385), which is why this note now carries a pointer instead of a duplicate. If a future rung gives the fs transport its own cited catalog row, it enrolls below like any other." +// DECLARED EXCLUSION, so the roster's coverage claim is checkable rather than merely asserted: +// extdeps.realization.artifact_store_fs is named in the 6B mandate's grounding list but is +// deliberately NOT a row here, and the reason is directional. Every row below is a cache that will +// one day CONSUME the contract; artifact_store_fs sits UNDER it — it is the filesystem transport +// handler bound to std.artifact_store's persistent tier (put/get with typed grant refusals), and +// std.materialization_provider already consumes that store directly via store_put. It also has no +// CacheInterfaceId, because it describes a transport rather than a cache system, so it cannot +// inhabit ProviderContractTarget's cache field at all. Its scope and remaining gaps are tracked on +// that module and deliberately not restated here — an earlier version of this sentence copied a +// claim from that module's prose that its own functions refuted (operator-flagged on PR 7385), +// which is why this note now carries a pointer instead of a duplicate. If a future rung gives the +// fs transport its own cited catalog row, it enrolls below like any other. fn target_decl(module_path: String, decl_name: String) -> DeclarationRef { DeclarationRef { diff --git a/dag/gunbc/measurement_provenance.dag b/dag/gunbc/measurement_provenance.dag index 6209139f4cd..208cf3504fb 100644 --- a/dag/gunbc/measurement_provenance.dag +++ b/dag/gunbc/measurement_provenance.dag @@ -14,21 +14,124 @@ import gunbc.devboot.outcome { DevbootWorkStatus, } -data measurement_provenance_root_note: String = "FOUR SPECIMENS, ONE ABSENT RELATION, ONE DAY (2026-08-20, this fleet). (1) A stale binary produced first_generation_equal=true — tree and compiler were different vintages and nothing in the receipt said so. (2) A DIFFERENT stale binary produced a 9-declaration drop that did not exist, escalated through three sessions before execution refuted it byte-identical across 131 files — the binary had been replaced mid-sequence as collateral from an unrelated all-bins build, and the author never typed a command that built it. (3) `cargo build 2>&1 | tail -4 && next` takes tail's exit status, so a FAILED build proceeded as success and the downstream step measured a stale artifact. (4) A treatment merge hit 'refusing to merge unrelated histories' on a shallow clone; unguarded, the baseline numbers would have re-reported as the treatment arm. Every one returned a plausible, well-formed number. Every one was caught by a person noticing an inconsistency, never by a mechanism. The root is not 'stale binaries' — it is one relation, absent in all four: a reported observation is not structurally bound to the SUCCESSFUL CONSTRUCTION of its exact instrument and its exact subject. This module makes the unbound observation unrepresentable rather than checkable." +// FOUR SPECIMENS, ONE ABSENT RELATION, ONE DAY (2026-08-20, this fleet). (1) A stale binary +// produced first_generation_equal=true — tree and compiler were different vintages and nothing in +// the receipt said so. (2) A DIFFERENT stale binary produced a 9-declaration drop that did not +// exist, escalated through three sessions before execution refuted it byte-identical across 131 +// files — the binary had been replaced mid-sequence as collateral from an unrelated all-bins build, +// and the author never typed a command that built it. (3) `cargo build 2>&1 | tail -4 && next` +// takes tail's exit status, so a FAILED build proceeded as success and the downstream step measured +// a stale artifact. (4) A treatment merge hit 'refusing to merge unrelated histories' on a shallow +// clone; unguarded, the baseline numbers would have re-reported as the treatment arm. Every one +// returned a plausible, well-formed number. Every one was caught by a person noticing an +// inconsistency, never by a mechanism. The root is not 'stale binaries' — it is one relation, +// absent in all four: a reported observation is not structurally bound to the SUCCESSFUL +// CONSTRUCTION of its exact instrument and its exact subject. This module makes the unbound +// observation unrepresentable rather than checkable. -data measurement_provenance_construction_not_validation_note: String = "THE SHAPE THIS MODULE REFUSES TO BE: a function whose job is to verify that a measurement carries its provenance. If such a function can exist, the bad state was already writable and this would be a validator standing where construction belongs (DESIGN section 5). Instead Observation is a coproduct whose bound arm (Observed) is reachable ONLY by folding two retrievability observations that both landed on ArtifactAvailable. The unbound arms carry no StoredArtifact: there is nothing to copy forward, so a caller cannot manufacture instrument or subject identity for a build that did not produce retrievable bytes. observe is the ONLY constructor of Observed; no other function in this module builds one." +// THE SHAPE THIS MODULE REFUSES TO BE: a function whose job is to verify that a measurement carries +// its provenance. If such a function can exist, the bad state was already writable and this would +// be a validator standing where construction belongs (DESIGN section 5). Instead Observation +// is a coproduct whose bound arm (Observed) is reachable ONLY by folding two retrievability +// observations that both landed on ArtifactAvailable. The unbound arms carry no StoredArtifact: +// there is nothing to copy forward, so a caller cannot manufacture instrument or subject identity +// for a build that did not produce retrievable bytes. observe is the ONLY constructor of Observed; +// no other function in this module builds one. -data measurement_provenance_authority_note: String = "WHY THIS MODULE MINTS NO OUTCOME VOCABULARY OF ITS OWN. An earlier revision of this carrier declared its own InstrumentConstruction and SubjectConstruction coproducts, each collapsing every way a construction can fail into one NonEmptyStr detail. That was a second and strictly poorer authority for a concept gunbc.devboot.outcome already owns (DESIGN section 3), and the poverty was not cosmetic: devboot keeps BuildFailureCause and InfrastructureRefusalCause deliberately APART because a compilation failure is a fact about the subject and retrying changes nothing, while an infrastructure refusal is a fact about this host at this moment and the same subject may succeed later — fusing them makes 'is retry pointless' unanswerable, which is precisely what a single detail string does. Specimen (4), a subject that could not be fetched, exists there as the TYPE InfrastructureRefusalCause SubjectNotFetchable rather than as a sentence. So this module imports that authority and contributes only what devboot does not model: the binding of a reported Value to the identities of the instrument that produced it and the subject it was taken of. devboot answers 'may a caller believe it holds these bytes'; this module answers 'may a caller believe this number was produced by that instrument from that subject', and it answers it by having no arm in which the number travels without both." +// WHY THIS MODULE MINTS NO OUTCOME VOCABULARY OF ITS OWN. An earlier revision of this carrier +// declared its own InstrumentConstruction and SubjectConstruction coproducts, each collapsing every +// way a construction can fail into one NonEmptyStr detail. That was a second and strictly poorer +// authority for a concept gunbc.devboot.outcome already owns (DESIGN section 3), and the poverty +// was not cosmetic: devboot keeps BuildFailureCause and InfrastructureRefusalCause deliberately +// APART because a compilation failure is a fact about the subject and retrying changes nothing, +// while an infrastructure refusal is a fact about this host at this moment and the same subject may +// succeed later — fusing them makes 'is retry pointless' unanswerable, which is precisely what a +// single detail string does. Specimen (4), a subject that could not be fetched, exists there as the +// TYPE InfrastructureRefusalCause SubjectNotFetchable rather than as a sentence. So this module +// imports that authority and contributes only what devboot does not model: the binding of a +// reported Value to the identities of the instrument that produced it and the subject it was taken +// of. devboot answers 'may a caller believe it holds these bytes'; this module answers 'may a +// caller believe this number was produced by that instrument from that subject', and it answers it +// by having no arm in which the number travels without both. -data measurement_provenance_stale_instrument_note: String = "WHAT THE FOLD ONTO devboot CLOSED, WHICH THIS CARRIER PREVIOUSLY DECLARED OUT OF REACH. Specimen (1) is a PAST successful build being read as evidence about a PRESENT binary. The earlier revision named that axis outside the modeled guarantee and stopped there. devboot had already split it on purpose: execution is history and availability is an observation of the store, ArtifactAvailable carries the read-back that established it, and availability_permits_retrieval reads the availability axis ALONE so that a past success contributes nothing to whether bytes can be had now. observe therefore selects its arm on availability alone and never on execution — an ExecutionProduced whose artifact is Missing, Corrupt or Unreadable yields a refusal, not an Observed — which is specimen (1)'s shape refused by construction rather than declared unmodeled. Execution is still CARRIED into the refusal arms, because that is where the retry question is answered; it simply has no vote on whether the observation binds." +// WHAT THE FOLD ONTO devboot CLOSED, WHICH THIS CARRIER PREVIOUSLY DECLARED OUT OF REACH. Specimen +// (1) is a PAST successful build being read as evidence about a PRESENT binary. The earlier +// revision named that axis outside the modeled guarantee and stopped there. devboot had already +// split it on purpose: execution is history and availability is an observation of the store, +// ArtifactAvailable carries the read-back that established it, and availability_permits_retrieval +// reads the availability axis ALONE so that a past success contributes nothing to whether bytes can +// be had now. observe therefore selects its arm on availability alone and never on execution — an +// ExecutionProduced whose artifact is Missing, Corrupt or Unreadable yields a refusal, not an +// Observed — which is specimen (1)'s shape refused by construction rather than declared unmodeled. +// Execution is still CARRIED into the refusal arms, because that is where the retry question is +// answered; it simply has no vote on whether the observation binds. -data measurement_provenance_source_vintage_note: String = "FOUR MORE SPECIMENS, SIX DAYS LATER, ONE MODE THIS CARRIER DID NOT CLOSE (2026-08-26, this fleet). In one night four sessions independently produced false BLOCKING defect reports about a healthy main. Three were the modes already modeled here — a baked binary of an older vintage, and a check that returned CLEAN because the entry compiled did not reach the stage that produces the diagnostic. The fourth was not: a binary that was FRESHLY BUILT, retrievable, uncorrupted and passing every freshness probe on offer, from a WORKTREE fourteen commits behind main. Its bytes were beyond reproach; the source they were built from was stale, and it refused three channels that current main models. The earlier revision of this carrier could not have said so. Its Observed arm carried StoredArtifact, whose only subject-identity field is subject_digest — which devboot's own header calls an index rather than a proof — so a measurement was bound to bytes and to nothing about the tree those bytes came from. Carrying StoredArtifactRecord instead binds tree_oid into the bound arm, so two instruments built from different trees are now distinguishable AT the observation rather than by a reader remembering to check. WHAT THIS DOES AND DOES NOT CLAIM: it makes the vintage REPRESENTABLE and CARRIED where it was previously absent, which is what lets a consumer compare it; it does not by itself refuse a stale instrument, because which tree a measurement ought to have been taken from is a fact about the reader's question and not about the store. That comparison is the consumer's, and this carrier's job is to make it possible to state." +// FOUR MORE SPECIMENS, SIX DAYS LATER, ONE MODE THIS CARRIER DID NOT CLOSE (2026-08-26, this +// fleet). In one night four sessions independently produced false BLOCKING defect reports about a +// healthy main. Three were the modes already modeled here — a baked binary of an older vintage, and +// a check that returned CLEAN because the entry compiled did not reach the stage that produces the +// diagnostic. The fourth was not: a binary that was FRESHLY BUILT, retrievable, uncorrupted and +// passing every freshness probe on offer, from a WORKTREE fourteen commits behind main. Its bytes +// were beyond reproach; the source they were built from was stale, and it refused three channels +// that current main models. The earlier revision of this carrier could not have said so. Its +// Observed arm carried StoredArtifact, whose only subject-identity field is subject_digest — which +// devboot's own header calls an index rather than a proof — so a measurement was bound to bytes and +// to nothing about the tree those bytes came from. Carrying StoredArtifactRecord instead binds +// tree_oid into the bound arm, so two instruments built from different trees are now +// distinguishable AT the observation rather than by a reader remembering to check. WHAT THIS DOES +// AND DOES NOT CLAIM: it makes the vintage REPRESENTABLE and CARRIED where it was previously +// absent, which is what lets a consumer compare it; it does not by itself refuse a stale +// instrument, because which tree a measurement ought to have been taken from is a fact about the +// reader's question and not about the store. That comparison is the consumer's, and this carrier's +// job is to make it possible to state. -data measurement_provenance_status_pairing_note: String = "A SECOND HAZARD CLOSED BY THE SAME SIGNATURE CHANGE, AND IT IS NOT THE ONE THE WORK STARTED FROM. The earlier observe took four loose axes — instrument execution, instrument availability, subject execution, subject availability — so a caller could pair one build's execution with another build's availability and no arm could refuse: the fold selects on availability, and the refusal it returns then reports the OTHER build's answer to 'is retry pointless'. Every arm would have been well-formed and the mis-pairing invisible. DevbootWorkStatus is devboot's own binding of the two axes to the request they describe, so taking two statuses makes the mismatch unwritable rather than undetected. Recorded separately from the vintage note because it was found by writing the signature rather than by the specimens, and a hazard folded silently into another finding's story is one nobody can later check." +// A SECOND HAZARD CLOSED BY THE SAME SIGNATURE CHANGE, AND IT IS NOT THE ONE THE WORK STARTED FROM. +// The earlier observe took four loose axes — instrument execution, instrument availability, subject +// execution, subject availability — so a caller could pair one build's execution with another +// build's availability and no arm could refuse: the fold selects on availability, and the refusal +// it returns then reports the OTHER build's answer to 'is retry pointless'. Every arm would have +// been well-formed and the mis-pairing invisible. DevbootWorkStatus is devboot's own binding of the +// two axes to the request they describe, so taking two statuses makes the mismatch unwritable +// rather than undetected. Recorded separately from the vintage note because it was found by writing +// the signature rather than by the specimens, and a hazard folded silently into another finding's +// story is one nobody can later check. -data measurement_provenance_boundary_note: String = "THE HONEST BOUNDARY THAT REMAINS, NARROWED BUT NOT CLOSED. What is structural: no Observation can carry a Value alongside an instrument or subject identity unless both retrievability observations landed on ArtifactAvailable, and the identities it carries are StoredArtifact values taken out of those arms rather than assembled here. What is NOT structural: whether the ArtifactAvailable handed to observe was itself established by a read-back performed by THIS invocation against the path actually used, rather than hand-authored with a fabricated Sha256Digest by a caller who bypassed the store entirely. devboot states that contract for its own producers; nothing in the type system enforces it against an arbitrary caller, and that is the same residual gap sole_constructor already names as unverified for generic carriers. So the wet boundary has moved — from 'no read-back is modeled at all' to 'the read-back is modeled and required, and its authenticity rests on an unverified construction ceiling' — and DESIGN section 4b's rule is to say which rung that is rather than claim a uniform climb. A SECOND RESIDUE, NAMED BECAUSE THIS REVISION MOVED IT RATHER THAN CLOSED IT: the StoredArtifactRecord the bound arm carries is assembled here from ONE DevbootWorkStatus — its subject paired with the artifact its own availability arm observed — rather than taken out of the store as a record. That pairing is faithful to what a status means, since both fields answer for the same work id; it is not the store's own read-back of the record, so a caller who hand-authored a status can still hand-author the pairing. The vintage is now carried and comparable, which is strictly more than it was; its authenticity rests on the same unverified construction ceiling as the digest above, and not on a stronger one. The practical discipline this does not replace: freeze the instrument outside the build tree before first measurement and invoke it by absolute path, because a binary can be silently replaced by an unrelated command between the observation and the measurement." +// THE HONEST BOUNDARY THAT REMAINS, NARROWED BUT NOT CLOSED. What is structural: no Observation can +// carry a Value alongside an instrument or subject identity unless both retrievability observations +// landed on ArtifactAvailable, and the identities it carries are StoredArtifact values taken out of +// those arms rather than assembled here. What is NOT structural: whether the ArtifactAvailable +// handed to observe was itself established by a read-back performed by THIS invocation against the +// path actually used, rather than hand-authored with a fabricated Sha256Digest by a caller who +// bypassed the store entirely. devboot states that contract for its own producers; nothing in the +// type system enforces it against an arbitrary caller, and that is the same residual gap +// sole_constructor already names as unverified for generic carriers. So the wet boundary has moved +// — from 'no read-back is modeled at all' to 'the read-back is modeled and required, and its +// authenticity rests on an unverified construction ceiling' — and DESIGN section 4b's rule is to +// say which rung that is rather than claim a uniform climb. A SECOND RESIDUE, NAMED BECAUSE THIS +// REVISION MOVED IT RATHER THAN CLOSED IT: the StoredArtifactRecord the bound arm carries is +// assembled here from ONE DevbootWorkStatus — its subject paired with the artifact its own +// availability arm observed — rather than taken out of the store as a record. That pairing is +// faithful to what a status means, since both fields answer for the same work id; it is not the +// store's own read-back of the record, so a caller who hand-authored a status can still hand-author +// the pairing. The vintage is now carried and comparable, which is strictly more than it was; its +// authenticity rests on the same unverified construction ceiling as the digest above, and not on a +// stronger one. The practical discipline this does not replace: freeze the instrument outside the +// build tree before first measurement and invoke it by absolute path, because a binary can be +// silently replaced by an unrelated command between the observation and the measurement. -data measurement_provenance_adoption_note: String = "ADOPTION STATUS, STATED RATHER THAN IMPLIED. Nothing in the accepted program folds through observe today, and the fold onto devboot did not change that: measured on this branch, gunbc.devboot is itself imported by four of its own siblings and three witness tests, and by no production consumer anywhere. So this carrier and the authority it now shares are one unconsumed cluster rather than two, which is progress in modeling and none in consumption. That is worth stating plainly because the precedent this design was originally argued from does NOT have that property — gunbc.regen_receipt earns its keep because required_regen_host folds the actual regen path through it — and a citation whose force depends on a consumption fact the citation does not carry is itself a specimen of this module's subject. The trigger for adoption is the first measurement site that needs a number bound to its instrument and subject; binding one is a separate packet scoped against a real wet path, not an addition to this carrier, and a caller invented here to occupy the consumer slot would be a fabricated consumer — the exact class this module exists to refuse." +// ADOPTION STATUS, STATED RATHER THAN IMPLIED. Nothing in the accepted program folds through +// observe today, and the fold onto devboot did not change that: measured on this branch, +// gunbc.devboot is itself imported by four of its own siblings and three witness tests, and by no +// production consumer anywhere. So this carrier and the authority it now shares are one unconsumed +// cluster rather than two, which is progress in modeling and none in consumption. That is worth +// stating plainly because the precedent this design was originally argued from does NOT have that +// property — gunbc.regen_receipt earns its keep because required_regen_host folds the actual regen +// path through it — and a citation whose force depends on a consumption fact the citation does not +// carry is itself a specimen of this module's subject. The trigger for adoption is the first +// measurement site that needs a number bound to its instrument and subject; binding one is a +// separate packet scoped against a real wet path, not an addition to this carrier, and a caller +// invented here to occupy the consumer slot would be a fabricated consumer — the exact class this +// module exists to refuse. // THE REPORTED FACT, BOUND OR REFUSED. Observation is generic over the reported // number/fact so this carrier is not re-minted per measurement site (DESIGN section 2 — one diff --git a/dag/gunbc/membership_reconcile.dag b/dag/gunbc/membership_reconcile.dag index ab6cafb6c5f..dc867707f73 100644 --- a/dag/gunbc/membership_reconcile.dag +++ b/dag/gunbc/membership_reconcile.dag @@ -76,7 +76,18 @@ type MembershipReconcileScopedOutcome duplicate: KeyedRow, M>, } -data membership_reconcile_authority_note: String = "THE grain-agnostic membership-diff reconcile (R2/R3/R5/R9). Desired set vs observed set keyed by member identity: Added -> add, Modified -> change, Removed -> remove (owned-only) or refuse, Unchanged -> noop (absence of a hunk). Add and change are DISTINCT arms and are NOT collapsed into one 'upsert'. Only REMOVAL consults ownership (gunbc.ownership Ensured constrains teardown only). Generic over member type M and key K — consumers pass (key_of, key_eq, value_eq, ownership_of) to membership_reconcile, they NEVER copy-adapt this fn. MemberAction carries `at` = the reconcile key so effect realization does not re-derive identity from mutable value. FUNCTIONAL-KEY ADMISSION: each population is built through keyed_roster_build BEFORE diffing — KeyedRosterBuildDuplicateKey refuses with typed evidence; no diff, no pick-one, no widen. R5 removal-owned-only is a CONSTRUCTION WALL on MemberRemovalRefused. membership_reconcile_scoped is the KeyRelation/KeyedResourceRelation path for relations with grounded scope (Tailscale vertical only until scope lands elsewhere)." +// THE grain-agnostic membership-diff reconcile (R2/R3/R5/R9). Desired set vs observed set keyed by +// member identity: Added -> add, Modified -> change, Removed -> remove (owned-only) or refuse, +// Unchanged -> noop (absence of a hunk). Add and change are DISTINCT arms and are NOT collapsed +// into one 'upsert'. Only REMOVAL consults ownership (gunbc.ownership Ensured constrains teardown +// only). Generic over member type M and key K — consumers pass (key_of, key_eq, value_eq, +// ownership_of) to membership_reconcile, they NEVER copy-adapt this fn. MemberAction carries `at` = +// the reconcile key so effect realization does not re-derive identity from mutable value. +// FUNCTIONAL-KEY ADMISSION: each population is built through keyed_roster_build BEFORE diffing — +// KeyedRosterBuildDuplicateKey refuses with typed evidence; no diff, no pick-one, no widen. R5 +// removal-owned-only is a CONSTRUCTION WALL on MemberRemovalRefused. membership_reconcile_scoped is +// the KeyRelation/KeyedResourceRelation path for relations with grounded scope (Tailscale vertical +// only until scope lands elsewhere). fn plain_member_rows( members: List, @@ -96,7 +107,18 @@ fn membership_plain_roster_build( ) } -data member_changed_both_sides_note: String = "WHY MemberChanged carries `from` as well as `to`. The prior shape collapsed a Modified hunk into an upsert of the `to` side and DISCARDED `from`, which is lossy in a load-bearing way rather than merely untidy: the prior value is what every safe realization of a change needs. An SCM ref update is a compare-and-swap that names the expected prior (git update-ref ), so without `from` the only reachable realization is the unconditional write and the atomicity the capability already offers cannot be used. A host resource whose address moved must retire the prior endpoint, and `from` is the only place that endpoint survives. gunbc.change_realization is the table that consumes both sides. A NOTE ON WHAT THIS IS NOT: an earlier draft of this change also refused a Modified hunk whose `from` was not Owned, on the reading that replacement is destruction. That was withdrawn — it over-fires on every Ensured consumer and contradicts gunbc.ownership, where Ensured constrains teardown only. Ownership gates removal; it does not gate convergence." +// WHY MemberChanged carries `from` as well as `to`. The prior shape collapsed a Modified hunk into +// an upsert of the `to` side and DISCARDED `from`, which is lossy in a load-bearing way rather than +// merely untidy: the prior value is what every safe realization of a change needs. An SCM ref +// update is a compare-and-swap that names the expected prior (git update-ref ), so +// without `from` the only reachable realization is the unconditional write and the atomicity the +// capability already offers cannot be used. A host resource whose address moved must retire the +// prior endpoint, and `from` is the only place that endpoint survives. gunbc.change_realization is +// the table that consumes both sides. A NOTE ON WHAT THIS IS NOT: an earlier draft of this change +// also refused a Modified hunk whose `from` was not Owned, on the reading that replacement is +// destruction. That was withdrawn — it over-fires on every Ensured consumer and contradicts +// gunbc.ownership, where Ensured constrains teardown only. Ownership gates removal; it does not +// gate convergence. type RemovalAdmission = RemovalAdmitted @@ -308,7 +330,18 @@ fn membership_plan_admitting_scoped_outcome( fn membership_subject_self_state_of(m: M) -> M { m } -data membership_effects_note: String = "The APPLY-grain projection of a plan: partitions a MembershipPlan into effects (add/replace/remove) OR a wholesale refusal. EffectReplace carries BOTH sides because an apply handler that replaces a member must be able to retire the prior realization, not merely write the new one over it; an EffectAdd carries one side because there is nothing there to retire. EffectsOrRefusal is a SUM, not a product: the ApplyRefused arm carries NO effects, so a plan with any refusal is UN-EMITTABLE — a consumer can obtain a List only from the EffectsReady arm, making drop-and-proceed (emit the effects, ignore the refusals) unrepresentable, not merely discouraged (DESIGN §5 construction-over-validation). Refused is PRESERVED and surfaced as MemberRefusal (member+cause: typed+located+counted), never dropped — the spine keeps producing it (plan grain), the apply grain refuses wholesale on it. flat_map (not a concat-in-fold) builds each list in one pass — no accumulator-copy. See membership_effects_wholesale_refuse_policy_trigger for the deliberate scope-dependent policy." +// The APPLY-grain projection of a plan: partitions a MembershipPlan into effects +// (add/replace/remove) OR a wholesale refusal. EffectReplace carries BOTH sides because an apply +// handler that replaces a member must be able to retire the prior realization, not merely write the +// new one over it; an EffectAdd carries one side because there is nothing there to retire. +// EffectsOrRefusal is a SUM, not a product: the ApplyRefused arm carries NO effects, so a plan with +// any refusal is UN-EMITTABLE — a consumer can obtain a List only from the +// EffectsReady arm, making drop-and-proceed (emit the effects, ignore the refusals) +// unrepresentable, not merely discouraged (DESIGN §5 construction-over-validation). Refused is +// PRESERVED and surfaced as MemberRefusal (member+cause: typed+located+counted), never dropped — +// the spine keeps producing it (plan grain), the apply grain refuses wholesale on it. flat_map (not +// a concat-in-fold) builds each list in one pass — no accumulator-copy. See +// membership_effects_wholesale_refuse_policy_trigger for the deliberate scope-dependent policy. fn membership_effects(plan: MembershipPlan) -> EffectsOrRefusal { let refusals = flat_map(plan.actions, a => @@ -345,7 +378,11 @@ type MembershipTally { refusals: Int } -data membership_tally_note: String = "The counts-monoid of a plan (zero = all-zero, combine = componentwise +). ONE catamorphism over the actions computes all four counts in a single pass; the membership_added/change/removal/refusal_count fns are its projections (was three separate folds — DESIGN §2 one-concept, the elegant fold the diff is). The accumulator is a fixed-size record (O(1)/step), not a growing list, so no copy cost." +// The counts-monoid of a plan (zero = all-zero, combine = componentwise +). ONE catamorphism over +// the actions computes all four counts in a single pass; the +// membership_added/change/removal/refusal_count fns are its projections (was three separate folds — +// DESIGN §2 one-concept, the elegant fold the diff is). The accumulator is a fixed-size record +// (O(1)/step), not a growing list, so no copy cost. fn membership_tally(plan: MembershipPlan) -> MembershipTally { fold(plan.actions, init: MembershipTally { adds: 0, changes: 0, removals: 0, refusals: 0 }, f: (acc, a) => @@ -372,7 +409,12 @@ fn membership_change_count(plan: MembershipPlan) -> Int { membership_tally(plan: plan).changes } -data membership_write_count_note: String = "adds + changes — the count of members this plan will WRITE, which is what the former membership_upsert_count actually answered for its consumers ('how much work is left'). It is kept as a DERIVED projection over the two separate tally fields, never as a stored merged counter: an add and a change have different realizations (gunbc.change_realization), so a single stored 'upserts' number is the conflation this change removed and re-storing it would reinstate it. A consumer that genuinely cares which kind of write it is reads adds and changes directly." +// adds + changes — the count of members this plan will WRITE, which is what the former +// membership_upsert_count actually answered for its consumers ('how much work is left'). It is kept +// as a DERIVED projection over the two separate tally fields, never as a stored merged counter: an +// add and a change have different realizations (gunbc.change_realization), so a single stored +// 'upserts' number is the conflation this change removed and re-storing it would reinstate it. A +// consumer that genuinely cares which kind of write it is reads adds and changes directly. fn membership_write_count(plan: MembershipPlan) -> Int { let t = membership_tally(plan: plan) @@ -387,6 +429,22 @@ fn membership_refusal_count(plan: MembershipPlan) -> Int { membership_tally(plan: plan).refusals } +// WHOLESALE-REFUSE is a deliberate, SCOPE-DEPENDENT execution policy (pass-2 review, calm-ferret + +// royal-carp) — this counted mark is its single authority (§6 carrier-is-authority; the design doc +// glosses it, does not own it). membership_effects encodes a POLICY, not only a type: any refusal +// => the WHOLE apply emits nothing. This is separable from the PLAN grain, where 'siblings +// continue' (membership_reconcile still produces every per-member action and counts refusals — an +// owned teardown is recorded next to a non-owned refuse). The correct policy turns on the future +// real observed-provider's SCOPE: observed = owned-artifacts-only => a refusal is a genuine anomaly +// => wholesale-refuse is the right conservative halt (stop the line); observed = everything-on-host +// => foreign members are normal => wholesale-refuse would let any foreign thing block our owned +// teardown, and siblings-continue would be wanted instead. MOOT for the pass-2 deploy poles (apply +// pole has no Removed; retract pole observes owned-only) — no refusal arises today, so this is a +// safety backstop for a future non-degenerate observed provider. DISSOLVES when that real +// observed-provider design lands AND someone makes the conscious wholesale-vs-siblings-continue +// call (editing membership_effects); until then the type must NOT be silently taken to lock the +// choice. + data membership_effects_wholesale_refuse_policy_trigger: Disposition = Scaffold { dissolves_to: RealizationDispatch, bind: DeclarationRef { @@ -395,5 +453,3 @@ data membership_effects_wholesale_refuse_policy_trigger: Disposition = Scaffold field: WholeDeclaration, } } - -data membership_effects_wholesale_refuse_policy_note: String = "WHOLESALE-REFUSE is a deliberate, SCOPE-DEPENDENT execution policy (pass-2 review, calm-ferret + royal-carp) — this counted mark is its single authority (§6 carrier-is-authority; the design doc glosses it, does not own it). membership_effects encodes a POLICY, not only a type: any refusal => the WHOLE apply emits nothing. This is separable from the PLAN grain, where 'siblings continue' (membership_reconcile still produces every per-member action and counts refusals — an owned teardown is recorded next to a non-owned refuse). The correct policy turns on the future real observed-provider's SCOPE: observed = owned-artifacts-only => a refusal is a genuine anomaly => wholesale-refuse is the right conservative halt (stop the line); observed = everything-on-host => foreign members are normal => wholesale-refuse would let any foreign thing block our owned teardown, and siblings-continue would be wanted instead. MOOT for the pass-2 deploy poles (apply pole has no Removed; retract pole observes owned-only) — no refusal arises today, so this is a safety backstop for a future non-degenerate observed provider. DISSOLVES when that real observed-provider design lands AND someone makes the conscious wholesale-vs-siblings-continue call (editing membership_effects); until then the type must NOT be silently taken to lock the choice." diff --git a/dag/gunbc/memory_provisioning.dag b/dag/gunbc/memory_provisioning.dag index 48d24423d8c..fddaa1f615f 100644 --- a/dag/gunbc/memory_provisioning.dag +++ b/dag/gunbc/memory_provisioning.dag @@ -58,36 +58,32 @@ import extdeps.ampere.mt_collins_product_brief.platform { // =========================================================================== -// TWO CONCEPTS, NOT ONE. This module previously carried a single -// RunnerServiceProfile that was simultaneously a reading of the CI runner -// market and a declaration of what gunbc sells. Those are different claims -// with different authorities, and fusing them produced a real miscitation: -// the arm-memory profile declared entitlement = DedicatedPhysicalCore on a -// basis of three providers whose entitlement the census itself records as -// UNSTATED. A provider's size table can inform what our bytes should be; it -// cannot establish what one of OUR vCPUs is backed by, because the provider -// never said what one of THEIRS is. +// TWO CONCEPTS, NOT ONE. This module once carried a single RunnerServiceProfile +// that was both a reading of the CI runner market and a declaration of what +// gunbc sells. Fusing the two produced a real miscitation: the arm-memory +// profile declared entitlement = DedicatedPhysicalCore on a basis of three +// providers whose entitlement the census records as UNSTATED. A provider's size +// table can inform our bytes; it cannot establish what backs one of OUR vCPUs, +// because the provider never said what backs one of THEIRS. // -// MarketShapeBenchmark — a receipt about what a provider ADVERTISED. +// MarketShapeBenchmark — a receipt of what a provider ADVERTISED. // Entitlement may be unstated; the ratio is read // from the census, never re-authored here. -// RunnerProductProfile — gunbc's own SKU declaration. OUR entitlement, -// OUR guaranteed bytes, with the benchmarks it -// was decided against carried as evidence whose -// POLARITY is derived rather than asserted. +// RunnerProductProfile — gunbc's own SKU: OUR entitlement, OUR guaranteed +// bytes, with the benchmarks it was decided against +// as evidence whose POLARITY is derived, not asserted. // -// This follows the standing rule that observations produced by this -// repository are receipts in the observing layer rather than facts authored -// inside the upstream module. +// Standing rule: observations this repository produces are receipts in the +// observing layer, not facts authored inside the upstream module. // =========================================================================== // ONE PROVIDER'S ADVERTISED SHAPE, on one architecture. The ratio field is // deliberately absent: it is DERIVED from the surveyed rows on demand, so a -// benchmark cannot record a figure the census refuses to produce. That is the -// structural half of the evidence-polarity repair below — the old carrier let -// a profile cite GitHub as supporting four GB per vCPU while the census could -// not derive any per-vCPU figure for GitHub at all — its five-vCPU/fourteen-GB -// arm64 size makes the ratio non-integral — because the citation was a bare +// benchmark cannot record a figure the census refuses to produce. This is the +// structural half of the evidence-polarity repair below: the old carrier let a +// profile cite GitHub as supporting four GB per vCPU while the census could +// derive no per-vCPU figure for GitHub at all (its five-vCPU/fourteen-GB arm64 +// size makes the ratio non-integral), because the citation was a bare // DeclarationRef with nothing to disagree with. type MarketShapeBenchmark { provider_label: NonEmptyStr @@ -108,25 +104,22 @@ fn benchmark_reading(benchmark: MarketShapeBenchmark) -> MemoryPerVcpuReading { // --------------------------------------------------------------------------- // EVIDENCE POLARITY, DERIVED. A citation that CONTRADICTS a figure is honest -// context and belongs in the record; what it must never do is read as support -// for that figure. The old CitedBasis was one flat list, so CircleCI — the -// explicit counterexample to four GB per vCPU — sat in the x64 profile's basis -// indistinguishable from the providers that agree, and the profile looked -// better sourced for carrying it. +// context and belongs in the record; it must never read as support. The old +// CitedBasis was one flat list, so CircleCI — the explicit counterexample to +// four GB per vCPU — sat in the x64 profile's basis indistinguishable from the +// providers that agree, and the profile looked better sourced for carrying it. // -// The polarity is not a field an author fills in. It is COMPUTED by comparing -// the declared figure against the census reading, so a benchmark cannot be -// filed on the wrong side: mislabelling would require the census to change its -// answer. That is the construction-over-validation move — a wrongly-polarised -// citation has no way into the record rather than being caught by a check. +// Polarity is not an authored field: it is COMPUTED by comparing the declared +// figure against the census reading, so a benchmark cannot be filed on the +// wrong side — mislabelling would require the census to change its answer. +// Construction over validation: a wrongly-polarised citation has no way in. // -// The comparison is numeral-and-unit-word, deliberately NOT a byte comparison. -// The census ratio is basis-less by construction (a quotient of two figures -// quoted in one unit is a figure in that same unit, and no surveyed page says -// which "GB" it means), while our own profile DECLARES its basis. Resolving -// the market's numeral to bytes in order to compare would assert the basis the -// census exists to withhold; comparing the numerals under a matching unit word -// asks exactly the question the market can answer. +// The comparison is numeral-and-unit-word, deliberately NOT bytes. The census +// ratio is basis-less by construction (a quotient of two figures quoted in one +// unit is a figure in that unit, and no surveyed page says which "GB" it means) +// while our profile DECLARES its basis. Resolving the market's numeral to bytes +// would assert the basis the census exists to withhold; comparing numerals +// under a matching unit word asks exactly what the market can answer. // --------------------------------------------------------------------------- type EvidenceStanding = SupportsDeclaredRatio { advertised_per_entitlement: QuotedByteMagnitude, sizes_agreeing: Nat } @@ -182,10 +175,10 @@ fn standing_supports(standing: EvidenceStanding) -> Bool { } } -// The evidence a profile actually stands on, as a derived verdict rather than -// an authored list. A profile whose consulted benchmarks contain NO supporting -// one is unsupported and says so; carrying only contradicting citations while -// presenting them as a basis is the state this replaces. +// The evidence a profile stands on, as a derived verdict rather than an +// authored list. A profile with NO supporting benchmark is unsupported and says +// so; carrying only contradicting citations as a basis is the state this +// replaces. type ProfileEvidenceVerdict = EvidenceSupported { supporting_count: Nat, contrasting_count: Nat } | EvidenceUnsupported { consulted_count: Nat, contrasting_count: Nat } @@ -229,14 +222,13 @@ fn evidence_verdict( // --------------------------------------------------------------------------- // GUNBC'S OWN SKU. advertised_entitlement is OUR statement about OUR hardware, -// which is why no benchmark is consulted for it: we own the machine, so the -// fact is ours to declare and no provider's silence bears on it. The evidence -// below speaks ONLY to the byte figure, and the module says so here so that a -// later reader cannot mistake a well-evidenced ratio for an evidenced -// entitlement — the exact conflation this split exists to end. +// so no benchmark is consulted for it: we own the machine and no provider's +// silence bears on it. The evidence below speaks ONLY to the byte figure, said +// here so a reader cannot mistake a well-evidenced ratio for an evidenced +// entitlement — the conflation this split ends. // -// consulted_benchmarks is the population the ratio decision was made against, -// including the ones that disagree. Their polarity is derived, so listing a +// consulted_benchmarks is the population the ratio was decided against, +// including the ones that disagree. Polarity is derived, so listing a // counterexample strengthens the record instead of inflating the basis count. // --------------------------------------------------------------------------- type RunnerProductProfile { @@ -269,20 +261,19 @@ fn entitlement_bytes(profile: RunnerProductProfile) -> EntitlementBytes { } } -// HOW MANY ENTITLEMENTS ONE PROCESSOR OFFERS, on the profile's own axis. A -// dedicated-core profile is bounded by physical cores; an SMT profile by -// threads. An unstated entitlement cannot be counted at all — that is the -// honest answer, not a silent fallback to cores, because falling back would -// make an unstated-entitlement profile silently claim the more conservative -// number and read as safe. +// HOW MANY ENTITLEMENTS ONE PROCESSOR OFFERS, on the profile's own axis: a +// dedicated-core profile is bounded by physical cores, an SMT profile by +// threads. An unstated entitlement cannot be counted — the honest answer, not +// a silent fallback to cores, which would let such a profile claim the more +// conservative number and read as safe. // -// NEXT-RUNG NOTE: for a PRODUCT profile the unstated arm is arguably -// unwritable — we know what our own vCPU is — and a DeclaredEntitlement type -// without that variant would climb this refusal to structural impossibility. -// It is not taken here because CpuEntitlement is the shared axis the census -// rows are read on, and forking it for the product side is the nicknaming this -// lane is otherwise removing. Trigger: a product-side entitlement authority -// that the benchmark side projects into, rather than a second enum beside it. +// NEXT-RUNG NOTE: for a PRODUCT profile the unstated arm is arguably unwritable +// — we know what our own vCPU is — and a DeclaredEntitlement type without that +// variant would climb this refusal to structural impossibility. Not taken here +// because CpuEntitlement is the shared axis the census rows are read on, and +// forking it for the product side is the nicknaming this lane removes. Trigger: +// a product-side entitlement authority the benchmark side projects into, rather +// than a second enum beside it. type CpuEntitlementSupply = EntitlementsAvailable { count: Nat } | EntitlementKindUnstated @@ -297,16 +288,16 @@ fn cpu_entitlement_supply(cpu: CpuModelCatalogRow, entitlement: CpuEntitlement) } // --------------------------------------------------------------------------- -// OBSERVED CAPACITY IS NOT ADMISSION. The old fold answered exactly one -// question — "can this machine satisfy the SKU" — and could only return -// populations that already met the target, which is why its bound axis had no -// memory-bound arm. That made the OTHER question unaskable: an existing host -// with 128 GiB installed cannot be told how many units it currently provides, -// because a machine that does not meet the target simply produced no answer. +// OBSERVED CAPACITY IS NOT ADMISSION. The old fold answered one question — +// "can this machine satisfy the SKU" — and returned only populations already +// meeting the target, so its bound axis had no memory-bound arm. That left the +// OTHER question unaskable: an existing host with 128 GiB installed could not +// be told how many units it currently provides, because a machine below target +// produced no answer. // // The live fleet is exactly that partial state, so this is load-bearing for -// accumulation rather than a completeness nicety: without it an existing host -// cannot participate at all until it is fully provisioned. +// accumulation, not a completeness nicety: without it an existing host cannot +// participate until fully provisioned. // // Two separate readings, deliberately: // ObservedCapacity — what a machine PROVIDES. Total, always answerable, @@ -379,13 +370,12 @@ fn observed_capacity_for( } // --------------------------------------------------------------------------- -// WHY A PROFILE READING CAN REFUSE. Architecture is the arm this lane adds and -// it was a live fail-open: the profile carried an architecture, the processor -// row carried an architecture, and nothing compared them, so the x64 profile -// applied to an Altra row derived an SMT target from an arm processor's thread -// count and returned a clean population. That is a fabricated answer to a -// buying question, which is worse than a refusal because it is indistinguishable -// downstream from a grounded one. +// WHY A PROFILE READING CAN REFUSE. Architecture is the arm this lane adds, and +// it was a live fail-open: profile and processor row each carried an +// architecture and nothing compared them, so the x64 profile applied to an +// Altra row derived an SMT target from an arm processor's thread count and +// returned a clean population — a fabricated answer to a buying question, worse +// than a refusal because downstream it is indistinguishable from a grounded one. // --------------------------------------------------------------------------- type ProfileCapacityRefusalCause = ProfileBasisUndeclared @@ -467,21 +457,21 @@ fn observe_installed_capacity( // --------------------------------------------------------------------------- // BOARD SUPPORT AND SELLING POLICY ARE DIFFERENT FACTS. The old fold made a // non-full-channel population unexpressible, which read as "the platform does -// not support it". The ALTRAD8UD documentation says otherwise: its own -// documented populations are one, two, four and eight modules, and the -// four-module shape A1+B1+E1+F1 is explicitly NOT one per channel. A shape the -// vendor documents and we decline to sell is DECLINED, not invalid, and -// collapsing the two hides which authority actually objected. +// not support it". The ALTRAD8UD documentation says otherwise: its documented +// populations are one, two, four and eight modules, and the four-module shape +// A1+B1+E1+F1 is explicitly NOT one per channel. A shape the vendor documents +// and we decline to sell is DECLINED, not invalid; collapsing the two hides +// which authority objected. // // ChannelCoverage — derived platform fact: does this shape light // every channel, or leave some dark? // ChannelPopulationPolicy — gunbc's supplied selling rule. // -// Full-channel shapes are derived from the platform authority's closed -// topology coproduct times the board's channel count, so they are available -// for every board. Non-full-channel shapes exist only where the BOARD -// documents them, and a board with no cited population table refuses under the -// permissive policy rather than having shapes invented for it. +// Full-channel shapes are derived from the platform authority's closed topology +// coproduct times the board's channel count, so every board has them. +// Non-full-channel shapes exist only where the BOARD documents them; a board +// with no cited population table refuses under the permissive policy rather +// than having shapes invented for it. // --------------------------------------------------------------------------- type ChannelCoverage = FullChannel { topology: DimmsPerChannelTopology } @@ -489,14 +479,13 @@ type ChannelCoverage type ChannelPopulationPolicy = FullChannelRequiredForSellableRunner | AnyBoardDocumentedPopulation -// THE BOARD'S SLOT GEOMETRY, as the facts that decide a population: how many -// channels the memory controller drives, the deepest linear topology the -// platform admits, the DRAM generation the platform speaks, and whichever -// populations the board's own documentation states. The topology axis is -// imported from the Altra platform authority rather than re-coined as a slot -// integer, because that specification states the admitted topologies as a -// closed set — a population outside them is unsupported, not a smaller -// supported one. +// THE BOARD'S SLOT GEOMETRY, as the facts that decide a population: channels +// the memory controller drives, deepest linear topology the platform admits, +// DRAM generation the platform speaks, and the populations the board's own +// documentation states. The topology axis is imported from the Altra platform +// authority rather than re-coined as a slot integer, because that specification +// states the admitted topologies as a closed set — a population outside them is +// unsupported, not a smaller supported one. type MemoryChannelGeometry { board_label: NonEmptyStr channels: Nat @@ -537,17 +526,16 @@ fn full_channel_shapes(g: MemoryChannelGeometry) -> List { map(admitted_topologies(g: g), t => full_channel_shape(g: g, topology: t)) } -// The coverage a documented module count produces. A count that happens to -// equal a full-channel count IS full channel and is reported as such, so the -// two shape sources agree rather than double-counting the eight-module row. +// The coverage a documented module count produces. A count equal to a +// full-channel count IS full channel and is reported as such, so the two shape +// sources agree rather than double-counting the eight-module row. // -// The search runs over the topologies the BOARD admits, not over the whole -// coproduct. Matching against every topology unconditionally would let a -// sixteen-module population on a one-DIMM-per-channel board report -// FullChannel{TwoDimmsPerChannel} — a coverage claim citing a topology that -// board's platform does not admit. No current row can reach that state, which -// is exactly why it is worth closing by construction rather than leaving for -// the first board that can. +// The search runs over the topologies the BOARD admits, not the whole +// coproduct: matching every topology would let a sixteen-module population on +// a one-DIMM-per-channel board report FullChannel{TwoDimmsPerChannel}, a +// coverage claim citing a topology that board's platform does not admit. No +// current row can reach that state, which is why it is closed by construction +// rather than left for the first board that can. type CoverageSearch = CoverageFound { topology: DimmsPerChannelTopology } | CoverageNotFound fn coverage_search_step(acc: CoverageSearch, g: MemoryChannelGeometry, dimm_count: Nat, topology: DimmsPerChannelTopology) -> CoverageSearch { @@ -595,11 +583,10 @@ fn coverage_is_full_channel(coverage: ChannelCoverage) -> Bool { } } -// The shapes a POLICY admits, and the reason the permissive arm can refuse: it -// reads the board's documentation, and a board whose population table this -// repository has not cited has none to read. Inventing shapes from the slot -// count would be exactly the fabricated platform-capability claim the split -// exists to prevent. +// The shapes a POLICY admits, and why the permissive arm can refuse: it reads +// the board's documentation, and a board whose population table this +// repository has not cited has none. Inventing shapes from the slot count would +// be the fabricated platform-capability claim the split exists to prevent. type ShapeSelection = ShapesSelected { admitted: List, declined_by_policy: List } | ShapeSelectionRefused { cause: PopulationRefusalCause } @@ -624,7 +611,7 @@ fn select_shapes(g: MemoryChannelGeometry, policy: ChannelPopulationPolicy) -> S // // margin_bytes NAMES THE OVERSHOOT. Slot geometry and per-DIMM capacities are // both coarse, so a population that meets a target almost always exceeds it; -// reporting only "met" would let a purchase round silently up to the next SKU. +// reporting only "met" would let a purchase silently round up to the next SKU. type PopulationOption { module: DramModuleCatalogRow dimm_count: Nat @@ -636,13 +623,13 @@ type PopulationOption { // WHY A DERIVATION CAN REFUSE, and why each cause carries its numbers: a // refusal a buyer cannot act on is typed but not located. // -// ModuleGenerationUnsupportedByPlatform is this lane's second fail-open -// repair. Offered modules were folded into populations with no join against -// the platform at all, so a DDR5 row on the DDR4 Altra platform produced a -// clean admissible population whose capacity arithmetic was correct and whose -// electrical premise was false. The refusal names both generations rather than -// dropping the module, because silently skipping an incompatible row would -// narrow the answer while reporting success. +// ModuleGenerationUnsupportedByPlatform is this lane's second fail-open repair. +// Offered modules were folded into populations with no join against the +// platform, so a DDR5 row on the DDR4 Altra platform produced a clean +// admissible population with correct capacity arithmetic and a false electrical +// premise. The refusal names both generations rather than dropping the module: +// silently skipping an incompatible row would narrow the answer while reporting +// success. type PopulationRefusalCause = TargetUnreachableInSlotGeometry { required_bytes: ByteSize @@ -751,13 +738,13 @@ fn best_reachable_bytes( // THE DERIVATION. (board geometry x selling policy x sourced requirement x // offered modules) -> the populations that meet the requirement, each with its -// named overshoot and its channel coverage; or one typed refusal that says why -// none does. +// named overshoot and channel coverage; or one typed refusal saying why none +// does. // // The module list is offered by the caller rather than read from a global // catalog because what is BUYABLE is an inventory fact, not a specification -// fact — the sibling inventory ledger owns that population, and this fold -// stays a pure derivation over whatever is offered. +// fact — the sibling inventory ledger owns that population; this fold stays a +// pure derivation over whatever is offered. fn derive_populations( geometry: MemoryChannelGeometry, policy: ChannelPopulationPolicy, @@ -803,10 +790,10 @@ fn derive_populations( } // A mixed-capacity population is refused rather than derived. It is a separate -// entry point instead of an arm inside the fold above because the fold has no -// way to express one: every option it builds carries a single module row. This -// names the refusal for a caller that holds two capacities and would otherwise -// have to infer the rule from the fold's shape. +// entry point because the fold above cannot express one: every option it +// builds carries a single module row. This names the refusal for a caller +// holding two capacities that would otherwise infer the rule from the fold's +// shape. fn admit_mixed_capacity_population(geometry: MemoryChannelGeometry) -> PopulationDerivation { PopulationDerivationRefused { board_label: geometry.board_label, @@ -862,10 +849,10 @@ fn capacity_for_option( // profile x offered modules) -> per-profile admissible populations with their // servable capacity. // -// The target every population is admitted against is the profile's own -// per-entitlement figure times the entitlements this processor supplies, so a -// dedicated-core profile and an SMT profile on the SAME processor produce -// different targets, which is the whole point of carrying the axis. +// Each population is admitted against the profile's per-entitlement figure +// times the entitlements this processor supplies, so a dedicated-core profile +// and an SMT profile on the SAME processor produce different targets — the +// point of carrying the axis. fn derive_profile_capacity( geometry: MemoryChannelGeometry, policy: ChannelPopulationPolicy, @@ -904,10 +891,9 @@ fn derive_profile_capacity( } // The per-profile fan-out: one derivation per declared profile, so a candidate -// board carries an arm-value reading, an arm-memory reading and an x64 reading -// side by side rather than one blended number. With the architecture join in -// place the x64 reading against an arm board is now a refusal rather than a -// number, which is the point. +// board carries arm-value, arm-memory and x64 readings side by side rather than +// one blended number. With the architecture join, the x64 reading against an +// arm board is now a refusal rather than a number, which is the point. fn derive_all_profile_capacities( geometry: MemoryChannelGeometry, policy: ChannelPopulationPolicy, @@ -932,9 +918,9 @@ fn derivation_option_count(d: PopulationDerivation) -> Nat { // =========================================================================== // ROWS: the benchmarks surveyed, the SKUs declared, and the boards answered. // -// These are receipts in the observing layer, not facts authored inside the -// upstream modules: each one DERIVES its fields from a cited row and carries -// that row's authority, so the derivation is joinable back to its source. +// Receipts in the observing layer, not facts authored inside the upstream +// modules: each DERIVES its fields from a cited row and carries that row's +// authority, so the derivation is joinable back to its source. // =========================================================================== data ubicloud_shapes_ref: DeclarationRef = decl_ref( @@ -968,10 +954,9 @@ data circleci_shapes_ref: DeclarationRef = decl_ref( ) // THE SURVEYED BENCHMARKS. advertised_entitlement records what each provider -// SAYS backs one of its vCPUs — and for five of six that is nothing at all. -// Ubicloud alone states a dedicated Altra core; Blacksmith's "bare metal" is a -// claim about the host rather than about one advertised vCPU and is not -// promoted to an entitlement. +// SAYS backs one of its vCPUs — for five of six, nothing. Ubicloud alone states +// a dedicated Altra core; Blacksmith's "bare metal" is a claim about the host, +// not one advertised vCPU, and is not promoted to an entitlement. data ubicloud_arm_benchmark: MarketShapeBenchmark = MarketShapeBenchmark { provider_label: "Ubicloud", provider: ubicloud_provider_identity, @@ -1037,11 +1022,11 @@ data warpbuild_x64_benchmark: MarketShapeBenchmark = MarketShapeBenchmark { } // The explicit COUNTEREXAMPLE, carried on purpose. CircleCI's x86 Docker -// classes are two GB per vCPU, so it disagrees with the four-GB figure the SKU +// classes are two GB per vCPU, disagreeing with the four-GB figure the SKU // below declares. It is consulted rather than omitted because a basis built -// only from the providers that agree looks better sourced while being less -// honest about the spread — and because its polarity is DERIVED, listing it -// cannot inflate the support count. +// only from agreeing providers looks better sourced while hiding the spread — +// and because its polarity is DERIVED, listing it cannot inflate the support +// count. data circleci_x64_benchmark: MarketShapeBenchmark = MarketShapeBenchmark { provider_label: "CircleCI", provider: circleci_provider_identity, @@ -1066,13 +1051,13 @@ fn surveyed_market_benchmarks() -> List { // THE VALUE SKU: three GB per DEDICATED physical core. The entitlement is OUR // declaration about OUR hardware; the byte figure is what two surveyed arm64 -// surfaces publish, and Ubicloud — the one provider that states a dedicated -// Altra core — is among them, so the ratio was read against machines of the -// same family this purchase is about. +// surfaces publish, among them Ubicloud — the one provider stating a dedicated +// Altra core — so the ratio was read against machines of the family this +// purchase is about. // // The basis is declared decimal because that is how the cited surfaces quote -// it; declaring it through the constructor is what makes the resulting byte -// target inspectable rather than assumed. +// it; declaring it through the constructor makes the byte target inspectable +// rather than assumed. data arm_value_profile: RunnerProductProfile = RunnerProductProfile { label: "arm-value", architecture: Aarch64, @@ -1081,16 +1066,16 @@ data arm_value_profile: RunnerProductProfile = RunnerProductProfile { consulted_benchmarks: [ubicloud_arm_benchmark, blacksmith_arm_benchmark, github_arm_benchmark], } -// THE MEMORY SKU: four GB per entitlement. Carried as a separate SKU rather -// than as a correction to the one above, because both figures are real: the -// market offers both, and which one gunbc sells is a policy choice rather than -// a fact to be resolved by averaging. +// THE MEMORY SKU: four GB per entitlement. A separate SKU rather than a +// correction to the one above: both figures are real, the market offers both, +// and which one gunbc sells is a policy choice, not a fact resolved by +// averaging. // -// Its consulted set includes GitHub, whose arm64 sizes the census cannot -// reduce to any per-vCPU figure (the five-vCPU/fourteen-GB size makes the -// ratio non-integral). Under the old flat basis that citation read as support -// for four GB; here it derives as BenchmarkUnreadable and the SKU stands on -// Depot and WarpBuild, which is what was actually true all along. +// Its consulted set includes GitHub, whose arm64 sizes the census cannot reduce +// to any per-vCPU figure (the five-vCPU/fourteen-GB size makes the ratio +// non-integral). Under the old flat basis that read as support for four GB; +// here it derives as BenchmarkUnreadable and the SKU stands on Depot and +// WarpBuild, which was true all along. data arm_memory_profile: RunnerProductProfile = RunnerProductProfile { label: "arm-memory", architecture: Aarch64, @@ -1120,12 +1105,12 @@ fn declared_product_profiles() -> List { } // --------------------------------------------------------------------------- -// THE BOARDS. Each geometry DERIVES its channel count from the board's own -// cited mechanical row (total slots over DIMMs per channel) rather than -// authoring a channel integer beside it, so a board row and its geometry -// cannot disagree. The DRAM generation comes from the Altra platform authority -// rather than from each board's page, because it is a platform fact and one of -// the three boards states it only as a data rate. +// THE BOARDS. Each geometry DERIVES its channel count from the board's cited +// mechanical row (total slots over DIMMs per channel) rather than authoring a +// channel integer beside it, so a board row and its geometry cannot disagree. +// The DRAM generation comes from the Altra platform authority, not each board's +// page: it is a platform fact and one of the three boards states it only as a +// data rate. // --------------------------------------------------------------------------- fn derived_channel_count(dimm_slots_total: Nat, dimms_per_channel: Nat) -> Nat { @@ -1133,11 +1118,11 @@ fn derived_channel_count(dimm_slots_total: Nat, dimms_per_channel: Nat) -> Nat { } // The ASRock ALTRAD8UD-1L2T. Eight slots at one DIMM per channel, so the -// one-per-channel population IS the full population for this board. Its -// documented populations are carried because they are the specimen that makes -// the policy split necessary: the board documents a FOUR-module shape -// (A1+B1+E1+F1) that is not one per channel, so under the sellable-runner -// policy it is declined rather than invalid. +// one-per-channel population IS the full population. Its documented +// populations are the specimen that makes the policy split necessary: the +// board documents a FOUR-module shape (A1+B1+E1+F1) that is not one per +// channel, so under the sellable-runner policy it is declined rather than +// invalid. data asrock_altrad8ud_1l2t_geometry: MemoryChannelGeometry = MemoryChannelGeometry { board_label: "ASRock Rack ALTRAD8UD-1L2T", channels: derived_channel_count( @@ -1152,9 +1137,9 @@ data asrock_altrad8ud_1l2t_geometry: MemoryChannelGeometry = MemoryChannelGeomet // The Supermicro R12SPD-A: sixteen slots at two DIMMs per channel on ONE // socket, the memory-richest single-socket geometry in this census. Its vendor -// surface states slot count and depth but publishes no supported-population -// table, so documented_populations is empty and the permissive policy REFUSES -// for this board rather than having shapes invented from the slot count. +// surface states slot count and depth but no supported-population table, so +// documented_populations is empty and the permissive policy REFUSES for this +// board rather than inventing shapes from the slot count. data supermicro_r12spd_a_geometry: MemoryChannelGeometry = MemoryChannelGeometry { board_label: "Supermicro R12SPD-A", channels: derived_channel_count( @@ -1168,13 +1153,13 @@ data supermicro_r12spd_a_geometry: MemoryChannelGeometry = MemoryChannelGeometry } // The Mt. Collins platform, PER SOCKET. The machine is dual-socket with -// thirty-two DIMM connectors, and this row deliberately describes ONE socket's -// sixteen: the derivation's other input is a single CpuModelCatalogRow, so a -// geometry carrying both sockets' slots against one processor's entitlements -// would state a memory target for hardware the CPU row does not describe. A -// two-socket machine is two of these rows and two processors, and this module -// does not currently fold that pair — a smaller claim than the machine, and -// named rather than silently averaged. +// thirty-two DIMM connectors; this row describes ONE socket's sixteen because +// the derivation's other input is a single CpuModelCatalogRow, and a geometry +// carrying both sockets' slots against one processor's entitlements would state +// a memory target for hardware the CPU row does not describe. A two-socket +// machine is two of these rows and two processors, which this module does not +// currently fold — a smaller claim than the machine, named rather than silently +// averaged. data mt_collins_per_socket_geometry: MemoryChannelGeometry = MemoryChannelGeometry { board_label: "Ampere Mt. Collins (per socket)", channels: derived_channel_count( @@ -1192,12 +1177,11 @@ fn answered_candidate_geometries() -> List { } // THE CANDIDATE SET THIS MODULE CAN ANSWER FOR, and what it still cannot. The -// three boards above now carry cited geometry. What remains absent is the -// Ampere Q80-30's own catalog row on the processor side of some candidate -// builds, and the second ASRock row; none of them is minted here, because a -// geometry invented to make a derivation runnable is a fabricated input to a -// buying decision, which is worse than an absent one because it is -// indistinguishable from a cited one downstream. +// three boards above carry cited geometry. Still absent: the Ampere Q80-30's +// own catalog row on the processor side of some candidate builds, and the +// second ASRock row. Neither is minted here: a geometry invented to make a +// derivation runnable is a fabricated input to a buying decision, worse than an +// absent one because downstream it is indistinguishable from a cited one. fn answered_candidate_board_count() -> Nat { count(answered_candidate_geometries()) } diff --git a/dag/gunbc/merge_admission.dag b/dag/gunbc/merge_admission.dag index 51b36a49965..c6ff6b6dfee 100644 --- a/dag/gunbc/merge_admission.dag +++ b/dag/gunbc/merge_admission.dag @@ -383,11 +383,34 @@ type CheckCoverage required_roster: ContentHash } -data check_coverage_subject_universe_note: String = "CHECK COVERAGE IS A TOTAL CLASSIFICATION OVER AN EXPLICIT SUBJECT UNIVERSE (operator mandate 2026-08-01). The subject is (required PR head, required gate-roster identity, nonempty required gate population). No check runs is CheckCoverageAbsent, never an empty list that all() can call green. A run still executing is Running. A dependency stop is Stopped and carries both the completed and UNEXECUTED populations plus a cause, so batch-1 drift stopping the witness batches cannot masquerade as a witness verdict. Complete is the only arm admission examines for success. Stale carries both observed and required identities; it is not repaired by relabeling the observation. Adding a coverage state requires a new coproduct arm and turns every exhaustive consumer red until classified — unclassified is structurally unavailable." - -data check_coverage_external_consumer_boundary_note: String = "BOUNDARY, NOT AN END-TO-END CLOSURE CLAIM. This module is the repository's admission authority and now makes the correct semantics representable and executable. The live fail-open observed 2026-08-01 was in the external dashboard readout: it reported no failing or pending checks with ok=true when the healed head had NO run. That consumer is ctrl session-dashboard: scripts/session-dashboard/lib/summarize_checks.mjs supplies the check classification consumed by pr_merge_ready_digest.mjs and server.mjs. It lives outside this repository and is not changed here. Until it conforms to classify_check_coverage_admission, the operational rule is stricter than its readout: never merge unless an executing run explicitly names the current PR head and reaches every required witness; an auto-heal head with no subsequent run is untested." - -data check_coverage_external_consumer_adoption_receipt_in_flight: String = "ADOPTION IN FLIGHT, NOT COMPLETE (observed 2026-08-01). gunb-ai/ctrl PR #1922, Adopt gunbc exact-head CI admission semantics in dashboard checks, is OPEN, non-draft, and mergeStateStatus CLEAN. It maps CANCELLED runs to superseded rather than failed, an empty check rollup to none rather than passing, and permits the merge gate only when checksState is passing. This receipt closes only after #1922 is merged AND the live session-dashboard consumer is observed reading those semantics. gunbc records that external obligation but does not own or claim completion of the ctrl change. The repeated cancelled-run false negatives observed while validating gunbc PR #7544 are the live symptom this adoption removes." +// CHECK COVERAGE IS A TOTAL CLASSIFICATION OVER AN EXPLICIT SUBJECT UNIVERSE (operator mandate +// 2026-08-01). The subject is (required PR head, required gate-roster identity, nonempty required +// gate population). No check runs is CheckCoverageAbsent, never an empty list that all() can call +// green. A run still executing is Running. A dependency stop is Stopped and carries both the +// completed and UNEXECUTED populations plus a cause, so batch-1 drift stopping the witness batches +// cannot masquerade as a witness verdict. Complete is the only arm admission examines for success. +// Stale carries both observed and required identities; it is not repaired by relabeling the +// observation. Adding a coverage state requires a new coproduct arm and turns every exhaustive +// consumer red until classified — unclassified is structurally unavailable. + +// BOUNDARY, NOT AN END-TO-END CLOSURE CLAIM. This module is the repository's admission authority +// and now makes the correct semantics representable and executable. The live fail-open observed +// 2026-08-01 was in the external dashboard readout: it reported no failing or pending checks with +// ok=true when the healed head had NO run. That consumer is ctrl session-dashboard: +// scripts/session-dashboard/lib/summarize_checks.mjs supplies the check classification consumed by +// pr_merge_ready_digest.mjs and server.mjs. It lives outside this repository and is not changed +// here. Until it conforms to classify_check_coverage_admission, the operational rule is stricter +// than its readout: never merge unless an executing run explicitly names the current PR head and +// reaches every required witness; an auto-heal head with no subsequent run is untested. + +// ADOPTION IN FLIGHT, NOT COMPLETE (observed 2026-08-01). gunb-ai/ctrl PR #1922, Adopt gunbc +// exact-head CI admission semantics in dashboard checks, is OPEN, non-draft, and mergeStateStatus +// CLEAN. It maps CANCELLED runs to superseded rather than failed, an empty check rollup to none +// rather than passing, and permits the merge gate only when checksState is passing. This receipt +// closes only after #1922 is merged AND the live session-dashboard consumer is observed reading +// those semantics. gunbc records that external obligation but does not own or claim completion of +// the ctrl change. The repeated cancelled-run false negatives observed while validating gunbc PR +// #7544 are the live symptom this adoption removes. type CheckCoverageAdmissionVerdict = CheckCoverageAdmitted @@ -631,9 +654,27 @@ fn classify_merge_admission_verdict( } } -data merge_admission_attempt_scope_note: String = "ATTEMPT SCOPE (ruling 2026-07-30): the receipt carries the walk-attempt identity in the PAYLOAD, not just the path, and the gate requires receipt.attempt_id == current — path identity alone is not enough, because a misrouted read must fail on the content too. A receipt from another attempt is neither stale nor fresh: it is NOT THE SUBJECT, so MergeDeniedWrongAttempt is checked FIRST, before conclusion, base, and roster — asking whether a foreign receipt's base is current would be answering a question about the wrong run. The identity derives from GITHUB_RUN_ID + GITHUB_RUN_ATTEMPT + GITHUB_JOB on GitHub; non-GitHub execution must supply GUNBC_WALK_ATTEMPT_ID explicitly or the walk-side composition refuses — never a silent constant like a bare local, which would make every local run one attempt and the wrong-attempt refusal unreachable off CI. Deleting or recreating the attempt directory at walk start remains hygiene; correctness never depends on cleaning a shared path." - -data git_object_id_grounding_note: String = "The tested base tree is a GitObjectId, not a ContentHash. DESIGN's own §3 residue records that ContentHash is one brand over two unrelated hash families (structural fnv1a64 fingerprints and cited cryptographic digests) with no modeled discriminant; a Git tree object id is a THIRD domain, and admitting it into the same brand would let a gate-roster structural fingerprint inhabit the tested-tree field and typecheck. extdeps.git.object_store.GitObjectId is the existing single authority and already models the SHA-1/SHA-256 repository-format transition, so this grounds on it rather than minting a GitTreeOid nickname beside it (§3). Equality and the validating textual constructors live THERE, not here: both are generic Git behaviour, and holding them in this consumer was a fork this module briefly carried (review 2026-07-30)." +// ATTEMPT SCOPE (ruling 2026-07-30): the receipt carries the walk-attempt identity in the PAYLOAD, +// not just the path, and the gate requires receipt.attempt_id == current — path identity alone is +// not enough, because a misrouted read must fail on the content too. A receipt from another attempt +// is neither stale nor fresh: it is NOT THE SUBJECT, so MergeDeniedWrongAttempt is checked FIRST, +// before conclusion, base, and roster — asking whether a foreign receipt's base is current would be +// answering a question about the wrong run. The identity derives from GITHUB_RUN_ID + +// GITHUB_RUN_ATTEMPT + GITHUB_JOB on GitHub; non-GitHub execution must supply GUNBC_WALK_ATTEMPT_ID +// explicitly or the walk-side composition refuses — never a silent constant like a bare local, +// which would make every local run one attempt and the wrong-attempt refusal unreachable off CI. +// Deleting or recreating the attempt directory at walk start remains hygiene; correctness never +// depends on cleaning a shared path. + +// The tested base tree is a GitObjectId, not a ContentHash. DESIGN's own §3 residue records that +// ContentHash is one brand over two unrelated hash families (structural fnv1a64 fingerprints and +// cited cryptographic digests) with no modeled discriminant; a Git tree object id is a THIRD +// domain, and admitting it into the same brand would let a gate-roster structural fingerprint +// inhabit the tested-tree field and typecheck. extdeps.git.object_store.GitObjectId is the existing +// single authority and already models the SHA-1/SHA-256 repository-format transition, so this +// grounds on it rather than minting a GitTreeOid nickname beside it (§3). Equality and the +// validating textual constructors live THERE, not here: both are generic Git behaviour, and holding +// them in this consumer was a fork this module briefly carried (review 2026-07-30). // tested_base_commit_sha REPLACES a tested_base_tree_hash and CLOSES A FALSE-ADMIT ARM; see gunbc.merge_admission_subject TestedSubject // for the executed demonstration. In short: a three-way merge reads the MERGE BASE, which ancestry @@ -667,7 +708,17 @@ type MergeAdmissionReceiptV2 { conclusion: CheckConclusion } -data merge_admission_subject_binding_note: String = "THE GATE CONSUMES THE CAPTURED SUBJECT, not merely a self-describing receipt (review 2026-07-30). The v2 receipt carried tested_head_sha from the start, and nothing read it: the classifier checked attempt, conclusion, base, roster, so a receipt was free to RESTATE which subject it allegedly certified and be believed. The subject is captured before the floor runs and is the independent fact; the receipt is the floor's claim about it. Three bindings are therefore required BEFORE any freshness question — same attempt, same head, same base tree — and a mismatch is MergeDeniedSubjectMismatch, its own state: the receipt is well-formed and from this attempt, but it describes work on something other than what was captured, which is neither staleness (a fact about the world moving) nor a wrong attempt (a fact about which run). Asking whether such a receipt's base is current would be answering a question about the wrong subject, which is exactly why WrongAttempt is first and SubjectMismatch is second." +// THE GATE CONSUMES THE CAPTURED SUBJECT, not merely a self-describing receipt (review 2026-07-30). +// The v2 receipt carried tested_head_sha from the start, and nothing read it: the classifier +// checked attempt, conclusion, base, roster, so a receipt was free to RESTATE which subject it +// allegedly certified and be believed. The subject is captured before the floor runs and is the +// independent fact; the receipt is the floor's claim about it. Three bindings are therefore +// required BEFORE any freshness question — same attempt, same head, same base tree — and a mismatch +// is MergeDeniedSubjectMismatch, its own state: the receipt is well-formed and from this attempt, +// but it describes work on something other than what was captured, which is neither staleness (a +// fact about the world moving) nor a wrong attempt (a fact about which run). Asking whether such a +// receipt's base is current would be answering a question about the wrong subject, which is exactly +// why WrongAttempt is first and SubjectMismatch is second. // BOTH COMPARISONS GAIN THE COMMIT OPERAND, AND NEITHER COLLAPSES INTO THE OTHER. The subject // binding asks "does this receipt describe the subject we captured"; the staleness check asks "has @@ -712,7 +763,15 @@ fn merge_admission_verdict_label(v: MergeAdmissionVerdict) -> String { } } -data merge_admission_current_context_staging_note: String = "STAGED INVOCABLE ENTRY, NOT A LIVE CONSUMER (review 46238, incident 2026-08-01). tools.merge_admission_current_context can fetch the current merge target, read attempt-scoped TestedSubject and MergeAdmissionReceiptV2 wires, and classify them against current_gate_roster_hash, but no production path invokes the entry and no production path mints either V2 wire. It therefore closes no runtime admission gap today. What IS delivered and executing is the discovery-corpus control pair: classify_merge_admission_verdict_v2 refuses a receipt carrying a synthetic changed-roster hash against the current roster, and admits a receipt minted under the current roster. A correctly computed but uninvoked verdict is staging, not a warning and not a wall." +// STAGED INVOCABLE ENTRY, NOT A LIVE CONSUMER (review 46238, incident 2026-08-01). +// tools.merge_admission_current_context can fetch the current merge target, read attempt-scoped +// TestedSubject and MergeAdmissionReceiptV2 wires, and classify them against +// current_gate_roster_hash, but no production path invokes the entry and no production path mints +// either V2 wire. It therefore closes no runtime admission gap today. What IS delivered and +// executing is the discovery-corpus control pair: classify_merge_admission_verdict_v2 refuses a +// receipt carrying a synthetic changed-roster hash against the current roster, and admits a receipt +// minted under the current roster. A correctly computed but uninvoked verdict is staging, not a +// warning and not a wall. data merge_admission_v2_production_wires_dissolution_trigger: DissolutionCondition = unbound_dissolution(description: "FIRST DISSOLVE-ON: production captures TestedSubject before the floor, mints MergeAdmissionReceiptV2 after the floor, and persists both through merge_admission_tested_subject_path and merge_admission_floor_receipt_path for the same WalkAttemptId. Until both producers execute, tools.merge_admission_current_context has no production subject or evidence to consume and cannot be called live.") diff --git a/dag/gunbc/merge_admission_produce.dag b/dag/gunbc/merge_admission_produce.dag index 123baae8913..b75169d2a89 100644 --- a/dag/gunbc/merge_admission_produce.dag +++ b/dag/gunbc/merge_admission_produce.dag @@ -47,8 +47,21 @@ data merge_admission_receipt_rel_path: String = ".gunbc/merge-admission-receipt. data merge_admission_receipt_schema_v2: String = "gunbc.merge_admission_receipt.v3" data wire_exact_arity_note: String = "BOTH v2 parsers require an EXACT line count, and every required field goes through its domain constructor (review 2026-07-30). The first cut accepted a SUPERSET — `length(lines) >= 5` — so trailing content rode along unread, and it validated only that the schema tag and attempt id were nonempty, leaving base ref, tree, and head SHA free to be empty strings that then bound into a receipt as though observed. Worse, the optional PR line converted a MALFORMED present field into an absent one: parse_int returned Absent on garbage and the receipt parsed clean with pr_number: none, which is the state-space conflation §5 forbids — `not supplied` and `supplied but unreadable` are different facts with different remedies. A seventh line that is not an integer now refuses the whole receipt. Exact arity is what makes a format extension VISIBLE: adding a field means bumping the schema tag, not silently appending a line older readers ignore." -data gate_roster_hash_wire_note: String = "Gate roster hash wire is the serialized fnv1a64 structural fingerprint (16 lowercase hex digits — atom_identity_hash / hash_combine wire form). parse_gate_roster_hash_wire routes through std.content_hash.fnv1a64_structural_hex_digest (exact length plus lowercase-hex syntax) before branding as GateRosterHash; blank, malformed, or wrong-length receipt lines refuse the whole receipt (review 45474). An earlier cut cast arbitrary text to NonEmptyStr via structural_content_hash unchecked." -data receipt_wire_v2_note: String = "Wire v2 is SELF-IDENTIFYING: line 1 is the schema tag, so a v1 wire (positional, no tag) read by the v2 parser refuses on the tag line instead of mis-binding fields by position — and a v2 wire read by the v1 parser mis-parses into a conclusion-line mismatch, also a refusal. Lines (v3): schema, attempt_id, gate_roster_hash, conclusion, tested_head_sha, tested_base_commit_sha, then the optional pr number. v3 REPLACED the tested_base_tree_hash line with tested_base_commit_sha rather than adding one: a commit determines its tree, and the merge base the merge actually reads is fixed by ancestry a tree does not carry. The v1 wire, path, and type stay until the entry migration completes and the old consumers delete with the emitter web (add-replacement, never in-place mutation of a live format)." +// Gate roster hash wire is the serialized fnv1a64 structural fingerprint (16 lowercase hex digits — +// atom_identity_hash / hash_combine wire form). parse_gate_roster_hash_wire routes through +// std.content_hash.fnv1a64_structural_hex_digest (exact length plus lowercase-hex syntax) before +// branding as GateRosterHash; blank, malformed, or wrong-length receipt lines refuse the whole +// receipt (review 45474). An earlier cut cast arbitrary text to NonEmptyStr via +// structural_content_hash unchecked. +// Wire v2 is SELF-IDENTIFYING: line 1 is the schema tag, so a v1 wire (positional, no tag) read by +// the v2 parser refuses on the tag line instead of mis-binding fields by position — and a v2 wire +// read by the v1 parser mis-parses into a conclusion-line mismatch, also a refusal. Lines (v3): +// schema, attempt_id, gate_roster_hash, conclusion, tested_head_sha, tested_base_commit_sha, then +// the optional pr number. v3 REPLACED the tested_base_tree_hash line with tested_base_commit_sha +// rather than adding one: a commit determines its tree, and the merge base the merge actually reads +// is fixed by ancestry a tree does not carry. The v1 wire, path, and type stay until the entry +// migration completes and the old consumers delete with the emitter web (add-replacement, never +// in-place mutation of a live format). fn render_receipt_wire_v2(receipt: MergeAdmissionReceiptV2) -> String { let core = join([ @@ -66,7 +79,13 @@ fn render_receipt_wire_v2(receipt: MergeAdmissionReceiptV2) -> String { } } -data wire_pr_number_three_states_note: String = "THREE states, named, because two of them are not the same absence: no seventh line at all (the receipt legitimately carries no PR number), a seventh line that parses, and a seventh line that does not. An Optional> would spell this with two Absents meaning different things — the state-space conflation DESIGN's failure list names — and the language refuses the spelling anyway, since `??` is the null-coalesce operator token and cannot appear in type position. WirePrMalformed is what makes the whole receipt refuse instead of quietly becoming a receipt with no PR number." +// THREE states, named, because two of them are not the same absence: no seventh line at all (the +// receipt legitimately carries no PR number), a seventh line that parses, and a seventh line that +// does not. An Optional> would spell this with two Absents meaning different things — +// the state-space conflation DESIGN's failure list names — and the language refuses the spelling +// anyway, since `??` is the null-coalesce operator token and cannot appear in type position. +// WirePrMalformed is what makes the whole receipt refuse instead of quietly becoming a receipt with +// no PR number. type ReceiptWirePrNumber = WirePrAbsent diff --git a/dag/gunbc/merge_admission_subject.dag b/dag/gunbc/merge_admission_subject.dag index 157636a6783..a64561a99e2 100644 --- a/dag/gunbc/merge_admission_subject.dag +++ b/dag/gunbc/merge_admission_subject.dag @@ -11,7 +11,11 @@ import extdeps.git.object_store { } import gunbc.repo_identity { gunbc_merge_target_ref } -data merge_admission_subject_defusion_note: String = "WalkAttemptId, TestedSubject, and their path/wire identity live here because capture and admission classification both consume them. This is a MOVE from gunbc.merge_admission and gunbc.merge_admission_produce, not a second representation: the domain modules import this carrier home. Keeping the checkout capture out of the classifier's commit-roster closure makes its declared Negligible profile measurable at the realization boundary." +// WalkAttemptId, TestedSubject, and their path/wire identity live here because capture and +// admission classification both consume them. This is a MOVE from gunbc.merge_admission and +// gunbc.merge_admission_produce, not a second representation: the domain modules import this +// carrier home. Keeping the checkout capture out of the classifier's commit-roster closure makes +// its declared Negligible profile measurable at the realization boundary. // The attempt identity is a PATH SEGMENT, because it is concatenated into one: merge_admission_attempt_dir builds /.gunbc/merge-admission//. As a bare string it was neither path-safe nor wire-safe. Both producers — GitHub-derived composition and explicit GUNBC_WALK_ATTEMPT_ID — go through walk_attempt_id, the only constructor, and refuse rather than sanitizing. type WalkAttemptId = PathSegment where brand("WalkAttemptId") @@ -56,7 +60,18 @@ type TestedSubject { base_commit_sha: CommitSha } -data tested_subject_note: String = "THE SUBJECT THE FLOOR TESTED, captured by a typed claim subprocess before the ordinary floor — before any substantive floor work — and bound into the receipt by the stamp. The child address space dies before batch 1, structurally reclaiming its evaluator arena while preserving the exact wire bytes. The stamp must NOT re-read HEAD or re-observe the merge target at stamp time: main can advance during a floor that runs for tens of minutes, and a receipt binding post-floor observations would claim the floor tested a tree it never saw. The gate then fetches the CURRENT target and classifies tested_base_commit_sha against it — two base facts with different jobs, never one observation doing both (the deleted single pre-floor fetch was exactly that fusion, and the freshness hole it left is what the old separate gate step's own fresh fetch existed to catch). The classified operand is the base COMMIT, not its tree: the merge base is fixed by ancestry, which a tree does not carry, so a base that returns to the same tree at a different commit changes the merge result while a tree comparison passes." +// THE SUBJECT THE FLOOR TESTED, captured by a typed claim subprocess before the ordinary floor — +// before any substantive floor work — and bound into the receipt by the stamp. The child address +// space dies before batch 1, structurally reclaiming its evaluator arena while preserving the exact +// wire bytes. The stamp must NOT re-read HEAD or re-observe the merge target at stamp time: main +// can advance during a floor that runs for tens of minutes, and a receipt binding post-floor +// observations would claim the floor tested a tree it never saw. The gate then fetches the CURRENT +// target and classifies tested_base_commit_sha against it — two base facts with different jobs, +// never one observation doing both (the deleted single pre-floor fetch was exactly that fusion, and +// the freshness hole it left is what the old separate gate step's own fresh fetch existed to +// catch). The classified operand is the base COMMIT, not its tree: the merge base is fixed by +// ancestry, which a tree does not carry, so a base that returns to the same tree at a different +// commit changes the merge result while a tree comparison passes. data merge_admission_merge_base_ref: String = gunbc_merge_target_ref as String // SCHEMA BUMPED v1 -> v2 WITH THE ADDED FIELD, per the rule gunbc.merge_admission_produce @@ -66,9 +81,23 @@ data merge_admission_merge_base_ref: String = gunbc_merge_target_ref as String // sixth. data merge_admission_tested_subject_schema: String = "gunbc.merge_admission_tested_subject.v2" -data compose_walk_attempt_id_note: String = "Pure composition of the walk-attempt identity from its parts; the ENV OBSERVATION lives with the wet entry, this fn only joins and refuses. Every part must be nonempty — an empty run id, attempt number, or job name yields Absent, and the caller REFUSES the run rather than substituting a constant: a silent local would make every non-GitHub run one attempt, so the wrong-attempt refusal could never fire off CI, which is exactly the unreachable-arm shape §5 forbids. Non-GitHub execution supplies GUNBC_WALK_ATTEMPT_ID explicitly and it passes through compose_explicit_walk_attempt_id under the same nonempty law." - -data git_object_id_wire_note: String = "A Git object id on the wire carries its FORMAT, because the type does: :. An anonymous hex string would be exactly the defect DESIGN's ContentHash-family residue records — a value whose family the reader has to guess — and a repository mid SHA-1/SHA-256 transition is precisely when guessing wrong is possible. This fn owns ONLY the : spelling; whether the hex is a well-formed object id of that family is extdeps.git.object_store's question, answered by git_sha1_object_id / git_sha256_object_id, which check exact length and canonical lowercase hex. An earlier cut cast the suffix straight into the brand, so sha1:x, sha1:not-hex and a 64-digit value labelled sha1 all parsed (review 2026-07-30) — family without length and syntax is not identification." +// Pure composition of the walk-attempt identity from its parts; the ENV OBSERVATION lives with the +// wet entry, this fn only joins and refuses. Every part must be nonempty — an empty run id, attempt +// number, or job name yields Absent, and the caller REFUSES the run rather than substituting a +// constant: a silent local would make every non-GitHub run one attempt, so the wrong-attempt +// refusal could never fire off CI, which is exactly the unreachable-arm shape §5 forbids. +// Non-GitHub execution supplies GUNBC_WALK_ATTEMPT_ID explicitly and it passes through +// compose_explicit_walk_attempt_id under the same nonempty law. + +// A Git object id on the wire carries its FORMAT, because the type does: :. An +// anonymous hex string would be exactly the defect DESIGN's ContentHash-family residue records — a +// value whose family the reader has to guess — and a repository mid SHA-1/SHA-256 transition is +// precisely when guessing wrong is possible. This fn owns ONLY the : spelling; whether +// the hex is a well-formed object id of that family is extdeps.git.object_store's question, +// answered by git_sha1_object_id / git_sha256_object_id, which check exact length and canonical +// lowercase hex. An earlier cut cast the suffix straight into the brand, so sha1:x, sha1:not-hex +// and a 64-digit value labelled sha1 all parsed (review 2026-07-30) — family without length and +// syntax is not identification. fn merge_admission_attempt_dir(root: String, attempt_id: WalkAttemptId) -> String { concat(concat(root, "/.gunbc/merge-admission/"), attempt_id as String) diff --git a/dag/gunbc/merge_lifecycle.dag b/dag/gunbc/merge_lifecycle.dag index 0768ed94b59..f0ae9e2b18a 100644 --- a/dag/gunbc/merge_lifecycle.dag +++ b/dag/gunbc/merge_lifecycle.dag @@ -14,13 +14,65 @@ import gunbc.merge_admission { merge_admission_fixture_git_tree_from_tagged, } -data merge_lifecycle_authority_note: String = "Lifecycle DYNAMICS over the gunbc.merge_admission authority — no second admission predicate (DESIGN 2: derive, never restate). gunbc.merge_admission owns the static question (is THIS receipt admissible against THIS target tree and roster); this module owns the temporal one (does a merge PROCESS whose merge step consults policy_admits ever advance main to a tree the process never verified). Squash semantics per gunbc.plans.branch_merge_admission_model section 1: the landed tree is the PR applied to CURRENT main at merge time (squash_result_tree), never the base the receipt recorded — which is exactly why staleness is expressible at all. Trees and rosters are symbolic fixture identities (content_hash_atom roots advanced by content_hash_tagged, injective in tag x payload), the same fixture idiom test.claim.merge_admission_witness already uses for its b0/b1/r0/r1 hashes." - -data merge_lifecycle_incident_note: String = "Grounding incident (2026-07-16, the #6663 x #6686 main-red): PR #6663 went green on run 29459154549 against a pre-#6686 base; #6686 landed during that window; the squash union — never tested — reddened every main push at floor resolve with 28 missing-field errors (first red: run 29461044828) until the root fix #6701. In this module's vocabulary: RunPrCi(#6663) minted a receipt at tip0; MergePr(#6686) advanced the tip; MergePr(#6663) then landed squash_result_tree(#6663, tip1) with only a tip0 receipt — admitted under PerPrGateOnly (the live policy: GitHub strict_required_status_checks false, no merge queue), refused under KeyedReceipt as MergeDeniedStaleBase. The incident trace is the permanent RED fixture in test.claim.merge_lifecycle_interleaving_witness." - -data merge_lifecycle_semantic_merge_debt_note: String = "SEMANTIC MERGE BREAKS ARE KNOWINGLY ACCEPTED DEBT, NOT AN UNOWNED GAP (operator ruling, 2026-08-27, relayed through the parent lane). THE CLASS: one PR widens a closed coproduct, another adds or holds a match total over it, neither touches the other lines. Git merges clean because MERGEABLE is a claim about TEXT, and the composed tree then carries a non-exhaustive match that neither source branch contained. Silent at textual integration, loud at semantic acceptance -- the closed coproduct and the total match do their job, but only after the merge is irreversible. THE INCIDENT NOTE ABOVE IS THE SAME LIFECYCLE SHAPE with a different break kind: 2026-07-16 landed 28 missing-field errors by the identical route, so the staleness this module models is not specific to exhaustiveness. Two further instances, both verified rather than relayed: gunbc PR 9343 (merged 2026-08-26, MAIN WENT RED -- 9227 adds a match over ClaimDisposition, 9263 widens ClaimDisposition, both green, both clean, refused at strict preparation the moment they met) and gunbc PR 9283 (caught pre-merge only because the merge touched a file the author also changed, which is a habit rather than a mechanism). IN THIS MODULE VOCABULARY the class is exactly lifecycle_admits_unverified_tip: MergePr advances the tip, a later MergePr lands squash_result_tree against a receipt minted at the older tip. PerPrGateOnly admits it; KeyedReceipt refuses it as MergeDeniedStaleBase. So the remedy is already modeled here and is simply not the live policy. WHY IT IS DEBT RATHER THAN A DEFECT TO FIX NOW: the required run is about 50 minutes, so a pre-merge current-base rerun would serialize the merge queue behind a full floor for every PR, and the operator expects the better answer to come from technology under construction -- SCM giving real ancestry and v2 lenses giving cheaper corpus questions -- rather than from another pre-merge gate. That is a priced decision, not an oversight. THE RUNG IS HONEST ONLY IF THE DIVERGENCE IS STATED: merge_admission_enforcement_policy declares KeyedReceipt, its only consumer in the corpus is a witness asserting that it equals KeyedReceipt, and merge-admission stamping sits on the DESIGN unguarded list after the floor cut. So the declared policy is not the enforced one, and a reader must not take the declaration as evidence that stale bases are refused. NEXT INSTANCE: add a line here rather than escalating. The class is owned, the remedy is named, and re-escalation costs a lane its afternoon to reach this same ruling." - -data merge_lifecycle_window_note: String = "The interleaving witness enumerates EVERY event sequence of exact length 4 over its alphabet (no sampling, no silent cap — the witness asserts the full count so truncation cannot read as coverage, DESIGN 5). The alphabet includes the same PR number at two different heads: the ordinary auto-heal transition where generated artifacts are committed after an earlier head earned evidence. The violation flag LATCHES in LifecycleState, so every shorter lifecycle is covered as a prefix of some length-4 sequence. 4 suffices for the grounded incident (4 events), for the roster axis (3 events: RunPrCi, RosterBump, MergePr), and for the heal axis (2 events: RunPrCi(old head), MergePr(healed head)); widening the window or alphabet is priced only when a concrete interleaving class needs it (DESIGN 6)." +// Lifecycle DYNAMICS over the gunbc.merge_admission authority — no second admission predicate +// (DESIGN 2: derive, never restate). gunbc.merge_admission owns the static question (is THIS +// receipt admissible against THIS target tree and roster); this module owns the temporal one (does +// a merge PROCESS whose merge step consults policy_admits ever advance main to a tree the process +// never verified). Squash semantics per gunbc.plans.branch_merge_admission_model section 1: the +// landed tree is the PR applied to CURRENT main at merge time (squash_result_tree), never the base +// the receipt recorded — which is exactly why staleness is expressible at all. Trees and rosters +// are symbolic fixture identities (content_hash_atom roots advanced by content_hash_tagged, +// injective in tag x payload), the same fixture idiom test.claim.merge_admission_witness already +// uses for its b0/b1/r0/r1 hashes. + +// Grounding incident (2026-07-16, the #6663 x #6686 main-red): PR #6663 went green on run +// 29459154549 against a pre-#6686 base; #6686 landed during that window; the squash union — never +// tested — reddened every main push at floor resolve with 28 missing-field errors (first red: run +// 29461044828) until the root fix #6701. In this module's vocabulary: RunPrCi(#6663) minted a +// receipt at tip0; MergePr(#6686) advanced the tip; MergePr(#6663) then landed +// squash_result_tree(#6663, tip1) with only a tip0 receipt — admitted under PerPrGateOnly (the live +// policy: GitHub strict_required_status_checks false, no merge queue), refused under KeyedReceipt +// as MergeDeniedStaleBase. The incident trace is the permanent RED fixture in +// test.claim.merge_lifecycle_interleaving_witness. + +// SEMANTIC MERGE BREAKS ARE KNOWINGLY ACCEPTED DEBT, NOT AN UNOWNED GAP (operator ruling, +// 2026-08-27, relayed through the parent lane). THE CLASS: one PR widens a closed coproduct, +// another adds or holds a match total over it, neither touches the other lines. Git merges clean +// because MERGEABLE is a claim about TEXT, and the composed tree then carries a non-exhaustive +// match that neither source branch contained. Silent at textual integration, loud at semantic +// acceptance -- the closed coproduct and the total match do their job, but only after the merge is +// irreversible. THE INCIDENT NOTE ABOVE IS THE SAME LIFECYCLE SHAPE with a different break kind: +// 2026-07-16 landed 28 missing-field errors by the identical route, so the staleness this module +// models is not specific to exhaustiveness. Two further instances, both verified rather than +// relayed: gunbc PR 9343 (merged 2026-08-26, MAIN WENT RED -- 9227 adds a match over +// ClaimDisposition, 9263 widens ClaimDisposition, both green, both clean, refused at strict +// preparation the moment they met) and gunbc PR 9283 (caught pre-merge only because the merge +// touched a file the author also changed, which is a habit rather than a mechanism). IN THIS MODULE +// VOCABULARY the class is exactly lifecycle_admits_unverified_tip: MergePr advances the tip, a +// later MergePr lands squash_result_tree against a receipt minted at the older tip. PerPrGateOnly +// admits it; KeyedReceipt refuses it as MergeDeniedStaleBase. So the remedy is already modeled here +// and is simply not the live policy. WHY IT IS DEBT RATHER THAN A DEFECT TO FIX NOW: the required +// run is about 50 minutes, so a pre-merge current-base rerun would serialize the merge queue behind +// a full floor for every PR, and the operator expects the better answer to come from technology +// under construction -- SCM giving real ancestry and v2 lenses giving cheaper corpus questions -- +// rather than from another pre-merge gate. That is a priced decision, not an oversight. THE RUNG IS +// HONEST ONLY IF THE DIVERGENCE IS STATED: merge_admission_enforcement_policy declares +// KeyedReceipt, its only consumer in the corpus is a witness asserting that it equals KeyedReceipt, +// and merge-admission stamping sits on the DESIGN unguarded list after the floor cut. So the +// declared policy is not the enforced one, and a reader must not take the declaration as evidence +// that stale bases are refused. NEXT INSTANCE: add a line here rather than escalating. The class is +// owned, the remedy is named, and re-escalation costs a lane its afternoon to reach this same +// ruling. + +// The interleaving witness enumerates EVERY event sequence of exact length 4 over its alphabet (no +// sampling, no silent cap — the witness asserts the full count so truncation cannot read as +// coverage, DESIGN 5). The alphabet includes the same PR number at two different heads: the +// ordinary auto-heal transition where generated artifacts are committed after an earlier head +// earned evidence. The violation flag LATCHES in LifecycleState, so every shorter lifecycle is +// covered as a prefix of some length-4 sequence. 4 suffices for the grounded incident (4 events), +// for the roster axis (3 events: RunPrCi, RosterBump, MergePr), and for the heal axis (2 events: +// RunPrCi(old head), MergePr(healed head)); widening the window or alphabet is priced only when a +// concrete interleaving class needs it (DESIGN 6). type PrIdentity { number: Int @@ -83,7 +135,11 @@ fn latest_receipt_for(receipts: List, pr: PrIdentity) -> }) } -data latest_receipt_semantics_note: String = "latest_receipt_for keeps the LAST minted receipt for the exact (PR number, head SHA) pair. GitHub required checks attach evidence to a commit, not to a mutable PR number: after auto-heal pushes a new head, the old head's green is historical evidence and the new head has no admissible receipt until CI re-runs. Joining on number alone admitted RunPrCi(old head) then MergePr(healed head), the exact stale-evidence class the heal transition creates." +// latest_receipt_for keeps the LAST minted receipt for the exact (PR number, head SHA) pair. GitHub +// required checks attach evidence to a commit, not to a mutable PR number: after auto-heal pushes a +// new head, the old head's green is historical evidence and the new head has no admissible receipt +// until CI re-runs. Joining on number alone admitted RunPrCi(old head) then MergePr(healed head), +// the exact stale-evidence class the heal transition creates. fn merge_enabled( policy: AdmissionPolicy, diff --git a/dag/gunbc/namespace/namespace_census_receipt.dag b/dag/gunbc/namespace/namespace_census_receipt.dag index 300daa1c4d9..591600faeb5 100644 --- a/dag/gunbc/namespace/namespace_census_receipt.dag +++ b/dag/gunbc/namespace/namespace_census_receipt.dag @@ -59,7 +59,12 @@ data namespace_census_compile_invocation: NamespaceCensusCompileInvocation = Nam stderr_capture: "/tmp/namespace-census/pop_stderr.log", } -data namespace_census_reproduction_note: String = "The ordered reproduction workflow authority for the pinned namespace census receipt. CompileSyntheticRoot carries namespace_census_compile_invocation, including the receipt-verified compiler binding, subcommand, source roots, entry, output directory, target, and stdout/stderr captures; none of those process facts is authority in the shell projection. The remaining steps name intent consumed by the pinned Python handlers. Measurement values and pinned identities remain single-authority in that receipt's summary.json and are checked by VerifyReceipt." +// The ordered reproduction workflow authority for the pinned namespace census receipt. +// CompileSyntheticRoot carries namespace_census_compile_invocation, including the receipt-verified +// compiler binding, subcommand, source roots, entry, output directory, target, and stdout/stderr +// captures; none of those process facts is authority in the shell projection. The remaining steps +// name intent consumed by the pinned Python handlers. Measurement values and pinned identities +// remain single-authority in that receipt's summary.json and are checked by VerifyReceipt. data namespace_census_shell_projection_dissolution_trigger: DissolutionCondition = unbound_dissolution(description: "🟡 dissolve-on: namespace_census_reproduction_steps rendered through the canonical orchestration-to-bash emitter (bash-emit #5828), replacing the hand-authored shell projection in docs/probes/namespace_census_2026-07-31.md without changing the typed step authority") diff --git a/dag/gunbc/namespace/namespace_clause_e_projection_law.dag b/dag/gunbc/namespace/namespace_clause_e_projection_law.dag index 05992438f4d..d545ae48159 100644 --- a/dag/gunbc/namespace/namespace_clause_e_projection_law.dag +++ b/dag/gunbc/namespace/namespace_clause_e_projection_law.dag @@ -22,7 +22,17 @@ import std.occurrence_binding_candidates { data namespace_clause_e_projection_law_note: String = "Clause-(e) projection law carrier (N3-B1): assess_controlled_two_mention_provenance_projection proves the controlled two-mention / one-declaration / one-file-dependency projection law on hand-built ReferenceDerivedClauseEProduction carriers. Returns NamespaceClauseEProjectionVerdict over the neutral ReferenceDerivedDependencyProjection seam — never the parent ReferenceDerivedClosureAdmission ontology (N3-D translates child evidence into RepeatedMentionsCollapseDependency). Row closure over OrdinaryLoadedCompilationClosure is B2's axis and is excluded from this module." -data lists_agree_at_nfr_disposition_note: String = "NFR census disposition (review 48425/48733): lists_agree_at is not separately rostered on gunbc.non_fold_residue.non_fold_residue_frontier. nfr_residue_sites attributes only match-on-closed-coproduct-param sites carrying a top-level wildcard arm; this helper's list-head Option projection arms (left |> first Absent) are not a param scrutinee, so a roster row is scan-invisible and would count permanently stale (receipt: row landed 6dcf9289a67, deleted a4f53019847 when stale_roster_count=1). Structural head/tail recursion (review 49368) maps length mismatch to ListsDisagree; assess_controlled_two_mention routes provenance |> first Absent to ProjectionListStructureBroken separately. assess_controlled_two_mention_provenance_projection routes ReferencePopulationRefused to ProjectionBindingRefused before population agreement. Dissolve lists_agree_at when list agreement becomes a total std carrier (algebra-derived zip fold) and delete this note with the row." +// NFR census disposition (review 48425/48733): lists_agree_at is not separately rostered on +// gunbc.non_fold_residue.non_fold_residue_frontier. nfr_residue_sites attributes only +// match-on-closed-coproduct-param sites carrying a top-level wildcard arm; this helper's list-head +// Option projection arms (left |> first Absent) are not a param scrutinee, so a roster row is +// scan-invisible and would count permanently stale (receipt: row landed 6dcf9289a67, deleted +// a4f53019847 when stale_roster_count=1). Structural head/tail recursion (review 49368) maps length +// mismatch to ListsDisagree; assess_controlled_two_mention routes provenance |> first Absent to +// ProjectionListStructureBroken separately. assess_controlled_two_mention_provenance_projection +// routes ReferencePopulationRefused to ProjectionBindingRefused before population agreement. +// Dissolve lists_agree_at when list agreement becomes a total std carrier (algebra-derived zip +// fold) and delete this note with the row. type NamespaceClauseEProjectionFailure = ProjectionBindingRefused { population: BoundReferencePopulation } diff --git a/dag/gunbc/namespace/namespace_cut_subject_observation.dag b/dag/gunbc/namespace/namespace_cut_subject_observation.dag index 0d8bfca6c65..919e39c382a 100644 --- a/dag/gunbc/namespace/namespace_cut_subject_observation.dag +++ b/dag/gunbc/namespace/namespace_cut_subject_observation.dag @@ -38,30 +38,28 @@ import gunbc.namespace_cut_subject_roster { // THE LIVE HALF OF THE SUBJECT ROSTER, SPLIT FROM THE PURE HALF ON PURPOSE. The roster module // carries only identities and joins, so a consumer (the program-standing report, a witness) can -// import it without its loader closure reaching live-read code -- the same pure/live split the +// import it without its loader closure reaching live-read code -- the pure/live split the // lens-enforcement gate note names as the repair shape for module_graph. This module is the -// live-read carrier home (v2.std.live_read live_read_carrier_homes_v0) and holds every -// observation that touches the tree. +// live-read carrier home (v2.std.live_read live_read_carrier_homes_v0) and holds every observation +// that touches the tree. // --------------------------------------------------------------------------------------------- // OBSERVATION. Three answers, because two media and a refusal are three states: // - SubjectProbePresent / SubjectProbeAbsent: decided by runtime declaration-ref resolution // over the reflection facts this module reads. -// - SubjectProbeUnobservable: the honest arm, and it is now NARROW. It carries an ambiguous -// resolution -- a corpus defect this roster must surface rather than adjudicate -- and the -// host-probe cases where the JOIN cannot reach, each of which names what is missing. Neither -// is Present and neither is Absent, and folding either into a verdict would be the -// empty-observation narrow. +// - SubjectProbeUnobservable: the honest arm, now NARROW. It carries an ambiguous resolution -- +// a corpus defect this roster must surface rather than adjudicate -- and the host-probe cases +// where the JOIN cannot reach, each naming what is missing. Neither is Present nor Absent, and +// folding either into a verdict would be the empty-observation narrow. // -// THE HOST HALF'S NEXT-RUNG TRIGGER IS DISCHARGED, and how it was discharged is the point. -// This module used to refuse every HostRustItemProbe with "no item-grain observation authority -// exists in this tree yet" and name building one as the trigger. The authority already existed: -// gunbc.rust_item_host_observation observe_current_live_hand_rust returns the live hand-Rust item -// population at item-identity grain, scanned by gunbc.rust_item_scan and scoped by the namespace -// rows that claim src/v1/stage0/src/ -- the exact tree every host probe in this roster names. The -// missing thing was never the authority, it was the JOIN, and writing a second Rust-item observer -// beside it would have been the parallel authority DESIGN section 3 forbids. So observe_host_probe -// below CONSUMES that producer. +// THE HOST HALF'S NEXT-RUNG TRIGGER IS DISCHARGED, and how is the point. This module used to refuse +// every HostRustItemProbe with "no item-grain observation authority exists in this tree yet" and +// name building one as the trigger. The authority already existed: gunbc.rust_item_host_observation +// observe_current_live_hand_rust returns the live hand-Rust item population at item-identity grain, +// scanned by gunbc.rust_item_scan and scoped by the namespace rows that claim src/v1/stage0/src/ -- +// the exact tree every host probe here names. The missing thing was the JOIN, never the authority, +// and a second Rust-item observer would have been the parallel authority DESIGN section 3 forbids. +// So observe_host_probe below CONSUMES that producer. // --------------------------------------------------------------------------------------------- type SubjectProbeObservation = SubjectProbePresent { probe: NamespaceCutSubjectProbe } @@ -98,10 +96,10 @@ fn observe_probe( } } -// A MODULE GONE IS THE SUBJECT GONE. DeclarationRefModuleAbsent maps to Absent rather than to a -// refusal because deleting a whole module is a legitimate terminal state for a deletion subject -// -- what must NOT map to Absent is ambiguity, which is two declarations answering one identity -// and is reported under its own name. +// A MODULE GONE IS THE SUBJECT GONE. DeclarationRefModuleAbsent maps to Absent rather than a +// refusal because deleting a whole module is a legitimate terminal state for a deletion subject -- +// what must NOT map to Absent is ambiguity, two declarations answering one identity, reported under +// its own name. fn observation_from_resolution( p: NamespaceCutSubjectProbe, r: DeclarationRefResolution @@ -221,20 +219,19 @@ fn observation_render(o: SubjectProbeObservation) -> String { // --function subject_roster_report --arg out=/tmp/namespace-cut-subjects.txt // // This module is a live-read carrier for exactly this entry (decl_facts + -// module_declaration_facts_live) and is enrolled in v2.std.live_read live_read_carrier_homes_v0. -// The FACT pool roots (per-root, below) deliberately include src/v1: most deletion subjects -// live in the seed's .dag corpus, and a walk that omitted it would report the cut complete -// while the seed still resolves imports -- the false Absent this instrument must not be able to -// produce. src/v1 is NOT a --source-root of the entry: the seed corpus is a SUBJECT the -// reflection walk reads, never a module pool this module executes from. +// module_declaration_facts_live), enrolled in v2.std.live_read live_read_carrier_homes_v0. The FACT +// pool roots (per-root, below) deliberately include src/v1: most deletion subjects live in the +// seed's .dag corpus, and a walk omitting it would report the cut complete while the seed still +// resolves imports -- the false Absent this instrument must not be able to produce. src/v1 is NOT a +// --source-root of the entry: the seed corpus is a SUBJECT the reflection walk reads, never a +// module pool this module executes from. // --------------------------------------------------------------------------------------------- -// ONE ACQUISITION PER ROOT, NOT ONE UNION. decl_facts retains a Node per declaration, so a -// union walk over the seed plus the v2 corpus held both node populations resident at once and -// was SIGKILLed on a memory-tight host (measured 2026-08-29, remote runner, ~14min then kill). -// Every dag-grain probe names a module under exactly one root, so the report acquires each -// root's facts separately and resolves each probe against its own root -- halving the peak and -// keeping the false-Absent property: a v1 probe is still resolved against a walk that includes -// src/v1. +// ONE ACQUISITION PER ROOT, NOT ONE UNION. decl_facts retains a Node per declaration, so a union +// walk over the seed plus the v2 corpus held both node populations resident at once and was +// SIGKILLed on a memory-tight host (measured 2026-08-29, remote runner, ~14min then kill). Every +// dag-grain probe names a module under exactly one root, so the report acquires each root's facts +// separately and resolves each probe against its own root -- halving the peak and keeping the +// false-Absent property: a v1 probe is still resolved against a walk that includes src/v1. data subject_roster_v1_pool_roots: List = ["src/v1"] data subject_roster_v2_pool_roots: List = ["src/v2"] @@ -247,9 +244,9 @@ fn probe_is_v1(p: NamespaceCutSubjectProbe) -> Bool { } // ONE HOST ACQUISITION PER REPORT, beside the two per-root declaration acquisitions. The host -// population is a whole-tree read of the namespace-claimed Rust trees, so taking it once and -// resolving every host probe against it is the same shape as the per-root split above -- and the -// same reason: the acquisition, not the resolution, is what costs. +// population is a whole-tree read of the namespace-claimed Rust trees, so it is taken once and +// every host probe resolved against it -- the same shape and reason as the per-root split above: +// the acquisition, not the resolution, is what costs. fn subject_report_lines() -> List { let v1_facts = decl_facts(pool_roots: subject_roster_v1_pool_roots) let v1_modules = module_declaration_facts_live(pool_roots: subject_roster_v1_pool_roots) diff --git a/dag/gunbc/namespace/namespace_cut_subject_roster.dag b/dag/gunbc/namespace/namespace_cut_subject_roster.dag index ae514d16b7f..52927fa9594 100644 --- a/dag/gunbc/namespace/namespace_cut_subject_roster.dag +++ b/dag/gunbc/namespace/namespace_cut_subject_roster.dag @@ -14,8 +14,8 @@ import gunbc.namespace_cut_stage { // gunbc.compiler_frontend_program_status has named as its missing NamespaceStepSubjectRoster // instrument since 2026-08-26 -- "the namespace cut's remaining per-step subjects as rows". // -// WHY THE PROBES ARE STRINGS AND NOT AUTHORED DeclarationRef LITERALS, which looks like the -// weaker spelling and is the only workable one. The ingestion-time citation wall +// WHY THE PROBES ARE STRINGS AND NOT AUTHORED DeclarationRef LITERALS -- the weaker-looking +// spelling and the only workable one. The ingestion-time citation wall // (v1_compiler.declaration_index, cited-symbol arm) resolves every decl_ref literal on every // required run and REFUSES when the target is gone. Every declaration this roster names is // SCHEDULED TO BE DELETED: the moment a wave deletes one, an authored citation to it would red @@ -64,15 +64,14 @@ fn namespace_cut_subject_label(s: NamespaceCutSubjectIdentity) -> NonEmptyStr { // the observation sibling executes, and HOST RUST ITEMS, resolved against // gunbc.rust_item_host_observation's live hand-Rust population. // -// THE HOST PROBE IS NO LONGER UNOBSERVABLE BY DEFAULT. An earlier revision of this comment said -// its observation instrument does not exist in this tree, and that was false when it was written: +// THE HOST PROBE IS NO LONGER UNOBSERVABLE BY DEFAULT. An earlier revision said its observation +// instrument does not exist in this tree; that was false when written: // gunbc.rust_item_host_observation observe_current_live_hand_rust has been the item-grain host // authority for seed-growth admission since before this roster existed. Refusing every host probe -// on that reading was an absent-authority claim about a present authority -- the same -// authority_substitution shape read in reverse -- and it is what made XL-0 look like a stage -// waiting on a tool. The sibling now JOINS against that producer and refuses only where the join -// itself cannot reach: see observe_host_probe, which names what is missing rather than the -// authority. +// on that reading was an absent-authority claim about a present authority -- the +// authority_substitution shape read in reverse -- and it made XL-0 look like a stage waiting on a +// tool. The sibling now JOINS against that producer and refuses only where the join itself cannot +// reach: see observe_host_probe, which names what is missing rather than the authority. type NamespaceCutSubjectProbe = DagDeclarationProbe { module_path: String, decl_name: String } | DagDeclarationFieldProbe { module_path: String, decl_name: String, field_name: String } @@ -170,26 +169,24 @@ fn subject_probe_list(s: NamespaceCutSubjectIdentity) -> List List, admissions: List, diff --git a/dag/gunbc/namespace/namespace_structural_observation_bridge_seed_growth.dag b/dag/gunbc/namespace/namespace_structural_observation_bridge_seed_growth.dag index 1f97821681d..f049701991a 100644 --- a/dag/gunbc/namespace/namespace_structural_observation_bridge_seed_growth.dag +++ b/dag/gunbc/namespace/namespace_structural_observation_bridge_seed_growth.dag @@ -5,7 +5,10 @@ import std.types { String } import gunbc.roadmap_model { RoadmapNodeId } import gunbc.seed_growth { SeedGrowthJustification } -data namespace_structural_observation_bridge_seed_growth_note: String = "Seed-growth obligation for the host bridge that carries the v1 parser walk's four in-file visibility observations to the dag closing check for roadmap node namespace-structural-observations. Homed beside the obligation it declares rather than in gunbc.seed_growth_admission, which owns the roster and the join." +// Seed-growth obligation for the host bridge that carries the v1 parser walk's four in-file +// visibility observations to the dag closing check for roadmap node +// namespace-structural-observations. Homed beside the obligation it declares rather than in +// gunbc.seed_growth_admission, which owns the roster and the join. data namespace_structural_observation_bridge_seed_growth_justification: SeedGrowthJustification = SeedGrowthJustification { hand_authored_declarations: [ diff --git a/dag/gunbc/namespace/namespace_structural_observations_contract.dag b/dag/gunbc/namespace/namespace_structural_observations_contract.dag index a2d0ec1130c..52cf86b93a6 100644 --- a/dag/gunbc/namespace/namespace_structural_observations_contract.dag +++ b/dag/gunbc/namespace/namespace_structural_observations_contract.dag @@ -38,7 +38,9 @@ import std.types { Bool, Int, List, String } // parsed source by the producer, never echoed from the caller's request -- and all four must name // the same one. Four admissions from four hand-built sources satisfy the capabilities and fail // this check, which is exactly the difference the row is about. -data namespace_structural_observations_contract_note: String = "Closing condition for roadmap node namespace-structural-observations. Four capabilities, established only through assess_reference_binding_observation over production observations, every one of them carrying the module path declared by the single compiled subject." +// Closing condition for roadmap node namespace-structural-observations. Four capabilities, +// established only through assess_reference_binding_observation over production observations, every +// one of them carrying the module path declared by the single compiled subject. type OrdinaryCompileStructuralAdmission { compiled_module: StructuralObservationSubjectModule diff --git a/dag/gunbc/namespace/namespace_structural_observations_production.dag b/dag/gunbc/namespace/namespace_structural_observations_production.dag index 4b017d32f1b..8054c0e116a 100644 --- a/dag/gunbc/namespace/namespace_structural_observations_production.dag +++ b/dag/gunbc/namespace/namespace_structural_observations_production.dag @@ -22,7 +22,9 @@ import std.types { List, String } // WHY THIS IS NOT IN THE CONTRACT MODULE: the v1 producer imports the contract module for the // subject and row types, so a builtin call there would close a cycle through the seed's emit // closure. The check stays pure; only this seam is host-bound. -data namespace_structural_observations_production_note: String = "Host bridge from the ordinary v1 compile path to the dag closing check. Carries no policy: the subject is supplied by the caller, the observations are composed in the v1 producer, and the capability assessment is the one shared authority." +// Host bridge from the ordinary v1 compile path to the dag closing check. Carries no policy: the +// subject is supplied by the caller, the observations are composed in the v1 producer, and the +// capability assessment is the one shared authority. fn namespace_structural_observations_production_admissions( subject: StructuralObservationSubject, diff --git a/dag/gunbc/namespace/namespace_wave_admission.dag b/dag/gunbc/namespace/namespace_wave_admission.dag index 6d3b14a0bdf..04ee8961881 100644 --- a/dag/gunbc/namespace/namespace_wave_admission.dag +++ b/dag/gunbc/namespace/namespace_wave_admission.dag @@ -20,13 +20,145 @@ import gunbc.guarantee_rung_drop { data namespace_wave_admission_note: String = "THE REQUIRED WAVE-ADMISSION WALL over closure, subject-membership and occurrence-binding deltas: gunbc.compiler_frontend_program_interlock milestone NamespaceWaveAdmissionEnrolled.\n\nWHAT IT ANSWERS. For one change, at merge-base grain: which modules entered or left each module's subject, what each module's transitive closure gained or lost, and whether any spelling already authored on both sides changed WHICH DECLARATIONS IT ADMITS. Every motion is classified into NamespaceDeltaDisposition and the run is ADMITTED only when the UNADJUDICATED set is empty -- never when the delta is empty, which is the one-word difference the ruling states explicitly and the weaker spelling of which would refuse the cut itself and then be repaired by weakening the wall.\n\nWHERE IT RUNS. As the namespace-wave-admission phase of claim_executor --required-ci, in the witnesses lane, consuming the index the parse phase built. It acquires no second corpus: the base index is the head index with the diff applied in reverse at FILE grain, so only the files a change touched are parsed again, from their base blobs, while closure and binding are recomputed over both whole graphs -- a module whose own text did not move can still have its subject moved by one that did.\n\nTHE CHANGE CLASS IS DERIVED, NOT DECLARED. NamespaceChangeClass splits preparatory work from work that alters membership or binding. Nothing asks an author which they wrote: the delta is measured and the answer falls out, so PreparatoryNoSemanticMotion is a property of a diff rather than a claim in a pull-request body. Construction over validation.\n\nTWO NON-VERDICTS ARE REPORTED UNDER THEIR OWN NAMES AND ONLY ONE PASSES. A push whose baseline IS its own head -- main after a squash merge -- has no diff to adjudicate, and that is NoSubject: nothing was compared and nothing is admitted. A baseline that does not resolve is NotEvaluated, which the ruling puts on the refusing side, because I could not see what changed and nothing changed are different states with different remedies and the second is the empty-observation narrow.\n\nA STALE ADMISSION REFUSES TOO. A transition-admission row matching no delta is a permission standing over nothing; leaving it would let the roster stop being a fact about the corpus and become a list nobody pruned." -data namespace_wave_admission_grain_note: String = "WHY THE GRAIN IS AUTHORED CONTAINMENT IDENTITY AND NOT OCCURRENCE IDENTITY, which is a CLOSED RESULT rather than a shortcut.\n\nv2.workflow.legacy_binding_delta states it for its own subject and the argument transfers unchanged: std.occurrence_identity's scope law forbids filename, SourceSpan, authored name, structural Node equality and content hash as identity inputs, and an OccurrenceId is a monotone counter consumed in walk order, so it ENCODES POSITION and shifts under any edit above it. A cross-compile correspondence is therefore something a TRANSFORMATION EMITS -- a function knows which output came from which input -- and between a merge base and a pull-request head there is no transformation to emit one. So an occurrence-grain delta between two arbitrary trees is not a better key waiting to be found; it is unavailable by construction.\n\nWHAT THE WALL USES INSTEAD is the identity legacy_binding_observation legacy_subject_identity already folds for its own subjects, read at the grain a cross-tree join can carry: module path, enclosing declaration, and the LEAF SEGMENT of the reference rather than its whole spelling. A row's VALUE is the SET of declaring identities that leaf admits, never a winner -- so a local declaration arriving beside an imported one WIDENS the set to two and reports NewAmbiguity, rather than silently picking the nearer one, which is the mechanism the namespace authority exists to delete.\n\nTHE LEAF RATHER THAN THE SPELLING IS AN INVARIANT OF THE OPERATION THE CUT PERFORMS, NOT A LUCKY FIXTURE. A requalification wave prepends the declarer's path to a reference and leaves the declarer fixed, so it changes the authored SPELLING and leaves the last segment unchanged BY CONSTRUCTION -- the key does not merely happen to survive the cut, it is invariant under exactly the transformation the cut applies. The reduction is not coined here either: v1.05_emit_rust rust_fn_sig_leaf_name_dotted_note names qualified_last_segment as THE single authority for taking an authored spelling to its last segment, and records a real defect from a site that skipped it and emitted dots into Rust output. Stated as an invariant rather than as a finding because a finding invites re-litigation and an invariant does not (measured against the carriers by snappy-dove-250, 2026-08-26). Keyed on the whole spelling instead, requalifying a reference reads as NewUnresolvedness, and the wall would refuse the only program it exists to admit -- which is how this was found, by a fixture going red rather than by review.\n\nTHE CONVERSE IS THE WALL WORKING AND IS NOT TO BE SOFTENED. If a cut ever repoints a reference to a DIFFERENT declaration whose leaf differs, the key moves and the wall refuses. The owning program has bound itself to splitting that out rather than asking for tolerance: a wave that both requalifies and moves a symbol is two change classes in one diff, and TargetChanged firing on it is the wall reporting a mis-shaped wave. No per-row exemption, threshold or allowance is admitted for that case -- a confidence threshold selecting a softer arm is the smuggled heuristic DESIGN rules out, and it would convert the one signal that catches a mis-shaped wave into silence.\n\nTHE CEILING, STATED SO IT IS NOT MISREAD AS COVERAGE: two occurrences of one spelling inside one declaration share a row, so WHICH occurrence took which member of the set is not observable here. ANYONE READING THE MILESTONE AS COVERING OCCURRENCE GRAIN WILL BE READING MORE THAN EXISTS, and that sentence is carried here rather than only in a pull-request body because a body is not an authority and the overclaim it prevents is the kind that recurs. The milestone NamespaceWaveAdmissionEnrolled delivers two adjudicated walls -- subject membership and binding at authored containment grain -- a MEASURED closure blast radius attributed to its generators, and this declared stall. It does not deliver a third wall over closure, and it does not deliver occurrence grain.\n\nNEXT-RUNG TRIGGER: a projector-emitted correspondence (legacy_binding_delta ProjectionProvenanceEntry) reaching a base/head pair, at which point the row key descends to the occurrence and this note is deleted rather than amended." +// WHY THE GRAIN IS AUTHORED CONTAINMENT IDENTITY AND NOT OCCURRENCE IDENTITY, which is a CLOSED +// RESULT rather than a shortcut. +// +// v2.workflow.legacy_binding_delta states it for its own subject and the argument transfers +// unchanged: std.occurrence_identity's scope law forbids filename, SourceSpan, authored name, +// structural Node equality and content hash as identity inputs, and an OccurrenceId is a monotone +// counter consumed in walk order, so it ENCODES POSITION and shifts under any edit above it. A +// cross-compile correspondence is therefore something a TRANSFORMATION EMITS -- a function knows +// which output came from which input -- and between a merge base and a pull-request head there is +// no transformation to emit one. So an occurrence-grain delta between two arbitrary trees is not a +// better key waiting to be found; it is unavailable by construction. +// +// WHAT THE WALL USES INSTEAD is the identity legacy_binding_observation legacy_subject_identity +// already folds for its own subjects, read at the grain a cross-tree join can carry: module path, +// enclosing declaration, and the LEAF SEGMENT of the reference rather than its whole spelling. A +// row's VALUE is the SET of declaring identities that leaf admits, never a winner -- so a local +// declaration arriving beside an imported one WIDENS the set to two and reports NewAmbiguity, +// rather than silently picking the nearer one, which is the mechanism the namespace authority +// exists to delete. +// +// THE LEAF RATHER THAN THE SPELLING IS AN INVARIANT OF THE OPERATION THE CUT PERFORMS, NOT A LUCKY +// FIXTURE. A requalification wave prepends the declarer's path to a reference and leaves the +// declarer fixed, so it changes the authored SPELLING and leaves the last segment unchanged BY +// CONSTRUCTION -- the key does not merely happen to survive the cut, it is invariant under exactly +// the transformation the cut applies. The reduction is not coined here either: v1.05_emit_rust +// rust_fn_sig_leaf_name_dotted_note names qualified_last_segment as THE single authority for taking +// an authored spelling to its last segment, and records a real defect from a site that skipped it +// and emitted dots into Rust output. Stated as an invariant rather than as a finding because a +// finding invites re-litigation and an invariant does not (measured against the carriers by +// snappy-dove-250, 2026-08-26). Keyed on the whole spelling instead, requalifying a reference reads +// as NewUnresolvedness, and the wall would refuse the only program it exists to admit -- which is +// how this was found, by a fixture going red rather than by review. +// +// THE CONVERSE IS THE WALL WORKING AND IS NOT TO BE SOFTENED. If a cut ever repoints a reference to +// a DIFFERENT declaration whose leaf differs, the key moves and the wall refuses. The owning +// program has bound itself to splitting that out rather than asking for tolerance: a wave that both +// requalifies and moves a symbol is two change classes in one diff, and TargetChanged firing on it +// is the wall reporting a mis-shaped wave. No per-row exemption, threshold or allowance is admitted +// for that case -- a confidence threshold selecting a softer arm is the smuggled heuristic DESIGN +// rules out, and it would convert the one signal that catches a mis-shaped wave into silence. +// +// THE CEILING, STATED SO IT IS NOT MISREAD AS COVERAGE: two occurrences of one spelling inside one +// declaration share a row, so WHICH occurrence took which member of the set is not observable here. +// ANYONE READING THE MILESTONE AS COVERING OCCURRENCE GRAIN WILL BE READING MORE THAN EXISTS, and +// that sentence is carried here rather than only in a pull-request body because a body is not an +// authority and the overclaim it prevents is the kind that recurs. The milestone +// NamespaceWaveAdmissionEnrolled delivers two adjudicated walls -- subject membership and binding +// at authored containment grain -- a MEASURED closure blast radius attributed to its generators, +// and this declared stall. It does not deliver a third wall over closure, and it does not deliver +// occurrence grain. +// +// NEXT-RUNG TRIGGER: a projector-emitted correspondence (legacy_binding_delta +// ProjectionProvenanceEntry) reaching a base/head pair, at which point the row key descends to the +// occurrence and this note is deleted rather than amended. -data namespace_wave_admission_reference_channel_note: String = "WHY THE BINDING CHANNEL IS NOT THE IMPORT CHANNEL, which is the load-bearing design choice.\n\nA wall reading bindings only through import members would observe the import-name universe being deleted and then see NOTHING AT ALL -- blind on precisely the change it gates, in the first wave it exists to govern. So the channel is every authored NAME OCCURRENCE in a module's own parsed tree, resolved independently: a dotted spelling by its longest module prefix, a bare spelling against the module's own declarations and its import claims, following a re-export chain to the module that actually DECLARES the name. It survives the cut because it never depended on the construct being cut, and the FUNCTION is the same on both sides, so a base measured before a cut and a head measured after it are measured by one instrument.\n\nIT IS PARSE-THEN-DERIVE AND NOT A TEXT SCANNER. DESIGN's namespace-cut correction ruled the raw-text scanner family -- referenced_module_paths_in_text and kin -- a heuristic that cannot distinguish a reference from prose, with the in-tree receipt of the English word edge binding to fn edge. This reads the Node tree. What it does NOT do is resolve: a parameter name, a field name and a let binder land in the same set as a genuine reference, because telling them apart is the resolver's job. That over-collection is SYMMETRIC across two trees, which is the only property the delta needs -- a spelling denoting nothing on both sides contributes no row.\n\nWHERE THAT IS CONSERVATIVE AND IN WHICH DIRECTION, because an approximation whose direction is unstated is a hole. On the BINDING channel it can only produce rows about spellings that are not references, and such a row is a delta only if its candidate set actually moved -- so the direction is toward refusing, never toward admitting. On the MEMBERSHIP-SUPPORT question -- does any name in this module reach into the target -- it is toward admitting, and that is deliberate: refusing an added dependency because the reader could not see the reference would make ordinary work an adjudication, and a wall paid for in false refusals is a wall that gets weakened." +// WHY THE BINDING CHANNEL IS NOT THE IMPORT CHANNEL, which is the load-bearing design choice. +// +// A wall reading bindings only through import members would observe the import-name universe being +// deleted and then see NOTHING AT ALL -- blind on precisely the change it gates, in the first wave +// it exists to govern. So the channel is every authored NAME OCCURRENCE in a module's own parsed +// tree, resolved independently: a dotted spelling by its longest module prefix, a bare spelling +// against the module's own declarations and its import claims, following a re-export chain to the +// module that actually DECLARES the name. It survives the cut because it never depended on the +// construct being cut, and the FUNCTION is the same on both sides, so a base measured before a cut +// and a head measured after it are measured by one instrument. +// +// IT IS PARSE-THEN-DERIVE AND NOT A TEXT SCANNER. DESIGN's namespace-cut correction ruled the +// raw-text scanner family -- referenced_module_paths_in_text and kin -- a heuristic that cannot +// distinguish a reference from prose, with the in-tree receipt of the English word edge binding to +// fn edge. This reads the Node tree. What it does NOT do is resolve: a parameter name, a field name +// and a let binder land in the same set as a genuine reference, because telling them apart is the +// resolver's job. That over-collection is SYMMETRIC across two trees, which is the only property +// the delta needs -- a spelling denoting nothing on both sides contributes no row. +// +// WHERE THAT IS CONSERVATIVE AND IN WHICH DIRECTION, because an approximation whose direction is +// unstated is a hole. On the BINDING channel it can only produce rows about spellings that are not +// references, and such a row is a delta only if its candidate set actually moved -- so the +// direction is toward refusing, never toward admitting. On the MEMBERSHIP-SUPPORT question -- does +// any name in this module reach into the target -- it is toward admitting, and that is deliberate: +// refusing an added dependency because the reader could not see the reference would make ordinary +// work an adjudication, and a wall paid for in false refusals is a wall that gets weakened. -data namespace_wave_admission_closure_note: String = "WHAT THE WALL DOES WITH CLOSURE, AND THE ARM IT DELIBERATELY DOES NOT AUTHOR.\n\nClosure is a pure function of membership, so the closure moved and no membership moved is not a state any fixture can author. An arm for it would be permanently green by construction -- the decoration DESIGN 4b calls worse than absent, because it gets cited as coverage. Closure is therefore MEASURED and ATTRIBUTED rather than separately adjudicated: every moved closure row is grouped under the membership delta that generates it, and each delta carries the number of modules whose subject it moves, so a refusal names its blast radius. The adjudication happens at the generators, which is where the decision is; adjudicating the consequence as well would be a second representation of one fact.\n\nSO THE THREE DELTAS THE MILESTONE NAMES ARE NOT THREE WALLS. Two are adjudicated and the third is the derived consequence of one of them, reported beside it. Saying so here is the difference between a scope statement and an inflated one." +// WHAT THE WALL DOES WITH CLOSURE, AND THE ARM IT DELIBERATELY DOES NOT AUTHOR. +// +// Closure is a pure function of membership, so the closure moved and no membership moved is not a +// state any fixture can author. An arm for it would be permanently green by construction -- the +// decoration DESIGN 4b calls worse than absent, because it gets cited as coverage. Closure is +// therefore MEASURED and ATTRIBUTED rather than separately adjudicated: every moved closure row is +// grouped under the membership delta that generates it, and each delta carries the number of +// modules whose subject it moves, so a refusal names its blast radius. The adjudication happens at +// the generators, which is where the decision is; adjudicating the consequence as well would be a +// second representation of one fact. +// +// SO THE THREE DELTAS THE MILESTONE NAMES ARE NOT THREE WALLS. Two are adjudicated and the third is +// the derived consequence of one of them, reported beside it. Saying so here is the difference +// between a scope statement and an inflated one. -data namespace_wave_admission_admission_grain_note: String = "THE TRANSITION-ADMISSION ROSTER IS EMPTY AT LANDING, AND THAT IS THE CORRECT STATE: no wave has run, so no transition has been authorised. A row names one exact subject under one exact disposition, and a row matching no delta refuses as stale, so the roster can only shrink toward its subject.\n\nWHAT THE FIRST WAVE WILL NEED, RECORDED NOW AND NOT BUILT NOW. The import/namespace program's owning session measured the expected magnitude against the import-strip receipts: THOUSANDS of transitions, dominated by one class. That figure is STALE AS A POPULATION -- the same anchor recipe re-run on 2026-08-26 returns a different corpus hash than the receipts record, so the receipts describe no current tree -- and it is quoted here ONLY as an order of magnitude, which is the part that decides a design question. A per-row roster at that scale is unreviewable, and an unreviewable roster is rubber-stamped, which converts this wall into a formality.\n\nSO A CLASS ADMISSION MUST BE BOUNDED BY ENUMERATED IDENTITY AND NEVER BY PREDICATE. These exact bindings, taken from the pre-deletion baseline observation, become unresolved is admissible; unresolvedness is expected during the wave is not, because it admits everything and zeroes the wall's deficit frequency by construction -- the absorbing fallback wearing the wave's clothes. The distinction is the whole difference between a wall that survives its own transition and one that is switched off for its duration.\n\nTHE POPULATION ARRIVES ONE GRAIN DOWN FROM THIS WALL'S ROWS, AND THE PROJECTION IS THE RECEIVING END'S JOB. Measured on the carrier rather than assumed: std.legacy_binding_observation LegacyBindingObservationRow carries exactly two fields, an occurrence identity and an outcome -- no module, no enclosing declaration, no leaf. So a class admission enumerated from the baseline observation cannot hand this wall its key directly. The route down is the leaf from the validated occurrence transport's index entries, whose OccurrenceProjection authored_name is the authored spelling; the enclosing declaration from the occurrence containment path's ancestors joined against that transport's declarations; and the module from the containment root.\n\nONE TRAP ON THAT ROUTE, NAMED BECAUSE IT FAILS SILENTLY. authored_name CARRIES DOTS -- v1.02_parse populates it from the raw node name and every consumption site applies qualified_last_segment to reduce it. Keying on authored_name raw reproduces exactly the spelling hazard the grain note above rejects, and reproduces it INVISIBLY, because on an unqualified reference the spelling and the leaf are identical and every naturally-written test passes. The reduction is not optional and is not this module's to re-coin.\n\nWHY THE COARSE CARRIER IS NOT AUTHORED HERE. It would be a mechanism with no consumer until a wave needs it, and a new artifact with no final consumer is the experimental residue DESIGN 6 names. What is fixed today is the CONSTRAINT on its shape, not the shape itself; the population it enumerates comes from the milestone NamespaceBaselineObservationComplete, and where that baseline is unavailable for the base being gated, the honest disposition is NotEvaluated refusing rather than a predicate substituted for a missing enumeration." +// THE TRANSITION-ADMISSION ROSTER IS EMPTY AT LANDING, AND THAT IS THE CORRECT STATE: no wave has +// run, so no transition has been authorised. A row names one exact subject under one exact +// disposition, and a row matching no delta refuses as stale, so the roster can only shrink toward +// its subject. +// +// WHAT THE FIRST WAVE WILL NEED, RECORDED NOW AND NOT BUILT NOW. The import/namespace program's +// owning session measured the expected magnitude against the import-strip receipts: THOUSANDS of +// transitions, dominated by one class. That figure is STALE AS A POPULATION -- the same anchor +// recipe re-run on 2026-08-26 returns a different corpus hash than the receipts record, so the +// receipts describe no current tree -- and it is quoted here ONLY as an order of magnitude, which +// is the part that decides a design question. A per-row roster at that scale is unreviewable, and +// an unreviewable roster is rubber-stamped, which converts this wall into a formality. +// +// SO A CLASS ADMISSION MUST BE BOUNDED BY ENUMERATED IDENTITY AND NEVER BY PREDICATE. These exact +// bindings, taken from the pre-deletion baseline observation, become unresolved is admissible; +// unresolvedness is expected during the wave is not, because it admits everything and zeroes the +// wall's deficit frequency by construction -- the absorbing fallback wearing the wave's clothes. +// The distinction is the whole difference between a wall that survives its own transition and one +// that is switched off for its duration. +// +// THE POPULATION ARRIVES ONE GRAIN DOWN FROM THIS WALL'S ROWS, AND THE PROJECTION IS THE RECEIVING +// END'S JOB. Measured on the carrier rather than assumed: std.legacy_binding_observation +// LegacyBindingObservationRow carries exactly two fields, an occurrence identity and an outcome -- +// no module, no enclosing declaration, no leaf. So a class admission enumerated from the baseline +// observation cannot hand this wall its key directly. The route down is the leaf from the validated +// occurrence transport's index entries, whose OccurrenceProjection authored_name is the authored +// spelling; the enclosing declaration from the occurrence containment path's ancestors joined +// against that transport's declarations; and the module from the containment root. +// +// ONE TRAP ON THAT ROUTE, NAMED BECAUSE IT FAILS SILENTLY. authored_name CARRIES DOTS -- +// v1.02_parse populates it from the raw node name and every consumption site applies +// qualified_last_segment to reduce it. Keying on authored_name raw reproduces exactly the spelling +// hazard the grain note above rejects, and reproduces it INVISIBLY, because on an unqualified +// reference the spelling and the leaf are identical and every naturally-written test passes. The +// reduction is not optional and is not this module's to re-coin. +// +// WHY THE COARSE CARRIER IS NOT AUTHORED HERE. It would be a mechanism with no consumer until a +// wave needs it, and a new artifact with no final consumer is the experimental residue DESIGN 6 +// names. What is fixed today is the CONSTRAINT on its shape, not the shape itself; the population +// it enumerates comes from the milestone NamespaceBaselineObservationComplete, and where that +// baseline is unavailable for the base being gated, the honest disposition is NotEvaluated refusing +// rather than a predicate substituted for a missing enumeration. // THE RUNG, AND WHAT IT DOES NOT REACH. The class is a namespace change merging with its // closure, membership or binding motion unexamined. It was OUTSIDE THE LADDER -- not diff --git a/dag/gunbc/native_scm_interaction_contract.dag b/dag/gunbc/native_scm_interaction_contract.dag index 6b7353ba3f4..c74d74e46f1 100644 --- a/dag/gunbc/native_scm_interaction_contract.dag +++ b/dag/gunbc/native_scm_interaction_contract.dag @@ -9,7 +9,18 @@ import gunbc.scm_compatibility_shape { compatibility_projection_upsert, } -data native_scm_interaction_contract_note: String = "Model-first contract carrier for the native SCM interaction slice. The model stays on the presentation side of the split: a proposal has a stable identity, every displayed label is backed by a stored resumable option, and selection uses the option's structural position rather than caller-authored display text. Equal labels therefore cannot collapse two resume tokens into an ambiguous identity, and selection returns the stored option for resumption rather than fabricating Landed before semantic admission, transition, and independent read-back. The user-facing terminal outcome is Landed | ChoiceRequired | CouldNotLand, review/access/check/operational remain orthogonal fields, operational pending causes distinguish projection repair from unrelated internal work, workspace/target context stays optional, and compatibility projections are indexed by realization and target rather than a Git-only slot. No engine, semantic admission, transport, or CLI lives here; the witnesses exercise the contract as a black-box surface." +// Model-first contract carrier for the native SCM interaction slice. The model stays on the +// presentation side of the split: a proposal has a stable identity, every displayed label is backed +// by a stored resumable option, and selection uses the option's structural position rather than +// caller-authored display text. Equal labels therefore cannot collapse two resume tokens into an +// ambiguous identity, and selection returns the stored option for resumption rather than +// fabricating Landed before semantic admission, transition, and independent read-back. The +// user-facing terminal outcome is Landed | ChoiceRequired | CouldNotLand, +// review/access/check/operational remain orthogonal fields, operational pending causes distinguish +// projection repair from unrelated internal work, workspace/target context stays optional, and +// compatibility projections are indexed by realization and target rather than a Git-only slot. No +// engine, semantic admission, transport, or CLI lives here; the witnesses exercise the contract as +// a black-box surface. type NativeScmProposalIdentity { name: String @@ -323,6 +334,11 @@ fn native_scm_record_repaired_projection( } } +// Named scenario construction and the submission-ledger oracle live only in +// test.claim.native_scm_interaction_scenario_fixture. This production carrier owns presentation, +// structural continuation identity, terminal-outcome shape, projection attachment, and inspection +// only; it cannot manufacture Landed from a scenario name. + fn native_scm_inspect_receipt(receipt: NativeScmReceipt, authorized: Bool) -> Optional { if authorized { match receipt.receipt_details { @@ -345,4 +361,3 @@ fn native_scm_inspect_receipt(receipt: NativeScmReceipt, authorized: Bool) -> Op optional_absent() } } -data native_scm_contract_test_fixture_boundary_note: String = "Named scenario construction and the submission-ledger oracle live only in test.claim.native_scm_interaction_scenario_fixture. This production carrier owns presentation, structural continuation identity, terminal-outcome shape, projection attachment, and inspection only; it cannot manufacture Landed from a scenario name." diff --git a/dag/gunbc/native_witness_transition_receipt.dag b/dag/gunbc/native_witness_transition_receipt.dag index c54331ea634..7ca8c7b357c 100644 --- a/dag/gunbc/native_witness_transition_receipt.dag +++ b/dag/gunbc/native_witness_transition_receipt.dag @@ -4,7 +4,42 @@ import std.decl_ref { DeclarationRef, decl_ref } import std.types { CommitSha, Int, String } import gunbc.roadmap_model { RoadmapNodeIdentity } -data native_witness_transition_receipt_note: String = "ONE HOME FOR A MUTABLE PROGRAM STATUS, SO NO DOCUMENT HAS TO HOLD IT TWICE (operator ruling on gunbc#7710, 2026-08-02). That PR wrote the same changing observation -- native-at-small-scale evidence landed, with its pull request, merge commit, evaluated head and pass count -- independently into gunbc.roadmap_authority five_minute_ci_gate_program_note AND into gunbc.design_document, and therefore into generated DESIGN.md. Two hand-maintained copies of one advancing status is the DESIGN section 3 dual representation: someone updates one, the other silently goes stale, and both read as authoritative. The operator's split is the model here -- the DURABLE DESIGN FACT is two clauses (native-at-small-scale evidence has landed; operational migration rate remains open) and belongs in the design criteria; the pull request, merge commit, evaluated head and executed witness are EXECUTION EVIDENCE and belong in exactly one typed receipt that both documents cite.\n\nWHAT IS DELIBERATELY NOT STORED, and why the exclusion is the point. No pass count. gunbc#7710 recorded 25 of 25 witness receipts; that module declared 31 test functions less than a day later, so a transcribed count is stale before the ink dries and no oracle in this repository could have caught it. The per-member verdicts already have a single authority in std.selected_witness_bundle NativeWitnessBundleExecutionReceipt member_equivalence, produced by the run itself; a hand-typed integer beside it would be a second representation of that list. executed_evidence therefore cites the witness SYMBOL, per the DESIGN section 3 rule that a citation names the module and declaration rather than a position or a snapshot. No rendered status string either: standing is DERIVED below, never stored, so a transcribed standing is unrepresentable rather than lens-caught.\n\nTHIS IS NOT AN ACCEPTANCE AND MUST NOT BE READ AS ONE. gunbc.roadmap_model RoadmapAcceptanceReceipt remains the only fact node_accepted consults, and it requires the node's criteria digest plus executed red-control and delivered handback evidence. This receipt records that ONE piece of evidence landed under a node that is still OPEN -- for the native-selected-witness-bundle node, operational migration rate, fallback counting, artifact identity and fail-closed memory all remain open. A receipt here moves nothing on the roadmap graph.\n\nGENERALIZATION TRIGGER, named rather than pre-built (DESIGN section 6, model just-in-time): partial evidence landing under an open node is not native-witness-specific, but it has exactly one inhabitant today. On the SECOND subject needing this shape, the node identity plus landed change plus evaluated head plus cited evidence generalize into one carrier and this module becomes an instantiation; minting the general shape now would be uninhabited generality." +// ONE HOME FOR A MUTABLE PROGRAM STATUS, SO NO DOCUMENT HAS TO HOLD IT TWICE (operator ruling on +// gunbc#7710, 2026-08-02). That PR wrote the same changing observation -- native-at-small-scale +// evidence landed, with its pull request, merge commit, evaluated head and pass count -- +// independently into gunbc.roadmap_authority five_minute_ci_gate_program_note AND into +// gunbc.design_document, and therefore into generated DESIGN.md. Two hand-maintained copies of one +// advancing status is the DESIGN section 3 dual representation: someone updates one, the other +// silently goes stale, and both read as authoritative. The operator's split is the model here -- +// the DURABLE DESIGN FACT is two clauses (native-at-small-scale evidence has landed; operational +// migration rate remains open) and belongs in the design criteria; the pull request, merge commit, +// evaluated head and executed witness are EXECUTION EVIDENCE and belong in exactly one typed +// receipt that both documents cite. +// +// WHAT IS DELIBERATELY NOT STORED, and why the exclusion is the point. No pass count. gunbc#7710 +// recorded 25 of 25 witness receipts; that module declared 31 test functions less than a day later, +// so a transcribed count is stale before the ink dries and no oracle in this repository could have +// caught it. The per-member verdicts already have a single authority in std.selected_witness_bundle +// NativeWitnessBundleExecutionReceipt member_equivalence, produced by the run itself; a hand-typed +// integer beside it would be a second representation of that list. executed_evidence therefore +// cites the witness SYMBOL, per the DESIGN section 3 rule that a citation names the module and +// declaration rather than a position or a snapshot. No rendered status string either: standing is +// DERIVED below, never stored, so a transcribed standing is unrepresentable rather than +// lens-caught. +// +// THIS IS NOT AN ACCEPTANCE AND MUST NOT BE READ AS ONE. gunbc.roadmap_model +// RoadmapAcceptanceReceipt remains the only fact node_accepted consults, and it requires the node's +// criteria digest plus executed red-control and delivered handback evidence. This receipt records +// that ONE piece of evidence landed under a node that is still OPEN -- for the +// native-selected-witness-bundle node, operational migration rate, fallback counting, artifact +// identity and fail-closed memory all remain open. A receipt here moves nothing on the roadmap +// graph. +// +// GENERALIZATION TRIGGER, named rather than pre-built (DESIGN section 6, model just-in-time): +// partial evidence landing under an open node is not native-witness-specific, but it has exactly +// one inhabitant today. On the SECOND subject needing this shape, the node identity plus landed +// change plus evaluated head plus cited evidence generalize into one carrier and this module +// becomes an instantiation; minting the general shape now would be uninhabited generality. type NativeSelectedWitnessTransitionReceipt { node: RoadmapNodeIdentity @@ -14,7 +49,19 @@ type NativeSelectedWitnessTransitionReceipt { executed_evidence: DeclarationRef } -data transition_evidence_standing_note: String = "THE HEAD THE WITNESSES RAN AT IS NOT THE COMMIT THAT LANDED THEM, AND THE DIFFERENCE IS THE WHOLE HONESTY CONTENT. gunbc#7710 carried this distinction in prose -- receipts executed at the pre-merge head, not at the merge -- which is exactly the kind of true sentence that survives only as long as its author is watching. gunbc.roadmap_verification_receipt already states the underlying law for the verification case: head A and head B are different subjects, so evidence gathered at one cannot silently impersonate evidence about the other. Deriving the standing from the two commits the receipt already carries turns that sentence into structure: a reader cannot quote an evidence run as verified-at-the-merged-tree unless the two commits actually agree, because the arm naming both commits is what the fold returns when they do not.\n\nBOTH ARMS ARE ADMISSIBLE EVIDENCE. EvidenceExecutedBeforeLanding is not a refusal and not a defect -- pre-merge execution is the normal shape of a controls-clear-then-merge sequence. It is a NAMED standing, so that the weaker claim cannot be reported as the stronger one." +// THE HEAD THE WITNESSES RAN AT IS NOT THE COMMIT THAT LANDED THEM, AND THE DIFFERENCE IS THE WHOLE +// HONESTY CONTENT. gunbc#7710 carried this distinction in prose -- receipts executed at the +// pre-merge head, not at the merge -- which is exactly the kind of true sentence that survives only +// as long as its author is watching. gunbc.roadmap_verification_receipt already states the +// underlying law for the verification case: head A and head B are different subjects, so evidence +// gathered at one cannot silently impersonate evidence about the other. Deriving the standing from +// the two commits the receipt already carries turns that sentence into structure: a reader cannot +// quote an evidence run as verified-at-the-merged-tree unless the two commits actually agree, +// because the arm naming both commits is what the fold returns when they do not. +// +// BOTH ARMS ARE ADMISSIBLE EVIDENCE. EvidenceExecutedBeforeLanding is not a refusal and not a +// defect -- pre-merge execution is the normal shape of a controls-clear-then-merge sequence. It is +// a NAMED standing, so that the weaker claim cannot be reported as the stronger one. type TransitionEvidenceStanding = EvidenceExecutedAtLandedCommit @@ -36,7 +83,17 @@ fn transition_evidence_standing( } } -data native_at_small_scale_receipt_note: String = "THE ONE INHABITANT. Subject is the durable roadmap node identity rn_2I5K7M9O1Q3R6T8V0W2X4Y7Z9A1C, the native-selected-witness-bundle node, rather than its editable slug -- the same choice gunbc.roadmap_verification_receipt VerificationSubject makes, and it matters here because a slug rename must not orphan the evidence. The landed change is gunbc#7599, merge commit 1ebff27f1e538a06edbaf6a1871bff4c10887b39. The witnesses executed at 2f9e780e6d2130680c4d9fb49739bf0c6cc7c746, the pre-merge branch head, so this receipt's derived standing is EvidenceExecutedBeforeLanding and no consumer may report it as verified at the merged tree. The cited evidence is the cutover-evidence witness in v2.test.execution.native_selected_witness_bundle, which is where the equivalence between native and interpreted execution is actually established; the timing ratio against interpreted and warm-cargo paths remains claimed-not-verified per the design note, and nothing here asserts it." +// THE ONE INHABITANT. Subject is the durable roadmap node identity rn_2I5K7M9O1Q3R6T8V0W2X4Y7Z9A1C, +// the native-selected-witness-bundle node, rather than its editable slug -- the same choice +// gunbc.roadmap_verification_receipt VerificationSubject makes, and it matters here because a slug +// rename must not orphan the evidence. The landed change is gunbc#7599, merge commit +// 1ebff27f1e538a06edbaf6a1871bff4c10887b39. The witnesses executed at +// 2f9e780e6d2130680c4d9fb49739bf0c6cc7c746, the pre-merge branch head, so this receipt's derived +// standing is EvidenceExecutedBeforeLanding and no consumer may report it as verified at the merged +// tree. The cited evidence is the cutover-evidence witness in +// v2.test.execution.native_selected_witness_bundle, which is where the equivalence between native +// and interpreted execution is actually established; the timing ratio against interpreted and +// warm-cargo paths remains claimed-not-verified per the design note, and nothing here asserts it. fn native_at_small_scale_transition_receipt() -> NativeSelectedWitnessTransitionReceipt { NativeSelectedWitnessTransitionReceipt { diff --git a/dag/gunbc/node_http_server_emit.dag b/dag/gunbc/node_http_server_emit.dag index 57b9571f00b..de32d73089d 100644 --- a/dag/gunbc/node_http_server_emit.dag +++ b/dag/gunbc/node_http_server_emit.dag @@ -40,7 +40,18 @@ import extdeps.languages.typescript.program { ts_spread, } -data node_http_server_emit_disposition_note: String = "DISPOSITION (P5, UI-model migration): the belt-A ROADMAP dashboard emit is DELETED. Belt B (gunbc serve / gunbc.roadmap_serve) retired it from deploy membership (live_deploy_spec_belt_b_note: the service is `gunbc serve`, not an emitted server.js) and this PR proved the belt-B path end-to-end by execution (GET / , /roadmap, /sandbox render; POST /dispatch and /sandbox/echo answer in-process). Gone with it: the belt-A smuggled-exec dispatch route (BeltDispatchRouteConfig + the execFile-gunbc handler — the very env-var-channel shell-out belt B dissolves to an in-process call), the roadmap static route table, and every roadmap_dashboard_* emitter. This module SURVIVES only for the gunbhub static-server emit (gunbhub_node_http_server_source, the sole live consumer via dag/gunbc/instruments/emit_host_transport.dag) — a static server with no dispatch. Its own dissolution trigger is gunbhub's migration to belt B (gunbc serve), the named follow-on (OUT OF SCOPE here: gunbhub_serve). Not a silent zombie: the roadmap emit is removed, not merely marked, and the remaining consumer is named." +// DISPOSITION (P5, UI-model migration): the belt-A ROADMAP dashboard emit is DELETED. Belt B (gunbc +// serve / gunbc.roadmap_serve) retired it from deploy membership (live_deploy_spec_belt_b_note: the +// service is `gunbc serve`, not an emitted server.js) and this PR proved the belt-B path end-to-end +// by execution (GET / , /roadmap, /sandbox render; POST /dispatch and /sandbox/echo answer +// in-process). Gone with it: the belt-A smuggled-exec dispatch route (BeltDispatchRouteConfig + the +// execFile-gunbc handler — the very env-var-channel shell-out belt B dissolves to an in-process +// call), the roadmap static route table, and every roadmap_dashboard_* emitter. This module +// SURVIVES only for the gunbhub static-server emit (gunbhub_node_http_server_source, the sole live +// consumer via dag/gunbc/instruments/emit_host_transport.dag) — a static server with no dispatch. +// Its own dissolution trigger is gunbhub's migration to belt B (gunbc serve), the named follow-on +// (OUT OF SCOPE here: gunbhub_serve). Not a silent zombie: the roadmap emit is removed, not merely +// marked, and the remaining consumer is named. type NodeHttpStaticServerSpec { service_name: String diff --git a/dag/gunbc/non_fold_residue.dag b/dag/gunbc/non_fold_residue.dag index d0f36e19d3f..bf593414111 100644 --- a/dag/gunbc/non_fold_residue.dag +++ b/dag/gunbc/non_fold_residue.dag @@ -4,7 +4,17 @@ import std.keyed_roster { KeyedRosterBuild } import std.roster_frontier { FrontierRow, FrontierSubject, PathSubject, frontier_units, frontier_rows_keyed_roster_build, frontier_rows_well_formed } import std.dissolution { DissolutionCondition, unbound_dissolution } -data non_fold_residue_authority_note: String = "Single authority for the non-fold-residue roster (DESIGN §6 census; group-of-units re-home per gunbc.roster_registry, operator ruling 2026-07-22 — formerly the NON_FOLD_RESIDUE_ROSTER const in src/v1/stage0/src/cli_run.rs). Each row asserts a corpus site (a path::fn key) whose match over a closed-coproduct param carries a top-level wildcard arm, with its reason and dissolution trigger as typed fields — shared class strings below, per-row receipts where a dated landing receipt existed in the Rust const's comments. The census SCAN stays host-side (nfr_residue_sites in cli_run.rs) and reads these rows through non_fold_residue_units_from_module_source, so rows must stay DIRECT record literals (a helper-fn call would blind the host reader); the scan and reader fold together into a pure .dag Node-tree lens when compile-graph access lands (gunbc#5364). Detection-only: never auto-delete rows — greening a site deletes its row by hand with the receipt on the PR." +// Single authority for the non-fold-residue roster (DESIGN §6 census; group-of-units re-home per +// gunbc.roster_registry, operator ruling 2026-07-22 — formerly the NON_FOLD_RESIDUE_ROSTER const in +// src/v1/stage0/src/cli_run.rs). Each row asserts a corpus site (a path::fn key) whose match over a +// closed-coproduct param carries a top-level wildcard arm, with its reason and dissolution trigger +// as typed fields — shared class strings below, per-row receipts where a dated landing receipt +// existed in the Rust const's comments. The census SCAN stays host-side (nfr_residue_sites in +// cli_run.rs) and reads these rows through non_fold_residue_units_from_module_source, so rows must +// stay DIRECT record literals (a helper-fn call would blind the host reader); the scan and reader +// fold together into a pure .dag Node-tree lens when compile-graph access lands (gunbc#5364). +// Detection-only: never auto-delete rows — greening a site deletes its row by hand with the receipt +// on the PR. data non_fold_residue_stale_row_ruling_note: String = "2026-07-20 stale-row-removal receipt (carried over from the Rust const): create_double_init_collapsible and create_effect_is_dedupable were rostered AND declared kernel-permanent in cla_is_kernel_permanent_fn, so the analyzer never reports them as live sites and the rows could only ever count as stale. DESIGN §6 makes the classes mutually exclusive — a non-fold residue is either a named irreducible kernel or un-migrated modeling, there is no third — so a declared kernel must not also carry a burn-down row; the kernel declaration is the authority. Attribution (checked, not assumed): main was already red on non_fold_residue_clean_holds / non_fold_residue_no_unrostered_or_stale before #6848 merged — the scheduled cold falsifier failed exactly those witnesses at 2026-07-20T14:12Z on main at 73eea76dd7 (#6914); per-PR ci stayed green because affected-set selection did not run the corpus-read nfr witnesses for those diffs (the masking class the dated reason strings below describe)." diff --git a/dag/gunbc/observation_ci_render.dag b/dag/gunbc/observation_ci_render.dag index a26318f5009..0cdbd016389 100644 --- a/dag/gunbc/observation_ci_render.dag +++ b/dag/gunbc/observation_ci_render.dag @@ -101,9 +101,20 @@ import std.observation { clock_basis_label, } -data observation_ci_render_note: String = "P1 of the progress-and-observation lane: the CI-log renderer, built first because the CI log is where the pain lives. Append-only, no repaint — a GitHub Actions log cannot be redrawn, so every line is final when written. This module PROJECTS the std.observation event vocabulary into lines; it computes no facts of its own and holds no telemetry source. Every number it prints arrives in an event or a heartbeat sample that the process already had (law 5), which is why the flagship acceptance can replay a captured run: the renderer is a pure function of the stream." - -data observation_ci_render_tone_note: String = "Tone contract (operator, 2026-07-24). Arm's-length lines are plain sentences a human reads without decoding — not dense key-equals-value chains, which belong in receipt boxes and files. Magnitudes are in human units: gibibytes, minutes, percent. The named negative example is the floor memory heartbeat's current shape, sixty-plus context-free byte dumps an hour, each carrying a raw current=16106717184 and no subject at all; that line is what the operator watched for ten minutes while a single module typechecked, and it is the exact shape this renderer must not reproduce." +// P1 of the progress-and-observation lane: the CI-log renderer, built first because the CI log is +// where the pain lives. Append-only, no repaint — a GitHub Actions log cannot be redrawn, so every +// line is final when written. This module PROJECTS the std.observation event vocabulary into lines; +// it computes no facts of its own and holds no telemetry source. Every number it prints arrives in +// an event or a heartbeat sample that the process already had (law 5), which is why the flagship +// acceptance can replay a captured run: the renderer is a pure function of the stream. + +// Tone contract (operator, 2026-07-24). Arm's-length lines are plain sentences a human reads +// without decoding — not dense key-equals-value chains, which belong in receipt boxes and files. +// Magnitudes are in human units: gibibytes, minutes, percent. The named negative example is the +// floor memory heartbeat's current shape, sixty-plus context-free byte dumps an hour, each carrying +// a raw current=16106717184 and no subject at all; that line is what the operator watched for ten +// minutes while a single module typechecked, and it is the exact shape this renderer must not +// reproduce. // The scale factors are CONSUMED from std.measure, never respelled here. A renderer carrying its own copy of how many bytes are in a gibibyte is a second unit authority hiding inside a formatting helper, and the fork is easy to miss precisely because it looks like arithmetic rather than modelling. data ci_one_decimal: Nat = 10 @@ -151,7 +162,12 @@ fn ci_human_elapsed(d: Nanosecond) -> String { } } -data ci_human_duration_note: String = "A second duration projection beside gunbc.ci_render.format_duration is deliberate and is not a fork: that one is the COMPACT technical form for receipt boxes and timing tables (230ms, 41s), this one is the SENTENCE form for arm's-length prose (10 minutes). Same fact, two surfaces, chosen by the tone contract rather than by taste. If a third caller wants a third spelling, that is the signal to unify them behind one carrier with a declared style axis rather than to add a fourth." +// A second duration projection beside gunbc.ci_render.format_duration is deliberate and is not a +// fork: that one is the COMPACT technical form for receipt boxes and timing tables (230ms, 41s), +// this one is the SENTENCE form for arm's-length prose (10 minutes). Same fact, two surfaces, +// chosen by the tone contract rather than by taste. If a third caller wants a third spelling, that +// is the signal to unify them behind one carrier with a declared style axis rather than to add a +// fourth. // ObservationEvent retains exact Nanosecond wall time through classification and ordering. Arm's-length lines keep existing millisecond-and-larger spelling byte-stable, while a sub-millisecond event prints its exact nanosecond count instead of fabricating 0ms. The floor conversion is confined to this named presentation boundary. fn ci_measured_bytes_text(m: Measured) -> String { @@ -234,7 +250,12 @@ type ObservationActivity { phase: String } -data ci_activity_grain_note: String = "A subject path carries TWO grains a reader needs at once, and collapsing them loses the one that orients: the coarse phase the run is in (the batch label — witness discovery) and the fine activity inside it (typecheck of a named module, or a declaration-grain witness). A projection that keeps only the deepest segment answers what is happening but not where, and one that keeps only the batch answers where but not what. The heartbeat sentence carries both because the crawl window proved both were missing." +// A subject path carries TWO grains a reader needs at once, and collapsing them loses the one that +// orients: the coarse phase the run is in (the batch label — witness discovery) and the fine +// activity inside it (typecheck of a named module, or a declaration-grain witness). A projection +// that keeps only the deepest segment answers what is happening but not where, and one that keeps +// only the batch answers where but not what. The heartbeat sentence carries both because the crawl +// window proved both were missing. fn ci_subject_activity(subject: ObservationSubject) -> ObservationActivity { fold( @@ -317,7 +338,11 @@ fn ci_duration_suffix(wall: Measured) -> String { } } -data ci_duration_absence_note: String = "An unmeasured duration NAMES ITSELF rather than vanishing from the line. Dropping the clause would have been the model own fabrication rule broken by its first renderer: observation_measured_note says an absent measurement never prints a zero and never omits the field, because a silently omitted number reads as a fact that was never in question. A reader who sees no duration assumes nobody timed it; a reader who sees the cause knows why." +// An unmeasured duration NAMES ITSELF rather than vanishing from the line. Dropping the clause +// would have been the model own fabrication rule broken by its first renderer: +// observation_measured_note says an absent measurement never prints a zero and never omits the +// field, because a silently omitted number reads as a fact that was never in question. A reader who +// sees no duration assumes nobody timed it; a reader who sees the cause knows why. // Every outcome line carries its duration. The reference implementation studied for this lane omits them, which is why a reader cannot tell a fast green from a slow one without diffing timestamps by hand — and the slow green is the one that becomes next month's wall. fn ci_event_line_at_attention(event: ObservationEvent, attention: AttentionLevel) -> RenderedLine { @@ -437,7 +462,11 @@ fn ci_escalation_emission_count(quiet: Millisecond, t: DwellThreshold) -> Nat { scanned.count } -data ci_responsiveness_note: String = "The responsiveness law stated as a predicate the flagship can execute against a real captured window: a subject quiet past T must have produced at least one line naming it. The captured crawl window of run 30044816605 fails this by construction — ten minutes quiet, zero lines — which is what makes it the right acceptance fixture rather than a synthetic one. A planted silent phase reds this predicate; that is the RED control, and it is the same shape as the historical defect." +// The responsiveness law stated as a predicate the flagship can execute against a real captured +// window: a subject quiet past T must have produced at least one line naming it. The captured crawl +// window of run 30044816605 fails this by construction — ten minutes quiet, zero lines — which is +// what makes it the right acceptance fixture rather than a synthetic one. A planted silent phase +// reds this predicate; that is the RED control, and it is the same shape as the historical defect. fn ci_window_is_responsive(quiet: Millisecond, t: DwellThreshold) -> Bool { if millisecond_count(m: quiet) <= millisecond_count(m: t.period) { @@ -503,10 +532,16 @@ fn ci_refusal_restate_line(subject: ObservationSubject, diagnostic: NonEmptyStr) } } -data ci_subject_text_guard_note: String = "A restated diagnostic is text this repo relays from a SUBJECT process, so it goes through extdeps.github.log_annotations.neutralize_workflow_commands: a child whose captured stderr happens to begin with the workflow-command prefix would otherwise mint annotations in the parent run that no gunbc code authored. Consuming the existing guard rather than re-deriving it is the point — the neutralization rule is that module's fact, not this renderer's." - +// A restated diagnostic is text this repo relays from a SUBJECT process, so it goes through +// extdeps.github.log_annotations.neutralize_workflow_commands: a child whose captured stderr +// happens to begin with the workflow-command prefix would otherwise mint annotations in the parent +// run that no gunbc code authored. Consuming the existing guard rather than re-deriving it is the +// point — the neutralization rule is that module's fact, not this renderer's. -data ci_measurement_rss_note: String = "Measurement-feed projection (wiring flip): peak-RSS and cgroup samples are not progress Begin/Concluded pairs — they are Measured facts about the run. This line is Ambient, identity-first (the label), vitals in human GiB via ci_measured_bytes_text, never a raw byte dump. Placement consumers and receipts still read the typed facts; this is the log surface only." +// Measurement-feed projection (wiring flip): peak-RSS and cgroup samples are not progress +// Begin/Concluded pairs — they are Measured facts about the run. This line is Ambient, +// identity-first (the label), vitals in human GiB via ci_measured_bytes_text, never a raw byte +// dump. Placement consumers and receipts still read the typed facts; this is the log surface only. fn ci_measurement_rss_line(label: NonEmptyStr, rss: Measured) -> RenderedLine { RenderedLine { @@ -544,7 +579,6 @@ type LeafEmissionReason | RunSummaryShown | NoAncestorAtGrain - // The seed's per-witness suppression asked the SAME question the projection answers — does routine // work fold? — and answered it in hand-Rust, so the density rule had two representations and only // this one was witnessed. It cannot call ci_routine_projection per witness (one .dag evaluation per @@ -592,7 +626,6 @@ fn ci_rollup_measurement_note(rollup: ObservationRollup) -> String { } } - // Elapsed is the SUM of the folded leaves' measured walls, and the leaves run concurrently across // shards, so it is WORK-TIME and never elapsed wall — DESIGN's cost-partition law says exactly this // ("elapsed wall is NOT additive over concurrent spans"). The word `work` is in the rendered line @@ -708,9 +741,29 @@ fn ci_batch_summary_text( // Per-witness claim-result lines — the ONE presentation authority for floor output. // --------------------------------------------------------------------------- -data ci_witness_claim_result_note: String = "Bazel-shaped claim-result lines for every witness the floor executes. The seed transports subject, function, verdict and wall time; every choice about how the line READS lives here. Subject is either a dotted module path (test.claim.foo) or a repo-relative entry path (dag/test/claim/foo_test.dag); both normalize to a //package:target label. Column width is a display policy, not a semantic fact — the outcome and duration are always present so a fast green and a slow green read differently at arm's length." - -data ci_witness_claim_result_interim_scalar_note: String = "KNOWN BLIND SPOT, NOT A FORMATTING SIMPLIFICATION. Wave 6 routes E1 formatting through this authority but the seed still collapses ClaimOutcome to passed: Bool at the boundary because the hot path cannot afford per-line interpreter eval. That collapse is lossy in the direction that hides the class gunbc#8423 just surfaced: Pass, Fail, and TimedOut all become two display tokens, and TimedOut — budget interruption, not a semantic verdict — is the one that matters. The Bool must not become the shared interface other consumers key on. Warn-tier cost debt was a separate projection until 2026-08-20, when the tier and its renderer were deleted outright (see ci_witness_budget_warn_retirement_receipt). Dissolve-on: replace subject/function/passed/wall with ONE typed per-witness receipt the floor produces once; the renderer projects human lines from the PRODUCT of two independent facts — (1) outcome: ClaimOutcome (Pass | Fail | NotBool | RuntimeError | TimedOut\{elapsed_ms,budget_ms,kind:BudgetKind\}) where TimedOut is the sole budget-refusal variant and elapsed_ms is a lower bound, not a measured duration; (2) disposition: ExpectedRedArm Held | NowPassing | BudgetRefused when enrolled, else not enrolled. The failed / stale_quarantine / budget_refused reporting channels on RequiredFloorOutcome are a run-level partition, not per-witness facts — do not key the renderer on them. Held rows currently print nothing (261 of 795 measured); a follow-up renderer must not assume only refusals get lines." +// Bazel-shaped claim-result lines for every witness the floor executes. The seed transports +// subject, function, verdict and wall time; every choice about how the line READS lives here. +// Subject is either a dotted module path (test.claim.foo) or a repo-relative entry path +// (dag/test/claim/foo_test.dag); both normalize to a //package:target label. Column width is a +// display policy, not a semantic fact — the outcome and duration are always present so a fast green +// and a slow green read differently at arm's length. + +// KNOWN BLIND SPOT, NOT A FORMATTING SIMPLIFICATION. Wave 6 routes E1 formatting through this +// authority but the seed still collapses ClaimOutcome to passed: Bool at the boundary because the +// hot path cannot afford per-line interpreter eval. That collapse is lossy in the direction that +// hides the class gunbc#8423 just surfaced: Pass, Fail, and TimedOut all become two display tokens, +// and TimedOut — budget interruption, not a semantic verdict — is the one that matters. The Bool +// must not become the shared interface other consumers key on. Warn-tier cost debt was a separate +// projection until 2026-08-20, when the tier and its renderer were deleted outright (see +// ci_witness_budget_warn_retirement_receipt). Dissolve-on: replace subject/function/passed/wall +// with ONE typed per-witness receipt the floor produces once; the renderer projects human lines +// from the PRODUCT of two independent facts — (1) outcome: ClaimOutcome (Pass | Fail | NotBool | +// RuntimeError | TimedOut{elapsed_ms,budget_ms,kind:BudgetKind}) where TimedOut is the sole +// budget-refusal variant and elapsed_ms is a lower bound, not a measured duration; (2) disposition: +// ExpectedRedArm Held | NowPassing | BudgetRefused when enrolled, else not enrolled. The failed / +// stale_quarantine / budget_refused reporting channels on RequiredFloorOutcome are a run-level +// partition, not per-witness facts — do not key the renderer on them. Held rows currently print +// nothing (261 of 795 measured); a follow-up renderer must not assume only refusals get lines. data ci_witness_claim_column_width: Nat = 60 @@ -912,9 +965,32 @@ fn ci_witness_claim_result_text( concat(padded, ci_witness_claim_suffix(verdict: verdict, wall: wall)) } -data ci_witness_budget_warn_retirement_receipt: String = "RETIRED 2026-08-20 with the warn tier itself. ci_witness_budget_warn_text rendered the floor fold's [floor-witness-slow] line; the tier it served is deleted at its authority (v2.workflow.required_floor, see the deletion note there), so the renderer has no subject. Its evidence is retired with it rather than left standing, per DESIGN 4b: a witness whose only subject is behaviour the tree deliberately rejects would otherwise become the mechanism by which the rejected behaviour grants itself immortality. What went with it: the two witnesses w_witness_budget_warn_line_holds and w_RED_witness_budget_warn_requires_ceiling_clause, the seed mirror render_witness_budget_warn_text_mirror and its parity test, RequiredFloorClaim.warn_ms, RequiredFloorOutcome.over_warn (which no consumer ever read), the ClaimBudgetTiersInverted refusal, and the emit-census site row witness_budget_warn_site. The two safety deadlines and the diagnostic completed-cost line are untouched — this retires an advisory tier, not a limit." - -data ci_shared_fill_row_note: String = "fill_ms is SELF time and inclusive_ms includes the fills nested inside this one, because these caches compose: building the module graph builds the reference edges and the path index on its way, so a sum over inclusive figures counts each inner scan once per enclosing scan. Measured on the first floor receipt, a 17890ms module-graph fill contained a 12054ms path-index fill and a 5141ms reference-edge fill and was ~695ms of its own. The total is summed over SELF; inclusive is carried beside it because it is what names the caller that caused the inner scans. One line per SHARED FILL in the floor fold — a process-global corpus computation, what it cost, which claim paid it, and how many claims and modules read that same fill for free. This reports the part of a row's wall time that did not belong to that row. It was formerly contrasted here with ci_witness_budget_warn_text, the warn-tier line, which is deleted (see ci_witness_budget_warn_retirement_receipt) — this row-cost projection is unaffected by that retirement and remains live. The disposition is derived from the payer and the consumer breadth by gunbc.witness_row_cost shared_fill_disposition, so the line never carries a verdict the seed authored beside the numbers that decide it." +// RETIRED 2026-08-20 with the warn tier itself. ci_witness_budget_warn_text rendered the floor +// fold's [floor-witness-slow] line; the tier it served is deleted at its authority +// (v2.workflow.required_floor, see the deletion note there), so the renderer has no subject. Its +// evidence is retired with it rather than left standing, per DESIGN 4b: a witness whose only +// subject is behaviour the tree deliberately rejects would otherwise become the mechanism by which +// the rejected behaviour grants itself immortality. What went with it: the two witnesses +// w_witness_budget_warn_line_holds and w_RED_witness_budget_warn_requires_ceiling_clause, the seed +// mirror render_witness_budget_warn_text_mirror and its parity test, RequiredFloorClaim.warn_ms, +// RequiredFloorOutcome.over_warn (which no consumer ever read), the ClaimBudgetTiersInverted +// refusal, and the emit-census site row witness_budget_warn_site. The two safety deadlines and the +// diagnostic completed-cost line are untouched — this retires an advisory tier, not a limit. + +// fill_ms is SELF time and inclusive_ms includes the fills nested inside this one, because these +// caches compose: building the module graph builds the reference edges and the path index on its +// way, so a sum over inclusive figures counts each inner scan once per enclosing scan. Measured on +// the first floor receipt, a 17890ms module-graph fill contained a 12054ms path-index fill and a +// 5141ms reference-edge fill and was ~695ms of its own. The total is summed over SELF; inclusive is +// carried beside it because it is what names the caller that caused the inner scans. One line per +// SHARED FILL in the floor fold — a process-global corpus computation, what it cost, which claim +// paid it, and how many claims and modules read that same fill for free. This reports the part of a +// row's wall time that did not belong to that row. It was formerly contrasted here with +// ci_witness_budget_warn_text, the warn-tier line, which is deleted (see +// ci_witness_budget_warn_retirement_receipt) — this row-cost projection is unaffected by that +// retirement and remains live. The disposition is derived from the payer and the consumer breadth +// by gunbc.witness_row_cost shared_fill_disposition, so the line never carries a verdict the seed +// authored beside the numbers that decide it. fn ci_shared_fill_row_text( cache: String, diff --git a/dag/gunbc/observation_emit_census.dag b/dag/gunbc/observation_emit_census.dag index 545b1a7823b..25d82053c27 100644 --- a/dag/gunbc/observation_emit_census.dag +++ b/dag/gunbc/observation_emit_census.dag @@ -6,22 +6,79 @@ import std.nat { Nat } import std.disposition { Disposition, Terminal, Scaffold, SingleAuthority } import std.decl_ref { DeclarationRef, WholeDeclaration } -data observation_emit_census_note: String = "P3 of the progress-and-observation lane: the census wall. Every place the floor emits a progress line today is either a PROJECTION of the observation event stream (migrated, P1/P2) or a COUNTED FRONTIER ROW carrying a reason and a dissolve-on — the same discipline the site subsumption lane uses for unthemed colours. This module is the census authority: the classification is a closed sum, so a site cannot be half-classified, and the roster below names the structured-tag emit families that exist in the seed today. The executable hygiene witness is bidirectional: it reds when a rostered marker vanishes AND when a known live tag family is missing from the roster, so growth after the snapshot cannot silently understate." - -data observation_emit_census_sequencing_note: String = "Sequencing, per the operator ruling and live-log review (run 30142403230): tagged migrations landed (gantt, governor, typecheck-attribution, measurement, shell with named-intent subjects; Ambient ShellTrace Suppressed at Normal). Post-census growth tags ([floor-drain], [receipt], [file], and #7205's [resolve-split]/[assembly-split]) are CountedFrontierSite rows so the roster cannot silently understate. [gate-warm-cost] and [witness-row-cost-drift] were rows here until 2026-08-11, and #7284's [witness-row-cost-migration-disclosure] until 2026-08-22, each leaving when its raw emit was deleted outright rather than migrated. [floor-memory] LEFT THE ROSTER ON 2026-08-26 AND IT IS NOT ONE OF THESE DEPARTURES, WHICH IS THE FINDING. The marker and its render_heartbeat_line_mirror are gone from the seed (measured: no occurrence of either anywhere under src/, only one doc comment naming the mirror), and read no further that looks exactly like the three retirements above -- an emit deleted at the root, a row simply absent. It is the opposite. The floor still emits a periodic progress line and a per-claim RSS growth line; they are now [floor-heartbeat] and [floor-claim-memory], raw formatted eprintlns in cli_run.rs with no projection behind them. So a MIGRATED row's projection was DELETED and raw emits took over its job under new names -- a reversal, not a retirement. Both successors are on the roster above as CountedFrontierSite, which moves the frontier count UP by two. THE NEAR-MISS IS WHY THIS IS WRITTEN AT LENGTH: the row was first deleted as a completed retirement on the evidence that its marker was gone, and marker-absence is exactly as consistent with a reversal as with a retirement. The census cannot tell them apart by looking at the row that vanished; only by asking what now does the job. That question is not asked by any check here, and it is a second face of the same missing discovery the completeness note names -- an unrostered successor is invisible for precisely the reason an unrostered new family is. THIS NOTE PREVIOUSLY GROUNDED [witness-row-cost] ON `claim_executor discovery_claim_result`, AND THAT FUNCTION DOES NOT EXIST -- not renamed, not moved: no declaration by that name is anywhere in the seed, and the surviving [witness-row-cost] refusals are the witness_cost_* helpers in cli_run.rs. The citation was fabricated at the moment it was written, in the same 2026-08-22 change that restored the row on a correct premise, and it was copied from here into test.claim.observation_raw_print_retirement_acceptance_test, so one invented symbol became two files' evidence. The family is still live and still on the roster; only the symbol naming its occurrence was false." +// P3 of the progress-and-observation lane: the census wall. Every place the floor emits a progress +// line today is either a PROJECTION of the observation event stream (migrated, P1/P2) or a COUNTED +// FRONTIER ROW carrying a reason and a dissolve-on — the same discipline the site subsumption lane +// uses for unthemed colours. This module is the census authority: the classification is a closed +// sum, so a site cannot be half-classified, and the roster below names the structured-tag emit +// families that exist in the seed today. The executable hygiene witness is bidirectional: it reds +// when a rostered marker vanishes AND when a known live tag family is missing from the roster, so +// growth after the snapshot cannot silently understate. + +// Sequencing, per the operator ruling and live-log review (run 30142403230): tagged migrations +// landed (gantt, governor, typecheck-attribution, measurement, shell with named-intent subjects; +// Ambient ShellTrace Suppressed at Normal). Post-census growth tags ([floor-drain], [receipt], +// [file], and #7205's [resolve-split]/[assembly-split]) are CountedFrontierSite rows so the roster +// cannot silently understate. [gate-warm-cost] and [witness-row-cost-drift] were rows here until +// 2026-08-11, and #7284's [witness-row-cost-migration-disclosure] until 2026-08-22, each leaving +// when its raw emit was deleted outright rather than migrated. [floor-memory] LEFT THE ROSTER ON +// 2026-08-26 AND IT IS NOT ONE OF THESE DEPARTURES, WHICH IS THE FINDING. The marker and its +// render_heartbeat_line_mirror are gone from the seed (measured: no occurrence of either anywhere +// under src/, only one doc comment naming the mirror), and read no further that looks exactly like +// the three retirements above -- an emit deleted at the root, a row simply absent. It is the +// opposite. The floor still emits a periodic progress line and a per-claim RSS growth line; they +// are now [floor-heartbeat] and [floor-claim-memory], raw formatted eprintlns in cli_run.rs with no +// projection behind them. So a MIGRATED row's projection was DELETED and raw emits took over its +// job under new names -- a reversal, not a retirement. Both successors are on the roster above as +// CountedFrontierSite, which moves the frontier count UP by two. THE NEAR-MISS IS WHY THIS IS +// WRITTEN AT LENGTH: the row was first deleted as a completed retirement on the evidence that its +// marker was gone, and marker-absence is exactly as consistent with a reversal as with a +// retirement. The census cannot tell them apart by looking at the row that vanished; only by asking +// what now does the job. That question is not asked by any check here, and it is a second face of +// the same missing discovery the completeness note names -- an unrostered successor is invisible +// for precisely the reason an unrostered new family is. THIS NOTE PREVIOUSLY GROUNDED +// [witness-row-cost] ON `claim_executor discovery_claim_result`, AND THAT FUNCTION DOES NOT EXIST +// -- not renamed, not moved: no declaration by that name is anywhere in the seed, and the surviving +// [witness-row-cost] refusals are the witness_cost_* helpers in cli_run.rs. The citation was +// fabricated at the moment it was written, in the same 2026-08-22 change that restored the row on a +// correct premise, and it was copied from here into +// test.claim.observation_raw_print_retirement_acceptance_test, so one invented symbol became two +// files' evidence. The family is still live and still on the roster; only the symbol naming its +// occurrence was false. type EmitSiteDisposition = MigratedToObservation { via: NonEmptyStr } | CountedFrontierSite { reason: NonEmptyStr, dissolution: DissolutionCondition } -data emit_site_disposition_note: String = "A closed sum, so a site is exactly one of two things and never silently neither. MigratedToObservation names the projection that replaced the raw emit — the evidence the site now speaks the event vocabulary. CountedFrontierSite carries a reason it has not migrated yet and the trigger that will dissolve it, so the debt is countable and prioritizable rather than an open-ended intention. There is no third arm, which is the point: an emit site the census has not looked at cannot be represented here, so the roster's completeness is what the witness checks against the seed." +// A closed sum, so a site is exactly one of two things and never silently neither. +// MigratedToObservation names the projection that replaced the raw emit — the evidence the site now +// speaks the event vocabulary. CountedFrontierSite carries a reason it has not migrated yet and the +// trigger that will dissolve it, so the debt is countable and prioritizable rather than an +// open-ended intention. There is no third arm, which is the point: an emit site the census has not +// looked at cannot be represented here, so the roster's completeness is what the witness checks +// against the seed. type CensusedEmitSite { marker: NonEmptyStr disposition: EmitSiteDisposition } -data censused_emit_site_has_no_source_file_note: String = "A ROW NAMES A TAG FAMILY, NOT A FILE, AND THE FIELD THAT NAMED A FILE IS DELETED (2026-08-26). CensusedEmitSite carried source_file: NonEmptyStr, whose sole consumer anywhere in the corpus was the witness's census_marker_present, which read that one file and grepped it. That is a positional citation in the sense §3 rules against -- a second naming scheme for something the seed already contains -- and it rots in the one way this census cannot tolerate: it goes false when the emit MOVES, which is not a census fact at all. It did. #9228 deleted claim_executor's unreachable plan/walk surface (21,366 -> 11,508 lines) and the file-pinned rows went red five at once -- floor-memory, receipt, resolve-split, assembly-split, witness-row-cost -- of which exactly ONE was a real census event (floor-memory's emit left the seed, and its row is deleted accordingly) and four were the pin rotting while the tag family sat untouched in a sibling seed file. A check whose red says `the code moved` where the census asks `does this still emit` is measuring the wrong quantity, and four false reds beside one true one is worse than no check, because the true one is indistinguishable in the noise. What replaces it is seed_emit_sources below: presence is searched across the SEED, so a marker that moves between seed files stays green and a marker that leaves the seed reds. The denominator became the thing the census is actually about." +// A ROW NAMES A TAG FAMILY, NOT A FILE, AND THE FIELD THAT NAMED A FILE IS DELETED (2026-08-26). +// CensusedEmitSite carried source_file: NonEmptyStr, whose sole consumer anywhere in the corpus was +// the witness's census_marker_present, which read that one file and grepped it. That is a +// positional citation in the sense §3 rules against -- a second naming scheme for something the +// seed already contains -- and it rots in the one way this census cannot tolerate: it goes false +// when the emit MOVES, which is not a census fact at all. It did. #9228 deleted claim_executor's +// unreachable plan/walk surface (21,366 -> 11,508 lines) and the file-pinned rows went red five at +// once -- floor-memory, receipt, resolve-split, assembly-split, witness-row-cost -- of which +// exactly ONE was a real census event (floor-memory's emit left the seed, and its row is deleted +// accordingly) and four were the pin rotting while the tag family sat untouched in a sibling seed +// file. A check whose red says `the code moved` where the census asks `does this still emit` is +// measuring the wrong quantity, and four false reds beside one true one is worse than no check, +// because the true one is indistinguishable in the noise. What replaces it is seed_emit_sources +// below: presence is searched across the SEED, so a marker that moves between seed files stays +// green and a marker that leaves the seed reds. The denominator became the thing the census is +// actually about. data seed_emit_sources: List = [ "src/v1/stage0/src/bin/claim_executor.rs", @@ -32,7 +89,19 @@ data seed_emit_sources: List = [ "src/v1/stage0/src/memory_governor.rs" ] -data seed_emit_sources_note: String = "THE CENSUS'S DENOMINATOR: the seed files a tagged progress emit can live in. Presence of a rostered marker is asked of this SET, never of one named file, which is what makes a row survive #9228-shaped code movement and red only when the tag family actually leaves the seed. THIS LIST IS ITSELF HAND-MAINTAINED AND THAT IS THE RESIDUE, stated rather than hidden: a seed file dropped from it narrows the search silently, which is the empty-observation narrow this repository names as strictly worse than a widen. It is mitigated, not closed -- the witness asserts every listed source reads non-empty, so a path that stops existing reds instead of quietly contributing nothing, and the list is one authority for the whole census rather than a pin per row, so it cannot rot thirteen different ways. What would close it is the source-derived denominator both this module's witness and the retirement acceptance already name as their residue: the seed's tagged emits DISCOVERED at occurrence grain from syntax, joined against this roster, reporting live-but-unrostered and rostered-but-absent simultaneously. That discovery is unbuilt, and until it lands this list is the honest approximation of it." +// THE CENSUS'S DENOMINATOR: the seed files a tagged progress emit can live in. Presence of a +// rostered marker is asked of this SET, never of one named file, which is what makes a row survive +// #9228-shaped code movement and red only when the tag family actually leaves the seed. THIS LIST +// IS ITSELF HAND-MAINTAINED AND THAT IS THE RESIDUE, stated rather than hidden: a seed file dropped +// from it narrows the search silently, which is the empty-observation narrow this repository names +// as strictly worse than a widen. It is mitigated, not closed -- the witness asserts every listed +// source reads non-empty, so a path that stops existing reds instead of quietly contributing +// nothing, and the list is one authority for the whole census rather than a pin per row, so it +// cannot rot thirteen different ways. What would close it is the source-derived denominator both +// this module's witness and the retirement acceptance already name as their residue: the seed's +// tagged emits DISCOVERED at occurrence grain from syntax, joined against this roster, reporting +// live-but-unrostered and rostered-but-absent simultaneously. That discovery is unbuilt, and until +// it lands this list is the honest approximation of it. data floor_heartbeat_site: CensusedEmitSite = CensusedEmitSite { marker: "[floor-heartbeat]" as NonEmptyStr, @@ -100,7 +169,6 @@ data floor_drain_site: CensusedEmitSite = CensusedEmitSite { } } - data receipt_site: CensusedEmitSite = CensusedEmitSite { marker: "[receipt]" as NonEmptyStr, disposition: CountedFrontierSite { @@ -158,7 +226,32 @@ data observation_emit_roster: List = [ witness_row_cost_site ] -data observation_emit_roster_understatement_finding: String = "THE ROSTER UNDERSTATES, MEASURED, AND THE BIDIRECTIONAL CLAIM ABOVE IS THEREFORE FALSE AS STATED (2026-08-26). Counting distinct bracket-tag string literals across the six sources in seed_emit_sources gives 57 tag spellings; this roster carries 14 rows, 13 of them bracket tags (the fourteenth is a mirror function name). Subtracting leaves 44 spellings live in the seed and absent from this roster -- among them [ancestry], [interp-stats], [floor-phase], [floor-cgroup], [floor-terminal], [affected-set], [realize-advisory] and [expected-red-roster-join]. THAT 45 IS AN UPPER BOUND AND IS NOT THE FRONTIER DEBT, and saying so precisely is the point: a bracket literal is not the same thing as a tagged progress emit, several of these are certainly not emit tags at all, and nothing here distinguishes a live emit from a literal in a comparison, a parser, or a doc example. The instrument that produced the figure is a spelling grep over six files, which is exactly the instrument this module's witness note already rules insufficient -- so the number is quoted as the shape of the gap, never as its size. WHY IT IS RECORDED RATHER THAN REPAIRED BY ADDING 45 ROWS: every one of those rows would be hand-authored from a spelling, with a disposition and a dissolution trigger invented by whoever wrote it, which is the failure this file has now recorded four times and the thing its own witness note calls `a hand-maintained approximation of a discovery it cannot perform`. Enlarging the approximation does not close the gap; it multiplies the surface that rots. NEXT-RUNG TRIGGER, AND IT IS A SUBSTRATE CAPABILITY RATHER THAN EFFORT: the discovery this needs -- occurrence-grain extraction of tagged emits from seed source -- is not expressible in .dag today. The interpreter registers exactly two string builtins, string_contains and string_length (measured over the seed's builtin registrations), so a fold can ask `does this text contain that` and can ask nothing about WHERE, HOW MANY, or WHAT ELSE OF THIS SHAPE. Membership is decidable; enumeration is not reachable. This class therefore sits at MITIGATABLE with a named trigger rather than at an untracked stall: the trigger is a modeled scan surface over source text -- occurrence positions, or a split, or an ingestion of the seed as a Node tree the way v1_compiler.declaration_index now ingests .dag modules -- after which the roster is JOINED against discovered occurrences and this note is deleted rather than amended." +// THE ROSTER UNDERSTATES, MEASURED, AND THE BIDIRECTIONAL CLAIM ABOVE IS THEREFORE FALSE AS STATED +// (2026-08-26). Counting distinct bracket-tag string literals across the six sources in +// seed_emit_sources gives 57 tag spellings; this roster carries 14 rows, 13 of them bracket tags +// (the fourteenth is a mirror function name). Subtracting leaves 44 spellings live in the seed and +// absent from this roster -- among them [ancestry], [interp-stats], [floor-phase], [floor-cgroup], +// [floor-terminal], [affected-set], [realize-advisory] and [expected-red-roster-join]. THAT 45 IS +// AN UPPER BOUND AND IS NOT THE FRONTIER DEBT, and saying so precisely is the point: a bracket +// literal is not the same thing as a tagged progress emit, several of these are certainly not emit +// tags at all, and nothing here distinguishes a live emit from a literal in a comparison, a parser, +// or a doc example. The instrument that produced the figure is a spelling grep over six files, +// which is exactly the instrument this module's witness note already rules insufficient -- so the +// number is quoted as the shape of the gap, never as its size. WHY IT IS RECORDED RATHER THAN +// REPAIRED BY ADDING 45 ROWS: every one of those rows would be hand-authored from a spelling, with +// a disposition and a dissolution trigger invented by whoever wrote it, which is the failure this +// file has now recorded four times and the thing its own witness note calls `a hand-maintained +// approximation of a discovery it cannot perform`. Enlarging the approximation does not close the +// gap; it multiplies the surface that rots. NEXT-RUNG TRIGGER, AND IT IS A SUBSTRATE CAPABILITY +// RATHER THAN EFFORT: the discovery this needs -- occurrence-grain extraction of tagged emits from +// seed source -- is not expressible in .dag today. The interpreter registers exactly two string +// builtins, string_contains and string_length (measured over the seed's builtin registrations), so +// a fold can ask `does this text contain that` and can ask nothing about WHERE, HOW MANY, or WHAT +// ELSE OF THIS SHAPE. Membership is decidable; enumeration is not reachable. This class therefore +// sits at MITIGATABLE with a named trigger rather than at an untracked stall: the trigger is a +// modeled scan surface over source text -- occurrence positions, or a split, or an ingestion of the +// seed as a Node tree the way v1_compiler.declaration_index now ingests .dag modules -- after which +// the roster is JOINED against discovered occurrences and this note is deleted rather than amended. data observation_emit_roster_completeness_disposition: Disposition = Scaffold { dissolves_to: SingleAuthority, @@ -169,7 +262,59 @@ data observation_emit_roster_completeness_disposition: Disposition = Scaffold { } } -data observation_emit_roster_completeness_note: String = "Roster is bidirectional: every rostered marker must still exist in the seed, AND every known live tag family born after the initial snapshot must appear on the roster (operator finding 3, run 30142403230). The frontier families are floor-drain, receipt, file, resolve-split, assembly-split and witness-row-cost. THE COUNT IS DELIBERATELY NOT WRITTEN HERE: observation_emit_frontier_count folds the roster, so it is the only authority for how many there are, and a number recited beside an enumeration is a second account of a population this module already computes. TWO CORRECTIONS LANDED HERE ON 2026-08-22, in opposite directions, and both are recorded because each was nearly made worse by the other. The recital that stood here said 8 and named witness-row-cost-migration-disclosure among the live families; that one is genuinely gone, its emit deleted at the root with the batch-fed materialization. But this note ALSO named selection-degradation, which has no emit in the seed and has never been on this roster -- so the enumeration overstated by a family that never existed while the count beside it was computed from rows that did, and the two errors partially cancelled. AND witness-row-cost was removed from this roster in that same change on the premise that its marker was gone; it is not. A live eprintln survives on a path with production callers, so removing the row broke the bidirectional rule this very sentence states -- a live tag family absent from the roster. (THE SYMBOL THAT SENTENCE ORIGINALLY NAMED, `claim_executor discovery_claim_result`, RESOLVES TO NOTHING and was fabricated when written; the emits are the witness_cost_* helpers in cli_run.rs, corrected 2026-08-26 on the row itself. The row's premise was right and its citation was invented, which is why the correction is a citation repair and not a reclassification.) The row is restored above with a rewritten reason. What makes this worth recording rather than quietly fixing: the removal was reviewed, the review agreed the marker was gone, and NOTHING WOULD HAVE CAUGHT IT, because the hand-maintained bidirectional conjunction that covers this tag was deleted in the same diff. Correcting the prose to match the roster would have cemented the defect instead of exposing it -- the census's own hygiene depends on a check that a prose-consistency pass can silently remove. THE MIGRATED FAMILIES ARE NOT ENUMERATED HERE ANY LONGER, and the deletion of that enumeration is this note's own rule finally applied to itself: the sentence that stood here named floor-memory first, and on 2026-08-26 floor-memory's emit left the seed with #9228, so the recital went false in exactly the way the paragraphs above and below predict a hand-maintained enumeration will. observation_emit_roster names the migrated rows; restating them here was the second naming scheme, and it is the one that rotted. NEITHER A COUNT NOR THIS ENUMERATION SHOULD BE MAINTAINED HERE AT ALL, and the enumeration above survives only as the corrected record of what went wrong: observation_emit_roster names the migrated rows, so restating them in prose is a second naming scheme for something the roster already names, and it is the naming scheme that rots. No derived migrated_count is minted beside observation_emit_frontier_count either, deliberately -- the frontier count has a real target (the zero-target acceptance drives it to nothing) while a migrated count has nothing driving it, so it would be an artifact with no consumer, and the only assertions available over it are `measure() == measure()` or a literal copied from the current tree. THIS RECITAL SAID `Eight` AND NAMED witness-budget-warn, which has no row in this roster and never had one, while omitting nothing -- so the stated count exceeded the population by exactly the fabricated member. That is the SAME two-directional failure this note already records on the frontier side, arriving on the migrated side and surviving the pass that fixed the other: a count and an enumeration maintained by hand beside each other, where a wrong member and a wrong total can agree. RETIRED BY DELETION 2026-08-11, and the distinction matters: gate-warm-cost, witness-row-cost-drift and compile-clean-cost-drift left this roster because their raw emits were DELETED from the seed, not because they migrated to the observation vocabulary. There is no roster arm for that, and there should not be one — the census rosters emit sites that exist, so a site with no emit is simply absent. What replaced them is the floor-receipts artifact plus each receipt's [receipt] summary line, and [receipt] is itself still a counted frontier family on this roster, so the debt those three represented did not vanish, it CONSOLIDATED into a row that is still counted. Reading 10 -> 8 as two families' worth of progress would overstate it; the honest reading is that three raw TSV mirrors stopped existing and one already-counted summary family absorbed their role. The unmarked eprintln sites remain the per-print completeness scaffold." +// Roster is bidirectional: every rostered marker must still exist in the seed, AND every known live +// tag family born after the initial snapshot must appear on the roster (operator finding 3, run +// 30142403230). The frontier families are floor-drain, receipt, file, resolve-split, assembly-split +// and witness-row-cost. THE COUNT IS DELIBERATELY NOT WRITTEN HERE: observation_emit_frontier_count +// folds the roster, so it is the only authority for how many there are, and a number recited beside +// an enumeration is a second account of a population this module already computes. TWO CORRECTIONS +// LANDED HERE ON 2026-08-22, in opposite directions, and both are recorded because each was nearly +// made worse by the other. The recital that stood here said 8 and named +// witness-row-cost-migration-disclosure among the live families; that one is genuinely gone, its +// emit deleted at the root with the batch-fed materialization. But this note ALSO named +// selection-degradation, which has no emit in the seed and has never been on this roster -- so the +// enumeration overstated by a family that never existed while the count beside it was computed from +// rows that did, and the two errors partially cancelled. AND witness-row-cost was removed from this +// roster in that same change on the premise that its marker was gone; it is not. A live eprintln +// survives on a path with production callers, so removing the row broke the bidirectional rule this +// very sentence states -- a live tag family absent from the roster. (THE SYMBOL THAT SENTENCE +// ORIGINALLY NAMED, `claim_executor discovery_claim_result`, RESOLVES TO NOTHING and was fabricated +// when written; the emits are the witness_cost_* helpers in cli_run.rs, corrected 2026-08-26 on the +// row itself. The row's premise was right and its citation was invented, which is why the +// correction is a citation repair and not a reclassification.) The row is restored above with a +// rewritten reason. What makes this worth recording rather than quietly fixing: the removal was +// reviewed, the review agreed the marker was gone, and NOTHING WOULD HAVE CAUGHT IT, because the +// hand-maintained bidirectional conjunction that covers this tag was deleted in the same diff. +// Correcting the prose to match the roster would have cemented the defect instead of exposing it -- +// the census's own hygiene depends on a check that a prose-consistency pass can silently remove. +// THE MIGRATED FAMILIES ARE NOT ENUMERATED HERE ANY LONGER, and the deletion of that enumeration is +// this note's own rule finally applied to itself: the sentence that stood here named floor-memory +// first, and on 2026-08-26 floor-memory's emit left the seed with #9228, so the recital went false +// in exactly the way the paragraphs above and below predict a hand-maintained enumeration will. +// observation_emit_roster names the migrated rows; restating them here was the second naming +// scheme, and it is the one that rotted. NEITHER A COUNT NOR THIS ENUMERATION SHOULD BE MAINTAINED +// HERE AT ALL, and the enumeration above survives only as the corrected record of what went wrong: +// observation_emit_roster names the migrated rows, so restating them in prose is a second naming +// scheme for something the roster already names, and it is the naming scheme that rots. No derived +// migrated_count is minted beside observation_emit_frontier_count either, deliberately -- the +// frontier count has a real target (the zero-target acceptance drives it to nothing) while a +// migrated count has nothing driving it, so it would be an artifact with no consumer, and the only +// assertions available over it are `measure() == measure()` or a literal copied from the current +// tree. THIS RECITAL SAID `Eight` AND NAMED witness-budget-warn, which has no row in this roster +// and never had one, while omitting nothing -- so the stated count exceeded the population by +// exactly the fabricated member. That is the SAME two-directional failure this note already records +// on the frontier side, arriving on the migrated side and surviving the pass that fixed the other: +// a count and an enumeration maintained by hand beside each other, where a wrong member and a wrong +// total can agree. RETIRED BY DELETION 2026-08-11, and the distinction matters: gate-warm-cost, +// witness-row-cost-drift and compile-clean-cost-drift left this roster because their raw emits were +// DELETED from the seed, not because they migrated to the observation vocabulary. There is no +// roster arm for that, and there should not be one — the census rosters emit sites that exist, so a +// site with no emit is simply absent. What replaced them is the floor-receipts artifact plus each +// receipt's [receipt] summary line, and [receipt] is itself still a counted frontier family on this +// roster, so the debt those three represented did not vanish, it CONSOLIDATED into a row that is +// still counted. Reading 10 -> 8 as two families' worth of progress would overstate it; the honest +// reading is that three raw TSV mirrors stopped existing and one already-counted summary family +// absorbed their role. The unmarked eprintln sites remain the per-print completeness scaffold. fn emit_site_is_frontier(site: CensusedEmitSite) -> Bool { match site.disposition { diff --git a/dag/gunbc/observation_seed_render.dag b/dag/gunbc/observation_seed_render.dag index a62dc3f27f6..3d39e5a6cd5 100644 --- a/dag/gunbc/observation_seed_render.dag +++ b/dag/gunbc/observation_seed_render.dag @@ -60,7 +60,16 @@ import gunbc.observation_ci_render { RenderedLine, } -data observation_seed_render_note: String = "The seed to .dag render boundary. The v1 seed (claim_executor and its siblings) emits progress lines, but the FORMAT is a single authority in gunbc.observation_ci_render — a raw eprintln in the seed forks that format the way a hand-rolled duration string forks std.measure. This module is the boundary the seed calls across, exactly as cli_run.install_output_policy calls output_policy.resolve_channel_policy: primitive arguments in, a rendered line out, no format logic on the seed side. It constructs the ObservationEvent the seed's occurrence corresponds to (a phase concluding is a Concluded event on a PhaseSegment subject) and projects it through the existing renderer, so the seed cannot drift from the model. It holds no telemetry source and computes no facts of its own; every field it fills came in as an argument the seed already had (observation law 5)." +// The seed to .dag render boundary. The v1 seed (claim_executor and its siblings) emits progress +// lines, but the FORMAT is a single authority in gunbc.observation_ci_render — a raw eprintln in +// the seed forks that format the way a hand-rolled duration string forks std.measure. This module +// is the boundary the seed calls across, exactly as cli_run.install_output_policy calls +// output_policy.resolve_channel_policy: primitive arguments in, a rendered line out, no format +// logic on the seed side. It constructs the ObservationEvent the seed's occurrence corresponds to +// (a phase concluding is a Concluded event on a PhaseSegment subject) and projects it through the +// existing renderer, so the seed cannot drift from the model. It holds no telemetry source and +// computes no facts of its own; every field it fills came in as an argument the seed already had +// (observation law 5). // The attention basis governs PLACEMENT (ambient collapses, an anomaly surfaces), not the rendered string — ci_render_line reads only the glyph and text. It is supplied here, not omitted, so a future placement consumer reads a real level rather than a fabricated one, and it is grounded in the clamp overhead the floor already models (the non-per-unit floor time gunbc.ci_spec budgets as gunbc_ci_floor_batch_clamp_params overhead_seconds) rather than a threshold invented in a formatting helper: a prelude phase completing inside the whole overhead budget is routine, so it reads Ambient. The seed passes that overhead in as overhead_ms — this module does not reach for it, keeping the module a pure projection. fn seed_tier(emoji: Bool) -> Tier { @@ -78,7 +87,17 @@ type ScopedRunMeasuredProjection type ScopedRunTimingReceipt = ScopedRunTimingProjected { wall: Nanosecond, seed_resolution: Nanosecond } -data scoped_run_observation_note: String = "The first real density-aware seed consumer. The user program's entry graph stays independent: cli_run resolves this authority as a dedicated graph and transports only the scoped run facts it already owns. The interpreter seed bridge is the narrow primitive-argument call into this module; its hand seam dissolves when the generated host-effect bridge can carry the same typed arguments without a stringly call scaffold. This module constructs the Run/Entry/Phase subject, derives attention and cursor action through observation_tty_render, and delegates every terminal byte and dynamic-line transition to extdeps.render.terminal. The seed may retain the returned canonical dynamic-line state; it may not spell a fallback line or reinterpret a refusal. Monotonic Nanosecond elapsed wall values include the bridge-resolution overhead by starting before authority resolution; no acceptance threshold is invented here, and the measured overhead is recorded by the seed witness receipt." +// The first real density-aware seed consumer. The user program's entry graph stays independent: +// cli_run resolves this authority as a dedicated graph and transports only the scoped run facts it +// already owns. The interpreter seed bridge is the narrow primitive-argument call into this module; +// its hand seam dissolves when the generated host-effect bridge can carry the same typed arguments +// without a stringly call scaffold. This module constructs the Run/Entry/Phase subject, derives +// attention and cursor action through observation_tty_render, and delegates every terminal byte and +// dynamic-line transition to extdeps.render.terminal. The seed may retain the returned canonical +// dynamic-line state; it may not spell a fallback line or reinterpret a refusal. Monotonic +// Nanosecond elapsed wall values include the bridge-resolution overhead by starting before +// authority resolution; no acceptance threshold is invented here, and the measured overhead is +// recorded by the seed witness receipt. fn scoped_run_subject(entry_file: NonEmptyStr, module_path: NonEmptyStr, function: NonEmptyStr) -> ObservationSubject { ObservationSubject { @@ -339,7 +358,14 @@ fn seed_phase_subject(phase: NonEmptyStr) -> ObservationSubject { ObservationSubject { segments: [ PhaseSegment { name: phase } ] } } -data seed_phase_begin_note: String = "A phase Begin is the matched half of observation law 1 for phase-grain subjects. The floor prelude historically only printed Concluded (the mark fires when the phase ends); compile-path gantt marks already pair .begin/.done, so the seed boundary exposes both arms. Wall at Begin is unmeasured by construction — elapsed does not exist yet — and RSS stays on heartbeat/measurement (ci_event_line does not project event.rss). The compile-path seed renders through a Rust MIRROR of these fns rather than the interpreter: trace_mark runs inside compile itself, and calling back into the interpreter to render would recurse. The mirror is proven byte-equal to this oracle by the seed RED (same pattern as seed_heartbeat_line / render_heartbeat_line_mirror)." +// A phase Begin is the matched half of observation law 1 for phase-grain subjects. The floor +// prelude historically only printed Concluded (the mark fires when the phase ends); compile-path +// gantt marks already pair .begin/.done, so the seed boundary exposes both arms. Wall at Begin is +// unmeasured by construction — elapsed does not exist yet — and RSS stays on heartbeat/measurement +// (ci_event_line does not project event.rss). The compile-path seed renders through a Rust MIRROR +// of these fns rather than the interpreter: trace_mark runs inside compile itself, and calling back +// into the interpreter to render would recurse. The mirror is proven byte-equal to this oracle by +// the seed RED (same pattern as seed_heartbeat_line / render_heartbeat_line_mirror). fn phase_begin_line(phase: NonEmptyStr, overhead_ms: Nat, emoji: Bool) -> String { let event = ObservationEvent { @@ -427,7 +453,12 @@ fn seed_high_water_hold_line(current_bytes: Nat, high_water_bytes: Nat, emoji: B ) } -data seed_typecheck_attribution_note: String = "Per-module typecheck attribution (wiring flip): a Concluded event on ModuleSegment+PhaseSegment\{typecheck\} carrying the measured wall, rendered by ci_event_line — written as the module concludes rather than batched to walk end. The 2s threshold that kept the floor log quiet is preserved at the emit site (pathology lane only). Hot-path mirror in cli_run (interpreter render per module under resolve would dominate the wall); oracle RED keeps the mirror byte-equal to this seed." +// Per-module typecheck attribution (wiring flip): a Concluded event on +// ModuleSegment+PhaseSegment{typecheck} carrying the measured wall, rendered by ci_event_line — +// written as the module concludes rather than batched to walk end. The 2s threshold that kept the +// floor log quiet is preserved at the emit site (pathology lane only). Hot-path mirror in cli_run +// (interpreter render per module under resolve would dominate the wall); oracle RED keeps the +// mirror byte-equal to this seed. fn seed_typecheck_subject(module_path: NonEmptyStr) -> ObservationSubject { ObservationSubject { @@ -466,7 +497,10 @@ fn typecheck_concluded_line(module_path: NonEmptyStr, elapsed_ms: Nat, overhead_ data seed_measurement_unreadable_cause: NonEmptyStr = "no /proc/self/status" as NonEmptyStr -data seed_measurement_note: String = "Measurement-feed projection (wiring flip): peak-RSS samples become ci_measurement_rss_line — identity-first label, human GiB, MeasuredUnavailable names its cause. Hot-path mirrors in claim_executor / claim_batch (same class as heartbeat). The typed receipt consumers are unchanged; only the log dialect migrates." +// Measurement-feed projection (wiring flip): peak-RSS samples become ci_measurement_rss_line — +// identity-first label, human GiB, MeasuredUnavailable names its cause. Hot-path mirrors in +// claim_executor / claim_batch (same class as heartbeat). The typed receipt consumers are +// unchanged; only the log dialect migrates. fn seed_peak_rss_line(label: NonEmptyStr, rss_bytes: Nat, rss_available: Bool, emoji: Bool) -> String { ci_render_line( @@ -482,7 +516,13 @@ fn seed_peak_rss_line(label: NonEmptyStr, rss_bytes: Nat, rss_available: Bool, e ) } -data seed_shell_effect_note: String = "Shell host-effect projection (operator live-log 2026-07-25 acceptance): subjects are NAMED INTENTS — the typed service.op key (`git.Inspect.HeadCommit`, `shell.Env.Get`), never raw `$ argv`. Argv is demoted to Failed.error (`$ (exit=N)`, whitespace-collapsed) and the Verbose/title channel. Routine Begin/Concluded\{Done\} are Ambient (ShellTrace-gated; Suppressed at Normal so scaffolding collapses); failing Concluded\{Failed\} is Anomaly (OutsideGroup by law 4), gated by divergence disposition alone — channel Suppressed must never silence a failure. Hot-path mirrors in v1_interpreter; oracle RED keeps them honest." +// Shell host-effect projection (operator live-log 2026-07-25 acceptance): subjects are NAMED +// INTENTS — the typed service.op key (`git.Inspect.HeadCommit`, `shell.Env.Get`), never raw `$ +// argv`. Argv is demoted to Failed.error (`$ (exit=N)`, whitespace-collapsed) and the +// Verbose/title channel. Routine Begin/Concluded{Done} are Ambient (ShellTrace-gated; Suppressed at +// Normal so scaffolding collapses); failing Concluded{Failed} is Anomaly (OutsideGroup by law 4), +// gated by divergence disposition alone — channel Suppressed must never silence a failure. Hot-path +// mirrors in v1_interpreter; oracle RED keeps them honest. fn seed_shell_subject(intent: NonEmptyStr) -> ObservationSubject { ObservationSubject { segments: [ PhaseSegment { name: intent } ] } diff --git a/dag/gunbc/observation_tty_render.dag b/dag/gunbc/observation_tty_render.dag index c50753c2fa7..d87448a69aa 100644 --- a/dag/gunbc/observation_tty_render.dag +++ b/dag/gunbc/observation_tty_render.dag @@ -65,7 +65,15 @@ import std.observation { PsiPressure, } -data observation_tty_render_note: String = "P2 of the progress-and-observation lane: the interactive TTY renderer, a SIBLING projection of the same std.observation carriers the CI renderer projects — not a second model and not the CI renderer's successor. The two differ only where the medium differs. A terminal can be repainted, so routine progress overwrites one line in place and stays quiet; a CI log cannot, so it appends. Everything the two share — the event vocabulary, the density authority, the glyph table, the duration and byte and percent projections, the hold-cause text — is imported from the CI renderer or the model, never re-derived. That sharing is the proof that one event stream drives N renderers (DESIGN section 2); a TTY renderer that re-spelled ci_human_duration would be the fork this lane exists to avoid." +// P2 of the progress-and-observation lane: the interactive TTY renderer, a SIBLING projection of +// the same std.observation carriers the CI renderer projects — not a second model and not the CI +// renderer's successor. The two differ only where the medium differs. A terminal can be repainted, +// so routine progress overwrites one line in place and stays quiet; a CI log cannot, so it appends. +// Everything the two share — the event vocabulary, the density authority, the glyph table, the +// duration and byte and percent projections, the hold-cause text — is imported from the CI renderer +// or the model, never re-derived. That sharing is the proof that one event stream drives N +// renderers (DESIGN section 2); a TTY renderer that re-spelled ci_human_duration would be the fork +// this lane exists to avoid. // The three upgrades over the reference implementation studied for this lane, all inherited from the model rather than re-earned here: outcome lines carry durations (ci_event_line already appends them), Refused is distinct from Failed (the outcome sum and the glyph authority keep them apart), and dwell escalation is recursive rather than one-level (observation_escalation_subject walks the whole tree). The reference has none of these; the TTY renderer gets them for free by projecting the same carriers. type PreambleFields { @@ -75,7 +83,12 @@ type PreambleFields { entry_total: Nat } -data observation_tty_preamble_note: String = "The preamble is REQUIRED — no anonymous process. The reference implementation makes this a construction rule (a titled, described process or nothing), and it is kept: a TTY run opens with what is running, why it was triggered, and how much work it faces, so the first thing on screen answers the question the crawl window could not. The diff-to-runs selection summary is the centre of it (section 6b), but its declaration-grain form rides the namespace lane; this carrier holds the fields that exist today and grows without a format change when decl-grain selection lands." +// The preamble is REQUIRED — no anonymous process. The reference implementation makes this a +// construction rule (a titled, described process or nothing), and it is kept: a TTY run opens with +// what is running, why it was triggered, and how much work it faces, so the first thing on screen +// answers the question the crawl window could not. The diff-to-runs selection summary is the centre +// of it (section 6b), but its declaration-grain form rides the namespace lane; this carrier holds +// the fields that exist today and grows without a format change when decl-grain selection lands. fn tty_preamble_line(fields: PreambleFields) -> RenderedLine { RenderedLine { @@ -108,7 +121,10 @@ fn tty_cursor_action(density: ObservationDensity, attention: AttentionLevel, cap } } -data observation_tty_group_note: String = "The live group line is a repainting projection of the currently-running subject: a spinner, the phase, and the k-of-n counter, overwritten each tick. It names the current entry the way the reference's group line does, and it carries the same identity-first discipline as the heartbeat — the spinner says alive, the text says what is alive." +// The live group line is a repainting projection of the currently-running subject: a spinner, the +// phase, and the k-of-n counter, overwritten each tick. It names the current entry the way the +// reference's group line does, and it carries the same identity-first discipline as the heartbeat — +// the spinner says alive, the text says what is alive. fn tty_group_line(active: ObservationSubject, k: Nat, n: Nat, spinner: SymbolId) -> RenderedLine { let detail = ci_subject_detail_text(subject: active) diff --git a/dag/gunbc/occurrence_identity_acceptance_closure.dag b/dag/gunbc/occurrence_identity_acceptance_closure.dag index 9afeffbdc9f..549d6e0bfb7 100644 --- a/dag/gunbc/occurrence_identity_acceptance_closure.dag +++ b/dag/gunbc/occurrence_identity_acceptance_closure.dag @@ -26,7 +26,27 @@ type OccurrenceIdentityReceiptEnrollmentDebt { dissolution: DissolutionCondition } -data occurrence_identity_consumer_red_debt_rehome_note: String = "PR 7530 rehomed five rows from namespace-canonical-binding to namespace-reference-derived-closure by their actual acceptance boundary. Four receipts are green by local execution in v1.tests.claim.occurrence_identity_debt_receipt_test, but their consumer debt rows remain until gunbc.ci_layer_roots.parser_transport_witness_entries exists on main and executes them per PR. rebuilt-reference-identity-preservation has a deliberately split load-bearing receipt: w_rebuilt_reference_identity_preservation_executes_holds proves identity reaches the canonical sidecar consumer, while the generated pipeline_occurrence_sidecar_serde_round_trip assertion proves the production compile/reconcile rebuild preserves the exact transport. occurrence_identity_receipt_enrollment_debts tracks both missing enrollment surfaces. inferred-target-anchor-identity still belongs to namespace-canonical-binding because its receipt cannot execute before the production projection sites switch over.\n\none-occurrence-collector-dedupe remains on namespace-reference-derived-closure but cannot yet climb: the rehome test asked whether it conceptually precedes production changeover and missed that no production occurrence-aware repeated-discovery collector exists to observe. merge_occurrence_transports concatenates complete authored transports and validation correctly refuses duplicate authored rows; std.occurrence_binding_resolve P1 correctly refuses duplicate supplied candidate ids; the open structural producer buckets each already-validated declaration once and exposes no repeated-observation seam. None is the collector this receipt requires. The retained row names that missing seam as its next-rung trigger rather than fabricating a test-local deduper." +// PR 7530 rehomed five rows from namespace-canonical-binding to namespace-reference-derived-closure +// by their actual acceptance boundary. Four receipts are green by local execution in +// v1.tests.claim.occurrence_identity_debt_receipt_test, but their consumer debt rows remain until +// gunbc.ci_layer_roots.parser_transport_witness_entries exists on main and executes them per PR. +// rebuilt-reference-identity-preservation has a deliberately split load-bearing receipt: +// w_rebuilt_reference_identity_preservation_executes_holds proves identity reaches the canonical +// sidecar consumer, while the generated pipeline_occurrence_sidecar_serde_round_trip assertion +// proves the production compile/reconcile rebuild preserves the exact transport. +// occurrence_identity_receipt_enrollment_debts tracks both missing enrollment surfaces. +// inferred-target-anchor-identity still belongs to namespace-canonical-binding because its receipt +// cannot execute before the production projection sites switch over. +// +// one-occurrence-collector-dedupe remains on namespace-reference-derived-closure but cannot yet +// climb: the rehome test asked whether it conceptually precedes production changeover and missed +// that no production occurrence-aware repeated-discovery collector exists to observe. +// merge_occurrence_transports concatenates complete authored transports and validation correctly +// refuses duplicate authored rows; std.occurrence_binding_resolve P1 correctly refuses duplicate +// supplied candidate ids; the open structural producer buckets each already-validated declaration +// once and exposes no repeated-observation seam. None is the collector this receipt requires. The +// retained row names that missing seam as its next-rung trigger rather than fabricating a +// test-local deduper. data occurrence_identity_consumer_red_debts: List = [ OccurrenceIdentityConsumerRedDebt { @@ -118,7 +138,23 @@ type OccurrenceIdentityClosingVerdict | OccurrenceIdentityClosingOpen { missing_law_ids: List } | OccurrenceIdentityClosingRefused { refusals: List } -data occurrence_identity_closing_check_note: String = "Validation/workflow carrier for the namespace-reference-derived-closure node; execution outcomes, admitted deferrals, and node-closing verdicts deliberately do not live in the std.occurrence_identity identity authority. The closing verdict is derived from that authority's permanent occurrence_identity_acceptance_laws rows, scoped-batch execution observations, and admitted deferrals. The law population is not a caller parameter, so deleting a debt row or passing a narrowed list cannot delete a law. A receipt counts only when its law id and exact required_receipt still match and its execution observation is Passed; the scoped witness batch must supply that observation from execution, never from a roster entry asserting its own success. Unknown, duplicate, stale, non-green, and receipt-plus-deferral rows refuse. Covered with deferred_law_ids is an explicit deferred state, not completion; only Covered with an empty deferred population closes the node. The exact-denominator census is only a hand-maintained spec-growth tripwire; this derived unmatched-law-to-Open fold is the load-bearing guarantee, and the census must be deleted rather than automated into a self-comparison. This five-law occurrence-identity acceptance family is distinct from the six candidate-producer conformance laws owned by the namespace mechanism. Synthetic Passed rows are controls for this fold only and never evidence that a production receipt executed; production integration must replace them with the live scoped execution-observation join." +// Validation/workflow carrier for the namespace-reference-derived-closure node; execution outcomes, +// admitted deferrals, and node-closing verdicts deliberately do not live in the +// std.occurrence_identity identity authority. The closing verdict is derived from that authority's +// permanent occurrence_identity_acceptance_laws rows, scoped-batch execution observations, and +// admitted deferrals. The law population is not a caller parameter, so deleting a debt row or +// passing a narrowed list cannot delete a law. A receipt counts only when its law id and exact +// required_receipt still match and its execution observation is Passed; the scoped witness batch +// must supply that observation from execution, never from a roster entry asserting its own success. +// Unknown, duplicate, stale, non-green, and receipt-plus-deferral rows refuse. Covered with +// deferred_law_ids is an explicit deferred state, not completion; only Covered with an empty +// deferred population closes the node. The exact-denominator census is only a hand-maintained +// spec-growth tripwire; this derived unmatched-law-to-Open fold is the load-bearing guarantee, and +// the census must be deleted rather than automated into a self-comparison. This five-law +// occurrence-identity acceptance family is distinct from the six candidate-producer conformance +// laws owned by the namespace mechanism. Synthetic Passed rows are controls for this fold only and +// never evidence that a production receipt executed; production integration must replace them with +// the live scoped execution-observation join. fn occurrence_identity_law_id_known( law_id: String diff --git a/dag/gunbc/offline_local_recipe.dag b/dag/gunbc/offline_local_recipe.dag index 8dcaa7c469d..7f983797d46 100644 --- a/dag/gunbc/offline_local_recipe.dag +++ b/dag/gunbc/offline_local_recipe.dag @@ -26,7 +26,13 @@ import std.witness_admission { OfflineLocalRecipe, witness_consumer_cadence_eq } // consumer can read, join and refuse over. It is NOT a claim that the recipe ran: no // row here asserts a verdict, and offline_recipe_consumer_defects below carries the // separate, dated, declared population of recipes MEASURED not to produce one. -data offline_local_recipe_authority_note: String = "Typed authority for the documented OfflineLocalRecipe consumers named in gunbc.ci_layer_roots witness_admission_offline_note -- five of the six policies it enumerates, the sixth deliberately absent for the reason identity_captured_navigation_recipe_absence_note states. Replaces the prose recipe sentence in each paired note as the authority for the invocation; the notes keep their rationale and cite this carrier for the command. The rows carry no measurement -- a recipe row is a declaration, not a receipt -- and the measured non-executing population is the separate declared roster offline_recipe_consumer_defects." +// Typed authority for the documented OfflineLocalRecipe consumers named in gunbc.ci_layer_roots +// witness_admission_offline_note -- five of the six policies it enumerates, the sixth deliberately +// absent for the reason identity_captured_navigation_recipe_absence_note states. Replaces the prose +// recipe sentence in each paired note as the authority for the invocation; the notes keep their +// rationale and cite this carrier for the command. The rows carry no measurement -- a recipe row is +// a declaration, not a receipt -- and the measured non-executing population is the separate +// declared roster offline_recipe_consumer_defects. // A recipe names either ONE entry file, or a directory HOME whose residents each take // the same invocation shape. long_lane_exclusion_note is the second kind: test/claim/long/ @@ -272,7 +278,14 @@ fn every_recipe_names_a_declared_policy() -> Bool { // minting a corrected row here would recreate, as typed data, prose that has already been // ruled NOT coverage. A recipe row is the statement that a local consumer exists; for that // policy the ruling is that none should. -data identity_captured_navigation_recipe_absence_note: String = "identity_captured_navigation_roster_gate_exclusion_note was the sixth policy in gunbc.ci_layer_roots witness_admission_offline_note before gunbc#9206 and deliberately owns NO row here: its exclusion row and recipe prose are deleted by gunbc#9206 under an operator ruling for that lane, which is the DELETE arm of the same dissolution this carrier actions for the other five. Measured on 7fbbb84202 by execution: the documented entry path does not exist and claim_batch refuses; at the relocated path the witness executes and answers false. Neither fact is repaired here, because repairing a recipe ruled not-coverage would restore the thing the ruling removes." +// identity_captured_navigation_roster_gate_exclusion_note was the sixth policy in +// gunbc.ci_layer_roots witness_admission_offline_note before gunbc#9206 and deliberately owns NO +// row here: its exclusion row and recipe prose are deleted by gunbc#9206 under an operator ruling +// for that lane, which is the DELETE arm of the same dissolution this carrier actions for the other +// five. Measured on 7fbbb84202 by execution: the documented entry path does not exist and +// claim_batch refuses; at the relocated path the witness executes and answers false. Neither fact +// is repaired here, because repairing a recipe ruled not-coverage would restore the thing the +// ruling removes. // THE FIRST ARM OF THE DECLARED DISSOLUTION HAS NO TARGET, AND SAYING SO IS PART OF // ACTIONING IT. "Graduates to a scheduled CI consumer" was written when scheduled cadences @@ -401,7 +414,6 @@ fn every_defect_row_has_a_recipe() -> Bool { ) } - // THE ARGV IS DERIVED FROM THE ROW, WHICH IS THE WHOLE POINT OF THE ROW. A recipe carried // as prose has to be re-typed by hand at every use and re-edited by hand at every move; // rendered from the carrier it has exactly one authority, and the source roots come from diff --git a/dag/gunbc/ollama_runtime_bundle.dag b/dag/gunbc/ollama_runtime_bundle.dag index 331ce738028..54b0de184bf 100644 --- a/dag/gunbc/ollama_runtime_bundle.dag +++ b/dag/gunbc/ollama_runtime_bundle.dag @@ -12,7 +12,10 @@ import extdeps.ollama.binary_release { } import extdeps.archive.format { ArchiveCompressionCodec, Gzip, Zstd } -data ollama_runtime_bundle_note: String = "Release-bundled Ollama runtime closure identity at ollama_required_release (extdeps.ollama.binary_release). Produces the RuntimeArtifactIdentity.closure_digest consumed by gunbc.spark.serving_release. Materialization binds through gunbc.package_delivery per #8225 — this module derives identity only; it does not install." +// Release-bundled Ollama runtime closure identity at ollama_required_release +// (extdeps.ollama.binary_release). Produces the RuntimeArtifactIdentity.closure_digest consumed by +// gunbc.spark.serving_release. Materialization binds through gunbc.package_delivery per #8225 — +// this module derives identity only; it does not install. // THE CODEC, NOT THE DISTRIBUTION SUFFIX. The suffix is a filename convention -- `.tgz` and // `.tar.gz` are one codec spelled two ways and unpack identically -- so hashing it would split one @@ -64,7 +67,10 @@ fn ollama_runtime_materialization_identity_for_required_release() -> Fnv1a64Stru ollama_runtime_materialization_identity(rel: ollama_required_release) } -data ollama_runtime_observation_identity_note: String = "Maps observed /api/version plus the cited release-asset digest (from install-provenance receipt readback, never desired-state re-derivation) to exactly one catalog row. Version wire alone is ambiguous across generic-cuda and jetpack builds at v0.32.9 — never collapse to ollama_required_release without the asset digest." +// Maps observed /api/version plus the cited release-asset digest (from install-provenance receipt +// readback, never desired-state re-derivation) to exactly one catalog row. Version wire alone is +// ambiguous across generic-cuda and jetpack builds at v0.32.9 — never collapse to +// ollama_required_release without the asset digest. fn ollama_runtime_materialization_identity_for_observed_release( version: String, diff --git a/dag/gunbc/oomd_install.dag b/dag/gunbc/oomd_install.dag index a81579de4d1..a1e6af096e7 100644 --- a/dag/gunbc/oomd_install.dag +++ b/dag/gunbc/oomd_install.dag @@ -109,17 +109,16 @@ type SliceMembershipSignal { } // ONE PARENTED CONTAINER USED TO LAUNDER EVERY UNPARENTED ONE. membership_grounding_check asked -// sessions_in_slice > 0 and passed, so a host running one managed session beside fifty flat -// docker-*.scope sessions under system.slice reported OomdEnforced -- and the fifty are exactly the -// ones oomd cannot evict. The predicate was total over the signal and blind to the distinction that -// decides the outcome, which is the failure mode DESIGN names as total at the level examined. +// sessions_in_slice > 0, so one managed session beside fifty flat docker-*.scope sessions under +// system.slice reported OomdEnforced -- and the fifty are exactly what oomd cannot evict. The +// predicate was total over the signal and blind to the deciding distinction: DESIGN's total at the +// level examined. // -// THE FOUR STANDINGS ARE NOT COLLAPSIBLE TO A BOOL, and Mixed and AllLegacy stay apart because -// their remedies differ: Mixed is a PARTIAL placement or convergence defect on a host that is -// otherwise working, AllLegacy is a managed slice that is completely hollow. NoSessionsObserved is -// neither -- nothing is running, so nothing is wrong and nothing is proven either. Only -// AllSessionsManaged grounds the backstop, because only then is every running session inside the -// cgroup oomd evicts within. +// THE FOUR STANDINGS ARE NOT COLLAPSIBLE TO A BOOL; Mixed and AllLegacy stay apart because their +// remedies differ: Mixed is a PARTIAL placement or convergence defect on an otherwise working host, +// AllLegacy a completely hollow managed slice. NoSessionsObserved is neither -- nothing runs, so +// nothing is wrong and nothing is proven. Only AllSessionsManaged grounds the backstop, because +// only then is every running session inside the cgroup oomd evicts within. type SessionMembershipStanding = AllSessionsManaged { managed_count: Nat } | MixedSessionMembership { managed_count: Nat, legacy_count: Nat } diff --git a/dag/gunbc/os_install_mechanism.dag b/dag/gunbc/os_install_mechanism.dag index ae45943de26..874b314bb38 100644 --- a/dag/gunbc/os_install_mechanism.dag +++ b/dag/gunbc/os_install_mechanism.dag @@ -301,7 +301,28 @@ data srv4_install_mechanism_solution: OsInstallMechanismSolution = solve_install firmware_transition: fleet_firmware_transition_standing ) -data srv3_install_mechanism_board_default_residue: String = "🟡 dissolve-on: srv3_install_mechanism is the last pre-repair row reading the BOARD's shipped-firmware capability row (asrock_altrad8ud_shipped_bmc_firmware_capabilities) instead of a host's own firmware evidence. Its consumer set is now exactly ONE: gunbc.os_install fleet_install_server_specs, which feeds gunbc.generated_artifact — the generated-artifact registry is a total List with no representation for a producer refusal, so migrating that last row requires the registry itself to become a standing (GENERATED-REGISTRY-0). srv3_install_boot_target was DELETED with this note's narrowing: gunbc.bmc_onboarding was its last consumer, and it did not need it — BmcOnboardingPlan carried os_install_boot_target while no production consumer read it, which is how srv4 came to carry srv3's board-default CD target despite having no derived install path at all. The remaining row NO LONGER agrees with srv3_install_mechanism_solution, and that is this note's own prediction coming true rather than a new defect: it reads the board's shipped 2.07.00 capabilities and still answers NbdProxyVirtualMediaInstall, while srv3's BMC is now 3.22.00 and the evidence-derived solution refuses with InstallNoVirtualMediaAndNetworkBootUnestablished. legacy_board_default_row_no_longer_agrees_with_evidence executes that divergence so the stale row cannot be read as a current plan, and there is deliberately no srv4 legacy row because it would be wrong. The refusal is UNESTABLISHED, not impossible: srv4 runs this same firmware and was installed over the configfs USB-gadget transport that extdeps.bmc.virtual_media models as ConfigfsGadgetTransport, which this solver cannot select because OsInstallMechanism predates that authority — teaching it to consume VirtualMediaTransport is the INSTALL-0 repair. DISSOLVES WHEN the registry becomes a standing and fleet_install_server_specs takes the evidence-derived solution; at that point the row, the coincidence witness and this note are all DELETED." +// 🟡 dissolve-on: srv3_install_mechanism is the last pre-repair row reading the BOARD's +// shipped-firmware capability row (asrock_altrad8ud_shipped_bmc_firmware_capabilities) instead of a +// host's own firmware evidence. Its consumer set is now exactly ONE: gunbc.os_install +// fleet_install_server_specs, which feeds gunbc.generated_artifact — the generated-artifact +// registry is a total List with no representation for a producer refusal, so +// migrating that last row requires the registry itself to become a standing (GENERATED-REGISTRY-0). +// srv3_install_boot_target was DELETED with this note's narrowing: gunbc.bmc_onboarding was its +// last consumer, and it did not need it — BmcOnboardingPlan carried os_install_boot_target while no +// production consumer read it, which is how srv4 came to carry srv3's board-default CD target +// despite having no derived install path at all. The remaining row NO LONGER agrees with +// srv3_install_mechanism_solution, and that is this note's own prediction coming true rather than a +// new defect: it reads the board's shipped 2.07.00 capabilities and still answers +// NbdProxyVirtualMediaInstall, while srv3's BMC is now 3.22.00 and the evidence-derived solution +// refuses with InstallNoVirtualMediaAndNetworkBootUnestablished. +// legacy_board_default_row_no_longer_agrees_with_evidence executes that divergence so the stale row +// cannot be read as a current plan, and there is deliberately no srv4 legacy row because it would +// be wrong. The refusal is UNESTABLISHED, not impossible: srv4 runs this same firmware and was +// installed over the configfs USB-gadget transport that extdeps.bmc.virtual_media models as +// ConfigfsGadgetTransport, which this solver cannot select because OsInstallMechanism predates that +// authority — teaching it to consume VirtualMediaTransport is the INSTALL-0 repair. DISSOLVES WHEN +// the registry becomes a standing and fleet_install_server_specs takes the evidence-derived +// solution; at that point the row, the coincidence witness and this note are all DELETED. data srv3_install_mechanism: OsInstallMechanism = solve_install_mechanism( current: asrock_altrad8ud_shipped_bmc_firmware_capabilities, diff --git a/dag/gunbc/output_policy.dag b/dag/gunbc/output_policy.dag index 459fbce79bf..40625a05943 100644 --- a/dag/gunbc/output_policy.dag +++ b/dag/gunbc/output_policy.dag @@ -153,11 +153,61 @@ fn is_condensed(channel: OutputChannel, verbosity: Verbosity) -> Bool { } } -data effect_expectation_note: String = "channel_decision above answers how loud a CHANNEL is. It cannot answer whether a given effect's captured streams are worth surfacing, because that needs a fact it was never given: what the caller EXPECTED. Before this row 'failure' was hand-coded downstream as exit != 0 — an untyped proxy that silently assumes every effect expects success (the empty set). Naming the expectation makes the rule one line: DIVERGENCE SURFACES, AGREEMENT COUNTS, where failure means observed differs from declared-expected at the grain that declared it. That closes two leaks in one dispatch. The noisy one: a red-control child failing exactly as declared is AGREEMENT, so its stderr is counted, not replayed — and a subject stderr carrying '::error::' stops minting annotations in the parent run. The silent one, which nobody has hit yet and which is the real payoff: a red control that SUCCEEDS (the wall failing to fire) is divergence, and today logs nothing at all — the single most diagnosis-worthy event in the system is currently the quietest. There is deliberately no ExpectAny arm: an effect whose expectation is unknown would make every observation agree, restoring the empty set (DESIGN 5 — a failure arm must refuse, never widen)." - -data outcome_is_data_note: String = "OutcomeIsData is the third arm (operator ruling 2026-07-25), and it exists because ExpectSuccess|ExpectFailure mis-models a whole class of site: a PROBE whose exit code is an ANSWER, not a verdict. `test -x` returning 1 means 'subject absent' — ambient in BOTH contexts, because production's ensure-bins responds by building and a witness's discriminating arm responds by asserting. The effect layer's anomaly glyph there is a second, cruder judgment of an event whose real adjudicator is typed logic downstream, so the honest disposition is: render ambient regardless of exit; only DISPATCH failure (the probe itself could not run) is anomalous. That distinction — subject-absent versus probe-broken — is the DaemonUnsupervised lesson applied to exit codes. Loudness does not disappear under this arm, it MOVES to the layer holding the context, which is this model's whole thesis. It is annotatable on a shared helper's own call node, once, correct for every caller — which is why it dissolves the apparent trilemma between threading a test concern through production signatures and an ambient frame that can be left open. ADMISSIBILITY GUARD (§5, what stops this becoming a silence-anything sticker): OutcomeIsData is admissible ONLY where the exit provably feeds a typed consumer — the annotated helper returns a typed observation or verdict, never unit. Both clusters it was ruled for qualify today: witness_bin_artifact_readiness returns its WitnessBinReadiness arms, and belt_actuate_spawn_member's exit feeds the belt's per-variant status mapping. The guard is checkable, so it belongs in the wiring test and eventually a lens; until that lands it is a REVIEW obligation, not a mechanical one, and this note is where the criterion is written down." - -data expectation_declaration_frontier_note: String = "WHETHER a site declared, kept type-distinct from WHAT it declared (operator ruling 2026-07-25: 'we should never have an undeclared expectation; get rid of the fallback'). Until 2026-07-26 the dispatch boundary closed the gap itself — `declared_expectation.unwrap_or(ExpectSuccess)` — which is textbook absorbing fallback (§5): the missing declaration was answered with a plausible one INSIDE the callee, so it left no trace, the frequency of undeclared sites was zero by construction, and the deficit could never rank for fixing. The boundary now takes `ExpectationDeclaration = Declared(ExpectedOutcome) | UntracedDefault`, so absence is a value the caller must construct rather than a default the callee manufactures. UntracedDefault resolves to the SAME ExpectSuccess: deliberately not a behaviour change, and deliberately NOT a floor-time refusal (operator guardrail — the wall is construction at the boundary, not a refusal sweep redding sites nobody has traced). What changed is that it is typed, located, and COUNTED, surfaced as the [expectation-frontier] receipt. THE MEASURED FRONTIER at landing is real, not notional: the census finds 340 service-op call sites in dag/ + src/v2 against 16 carrying `expect:`, and one ordinary witness run reported 67 undeclared Filesystem.Read dispatches. RE-ARMING IS EVIDENCE-GATED, never bulk: a site moves to Declared when someone establishes what it actually expects, because a speculative ExpectFailure would silence a real fault — which is why 324 mechanical annotations were NOT landed with this change. 🟡 dissolve-on: the counted frontier reaches zero corpus-wide, at which point the UntracedDefault arm deletes and an undeclared site becomes a hard typed refusal — absence unwritable rather than merely counted. Executing consumer: dag/test/claim/expectation_frontier_witness_test.dag (present-and-absent pair, both live)." +// channel_decision above answers how loud a CHANNEL is. It cannot answer whether a given effect's +// captured streams are worth surfacing, because that needs a fact it was never given: what the +// caller EXPECTED. Before this row 'failure' was hand-coded downstream as exit != 0 — an untyped +// proxy that silently assumes every effect expects success (the empty set). Naming the expectation +// makes the rule one line: DIVERGENCE SURFACES, AGREEMENT COUNTS, where failure means observed +// differs from declared-expected at the grain that declared it. That closes two leaks in one +// dispatch. The noisy one: a red-control child failing exactly as declared is AGREEMENT, so its +// stderr is counted, not replayed — and a subject stderr carrying '::error::' stops minting +// annotations in the parent run. The silent one, which nobody has hit yet and which is the real +// payoff: a red control that SUCCEEDS (the wall failing to fire) is divergence, and today logs +// nothing at all — the single most diagnosis-worthy event in the system is currently the quietest. +// There is deliberately no ExpectAny arm: an effect whose expectation is unknown would make every +// observation agree, restoring the empty set (DESIGN 5 — a failure arm must refuse, never widen). + +// OutcomeIsData is the third arm (operator ruling 2026-07-25), and it exists because +// ExpectSuccess|ExpectFailure mis-models a whole class of site: a PROBE whose exit code is an +// ANSWER, not a verdict. `test -x` returning 1 means 'subject absent' — ambient in BOTH contexts, +// because production's ensure-bins responds by building and a witness's discriminating arm responds +// by asserting. The effect layer's anomaly glyph there is a second, cruder judgment of an event +// whose real adjudicator is typed logic downstream, so the honest disposition is: render ambient +// regardless of exit; only DISPATCH failure (the probe itself could not run) is anomalous. That +// distinction — subject-absent versus probe-broken — is the DaemonUnsupervised lesson applied to +// exit codes. Loudness does not disappear under this arm, it MOVES to the layer holding the +// context, which is this model's whole thesis. It is annotatable on a shared helper's own call +// node, once, correct for every caller — which is why it dissolves the apparent trilemma between +// threading a test concern through production signatures and an ambient frame that can be left +// open. ADMISSIBILITY GUARD (§5, what stops this becoming a silence-anything sticker): +// OutcomeIsData is admissible ONLY where the exit provably feeds a typed consumer — the annotated +// helper returns a typed observation or verdict, never unit. Both clusters it was ruled for qualify +// today: witness_bin_artifact_readiness returns its WitnessBinReadiness arms, and +// belt_actuate_spawn_member's exit feeds the belt's per-variant status mapping. The guard is +// checkable, so it belongs in the wiring test and eventually a lens; until that lands it is a +// REVIEW obligation, not a mechanical one, and this note is where the criterion is written down. + +// WHETHER a site declared, kept type-distinct from WHAT it declared (operator ruling 2026-07-25: +// 'we should never have an undeclared expectation; get rid of the fallback'). Until 2026-07-26 the +// dispatch boundary closed the gap itself — `declared_expectation.unwrap_or(ExpectSuccess)` — which +// is textbook absorbing fallback (§5): the missing declaration was answered with a plausible one +// INSIDE the callee, so it left no trace, the frequency of undeclared sites was zero by +// construction, and the deficit could never rank for fixing. The boundary now takes +// `ExpectationDeclaration = Declared(ExpectedOutcome) | UntracedDefault`, so absence is a value the +// caller must construct rather than a default the callee manufactures. UntracedDefault resolves to +// the SAME ExpectSuccess: deliberately not a behaviour change, and deliberately NOT a floor-time +// refusal (operator guardrail — the wall is construction at the boundary, not a refusal sweep +// redding sites nobody has traced). What changed is that it is typed, located, and COUNTED, +// surfaced as the [expectation-frontier] receipt. THE MEASURED FRONTIER at landing is real, not +// notional: the census finds 340 service-op call sites in dag/ + src/v2 against 16 carrying +// `expect:`, and one ordinary witness run reported 67 undeclared Filesystem.Read dispatches. +// RE-ARMING IS EVIDENCE-GATED, never bulk: a site moves to Declared when someone establishes what +// it actually expects, because a speculative ExpectFailure would silence a real fault — which is +// why 324 mechanical annotations were NOT landed with this change. 🟡 dissolve-on: the counted +// frontier reaches zero corpus-wide, at which point the UntracedDefault arm deletes and an +// undeclared site becomes a hard typed refusal — absence unwritable rather than merely counted. +// Executing consumer: dag/test/claim/expectation_frontier_witness_test.dag (present-and-absent +// pair, both live). type ExpectedOutcome = ExpectSuccess | ExpectFailure | OutcomeIsData @@ -199,7 +249,11 @@ fn divergence_disposition(diverges: Bool) -> StreamDisposition { } } -data effect_stream_dispatch_note: String = "Observation dissolve (operator live-log 2026-07-25): Ambient ShellTrace silence at Normal must not force StreamSuppressed — Anomaly Failed still surfaces via divergence alone. Quiet no longer swallows divergence either (DESIGN §5: a failing effect is never silent). Verbose still surfaces every stream (Full). The channel grades Ambient scaffolding; the stream policy grades subject content; they compose rather than conflate." +// Observation dissolve (operator live-log 2026-07-25): Ambient ShellTrace silence at Normal must +// not force StreamSuppressed — Anomaly Failed still surfaces via divergence alone. Quiet no longer +// swallows divergence either (DESIGN §5: a failing effect is never silent). Verbose still surfaces +// every stream (Full). The channel grades Ambient scaffolding; the stream policy grades subject +// content; they compose rather than conflate. fn effect_stream_disposition( channel: OutputChannel, @@ -223,7 +277,12 @@ fn surface_effect_stream(disposition: StreamDisposition, text: String) -> String } } -data effect_stream_policy_projection_note: String = "The four corners of effect_stream_disposition at a fixed channel and verbosity, bundled the way channel_policy bundles the five channels — the seed transports evaluated verdicts across the seed/.dag boundary and holds no rule. The ObservedFailure exit literal below is representative, not fabricated: effect_stream_disposition provably does not read the exit payload, and the witness effect_stream_disposition_is_exit_invariant pins that, so the four corners are the whole function and the projection is total." +// The four corners of effect_stream_disposition at a fixed channel and verbosity, bundled the way +// channel_policy bundles the five channels — the seed transports evaluated verdicts across the +// seed/.dag boundary and holds no rule. The ObservedFailure exit literal below is representative, +// not fabricated: effect_stream_disposition provably does not read the exit payload, and the +// witness effect_stream_disposition_is_exit_invariant pins that, so the four corners are the whole +// function and the projection is total. type EffectStreamPolicy { expect_success_observed_success: StreamDisposition @@ -269,7 +328,12 @@ fn resolve_shell_trace_stream_policy(verbose: Bool, quiet: Bool) -> EffectStream shell_trace_stream_policy(verbosity: resolve_verbosity(env: VerbosityEnv { verbose: verbose, quiet: quiet })) } -data shell_trace_observation_convergence_note: String = "DISSOLVED (operator live-log 2026-07-25, run 30142403230): shell host-effects now emit as ObservationEvents with named-intent subjects (service.op); Ambient Begin/Done are ShellTrace-gated (Suppressed at Normal — silent scaffolding); Anomaly Failed surfaces from divergence disposition alone (channel Suppressed no longer forces StreamSuppressed). Derived attention + ci_line_placement supersede the dual-class conflation. Keep this note as the dissolve receipt; delete when a later census no longer needs the history." +// DISSOLVED (operator live-log 2026-07-25, run 30142403230): shell host-effects now emit as +// ObservationEvents with named-intent subjects (service.op); Ambient Begin/Done are +// ShellTrace-gated (Suppressed at Normal — silent scaffolding); Anomaly Failed surfaces from +// divergence disposition alone (channel Suppressed no longer forces StreamSuppressed). Derived +// attention + ci_line_placement supersede the dual-class conflation. Keep this note as the dissolve +// receipt; delete when a later census no longer needs the history. // HOMED BESIDE routine_rollup_folds, NOT BESIDE THE RENDERER, and the reason is a measured one: the // seed resolves both verdicts by name in one entry context, and gunbc.observation_ci_render is not diff --git a/dag/gunbc/ownership.dag b/dag/gunbc/ownership.dag index 10e81ee50f2..f8e94f73e6f 100644 --- a/dag/gunbc/ownership.dag +++ b/dag/gunbc/ownership.dag @@ -2,6 +2,16 @@ module gunbc.ownership import std.types { String } -type Ownership = Owned | Ensured +// The single authority for the ownership classification of a reconcile member. Owned = this +// reconcile created/owns it (teardown-eligible); Ensured = a dependency it requires present but +// does NOT own (never torn down). The reconcile spine gunbc.membership_reconcile reads ownership +// through THIS type; teardown-owned-only (R5) partitions Removed members on it. +// gunbc.live_deploy.spec DeploymentStep does NOT define its own Owned/Ensured coproduct — its +// ArtifactStep/Dependency variants are the payload discriminator, and deployment_step_ownership +// PROJECTS each onto this authority (ArtifactStep -> Owned, Dependency -> Ensured; ownership +// DERIVED, never stored, so an inconsistent ownership/payload pair is unwritable — DESIGN §5 +// construction-over-validation). One Owned|Ensured coproduct in the tree, here. (The pass-1 +// ownership_deployment_step_reground_trigger Scaffold dissolved when this projection landed in pass +// 2 — §6: a scaffold and its dissolution trigger, fired.) -data ownership_authority_note: String = "The single authority for the ownership classification of a reconcile member. Owned = this reconcile created/owns it (teardown-eligible); Ensured = a dependency it requires present but does NOT own (never torn down). The reconcile spine gunbc.membership_reconcile reads ownership through THIS type; teardown-owned-only (R5) partitions Removed members on it. gunbc.live_deploy.spec DeploymentStep does NOT define its own Owned/Ensured coproduct — its ArtifactStep/Dependency variants are the payload discriminator, and deployment_step_ownership PROJECTS each onto this authority (ArtifactStep -> Owned, Dependency -> Ensured; ownership DERIVED, never stored, so an inconsistent ownership/payload pair is unwritable — DESIGN §5 construction-over-validation). One Owned|Ensured coproduct in the tree, here. (The pass-1 ownership_deployment_step_reground_trigger Scaffold dissolved when this projection landed in pass 2 — §6: a scaffold and its dissolution trigger, fired.)" +type Ownership = Owned | Ensured diff --git a/dag/gunbc/package_delivery.dag b/dag/gunbc/package_delivery.dag index b04e7dc6627..c0592092808 100644 --- a/dag/gunbc/package_delivery.dag +++ b/dag/gunbc/package_delivery.dag @@ -97,19 +97,31 @@ import gunbc.spark.serving_runtime_receipt { data package_delivery_placement_note: String = "FIRST CONSUMED package materializer for gunbc. Lives here — not in std — until a second real consumer proves a shared abstraction. Owns Codex (and later other) product policy over upstream package facts from extdeps.npm / extdeps.package. Every production effect boundary RE-ADJUDICATES raw graphs via package_lock_admit_graph — never trusts a freely constructed PackageLockGraphAdmitted. Decoding lock SRI text is NOT artifact verification." -data package_delivery_codex_integrity_note: String = "PRODUCTION PROFILE ≠ FORMAT CLAIM. Stage split: extdeps.npm owns wire decode → NpmRegistryIntegrity; this module admits only SHA-512 from that native integrity. Malformed wire never reaches Codex policy." +// PRODUCTION PROFILE ≠ FORMAT CLAIM. Stage split: extdeps.npm owns wire decode → +// NpmRegistryIntegrity; this module admits only SHA-512 from that native integrity. Malformed wire +// never reaches Codex policy. -data package_delivery_digest_observation_note: String = "Archive verification: DownloadToFile then sha512sum_observe_file (extdeps.tools.sha512sum) → PackageArchiveDigestObservation → admit_observed_archive_digest against lock expectation. Never digest_shell_verify_line." +// Archive verification: DownloadToFile then sha512sum_observe_file (extdeps.tools.sha512sum) → +// PackageArchiveDigestObservation → admit_observed_archive_digest against lock expectation. Never +// digest_shell_verify_line. -data package_delivery_no_digest_shell_route_note: String = "PROVED BY ABSENCE: gunbc.package_delivery does not import digest_shell_verify_line." +// PROVED BY ABSENCE: gunbc.package_delivery does not import digest_shell_verify_line. -data package_delivery_materialization_note: String = "CLOSED CHAIN: admit lock → select target-required registry nodes (host OS×arch ⋈ platform constraints; required nodes always; matching optional platform packages) → independently DownloadToFile+SHA-512-admit EVERY selected archive → seed an isolated npm cache from those exact bytes (npm.Cache.Add) → npm ci --offline --ignore-scripts against that cache only → read back wrapper bin and platform package directory. No network package acquisition after admission." +// CLOSED CHAIN: admit lock → select target-required registry nodes (host OS×arch ⋈ platform +// constraints; required nodes always; matching optional platform packages) → independently +// DownloadToFile+SHA-512-admit EVERY selected archive → seed an isolated npm cache from those exact +// bytes (npm.Cache.Add) → npm ci --offline --ignore-scripts against that cache only → read back +// wrapper bin and platform package directory. No network package acquisition after admission. data codex_runtime_lock_path: NonEmptyStr = "provider-runtime/codex/package-lock.json" data codex_runtime_manifest_path: NonEmptyStr = "provider-runtime/codex/package.json" -data codex_runtime_selection_note: String = "Exact Codex 0.146.0 selection is the committed provider-runtime/codex/\{package.json,package-lock.json\} pair generated by npm install --package-lock-only --ignore-scripts. Do not hand-author the packages map. Edges stay empty until an npm-owned realization supplies endpoints — the admitted value is a path-keyed roster, not a complete resolved dependency graph." +// Exact Codex 0.146.0 selection is the committed +// provider-runtime/codex/{package.json,package-lock.json} pair generated by npm install +// --package-lock-only --ignore-scripts. Do not hand-author the packages map. Edges stay empty until +// an npm-owned realization supplies endpoints — the admitted value is a path-keyed roster, not a +// complete resolved dependency graph. data codex_required_package_name: NonEmptyStr = "@openai/codex" @@ -206,7 +218,8 @@ type CodexRegistryLocatorAdmission cause: NonEmptyStr } -data codex_registry_locator_admission_note: String = "Digest matching proves content identity, not fetch authorization. First Codex profile admits only HTTPS locators under registry.npmjs.org/ before DownloadToFile. file:/http:/other hosts refuse." +// Digest matching proves content identity, not fetch authorization. First Codex profile admits only +// HTTPS locators under registry.npmjs.org/ before DownloadToFile. file:/http:/other hosts refuse. fn admit_codex_registry_artifact_locator( locator: NonEmptyStr, @@ -245,7 +258,10 @@ type AdmittedPackageArtifactReceipt { observed_digest: Sha512Digest } -data admitted_package_path_note: String = "Staging archive paths are NOT durable receipt fields. StagedAdmittedPackageArtifact carries local_artifact_path only while the staging directory lives; CodexRuntimeBundleReceipt / CodexRuntimeBundle retain digest-only AdmittedPackageArtifactReceipt rows after staging is deleted." +// Staging archive paths are NOT durable receipt fields. StagedAdmittedPackageArtifact carries +// local_artifact_path only while the staging directory lives; CodexRuntimeBundleReceipt / +// CodexRuntimeBundle retain digest-only AdmittedPackageArtifactReceipt rows after staging is +// deleted. type StagedAdmittedPackageArtifact { receipt: AdmittedPackageArtifactReceipt @@ -449,7 +465,12 @@ fn codex_expected_wrapper_version() -> NonEmptyStr { (codex_required_package_version as String) as NonEmptyStr } -data codex_runtime_version_identity_note: String = "ONE runtime version marker for every Codex CLI identity probe. codex_required_package_version is the sole authority; the marker is codex-cli as printed by codex --version. Weak codex-cli-only checks let srv1's ambient npm install (0.133.0) pass while extdeps.llm.codex_app_server cites 0.146.0 protocol facts — two runtimes producing incompatible protocol surfaces. SATISFACTION IS EXACT-LINE, not substring: a line trimmed must equal the marker, so codex-cli 0.146.01 / 0.146.0-rc1 / 0.146.0-dev cannot pass by prefix overlap." +// ONE runtime version marker for every Codex CLI identity probe. codex_required_package_version is +// the sole authority; the marker is codex-cli as printed by codex --version. Weak +// codex-cli-only checks let srv1's ambient npm install (0.133.0) pass while +// extdeps.llm.codex_app_server cites 0.146.0 protocol facts — two runtimes producing incompatible +// protocol surfaces. SATISFACTION IS EXACT-LINE, not substring: a line trimmed must equal the +// marker, so codex-cli 0.146.01 / 0.146.0-rc1 / 0.146.0-dev cannot pass by prefix overlap. fn codex_runtime_version_identity_marker() -> String { concat("codex-cli ", codex_required_package_version as String) @@ -467,7 +488,9 @@ fn codex_runtime_version_identity_satisfied(combined_output: String) -> Bool { ) } -data publish_codex_runtime_current_link_note: String = "Typed symlink publish via extdeps.shell.Symlink.AtomicReplace (ln -sfnT argv binding). Runs inside materialize_codex_runtime_bundle during gunbc run materialize, not on the host_effect_apply observe/ensure/read-back transport." +// Typed symlink publish via extdeps.shell.Symlink.AtomicReplace (ln -sfnT argv binding). Runs +// inside materialize_codex_runtime_bundle during gunbc run materialize, not on the +// host_effect_apply observe/ensure/read-back transport. fn publish_codex_runtime_current_link( release_parent: FilePath, @@ -556,7 +579,10 @@ fn observe_codex_npm_runtime_target() -> CodexNpmRuntimeTargetObservation { } } -data codex_exact_platform_lock_key_note: String = "FIRST PROFILE PRODUCT POLICY — not a general npm resolver. Exactly one wrapper lock-node plus exactly one (os,cpu)→platform lock-node via exact-cardinality lookup. Product admission ALSO requires exact package name, version, optionality, and platform os/cpu metadata — a modified lock that keeps the key but changes those facts refuses." +// FIRST PROFILE PRODUCT POLICY — not a general npm resolver. Exactly one wrapper lock-node plus +// exactly one (os,cpu)→platform lock-node via exact-cardinality lookup. Product admission ALSO +// requires exact package name, version, optionality, and platform os/cpu metadata — a modified lock +// that keeps the key but changes those facts refuses. fn codex_platform_lock_path_key(target: CodexNpmRuntimeTarget) -> NonEmptyStr? { if (target.os as String) == "linux" && (target.cpu as String) == "arm64" { @@ -683,7 +709,9 @@ type CodexSelectedNodeAdmission cause: NonEmptyStr } -data codex_install_script_refusal_note: String = "npm ci --ignore-scripts prevents execution; it does not admit a package whose lock row requires an unadmitted build effect. First Codex profile refuses any selected node with hasInstallScript=true. Measured 0.146.0 closure has none — no exception roster." +// npm ci --ignore-scripts prevents execution; it does not admit a package whose lock row requires +// an unadmitted build effect. First Codex profile refuses any selected node with +// hasInstallScript=true. Measured 0.146.0 closure has none — no exception roster. fn list_contains_str(xs: List, needle: NonEmptyStr) -> Bool { xs |> any(x => (x as String) == (needle as String)) @@ -889,9 +917,20 @@ data codex_runtime_release_parent: FilePath = srv1_codex_runtime_release_root data codex_runtime_receipt_sidecar_name: NonEmptyStr = ".gunbc-codex-runtime-receipt" as NonEmptyStr -data admitted_codex_runtime_source_note: String = "ONE carrier binds the bytes adjudicated to the bytes executed. materialize_codex_runtime_bundle admits lock_bytes, then Filesystem.Write those exact lock_bytes and manifest_bytes into the build root before npm ci. A path-based copy of a second lock file is unwritable on this path — the admitted/executed fork is construction-closed." - -data codex_runtime_materialize_memory_defect_note: String = "PRODUCTION DEFECT — CONFIRMED (srv1 2026-08-14, warm-dove-760 + eager-koi-458 independent replay, systemd MemoryMax=8G). ONE file codex_app_server_protocol.schemas.json, 506,984 bytes (~507 KB). Read + parse_json only (parse-only rung) pins 8 GiB (~16,500x input), no completion — curve 0.05→2.36→4.89→7.99 GiB flat. Parse+canonicalize on same file/cap is indistinguishable (0.05→2.36→2.97→7.99). DEFECT: parse_json / JsonValue representation it constructs. EXONERATED: canonicalize_json_value (no separate marginal blow-up), .dag caller (no fold/accumulator/cross-file retention). NOT a Codex/materialize caller problem — three .dag rewrites moved ceiling zero. Remedy: substrate JsonValue representation; operator design review BEFORE implementation. #8166: stop .dag optimisation here." +// ONE carrier binds the bytes adjudicated to the bytes executed. materialize_codex_runtime_bundle +// admits lock_bytes, then Filesystem.Write those exact lock_bytes and manifest_bytes into the build +// root before npm ci. A path-based copy of a second lock file is unwritable on this path — the +// admitted/executed fork is construction-closed. + +// PRODUCTION DEFECT — CONFIRMED (srv1 2026-08-14, warm-dove-760 + eager-koi-458 independent replay, +// systemd MemoryMax=8G). ONE file codex_app_server_protocol.schemas.json, 506,984 bytes (~507 KB). +// Read + parse_json only (parse-only rung) pins 8 GiB (~16,500x input), no completion — curve +// 0.05→2.36→4.89→7.99 GiB flat. Parse+canonicalize on same file/cap is indistinguishable +// (0.05→2.36→2.97→7.99). DEFECT: parse_json / JsonValue representation it constructs. EXONERATED: +// canonicalize_json_value (no separate marginal blow-up), .dag caller (no +// fold/accumulator/cross-file retention). NOT a Codex/materialize caller problem — three .dag +// rewrites moved ceiling zero. Remedy: substrate JsonValue representation; operator design review +// BEFORE implementation. #8166: stop .dag optimisation here. type AdmittedCodexRuntimeSource { manifest_bytes: String @@ -905,7 +944,13 @@ fn admitted_and_executed_lock_bytes_agree( admitted_lock_bytes == executed_lock_bytes } -data codex_runtime_bundle_receipt_note: String = "FULL cryptographic CodexRuntimeBundleReceipt: installed manifest+lock digests + target + digest-only admitted rows + installed-tree identity + native executable digest + npm+node toolchain + canonical protocol-schema bundle identity. Bundle root is content-addressed under gunbc.host_layout.ProviderRuntimePlacement.runtime_release_root/ (srv1 via srv1_provider_runtime_placement; not /tmp). Mutable Codex state lives under runtime_mutable_state_root — never equate a content-derived path with immutability. Reuse requires sidecar + native digest + full rematerialized-tree identity match." +// FULL cryptographic CodexRuntimeBundleReceipt: installed manifest+lock digests + target + +// digest-only admitted rows + installed-tree identity + native executable digest + npm+node +// toolchain + canonical protocol-schema bundle identity. Bundle root is content-addressed under +// gunbc.host_layout.ProviderRuntimePlacement.runtime_release_root/ (srv1 via +// srv1_provider_runtime_placement; not /tmp). Mutable Codex state lives under +// runtime_mutable_state_root — never equate a content-derived path with immutability. Reuse +// requires sidecar + native digest + full rematerialized-tree identity match. type PackageTreeEntryKind = PackageTreeRegularFile { @@ -955,7 +1000,9 @@ type CodexRuntimeBundle { receipt: CodexRuntimeBundleReceipt } -data admitted_package_set_identity_note: String = "AdmittedCodexPackageSetIdentity: SHA-512 over a canonical newline record of (lock_node_key, version, source_locator, observed_digest), rows ordered by lock_node_key ascending (exact two-node closure). Nested inside CodexRuntimeBundleReceipt." +// AdmittedCodexPackageSetIdentity: SHA-512 over a canonical newline record of (lock_node_key, +// version, source_locator, observed_digest), rows ordered by lock_node_key ascending (exact +// two-node closure). Nested inside CodexRuntimeBundleReceipt. fn admitted_package_receipt_canonical_line( receipt: AdmittedPackageArtifactReceipt, @@ -1111,9 +1158,13 @@ fn package_tree_entry_canonical_line(entry: PackageTreeEntryReceipt) -> String { } } -data package_tree_identity_mode_symlink_note: String = "Tree identity covers regular files AND symlinks. Each entry carries mode_octal plus either SHA-512 content (regular file) or symlink target. Enumeration uses GNU find -printf '%y\\t%m\\t%P\\t%l' so a content-preserving mode flip or symlink retarget refuses reuse." +// Tree identity covers regular files AND symlinks. Each entry carries mode_octal plus either +// SHA-512 content (regular file) or symlink target. Enumeration uses GNU find -printf +// '%y\t%m\t%P\t%l' so a content-preserving mode flip or symlink retarget refuses reuse. -data package_materialized_tree_linux_find_note: String = "Installed-tree enumeration currently uses GNU find -printf (mode+symlink target). The first Codex runtime profile is therefore Linux-only; Darwin targets remain declared for package selection but reuse validation on Darwin requires a typed portable filesystem enumeration (dissolve-on)." +// Installed-tree enumeration currently uses GNU find -printf (mode+symlink target). The first Codex +// runtime profile is therefore Linux-only; Darwin targets remain declared for package selection but +// reuse validation on Darwin requires a typed portable filesystem enumeration (dissolve-on). data package_materialized_tree_observation_note: String = "TYPED FIND, TYPED REFUSAL (review 50535). This read previously built its argv by concat-ing the root into a single-quoted find string and running it through retained_foreign — medium-as-string (DESIGN §7), and a root containing an apostrophe escaped the wrapper outright. extdeps.shell already owned shell.Find, so the string form was also a §3 fork of an existing authority: the enumeration now goes through shell.Find.FilesAndSymlinksWithMode, where root is a distinct argv element and no quoting exists to escape. The trailing pipe to sort is gone too — std sort orders the rows in-substrate rather than borrowing the shell's collation, which is locale-dependent and was never declared.\n\nThe refusal half: every failure arm returned a bare none, so a missing find, a permission denial, an uninterpretable row, a digest failure and a write failure were one indistinguishable Absent and the caller's cause string was canned regardless of what actually happened (DESIGN §5 — a failure arm must refuse, and the refusal must be typed and located to be countable). Each arm now names its own cause. NOT a state this fixes, stated because the review named it: an EMPTY tree was never ambiguous — it returns an Observed receipt carrying zero entries, not a refusal." @@ -1318,7 +1369,11 @@ fn canonicalize_json_value(v: JsonValue) -> JsonValue? { } } -data protocol_schema_bundle_identity_note: String = "Protocol schema bundle identity is a content-addressed manifest of per-file canonical digests: for each *.json under the generated schema tree, canonicalize JSON (set membership + sort_by on object members), SHA-512 canonical bytes via one staging file, append only relative-path TAB digest-hex to the manifest file incrementally, then SHA-512 the manifest. Fold retains only a success bit — not manifest lines or canonical bodies." +// Protocol schema bundle identity is a content-addressed manifest of per-file canonical digests: +// for each *.json under the generated schema tree, canonicalize JSON (set membership + sort_by on +// object members), SHA-512 canonical bytes via one staging file, append only relative-path TAB +// digest-hex to the manifest file incrementally, then SHA-512 the manifest. Fold retains only a +// success bit — not manifest lines or canonical bodies. fn protocol_schema_relative_path(abs_s: String, schema_prefix: String) -> String { if abs_s.starts_with(prefix: schema_prefix) { @@ -1852,7 +1907,13 @@ fn validate_existing_codex_runtime_release( } } -data codex_runtime_release_publish_note: String = "Publication uses a UNIQUE same-parent staging directory (mktemp -d ..publishing.XXXXXX), never a deterministic ..publishing path. Tree-identity scratch (materialized-tree.canonical) is ALWAYS outside the publishing/release tree — passed as scratch_dir — so validation cannot plant a file into the tree it is measuring. Final install is GNU mv -T (no-target-directory): equivalent to renameat2(RENAME_NOREPLACE) for directories on Linux — never nests into an existing winner. On AlreadyExists, delete the contender staging tree and validate the winner with the same external scratch." +// Publication uses a UNIQUE same-parent staging directory (mktemp -d ..publishing.XXXXXX), +// never a deterministic ..publishing path. Tree-identity scratch (materialized-tree.canonical) +// is ALWAYS outside the publishing/release tree — passed as scratch_dir — so validation cannot +// plant a file into the tree it is measuring. Final install is GNU mv -T (no-target-directory): +// equivalent to renameat2(RENAME_NOREPLACE) for directories on Linux — never nests into an existing +// winner. On AlreadyExists, delete the contender staging tree and validate the winner with the same +// external scratch. // `emptied.success == false` and `moved.success == false` are NOT the same state (review // on #8621): the former means the mktemp placeholder was not empty -- something else wrote @@ -2646,7 +2707,13 @@ fn codex_runtime_install_context_from_source( data protocol_schema_largest_file_rel: NonEmptyStr = "codex_app_server_protocol.schemas.json" -data protocol_schema_interpreter_bisect_note: String = "INTERPRETER DEFECT CONFIRMED (srv1 2026-08-14, two sessions, two harnesses, hard 8G cap agrees with uncapped). Subject: codex_app_server_protocol.schemas.json, 506,984 bytes (~507 KB). parse-only and parse+canonicalize both pin 8 GiB without completion (~16,500x input) — indistinguishable curves. Defect is parse_json / JsonValue construction, not canonicalize_json_value or .dag caller shape. Witness + claim_batch recipe: dag/test/claim/json_protocol_schema_memory_split_wet_witness_test.dag witness_note. Substrate design review precedes fix." +// INTERPRETER DEFECT CONFIRMED (srv1 2026-08-14, two sessions, two harnesses, hard 8G cap agrees +// with uncapped). Subject: codex_app_server_protocol.schemas.json, 506,984 bytes (~507 KB). +// parse-only and parse+canonicalize both pin 8 GiB without completion (~16,500x input) — +// indistinguishable curves. Defect is parse_json / JsonValue construction, not +// canonicalize_json_value or .dag caller shape. Witness + claim_batch recipe: +// dag/test/claim/json_protocol_schema_memory_split_wet_witness_test.dag witness_note. Substrate +// design review precedes fix. fn finish_bisect_observe_sha512_file(path: FilePath) -> Bool { match observe_file_sha512(path: path) { @@ -3268,7 +3335,13 @@ fn materialize_codex_runtime_bundle( } } -data spark_serving_ollama_runtime_materialization_note: String = "Archive admission only — NOT runtime install. Downloads the cited extdeps.ollama.binary_release archive, admits SHA-256, writes an executor-local gunbc.spark.serving_runtime_receipt (release-asset digest only — no host claim), then deletes the staging copy (local_path is not consumed downstream because nothing extracts). Does not extract, place bin/ollama, enable gunbc-spark-serving.service, or pull gpt-oss:20b. This PR cannot make srv5/srv6 answer — wet observation is a separate SSH read path. Full per-host install is #8225 follow-on; fleet registration is #8230." +// Archive admission only — NOT runtime install. Downloads the cited extdeps.ollama.binary_release +// archive, admits SHA-256, writes an executor-local gunbc.spark.serving_runtime_receipt +// (release-asset digest only — no host claim), then deletes the staging copy (local_path is not +// consumed downstream because nothing extracts). Does not extract, place bin/ollama, enable +// gunbc-spark-serving.service, or pull gpt-oss:20b. This PR cannot make srv5/srv6 answer — wet +// observation is a separate SSH read path. Full per-host install is #8225 follow-on; fleet +// registration is #8230. type SparkServingOllamaArchiveAdmission = SparkServingOllamaArchiveAdmitted { diff --git a/dag/gunbc/physics/bach_double_slit_path_additivity.dag b/dag/gunbc/physics/bach_double_slit_path_additivity.dag index 3f1a7b4ed58..1b3c5847108 100644 --- a/dag/gunbc/physics/bach_double_slit_path_additivity.dag +++ b/dag/gunbc/physics/bach_double_slit_path_additivity.dag @@ -11,9 +11,17 @@ import extdeps.physics.bach_et_al_2013_double_slit { BothSlitsBlocked, } -data bach_double_slit_path_additivity_note: String = "Bach et al. 2013 double-slit scoped path-additivity model: exclusive-alternative raw-count additivity N_both = N_left + N_right over Bach slit-mask configurations. Not std.algebra_law — not a generic classical-additivity hub until a second experiment consumer lands." +// Bach et al. 2013 double-slit scoped path-additivity model: exclusive-alternative raw-count +// additivity N_both = N_left + N_right over Bach slit-mask configurations. Not std.algebra_law — +// not a generic classical-additivity hub until a second experiment consumer lands. -data path_additivity_refutation_receipt_admission_note: String = "Validation-rung admission: path_additivity_refutation_receipt_resolution_from_resolved_counts is the sole production path from bare counts to a receipt on the fold path; equal observed/prediction returns RefutationNotEstablished. PathAdditivityRefutationReceipt remains directly constructible at this rung — witness tests fabricate bad receipts to exercise path_additivity_witness_is_counterexample. Terminal shape: PathAdditivityBandVerdict from derive_path_additivity_refutation_at_band is the authority; receipt is a projection of PathAdditivityRefutationAtBand." +// Validation-rung admission: path_additivity_refutation_receipt_resolution_from_resolved_counts is +// the sole production path from bare counts to a receipt on the fold path; equal +// observed/prediction returns RefutationNotEstablished. PathAdditivityRefutationReceipt remains +// directly constructible at this rung — witness tests fabricate bad receipts to exercise +// path_additivity_witness_is_counterexample. Terminal shape: PathAdditivityBandVerdict from +// derive_path_additivity_refutation_at_band is the authority; receipt is a projection of +// PathAdditivityRefutationAtBand. type ScreenBand = CentralBand diff --git a/dag/gunbc/plan.dag b/dag/gunbc/plan.dag index f4b86eb2835..c77ce8c121b 100644 --- a/dag/gunbc/plan.dag +++ b/dag/gunbc/plan.dag @@ -16,19 +16,18 @@ type PlanRetirement // WHETHER THIS PLAN'S MARKDOWN IS A COMMITTED ARTIFACT AT ALL, declared by the plan and by // nothing else. // -// A plan carrier declares a slug, and the generated-artifact registry turned that slug into a -// committed path unconditionally -- so deleting a plan's markdown left the authority still -// producing it, and the registry kept offering a path for a file the repository had decided -// not to commit. That went unnoticed for exactly as long as nothing adjudicated the population. -// The moment a drift phase does, the absence stops the line and the cheapest way to clear it is -// to REGENERATE, which recreates content a commit deliberately removed: a dormant registry -// mistake converted into a standing obligation to resurrect deleted files, green either way. +// The generated-artifact registry turned every plan slug into a committed path unconditionally, +// so deleting a plan's markdown left the authority still producing it and the registry offering a +// path the repository had decided not to commit. Unnoticed until a drift phase adjudicated the +// population; then the absence stops the line and the cheapest clearance is to REGENERATE content +// a commit deliberately removed -- a dormant registry mistake turned into a standing obligation +// to resurrect deleted files, green either way. // -// So the disposition is a declared fact on the plan rather than an inference from the tree. -// `PlanIsAuthorityOnly` carries the ruling that removed the projection, symbolically, so the -// row cannot drift from its reason; it is not an exemption list and not a shrink of the -// registry -- the plan is still rostered, still generated, still refused if generation fails. -// What changes is only whether its bytes are expected on disk. +// So the disposition is a declared fact on the plan, not an inference from the tree. +// `PlanIsAuthorityOnly` carries the ruling that removed the projection, symbolically, so the row +// cannot drift from its reason; it is not an exemption list and not a shrink of the registry -- +// the plan is still rostered, generated, and refused if generation fails. Only whether its bytes +// are expected on disk changes. type PlanProjection = PlanProjectsCommittedMarkdown | PlanIsAuthorityOnly { ruling: String } @@ -61,11 +60,10 @@ fn plan_to_document(p: Plan) -> MarkdownDocument { MarkdownDocument { blocks: plan_to_blocks(p: p) } } -// Retained deliberately. The payload is now a DissolutionCondition, whose own -// literal-empty form is refused at construction, but refinement at NON-literal -// positions is still deferred -- so an empty description can still reach this -// carrier through that gap and this is the check that refuses it. It dissolves -// when nonliteral refinement enforcement is real, not before. +// Retained deliberately. The payload is a DissolutionCondition whose literal-empty form is refused +// at construction, but refinement at NON-literal positions is still deferred, so an empty +// description can still reach this carrier through that gap; this check refuses it. Dissolves when +// nonliteral refinement enforcement is real, not before. fn plan_has_retirement(p: Plan) -> Bool { match p.retirement { PlanRetiresWhen { condition: c } => dissolution_description(condition: c).length() > 0 diff --git a/dag/gunbc/plans/extdeps_modeling_preflight.dag b/dag/gunbc/plans/extdeps_modeling_preflight.dag index 4539b3e5fa0..2996ba5cda4 100644 --- a/dag/gunbc/plans/extdeps_modeling_preflight.dag +++ b/dag/gunbc/plans/extdeps_modeling_preflight.dag @@ -6,7 +6,11 @@ import std.types { NonEmptyStr } import std.markdown { MarkdownBlock, TableBlock, BlockquoteBlock, AlignNone } import gunbc.plans.md_helpers { h2, p, li, ul, cell, row } -data extdeps_modeling_preflight_law: String = "Normative authority rows for the extdeps modeling preflight (operator verdict on gunbc#7556; DESIGN §3 'External upstream decomposition'): before any new extdeps work, the agent returns this table, and no implementation starts until the table is coherent. The typed rows below are the single authority; the registered plan doc renders its table FROM these rows (derive, never a second copy)." +// Normative authority rows for the extdeps modeling preflight (operator verdict on gunbc#7556; +// DESIGN §3 'External upstream decomposition'): before any new extdeps work, the agent returns this +// table, and no implementation starts until the table is coherent. The typed rows below are the +// single authority; the registered plan doc renders its table FROM these rows (derive, never a +// second copy). type ExtdepsPreflightRow { question: NonEmptyStr diff --git a/dag/gunbc/plans/import_namespace_program.dag b/dag/gunbc/plans/import_namespace_program.dag index 8390e4fa41f..297382575fd 100644 --- a/dag/gunbc/plans/import_namespace_program.dag +++ b/dag/gunbc/plans/import_namespace_program.dag @@ -8,9 +8,9 @@ import gunbc.plans.md_helpers { h2, h3, p, li, ul, quote, cell, row } // WHY THIS ROSTER IS SHORT. The bindings at the foot of this module are the only claims here // joined to a symbol the ingestion-time citation wall resolves. Everything else in this plan is -// PROSE, and is marked as prose in the text rather than dressed as a citation. That is the -// owning session's own instruction, and it is the right one: a long half-bound roster asserts -// that a machine is checking claims it is not checking. +// PROSE, marked as prose in the text rather than dressed as a citation -- the owning session's own +// instruction, and the right one: a long half-bound roster asserts that a machine is checking +// claims it is not checking. fn status_block() -> MarkdownBlock { quote(blocks: [ @@ -140,13 +140,12 @@ fn section_9() -> List { ] } -// Only two claims in this plan are bound to a symbol. Both were vouched for by the owning -// session and both were verified to resolve before this module was authored. The measurement -// receipts, the step ownership and the port-locus item rest on no symbol and are marked as -// prose in the text above rather than given a citation they cannot support. -// WHAT THE INTERLOCK ROWS BELOW ARE: the cross-program precedence ruling (operator, -// 2026-08-26), carried once in its own carrier and cited by both plans rather than -// restated in either. +// Only two claims in this plan are bound to a symbol, both vouched for by the owning session and +// verified to resolve before this module was authored. The measurement receipts, the step ownership +// and the port-locus item rest on no symbol and are marked as prose above rather than given a +// citation they cannot support. +// WHAT THE INTERLOCK ROWS BELOW ARE: the cross-program precedence ruling (operator, 2026-08-26), +// carried once in its own carrier and cited by both plans rather than restated in either. data import_namespace_program_bindings: List = [ decl_ref(module_path: "gunbc.namespace_cut_landing_order", decl_name: "current_landing_order"), decl_ref(module_path: "gunbc.compiler_frontend_program_interlock", decl_name: "milestone_prerequisites"), diff --git a/dag/gunbc/pr_base_freshness.dag b/dag/gunbc/pr_base_freshness.dag index 21f1cfed735..e6eef36c79e 100644 --- a/dag/gunbc/pr_base_freshness.dag +++ b/dag/gunbc/pr_base_freshness.dag @@ -3,11 +3,10 @@ module gunbc.pr_base_freshness import std.types { String, Bool, Int, List, CommitSha, Timestamp } import std.process { ProcessExit, ExitSuccess, ExitFailure } // THE workflow_runs IMPORT BINDS NAMES THIS MODULE NEVER SPELLS, and it is load-bearing anyway: -// importing the module is what registers `github.WorkflowRuns` as a callable service, and dropping -// it fails at RUN time with `unknown service operation`, not at resolve time. It is a selective -// import rather than a whole-module one because `WorkflowRunStatus` and `extdeps.github.pulls`'s -// `ReviewState` both declare a `Pending` variant, and importing both modules wholesale is an -// ambiguity the resolver refuses. +// importing the module registers `github.WorkflowRuns` as a callable service, and dropping it +// fails at RUN time with `unknown service operation`, not at resolve time. It is selective rather +// than whole-module because `WorkflowRunStatus` and `extdeps.github.pulls`'s `ReviewState` both +// declare a `Pending` variant, and importing both wholesale is an ambiguity the resolver refuses. import extdeps.github.workflow_runs { WorkflowRunList, WorkflowRun } import extdeps.github.pulls import extdeps.github.commits { @@ -23,61 +22,56 @@ import extdeps.github.commits { // WHAT THIS MEASURES, AND WHAT IT DELIBERATELY IS NOT. This module answers ONE question about an // open pull request: WHICH BASE COMMIT DID ITS CHECK RUN ACTUALLY TEST, and where does that commit // stand relative to the branch the pull request would merge into RIGHT NOW. It is a reporting -// measurement. It is NOT an admission verdict, it does not decide whether a merge may proceed, and -// it is deliberately named for the observation rather than for the decision. +// measurement, NOT an admission verdict; it does not decide whether a merge may proceed, and it is +// named for the observation rather than the decision. // // THE DISTINCTION IS LOAD-BEARING, NOT NOMENCLATURE. gunbc.merge_admission already models the // admission decision (classify_merge_admission_verdict_v2) over a MergeAdmissionReceiptV2 that the // RUN ITSELF mints -- attempt identity, tested subject, gate roster hash, conclusion. This module -// mints no receipt and reads none. It observes what can be recovered from outside the run, which is -// strictly less, and it must never be read as the weaker mechanism standing in for the stronger -// one. A reader that concluded "freshness reported, therefore admissible" would be substituting -// this authority's answer for a question it cannot reach -- the authority-substitution failure -// DESIGN records, where nothing asserted is false and only the arrow between two carriers is -// invented. +// mints no receipt and reads none. It observes what can be recovered from outside the run, which +// is strictly less, and must never be read as the weaker mechanism standing in for the stronger. +// Concluding "freshness reported, therefore admissible" is the authority-substitution failure +// DESIGN records: nothing asserted is false and only the arrow between two carriers is invented. // // WHY THE MEASUREMENT IS BOUNDED THE WAY IT IS, measured rather than assumed (2026-08-24, against // 51 open pull requests on gunb-ai/gunbc, 48 of which carried both a completed run and a merge -// ref). Our workflow triggers on `pull_request`, so a run tests refs/pull/N/merge -- a merge of the -// head into the base AS OF THAT MOMENT -- and the base it tested is that merge commit's first +// ref). Our workflow triggers on `pull_request`, so a run tests refs/pull/N/merge -- a merge of +// the head into the base AS OF THAT MOMENT -- and the base it tested is that merge commit's first // parent. THAT REF IS MUTABLE. GitHub recomputes it when the base branch moves, and the merge -// commit the run actually tested is then unreferenced and unrecoverable. 9 of those 48 pull -// requests already had a merge-ref base committed AFTER their run started, so what their run tested -// is gone. +// commit the run actually tested is then unreferenced and unrecoverable. 9 of those 48 already had +// a merge-ref base committed AFTER their run started, so what their run tested is gone. // -// AND THE LOSS IS NOT NEUTRAL -- IT FAILS IN THE ADMITTING DIRECTION, which is the whole reason -// this module carries a refusing arm instead of a Bool. Recomputation re-points the ref at the -// CURRENT base tip, so a pull request whose run tested something long superseded compares EQUAL to -// the target and reads as perfectly fresh. Observed instance: pull request 9097's merge-ref base -// was the then-current tip mid-measurement, so the derived answer was "at target" for a run that -// had tested an older base; the target advanced minutes later and the same pull request read -// "behind" again. A mechanism whose wrong answers arrive as greens, and whose right answers depend -// on when you happened to look, cannot be allowed a two-state result. +// AND THE LOSS IS NOT NEUTRAL -- IT FAILS IN THE ADMITTING DIRECTION, which is why this module +// carries a refusing arm instead of a Bool. Recomputation re-points the ref at the CURRENT base +// tip, so a pull request whose run tested something long superseded compares EQUAL to the target +// and reads as perfectly fresh. Observed instance: pull request 9097's merge-ref base was the +// then-current tip mid-measurement, so the derived answer was "at target" for a run that had +// tested an older base; the target advanced minutes later and the same pull request read "behind" +// again. A mechanism whose wrong answers arrive as greens, and whose right answers depend on when +// you looked, cannot be allowed a two-state result. // -// SO `BaseUnknown` IS ITS OWN ARM AND IT REFUSES. It is not folded into "behind" (which would -// assert a distance nothing measured), it is not folded into "at target" (which would be the -// fabricated green above), and it is not silently dropped from the population (which would zero the -// deficit's frequency by construction and stop it ever ranking for repair). It is typed, it carries -// its cause, it is counted, and a report containing any of it is a report that has told you what it -// could not see. +// SO `BaseUnknown` IS ITS OWN ARM AND IT REFUSES. Not folded into "behind" (asserting a distance +// nothing measured), not into "at target" (the fabricated green above), and not silently dropped +// from the population (zeroing the deficit's frequency by construction so it never ranks for +// repair). It is typed, carries its cause, is counted, and a report containing any of it has told +// you what it could not see. // THE PROVENANCE QUESTION, ASKED BEFORE THE FRESHNESS QUESTION. Whether the recovered base is // EVIDENCE about this run is prior to where that base stands: asking how far behind an -// unrecoverable base sits would be answering a question about a commit the run never saw. The -// discriminating fact is ordinary and decidable -- a merge ref whose base parent was committed -// after the run started cannot be the base that run tested. +// unrecoverable base sits answers a question about a commit the run never saw. The discriminating +// fact is ordinary and decidable -- a merge ref whose base parent was committed after the run +// started cannot be the base that run tested. type TestedBaseProvenance = TestedBaseRecovered { base: CommitSha } | TestedBaseSuperseded { observed_base: CommitSha } // THE FRESHNESS ANSWER. Four states, and the fourth is not a degenerate case of the other three. -// WHY THE UNKNOWNS SHARE ONE ARM AND A TYPED REASON, rather than becoming four more peers. They are -// one answer to the consumer's question -- THIS RUN'S EVIDENCE CANNOT BE VOUCHED FOR -- and every -// one of them takes the same action: re-run against current main. Splitting them into peers of -// `BaseBehind` would make every consumer re-derive that grouping, and the first consumer to forget -// one would silently treat it as a freshness answer. Keeping the reason typed and on the row keeps -// the deficit COUNTABLE per cause, which is what makes a specific gap rank for repair instead of -// disappearing into an aggregate. +// WHY THE UNKNOWNS SHARE ONE ARM AND A TYPED REASON, rather than four more peers. They are one +// answer to the consumer's question -- THIS RUN'S EVIDENCE CANNOT BE VOUCHED FOR -- and every one +// takes the same action: re-run against current main. Splitting them into peers of `BaseBehind` +// would make every consumer re-derive that grouping, and the first to forget one would silently +// treat it as a freshness answer. Keeping the reason typed on the row keeps the deficit COUNTABLE +// per cause, so a specific gap ranks for repair instead of disappearing into an aggregate. type BaseUnknownReason = MergeRefRecomputed { observed_base: CommitSha } | MergeCommitNotComputed @@ -103,10 +97,10 @@ fn base_freshness_is_current(f: BaseFreshness) -> Bool { } // UNKNOWN IS NOT CURRENT AND NOT STALE -- IT IS UNKNOWN, so it gets its own predicate rather than -// being read off the negation of the one above. A consumer that wanted "how many are stale" and -// took `!is_current` would silently absorb the unmeasurable rows into the stale count, which is the -// mirror of absorbing them into the fresh count: both destroy the deficit's frequency, and this -// one merely errs in the safer direction while being just as blind. +// the negation of the one above. A consumer wanting "how many are stale" that took `!is_current` +// would silently absorb the unmeasurable rows into the stale count -- the mirror of absorbing +// them into the fresh count: both destroy the deficit's frequency, this one merely erring in the +// safer direction while just as blind. fn base_freshness_is_unknown(f: BaseFreshness) -> Bool { match f { BaseAtTarget => false @@ -145,12 +139,12 @@ fn first_parent(parents: List) -> CommitSha? { } // THE RECOMPUTATION TEST, ON TIMESTAMPS RATHER THAN EPOCH INTEGERS. Both operands are GitHub REST -// `Timestamp`s, which are ISO 8601 in UTC with a fixed-width layout (`2026-08-24T23:37:40Z`), and -// for that encoding lexicographic order IS chronological order — same field widths, most -// significant first, one timezone. So the comparison is exact and needs no epoch conversion, which -// would otherwise be a second representation of the same instant for a parser to disagree about. -// This soundness depends on the fixed-width UTC form and would NOT survive an offset like `+01:00`, -// which GitHub does not emit here. +// `Timestamp`s, ISO 8601 in UTC with a fixed-width layout (`2026-08-24T23:37:40Z`), and for that +// encoding lexicographic order IS chronological order — same field widths, most significant +// first, one timezone. So the comparison is exact and needs no epoch conversion, which would be a +// second representation of the same instant for a parser to disagree about. This soundness +// depends on the fixed-width UTC form and would NOT survive an offset like `+01:00`, which GitHub +// does not emit here. fn merge_ref_was_recomputed( merge_ref_base_committed_at: Timestamp, run_started_at: Timestamp, @@ -174,10 +168,10 @@ fn tested_base_provenance_from_timestamps( } // THE COMPARISON IS READ IN THE DIRECTION IT WAS ASKED. `compare/{tested_base}...{target}` reports -// how the TARGET stands relative to the TESTED BASE, so the target being `Ahead` is the tested base -// being behind, and `ahead_by` is that distance. Naming this explicitly because the inversion is -// the easiest thing in this module to get backwards, and getting it backwards would report a stale -// pull request as current. +// how the TARGET stands relative to the TESTED BASE, so the target being `Ahead` is the tested +// base being behind, and `ahead_by` is that distance. Named explicitly because the inversion is +// the easiest thing in this module to get backwards, and backwards reports a stale pull request as +// current. fn classify_base_freshness( provenance: TestedBaseProvenance, status: ComparisonStatus, @@ -207,15 +201,15 @@ fn base_unknown_reason_label(r: BaseUnknownReason) -> String { } } -// THE RUN START IS ACQUIRED, NOT SUPPLIED, and that is what makes `NoRunForHead` a real state -// rather than a declared one. An earlier revision took `run_started_at` as a parameter, which -// pushed the hardest acquisition onto every caller and left the arm describing a case this module -// could never reach. A head can genuinely carry no run -- the workflow was never triggered, the run -// was garbage-collected, the head was force-pushed past -- and in that case there is no instant to -// compare the merge ref against, so the provenance question has no answer at all. +// THE RUN START IS ACQUIRED, NOT SUPPLIED, which makes `NoRunForHead` a real state rather than a +// declared one. An earlier revision took `run_started_at` as a parameter, pushing the hardest +// acquisition onto every caller and leaving the arm describing a case this module could never +// reach. A head can genuinely carry no run -- the workflow was never triggered, the run was +// garbage-collected, the head was force-pushed past -- and then there is no instant to compare the +// merge ref against, so the provenance question has no answer at all. // -// THE MOST RECENT RUN IS THE ONE THAT MATTERS. GitHub returns runs newest-first for a head, and it -// is the newest run whose green a reader would be relying on at merge time. +// THE MOST RECENT RUN IS THE ONE THAT MATTERS. GitHub returns runs newest-first for a head, and +// the newest run's green is what a reader relies on at merge time. fn pull_request_run_started_at(owner: String, repo: String, head_sha: CommitSha) -> Timestamp? { let runs = github.WorkflowRuns.ListForRef( auth_token: "", @@ -229,11 +223,11 @@ fn pull_request_run_started_at(owner: String, repo: String, head_sha: CommitSha) } } -// THE LIVE OBSERVATION FOR ONE PULL REQUEST. Four upstream facts, and every one of them has a way -// of not being there -- which is why each `Absent` below becomes a typed reason rather than a -// default. The forge computes the test-merge asynchronously, a commit can have no parents, and a -// head can carry no run at all; a version of this that filled any of those in would be answering -// from ignorance in exactly the direction that reads as fresh. +// THE LIVE OBSERVATION FOR ONE PULL REQUEST. Four upstream facts, each with a way of not being +// there -- which is why each `Absent` below becomes a typed reason rather than a default. The +// forge computes the test-merge asynchronously, a commit can have no parents, and a head can carry +// no run at all; filling any of those in would answer from ignorance in exactly the direction that +// reads as fresh. // WHY THE FOUR ABSENCES BELOW CANNOT BE FIXTURED, AND WHAT THAT MEANS FOR READING THEM. // // `MergeRefRecomputed` is produced by `classify_base_freshness` from a provenance a CALLER HANDS @@ -248,8 +242,8 @@ fn pull_request_run_started_at(owner: String, repo: String, head_sha: CommitSha) // // SO A READER COUNTING FIVE LABELLED REASONS SHOULD NOT READ FIVE EQUALLY GUARDED PATHS. This is a // property of where each reason is produced, not a coverage tally: a tally recorded here would go -// stale without this file changing, and the tree answers that question directly. Building the mock -// is what moves the four; nothing in this module can. +// stale without this file changing, and the tree answers that question directly. Building the +// mock is what moves the four; nothing in this module can. fn observe_pull_request_base_freshness( owner: String, repo: String, @@ -306,18 +300,18 @@ fn observe_pull_request_base_freshness( } } -// THE ENTRY. The operator's question at merge time is a yes/no — is this pull request's green about -// the tree it would merge into — so the exit code answers exactly that, and everything that is not -// a confirmed yes is a failure carrying its reason. `BaseUnknown` FAILS here rather than passing: -// the entry's contract is "this evidence is current", and an unvouchable run does not establish it. -// That is the whole asymmetry this module exists to preserve, expressed one last time at the point -// where a human acts on it. +// THE ENTRY. The operator's question at merge time is a yes/no — is this pull request's green +// about the tree it would merge into — so the exit code answers exactly that, and everything that +// is not a confirmed yes is a failure carrying its reason. `BaseUnknown` FAILS here rather than +// passing: the entry's contract is "this evidence is current", and an unvouchable run does not +// establish it. That is the asymmetry this module exists to preserve, expressed where a human +// acts on it. // // THE MERGE TARGET IS AN ARGUMENT, NOT AN AMBIENT READ, and it is echoed in every failure reason. -// The target moved DURING the 48-pull-request measurement that motivated this module, so a verdict -// that did not name the commit it was computed against would be unreadable minutes later — the same -// head-bound discipline the required floor's own refusal states when it says a count is bound to -// the head it names and must never be cited bare. +// The target moved DURING the 48-pull-request measurement that motivated this module, so a +// verdict that did not name the commit it was computed against would be unreadable minutes later +// — the same head-bound discipline the required floor's own refusal states when it says a count +// is bound to the head it names and must never be cited bare. // // Invoked as the explicit actuator DESIGN names: // gunbc run --source-root dag --source-root src/v2 \ diff --git a/dag/gunbc/proactive_verification_ledger.dag b/dag/gunbc/proactive_verification_ledger.dag index d739f1e5751..841ec30e6b8 100644 --- a/dag/gunbc/proactive_verification_ledger.dag +++ b/dag/gunbc/proactive_verification_ledger.dag @@ -7,7 +7,19 @@ import v2.std.algebra { any, filter, fold_list, length } import v2.std.node { Symbol } import std.dissolution { DissolutionCondition, unbound_dissolution } -data proactive_verification_ledger_law: String = "The PROACTIVE VERIFICATION LEDGER (operator no-soak ruling 2026-07-20; gunbc.roadmap_authority v1-verification-ledger, gating v1-interpreter-delete through the cutover chain): enumerated executed receipts — never calendar soak, never elapsed-time claims. Each row is ONE deliberate run with a typed execution shape, a disposition (ExecutableNow | KnownRedPendingUnquarantine | BlockedOnCiFloorCutover), and an execution outcome (OutcomeGreen | OutcomePending | OutcomeRed) recorded only after a real run — never fabricated. Spine rows carry hand-declared disposition (BlockedOnCiFloorCutover until cutover). Family disposition is DERIVED by projecting gunbc.ci_layer_roots known_red_probe_roster (derived from the gunbc.explicit_witness_admission rows) — never hand-declared (§3 single authority). Spine: (1) whole-corpus cold v1-vs-v2 witness parity — falsifier predict-only cadence, zero unexplained divergence; (2) compile-clean parity; (3) regen behavioral fixed point. Families: nine #6879 partition families as equals_eval receipts (meet_join contributes meet + join peer primaries — review 43336). Dissolve-on: ci_floor_cutover + ledger all OutcomeGreen." +// The PROACTIVE VERIFICATION LEDGER (operator no-soak ruling 2026-07-20; gunbc.roadmap_authority +// v1-verification-ledger, gating v1-interpreter-delete through the cutover chain): enumerated +// executed receipts — never calendar soak, never elapsed-time claims. Each row is ONE deliberate +// run with a typed execution shape, a disposition (ExecutableNow | KnownRedPendingUnquarantine | +// BlockedOnCiFloorCutover), and an execution outcome (OutcomeGreen | OutcomePending | OutcomeRed) +// recorded only after a real run — never fabricated. Spine rows carry hand-declared disposition +// (BlockedOnCiFloorCutover until cutover). Family disposition is DERIVED by projecting +// gunbc.ci_layer_roots known_red_probe_roster (derived from the gunbc.explicit_witness_admission +// rows) — never hand-declared (§3 single authority). Spine: (1) whole-corpus cold v1-vs-v2 witness +// parity — falsifier predict-only cadence, zero unexplained divergence; (2) compile-clean parity; +// (3) regen behavioral fixed point. Families: nine #6879 partition families as equals_eval receipts +// (meet_join contributes meet + join peer primaries — review 43336). Dissolve-on: ci_floor_cutover +// + ledger all OutcomeGreen. type VerificationReceiptKind = WholeCorpusWitnessParityCold @@ -46,11 +58,19 @@ type FamilyVerificationLedgerRow { dissolution: DissolutionCondition } -data spine_compile_clean_recipe: String = "claim_executor batch-1 dag_compile_clean_gate (--target dag whole-tree). THE AUTHORITY THIS RECIPE NAMED IS DELETED: it read 'same authority as gunbc_ci_floor_plan', which the 2026-08-15 floor cut removed, and the recipe is retained as the historical spine rather than as a runnable command -- DESIGN records that compile-clean has no reachable entry point at all today, so no repointing would make this executable." +// claim_executor batch-1 dag_compile_clean_gate (--target dag whole-tree). THE AUTHORITY THIS +// RECIPE NAMED IS DELETED: it read 'same authority as gunbc_ci_floor_plan', which the 2026-08-15 +// floor cut removed, and the recipe is retained as the historical spine rather than as a runnable +// command -- DESIGN records that compile-clean has no reachable entry point at all today, so no +// repointing would make this executable. data spine_regen_recipe: String = "claim_executor --required-regen then --required-regen-fixed-point (RequiredRegenGate transport; first_generation_equal and fixed_point_equal are distinct invocations)" -data family_partition_note: String = "Nine witness-realization families (#6879 kernel partition): add (serial kernel), match+loop+fold_closure (lane 3), field_access+complement (lane 2), meet_join+variant_construct+classical_not_ingested (lane 4). Emitter clean across all nine — eval/harness gaps closed by #6912 and #6917. Family KnownRed disposition = exact (entry, function) projection of known_red_probe_roster; un-quarantine deletes the probe row." +// Nine witness-realization families (#6879 kernel partition): add (serial kernel), +// match+loop+fold_closure (lane 3), field_access+complement (lane 2), +// meet_join+variant_construct+classical_not_ingested (lane 4). Emitter clean across all nine — +// eval/harness gaps closed by #6912 and #6917. Family KnownRed disposition = exact (entry, +// function) projection of known_red_probe_roster; un-quarantine deletes the probe row. data proactive_verification_spine_rows: List = [ SpineVerificationLedgerRow { diff --git a/dag/gunbc/product/compute_board/admission.dag b/dag/gunbc/product/compute_board/admission.dag index 8189ea1c753..2bb6f7f712d 100644 --- a/dag/gunbc/product/compute_board/admission.dag +++ b/dag/gunbc/product/compute_board/admission.dag @@ -44,7 +44,25 @@ import product.compute_board.article { SignalReferenceConnection, } -data admission_note: String = "WHOLE-ARTICLE ADMISSION, BEFORE ANY PROJECTION READS A FIELD. Every projection in this product was checking the facts it personally needed and no more, which meant the ARTICLE's own coherence was nobody's: duplicate component identities, a net member naming a terminal no component owns, a bond attached to a nonexistent node, an output nothing drives, two equations driving one output — each is well-formed to every individual projection and each makes the article describe a board that cannot exist. THE SPLIT IS DELIBERATE AND IS NOT A DUPLICATED CHECK: what lives here is article-level structure — identity uniqueness and referential integrity across the analog, bonding and digital halves. What stays in a projection is target-specific: which terminals need a node (SPICE), which spellings a target's lexer admits (Verilog). A digital-only target has no opinion about whether a capacitor terminal sits on a net, so that fact is not an article-level admission. THE LINE WAS DRAWN WRONG ONCE AND THE CORRECTION IS INSTRUCTIVE: the Verilog projection also checked that every driven signal is a declared output and that no equation references an undeclared one. Those are article-level, they are refused here, and keeping them there was one concept under two names in two modules — plus, once the projection took the sealed wrapper, arms that no production call could reach. They were deleted rather than left as a second opinion. REFUSALS ARE COLLECTED, NOT FIRST-WINS. A first-refusal fold reports one cause per edit cycle, which turns a coherent set of related defects into a queue discovered one at a time; and it makes the reported cause depend on authoring order, which is the same list-order accident this article has now closed three times." +// WHOLE-ARTICLE ADMISSION, BEFORE ANY PROJECTION READS A FIELD. Every projection in this product +// was checking the facts it personally needed and no more, which meant the ARTICLE's own coherence +// was nobody's: duplicate component identities, a net member naming a terminal no component owns, a +// bond attached to a nonexistent node, an output nothing drives, two equations driving one output — +// each is well-formed to every individual projection and each makes the article describe a board +// that cannot exist. THE SPLIT IS DELIBERATE AND IS NOT A DUPLICATED CHECK: what lives here is +// article-level structure — identity uniqueness and referential integrity across the analog, +// bonding and digital halves. What stays in a projection is target-specific: which terminals need a +// node (SPICE), which spellings a target's lexer admits (Verilog). A digital-only target has no +// opinion about whether a capacitor terminal sits on a net, so that fact is not an article-level +// admission. THE LINE WAS DRAWN WRONG ONCE AND THE CORRECTION IS INSTRUCTIVE: the Verilog +// projection also checked that every driven signal is a declared output and that no equation +// references an undeclared one. Those are article-level, they are refused here, and keeping them +// there was one concept under two names in two modules — plus, once the projection took the sealed +// wrapper, arms that no production call could reach. They were deleted rather than left as a second +// opinion. REFUSALS ARE COLLECTED, NOT FIRST-WINS. A first-refusal fold reports one cause per edit +// cycle, which turns a coherent set of related defects into a queue discovered one at a time; and +// it makes the reported cause depend on authoring order, which is the same list-order accident this +// article has now closed three times. type BoardArticleRefusal = DuplicateComponentIdentity { component: NonEmptyStr } diff --git a/dag/gunbc/product/compute_board/analog.dag b/dag/gunbc/product/compute_board/analog.dag index d966b506370..2d90a40bc56 100644 --- a/dag/gunbc/product/compute_board/analog.dag +++ b/dag/gunbc/product/compute_board/analog.dag @@ -14,7 +14,19 @@ import product.electrical.connectivity { } import std.measure { Measure, Resistance, Capacitance, Volt, Kilo, Nano } -data analog_note: String = "THE ANALOG CIRCUIT INTENT, LANGUAGE-NEUTRAL. This replaces a BoardSlice that the board article used to hold directly — a type imported from the SPICE projection, carrying SPICE NodeRefs, substrate Symbols and value_lexeme strings. The dependency ran article to projection, which is backwards: the supposed authority was shaped by one of its targets, so 'the article feeds every interface' was true only in the sense that one interface had already decided what the article looked like. WHAT CHANGED, CONCRETELY: component values are typed magnitudes on std.measure's existing Resistance, Capacitance and ElectricPotentialDifference quantities — which were already in the corpus, so this needed no new authority and was never blocked on one. '10k' and '100n' are gone from the model; the SPICE projection now derives those spellings, including the scale suffix, because a suffix is target syntax and belongs where the target lives. TERMINALS AND NETS ARE IDENTITIES, not strings: a net references terminals by component and terminal identity, so 'is this terminal on that net' is a decidable join rather than a spelling coincidence between two prose fields." +// THE ANALOG CIRCUIT INTENT, LANGUAGE-NEUTRAL. This replaces a BoardSlice that the board article +// used to hold directly — a type imported from the SPICE projection, carrying SPICE NodeRefs, +// substrate Symbols and value_lexeme strings. The dependency ran article to projection, which is +// backwards: the supposed authority was shaped by one of its targets, so 'the article feeds every +// interface' was true only in the sense that one interface had already decided what the article +// looked like. WHAT CHANGED, CONCRETELY: component values are typed magnitudes on std.measure's +// existing Resistance, Capacitance and ElectricPotentialDifference quantities — which were already +// in the corpus, so this needed no new authority and was never blocked on one. '10k' and '100n' are +// gone from the model; the SPICE projection now derives those spellings, including the scale +// suffix, because a suffix is target syntax and belongs where the target lives. TERMINALS AND NETS +// ARE IDENTITIES, not strings: a net references terminals by component and terminal identity, so +// 'is this terminal on that net' is a decidable join rather than a spelling coincidence between two +// prose fields. // A COMPONENT'S KIND AND ITS VALUE ARE ONE FACT, NOT TWO. An earlier revision carried an // AnalogComponentRole beside an AnalogComponentValue, which made a resistor rated in nanofarads diff --git a/dag/gunbc/product/compute_board/article.dag b/dag/gunbc/product/compute_board/article.dag index 99aff904954..98a54829f02 100644 --- a/dag/gunbc/product/compute_board/article.dag +++ b/dag/gunbc/product/compute_board/article.dag @@ -44,7 +44,16 @@ import std.measure { Measure } // so a projection admitted against this board cannot be replayed against another whose labels agree. data board_power_interlock_design: ElectricalDesignIdentity = "compute_board.power_interlock" as ElectricalDesignIdentity -data article_note: String = "ONE ARTICLE IDENTITY FEEDING EVERY INTERFACE. The analog slice, the digital control, the bonding topology, the platform-firmware contract and the physical and manufacturing standings are fields of ONE root, so a projection cannot quietly acquire its own private copy of the board. That is the property the whole design turns on and it is the one that decays silently: two projections each holding their own fixture agree until someone edits one of them, and nothing reports the divergence because there was never a shared referent to disagree with. THE ARTICLE IS DELIBERATELY SMALL — a two-rail power-good and reset interlock — and is a real future motherboard subassembly rather than a demonstration. It does not claim to implement the Altra power sequence; it crosses every interface that sequence will need, which is a different and more useful thing to have finished." +// ONE ARTICLE IDENTITY FEEDING EVERY INTERFACE. The analog slice, the digital control, the bonding +// topology, the platform-firmware contract and the physical and manufacturing standings are fields +// of ONE root, so a projection cannot quietly acquire its own private copy of the board. That is +// the property the whole design turns on and it is the one that decays silently: two projections +// each holding their own fixture agree until someone edits one of them, and nothing reports the +// divergence because there was never a shared referent to disagree with. THE ARTICLE IS +// DELIBERATELY SMALL — a two-rail power-good and reset interlock — and is a real future motherboard +// subassembly rather than a demonstration. It does not claim to implement the Altra power sequence; +// it crosses every interface that sequence will need, which is a different and more useful thing to +// have finished. // ELECTRICAL REFERENCES ARE NOT ONE THING, and fusing them into a single `Ground` is the // modelling error this type exists to prevent. A power return, a signal reference, a chassis diff --git a/dag/gunbc/product/compute_board/composition.dag b/dag/gunbc/product/compute_board/composition.dag index b5b0f0528f9..cfb1ad175df 100644 --- a/dag/gunbc/product/compute_board/composition.dag +++ b/dag/gunbc/product/compute_board/composition.dag @@ -33,7 +33,16 @@ import product.compute_board.intent { requirement_capability, } -data composition_note: String = "THE EARNED-ELEMENT FOLD: given a contract and a set of selected components, which required capabilities are unsatisfied, and which components are unearned. THE ASYMMETRY IS THE POINT AND IT IS EASY TO GET WRONG: an unsatisfied capability is a hole in the design, and an unearned component is a part nobody can justify — they are not two readings of one number, and a board that satisfies every capability can still carry parts that discharge nothing. A COMPONENT MAY DISCHARGE MORE THAN ONE OBLIGATION, and may also be earned by another component's imposed constraint rather than by a contract capability directly: a decoupling network is required by the device it serves, not by 'serve web workloads'. That second route is modelled as ImposedByComponent so the derivation stays a real chain back to the contract, rather than becoming an escape hatch that launders any part as necessary." +// THE EARNED-ELEMENT FOLD: given a contract and a set of selected components, which required +// capabilities are unsatisfied, and which components are unearned. THE ASYMMETRY IS THE POINT AND +// IT IS EASY TO GET WRONG: an unsatisfied capability is a hole in the design, and an unearned +// component is a part nobody can justify — they are not two readings of one number, and a board +// that satisfies every capability can still carry parts that discharge nothing. A COMPONENT MAY +// DISCHARGE MORE THAN ONE OBLIGATION, and may also be earned by another component's imposed +// constraint rather than by a contract capability directly: a decoupling network is required by the +// device it serves, not by 'serve web workloads'. That second route is modelled as +// ImposedByComponent so the derivation stays a real chain back to the contract, rather than +// becoming an escape hatch that launders any part as necessary. // Why a component is in the design. There are exactly two legitimate answers and no third: // the contract asked for the capability, or a component we already selected requires it. A diff --git a/dag/gunbc/product/compute_board/contact_population.dag b/dag/gunbc/product/compute_board/contact_population.dag index 159cc67a2bd..cddcf5faad0 100644 --- a/dag/gunbc/product/compute_board/contact_population.dag +++ b/dag/gunbc/product/compute_board/contact_population.dag @@ -4,7 +4,16 @@ import std.types { String, Bool, NonEmptyStr, List } import std.integer { Int } import std.nat { Nat } -data contact_population_note: String = "FOUR POPULATIONS THAT ARE NOT ONE POPULATION, and the count was already being compared across them. An earlier revision asserted bank_population_total == roster_pin_count and called it two independent authorities agreeing — 92 general-purpose contacts plus 4 configuration contacts against Project IceStorm's 96 placeable positions. Both sides really are 96 and the comparison really did execute, which is what makes it the dangerous kind of evidence: nothing anywhere stated that those two totals count the same THINGS. Lattice's current product table describes the same device as 98 total I/O plus 2 dedicated I/O, which is 100 by a third inclusion rule, so at least one of the three is counting something the others are not. A bare Int == Int cannot notice any of that; it is the cross-family comparison this repository already closed for content hashes, reappearing wherever two numbers happen to share a type." +// FOUR POPULATIONS THAT ARE NOT ONE POPULATION, and the count was already being compared across +// them. An earlier revision asserted bank_population_total == roster_pin_count and called it two +// independent authorities agreeing — 92 general-purpose contacts plus 4 configuration contacts +// against Project IceStorm's 96 placeable positions. Both sides really are 96 and the comparison +// really did execute, which is what makes it the dangerous kind of evidence: nothing anywhere +// stated that those two totals count the same THINGS. Lattice's current product table describes the +// same device as 98 total I/O plus 2 dedicated I/O, which is 100 by a third inclusion rule, so at +// least one of the three is counting something the others are not. A bare Int == Int cannot notice +// any of that; it is the cross-family comparison this repository already closed for content hashes, +// reappearing wherever two numbers happen to share a type. // A COUNT IS ONLY MEANINGFUL WITH ITS INCLUSION RULE. These are separate kinds rather than one // labelled number because the whole failure mode is that two counts compared cleanly while diff --git a/dag/gunbc/product/compute_board/device_binding.dag b/dag/gunbc/product/compute_board/device_binding.dag index 2d109cf8111..0fc3f320c89 100644 --- a/dag/gunbc/product/compute_board/device_binding.dag +++ b/dag/gunbc/product/compute_board/device_binding.dag @@ -60,7 +60,20 @@ import v2.extdeps.formats.icestorm_pcf { pcf_emit, } -data device_binding_note: String = "THE PIN CHAIN, AND THE COPIED STRING IT EXISTS TO MAKE IMPOSSIBLE. A signal reaches a package pin through four separate facts, each owned somewhere else: the article declares a logical signal, the Verilog binding table says how this target spells it, this module says which package pin it lands on, and the package roster says whether that pin exists. The tempting shape is a constraint file authored beside the Verilog with the port name typed in again — and it works, right up until one of the two spellings changes and the design is placed on the wrong pin with every artifact still looking correct. So the constraint's port name is READ FROM the Verilog binding rather than authored here: there is no field in this module that could disagree with it. WHAT IS REFUSED, and each is a defect that produces a working-looking artifact rather than an error: a pin no package roster admits, because nextpnr would reject it late or place it somewhere else; two signals on one pin, which is a short at the package boundary; one signal on two pins, which is ambiguous and would be resolved by list order; a bound signal the article never declared; and — the one an author is most likely to miss — an article signal with NO pin, because the emitted constraint file would simply not mention it and the tool would place it wherever it liked." +// THE PIN CHAIN, AND THE COPIED STRING IT EXISTS TO MAKE IMPOSSIBLE. A signal reaches a package pin +// through four separate facts, each owned somewhere else: the article declares a logical signal, +// the Verilog binding table says how this target spells it, this module says which package pin it +// lands on, and the package roster says whether that pin exists. The tempting shape is a constraint +// file authored beside the Verilog with the port name typed in again — and it works, right up until +// one of the two spellings changes and the design is placed on the wrong pin with every artifact +// still looking correct. So the constraint's port name is READ FROM the Verilog binding rather than +// authored here: there is no field in this module that could disagree with it. WHAT IS REFUSED, and +// each is a defect that produces a working-looking artifact rather than an error: a pin no package +// roster admits, because nextpnr would reject it late or place it somewhere else; two signals on +// one pin, which is a short at the package boundary; one signal on two pins, which is ambiguous and +// would be resolved by list order; a bound signal the article never declared; and — the one an +// author is most likely to miss — an article signal with NO pin, because the emitted constraint +// file would simply not mention it and the tool would place it wherever it liked. // A logical signal to a package position. The pin is the package's own designator, carried as // authored: whether it exists is the roster's answer, not this type's. diff --git a/dag/gunbc/product/compute_board/digital_control.dag b/dag/gunbc/product/compute_board/digital_control.dag index 5d9263b946e..f4912e67762 100644 --- a/dag/gunbc/product/compute_board/digital_control.dag +++ b/dag/gunbc/product/compute_board/digital_control.dag @@ -2,7 +2,20 @@ module product.compute_board.digital_control import std.types { String, NonEmptyStr, List } -data digital_control_note: String = "THE BOARD'S DIGITAL CONTROL INTENT, LANGUAGE-NEUTRAL BY CONSTRUCTION. This is what a board-management interlock DOES, expressed as signals and equations; it names no target language and carries no target syntax. There is no body_lexeme here and no place to put one: an equation is a tree over signal references and boolean operators, so 'assign power_permit = conditioned_good & ~fault;' cannot be authored at this layer — it can only be DERIVED by a projection that owns Verilog spelling. THE REASON THIS MATTERS more than it looks: the moment a target string can be written in the intent, the intent stops being the authority and becomes a container for pre-rendered output, and every other projection of the same board silently disagrees with it. SIGNAL IDENTITY IS A BRANDED NAME, not a substrate Symbol, and that is a deliberate departure from how v2.extdeps.formats.spice does it. That module carries a hand-written Symbol-to-spelling table covering exactly r1/c1/v1/n1/n2, because the substrate offers no general projection from a Symbol to its text — so anything it emits is bounded by a five-entry lookup nobody can extend without editing the language module. A branded identifier carries its own spelling, is validated at construction, and needs no such table." +// THE BOARD'S DIGITAL CONTROL INTENT, LANGUAGE-NEUTRAL BY CONSTRUCTION. This is what a +// board-management interlock DOES, expressed as signals and equations; it names no target language +// and carries no target syntax. There is no body_lexeme here and no place to put one: an equation +// is a tree over signal references and boolean operators, so 'assign power_permit = +// conditioned_good & ~fault;' cannot be authored at this layer — it can only be DERIVED by a +// projection that owns Verilog spelling. THE REASON THIS MATTERS more than it looks: the moment a +// target string can be written in the intent, the intent stops being the authority and becomes a +// container for pre-rendered output, and every other projection of the same board silently +// disagrees with it. SIGNAL IDENTITY IS A BRANDED NAME, not a substrate Symbol, and that is a +// deliberate departure from how v2.extdeps.formats.spice does it. That module carries a +// hand-written Symbol-to-spelling table covering exactly r1/c1/v1/n1/n2, because the substrate +// offers no general projection from a Symbol to its text — so anything it emits is bounded by a +// five-entry lookup nobody can extend without editing the language module. A branded identifier +// carries its own spelling, is validated at construction, and needs no such table. // A LOGICAL signal identity. It is a nominal brand and nothing more, and an earlier revision of // this comment claimed otherwise — it said validity was checked at construction so an diff --git a/dag/gunbc/product/compute_board/emission_entry.dag b/dag/gunbc/product/compute_board/emission_entry.dag index 1b577c91c5f..8ecc6fbf0dd 100644 --- a/dag/gunbc/product/compute_board/emission_entry.dag +++ b/dag/gunbc/product/compute_board/emission_entry.dag @@ -33,7 +33,19 @@ import product.compute_board.verilog_projection { power_interlock_verilog_bindings, } -data emission_entry_note: String = "THE ENTRY POINTS THAT ACTUALLY PRODUCE THE ARTIFACTS, AND THE REFUSAL THEY USED TO SWALLOW. Everything else in this product returns a typed projection that a witness inspects; these are the host boundary, where bytes leave. An earlier revision returned a bare String and answered a refusal with the literal 'REFUSED: article not admitted' — which is the fabricated plausible output DESIGN section 5 forbids, at the one seam where a caller is furthest from the model and least able to tell. It is also the identical defect this same lane had just fixed one layer down, where a String-returning ngspice entry bypassed the projection gate for exactly the same reason: a String has nowhere to put a refusal, so a refusal becomes prose that reads like a result. THE SHAPE: BoardEmission is parameterized by the target's own refusal type rather than fusing SPICE and Verilog causes into one coproduct, so adding a third target is an instantiation and not a widened enum. Admission refusal and projection refusal stay separate arms because they have different remedies — one means the board is incoherent, the other means this target cannot render a coherent board." +// THE ENTRY POINTS THAT ACTUALLY PRODUCE THE ARTIFACTS, AND THE REFUSAL THEY USED TO SWALLOW. +// Everything else in this product returns a typed projection that a witness inspects; these are the +// host boundary, where bytes leave. An earlier revision returned a bare String and answered a +// refusal with the literal 'REFUSED: article not admitted' — which is the fabricated plausible +// output DESIGN section 5 forbids, at the one seam where a caller is furthest from the model and +// least able to tell. It is also the identical defect this same lane had just fixed one layer down, +// where a String-returning ngspice entry bypassed the projection gate for exactly the same reason: +// a String has nowhere to put a refusal, so a refusal becomes prose that reads like a result. THE +// SHAPE: BoardEmission is parameterized by the target's own refusal type rather than fusing SPICE +// and Verilog causes into one coproduct, so adding a third target is an instantiation and not a +// widened enum. Admission refusal and projection refusal stay separate arms because they have +// different remedies — one means the board is incoherent, the other means this target cannot render +// a coherent board. // EACH ENTRY TAKES THE ARTICLE, and the production one is a wrapper over it. That is not // ceremony: entries hardwired to the production article have refusal arms no call can reach, so diff --git a/dag/gunbc/product/compute_board/intent.dag b/dag/gunbc/product/compute_board/intent.dag index e711e2f9ca0..88ac9ebfd8a 100644 --- a/dag/gunbc/product/compute_board/intent.dag +++ b/dag/gunbc/product/compute_board/intent.dag @@ -4,14 +4,26 @@ import std.types { String, NonEmptyStr, List } import std.nat { Nat } import std.measure { ByteSize, HardwareThreadCount, byte_size, hardware_thread_count } -data compute_board_intent_note: String = "THE MACHINE CONTRACT, WHICH IS WHERE A BOARD DESIGN STARTS INSTEAD OF A FEATURE CHECKLIST. Every element of a resolved board must trace to a capability required here or to a constraint some selected component imposes; an element that traces to neither is unearned and must not be in the design. THE TWO STRUCTURAL CHECKS THIS BUYS, and the reason the contract is modelled rather than written in prose: removing an element must identify the obligation that becomes unsatisfied, and an element whose removal breaks no obligation is detectable rather than debatable. WHAT THIS TYPE DELIBERATELY DOES NOT DECIDE: it names capabilities, never realizations. 'Needs one boot-storage path' is a requirement; 'has an M.2 connector' is a choice made downstream against authorities, and fusing the two here would smuggle a component selection into the contract that is supposed to justify it. FEATURE-MINIMAL IS NOT COST-MINIMAL, so this contract carries no instruction to minimize part count: deleting a small support device can force more layers, harder routing, more firmware, or a worse process, and the quantity that actually matters is total realized cost. Nothing here optimizes anything; it only fixes what must be true." +// THE MACHINE CONTRACT, WHICH IS WHERE A BOARD DESIGN STARTS INSTEAD OF A FEATURE CHECKLIST. Every +// element of a resolved board must trace to a capability required here or to a constraint a +// selected component imposes; an element tracing to neither is unearned and must not be in the +// design. THE TWO STRUCTURAL CHECKS THIS BUYS, and why the contract is modelled rather than prose: +// removing an element must identify the obligation that becomes unsatisfied, and an element whose +// removal breaks no obligation is detectable rather than debatable. WHAT THIS TYPE DELIBERATELY +// DOES NOT DECIDE: it names capabilities, never realizations. 'Needs one boot-storage path' is a +// requirement; 'has an M.2 connector' is a choice made downstream against authorities, and fusing +// them here would smuggle a component selection into the contract meant to justify it. +// FEATURE-MINIMAL IS NOT COST-MINIMAL, so this contract carries no instruction to minimize part +// count: deleting a small support device can force more layers, harder routing, more firmware, or a +// worse process, and the quantity that matters is total realized cost. Nothing here optimizes; it +// only fixes what must be true. -// The capability axis of a compute node, closed deliberately. This is OUR product contract, -// not an upstream taxonomy, so a closed enum is the right shape: a new capability is a -// considered widening of what we build, not an open extension point. Expansion estates -// (general-purpose slots, SATA/RAID, audio, display, broad USB, legacy I/O) are absent from -// the axis rather than present and disabled — an omitted feature must be unrepresentable, not -// carried as `enabled: false`, or the type keeps describing a machine we are not building. +// The capability axis of a compute node, closed deliberately. This is OUR product contract, not an +// upstream taxonomy, so a closed enum is right: a new capability is a considered widening of what +// we build, not an open extension point. Expansion estates (general-purpose slots, SATA/RAID, +// audio, display, broad USB, legacy I/O) are absent from the axis rather than present and disabled +// — an omitted feature must be unrepresentable, not carried as `enabled: false`, or the type keeps +// describing a machine we are not building. type BoardCapability = Compute | MemorySubsystem @@ -54,10 +66,9 @@ fn requirement_capability(r: CapabilityRequirement) -> BoardCapability { } // Equality via ONE ordinal projection rather than a ten-by-ten nested match. The nested form -// carried a wildcard arm per row, which meant a new capability compared unequal to everything -// including itself in nine of them and nobody would hear about it. Here a new variant is a -// typecheck failure in exactly one place, and equality is derived from the ordinal rather than -// being restated per pair. +// carried a wildcard arm per row, so a new capability compared unequal to everything including +// itself in nine of them, silently. Here a new variant is a typecheck failure in exactly one place, +// and equality is derived from the ordinal rather than restated per pair. fn capability_ordinal(c: BoardCapability) -> Int { match c { Compute => 0 diff --git a/dag/gunbc/product/compute_board/power_distribution.dag b/dag/gunbc/product/compute_board/power_distribution.dag index c0cc9f68a5c..b36ecc665fe 100644 --- a/dag/gunbc/product/compute_board/power_distribution.dag +++ b/dag/gunbc/product/compute_board/power_distribution.dag @@ -39,7 +39,17 @@ import extdeps.programmable_logic.ice40.supply { supply_rails_equal, } -data power_distribution_note: String = "WHAT THE BOARD SUPPLIES, CHECKED AGAINST WHAT THE DEVICE REQUIRES. The device's requirements are cited facts and live in extdeps; what a particular board chooses to build is a product decision and lives here. The check between them is the whole content of this module, and it is deliberately a REFUSAL rather than a report: a rail outside its window is not a warning, it is a board that does not work. THE THREE REFUSALS THAT ARE NOT ABOUT A SINGLE NUMBER, and they are the reason this is not a loop over min/max pairs. A rail may sit inside its own window and still be wrong because a bank driving LVCMOS 3.3 needs a tighter window than the bank rail's own envelope; because two rails at one voltage must be one supply and the board built two; or because a pin the vendor says to leave unconnected was connected to something perfectly reasonable. A design satisfying every window and violating any of these is a design the vendor does not support, so the windows alone are not the test." +// WHAT THE BOARD SUPPLIES, CHECKED AGAINST WHAT THE DEVICE REQUIRES. The device's requirements are +// cited facts and live in extdeps; what a particular board chooses to build is a product decision +// and lives here. The check between them is the whole content of this module, and it is +// deliberately a REFUSAL rather than a report: a rail outside its window is not a warning, it is a +// board that does not work. THE THREE REFUSALS THAT ARE NOT ABOUT A SINGLE NUMBER, and they are the +// reason this is not a loop over min/max pairs. A rail may sit inside its own window and still be +// wrong because a bank driving LVCMOS 3.3 needs a tighter window than the bank rail's own envelope; +// because two rails at one voltage must be one supply and the board built two; or because a pin the +// vendor says to leave unconnected was connected to something perfectly reasonable. A design +// satisfying every window and violating any of these is a design the vendor does not support, so +// the windows alone are not the test. // WHAT THE BOARD ACTUALLY BUILDS. A supply is a net at a voltage, and both halves matter: the // voltage decides whether the device is in spec, and the NET IDENTITY decides whether two rails diff --git a/dag/gunbc/product/compute_board/simulation_receipt.dag b/dag/gunbc/product/compute_board/simulation_receipt.dag index 8ab0400e9f9..5ccb24bc9f2 100644 --- a/dag/gunbc/product/compute_board/simulation_receipt.dag +++ b/dag/gunbc/product/compute_board/simulation_receipt.dag @@ -3,7 +3,18 @@ module product.compute_board.simulation_receipt import std.types { String, NonEmptyStr, List } import std.decl_ref { DeclarationRef, WholeDeclaration } -data simulation_receipt_note: String = "EXECUTED RECEIPTS, AND EXACTLY WHAT THEY DO AND DO NOT CLAIM. Every prior claim about these two emissions was that the bytes matched a golden authored in the same repository — which establishes that the projection is stable, and nothing whatever about whether a real tool accepts them. These rows record that the emitted artifacts were run: Icarus Verilog compiled the module and simulated its four-row truth table, and ngspice solved the operating point of the deck. WHAT THIS IS NOT: it is not a re-execution. Nothing in an Accepted program here runs a simulator; the observation happened once, at a named tool version, and is recorded. THE JOIN THAT KEEPS IT HONEST is the enrolled witness beside it: it asserts that the artifact recorded in each receipt is byte-identical to what the projection emits TODAY. So the receipt cannot quietly describe an emission the product no longer produces — the moment the projection's bytes move, the receipt is stale and says so by failing, rather than by continuing to vouch for an artifact nobody emits any more. Re-executing on a cadence is the next rung and is not claimed here." +// EXECUTED RECEIPTS, AND EXACTLY WHAT THEY DO AND DO NOT CLAIM. Every prior claim about these two +// emissions was that the bytes matched a golden authored in the same repository — which establishes +// that the projection is stable, and nothing whatever about whether a real tool accepts them. These +// rows record that the emitted artifacts were run: Icarus Verilog compiled the module and simulated +// its four-row truth table, and ngspice solved the operating point of the deck. WHAT THIS IS NOT: +// it is not a re-execution. Nothing in an Accepted program here runs a simulator; the observation +// happened once, at a named tool version, and is recorded. THE JOIN THAT KEEPS IT HONEST is the +// enrolled witness beside it: it asserts that the artifact recorded in each receipt is +// byte-identical to what the projection emits TODAY. So the receipt cannot quietly describe an +// emission the product no longer produces — the moment the projection's bytes move, the receipt is +// stale and says so by failing, rather than by continuing to vouch for an artifact nobody emits any +// more. Re-executing on a cadence is the next rung and is not claimed here. // Which tool, at which observed version. The version is what the tool REPORTED when it ran, not // a version this repository requires — a required version is a policy fact and would belong with diff --git a/dag/gunbc/product/compute_board/spice_projection.dag b/dag/gunbc/product/compute_board/spice_projection.dag index a7e130f046d..ee36238529c 100644 --- a/dag/gunbc/product/compute_board/spice_projection.dag +++ b/dag/gunbc/product/compute_board/spice_projection.dag @@ -66,7 +66,18 @@ import product.compute_board.article { BoardPowerInterfaceArticle, } -data spice_projection_note: String = "SPICE AS A PROJECTION OF A LANGUAGE-NEUTRAL ANALOG INTENT. Every piece of SPICE spelling for this article lives in this module and nowhere upstream: the element letter, the node names, the reference node '0', and the magnitude SUFFIXES. That last one is the correction that mattered — an earlier revision carried value_lexeme: '10k' in the model, which is a SPICE spelling of a resistance, so the supposed authority was holding target syntax. The magnitude is now a typed measure and 'k' is derived here from the variant's declared scale. THE PRIOR SHAPE WAS WORSE THAN A STRAY STRING: the board article held a BoardSlice imported FROM this module, so the article depended on one of its own targets and 'the article feeds every interface' was true only because that interface had already decided what the article looked like. WHAT THIS MODULE REFUSES BEFORE EMITTING A BYTE: an element with no model binding at all, an element whose vendor model this profile cannot render, and a TERMINAL ON NO NET. That last refusal is not pedantry — a floating terminal and a grounded one are different circuits, and defaulting the first to the second emits a deck that simulates cleanly and describes a different board." +// SPICE AS A PROJECTION OF A LANGUAGE-NEUTRAL ANALOG INTENT. Every piece of SPICE spelling for this +// article lives in this module and nowhere upstream: element letter, node names, reference node +// '0', and the magnitude SUFFIXES. The last is the correction that mattered — an earlier revision +// carried value_lexeme: '10k' in the model, a SPICE spelling of a resistance, so the supposed +// authority held target syntax. The magnitude is now a typed measure and 'k' is derived here from +// the variant's declared scale. THE PRIOR SHAPE WAS WORSE THAN A STRAY STRING: the board article +// held a BoardSlice imported FROM this module, so the article depended on one of its own targets +// and 'the article feeds every interface' held only because that interface had already decided +// what the article looked like. WHAT THIS MODULE REFUSES BEFORE EMITTING A BYTE: an element with no +// model binding, an element whose vendor model this profile cannot render, and a TERMINAL ON NO +// NET — a floating terminal and a grounded one are different circuits, and defaulting the first to +// the second emits a deck that simulates cleanly and describes a different board. type SpiceProjectionRefusal = ElectricalModelUnavailable { part: NonEmptyStr } @@ -78,10 +89,9 @@ type SpiceProjectionRefusal | IdentityNotSpellableInTarget { component: NonEmptyStr } | NetNotSpellableInTarget { net: NonEmptyStr } -// The receipt carries the article identity, so an emission is traceable to the root that -// produced it. Without this, two unrelated articles with equal extracted fields produce -// indistinguishable output and 'one article feeds both projections' is a convention in a -// witness rather than a property of the types. +// The receipt carries the article identity, so an emission is traceable to the root that produced +// it. Otherwise two unrelated articles with equal extracted fields produce indistinguishable output +// and 'one article feeds both projections' is a witness convention, not a property of the types. type SpiceEmissionReceipt { source_article: BoardArticleIdentity source: String @@ -91,16 +101,15 @@ type SpiceProjection = SpiceSliceProjected { receipt: SpiceEmissionReceipt } | SpiceSliceRefused { cause: SpiceProjectionRefusal } -// The element letter and the magnitude suffix now come from ONE match, because the kind and the -// value are one constructor upstream. The earlier pair of matches over two independent fields -// could disagree — a PassiveResistor carrying nanofarads emitted "R... 100n", a deck that -// simulates and describes a part nobody specified. -// THE DECK IS BUILT AS A NETLIST AND SERIALIZED BY THE FORMAT MODULE. Every byte of SPICE -// spelling this module used to author — the element letter, the reference node "0", the space -// between fields, the newline between lines and the "k"/"n" suffixes — was a second SPICE -// authority sitting in the product layer, exactly the fork the Verilog side removed when its -// printer was deleted. What remains here is the only thing that is genuinely a projection fact: -// which target scale each modelled quantity is spelled at. +// The element letter and the magnitude suffix come from ONE match, because kind and value are one +// constructor upstream. The earlier pair of matches over two independent fields could disagree — a +// PassiveResistor carrying nanofarads emitted "R... 100n", a deck that simulates and describes a +// part nobody specified. +// THE DECK IS BUILT AS A NETLIST AND SERIALIZED BY THE FORMAT MODULE. Every byte of SPICE spelling +// this module used to author — element letter, reference node "0", the space between fields, the +// newline between lines, the "k"/"n" suffixes — was a second SPICE authority in the product layer, +// the fork the Verilog side removed when its printer was deleted. What remains is the only genuine +// projection fact: which target scale each modelled quantity is spelled at. fn component_node( intent: AnalogCircuitIntent, reference: NetIdentity, @@ -161,10 +170,9 @@ fn binding_for(bindings: List, c: ElectricalComponentInst } // HOW MANY bindings claim this component. binding_for answers with the FIRST match, so two rows -// disagreeing about one component's model resolved silently by list order — an ideal-lumped row -// and a vendor-subcircuit row for the same part emit or refuse depending on which was authored -// first, which is a verdict decided by an editing accident. Ambiguity is not a model standing -// and is refused rather than resolved. +// disagreeing about one component's model resolved silently by list order — an ideal-lumped row and +// a vendor-subcircuit row for the same part emit or refuse depending on which was authored first, +// a verdict decided by editing accident. Ambiguity is not a model standing; it is refused. fn binding_count(bindings: List, c: ElectricalComponentInstance) -> Nat { fold(bindings, init: 0, f: fn(acc, b) { if component_identity_eq(a: b.component, b: component_identity(c: c)) { acc + 1 } else { acc } @@ -182,13 +190,12 @@ fn standing_refusal(c: ElectricalComponentInstance, s: ElectricalModelStanding) } } -// A terminal sits in exactly one of three states and they have DIFFERENT remedies: on no net -// (floating), on one net (netted), or on several (shorted between them). The earlier check was a -// Bool over "did net_of_terminal answer", which collapsed the third into the second — so a -// terminal shorting two nets emitted a deck naming whichever net was authored first, and the -// deck simulated cleanly while describing a board that is not the one modelled. That is the -// exact failure this module's note claims to prevent for the floating case, left open for the -// shorted one. +// A terminal sits in exactly one of three states with DIFFERENT remedies: on no net (floating), on +// one net (netted), or on several (shorted between them). The earlier check was a Bool over "did +// net_of_terminal answer", collapsing the third into the second — a terminal shorting two nets +// emitted a deck naming whichever net was authored first, simulating cleanly while describing a +// board that is not the one modelled: the exact failure this module's note claims to prevent for +// the floating case, left open for the shorted one. fn terminal_netting_refusal( intent: AnalogCircuitIntent, c: ElectricalComponentInstance, @@ -245,10 +252,10 @@ fn first_refusal( }) } -// A NAME THIS FORMAT CANNOT SPELL IS REFUSED, not repaired. The format module admits an -// identifier or answers Absent, and an Absent name renders as the empty string inside the deck — -// so the refusal has to happen here, before a line is built, or a component silently loses its -// name and the deck describes a different circuit. +// A NAME THIS FORMAT CANNOT SPELL IS REFUSED, not repaired. The format module admits an identifier +// or answers Absent, and an Absent name renders as the empty string inside the deck — so the +// refusal happens here, before a line is built, or a component silently loses its name and the +// deck describes a different circuit. fn spelling_refusal(c: ElectricalComponentInstance) -> SpiceProjectionRefusal? { if spice_identifier_is_admissible(spelling: component_local_label(c: component_identity(c: c)) as String) { none @@ -271,11 +278,10 @@ fn net_spelling_refusal(intent: AnalogCircuitIntent, reference: NetIdentity) -> }) } -// THE RUNNABLE DECK IS A SECOND PROJECTION OF THE SAME NETLIST, not a second netlist. What an -// analysis directive adds is a question to ask the simulator — an operating point, here — and -// that is a fact about a RUN rather than about the board, which is why the receipt's deck does -// not carry one. Both come from spice_netlist, so a run cannot exercise a circuit the receipt -// does not describe. +// THE RUNNABLE DECK IS A SECOND PROJECTION OF THE SAME NETLIST, not a second netlist. An analysis +// directive adds a question for the simulator — an operating point, here — a fact about a RUN, not +// the board, which is why the receipt's deck does not carry one. Both come from spice_netlist, so a +// run cannot exercise a circuit the receipt does not describe. fn first_refusal_or_net_spelling( intent: AnalogCircuitIntent, bindings: List, @@ -305,11 +311,10 @@ fn project_analog_to_spice( } } -// THE PRODUCTION ENTRY POINT, and the reason it takes the whole article rather than its parts. -// Every caller of project_analog_to_spice supplies the identity and the circuit SEPARATELY, so -// one article's identity paired with another article's circuit was perfectly constructible and -// the resulting receipt would name a board it does not describe. Taking the article closes that -// by construction: there is one value, and both facts are read out of it here. +// THE PRODUCTION ENTRY POINT, taking the whole article rather than its parts. Every caller of +// project_analog_to_spice supplies identity and circuit SEPARATELY, so one article's identity paired +// with another's circuit was constructible and the receipt would name a board it does not describe. +// Taking the article closes that by construction: one value, both facts read out of it here. fn project_article_to_spice(admitted: AdmittedBoardPowerInterfaceArticle) -> SpiceProjection { let article = admitted_article(a: admitted) project_analog_to_spice( @@ -320,13 +325,12 @@ fn project_article_to_spice(admitted: AdmittedBoardPowerInterfaceArticle) -> Spi ) } -// IT RETURNS THE SAME COPRODUCT, THROUGH THE SAME GATE, and an earlier revision did not — it -// returned a bare String and built the netlist directly, so an article that admission accepts and -// this module REFUSES could still hand a runnable deck to a simulator. That is the exact fail-open -// the netting refusals exist to close, reopened on the second path: component_node maps a terminal -// on no net to Ground, so the bypassing deck would have simulated cleanly and described a -// different board. There is one refusal gate and both projections are behind it; a String return -// is what made the bypass possible, because a String has nowhere to put a refusal. +// IT RETURNS THE SAME COPRODUCT, THROUGH THE SAME GATE. An earlier revision returned a bare String +// and built the netlist directly, so an article admission accepts and this module REFUSES could +// still hand a runnable deck to a simulator — the fail-open the netting refusals close, reopened on +// the second path: component_node maps a terminal on no net to Ground, so the bypassing deck would +// have simulated cleanly and described a different board. One refusal gate, both projections behind +// it; a String return made the bypass possible because a String has nowhere to put a refusal. fn project_article_to_ngspice_run(admitted: AdmittedBoardPowerInterfaceArticle) -> SpiceProjection { let article = admitted_article(a: admitted) let intent = article.analog diff --git a/dag/gunbc/product/compute_board/verilog_projection.dag b/dag/gunbc/product/compute_board/verilog_projection.dag index adabcbb5778..b4f40bb79e4 100644 --- a/dag/gunbc/product/compute_board/verilog_projection.dag +++ b/dag/gunbc/product/compute_board/verilog_projection.dag @@ -53,7 +53,21 @@ import product.compute_board.article { BoardPowerInterfaceArticle, } -data verilog_projection_note: String = "THE VERILOG PROJECTION, NOW A MAPPING RATHER THAN A PRINTER. This module names no Verilog keyword, no punctuation and no layout: it folds board vocabulary onto the target AST and v2.extdeps.languages.verilog renders it through the generic serializer. An earlier revision of this note said ALL Verilog spelling lived here and explained why routing through the target AST was blocked on a general Symbol-to-text projection. That premise was wrong twice over. No such projection is needed — a target resolves a Symbol through its own binding spellings, which is why VerilogBinding now carries the symbol beside the spelling — and the emitter was never the obstacle: relation rows derive from formal productions, and emission is their backward reading. WHAT THIS MODULE STILL OWNS is the semantics the intent can still get wrong: a driven signal that is not an output, a reference to a signal never declared, a signal with no binding or two, and a spelling the target refuses. Those are facts about the BOARD and its binding table. Identifier admissibility moved to the target, because what may be a Verilog identifier is a fact about Verilog. WHAT IT DOES NOT COVER remains a bound rather than a gap: sequential behaviour has no constructor in DigitalControlIntent, so an always block cannot be requested here and quietly emitted as combinational logic." +// THE VERILOG PROJECTION, NOW A MAPPING RATHER THAN A PRINTER. This module names no Verilog +// keyword, no punctuation and no layout: it folds board vocabulary onto the target AST and +// v2.extdeps.languages.verilog renders it through the generic serializer. An earlier revision of +// this note said ALL Verilog spelling lived here and explained why routing through the target AST +// was blocked on a general Symbol-to-text projection. That premise was wrong twice over. No such +// projection is needed — a target resolves a Symbol through its own binding spellings, which is why +// VerilogBinding now carries the symbol beside the spelling — and the emitter was never the +// obstacle: relation rows derive from formal productions, and emission is their backward reading. +// WHAT THIS MODULE STILL OWNS is the semantics the intent can still get wrong: a driven signal that +// is not an output, a reference to a signal never declared, a signal with no binding or two, and a +// spelling the target refuses. Those are facts about the BOARD and its binding table. Identifier +// admissibility moved to the target, because what may be a Verilog identifier is a fact about +// Verilog. WHAT IT DOES NOT COVER remains a bound rather than a gap: sequential behaviour has no +// constructor in DigitalControlIntent, so an always block cannot be requested here and quietly +// emitted as combinational logic. // VERILOG LEXICAL ADMISSION LIVES HERE, with the target, because legality is a target fact and // not a property of a logical signal. A logical SignalName is a nominal brand that validates @@ -114,14 +128,6 @@ fn spelling_for(bindings: List, name: SignalName) -> String? { }) } - - - - - - - - fn signal_is_declared_output(intent: DigitalControlIntent, name: SignalName) -> Bool { fold(intent.signals, init: false, f: fn(acc, s) { acc || (signal_direction_is_output(s: s) && (s.name as String) == (name as String)) diff --git a/dag/gunbc/product/cooling_qualification.dag b/dag/gunbc/product/cooling_qualification.dag index d7dc07fbb9d..f87d13408b7 100644 --- a/dag/gunbc/product/cooling_qualification.dag +++ b/dag/gunbc/product/cooling_qualification.dag @@ -208,6 +208,14 @@ fn cooling_catalog_verdict_admits_qualification( } } +// CoolingCatalogCompatible is a preflight over cited catalog rows and nothing more. It is never +// evidence that an installed cooler actually holds a processor within its thermal limits: mounting +// quality, thermal-interface application, DIMM/VRM/NIC airflow, sustained workload temperature, +// throttling, acoustics, and room heat rejection are all outside what a purchase record can decide. +// The live BMC thermal/tach readback remains the only source of those facts, and a compatible +// catalog verdict must never be substituted for it — a consumer needs BOTH, and this module +// supplies exactly one. + fn cooling_catalog_refusal_reason(verdict: CoolingCatalogVerdict) -> String { match verdict { CoolingCatalogCompatible { host: _, cooler_asset: _ } => @@ -231,5 +239,3 @@ fn cooling_catalog_refusal_reason(verdict: CoolingCatalogVerdict) -> String { } } } - -data cooling_catalog_scope_note: String = "CoolingCatalogCompatible is a preflight over cited catalog rows and nothing more. It is never evidence that an installed cooler actually holds a processor within its thermal limits: mounting quality, thermal-interface application, DIMM/VRM/NIC airflow, sustained workload temperature, throttling, acoustics, and room heat rejection are all outside what a purchase record can decide. The live BMC thermal/tach readback remains the only source of those facts, and a compatible catalog verdict must never be substituted for it — a consumer needs BOTH, and this module supplies exactly one." diff --git a/dag/gunbc/product/fabric/arbitration.dag b/dag/gunbc/product/fabric/arbitration.dag index 1c068f674c2..4f77af1dca0 100644 --- a/dag/gunbc/product/fabric/arbitration.dag +++ b/dag/gunbc/product/fabric/arbitration.dag @@ -16,43 +16,37 @@ import product.fabric.demand { } import product.fabric.execution { ExecutionGrant, ReservationRef, LeaseIdentity } -// This module is the second half of a command's core effect: it joins the -// demand-population transition, which product.fabric.demand owns, to the -// execution state that transition bears on. It exists as a peer rather than as -// more functions on demand.dag for one structural reason - execution.dag -// imports demand.dag, so the join cannot sit in demand.dag without inverting a -// live edge, and acyclicity is the import graph's only law. Nothing new is -// modeled here: the in-flight execution is read as ExecutionGrant, which -// already carries the attempt, the work and both reservation sets, so no -// projection of a grant is minted beside the grant. +// This module is the second half of a command's core effect: it joins the demand-population +// transition, which product.fabric.demand owns, to the execution state that transition bears on. It +// is a peer rather than more functions on demand.dag for one structural reason — execution.dag +// imports demand.dag, so the join cannot sit in demand.dag without inverting a live edge, and +// acyclicity is the import graph's only law. Nothing new is modeled: the in-flight execution is +// read as ExecutionGrant, which already carries the attempt, the work and both reservation sets, so +// no projection of a grant is minted beside it. -// The fabric does not release anything here. A directive NAMES what must be -// released and why; product.fabric.budget owns the money ledger and its release -// is a transition on an account. Deciding and actuating in one function would -// put a second authority for the released quantity in the caller's hands, which -// is exactly what release_money refuses by deriving the amount from the entry. -// Both reservation sets travel together because the prose claim is capacity AND -// budget: releasing money while holding capacity leaks a machine that is still -// billing under a work nothing requires. -// The lease travels with the directive because release is fenced: release_money -// refuses a generation that is not the one the hold was taken under, and a -// caller that had to re-derive the generation would be a second account of it, -// free to disagree with the grant that authorized the hold. The whole lease -// identity is carried rather than the generation alone, for the reason -// execution.dag already gives about receipt admission - a small integer that -// every lease starts at names nothing on its own. +// The fabric does not release anything here. A directive NAMES what must be released and why; +// product.fabric.budget owns the money ledger and its release is a transition on an account. +// Deciding and actuating in one function would put a second authority for the released quantity in +// the caller's hands — exactly what release_money refuses by deriving the amount from the entry. +// Both reservation sets travel together because the claim is capacity AND budget: releasing money +// while holding capacity leaks a machine still billing under work nothing requires. // -// authorized_by is the withdrawal's own evidence. A release that could not name -// the observation that caused it is a capacity and budget movement with no -// provenance, and the command was already carrying the answer. -// vouched_by carries the attestation's verification_authority forward. Naming -// who vouched on the ATTESTATION and then dropping it at the effect defeats the -// field's whole purpose one layer down: the release is the artifact a later -// reader actually finds, so an authority that stops short of it is unattributable -// exactly where attribution is needed. authorized_by and vouched_by are two -// facts, not one - the observation that meant withdraw, and the authority that -// vouched for the command carrying it - and collapsing them would lose whichever -// question was not asked first. +// The lease travels with the directive because release is fenced: release_money refuses a +// generation other than the one the hold was taken under, and a caller re-deriving the generation +// would be a second account of it, free to disagree with the grant that authorized the hold. The +// whole lease identity is carried rather than the generation alone, for the reason execution.dag +// gives about receipt admission — a small integer every lease starts at names nothing on its own. +// +// authorized_by is the withdrawal's own evidence. A release that cannot name the observation that +// caused it is a capacity and budget movement with no provenance, and the command already carried +// the answer. +// +// vouched_by carries the attestation's verification_authority forward. Naming who vouched on the +// ATTESTATION and dropping it at the effect defeats the field one layer down: the release is the +// artifact a later reader finds, so an authority stopping short of it is unattributable where +// attribution is needed. authorized_by and vouched_by are two facts — the observation that meant +// withdraw, and the authority that vouched for the command carrying it — and collapsing them loses +// whichever question was not asked first. type ReleaseDirective

{ attempt: FabricIdentity lease: LeaseIdentity @@ -63,30 +57,24 @@ type ReleaseDirective

{ vouched_by: DeclarationRef } -// Both execution-bearing arms carry a LIST, and the plural is load-bearing -// rather than defensive. More than one grant per Work is not a hypothetical: -// new_attempt_required explicitly admits a fresh attempt while one is already -// running, so a Work under re-execution holds two grants and two sets of -// reservations at once. An arm naming a single attempt made the caller's -// correct move unrepresentable - it would release the first grant's capacity -// and budget and silently strand every other one under a Work that nothing -// requires any more, which is the leak this model exists to close, arriving -// through the shape of the answer rather than through the decision. +// Both execution-bearing arms carry a LIST, and the plural is load-bearing, not defensive. More +// than one grant per Work is real: new_attempt_required admits a fresh attempt while one is +// running, so a Work under re-execution holds two grants and two sets of reservations. An arm +// naming a single attempt made the caller's correct move unrepresentable — it would release the +// first grant's capacity and budget and silently strand every other under a Work nothing requires, +// the leak this model exists to close, arriving through the shape of the answer. // -// The empty case keeps its own arm rather than becoming an empty list, because -// nothing running and a release plan that happens to be empty are different -// facts: the first is a Work that lost its last demand before any grant issued, -// and the second would be an actuator being handed nothing to do with no way to -// tell whether that was the answer or an omission. +// The empty case keeps its own arm rather than an empty list, because nothing running and an empty +// release plan are different facts: the first is a Work that lost its last demand before any grant +// issued; the second an actuator handed nothing with no way to tell an answer from an omission. -// The arms are the answers a binding is not allowed to reach on its own. Every -// combination that differs in what the caller must DO is its own arm, and none -// of them is a wildcard over the others: continuing an attempt and having no -// attempt to continue are the same population outcome with opposite execution -// consequences, and releasing a work with nothing running is a real state - not -// a degraded release - because a work can lose its last demand before any grant -// ever issued. Collapsing that into the stopping arm would hand the caller a -// release directive naming an attempt and reservations that do not exist. +// The arms are the answers a binding is not allowed to reach on its own. Every combination +// differing in what the caller must DO is its own arm, none a wildcard over the others: continuing +// an attempt and having none to continue are the same population outcome with opposite execution +// consequences, and releasing a work with nothing running is a real state — not a degraded release +// — because a work can lose its last demand before any grant issued. Collapsing it into the +// stopping arm would hand the caller a release directive naming an attempt and reservations that do +// not exist. type DemandCommandEffect

= CommandRefused { refusal: DemandRefusal

} | DemandAdmitted { population: DemandPopulation

, directive: SatisfactionDirective } @@ -126,9 +114,9 @@ fn execution_presence

( } } -// The reason is fixed by this module rather than supplied. A caller-chosen -// release reason is a second account of why the release happened, free to -// disagree with the transition that produced it. +// The reason is fixed by this module rather than supplied: a caller-chosen release reason is a +// second account of why the release happened, free to disagree with the transition that produced +// it. fn release_directive_for

( g: ExecutionGrant

, authorized_by: ObservationReceiptRef, @@ -145,11 +133,10 @@ fn release_directive_for

( } } -// A withdrawal or a supersede that emptied the work's demand population is the -// only path that may stop an attempt, and it may do so only after the -// population transition was admitted. Ordering matters: deciding the execution -// consequence first and authorizing afterwards is how an unauthorized -// withdrawal comes to have already cancelled something. +// A withdrawal or a supersede that emptied the work's demand population is the only path that may +// stop an attempt, and only after the population transition was admitted. Deciding the execution +// consequence first and authorizing afterwards is how an unauthorized withdrawal comes to have +// already cancelled something. fn withdrawal_effect

( population: DemandPopulation

, grants: List>, diff --git a/dag/gunbc/product/fabric/envelope.dag b/dag/gunbc/product/fabric/envelope.dag index 1ce4a775456..547f8b85250 100644 --- a/dag/gunbc/product/fabric/envelope.dag +++ b/dag/gunbc/product/fabric/envelope.dag @@ -20,7 +20,9 @@ import product.network_topology { NetworkEgressClass } // THE ROWS ARE MOVED VERBATIM, NOT RE-DERIVED. Every field, optionality and cited type is exactly // what fleet_container carried; this relocation changes the home and nothing else, so a reader // comparing against git history should find the definitions byte-identical. -data fabric_envelope_note: String = "General resource-requirement vocabulary for brokered compute. Moved verbatim from gunbc.fleet_container, which now consumes it: the questions are machine questions, not container questions." +// General resource-requirement vocabulary for brokered compute. Moved verbatim from +// gunbc.fleet_container, which now consumes it: the questions are machine questions, not container +// questions. type CpuRequirement { min_threads: HardwareThreadCount, architecture: Architecture? } diff --git a/dag/gunbc/product/fabric/isolation.dag b/dag/gunbc/product/fabric/isolation.dag index 7bcc878750e..704a514ae7f 100644 --- a/dag/gunbc/product/fabric/isolation.dag +++ b/dag/gunbc/product/fabric/isolation.dag @@ -17,7 +17,9 @@ import std.types { String, NonEmptyStr, Bool, List } // a sandbox. A container provides no exclusivity whatsoever: two brokers can each start one on the // same cell. Treating the sandbox as the exclusion wall is how a fabric double-books hardware // while every individual run looks correctly isolated. -data fabric_isolation_note: String = "Core isolation vocabulary: what work requires and an offer provides. The delivering mechanism (container, nspawn, microVM) is a realization handler and is deliberately unnamed here. Allocation exclusivity is a separate guarantee owned by the grant store, not by isolation." +// Core isolation vocabulary: what work requires and an offer provides. The delivering mechanism +// (container, nspawn, microVM) is a realization handler and is deliberately unnamed here. +// Allocation exclusivity is a separate guarantee owned by the grant store, not by isolation. // THE GUARANTEES, AS A CLOSED SET OF NAMED AXES RATHER THAN A BAG OF BOOLEANS. // diff --git a/dag/gunbc/product/fabric/sanitation.dag b/dag/gunbc/product/fabric/sanitation.dag index 86e911d8553..5d1c837075b 100644 --- a/dag/gunbc/product/fabric/sanitation.dag +++ b/dag/gunbc/product/fabric/sanitation.dag @@ -15,7 +15,9 @@ import std.types { String, NonEmptyStr, Bool, List, Int } // for exactly one attempt and no sandbox survives into the next. That split is what makes "fresh // spawn with caching" coherent rather than contradictory: fresh WRITABLE state per attempt, reused // IMMUTABLE material underneath, and the cache is never edited in place by tenant code. -data fabric_sanitation_note: String = "Per-attempt teardown and the readback that returns a cell to supply. Process exit is an input, never the verdict. Distinct from persistent host hygiene, which runs on its own cadence and answers a different question." +// Per-attempt teardown and the readback that returns a cell to supply. Process exit is an input, +// never the verdict. Distinct from persistent host hygiene, which runs on its own cadence and +// answers a different question. // THIS IS NOT THE PERSISTENT-HYGIENE LOOP, AND THE TWO MUST NOT MERGE. // @@ -25,7 +27,9 @@ data fabric_sanitation_note: String = "Per-attempt teardown and the readback tha // word cleanup and nothing else: one is a cadence over a host, the other a barrier between two // tenants. Folding them would make a slow disk-space job into a correctness dependency for every // job start, and would let a hygiene backlog read as a sanitation failure. -data sanitation_is_not_host_hygiene_note: String = "Per-attempt sanitation gates cell reuse and runs once per attempt; persistent host hygiene (gunbc.host_disk_reclaim) runs on a cadence and does not gate anything. Both feed cell readiness, from different directions." +// Per-attempt sanitation gates cell reuse and runs once per attempt; persistent host hygiene +// (gunbc.host_disk_reclaim) runs on a cadence and does not gate anything. Both feed cell readiness, +// from different directions. type CellId = NonEmptyStr where brand("CellId") diff --git a/dag/gunbc/product/installed_bom_reconcile.dag b/dag/gunbc/product/installed_bom_reconcile.dag index 70e76d303f9..3df7b2279c1 100644 --- a/dag/gunbc/product/installed_bom_reconcile.dag +++ b/dag/gunbc/product/installed_bom_reconcile.dag @@ -5,27 +5,27 @@ import product.placement_supply { HostIdentity } import extdeps.memory.types { DramModuleCatalogRow, MemoryPopulationRow } import extdeps.cpu.types { CpuModelCatalogRow } -// WHAT A HOST IS BELIEVED TO CONTAIN IS NOT WHAT IT CONTAINS, and this module exists to keep the -// two apart. An EXPECTATION is a claim someone makes about an installed bill of materials before -// anybody reads it off the hardware; an OBSERVATION is a receipt from an instrument that looked. -// Collapsing them is how a purchase record becomes a fleet fact, which is exactly the substitution +// WHAT A HOST IS BELIEVED TO CONTAIN IS NOT WHAT IT CONTAINS, and this module keeps the two apart. +// An EXPECTATION is a claim someone makes about an installed bill of materials before anybody +// reads it off the hardware; an OBSERVATION is a receipt from an instrument that looked. +// Collapsing them is how a purchase record becomes a fleet fact, the substitution // product.cooling_qualification warns about at the thermal boundary and this module refuses at the // identity boundary. // // The verdict is therefore THREE-VALUED and never two. Confirmed and Refused both require an -// observation; the third state is that nobody looked, and it is a coverage obligation rather than -// a pass. An unobserved host that reported Confirmed would make the observation program's own -// deficit invisible - the absorbing fallback in its purest form, since the frequency of a -// mismatch we never look for is zero by construction. +// observation; the third state is that nobody looked, a coverage obligation rather than a pass. An +// unobserved host reporting Confirmed would make the observation program's own deficit invisible +// - the absorbing fallback in its purest form, since the frequency of a mismatch we never look for +// is zero by construction. // // A MISMATCH IS THE POINT. The reconciliation refuses loudly, at slot grain where the instrument // reports slots, naming the expected part, the observed part, and where it sat. A reconciliation -// that answered "close enough" for a differing part number would be reporting the expectation -// back to its author. +// answering "close enough" for a differing part number would report the expectation back to its +// author. // -// NO PRICE, NO PURCHASE PROVENANCE. Nothing in this module carries money, order identity, or -// where a part was bought. Those are private facts; the public model carries what the hardware -// is, not what it cost. +// NO PRICE, NO PURCHASE PROVENANCE. Nothing here carries money, order identity, or where a part +// was bought. Those are private facts; the public model carries what the hardware is, not what it +// cost. type DimmSlotLocator = NonEmptyStr where brand("DimmSlotLocator") @@ -44,11 +44,11 @@ type ObservedDimmSlot { } // TWO INSTRUMENTS, TWO GRAINS, ONE RECONCILIATION. The BMC Redfish read this fleet already -// performed reports a population - module times stick count - and carries no slot locator that -// survives into the model; dmidecode reports one record per populated slot with the locator the -// firmware assigns. These are different observations of one subject, not two subjects, so they -// are arms of one coproduct feeding one fold. Forking the verdict per instrument would make the -// slot-grain answer and the population-grain answer two authorities for one question. +// performed reports a population - module times stick count - with no slot locator that survives +// into the model; dmidecode reports one record per populated slot with the firmware-assigned +// locator. Different observations of one subject, not two subjects, so they are arms of one +// coproduct feeding one fold. Forking the verdict per instrument would make the slot-grain answer +// and the population-grain answer two authorities for one question. type HostDimmObservation = DimmSlotsObserved { host: HostIdentity, slots: List } | DimmPopulationObserved { host: HostIdentity, rows: List } @@ -62,11 +62,10 @@ type HostDimmExpectation { // A CONTRADICTION IS A REPORT, NOT A BOOLEAN, AND NOT A FIRST REFUSAL EITHER. Older observation // receipts of this fleet disagree with the current expectation, so the reconciliation's product -// has to be the thing that settles them: every slot whose observed part differs is named, with -// where it sat, what was expected there, and what was actually found. A fold that stopped at the -// first difference would answer "at least one" - which is enough to fail a gate and useless for -// reconciling two disagreeing receipts, because the second difference only appears after somebody -// fixes the first. +// must settle them: every slot whose observed part differs is named, with where it sat, what was +// expected there, and what was found. A fold stopping at the first difference would answer "at +// least one" - enough to fail a gate and useless for reconciling two disagreeing receipts, because +// the second difference only appears after somebody fixes the first. type DimmDiscrepancyLocation = AtSlot { locator: DimmSlotLocator } | AtPopulationGrain @@ -84,14 +83,14 @@ type DimmCountReconcile // Refused carries BOTH axes because they are independent findings: a population can hold the right // count of the wrong part, the right part in the wrong count, or both, and each combination sends -// the reader somewhere different. Collapsing them into one cause would force the fold to pick -// which half of a two-part contradiction to report. +// the reader somewhere different. One cause would force the fold to pick which half of a two-part +// contradiction to report. // // Refused is reachable only from the fold below, and only when the discrepancy list is nonempty or -// the count differs. That is an invariant of the producer rather than of the carrier: the record -// is writable by hand with an empty list and a matching count, which would be a refusal asserting -// nothing. It sits at mechanically preventable, held by dimm_refusal_is_grounded and its witness, -// and it climbs when the substrate can seal a constructor to one fold. +// the count differs. That is an invariant of the producer, not the carrier: the record is writable +// by hand with an empty list and a matching count, a refusal asserting nothing. It sits at +// mechanically preventable, held by dimm_refusal_is_grounded and its witness, and climbs when the +// substrate can seal a constructor to one fold. type DimmReconcileVerdict = DimmPopulationConfirmed { host: HostIdentity, stick_count: Int } | DimmPopulationRefused { @@ -331,9 +330,8 @@ fn dimm_discrepancy_report(verdicts: List) -> List

sole_constructor { subject: TrainingRunSubject

attempts: List> } // EVERY WAY THE GRAPH CAN BE WRONG IS ITS OWN NAMED REFUSAL, because the remedies differ. An -// attempt under a foreign work is a routing defect; an absent predecessor is a lost observation; a -// duplicate identity is a producer emitting twice; an unrooted chain is a lineage that never -// reaches the demand and cannot be an attempt at this run at all. +// attempt under a foreign work is a routing defect; an absent predecessor a lost observation; a +// duplicate identity a producer emitting twice; an unrooted chain a lineage that never reaches the +// demand and cannot be an attempt at this run at all. type AttemptGraphRefusal

= AttemptWorkOutsideSubject { attempt: FabricIdentity, @@ -47,9 +47,9 @@ type AttemptGraphRefusal

| DuplicateAttemptIdentity { attempt: FabricIdentity } | AttemptLineageUnrooted { attempt: FabricIdentity } -// THE REFUSAL CARRIES EVERY DEFECT, NOT THE FIRST ONE FOUND. An ordered refusal would make the -// second defect invisible until the first is repaired, so a graph with three problems would take -// three round trips to admit. first + more is a non-empty population, not a ranking. +// THE REFUSAL CARRIES EVERY DEFECT, NOT THE FIRST FOUND. An ordered refusal hides the second defect +// until the first is repaired, so a graph with three problems would take three round trips to +// admit. first + more is a non-empty population, not a ranking. type AttemptGraphAdmission

= AttemptGraphAdmitted { graph: TrainingRunAttemptGraph

} | AttemptGraphRefused { @@ -134,11 +134,11 @@ fn predecessor_refusals

(attempts: List>) -> List(attempts: List>) -> List> { let ids = attempts.map(a => a.id) attempts.flat_map(a => @@ -150,9 +150,9 @@ fn duplicate_refusals

(attempts: List>) -> List { head_taken: Bool more: List> @@ -177,12 +177,12 @@ fn refusals_after_head

( // ROOTEDNESS IS A BOUNDED FIXPOINT, NOT A WALK. An attempt is rooted when its lineage reaches this // run's demand: directly, for an initial attempt or a requested re-execution, or through a // predecessor that is itself rooted. Each pass roots at least one further attempt or the set has -// stabilized, so folding over the attempt list -- whose length bounds the longest possible chain -- -// reaches the fixpoint without recursion and without a cycle being able to run forever. +// stabilized, so folding over the attempt list — whose length bounds the longest chain — reaches +// the fixpoint without recursion and without a cycle running forever. // -// What remains unrooted after the fixpoint, once absent predecessors have been separately -// accounted for, is exactly a lineage cycle or a chain rooted in some other demand: either way it -// is not an attempt at THIS run, which is the fact the graph exists to establish. +// What stays unrooted after the fixpoint, once absent predecessors are separately accounted for, is +// exactly a lineage cycle or a chain rooted in another demand: either way not an attempt at THIS +// run, the fact the graph exists to establish. fn directly_rooted

( subject: TrainingRunSubject

, attempts: List>, @@ -256,8 +256,8 @@ fn admit_attempt_graph

( } } -// MEMBERSHIP IS DECIDED AGAINST THE ADMITTED GRAPH AND NOTHING ELSE. This is the predicate every -// readback member must pass before its evidence may be joined at attempt grain. +// MEMBERSHIP IS DECIDED AGAINST THE ADMITTED GRAPH AND NOTHING ELSE — the predicate every readback +// member must pass before its evidence may be joined at attempt grain. fn graph_admits_attempt

( graph: TrainingRunAttemptGraph

, attempt: FabricIdentity, diff --git a/dag/gunbc/prose_row_frontier.dag b/dag/gunbc/prose_row_frontier.dag index 5bb11c64fc6..9e3e67ac291 100644 --- a/dag/gunbc/prose_row_frontier.dag +++ b/dag/gunbc/prose_row_frontier.dag @@ -3,310 +3,12 @@ module gunbc.prose_row_frontier import std.types { Bool, Int, String, List } import v2.std.optional { Optional } -// THE MIGRATION SCOPE, AND IT IS THE DISSOLUTION LEDGER RATHER THAN A CONFIGURATION KNOB. -// // DESIGN 4c rules that unclassified prose is forbidden and that `//` is the quarantine boundary, so a // prose-only `data NAME_note: String` row is debt in a representation no lens can classify. The wall -// that makes new debt unwritable is therefore correct corpus-wide. It is NOT enrolled corpus-wide, and -// the reason is measured rather than cautious: 25 of the 30 most recent first-parent commits on -// origin/main add at least one such row, with a tail of 81, 68, 63, 42 and 19. A corpus-wide flip on -// day one refuses roughly every concurrently open lane before the migration that would let them comply -// exists — cost displaced onto parties who did not cause it, which is the opposite of what DESIGN 6 -// prices a wall by. -// -// So the wall grows WITH the migration: each batch adds the paths it migrated to this roster in the -// SAME change that migrates them. That makes the roster a ledger of work actually done rather than a -// promise about what will be. -// -// A PATH IN SCOPE IS NOT A PATH WITH NO PROSE LEFT, and the distinction is load-bearing rather than -// pedantic. `jwt.dag` carries no legacy prose rows; four grandfathered rows remain in `oidc.dag`. They stay editable in place, because introduction is keyed on the declaration NAME and an edit removes and re-adds the same name. What enrollment buys is exactly one property: no NEW prose row lands here. Waiting for a path to be prose-free before defending it would let fresh debt accumulate in the very files being cleaned, at the pace the cleanup runs. -// -// (An earlier revision of this paragraph said a path enters scope `only once its prose actually left`. -// That was false of the first entry the same change enrolled — a claim in the authority contradicted by -// the roster three lines below it, which is the §3 defect this lane exists to remove, in prose rather -// than in a carrier.) -// -// MONOTONE, AND THAT IS A DECLARED RULE RATHER THAN AN ENFORCED ONE TODAY. A path may only ever be -// ADDED here. Removing one lowers an error class's rung, so under DESIGN 4b meta-obligation 3 it -// requires a declared previous rung, reason, bounded population and restoration trigger — never a quiet -// deletion to make a red go away. -// -// Nothing mechanically refuses such a deletion yet, and saying so is the point: an unenforced rule -// described as a wall is the coverage-by-illusion tier DESIGN 6 names. The mechanism is known and small -// — the same line-grain remove-only observation tools.extdeps_scope_placement_gate already runs over -// its frozen manifest, pointed at this declaration instead. NEXT-RUNG TRIGGER: that observation lands -// here with a control proving a scope-line deletion refuses, at which point this paragraph says -// `cannot` rather than `must not`. -// -// DISSOLVE-ON: when this roster spans the corpus, path scoping has no residue left to admit. At that -// point prose_row_path_in_scope is deleted, the refusal becomes unconditional over every introduced -// row, and this declaration goes with it. -data prose_row_migration_scope: List = [ - "dag/extdeps/auth/", - "dag/std/source_annotation.dag", - "src/v1/annotation_bind.dag", - "src/v1/tests/claim/v1_annotation_round_trip_test.dag", - "dag/extdeps/access/posix_effective_principal.dag", - "dag/extdeps/advertising/google_ads.dag", - "dag/extdeps/advertising/meta_ads.dag", - "dag/extdeps/astronomy/stellar_classification.dag", - "dag/extdeps/bmc/types.dag", - "dag/extdeps/clock/clock.dag", - "dag/extdeps/cloud_init/cloud_init.dag", - "dag/extdeps/color/srgb.dag", - "dag/extdeps/container/docker_ce.dag", - "dag/extdeps/cpu/amd.dag", - "dag/extdeps/cpu/ampere.dag", - "dag/extdeps/crm/salesforce_opportunity.dag", - "dag/extdeps/currency/currency.dag", - "dag/extdeps/darwin/sysctl.dag", - "dag/extdeps/exec/xargs.dag", - "dag/extdeps/filesystem/linux.dag", - "dag/extdeps/git/git.dag", - "dag/extdeps/git/object_store.dag", - "dag/extdeps/git/publication_transport.dag", - "dag/extdeps/github/log_annotations.dag", - "dag/extdeps/http/server.dag", - "dag/extdeps/instrument/pip_boy_3000.dag", - "dag/extdeps/instrument/pip_boy_3000_mk_v.dag", - "dag/extdeps/linux/procfs.dag", - "dag/extdeps/llm/cli.dag", - "dag/extdeps/llm/codex_auth.dag", - "dag/extdeps/llm/cursor_cli.dag", - "dag/extdeps/netplan/netplan.dag", - "dag/extdeps/network/endpoint.dag", - "dag/extdeps/os/ubuntu_autoinstall.dag", - "dag/extdeps/physics/bach_et_al_2013_double_slit.dag", - "dag/extdeps/pin.dag", - "dag/extdeps/posix/rusage.dag", - "dag/extdeps/pricing/gitlab_subscription.dag", - "dag/extdeps/pricing/object_storage.dag", - "dag/extdeps/process/gnu_bash_exit.dag", - "dag/extdeps/process/posix_exit.dag", - "dag/extdeps/provisioning/ubuntu_install_media_fetch.dag", - "dag/extdeps/provisioning/ubuntu_seeded_install_media.dag", - "dag/extdeps/provisioning/ubuntu_seeded_install_media_remaster.dag", - "dag/extdeps/render/terminal.dag", - "dag/extdeps/runtime/abi/sysv_amd64.dag", - "dag/extdeps/sec/edgar.dag", - "dag/extdeps/sec/edgar_rest.dag", - "dag/extdeps/sec/facts.dag", - "dag/extdeps/sec/issuer/atlassian_facts.dag", - "dag/extdeps/sec/issuer/gitlab_facts.dag", - "dag/extdeps/sec/operating_metrics.dag", - "dag/extdeps/sec/types.dag", - "dag/extdeps/sec/us_gaap.dag", - "dag/extdeps/ssh/keys.dag", - "dag/extdeps/standards/rfc_8118.dag", - "dag/extdeps/systemd/systemctl.dag", - "dag/extdeps/tailscale/serve.dag", - "dag/extdeps/time/rfc3339.dag", - "dag/extdeps/tmux/tmux.dag", - "dag/extdeps/toolchain/types.dag", - "dag/extdeps/tools/sha256sum.dag", - "dag/extdeps/vendor/the_wand_company.dag", - "dag/gunbc/bach_double_slit_observation.dag", - "dag/gunbc/bmc/bmc_converge.dag", - "dag/gunbc/bmc/bmc_netboot_shell_boot.dag", - "dag/gunbc/build_cache/build_cache_endpoint_path.dag", - "dag/gunbc/build_cache/build_cache_instance.dag", - "dag/gunbc/build_cache/build_cache_latency_probe.dag", - "dag/gunbc/build_cache/build_cache_unit.dag", - "dag/gunbc/chapman_which_path_observation.dag", - "dag/gunbc/ci/ci_deploy_access.dag", - "dag/gunbc/ci/ci_runner_placement.dag", - "dag/gunbc/econ/acquisition.dag", - "dag/gunbc/econ/free_tier_serving.dag", - "dag/gunbc/econ/knowable_wedge.dag", - "dag/gunbc/econ/llm_attempt_receipt.dag", - "dag/gunbc/econ/margin_envelope.dag", - "dag/gunbc/econ/regional_compute_premium.dag", - "dag/gunbc/econ/scm_serving_model.dag", - "dag/gunbc/executor_schedule_retention.dag", - "dag/gunbc/falsifier_alert_residue.dag", - "dag/gunbc/generated_artifact_merge_driver.dag", - "dag/gunbc/githooks/githooks_pre_push_emit.dag", - "dag/gunbc/heal_revalidation.dag", - "dag/gunbc/host/host_budget_source.dag", - "dag/gunbc/host/host_compile_pool.dag", - "dag/gunbc/host/host_network_diagnosis.dag", - "dag/gunbc/host/host_toolchain_components.dag", - "dag/gunbc/host/host_toolchain_ensure.dag", - "dag/gunbc/legacy_test_behavior_disposition.dag", - "dag/gunbc/live_deploy/operations.dag", - "dag/gunbc/merge_admission_subject.dag", - "dag/gunbc/merge_lifecycle.dag", - "dag/gunbc/observation_ci_render.dag", - "dag/gunbc/observation_seed_render.dag", - "dag/gunbc/observation_tty_render.dag", - "dag/gunbc/output_policy.dag", - "dag/gunbc/physics/which_path_four_model_comparison.dag", - "dag/gunbc/principal_projection.dag", - "dag/gunbc/provider_account.dag", - "dag/gunbc/publication_admission_delta.dag", - "dag/gunbc/publication_policy.dag", - "dag/gunbc/readback_independence.dag", - "dag/gunbc/roadmap/roadmap_acceptance_history_carrier.dag", - "dag/gunbc/roadmap/roadmap_acceptance_history_observation.dag", - "dag/gunbc/roadmap/roadmap_altitude.dag", - "dag/gunbc/roadmap/roadmap_dashboard_instance.dag", - "dag/gunbc/roadmap/roadmap_dashboard_instance_apply.dag", - "dag/gunbc/roadmap/roadmap_focus.dag", - "dag/gunbc/roadmap/roadmap_forecast.dag", - "dag/gunbc/roadmap/roadmap_identity.dag", - "dag/gunbc/roadmap/roadmap_model.dag", - "dag/gunbc/roadmap/roadmap_page.dag", - "dag/gunbc/roadmap/roadmap_presentation.dag", - "dag/gunbc/roadmap/roadmap_provider_events.dag", - "dag/gunbc/roadmap/roadmap_publication_helper.dag", - "dag/gunbc/roadmap/roadmap_publish.dag", - "dag/gunbc/roadmap/roadmap_publish_observe.dag", - "dag/gunbc/roadmap/roadmap_site_healthz_validate.dag", - "dag/gunbc/roadmap/roadmap_site_surface_observe.dag", - "dag/gunbc/roadmap/roadmap_style.dag", - "dag/gunbc/roadmap/roadmap_validation_oracle.dag", - "dag/gunbc/roadmap/roadmap_verification_receipt.dag", - "dag/gunbc/roadmap/roadmap_verify.dag", - "dag/gunbc/roadmap/roadmap_workflow_progress.dag", - "dag/gunbc/runner/runner_activation.dag", - "dag/gunbc/seed_growth_admission.dag", - "dag/gunbc/shell_target_conformance.dag", - "dag/gunbc/stage0/stage0_cargo_manifest.dag", - "dag/gunbc/test_module_hygiene.dag", - "dag/std/access.dag", - "dag/std/authorization_profile.dag", - "dag/std/citation.dag", - "dag/std/observation.dag", - "dag/std/primitive_identity.dag", - "dag/test/claim/anomaly_evidence_witness_test.dag", - "dag/test/claim/artifact_store_scratch_grant_witness_test.dag", - "dag/test/claim/bmc_firmware_thermal_witness_test.dag", - "dag/test/claim/build_cache_latency_probe_witness_test.dag", - "dag/test/claim/covering_grant_witness_test.dag", - "dag/test/claim/css_grain_witness_test.dag", - "dag/test/claim/cursor_cli_invocation_witness_test.dag", - "dag/test/claim/dispatch_attempts_witness_test.dag", - "dag/test/claim/effect_axes_witness_test.dag", - "dag/test/claim/effect_grant_witness_test.dag", - "dag/test/claim/exec_arg_limit_witness_test.dag", - "dag/test/claim/frame_boundary_simulated_witness_test.dag", - "dag/test/claim/frame_boundary_witness_test.dag", - "dag/test/claim/host_budget_source_witness_test.dag", - "dag/test/claim/host_network_diagnosis_witness_test.dag", - "dag/test/claim/host_swap_backing_witness_test.dag", - "dag/test/claim/host_toolchain_components_witness_test.dag", - "dag/test/claim/instrument_physical_witness_test.dag", - "dag/test/claim/instrument_sandbox_witness_test.dag", - "dag/test/claim/jwt_oidc_claims_witness_test.dag", - "dag/test/claim/language_target_identity_witness_test.dag", - "dag/test/claim/language_target_registry_totality_test.dag", - "dag/test/claim/language_target_subject_registration_test.dag", - "dag/test/claim/legacy_test_inflow_witness_test.dag", - "dag/test/claim/live_rust_observation_contract_test.dag", - "dag/test/claim/long/dag_compile_clean_perturb_corpus_witness_test.dag", - "dag/test/claim/long/v1_complexity_capability_census_resolution_test.dag", - "dag/test/claim/materialization_provider_consumer_hand_rust_witness_test.dag", - "dag/test/claim/materialization_provider_witness_test.dag", - "dag/test/claim/materialized_secret_witness_test.dag", - "dag/test/claim/media_type_witness_test.dag", - "dag/test/claim/output_policy_witness_test.dag", - "dag/test/claim/provider_account_admission_witness_test.dag", - "dag/test/claim/push_event_witness_test.dag", - "dag/test/claim/push_event_witness_wet_test.dag", - "dag/test/claim/readback_independence_witness_test.dag", - "dag/test/claim/regional_compute_witness_test.dag", - "dag/test/claim/repository_census_coverage_witness_test.dag", - "dag/test/claim/roadmap_altitude_witness_test.dag", - "dag/test/claim/roadmap_dashboard_instance_apply_witness_test.dag", - "dag/test/claim/roadmap_dashboard_instance_witness_test.dag", - "dag/test/claim/roadmap_execution_contract_witness_test.dag", - "dag/test/claim/roadmap_focus_witness_test.dag", - "dag/test/claim/roadmap_identity_witness_test.dag", - "dag/test/claim/long/roadmap_page_witness_test.dag", - "dag/test/claim/roadmap_presentation_witness_test.dag", - "dag/test/claim/roadmap_publication_helper_witness_test.dag", - "dag/test/claim/roadmap_publish_observe_witness_test.dag", - "dag/test/claim/roadmap_publish_witness_test.dag", - "dag/test/claim/roadmap_row_witness_test.dag", - "dag/test/claim/roadmap_tactile_witness_test.dag", - "dag/test/claim/roadmap_validation_oracle_witness_test.dag", - "dag/test/claim/roadmap_verify_witness_test.dag", - "dag/test/claim/roadmap_workflow_command_witness_test.dag", - "dag/test/claim/salience_witness_test.dag", - "dag/test/claim/scm_issuer_corpus_witness_test.dag", - "dag/test/claim/scm_provider_matrix_witness_test.dag", - "dag/test/claim/scm_serving_model_witness_test.dag", - "dag/test/claim/session_residency_witness_test.dag", - "dag/test/claim/source_annotation_attachment_witness_test.dag", - "dag/test/claim/state_response_totality_witness_test.dag", - "dag/test/claim/test_module_hygiene_hand_rust_equivalence_witness_test.dag", - "dag/test/claim/tool_pin_witness_test.dag", - "dag/test/claim/transport_script_wall_compile_red_test.dag", - "dag/test/claim/type_ref_hit_ne_bind_measure_witness_test.dag", - "dag/test/claim/type_reference_containment_binding_witness_test.dag", - "dag/test/claim/v1_complexity_capability_census_witness_test.dag", - "dag/test/claim/witness_row_cost_migration_disclosure_witness_test.dag", - "dag/gunbc/instruments/build_step.dag", - "dag/gunbc/instruments/extdeps_scope_placement_gate.dag", - "dag/gunbc/instruments/merge_admission_capture_transport.dag", - "dag/gunbc/instruments/self_host_curated_seed_linked_harness.dag", - "src/v2/compiler/01_tokenize.dag", - "src/v2/compiler/03_name_resolve.dag", - "src/v2/compiler/03_resolve.dag", - "src/v2/compiler/build_workspace_grant.dag", - "src/v2/compiler/manual/retry_eagain_bash.dag", - "src/v2/compiler/parse_diagnostic.dag", - "src/v2/compiler/self_host/direct_rust_door_observation.dag", - "src/v2/compiler/self_host/emitter_producer_provenance.dag", - "src/v2/compiler/self_host/v2_emitter_direct_rust_door_contract.dag", - "src/v2/compiler/source_authority.dag", - "src/v2/extdeps/runtimes/effect_io_host_grant.dag", - "src/v2/extdeps/runtimes/v2_evaluator.dag", - "src/v2/lens/affected_set/declared_source_ref_selection.dag", - "src/v2/lens/complexity_accumulator_copy/analyze.dag", - "src/v2/lens/grounding.dag", - "src/v2/lens/identity_captured_navigation.dag", - "src/v2/lens/identity_captured_navigation/analyze.dag", - "src/v2/lens/identity_captured_navigation/roster_gate.dag", - "src/v2/lens/identity_captured_navigation_test.dag", - "src/v2/lens/languages_consumer_census.dag", - "src/v2/lens/mandatory_tag.dag", - "src/v2/lens/production_qualification_origin_probe.dag", - "src/v2/lens/test_migration_debt.dag", - "src/v2/lens/vacuity_test.dag", - "src/v2/std/cardinality.dag", - "src/v2/std/compilers/coproduct_variant_shape.dag", - "src/v2/std/compilers/lexing.dag", - "src/v2/std/compilers/semantic_decl_emission.dag", - "src/v2/std/constraints.dag", - "src/v2/std/witness_evaluation.dag", - "src/v2/test/claim/build_workspace_grant_witness_test.dag", - "src/v2/test/claim/compile_door_ledger_ownership_test.dag", - "src/v2/test/claim/complexity_contract_subject_partition_witness_test.dag", - "src/v2/test/claim/emit/semantic_decl_serialize_parity_test.dag", - "src/v2/test/claim/emit/target_text_carrier_test.dag", - "src/v2/test/claim/execution/native_selected_witness_bundle_test.dag", - "src/v2/test/claim/identity_captured_navigation/long/roster_gate_test.dag", - "src/v2/test/claim/identity_captured_navigation/roster_gate_test.dag", - "src/v2/test/claim/long/module_storage_binding_derivation_test.dag", - "src/v2/test/claim/long/parse_table_memo_governed_witness_test.dag", - "src/v2/test/claim/manual/body_lowering_match.dag", - "src/v2/test/claim/manual/e0599_phase_a_body_evidence_probe.dag", - "src/v2/test/claim/meta_exec_confinement/lens_unit/discriminators_test.dag", - "src/v2/test/claim/round_trip/dag_comment_wall_test.dag", - "src/v2/test/claim/schedule_occurrence_multiplicity_test.dag", - "src/v2/test/claim/tokenize/annotation_channel_test.dag", - "src/v2/test/claim/witness_exclusion_reconciliation_test.dag", - "src/v2/test/compiler/pipeline/corpus.dag", - "src/v2/workflow/bash_command_fold_serialize.dag", - "src/v2/workflow/ci_materialization_emit.dag", - "src/v2/workflow/ci_release_build_emit.dag", - "src/v2/workflow/ci_retry_emit.dag", - "src/v2/workflow/enforcement_live.dag", - "src/v2/workflow/legacy_test_inflow_audit.dag", - "src/v2/workflow/locality_affinity.dag", - "src/v2/workflow/module_resolution_plan.dag", -] +// was first enrolled path by path, a roster grown in the same change as each migrated batch, so that +// a day-one corpus-wide flip did not refuse every open lane. Once the corpus-wide sweep migrated every +// unreferenced row the roster's declared dissolve-on was met: path scoping is deleted and the refusal +// is unconditional over every introduced row. // The shape the wall recognises, stated once so the gate, the receipt and the controls cannot drift // into three readings of it. A prose row is a MODULE-SCOPE `data` declaration whose name ends in @@ -480,28 +182,6 @@ fn prose_rows_introduced(added: List, removed: List) -> Lis added |> filter(row => !prose_row_name_removed(removed: removed, name: row.decl_name)) } -fn prose_row_path_in_scope(path: String) -> Bool { - fold(prose_row_migration_scope, init: false, f: fn(found, prefix) { - found || starts_with(s: path, prefix: prefix) - }) -} - -// THE REFUSED POPULATION: introduced rows under a migrated path. This is the wall. -fn prose_rows_refused(introduced: List) -> List { - introduced |> filter(row => prose_row_path_in_scope(path: row.path)) -} - -// THE COUNTED RESIDUE: introduced rows OUTSIDE the migrated paths. -// -// This is not a softer wall and must never be read as one. Path scoping leaves a real gap — new prose -// keeps landing everywhere the migration has not reached — and DESIGN 5 requires a bounded scope's -// residue to be observable and prioritizable rather than silent, because a deficit whose frequency is -// zero by construction never ranks for fixing. So the residue is counted and located at row grain and -// carried into the gate's receipt, where it drives which paths join the roster next. It gates nothing. -fn prose_rows_out_of_scope(introduced: List) -> List { - introduced |> filter(row => !prose_row_path_in_scope(path: row.path)) -} - fn prose_row_render(row: ProseRowRef) -> String { concat(row.path, concat(" ", row.decl_name)) } diff --git a/dag/gunbc/provider_account.dag b/dag/gunbc/provider_account.dag index 00869d7537b..a35f3e348bf 100644 --- a/dag/gunbc/provider_account.dag +++ b/dag/gunbc/provider_account.dag @@ -12,7 +12,18 @@ import extdeps.llm.codex_auth { codex_observed_concurrent_sessions_per_root, } -data provider_account_naming_note: String = "THE SUFFIX CONVENTION IS OURS; THE ISOLATION IS CODEX'S. extdeps.llm.codex_auth records that CODEX_HOME selects an independent credential store — that is the CLI's capability and exists regardless of what anyone names the directories. What lives here is the choice to name them ~/.codex@