diff --git a/.github/workflows/fleet-converge.yml b/.github/workflows/fleet-converge.yml index 17a5390c711..62b9219f677 100644 --- a/.github/workflows/fleet-converge.yml +++ b/.github/workflows/fleet-converge.yml @@ -1,3 +1,5 @@ +# Generated by gunbc.fleet_converge_workflow expected_fleet_converge_yml — do not hand-edit. +# Authority: gunbc.fleet_converge_workflow fleet_converge_workflow; regen via tools.generated_artifact_gate main_wet. name: fleet-converge run-name: fleet-converge ${{ inputs.mode }} ${{ inputs.host }} nonce=${{ inputs.transaction_nonce }} on: diff --git a/.github/workflows/fleet-desired.yml b/.github/workflows/fleet-desired.yml index a953e1c18b9..ead9c02ac51 100644 --- a/.github/workflows/fleet-desired.yml +++ b/.github/workflows/fleet-desired.yml @@ -1,3 +1,5 @@ +# Generated by gunbc.fleet_desired_admission_workflow expected_fleet_desired_yml — do not hand-edit. +# Authority: gunbc.fleet_desired_admission_workflow fleet_desired_admission_workflow; regen via tools.generated_artifact_gate main_wet. name: fleet-desired on: workflow_run: diff --git a/.github/workflows/witnesses.yml b/.github/workflows/witnesses.yml index 0e014fdff2c..dc3f09bace0 100644 --- a/.github/workflows/witnesses.yml +++ b/.github/workflows/witnesses.yml @@ -1,3 +1,5 @@ +# Generated by gunbc.witness_floor_workflow expected_witness_floor_yml — do not hand-edit. +# Authority: gunbc.witness_floor_workflow witness_floor_workflow; regen via tools.generated_artifact_gate main_wet. name: witnesses on: workflow_dispatch: diff --git a/dag/gunbc/fleet/fleet_converge_workflow.dag b/dag/gunbc/fleet/fleet_converge_workflow.dag index 44ffa761fc5..6c0524f1462 100644 --- a/dag/gunbc/fleet/fleet_converge_workflow.dag +++ b/dag/gunbc/fleet/fleet_converge_workflow.dag @@ -860,7 +860,9 @@ fn fleet_converge_yml_after_toolchain_admission() -> FleetConvergeYamlGeneration JobTimeoutExceedsCeiling { minutes: _, ceiling: _ } => FleetConvergeYamlGenerationRefused { reason: "fleet-converge job backstop timeout exceeds extdeps.github.actions max_job_timeout_minutes — split the job or reduce a tier's per-step budget, do not raise past the platform ceiling (see gunbc_ci_fleet_job_backstop_timeout_note in gunbc.fleet_workflow_steps)" } JobTimeoutWithinCeiling { minutes: _ } => - FleetConvergeYamlGenerated { content: serialize_yaml(v: project_workflow_to_yaml(workflow: fleet_converge_workflow)) } + FleetConvergeYamlGenerated { + content: serialize_yaml(v: project_workflow_to_yaml(workflow: fleet_converge_workflow)) + } } } else { FleetConvergeYamlGenerationRefused { reason: fleet_converge_capability_closure_refusal } diff --git a/dag/gunbc/generated_artifact.dag b/dag/gunbc/generated_artifact.dag index de380f63f8b..49208ea381f 100644 --- a/dag/gunbc/generated_artifact.dag +++ b/dag/gunbc/generated_artifact.dag @@ -222,4 +222,3 @@ fn artifact_eq(a: GeneratedArtifact, b: GeneratedArtifact) -> Bool { fn registry_contains(a: GeneratedArtifact) -> Bool { any(generated_artifact_registry, x => artifact_eq(a: x, b: a)) } - diff --git a/dag/gunbc/generated_artifact_emit.dag b/dag/gunbc/generated_artifact_emit.dag index ab400212a0e..acf7691ebd0 100644 --- a/dag/gunbc/generated_artifact_emit.dag +++ b/dag/gunbc/generated_artifact_emit.dag @@ -10,8 +10,16 @@ import gunbc.generated_artifact { Stage0CratePartitionGeneratedDagArtifact, Stage0ExecutableAssemblyGeneratedDagArtifact, V1InterpreterDispatchGeneratedRsArtifact, artifact_path, + artifact_commit_policy, + CommitRequired, NotCommitted, + GitProtocol, GithubActionsWorkflow, ProjectDocumentation, HostReconciler, committed_generated_artifacts } +import gunbc.generated_workflow_provenance { + generated_workflow_provenance_header, + GeneratedWorkflowProvenanceHeaderProduced, + GeneratedWorkflowProvenanceHeaderNotApplicable, +} import gunbc.fleet_converge_workflow { expected_fleet_converge_yml, FleetConvergeYamlGenerated, FleetConvergeYamlGenerationRefused } import gunbc.fleet_desired_admission_workflow { expected_fleet_desired_yml, FleetDesiredGenerationOutcome, FleetDesiredGenerated, FleetDesiredGenerationRefused } import gunbc.witness_floor_workflow { expected_witness_floor_yml, WitnessFloorGenerationOutcome, WitnessFloorGenerated, WitnessFloorGenerationRefused } @@ -54,7 +62,7 @@ fn artifact_generated(content: String) -> ArtifactGenerationOutcome { ArtifactGenerated { content: content } } -fn artifact_generate(a: GeneratedArtifact) -> ArtifactGenerationOutcome { +fn artifact_generate_unadorned(a: GeneratedArtifact) -> ArtifactGenerationOutcome { match a { WitnessFloorYamlArtifact => match expected_witness_floor_yml() { WitnessFloorGenerated { content } => artifact_generated(content: content) @@ -110,6 +118,29 @@ fn artifact_generate(a: GeneratedArtifact) -> ArtifactGenerationOutcome { } } +// One bytes boundary owns workflow provenance. A workflow admitted by the commit-policy registry +// without a provenance projection refuses here; non-workflow artifacts pass through unchanged. +// This is a rung-3 wall, not a rung-4 constructor removal: the inconsistent pair remains writable +// in the two authorities, but it cannot produce committed workflow bytes. +fn artifact_generate(a: GeneratedArtifact) -> ArtifactGenerationOutcome { + let generated = artifact_generate_unadorned(a: a) + match generated_workflow_provenance_header(a: a) { + GeneratedWorkflowProvenanceHeaderProduced { content: header } => match generated { + ArtifactGenerated { content } => artifact_generated(content: concat(header, content)) + ArtifactGenerationRefused { reason } => ArtifactGenerationRefused { reason: reason } + } + GeneratedWorkflowProvenanceHeaderNotApplicable => match artifact_commit_policy(a: a) { + CommitRequired { consumer: GithubActionsWorkflow } => ArtifactGenerationRefused { + reason: "GithubActionsWorkflow artifact has no generated-workflow provenance projection" + } + CommitRequired { consumer: GitProtocol } => generated + CommitRequired { consumer: ProjectDocumentation } => generated + CommitRequired { consumer: HostReconciler } => generated + NotCommitted => generated + } + } +} + // 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, diff --git a/dag/gunbc/generated_workflow_provenance.dag b/dag/gunbc/generated_workflow_provenance.dag new file mode 100644 index 00000000000..2faff8a6840 --- /dev/null +++ b/dag/gunbc/generated_workflow_provenance.dag @@ -0,0 +1,53 @@ +module gunbc.generated_workflow_provenance + +import std.types { String } +import gunbc.generated_artifact { + GeneratedArtifact, + WitnessFloorYamlArtifact, + FleetConvergeYamlArtifact, + FleetDesiredAdmissionYamlArtifact, +} + +// A generated workflow is reviewed at the bytes boundary, where its producing .dag graph is not +// visible. Keep the provenance in that boundary rather than asking a reviewer to infer it from the +// path or from a separate generated-artifact roster. The generator and authority are parameters +// because they are different facts; the header's spelling alone is shared. +type GeneratedWorkflowProvenanceHeader + = GeneratedWorkflowProvenanceHeaderProduced { content: String } + | GeneratedWorkflowProvenanceHeaderNotApplicable + +type GeneratedWorkflowProvenance { + generator: String + authority: String +} + +fn generated_workflow_provenance(a: GeneratedArtifact) -> GeneratedWorkflowProvenance? { + match a { + WitnessFloorYamlArtifact => Present { value: GeneratedWorkflowProvenance { + generator: "gunbc.witness_floor_workflow expected_witness_floor_yml", + authority: "gunbc.witness_floor_workflow witness_floor_workflow", + } } + FleetConvergeYamlArtifact => Present { value: GeneratedWorkflowProvenance { + generator: "gunbc.fleet_converge_workflow expected_fleet_converge_yml", + authority: "gunbc.fleet_converge_workflow fleet_converge_workflow", + } } + FleetDesiredAdmissionYamlArtifact => Present { value: GeneratedWorkflowProvenance { + generator: "gunbc.fleet_desired_admission_workflow expected_fleet_desired_yml", + authority: "gunbc.fleet_desired_admission_workflow fleet_desired_admission_workflow", + } } + _ => none + } +} + +fn generated_workflow_provenance_header(a: GeneratedArtifact) -> GeneratedWorkflowProvenanceHeader { + match generated_workflow_provenance(a: a) { + Absent => GeneratedWorkflowProvenanceHeaderNotApplicable + Present { value: provenance } => GeneratedWorkflowProvenanceHeaderProduced { + content: join([ + concat("# Generated by ", concat(provenance.generator, " — do not hand-edit.")), + concat("# Authority: ", concat(provenance.authority, "; regen via tools.generated_artifact_gate main_wet.")), + "" + ], "\n") + } + } +} diff --git a/dag/gunbc/non_fold_residue.dag b/dag/gunbc/non_fold_residue.dag index ceb983cc4e3..3eabc4991cd 100644 --- a/dag/gunbc/non_fold_residue.dag +++ b/dag/gunbc/non_fold_residue.dag @@ -154,6 +154,10 @@ data nfr_reason_acceptance_verdict_append: String = "one-variant append over Dag data nfr_dissolve_acceptance_verdict_append: DissolutionCondition = unbound_dissolution(description: "the verdict is assembled once from the receipt's derived outcome partition (first and further labels computed together) rather than appended one label at a time, so the two append fns have no caller and delete with their rows — or the two matches become total over the three verdict arms") +data nfr_reason_generated_workflow_provenance_projection: String = "subset projection over GeneratedArtifact: the three workflow artifact identities carry provenance and every non-workflow artifact shares one Absent arm. Enumerating the non-workflow variants would copy a live population beside the generated-artifact commit-policy registry. The production join in generated_artifact_emit refuses GeneratedWorkflowProvenanceHeaderNotApplicable whenever artifact_commit_policy says GithubActionsWorkflow, so omission on a future workflow admission is refused even though the inconsistent pair remains writable in the two authorities. This join does not adjudicate whether a present projected generator agrees with the actual producer; that disagreement class remains open until the projection is fused with generation. Declared when the required nfr_roster_receipt first exercised the new projection on #9954, so the ratchet re-arms with the site." + +data nfr_dissolve_generated_workflow_provenance_projection: DissolutionCondition = unbound_dissolution(description: "the generated-artifact registry admission for GithubActionsWorkflow carries provenance fused with its generating dispatch, so the artifact-visible citation is derivable only from the actual producer and neither an omitted nor a disagreeing provenance row is constructible; emission consumes that refined workflow-artifact declaration without projecting from the full GeneratedArtifact coproduct, and this wildcard match deletes with its row") + data non_fold_residue_frontier: List = [ FrontierRow { subject: PathSubject { path: "dag/gunbc/dispatch_selection.dag::offer_matches_request_shape" }, @@ -355,6 +359,11 @@ data non_fold_residue_frontier: List = [ reason: nfr_reason_land_red_era, dissolution: nfr_dissolve_owning_fold, }, + FrontierRow { + subject: PathSubject { path: "dag/gunbc/generated_workflow_provenance.dag::generated_workflow_provenance" }, + reason: nfr_reason_generated_workflow_provenance_projection, + dissolution: nfr_dissolve_generated_workflow_provenance_projection, + }, FrontierRow { subject: PathSubject { path: "dag/gunbc/commit_workflow.dag::commit_workflow_surface_eq" }, reason: nfr_reason_land_red_era, diff --git a/dag/gunbc/witness/witness_floor_workflow.dag b/dag/gunbc/witness/witness_floor_workflow.dag index ad75c9f9906..87b788baa16 100644 --- a/dag/gunbc/witness/witness_floor_workflow.dag +++ b/dag/gunbc/witness/witness_floor_workflow.dag @@ -2052,7 +2052,9 @@ fn expected_witness_floor_yml() -> WitnessFloorGenerationOutcome { WitnessFloorGenerationRefused { reason: toolchain_home_refusal_reason(job_id: j, refusal: r) } WorkflowToolchainHomesAdmitted => if witness_floor_capability_closure_holds() && build_lane_capability_closure_holds() && rust_unit_tests_capability_closure_holds() && fabric_evidence_capability_closure_holds() && emit_copy_qualification_capability_closure_holds() && required_lanes_gate_is_renderable() { - WitnessFloorGenerated { content: serialize_yaml(v: project_workflow_to_yaml(workflow: witness_floor_workflow)) } + WitnessFloorGenerated { + content: serialize_yaml(v: project_workflow_to_yaml(workflow: witness_floor_workflow)) + } } else { WitnessFloorGenerationRefused { reason: witness_floor_capability_closure_refusal_reason } }