Roadmap
Orchestration grows in milestones, each with an exit criterion that says when it is done. This page tracks them. The direction explains where orchestration is going and why, and the design contracts specify each part. Everything not marked done is planned and does not exist in Nyl yet.
Next step
Section titled “Next step”M4 is complete. Continue with M5: Kubernetes publications that consume image digests and infrastructure outputs.
- 1.Not done: Implement KubernetesPublication with pinned image digests and infrastructure outputs over render-tree and publish-tree.
- 2.Not done: Add publication observation, attestations, direct builds, and teardown ownership fences.
- 3.Not done: Add preview environment templates, expiry, limits, and end-to-end reference scenarios.
Milestones
Section titled “Milestones”A milestone is in progress once one of its items is done, and done only when its exit criterion is met. The line between milestones fills as the milestone above it progresses.
M1 Unit, input, state, and promotion contract
DoneThe written contract for units, inputs, state, and promotion, proven by walkthrough scenarios.
6 acceptance items
- Done: Define Release input declarations, DeploymentTarget bindings, types, and lock semantics for
fromGit(contract). - Done: Define Unit, Environment, and PromotionPath schemas; typed references; output admission; and artifact descriptors (core, promotion).
- Done: Define desired/observed ref layout, receipt freshness, identity fences, leases, deletion, and recovery invariants.
- Done: Define the driver trait, capabilities, and the command unit's process contract (core, infrastructure units).
- Done: Specify CLI effects, revision selection, partial plans, and exit categories.
- Done: Write the exit-criterion walkthroughs as scenario files for the M3 harness.
Done when: The contract explains a successful dependency wave, an unavailable upstream output, effects without a receipt, competing runners, a promotion with stale source evidence, and deletion, without hidden state or ambiguous command semantics. Met by the walkthroughs and their scenarios.
- Done: Define Release input declarations, DeploymentTarget bindings, types, and lock semantics for
M2 Release inputs without orchestration
DoneTyped Release inputs and DeploymentTarget bindings, without any orchestration.
11 acceptance items
- Done: Implement Release inputs and DeploymentTarget bindings for
value,fromFile,fromGit, andfromPublication, with type validation and defaults. - Done: Support
carryFileFromWorktreeforfromPublication, excluding carried files from the dirty check. - Done: Extend
nyl update source-locksto refreshfromGitlocks, with a--targetfilter. - Done: Apply target bindings in direct commands and add
--input/--inputs; a target that selects no group containing the Release fails unless--application-groupor--defaults-onlyis given. - Done: Require unique generated Argo CD names across every pair of targets whose instances resolve to the same cluster and namespace, including implicit per-target instances.
- Done: Record resolved inputs in the dependency recorder, render-cache key, and the existing ownership-index
inputsmap as digests under reserved@-prefixed keys, keeping index format version 2. - Done: Reject
fromUnitandfromPromotionbindings outside orchestration with an actionable message. - Done: Move binding declarations, validation, and resolution into one generic resolver in
nyl_core::bindings: typed declarations, exactly one source per binding, JSON Pointer selection, effective-value precedence, digests and provenance, and aggregated issues, with each binding kind served by a provider trait. Release input sources become providers innyl-render, so M3's unitvaluesandvariables, PromotionPath selectors, andnyl getreuse the same resolver instead of growing their own: it resolves declared slots and references at any depth of a document, records provenance per JSON Pointer, and lists blocked references by location. - Done: Document the feature and regenerate resource schemas.
- Done: Expand template values (
${ … }) in fields rendered later than the structural pass, starting withapplicationNameTemplate, which requires at least one expression and no longer expands the{% raw %}form. - Done: Extract
nyl-coreandnyl-renderfrom the current crate without behavior change, per the implementation architecture. Thenylpackage keeps the CLI role, and no crate is published to crates.io.
Done when: A target renders Releases from static, locked external, and same-branch publication state inputs, committed or carried, through
render-treeandpublish-tree; a concurrent state push makes publication fail rather than interleave; projects without inputs that the previous release rendered produce byte-identical output, except where the two listed validation changes now reject them: a{% raw %}applicationNameTemplate, and duplicate generated names across implicit per-target Argo CD instances.- Done: Implement Release inputs and DeploymentTarget bindings for
M3 Orchestration core with a constrained command unit
DoneEnvironments, state in Git, and dependency-ordered runs of command units.
14 acceptance items
- Done: Implement environments, YAML state files with published schemas,
nyl state init/move/forget(including moves between branches and custom refs), leases and run checkpoints under a configurable coordination ref prefix, validated against GitHub, GitLab, and Forgejo for custom refs and mixed atomic pushes, and one transition commit per operation with a machine-readable summary, pushed atomically with the lease check. - Done: Implement
nyl state delete, including decommissioning declared environments. - Done: Support optional dedicated SSH keys and HTTPS tokens, configured only when needed, for state pushes and
publish-tree. Remote Git operations run thegitcommand line throughnyl-git, so by default they authenticate as the user'sgitdoes (SSH config and keys, agent, credential helpers);[[git.credentials]]innyl.tomlnames a key file, or a key or token in an environment variable, per repository URL prefix, for runners without those. State pushes use the same transport, including atomic multi-ref pushes with per-ref leases; libgit2 handles only local repository access. - Done: Implement
plan,reconcile,status,verify,recover,teardown(including--hold),hold, andresumefor a dependency graph of command units withfromUnitreferences, including--localruns next to remote state on the clone's own state branches, state without a remote, andstate move --from localwhen a remote is added. - Done: Implement cross-environment references and multi-environment runs by repeated
-e, a label selector, or an EnvironmentGroup with shared defaults, ordered by those references, each environment with its own lease and transition commit. - Done: Implement approvals bound to the desired document, with recorded approval sources and identity (
verified,asserted,local), the GitHub approver lookup and the[approval] lookuphook, and the commonenvcredential admission. - Done: Implement pinned source worktrees, reachability checks against protected refs, and content-based execution keys.
- Done: Prove stale-evidence blocking, competing runners, and recovery after an effect succeeds but receipt publication fails.
- Done: Move the business rules the
nylpackage still holds into the crates that own them before orchestration builds on them: ownership-index input hashing (hash_inputs), the worktree source state with its carry exclusion (source_state), and publication-base verification (verify_publication_base). The command line then only parses arguments, wires crates, and renders reports. - Done: Replace the remote fields of
ApplicationGroupSourceandfromGit'sGitInputSourcewith the oneGitSourcetype of the implementation architecture, which command-unit sources then use too. Both already share one commit-lock rule. - Done: Start
nyl-state,nyl-orchestration, andnyl-driversas separate crates with the scenario harness, property tests, and crash injection. - Done: Expose a pure, versioned graph of literal unit addresses and dependency edges before upstream values exist, shared by local execution and future CI exporters. Prove separate unit-scoped invocations through the unit-jobs walkthrough without changing environment-wide lease or ownership semantics.
- Done: Add the
test-clockCargo feature and the test-only fake kinds. - Done: Add the two-tier harness for the reference scenarios: in-process with fakes, and through the real binary under the
tier2Cargo feature.
Done when: Two dependent command units reconcile locally and in CI with identical results; repeat execution is a no-op; interruption has a demonstrated recovery path; the M3 part of the reference scenarios and every walkthrough scenario marked M3 pass.
- Done: Implement environments, YAML state files with published schemas,
M4 Container image and Terraform units
DoneContainer image builds and Terraform or OpenTofu configurations as units.
8 acceptance items
- Done: Add the
OciImagekind overdocker buildx, recording digest references and aContainerImageartifact, with theregistryAuthhelper and named build contexts. - Done: Add
nyl buildfor kinds with thebuildcapability, starting withOciImage: builds from the current worktree, forwards driver options after--, and writes no state. - Done: Support
nyl reconcile --worktreefor one environment, using an immutable working-file snapshot throughout preflight and execution, retained in state and run history for fresh-clone recovery and teardown. - Done: Add the
TerraformandOpenTofukinds over one implementation, with native state and locking, declared output admission, change digests, plan approval (bind: plan), verification, and teardown. - Done: Support a local-module Terraform configuration pinned to a source commit and path.
- Done: Demonstrate Terraform-to-Terraform output references.
- Done: Check out only what a unit reads when its kind declares its inputs:
OciImageandTerraform/OpenTofuunits get a sparse worktree of their source paths and thenyl.tomldirectory, so their cost in a large monorepo follows the unit, not the repository. A Command unit can read anything in the repository, so it keeps a full worktree unless it declares the paths it reads. - Done: Add the
execdriver capability andnyl execfor held units, with the deployed or--worktreespec and source snapshot, ownership-scope checks, recorded spec differences, approval of the first execution afterwards, and refusal exit 125; implement it for OpenTofu and Terraform, with their missing-state guard.
Done when: A network configuration's outputs feed a dependent configuration; an image build records a digest; unchanged inputs plan no change; the M4 part of the reference scenarios passes with the real tools in the required
tier2CI job.- Done: Add the
M5 Images and Terraform outputs into Kubernetes releases
PlannedKubernetes publications that consume image digests and Terraform outputs.
8 acceptance items
- Not done: Add the Kubernetes publication unit over
render-tree/publish-tree. - Not done: Resolve
fromUnitRelease input bindings into an explicit pinned input snapshot for rendering. - Not done: Distinguish
publishedfromattestedevidence and named attestations for dependents. - Not done: Add the publication unit's observe mode: read generated Argo CD Applications' last successful sync and health, match them to publication commits by Application directory tree, record the observations, and attest
acceptedandhealthy. - Not done: Document an end-to-end local/CI example.
- Not done: Declare the
buildcapability forKubernetesPublication:nyl buildrenders the tree,--diffcompares it with the published tree, and--pushpublishes only targets no environment owns. - Not done: Support inline DeploymentTargets on
KubernetesPublication, its two-phase teardown with theteardownWaitstrategies, ownership-index owner fencing, and the teardown readiness check with its warnings. - Not done: Add EnvironmentTemplates for preview environments: instances through
nyl state init --template,teardown --all,state delete, shared state refs throughstate.path, cross-environment references to declared environments, sliding expiry, andmaxInstances.
Done when: One reconcile builds an image, applies Terraform, and publishes Kubernetes manifests consuming both; the same Releases still render with static inputs in a target that does not use orchestration; all three reference scenarios, including preview closure and expiry, pass in both tiers.
- Not done: Add the Kubernetes publication unit over
M6 Promotion paths
PlannedExplicit promotion of proven source commits and values between environments.
10 acceptance items
- Not done: Implement PromotionPath, PromotionRecord, and
nyl promotewith evidence checks and an optional pull-request change gate. - Not done: Promote dev's source commit and image digest to prod from one recorded dev state, with
record: stateandrecord: source, the whole-source evidence rule, recorded evidence for older states, artifact verification, and rollback through--revision. - Not done: Support several paths into one environment:
source.fromPromotion.paths, path-less bindings, the per-value supersede check with--supersede, and the hotfix pattern with a fifth reference scenario. - Not done: Add
nyl get states,nyl get promotion-candidates, andnyl promote --dry-run. - Not done: Promote values only, such as an image digest and a Terraform source commit, across differently named units and inputs, each from the state its consumer runs.
- Not done: Promote from a non-orchestrated target's published inputs, including a carried state file, with values verified against the recorded digests.
- Not done: Promote into a target outside every environment:
to: {target}, the record in the target'sspec.promotions,fromPromotionresolution in tree and direct commands, and the ownership index's applied sequence. - Not done: Implement attestations: driver-declared names,
spec.attestationson units and environments,nyl attestfor environments and targets, thepublished | attestedevidence levels with a path's per-unitattestations, blocking on failures with--ignore-attestation, andmaxAttestationAge. - Not done: Gate promotion on fresh recorded attestations for both PromotionPath sources, sourcing each value from the publication its Application runs.
- Not done: Show promotion lineage in
status.
Done when: Prod runs exactly the source commit and image digest dev proved, with auditable lineage; stale, missing, or mixed source evidence blocks promotion; a hotfix reaches prod through its own path, and a dev promotion without the fix is refused until the fix is merged or superseded; a value-only path promotes each value from what its consuming Application runs.
- Not done: Implement PromotionPath, PromotionRecord, and
M7 Continuous operation and scope decision
PlannedEvaluate continuous operation and decide what comes after the initial scope.
6 acceptance items
- Not done: Evaluate a scheduled or long-running runner and its observation cadence using M3–M6 evidence. The drift-repair policy is settled in M3 part 5: converging units repair recorded drift automatically by default.
- Not done: Add an opt-in
nyl reconcile --verify: the run verifies its current units under its own lease, then repairs what drifted bydriftRepair, all in one transition commit, so a scheduled job needs one command and no lease gap betweennyl verifyandnyl reconcile. It reuses the verify path; without the flag, reconcile does not verify. - Not done: Decide which command-unit uses warrant typed drivers, and whether to add the plugin driver protocol.
- Not done: Support minimum healthy durations and flapping guards over recorded observations.
- Not done: Evaluate application-level checks as promotion evidence.
- Not done: Record the selected direction and constraints in ROADMAP.md.
M8 Provider-neutral CI execution and first GitLab adapter
PlannedEach unit as its own CI job, starting with GitLab, with Nyl keeping the decisions.
6 acceptance items
- Not done: Implement
nyl ci export gitlabfor one environment, with reconcile and read-only plan operations, consuming M3's graph and typed reports. Keep CI runner configuration separate from unit semantics. - Not done: Specify provider-neutral graph/manifest types, capability negotiation, normalized diagnostics, and a conformance suite shared by GitLab and an in-memory test adapter. Provider IDs and scheduler syntax never enter unit identity, receipt formats, or the execution key. Unsupported capabilities fail explicitly or use a documented safe fallback.
- Not done: Generate stable, collision-checked job IDs and
needsedges, a pinned public graph manifest, per-unit reports, and an always-run read-only summary. Include current producers and unresolved consumers, and a lifecycle node:reconcile --leavingafter the unit jobs for reconciliation, and a read-only lifecycle report for planning, including empty selections, preserving the caller's--fail-on-leavingpolicy without state writes. - Not done: Bind jobs to the admitted source, project, environment, operation, and adapter configuration. Implement opt-in deployment admission: a versioned record on
state.admissionRefwith forward-only generations chosen by the environment's source rules, idempotent re-dispatch, authorized rollback, and normalized authorization evidence. Under the environment lease, superseded worker attempts skip before intent writes, imports, or effects;state restorecarries the record forward. Provider pipeline serialization is an optional optimization, not the fence. - Not done: Preserve waiting/failure/uncertainty categories and receipt-based retries; revalidate approvals and never pass authoritative outputs through CI artifacts.
- Not done: Cover the CI acceptance scenarios with fake drivers and local Git repositories, plus a GitLab configuration and integration check. Demonstrate the generated jobs with M4's real drivers.
Done when: A GitLab child pipeline displays separate producer and consumer jobs, executes at a pinned source, explains no-ops and blockers, and preserves the local core's freshness, lease, approval, and recovery rules across retries and cancellation. Independent unit jobs are observably serialized within one environment; no distributed-run guarantee is made. Retrying an old worker after a newer admission has no deployment effects, and a newly authorized rollback remains possible. Removing a unit shows a lifecycle job that destroys nothing without
--allow-teardown, and removing all units still produces a read-only MR lifecycle report. The in-memory adapter proves the same semantic graph and admission behavior without GitLab types or services.- Not done: Implement
M9 Domain modeling for units and Releases
PlannedTeam-owned, typed declarations that units implement and Releases read, the fourth layer of Nyl.
Open the M9 roadmap, which tracks its 11 slices.