diff --git a/dag/extdeps/systemd/systemctl.dag b/dag/extdeps/systemd/systemctl.dag index 1acf1739c59..b1e644d3f9e 100644 --- a/dag/extdeps/systemd/systemctl.dag +++ b/dag/extdeps/systemd/systemctl.dag @@ -225,8 +225,14 @@ fn systemctl_set_property_argv(unit: NonEmptyStr, property: NonEmptyStr, value: ] } +// `systemctl revert UNIT...` takes UNIT NAMES ONLY and removes every drop-in of each named unit +// (systemctl(1), systemd 255). Property names are not arguments: an earlier argv appended +// "MemoryMax" "MemoryHigh", which systemctl read as two more units (MemoryMax.service, +// MemoryHigh.service) and reverted silently with exit 0 -- so the argv claimed a property-scoped +// revert it never performed. What this reverts is the unit's whole drop-in set, including the +// control shadows set-property wrote, and that is what the caller is relying on. fn systemctl_revert_memory_caps_argv(unit: NonEmptyStr) -> List { - ["systemctl", "revert", unit as String, "MemoryMax", "MemoryHigh"] + ["systemctl", "revert", unit as String] } // THE USER MANAGER IS A DIFFERENT MANAGER, NOT A FLAG ON THE SAME QUERY. `systemctl show` without @@ -597,10 +603,10 @@ service systemd.Systemctl { success: Bool from "exit_success" stderr: String from "stderr" } - transport shell { argv: ["systemctl", "revert", "{unit}", "MemoryMax", "MemoryHigh"] } + transport shell { argv: ["systemctl", "revert", "{unit}"] } exit { 0 => Unit - nonzero => String "systemctl revert MemoryMax MemoryHigh failed" + nonzero => String "systemctl revert failed" } mock_response { 0 => { success: true, stderr: "" } "hermetic systemd.Systemctl.RevertMemoryCaps" diff --git a/dag/gunbc/ci/ci_deploy_access.dag b/dag/gunbc/ci/ci_deploy_access.dag index feeb259bd91..07213a79511 100644 --- a/dag/gunbc/ci/ci_deploy_access.dag +++ b/dag/gunbc/ci/ci_deploy_access.dag @@ -405,3 +405,21 @@ fn srv1_live_deploy_privileged_commands() -> List { p: fleet_posix_ci_runner_principal() ) } + +// EVERY ACCESS THAT APPLIES A DEPLOYMENT, AS ONE ROSTER. A host-shared resource whose content is a +// function of who deploys to that host -- /etc/sudoers.d/gunbc-deploy -- is derived from this list +// filtered by host, never from the one access that happens to be running, so applying any +// deployment through any access renders the same bytes (gunbc.ci_deploy_sudoers +// deploy_sudoers_dropin_body). A new access is a row here; one left out is a principal whose grants +// the next apply through another access would delete. +fn deploy_access_roster() -> List { + [ + ci_deploy_srv1_access, + ci_deploy_srv1_operator_access, + ci_deploy_srv1_operator_local_access, + ] +} + +fn deploy_accesses_on_host(host: ComputeHost) -> List { + filter(xs: deploy_access_roster(), predicate: a => (a.host.identity as String) == (host.identity as String)) +} diff --git a/dag/gunbc/ci/ci_deploy_sudoers.dag b/dag/gunbc/ci/ci_deploy_sudoers.dag index f2169cb8442..612da35305a 100644 --- a/dag/gunbc/ci/ci_deploy_sudoers.dag +++ b/dag/gunbc/ci/ci_deploy_sudoers.dag @@ -1,7 +1,8 @@ module gunbc.ci_deploy_sudoers import std.types { String, NonEmptyStr, List, Bool } -import v2.std.algebra { fold_list, any } +import v2.std.algebra { fold_list, any, list_flat_map } +import std.list { distinct_by_key } import extdeps.sudo.elevation { sudo_elevate_argv } import v2.extdeps.languages.bash_build { bash_build_command_from_lits, @@ -30,6 +31,7 @@ import extdeps.sudo.nopasswd_execute_probe_check_op { import gunbc.ci_deploy_access { DeployAccess, PrivilegedCommand, + deploy_accesses_on_host, gunbc_deploy_sudoers_dropin, deploy_access_sudoers_line, posix_sudo_grant_to_privileged, @@ -100,15 +102,27 @@ fn deploy_sudoers_line_for_grant(access: DeployAccess, g: PosixSudoGrant) -> Str ) } -// One line per grant, header first. The heredoc row (v2.workflow.bash_emit bash_emit_heredoc) +// THE DROP-IN IS HOST-SHARED, SO ITS BODY IS THE UNION OVER EVERY ACCESS THAT DEPLOYS TO THE HOST +// (gunbc.ci_deploy_access deploy_accesses_on_host), not the running access's own grants. Rendering +// from the running access alone made an operator-local apply rewrite the file to the operator's one +// line and delete the CI runner's grants. One line per (principal, grant), first occurrence kept +// in roster order, header first. The heredoc row (v2.workflow.bash_emit bash_emit_heredoc) // terminates the body itself, so the lines are joined without a trailing newline. -fn deploy_sudoers_dropin_body(access: DeployAccess) -> String { - join( - concat([deploy_sudoers_dropin_header()], map(access.principal.sudo_grants, g => deploy_sudoers_line_for_grant(access: access, g: g))), - "\n" +fn deploy_sudoers_dropin_lines_for(accesses: List) -> List { + distinct_by_key( + xs: list_flat_map(xs: accesses, f: a => map(a.principal.sudo_grants, g => deploy_sudoers_line_for_grant(access: a, g: g))), + key: l => l, ) } +fn deploy_sudoers_dropin_body_for(accesses: List) -> String { + join(concat([deploy_sudoers_dropin_header()], deploy_sudoers_dropin_lines_for(accesses: accesses)), "\n") +} + +fn deploy_sudoers_dropin_body(access: DeployAccess) -> String { + deploy_sudoers_dropin_body_for(accesses: deploy_accesses_on_host(host: access.host)) +} + // THE ELEVATION PREFIX IS READ FROM extdeps.sudo.elevation sudo_elevate_argv, not typed here, so the // two tokens that cross the privilege boundary have one authority in the repository. fn deploy_sudoers_elevated(command: List, tail: List) -> Node { diff --git a/dag/gunbc/live_deploy/apply.dag b/dag/gunbc/live_deploy/apply.dag index b6e8cd72b3f..74e4a2f79a6 100644 --- a/dag/gunbc/live_deploy/apply.dag +++ b/dag/gunbc/live_deploy/apply.dag @@ -23,7 +23,18 @@ import gunbc.live_deploy.spec { deployment_plan_host_identity, deployment_tailnet_door_socket, } -import gunbc.live_deploy.desired { deployment_spec_srv1 } +import gunbc.live_deploy.desired { + deployment_spec_srv1, + desired_deployment_for_instance_id, + selected_deployment_spec, + srv1_production_desired_deployment, + instance_id_text, + DesiredDeploymentFound, + DesiredDeploymentUnknownInstanceId, + DesiredDeploymentRosterRefused, + DesiredRosterSameHostCollision, + DesiredRosterDuplicateInstanceId, +} import std.types { String, CommitSha, NonEmptyStr } import gunbc.live_deploy.candidate { CandidateRelease, @@ -1127,19 +1138,66 @@ fn approval_broker_dark_install_srv1_wet() -> ProcessExit { ) } +// ONE APPLY ROUTE, SELECTED BY INSTANCE ID. The roster (gunbc.live_deploy.desired +// desired_deployment_roster) is the selection authority; this is the only place an apply entry +// turns an instance id into a DeploymentSpec, and the production entries below are this route +// called with production's id rather than a second fold. A row whose target host is not the host +// the access reaches refuses: an access is a principal on ONE host, and pointing it at another +// row would mutate the wrong machine under a correct-looking spec. +type DesiredApplySelection + = DesiredApplySelected { spec: DeploymentSpec } + | DesiredApplyRefused { reason: String } + +fn live_deploy_desired_apply_selection(instance_id: String, access: DeployAccess) -> DesiredApplySelection { + match desired_deployment_for_instance_id(instance_id: instance_id) { + DesiredDeploymentUnknownInstanceId { instance_id: id } => + DesiredApplyRefused { reason: join(["live_deploy apply: DesiredDeploymentUnknownInstanceId '", id, "' names no row of desired_deployment_roster"], "") } + DesiredDeploymentRosterRefused { refusal } => + match refusal { + DesiredRosterSameHostCollision { host } => + DesiredApplyRefused { reason: join(["live_deploy apply: DesiredRosterSameHostCollision on host ", host], "") } + DesiredRosterDuplicateInstanceId { instance_id: dup } => + DesiredApplyRefused { reason: join(["live_deploy apply: DesiredRosterDuplicateInstanceId ", dup], "") } + } + DesiredDeploymentFound { row } => + if (row.target.host.identity as String) != (access.host.identity as String) { + DesiredApplyRefused { reason: join(["live_deploy apply: DesiredApplyHostMismatch — row '", instance_id, "' targets ", row.target.host.identity as String, " but the access reaches ", access.host.identity as String], "") } + } else { + DesiredApplySelected { spec: selected_deployment_spec(row: row) } + } + } +} + +fn live_deploy_apply_desired_wet(instance_id: String, access: DeployAccess) -> ProcessExit { + match live_deploy_desired_apply_selection(instance_id: instance_id, access: access) { + DesiredApplyRefused { reason } => exit_failure(reason: reason) + DesiredApplySelected { spec } => live_deploy_wet_with_access(spec: spec, access: access) + } +} + +// The operator-local apply of any srv1 roster row, e.g. +// `--function live_deploy_apply_desired_operator_local_wet --arg instance_id=srv1-lab-codex-feedback-v0`. +fn live_deploy_apply_desired_operator_local_wet(instance_id: String) -> ProcessExit { + live_deploy_apply_desired_wet(instance_id: instance_id, access: ci_deploy_srv1_operator_local_access) +} + +fn srv1_production_instance_id() -> String { + instance_id_text(id: srv1_production_desired_deployment().instance.instance_id) +} + fn live_deploy_apply_srv1_wet() -> ProcessExit { - live_deploy_wet_with_access(spec: deployment_spec_srv1(), access: ci_deploy_srv1_access) + live_deploy_apply_desired_wet(instance_id: srv1_production_instance_id(), access: ci_deploy_srv1_access) } fn live_deploy_apply_srv1_operator_wet() -> ProcessExit { - live_deploy_wet_with_access(spec: deployment_spec_srv1(), access: ci_deploy_srv1_operator_access) + live_deploy_apply_desired_wet(instance_id: srv1_production_instance_id(), access: ci_deploy_srv1_operator_access) } // The converge verb's deploy mode: the same admitted path the CI entry runs, under the operator // principal on the host itself. Candidate observation, preflight, mutation and the readiness // gate are all shared; nothing here is a second deploy. fn live_deploy_apply_srv1_operator_local_wet() -> ProcessExit { - live_deploy_wet_with_access(spec: deployment_spec_srv1(), access: ci_deploy_srv1_operator_local_access) + live_deploy_apply_desired_operator_local_wet(instance_id: srv1_production_instance_id()) } fn live_deploy_retract_srv1_wet() -> ProcessExit { diff --git a/dag/gunbc/live_deploy/spec.dag b/dag/gunbc/live_deploy/spec.dag index 1bc03c1b494..5b3c3aa6256 100644 --- a/dag/gunbc/live_deploy/spec.dag +++ b/dag/gunbc/live_deploy/spec.dag @@ -1185,10 +1185,7 @@ fn deployment_ensured_srv1_host_directories() -> List owner: gunbc_service_user, }, }, - ], concat(map(fabric_storage_store_directories(), a => DeploymentDependencyStep { - kind: FabricStorageStoreDirectory, - subject: EnsuredManagedHostDirectory { admission: a }, - }), [ + ], [ DeploymentDependencyStep { kind: CodexRuntimeReleaseDirectory, subject: EnsuredHostDirectory { @@ -1196,11 +1193,28 @@ fn deployment_ensured_srv1_host_directories() -> List owner: gunbc_service_user, }, }, - ])) + ]) +} + +// THE FABRIC STORE IS THE PLACED INSTANCE'S, NOT THE HOST'S (gunbc.fabric_storage_placement +// fabric_storage_placed_instance). While its directories rode on the host target, every deployment +// on srv1 converged them -- a lab twin's apply ran `install -d -m` over production's store. Only the +// placed instance ensures them; any other instance on the host reaches the store through its door +// at most and writes nothing here. Same rule PR #13583 states as +// fabric_storage_directory_demands(instance). +fn fabric_storage_ensured_steps(instance: HostDashboardInstance) -> List { + if fabric_storage_placed_on(instance: instance) { + map(fabric_storage_store_directories(), a => DeploymentDependencyStep { + kind: FabricStorageStoreDirectory, + subject: EnsuredManagedHostDirectory { admission: a }, + }) + } else { + [] + } } -fn deployment_ensured_steps(target: DeploymentHostTarget) -> List { - concat(deployment_ensured_packages(), target.ensured_directories) +fn deployment_ensured_steps(instance: HostDashboardInstance, target: DeploymentHostTarget) -> List { + concat(deployment_ensured_packages(), concat(target.ensured_directories, fabric_storage_ensured_steps(instance: instance))) } // THE PROVIDER-STATE ROOT IS AN ENSURED MEMBER OF THE INSTANCE, NOT OF THE HOST (CONVERGENCE-ONE @@ -1287,7 +1301,7 @@ fn deployment_steps_apply_order( names: DeploymentNames, ) -> List { concat( - map(deployment_ensured_steps(target: target), s => Dependency { step: s }), + map(deployment_ensured_steps(instance: instance, target: target), s => Dependency { step: s }), map( deployment_owned_steps(instance: instance, names: names), s => ArtifactStep { step: s }, diff --git a/dag/gunbc/roadmap/roadmap_dashboard_instance.dag b/dag/gunbc/roadmap/roadmap_dashboard_instance.dag index 7e72e070ab6..a54464a5786 100644 --- a/dag/gunbc/roadmap/roadmap_dashboard_instance.dag +++ b/dag/gunbc/roadmap/roadmap_dashboard_instance.dag @@ -1063,7 +1063,7 @@ fn srv1_lab_dashboard_instance() -> HostDashboardInstance { root: srv1_dashboard_lab_root, serve_function: "roadmap_serve_handle_srv1_lab" as NonEmptyStr, listen_port: 18081, - source_anchor_ref: "refs/heads/session/codex-provider-feedback-v0" as NonEmptyStr, + source_anchor_ref: "refs/remotes/origin/main" as NonEmptyStr, actuation: DashboardActuating, provision_transport: DashboardSshProvision { host: "srv1" as NonEmptyStr }, ) diff --git a/dag/test/claim/ci/deploy_sudoers_host_union_witness_test.dag b/dag/test/claim/ci/deploy_sudoers_host_union_witness_test.dag new file mode 100644 index 00000000000..a955a78bbeb --- /dev/null +++ b/dag/test/claim/ci/deploy_sudoers_host_union_witness_test.dag @@ -0,0 +1,39 @@ +module test.claim.ci.deploy_sudoers_host_union_witness_test + +import std.types { Bool, String, List } +import gunbc.ci_deploy_access { + DeployAccess, + ci_deploy_srv1_access, + ci_deploy_srv1_operator_access, + ci_deploy_srv1_operator_local_access, + deploy_accesses_on_host, +} +import gunbc.ci_deploy_sudoers { deploy_sudoers_dropin_body, deploy_sudoers_dropin_body_for } + +// /etc/sudoers.d/gunbc-deploy IS HOST-SHARED (gunbc.ci_deploy_sudoers deploy_sudoers_dropin_body). +// The drop-in body is a function of the access alone -- no deployment row reaches it -- so the +// lab row and the production row render it identically by construction; what these claims pin is +// that every srv1 access renders the SAME bytes and that those bytes keep the CI runner's grants. +data ghrunner_systemctl_line: String = "ghrunner ALL=(ALL) NOPASSWD: /usr/bin/systemctl" + +test fn every_srv1_access_renders_the_same_dropin_and_it_keeps_the_ci_runner_grants() -> Bool { + let local = deploy_sudoers_dropin_body(access: ci_deploy_srv1_operator_local_access) + local == deploy_sudoers_dropin_body(access: ci_deploy_srv1_access) + && local == deploy_sudoers_dropin_body(access: ci_deploy_srv1_operator_access) + && string_contains(s: local, pattern: ghrunner_systemctl_line) + && string_contains(s: local, pattern: "briansrls ALL=(ALL) NOPASSWD: /usr/bin/hostnamectl") +} + +fn is_ci_runner_access(a: DeployAccess) -> Bool { + (a.principal.posix.name as String) == (ci_deploy_srv1_access.principal.posix.name as String) +} + +// THE DISCRIMINATOR: the same body over the host roster with the CI principal removed loses the +// runner's lines. This is the drop-in the operator-local apply used to install. +test fn removing_the_ci_principal_from_the_union_drops_its_grants() -> Bool { + let without_ci = filter( + xs: deploy_accesses_on_host(host: ci_deploy_srv1_access.host), + predicate: a => !is_ci_runner_access(a: a), + ) + !string_contains(s: deploy_sudoers_dropin_body_for(accesses: without_ci), pattern: ghrunner_systemctl_line) +} diff --git a/dag/test/claim/devboot_subject_identity_witness_test.dag b/dag/test/claim/devboot_subject_identity_witness_test.dag index 57b9f9a2948..27946418ec5 100644 --- a/dag/test/claim/devboot_subject_identity_witness_test.dag +++ b/dag/test/claim/devboot_subject_identity_witness_test.dag @@ -1,5 +1,6 @@ module test.claim.devboot_subject_identity_witness +import gunbc.roadmap_dashboard_instance { srv1_live_dashboard_instance } import std.types { Bool, Int, List, NonEmptyStr, String } import std.measure { byte_size } import std.content_hash { @@ -734,7 +735,7 @@ fn devboot_ensured_step_projects_layout(acc: Int, step: DeploymentDependencyStep test fn the_devboot_store_root_is_an_ensured_host_resource_projecting_the_layout_row() -> Bool { fold( - deployment_ensured_steps(target: deployment_srv1_target()), + deployment_ensured_steps(instance: srv1_live_dashboard_instance(), target: deployment_srv1_target()), init: 0, f: (acc, step) => devboot_ensured_step_projects_layout(acc: acc, step: step), ) == 1 diff --git a/dag/test/claim/host/host_axis_caps_witness_test.dag b/dag/test/claim/host/host_axis_caps_witness_test.dag index b26a4571d08..227dad89db4 100644 --- a/dag/test/claim/host/host_axis_caps_witness_test.dag +++ b/dag/test/claim/host/host_axis_caps_witness_test.dag @@ -1,5 +1,6 @@ module test.claim.host_axis_caps +import extdeps.systemd.systemctl { systemctl_revert_memory_caps_argv } import std.logic { Bool } import std.types { NonEmptyStr, String, List, list_length } import gunbc.host_axis_caps { @@ -261,3 +262,12 @@ test fn witness_cap_upsert_script_carries_no_hand_spelled_printf_quoting() -> Bo !string_contains(s: cap_upsert_script(m: apostrophe_cap_member), pattern: "printf '%s' '") && !string_contains(s: deploy_runner_memory_cap_author_script(), pattern: "printf '%s' '") } + +// `systemctl revert` TAKES UNIT NAMES ONLY (extdeps.systemd.systemctl +// systemctl_revert_memory_caps_argv). The earlier argv appended "MemoryMax" "MemoryHigh", which +// systemctl read as two more units and reverted silently with exit 0 (observed, systemd 255). Every +// token after the verb must be the unit itself. +test fn the_revert_argv_names_only_the_unit() -> Bool { + join(systemctl_revert_memory_caps_argv(unit: "actions-runner@srv1-01.service"), " ") + == "systemctl revert actions-runner@srv1-01.service" +} diff --git a/dag/test/claim/live_deploy/desired_apply_selection_witness_test.dag b/dag/test/claim/live_deploy/desired_apply_selection_witness_test.dag new file mode 100644 index 00000000000..6ce48c16a59 --- /dev/null +++ b/dag/test/claim/live_deploy/desired_apply_selection_witness_test.dag @@ -0,0 +1,107 @@ +module test.claim.live_deploy.desired_apply_selection_witness_test + +import std.types { Bool, String, Int } +import gunbc.live_deploy.apply { + live_deploy_desired_apply_selection, + srv1_production_instance_id, + DesiredApplySelected, + DesiredApplyRefused, +} +import gunbc.live_deploy.desired { deployment_spec_srv1, selected_deployment_spec, srv1_lab_desired_deployment } +import gunbc.live_deploy.spec { + DeploymentSpec, DeploymentStep, Dependency, ArtifactStep, + FabricStorageStoreDirectory, TailscalePackage, TmuxPackage, DevbootArtifactStoreDirectory, + CodexRuntimeReleaseDirectory, ProviderStateRootDirectory, +} +import gunbc.ci_deploy_access { ci_deploy_srv1_operator_local_access } +import gunbc.roadmap_dashboard_instance { srv1_dashboard_lab_id, srv2_deploy_dashboard_id } + +// THE ONE APPLY ROUTE SELECTS BY INSTANCE ID (gunbc.live_deploy.apply +// live_deploy_desired_apply_selection). This claim's subject is the SELECTION: the lab id yields +// the lab roster row's spec, never production's. What that spec emits -- its own unit, port, +// slice and repo, disjoint from production -- is witnessed at the emitter's interface by +// test.claim.live_deploy.desired_roster_witness_test selecting_lab_changes_emitted_unit_bytes and +// same_host_production_and_lab_do_not_share_port_unit_or_handler, over the same +// selected_deployment_spec(srv1_lab_desired_deployment()) value this asserts was chosen; rendering +// the apply program here would re-pay that emission (~1.3M eval steps measured) per claim. +test fn the_lab_instance_id_selects_the_lab_spec_and_never_the_production_unit() -> Bool { + match live_deploy_desired_apply_selection( + instance_id: srv1_dashboard_lab_id as String, + access: ci_deploy_srv1_operator_local_access, + ) { + DesiredApplyRefused { reason: _ } => false + DesiredApplySelected { spec } => lab_spec_selected(spec: spec) + } +} + +fn lab_spec_selected(spec: DeploymentSpec) -> Bool { + let lab = selected_deployment_spec(row: srv1_lab_desired_deployment()) + (spec.instance_id as String) == (srv1_dashboard_lab_id as String) + && (spec.service.unit_name as String) == (lab.service.unit_name as String) + && (spec.service.repo_root as String) == (lab.service.repo_root as String) + && (spec.service.unit_name as String) != (deployment_spec_srv1().service.unit_name as String) + && (spec.service.unit_name as String) != "gunbc-roadmap.service" +} + +// Positive control for the production entries, which now route through the same selection: +// production's id must yield exactly deployment_spec_srv1, so the byte oracles over that spec +// keep describing what the production entry applies. +test fn the_production_instance_id_selects_deployment_spec_srv1() -> Bool { + match live_deploy_desired_apply_selection( + instance_id: srv1_production_instance_id(), + access: ci_deploy_srv1_operator_local_access, + ) { + DesiredApplyRefused { reason: _ } => false + DesiredApplySelected { spec } => + (spec.instance_id as String) == (deployment_spec_srv1().instance_id as String) + && (spec.service.unit_name as String) == (deployment_spec_srv1().service.unit_name as String) + && (spec.service.repo_root as String) == (deployment_spec_srv1().service.repo_root as String) + } +} + +test fn an_unknown_instance_id_refuses_with_its_typed_cause() -> Bool { + match live_deploy_desired_apply_selection( + instance_id: "no-such-dashboard-instance", + access: ci_deploy_srv1_operator_local_access, + ) { + DesiredApplySelected { spec: _ } => false + DesiredApplyRefused { reason } => string_contains(s: reason, pattern: "DesiredDeploymentUnknownInstanceId") + } +} + +// A rostered row on ANOTHER host refuses under srv1's access rather than mutating srv1 with it. +test fn a_row_on_another_host_refuses_under_srv1_access() -> Bool { + match live_deploy_desired_apply_selection( + instance_id: srv2_deploy_dashboard_id as String, + access: ci_deploy_srv1_operator_local_access, + ) { + DesiredApplySelected { spec: _ } => false + DesiredApplyRefused { reason } => string_contains(s: reason, pattern: "DesiredApplyHostMismatch") + } +} + +// THE FABRIC STORE IS THE PLACED INSTANCE'S (gunbc.live_deploy.spec fabric_storage_ensured_steps). +// The lab row's step list carries no FabricStorageStoreDirectory member; production's does. +fn is_fabric_store_step(st: DeploymentStep) -> Bool { + match st { + Dependency { step } => + match step.kind { + FabricStorageStoreDirectory => true + TailscalePackage => false + TmuxPackage => false + DevbootArtifactStoreDirectory => false + CodexRuntimeReleaseDirectory => false + ProviderStateRootDirectory => false + } + ArtifactStep { step: _ } => false + } +} + +fn fabric_store_step_count(spec: DeploymentSpec) -> Int { + count(filter(xs: spec.steps, predicate: st => is_fabric_store_step(st: st))) +} + +test fn the_lab_row_does_not_converge_the_fabric_store_and_production_does() -> Bool { + fabric_store_step_count(spec: selected_deployment_spec(row: srv1_lab_desired_deployment())) == 0 + && fabric_store_step_count(spec: deployment_spec_srv1()) > 0 +} diff --git a/dag/test/claim/roadmap/roadmap_dashboard_instance_witness_test.dag b/dag/test/claim/roadmap/roadmap_dashboard_instance_witness_test.dag index d2b368b2630..b366e8ded37 100644 --- a/dag/test/claim/roadmap/roadmap_dashboard_instance_witness_test.dag +++ b/dag/test/claim/roadmap/roadmap_dashboard_instance_witness_test.dag @@ -412,7 +412,7 @@ test fn dashboard_source_capture_has_no_manual_await_step() -> Bool { anchor_ref, } => controller_repo == srv1_dashboard_controller_repo - && anchor_ref == "refs/heads/session/codex-provider-feedback-v0" + && anchor_ref == "refs/remotes/origin/main" _ => false }) && any(plan, op => match op { @@ -537,8 +537,8 @@ fn witness_synthetic_instance() -> HostDashboardInstance { // page all read correctly — no refusal, just unintended content rendered plausibly. Unlike the host // facts, which produced identical values on both hosts and therefore no wrong output at all, this // one produces a page that looks right and is not.\n\nThe claim asserts the anchors are -// per-instance and DIFFER, so collapsing them back to one shared literal fails whichever way it -// collapses. +// per-instance: the synthetic instance's own anchor fails if the constructor collapses back to one +// shared literal. Two labs may lawfully follow the same ref (srv1 and srv2 labs both follow main). test fn source_anchor_is_per_instance_not_one_shared_literal() -> Bool { let synthetic = instance_anchor_ref(i: witness_synthetic_instance()) @@ -546,8 +546,7 @@ test fn source_anchor_is_per_instance_not_one_shared_literal() -> Bool { let srv1_lab = instance_anchor_ref(i: srv1_lab_dashboard_instance()) synthetic == "refs/heads/witness-anchor" && srv2_lab == "refs/remotes/origin/main" - && srv1_lab == "refs/heads/session/codex-provider-feedback-v0" - && !(srv2_lab == srv1_lab) + && srv1_lab == "refs/remotes/origin/main" } // serve_binary MOVED OFF THE LAYOUT AND THIS CLAIM CAUGHT IT, which is the whole reason it exists.