diff --git a/INVARIANTS.md b/INVARIANTS.md index 6bf9e09065c..15369296399 100644 --- a/INVARIANTS.md +++ b/INVARIANTS.md @@ -341,6 +341,7 @@ Per **Dispatch-Discipline Mechanisms (b)** above, each **new** path added to `EX | `src/v3/compiler/tests/integration/ctrl_pr_digests_dag_smoke_test.rs` | **Project plan:** `docs/r4-ctrl-dag-migration-project-plan.md` §3 — catalog **#8** `dsl/ctrl/pr_digests.dag` (Wave-1 ctrl → `.dag` subsystem modeling). **PR receipt (P5 Mechanism (b)):** this INVARIANTS row + the matching `EXPECTED_HAND_AUTHORED_TEST` line in `sg0_census_test.rs` land in the same PR as the smoke test. **Dissolution:** remove when `compile_to_dag` (or a single generated harness) validates `module … service …` ctrl carrier files end-to-end without a parallel Rust string/lexer ratchet, or when the contract migrates to `.dag` `TestClaim` coverage. **Interim ratchet:** `ctrl_pr_digests_dag_tokenizes_and_matches_expected_surface` requires clean tokenization plus presence of `module ctrl.pr_digests`, `import extdeps.github.pulls { PullRequest, PullReview, ReviewComment }`, `std.errors` / `std.types` imports, the four Practice-4 sum/record carriers (with `🟡 STAGED` / `🟢 TERMINAL` markers per `dsl/ctrl/README.md`), `ReviewCommentBody` + `review_line_comments` wiring, and `service ctrl.PrDigests` / the four `operation` blocks / `readonly`. | | `src/v3/compiler/tests/integration/extdeps_sql_transport_test.rs` | **ROADMAP:** `ROADMAP.md` § **Nine lanes** row `T-PB-B` / `pb_rust_tests_outside_residual_zero`; this Rust integration receipt keeps HTTP/SQL/audit extdeps compiled by the existing v3 parser before downstream emission-target consumers rely on them. **Dissolution:** remove when extdeps transport and Phase 3 emission-target files are covered by a `.dag`-native parse/authority suite or generated test harness rather than per-file Rust `compile_to_dag` probes. **Interim ratchet:** `rest_transport_dag_compiles_cleanly` and `sql_transport_dag_compiles_cleanly` pin `dsl/extdeps/transports/rest.dag` and `dsl/extdeps/transports/sql.dag`; `http_server_extdep_dag_compiles_cleanly`, `sql_migration_extdep_dag_compiles_cleanly`, and `audit_event_extdep_dag_compiles_cleanly` pin `dsl/extdeps/http/server.dag`, `dsl/extdeps/sql/migration.dag`, and `dsl/extdeps/audit/event.dag` as parseable staged emission-target substrate. The field-sensitive companions (`http_server_target_fields_are_authoritative_substrate_edges`, `sql_migration_target_fields_bound_raw_sql_scaffold`, `audit_event_target_fields_preserve_cloudevents_core_names`) consume the new target-contract fields directly so they fail on raw `String`/`Int` regressions, missing SQL scaffold bounds, or CloudEvents alias drift while the first real projection consumer is still staged; the audit ratchet also locks CloudEvents core fields to branded carriers and `std.types.Timestamp` rather than raw strings. | | `src/v3/compiler/tests/integration/file_attachment_substrate_carrier_test.rs` | **R3 program plan:** `docs/r3-program-plan.md` §1.8 gate **#62** `substrate_gap_file_ingestion_closed` (T-Workflow-As-Data substrate-gap class; worker brief `docs/briefs/r3-substrate-gate-62-file-attachment-carrier-worker.md`). **Dissolution:** remove when a `.dag` `TestClaim` / PB-B-1 runner receipt can assert `FileAttachment` field names + cross-module nominal wiring against `generated_full_bootstrap_dag()` without this hand-Rust structural ratchet (same dissolution posture as `timing_lens_substrate_carrier_test.rs` for gate #55). **Interim ratchet:** `file_attachment_shape_locked` + `file_attachment_field_types_locked` + `file_attachment_field_count_is_five` pin the ratified Refined-B-1 five-field subset of `WorkflowObservationAnchor` (`NodeId`, `ContentHash`, `WorkflowProducerId`, `WorkflowRunId`, `Nanoseconds`) exactly as declared in `src/v3/std/timing_lens.dag`; existence proof carrier construction stays in-module as `gate_62_file_attachment_demo_record`. | +| `src/v3/compiler/tests/integration/workflow_substrate_carriers_test.rs` | **R3 program plan:** `docs/r3-program-plan.md` §1.8 gate **#53** `workflow_substrate_carriers_landed` (T-Workflow-As-Data Slice 1; worker brief `docs/briefs/r3-substrate-t-workflow-as-data-slice-1-worker.md`). **Dissolution:** remove when a `.dag` `TestClaim` / PB-B-1 runner receipt can assert `WorkflowSecret`, `SecretScope`, `CronSchedule`, and `CronField` structural shape against `generated_full_bootstrap_dag()` without this hand-Rust ratchet. **Interim ratchet:** `workflow_secret_shape_locked` + `secret_scope_variants_locked` + `cron_schedule_shape_locked` + `cron_field_variants_locked` pin the Slice 1 β-ratified carriers (STOP+PING discipline on `CronSchedule` field-count drift >5 per brief). | | `src/v3/compiler/tests/integration/m2_substrate_inhabitance_test.rs` | **ROADMAP:** `ROADMAP.md` — **Tier-3 perf-budget Phase-1 hand-Rust mirror benches** / T-Tier3-Dissolution paired-dispatch (`docs/briefs/r3-pb-t-tier3-consumer-slice-worker.md`). **Plan:** `docs/r3-program-plan.md` §1.8 gate **#2** `tier3_computation_mirror_dissolved` (state-check; **CONSUMER_LANDED** = §1.7 executable-consumer sense, not INVARIANTS §P2 generated-consumer — see plan §1.7 “State-check …” paragraph). **Dissolution:** retire remaining Tier3 host mirrors per `docs/r3-structure.md` §Acceptance as Evaluator-backed std bodies land. **Interim ratchet (P5 Mechanism (b)):** `tier3_computation_mirror_trivial_constructors_dissolved` (PR #2789 narrow slice) + `tier3_computation_mirror_kernel_algebra_profile_substrate_authority` with brace-bounded `dag.rs` scan asserting `type_iteration_dimension` delegates through `BOOTSTRAPPED_DAG.kernel_algebra_profile` (forbid parallel hand-maintained type→profile tables); sibling Tier3 rows in this file carry the same lane receipts. | | `src/v3/compiler/tests/integration/r3_gate_60_phase2_width_nat_parser_test.rs` | **ROADMAP:** `ROADMAP.md` — **`substrate_gap_parser_grammar_closed`** / R3 gate **#60** (Phase 2.1 parser slice: angle-only width nat in `<…>`, `TypeAngleArg` substrate split, `Compose>` lowering with literal phantom width). **Dissolution:** remove when gate #60 parse/lower/routing receipts are carried by `.dag` `TestClaim` / generated harness without this hand-authored `compile_to_dag` + `parse_for_test` module (per `docs/audit/r3-gate-60-decomposition.md` follow-on slices). **Interim ratchet:** `gate_60_phase2_parse_accepts_algebra_angle_width_nat`, `gate_60_phase2_int_64_lowers_to_compose_int_machine_width_literal`, `gate_60_phase2_nat_8_lowers_via_uint_slot`, `gate_60_integer_routing_witness_accepts_literal_nat_machine_width`, `gate_60_bare_numeric_type_is_parse_rejected`, `gate_60_int_disallowed_width_fails_closed_without_malformed_template_args`. | | `src/v3/compiler/tests/integration/t_gate_58_apply_lens_self_application_test.rs` | **ROADMAP:** `ROADMAP.md` → `### Nine lanes` → **T-PB-B** / `pb_rust_tests_outside_residual_zero`. **Plan:** `docs/r3-program-plan.md` §1.8 gate **#58** `apply_lens_self_application_demonstrated` — lane **T-Lens-Self-Application** (`EnforcedApplication` bootstrap receipt over `gate_58_apply_lens_self_application_pass` + typed witness `gate_58_modeled_ci_timing_measurement` (`gate_58_ci_workflow_timing_row` with `workflow: modeled_gunbc_ci_workflow`) in `src/v3/std/t_ci_workflow_as_data_demo.dag`). **Dissolution:** remove when a `.dag` `TestClaim` / runner receipt asserts the same `generated_full_bootstrap_dag()` facts (empty bootstrap diagnostics + witness declarations) without this Rust harness. **Interim ratchet:** `apply_lens_self_application_demonstrated_bootstrap_receipt` + `gate_58_modeled_ci_timing_measurement` presence checks. **Co-receipt (same PR, P5 single surface):** expanded `src/v3/compiler/src/enforced_lens_application.rs` timing consumer + `src/v3/compiler/build.rs` default-rank std `STAGED_FILES` ordering from parsed `import v3.std.*` edges (Kahn topo among co-ranked files; single authority with module imports) are documented in `scripts/ci-merge/sg0-pr-body-append.2827.txt` (prepended to the PR body in CI) with explicit ROADMAP rows + dissolution triggers per INVARIANTS §P5 Mechanism **(b)**. | diff --git a/docs/r3-program-plan.md b/docs/r3-program-plan.md index 32a86ecffca..2b5e80ad12c 100644 --- a/docs/r3-program-plan.md +++ b/docs/r3-program-plan.md @@ -277,7 +277,7 @@ This principle is NOT a separate lane; it's a per-lane gate-shape requirement ap | 50 | `auto_memoization_no_caching_for_one_shot` | demonstration | T-Free-Consequences-Demonstration | **PASSING** (PR #2547 — one-shot memoization witness landed 2026-05-10) | no scaffolding for single-call | | 51 | `cross_target_optimization_constant_fold_consistent` | structural-fold | T-Free-Consequences-Demonstration | **PASSING** (PR #2577 — constant-fold cost witness executable landed 2026-05-10) | structural cost-shrink across targets | | 52 | `cross_target_optimization_cost_structurally_derived` | structural-fold | T-Free-Consequences-Demonstration | **PASSING** (PR #2578 — structural cost receipt landed 2026-05-10) | cost-lens reading structurally derived | -| 53 | `workflow_substrate_carriers_landed` | substrate-shape | T-Workflow-As-Data | **CONSUMER_LANDED (partial)** (PR #2160 — WorkflowSecret + CronExpression β-ratified; refresh per cluster-analysis audit §1; remaining sub-carriers tracked in T-WAD slice queue) | `std.workflow` carriers | +| 53 | `workflow_substrate_carriers_landed` | substrate-shape | T-Workflow-As-Data | **CONSUMER_LANDED (partial)** (PR #2160 — WorkflowSecret + CronExpression β-ratified; refresh per cluster-analysis audit §1; remaining sub-carriers tracked in T-WAD slice queue). **Structural ratchet receipt (2026-05-14)**: `src/v3/compiler/tests/integration/workflow_substrate_carriers_test.rs` locks Slice 1 carriers against the full bootstrap Dag — `WorkflowSecret { name: SecretName, scope: SecretScope }`, `SecretScope = StepScope \| JobScope \| WorkflowScope`, `CronSchedule` (5 typed fields), `CronField` (5 variants). Sibling-carrier shape with gate #62 `file_attachment_substrate_carrier_test.rs`; STOP+PING discipline on field-count drift per brief. | `std.workflow` carriers | | 54 | `timing_lens_carrier_landed` | substrate-shape | T-Workflow-As-Data | **CONSUMER_LANDED** (PR #2360 — Substrate T-Workflow-As-Data timing-lens carrier post-T-LBP COMPLETE) | `Lens` carrier | | 55 | `shared_external_attachment_pattern_documented` | substrate-shape | T-Workflow-As-Data | **CONSUMER_LANDED** — pattern + six invariants in [`docs/design-timing-lens.md`](design-timing-lens.md) §2; typed `WorkflowObservationAnchor` / `TimingObservationEntry` in `src/v3/std/timing_lens.dag`; branded provenance nominals in `dsl/std/types.dag`; bootstrap ratchet `timing_lens_substrate_carrier_test.rs` (gate #55 inv.1–4 + §2.6 validate wiring) | `WorkflowObservationAnchor` + 6 invariants documented + typed | | 56 | `ci_workflow_modeled_as_dag` | demonstration | T-Workflow-As-Data | DECLARED | at least one workflow as `.dag` data | diff --git a/src/v3/compiler/tests/integration.rs b/src/v3/compiler/tests/integration.rs index 69c5ad7e6d5..c402e23cec1 100644 --- a/src/v3/compiler/tests/integration.rs +++ b/src/v3/compiler/tests/integration.rs @@ -257,6 +257,8 @@ mod value_body_substrate_mirror_isomorphism_test; mod wiring_scanner_test; #[path = "integration/workflow_root_port_test.rs"] mod workflow_root_port_test; +#[path = "integration/workflow_substrate_carriers_test.rs"] +mod workflow_substrate_carriers_test; mod t_demo_fixture_test { //! **Layer:** integration diff --git a/src/v3/compiler/tests/integration/sg0_census_test.rs b/src/v3/compiler/tests/integration/sg0_census_test.rs index 0f73d914170..a401a59bcb0 100644 --- a/src/v3/compiler/tests/integration/sg0_census_test.rs +++ b/src/v3/compiler/tests/integration/sg0_census_test.rs @@ -771,6 +771,14 @@ const EXPECTED_HAND_AUTHORED_TEST: &[&str] = &[ // `.dag` `TestClaim` form when testgen covers compile-and-fold // structural assertions. "src/v3/compiler/tests/integration/workflow_root_port_test.rs", + // R3 §1.8 gate #53 `workflow_substrate_carriers_landed` structural + // ratchet: locks Slice 1 β-ratified carriers (`WorkflowSecret`, + // `SecretScope`, `CronSchedule`, `CronField`) against the full + // bootstrap Dag. Sibling shape to gate #62 + // `file_attachment_substrate_carrier_test.rs`. Dissolves into + // `.dag` `TestClaim` form when testgen covers structural-shape + // assertions over substrate carriers. + "src/v3/compiler/tests/integration/workflow_substrate_carriers_test.rs", ]; // Non-`.rs` scaffold fragments under `src/v3/compiler/` that are diff --git a/src/v3/compiler/tests/integration/workflow_substrate_carriers_test.rs b/src/v3/compiler/tests/integration/workflow_substrate_carriers_test.rs new file mode 100644 index 00000000000..26ad49a5809 --- /dev/null +++ b/src/v3/compiler/tests/integration/workflow_substrate_carriers_test.rs @@ -0,0 +1,148 @@ +//! **Layer:** integration +//! +//! Structural acceptance for R3 §1.8 gate #53 `workflow_substrate_carriers_landed`: +//! Slice 1 workflow substrate carriers landed at `dsl/extdeps/github/actions.dag` + +//! `dsl/extdeps/cron_schedule_model.dag` per Director β-ratification at +//! gunbc#828 #issuecomment-4395945465 (PR #2160). Carriers ratcheted here: +//! +//! - `WorkflowSecret { name: SecretName, scope: SecretScope }` (provider-typed, +//! opaque-at-rest secret reference scoped by step / job / workflow) +//! - `SecretScope = StepScope | JobScope | WorkflowScope` +//! - `CronSchedule` (5 typed fields — minute / hour / day_of_month / month / day_of_week) +//! - `CronField` (5 variants — Wildcard / Exact / Listed / Ranged / Step) +//! +//! Sibling-carrier ratchet to `file_attachment_substrate_carrier_test.rs` (gate #62). + +use v3_compiler::dag::{Dag, DeclarationId, TypeConnective}; +use v3_compiler::generated_full_bootstrap_dag; + +fn conj_field_labels(dag: &Dag, name: &str) -> Vec { + let decl = dag + .declaration_by_name(name) + .unwrap_or_else(|| panic!("`{name}` missing from full bootstrap")); + match &decl.connective { + TypeConnective::Conj { children } => children.iter().map(|f| f.label.clone()).collect(), + other => panic!("`{name}` is not a Conj: {other:?}"), + } +} + +fn conj_field_ty(dag: &Dag, name: &str, field: &str) -> DeclarationId { + let decl = dag + .declaration_by_name(name) + .unwrap_or_else(|| panic!("`{name}` missing from full bootstrap")); + match &decl.connective { + TypeConnective::Conj { children } => { + children + .iter() + .find(|f| f.label == field) + .unwrap_or_else(|| panic!("`{name}` missing `{field}` field")) + .ty + } + other => panic!("`{name}` is not a Conj: {other:?}"), + } +} + +fn disj_variant_labels(dag: &Dag, name: &str) -> Vec { + let decl = dag + .declaration_by_name(name) + .unwrap_or_else(|| panic!("`{name}` missing from full bootstrap")); + match &decl.connective { + TypeConnective::Disj { variants } => variants.iter().map(|v| v.label.clone()).collect(), + other => panic!("`{name}` is not a Disj: {other:?}"), + } +} + +#[test] +fn workflow_secret_shape_locked() { + let dag = generated_full_bootstrap_dag(); + let mut labels = conj_field_labels(&dag, "WorkflowSecret"); + labels.sort(); + assert_eq!( + labels, + vec!["name".to_string(), "scope".to_string()], + "WorkflowSecret field set drifted from β-ratification (gate #53 / PR #2160)" + ); + + let secret_name = dag + .declaration_by_name("SecretName") + .expect("`SecretName` missing from full bootstrap (std.types authority)") + .id; + let secret_scope = dag + .declaration_by_name("SecretScope") + .expect("`SecretScope` missing from full bootstrap") + .id; + assert_eq!( + conj_field_ty(&dag, "WorkflowSecret", "name"), + secret_name, + "`WorkflowSecret.name` must be `SecretName` (cross-provider std.types authority)" + ); + assert_eq!( + conj_field_ty(&dag, "WorkflowSecret", "scope"), + secret_scope, + "`WorkflowSecret.scope` must be `SecretScope`" + ); +} + +#[test] +fn secret_scope_variants_locked() { + let dag = generated_full_bootstrap_dag(); + let mut variants = disj_variant_labels(&dag, "SecretScope"); + variants.sort(); + assert_eq!( + variants, + vec![ + "JobScope".to_string(), + "StepScope".to_string(), + "WorkflowScope".to_string(), + ], + "SecretScope variants drifted from β-ratification (gate #53)" + ); +} + +#[test] +fn cron_schedule_shape_locked() { + let dag = generated_full_bootstrap_dag(); + let mut labels = conj_field_labels(&dag, "CronSchedule"); + labels.sort(); + assert_eq!( + labels, + vec![ + "day_of_month".to_string(), + "day_of_week".to_string(), + "hour".to_string(), + "minute".to_string(), + "month".to_string(), + ], + "CronSchedule field set drifted (gate #53 STOP+PING — >5 fields requires Director ratification)" + ); + + let cron_field = dag + .declaration_by_name("CronField") + .expect("`CronField` missing from full bootstrap") + .id; + for field in ["minute", "hour", "day_of_month", "month", "day_of_week"] { + assert_eq!( + conj_field_ty(&dag, "CronSchedule", field), + cron_field, + "`CronSchedule.{field}` must be `CronField`" + ); + } +} + +#[test] +fn cron_field_variants_locked() { + let dag = generated_full_bootstrap_dag(); + let mut variants = disj_variant_labels(&dag, "CronField"); + variants.sort(); + assert_eq!( + variants, + vec![ + "Exact".to_string(), + "Listed".to_string(), + "Ranged".to_string(), + "Step".to_string(), + "Wildcard".to_string(), + ], + "CronField variants drifted (gate #53)" + ); +}