The control-plane half of the slice that runs a job without the forge. It
depends on syncode/workflow#3, which puts the plan on the wire, and is pinned
to that branch until it merges.
Protocol
proto/node.proto defines one persistent bidirectional stream per node: NodeSession.Open. The node sends Hello, progress and logs; the control
plane sends Welcome and assignments. Nothing polls, and the control plane
numbers what it sends so a node can tell a repeat from a new decision.
The assignment carries the compiled plan as bytes — the serialized domain
model, not protobuf messages restating it, per D018. The plan carries its own
schema version, so the node refuses one it does not speak without the protocol
having to describe the plan at all.
Domain
Run is an aggregate: RunCommand in, RunEvent out, state never assigned
directly, every event numbered by a monotonic Sequence. Finish and Start
are refused from a node that does not hold the run — the place where a fencing
token goes once leases arrive in the next slice.
ControlService keeps runs and the queue in memory. That is deliberate: the
aggregate decides, and where its log lives is a question this type exists to
answer by being replaced rather than by growing.
tests/node_session.rs starts the service on a real socket, connects as a node
would, and asserts the whole path: hello answered, the assignment carries the
plan the compiler produced, the decoded plan equals it and states the current
schema, the run knows its holder before the assignment leaves, a reported
success finishes the run, and a stranger's report is refused.
Not here yet
Enrollment and credentials, capabilities and capacity, leasing and fencing,
cancellation, matrix and reusable expansion, secrets, audit, persistence. Each
arrives with the test that proves it.
The control-plane half of the slice that runs a job without the forge. It
depends on `syncode/workflow#3`, which puts the plan on the wire, and is pinned
to that branch until it merges.
## Protocol
`proto/node.proto` defines one persistent bidirectional stream per node:
`NodeSession.Open`. The node sends `Hello`, progress and logs; the control
plane sends `Welcome` and assignments. Nothing polls, and the control plane
numbers what it sends so a node can tell a repeat from a new decision.
The assignment carries the compiled plan as bytes — the serialized domain
model, not protobuf messages restating it, per D018. The plan carries its own
schema version, so the node refuses one it does not speak without the protocol
having to describe the plan at all.
## Domain
`Run` is an aggregate: `RunCommand` in, `RunEvent` out, state never assigned
directly, every event numbered by a monotonic `Sequence`. `Finish` and `Start`
are refused from a node that does not hold the run — the place where a fencing
token goes once leases arrive in the next slice.
`ControlService` keeps runs and the queue in memory. That is deliberate: the
aggregate decides, and where its log lives is a question this type exists to
answer by being replaced rather than by growing.
## Verification
`cargo fmt --check`, `cargo clippy --workspace --all-targets --all-features -- -D warnings`,
`cargo test --workspace --all-targets --all-features`, `check-architecture.sh`,
`check-rust-loc.sh`.
`tests/node_session.rs` starts the service on a real socket, connects as a node
would, and asserts the whole path: hello answered, the assignment carries the
plan the compiler produced, the decoded plan equals it and states the current
schema, the run knows its holder before the assignment leaves, a reported
success finishes the run, and a stranger's report is refused.
## Not here yet
Enrollment and credentials, capabilities and capacity, leasing and fencing,
cancellation, matrix and reusable expansion, secrets, audit, persistence. Each
arrives with the test that proves it.
The control plane compiles a workflow, keeps the run as an aggregate advanced
by commands, and pushes the versioned plan down a bidirectional gRPC stream.
The node says hello and is given work; nothing polls, and a report only counts
from the node that holds the run.
The stream handler converted every failure at its call site. It now has one
error type with From impls and a single conversion to Status at the boundary.
control-domain said nothing about its contents. The runs the control plane
holds, the aggregate that decides about them and the queue in front of them are
one crate; the gRPC stream a node opens is the other.
dtolnay/rust-toolchain publishes versions as branches whose heads move, so a
commit pinned today is unreachable from any ref once the branch advances and a
fresh mirror no longer carries it. The branch is what a mirror can serve.
The runner on production cannot resolve an action reference named like a
version, so dtolnay/rust-toolchain is unusable there until it ships the fix.
The toolchain is pinned by image digest instead, which the job container
resolves without any action at all.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
The control-plane half of the slice that runs a job without the forge. It
depends on
syncode/workflow#3, which puts the plan on the wire, and is pinnedto that branch until it merges.
Protocol
proto/node.protodefines one persistent bidirectional stream per node:NodeSession.Open. The node sendsHello, progress and logs; the controlplane sends
Welcomeand assignments. Nothing polls, and the control planenumbers what it sends so a node can tell a repeat from a new decision.
The assignment carries the compiled plan as bytes — the serialized domain
model, not protobuf messages restating it, per D018. The plan carries its own
schema version, so the node refuses one it does not speak without the protocol
having to describe the plan at all.
Domain
Runis an aggregate:RunCommandin,RunEventout, state never assigneddirectly, every event numbered by a monotonic
Sequence.FinishandStartare refused from a node that does not hold the run — the place where a fencing
token goes once leases arrive in the next slice.
ControlServicekeeps runs and the queue in memory. That is deliberate: theaggregate decides, and where its log lives is a question this type exists to
answer by being replaced rather than by growing.
Verification
cargo fmt --check,cargo clippy --workspace --all-targets --all-features -- -D warnings,cargo test --workspace --all-targets --all-features,check-architecture.sh,check-rust-loc.sh.tests/node_session.rsstarts the service on a real socket, connects as a nodewould, and asserts the whole path: hello answered, the assignment carries the
plan the compiler produced, the decoded plan equals it and states the current
schema, the run knows its holder before the assignment leaves, a reported
success finishes the run, and a stranger's report is refused.
Not here yet
Enrollment and credentials, capabilities and capacity, leasing and fencing,
cancellation, matrix and reusable expansion, secrets, audit, persistence. Each
arrives with the test that proves it.
Pull request closed