Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/release-gates.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"pending_migrations": [
{
"pull_request": "https://github.com/ai-dynamo/dynamo/pull/14065",
"requirement": "Refresh and merge Dynamo's Planner and wheel-smoke migration to best_available(config), with required worker_type, nested estimator_config, and the released-wheel compatibility bridge. Validate against the canonical wheel before clearing this gate and releasing the API."
"requirement": "Publish a canonical nightly wheel, then validate and merge Dynamo's Planner and wheel-smoke migration to best_available(config), with required worker_type, nested estimator_config, and the released-wheel compatibility bridge. Clear this gate before a stable API release; it must not block development nightly publication."
}
]
}
16 changes: 2 additions & 14 deletions .github/workflows/nightly-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -130,20 +130,8 @@ jobs:
}
}
core.setFailed('commit_sha is not on main or any release/* branch');
# Both scheduled and approved manual publication require completed migrations.
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
ref: ${{ steps.target.outputs.sha }}
persist-credentials: false
# Keep the checker/current policy trusted while also checking the exact
# publication target's migration declarations. Neither can bypass the other.
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
ref: ${{ github.sha }}
path: release-policy
persist-credentials: false
- name: Require completed downstream API migrations
run: python3 release-policy/scripts/check_release_migrations.py --target-gates .github/release-gates.json
# Downstream migrations consume this development wheel before they can
# merge. Their completion is a stable-release prerequisite, not a nightly gate.
# Computed once here so both arch legs stamp the same date even if the
# run crosses a UTC midnight.
- name: Compute unique nightly version
Expand Down
8 changes: 8 additions & 0 deletions docs/ci.md
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,14 @@ by its zero-padded ten-digit workflow run number, for example
retries retain the same version, and later dates sort after earlier dates. Builds
use pinned tooling and record checksums and provenance.

Development nightlies must be available before downstream consumers can validate
and merge an API migration. Pending entries in
[the stable-release migration checklist](../.github/release-gates.json) therefore
do not block scheduled or approved manual nightlies. Build, compliance, wheel-smoke,
FPE qualification, and security requirements continue to apply. Publish the nightly,
validate and merge the downstream migration against that wheel, then complete the
migration checklist before a stable release.

Python dependency licenses are checked in isolated jobs on both architectures
before building or staging. Artifacts are then staged directly to internal
Artifactory through the protected `automated-release` environment. Each wheel
Expand Down
18 changes: 7 additions & 11 deletions docs/core-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -328,18 +328,14 @@ past-KV coordinate rather than the op-level mean-context coordinate.
role/options signatures and separate estimator constructors. This is a source
migration: use `ForwardPassPerfModelConfig::new(...)` in Rust or the SDK config
class in Python, and use the explicit migration helper for saved EngineConfig
values. Downstream Dynamo callers must migrate before this API is released;
values. Downstream Dynamo callers must migrate before this API's stable release;
keep the crate and wheel versions aligned at the coordinated minor release.
- Publication is blocked by [the release gate](../.github/release-gates.json)
until [Dynamo #14065](https://github.com/ai-dynamo/dynamo/pull/14065) is refreshed,
merged, and its Planner/wheel smoke validated against this API. Both scheduled
nightly CI and approved manual dispatch run `scripts/check_release_migrations.py`
before staging and the downstream publish trigger. Manual dispatch may select
a main/release commit, but both the workflow revision's policy and the selected
commit's migration declarations must pass before publication. The checker runs
from the workflow revision; missing or malformed target gates fail closed.
Clear the pending entry in a reviewed change only after the migration evidence
is available.
- [The migration checklist](../.github/release-gates.json) and
`scripts/check_release_migrations.py` apply before stable publication. Clear the
pending entry in a reviewed change after downstream validation and merge.
There is currently no standalone stable-publication workflow in this repository;
that release process must invoke the checker for both its policy and target
declarations (`--target-gates`). Missing or malformed declarations fail closed.
- The raw PyO3 class and ergonomic SDK wrapper intentionally share the name
`RustForwardPassPerfModel`; callers should import from `aisimulate_core.sdk`
unless they specifically need the JSON-oriented native binding.
Expand Down
4 changes: 2 additions & 2 deletions scripts/check_release_migrations.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

"""Block release staging while source-controlled downstream migrations remain."""
"""Check downstream migrations before stable publication; not a nightly gate."""

from __future__ import annotations

Expand Down Expand Up @@ -30,7 +30,7 @@ def require_completed_migrations(path: Path = GATES) -> None:
raise ValueError("each release gate needs a pull_request and requirement")
if pending:
details = "\n".join(f"- {item['pull_request']}: {item['requirement']}" for item in pending)
raise RuntimeError(f"Release publication is blocked by pending downstream migrations:\n{details}")
raise RuntimeError(f"Stable release publication is blocked by pending downstream migrations:\n{details}")


def main(argv: list[str] | None = None) -> int:
Expand Down
44 changes: 24 additions & 20 deletions tests/test_ci_workflow_contracts.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
ACTION_ROOT = REPOSITORY_ROOT / ".github" / "actions"


def test_release_migration_gate_blocks_publication_until_reviewed_clearance(tmp_path):
def test_stable_release_migrations_require_reviewed_clearance(tmp_path):
from scripts.check_release_migrations import GATES, require_completed_migrations

with pytest.raises(RuntimeError, match="dynamo/pull/14065"):
Expand All @@ -56,25 +56,29 @@ def test_release_migration_gate_blocks_publication_until_reviewed_clearance(tmp_
with pytest.raises(FileNotFoundError):
require_completed_migrations(path)

jobs = _workflow("nightly-ci.yml")["jobs"]

def test_nightly_can_publish_the_wheel_needed_by_pending_downstream_migrations():
workflow = _workflow("nightly-ci.yml")
serialized_workflow = json.dumps(workflow)
assert "check_release_migrations.py" not in serialized_workflow
assert "release-gates.json" not in serialized_workflow
jobs = workflow["jobs"]
guard = jobs["changes-guard"]
index = next(i for i, step in enumerate(guard["steps"]) if "check_release_migrations.py" in step.get("run", ""))
assert next(i for i, step in enumerate(guard["steps"]) if step.get("id") == "target") < index
assert index < next(i for i, step in enumerate(guard["steps"]) if step.get("id") == "decide")
assert "if" not in guard["steps"][index]
checkouts = [step for step in guard["steps"][:index] if step.get("uses", "").startswith("actions/checkout@")]
assert len(checkouts) == 2
assert checkouts[0]["with"]["ref"] == "${{ steps.target.outputs.sha }}"
assert "path" not in checkouts[0]["with"]
assert checkouts[1]["with"]["ref"] == "${{ github.sha }}"
assert checkouts[1]["with"]["path"] == "release-policy"
assert all("if" not in step and step["with"]["persist-credentials"] == "false" for step in checkouts)
assert guard["steps"][index]["run"] == (
"python3 release-policy/scripts/check_release_migrations.py --target-gates .github/release-gates.json"
)
assert "build-artifacts" in jobs["trigger-gitlab-security"]["needs"]
assert "changes-guard" in jobs["build-artifacts"]["needs"]
assert "needs.changes-guard.outputs.should-build == 'true'" in jobs["build-artifacts"]["if"]
commands = "\n".join(_run_commands(job) for job in jobs.values() if "steps" in job)
assert "check_release_migrations.py" not in commands
assert "release-gates.json" not in commands
Comment thread
jasonqinzhou marked this conversation as resolved.
assert not any(step.get("uses", "").startswith("actions/checkout@") for step in guard["steps"])
steps = [step.get("id") for step in guard["steps"]]
assert steps.index("target") < steps.index("version") < steps.index("decide")
assert guard["outputs"]["dev-version"] == "${{ steps.version.outputs.dev-version }}"
build = jobs["build-artifacts"]
assert "scripts/apply_dev_version.py" in _run_commands(build)
assert {"changes-guard", "manual-approval", "python-compliance"} <= set(build["needs"])
assert "needs.changes-guard.outputs.should-build == 'true'" in build["if"]
publish = jobs["trigger-gitlab-security"]
assert {"build-artifacts", "manual-approval", "fpe-support-matrix", "license-evidence"} <= set(publish["needs"])
for name in ("build-artifacts", "fpe-support-matrix", "license-evidence"):
assert f"needs.{name}.result == 'success'" in publish["if"]


@pytest.mark.parametrize(
Expand All @@ -87,7 +91,7 @@ def test_release_migration_gate_blocks_publication_until_reviewed_clearance(tmp_
("clear", "malformed", 1),
],
)
def test_publication_checks_current_policy_and_selected_target(tmp_path, monkeypatch, current, target, expected):
def test_stable_publication_checks_current_policy_and_selected_target(tmp_path, monkeypatch, current, target, expected):
from scripts import check_release_migrations as checker

paths = {}
Expand Down
Loading