Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
cf7e200
Ignore `.vtcode` workspace metadata
leynos Aug 10, 2026
805a825
Plan serial dependency ordering with dyndep (#552)
leynos Aug 10, 2026
5a859ab
Add serial dependency_order contract to AST and IR (#552)
leynos Aug 10, 2026
e45b2b9
Generate Ninja dyndep bundles for serial dependency ordering (#552)
leynos Aug 10, 2026
173a415
Materialize dyndep sidecars in every runner path (#552)
leynos Aug 10, 2026
75e6bed
Group dyndep staging state in one struct
leynos Aug 11, 2026
6da0a81
Harden serial dyndep implementation checks (#552)
leynos Aug 11, 2026
1e1f3b7
Use capabilities in dyndep materializer tests (#552)
leynos Aug 11, 2026
e98a7e8
Record serial dependency validation progress (#552)
leynos Aug 11, 2026
10232ea
Document serial dependency ordering (#552)
leynos Aug 11, 2026
7e74106
Record serial dependency review completion
leynos Aug 11, 2026
fecb169
Deduplicate dyndep staging assertions
leynos Aug 11, 2026
eb4ff59
Record dyndep test refactor validation
leynos Aug 11, 2026
b7bf0a2
Linearize dyndep atomic sidecar writes
leynos Aug 11, 2026
da80d2c
Record atomic dyndep refactor validation
leynos Aug 11, 2026
cea4c43
Split dyndep bundle edge rendering
leynos Aug 11, 2026
d98e618
Record bundle rendering refactor validation
leynos Aug 11, 2026
5d59097
Skip empty dyndep materialization (#552)
leynos Aug 12, 2026
e73d3c3
Harden dyndep temporary-file creation (#552)
leynos Aug 12, 2026
5fd927e
Cover serial dependency runtime guarantees (#552)
leynos Aug 12, 2026
c30e649
Separate dyndep generation from publication (#552)
leynos Aug 12, 2026
c0ff02c
Record serial ordering review evidence (#552)
leynos Aug 12, 2026
8c94c44
Resolve serial ordering review findings (#552)
leynos Aug 12, 2026
add0fdb
Reject Ninja control characters in paths (#552)
leynos Aug 12, 2026
572ca0a
Close serial dyndep review gaps (#552)
leynos Aug 12, 2026
ff6ab6a
Refresh serial dyndep documentation
leynos Aug 13, 2026
1333506
Cover serial CLI publication paths (#552)
leynos Aug 13, 2026
3e6387f
Record final serial dyndep review (#552)
leynos Aug 13, 2026
1f07837
Diagram serial dyndep publication flow (#552)
leynos Aug 13, 2026
d6d50ce
Branch Ninja command flow in design diagram
leynos Aug 14, 2026
0d57c3d
Harden serial dyndep publication and rendering (#552)
leynos Aug 14, 2026
84f73ee
Record final serial dyndep review evidence (#552)
leynos Aug 14, 2026
dcaa8f1
Renumber serial dyndep decision after rebase (#552)
leynos Aug 14, 2026
d5070ea
Record final rebase validation evidence (#552)
leynos Aug 14, 2026
59b7612
Correct serial dyndep diagram boundary
leynos Aug 14, 2026
b21c74b
Remove stale dyndep race diagnostic (#552)
leynos Aug 14, 2026
8f78bf4
Correct serial dyndep locale wording
leynos Aug 14, 2026
d416476
Correct serial dependency documentation
leynos Aug 14, 2026
6937138
Remove redundant serial edge key clone (#552)
leynos Aug 14, 2026
1c7e5e3
Document bounded dyndep sidecar retention (#552)
leynos Aug 14, 2026
f69b936
Bound generated dyndep sidecar retention (#552)
leynos Aug 15, 2026
9ae52a3
Document conditional Ninja version floor (#552)
leynos Aug 15, 2026
988201c
Correct locale translations for dyndep errors (#552)
leynos Aug 15, 2026
a89cccc
Correct dyndep documentation and translations
leynos Aug 15, 2026
5642e63
Test cross-process dyndep publication leases (#552)
leynos Aug 15, 2026
f2da1cd
Bound dyndep retention scan memory (#552)
leynos Aug 15, 2026
ce43fd5
Simplify retention telemetry assertions (#552)
leynos Aug 15, 2026
1c0fc3a
Share retention publication test setup (#552)
leynos Aug 15, 2026
d45dc6f
Bound dyndep retention candidate memory (#552)
leynos Aug 15, 2026
d05b35f
Strengthen dyndep retention validation (#552)
leynos Aug 15, 2026
d7cf1c1
Integrate serial dyndep after rebase (#552)
leynos Aug 15, 2026
73aa9ec
Fix dyndep retention ADR date (#552)
leynos Aug 15, 2026
37ce330
Share command-list integration setup (#552)
leynos Aug 15, 2026
3b03227
Clarify migration and locale wording
leynos Aug 15, 2026
b039fea
Harden serial generation review coverage (#552)
leynos Aug 15, 2026
62a64d6
Centralize dyndep namespace ownership (#552)
leynos Aug 15, 2026
0f9e1bb
Correct dyndep rename translations (#552)
leynos Aug 16, 2026
d70702e
Document dependency ordering locale fixes
leynos Aug 16, 2026
8c4fd69
Harden serial dyndep review paths (#552)
leynos Aug 16, 2026
0e14fb1
Simplify dyndep retention traversal (#552)
leynos Aug 16, 2026
a3b98f6
Clarify serial dyndep documentation
leynos Aug 16, 2026
d938bfc
Clarify staged sidecar ownership (#552)
leynos Aug 16, 2026
c5cea71
Stabilize dyndep retention selection (#552)
leynos Aug 16, 2026
0a8776f
Document fs4 retention lock dependency
leynos Aug 16, 2026
d01ea95
Repair rebased serial-dyndep review fixes (#552)
leynos Aug 17, 2026
37f7dda
Clarify serial-dyndep contracts (#552)
leynos Aug 17, 2026
783734e
Reuse validated spelling cache on HTTP 429
leynos Aug 17, 2026
7bdd97a
Correct serial-dyndep review details (#552)
leynos Aug 17, 2026
05079a9
Clarify Russian dyndep directory path (#552)
leynos Aug 18, 2026
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
11 changes: 11 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ serde-saphyr = "0.0.6"
minijinja = { version = "2.12.0", features = ["loader"] }
cap-primitives = "3.4.4"
cap-std = { version = "3.4.4", features = ["fs_utf8"] }
fs4 = "1.1.0"
camino = "1.2.0"
semver = { version = "1", features = ["serde"] }
anyhow = "1"
Expand Down
133 changes: 133 additions & 0 deletions docs/adr-011-use-ninja-dyndep-for-serial-dependency-ordering.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
# Architecture decision record (ADR): Use Ninja dyndep for serial dependency ordering

## Status

Accepted.

## Date

2026-08-11

## Context and problem statement

Manifest authors need an explicit way to run the direct `deps` of an action or
target in declaration order. The existing Ninja dependency classes preserve
freshness and scheduling constraints, but ordinary implicit dependencies are
all visible to the scheduler and may run concurrently.

The implementation must retain a single Ninja invocation so a shared dependency
runs once, propagate a failing early dependency to stop later work through the
annotated path, and leave unrelated branches available for normal concurrent
scheduling. It must also leave the Intermediate Representation (IR)
backend-agnostic and make generated Ninja output executable in every command
path.

## Decision

**Y-statement:** In the context of declaration-ordered direct dependencies,
and facing the forces of shared-work reuse, failure short-circuiting,
unrelated-branch concurrency, backend-neutral IR, and executable generated
output, we decided to use staged Ninja dyndep sidecars for
`dependency_order: serial`, accepting the Ninja 1.10 floor, reserved generated
state beneath `.netsuke`, and a path-scoped ordering guarantee.

In the context of a `dependency_order: serial` manifest `deps` list, Netsuke
will use staged Ninja dyndep sidecars to reveal one direct dependency at a time
and will materialize those sidecars atomically beneath `.netsuke/dyndep`. The
runner materializes every sidecar file before Ninja starts; no Ninja edge
produces sidecar content.

`dependency_order` is a closed `parallel`/`serial` enum on the shared action
and target AST shape. It is copied to `BuildEdge`, where it remains a logical
graph annotation. Only the Ninja generator lowers a serial list containing two
or more direct dependencies into synthetic phony gates beneath
`.netsuke/serial` and content-addressed dyndep sidecars beneath
`.netsuke/dyndep`.

The gate edge associated with the next sidecar depends on the preceding gate,
so each later direct dependency remains unavailable until earlier work
succeeds.

The main generated build file declares `ninja_required_version = 1.10` only
when staged serial lowering is present. The generator exposes a complete bundle
containing main-file text and every required sidecar. String-only generation
rejects a graph requiring sidecars instead of returning an incomplete file.

The serial guarantee is deliberately path-scoped: each direct dependency in the
annotated list becomes schedulable only after its predecessor succeeds. A later
dependency independently reachable through another requested path remains free
to run through that other path.
Comment thread
coderabbitai[bot] marked this conversation as resolved.

## Rationale

- **One scheduler preserves shared work.** The generated gates stay inside one
Ninja invocation, so Ninja continues to deduplicate a repeated or diamond
dependency.
- **Dyndep controls visibility.** A later real dependency is absent from the
relevant graph path until its sidecar is revealed, unlike an order-only edge
whose transitive inputs are already visible to Ninja.
- **The IR remains portable.** Gates, sidecar paths, and Ninja version syntax
are backend mechanics rather than manifest graph concepts.
- **Bundle ownership prevents incomplete output.** Treating sidecars as part of
the generated artefact makes every runner path materialize them before Ninja
loads the main file.
- **Content addressing makes state reusable.** Existing matching sidecars are
safely reused; mismatching content is corruption and is reported rather than
overwritten.

## Consequences

- Serial lists with zero or one dependency use ordinary Ninja lowering; no
relative order needs enforcing and no dyndep version floor is emitted.
- User graph paths in outputs, inputs, implicit dependencies, and order-only
dependencies cannot use `.netsuke/serial` or `.netsuke/dyndep`, because those
names are reserved generated state.
- `build`, `clean`, and `generate` each materialize sidecars relative to the
effective Ninja working directory. `clean` may leave the immutable,
content-addressed sidecars in place.
- `src/ninja_gen/dyndep.rs` owns staging and naming. The command-boundary
module `src/runner/dyndep_publication.rs` opens the effective capability and
orchestrates publication and retention; `src/runner/process/dyndep_files.rs`
owns atomic sidecar writes and verification, while
`src/runner/process/dyndep_retention.rs` owns the lease and cleanup. Neither
side of the boundary may broaden the path-scoped guarantee with a global
scheduler.
- Tests must continue to use real Ninja for ordered starts, failure
short-circuiting, shared-work reuse, and unrelated-branch concurrency.

## Alternatives considered

### Order-only phony gate chain

Rejected. Ninja eagerly schedules already-visible transitive inputs, so an
order-only chain can order gate completion without preventing later real
dependencies from starting early.

### Ninja pool with depth one

Rejected. A pool provides mutual exclusion, not declaration order, and would
serialize unrelated work outside the annotated dependency list.

### Recursive Ninja or Netsuke invocation per dependency

Rejected. Separate child schedulers lose the enclosing build's memoization and
can execute a shared dependency more than once.

### A Netsuke-owned global scheduler

Rejected for this feature. It would change the execution architecture and
global reachability semantics rather than implement the requested scoped
manifest policy. It requires a separately approved design.

## Implementation references

- Manifest and IR contract: [`src/ast/mod.rs`](../src/ast/mod.rs),
[`src/ir/graph.rs`](../src/ir/graph.rs), and
[`src/ir/from_manifest.rs`](../src/ir/from_manifest.rs)
- Ninja bundle generation:
[`src/ninja_gen/dyndep.rs`](../src/ninja_gen/dyndep.rs)
- Atomic sidecar materialization:
[`src/runner/process/dyndep_files.rs`](../src/runner/process/dyndep_files.rs)
- User contract: [user's guide](users-guide.md#run-direct-dependencies-serially)
- Implementation history:
[issue #552 ExecPlan](execplans/issue-552-support-serial-dependency-ordering-for-actions-and-targets.md)
107 changes: 107 additions & 0 deletions docs/adr-012-bound-dyndep-sidecar-retention.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# Architecture decision record (ADR): Bound dyndep sidecar retention

## Status

Accepted.

## Date

2026-08-15

## Context and problem statement

Serial dependency ordering uses immutable, content-addressed Ninja dyndep
sidecars beneath `.netsuke/dyndep`. A changed manifest therefore produces new
`.dd` files without changing or overwriting files that an existing generated
Ninja manifest references. Without a cleanup policy, obsolete sidecars would
accumulate indefinitely. Cleanup must also avoid removing a sidecar that a
concurrent Netsuke command is still consuming.

The policy must preserve every sidecar in the current generated bundle,
remove stale temporary files left by interrupted atomic writes, and bound
obsolete storage deterministically. It must define the failure boundary for
`clean` and make the compatibility consequence of retaining an old
`generate --output` manifest explicit.

## Decision

Netsuke will retain immutable, content-addressed dyndep sidecars. Each
sidecar-capable `build`, `generate`, or `clean` command materializes every
sidecar in its current bundle before writing or invoking the generated Ninja
file. Publication and cleanup use a capability-scoped, exclusive lease for
the `.netsuke/dyndep` directory. The lease remains held through Ninja
consumption for `build` and `clean`, or through generated-output consumption
for `generate`.

While that lease is held, Netsuke removes stale `.tmp` files and applies the
following deterministic policy to obsolete `.dd` files:

- every sidecar in the current bundle is retained;
- at most 32 obsolete `.dd` files are retained; and
- at most 1 MiB of obsolete `.dd` bytes is retained.

Obsolete files are considered in deterministic path order. A sidecar's
content is never changed in place. `build` and `generate` prune after
materialization. `clean` prunes only after `ninja -t clean` succeeds; a failed
clean does not prune sidecars.

## Rationale

- **Content addressing preserves active bundles.** A matching sidecar can be
reused and a mismatching file is corruption, not permission to overwrite
it.
- **The lease protects consumption.** Publication, temporary-file cleanup,
and pruning share one directory lease, so cleanup cannot remove files while
another serial command is using its bundle.
- **Fixed budgets are predictable.** File-count and byte limits provide a
bounded cache without relying on filesystem timestamps or an age-based
policy.
- **`clean` keeps failure evidence.** Deferring cleanup until successful
`ninja -t clean` avoids deleting historical state when the requested clean
did not complete.

## Consequences

An old arbitrary manifest written by `generate --output` may lose referenced
sidecars after a later Netsuke command applies retention. Such a manifest
must be regenerated before use when its sidecars have been pruned. Generated
manifests should therefore be treated as command outputs paired with the
current sidecar cache, not as permanently self-contained artefacts.

The policy does not use sidecar age, and it does not make sidecars mutable.
There is no guarantee that an obsolete sidecar remains available merely
because its manifest was generated successfully in an earlier command.

## Alternatives considered

### Retain sidecars by age

Rejected. Wall-clock age is not deterministic and does not bound storage.

### Keep every content-addressed sidecar

Rejected. Immutable files would accumulate without bound as manifests change.

### Mutate or overwrite existing sidecars

Rejected. A content-addressed path must continue to identify one byte
sequence, and overwriting it could change the graph seen by an existing
manifest.

### Prune without a directory lease

Rejected. Publication and cleanup could race with a command that is consuming
the current bundle, removing a required sidecar between materialization and
Ninja or output consumption.

## Implementation references

- Runner publication boundary:
[`src/runner/dyndep_publication.rs`](../src/runner/dyndep_publication.rs)
- Atomic sidecar materialization:
[`src/runner/process/dyndep_files.rs`](../src/runner/process/dyndep_files.rs)
- Retention and lease implementation:
[retention implementation](../src/runner/process/dyndep_retention.rs)
- User contract: [user's guide](users-guide.md#run-direct-dependencies-serially)
- Serial dyndep architecture:
[ADR-011](adr-011-use-ninja-dyndep-for-serial-dependency-ordering.md)
10 changes: 8 additions & 2 deletions docs/contents.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,20 @@ operator, user, and contributor references are easier to find.
- [adr-010-scope-glob-capability-to-literal-prefix.md](adr-010-scope-glob-capability-to-literal-prefix.md):
Glob capability-scoping decision record, opening the metadata capability at
a pattern's literal directory prefix instead of an ambient root.
- [adr-011-use-ninja-dyndep-for-serial-dependency-ordering.md](adr-011-use-ninja-dyndep-for-serial-dependency-ordering.md):
Serial `deps` ordering decision record, covering staged Ninja dyndep bundles,
their scoped execution guarantee, and generated-state ownership.
- [ADR-012](adr-012-bound-dyndep-sidecar-retention.md):
Deterministic retention, lease, and failure-boundary policy for generated
dyndep sidecars.

## User and operator guides

- [quickstart.md](quickstart.md): First-run walkthrough for building with
Netsuke.
- [v0-1-0-migration-guide.md](v0-1-0-migration-guide.md): Migration notes for
the v0.1.0 child-environment API additions and glob behaviour, and the
stability caveat that covers them.
the v0.1.0 child-environment API, glob behaviour, and serial-dependency
additions, plus the stability caveat that covers them.
- [users-guide.md](users-guide.md): End-user reference for authoring and
running Netsuke manifests, including executable discovery and
`command_available` branch selection.
Expand Down
Loading
Loading