Watch
0
0
Fork
You've already forked hyperhive
0

swarm-builder: lock the swarm meta flake on request

A request on $SWARM.builder.lock-meta carries a rendered flake.nix. The
builder writes it into a scratch directory, runs `nix flake lock` there
with the same forge credential helper its builds use, answers with the
flake.lock nix wrote, and removes the directory. It commits nothing; the
controller commits the lock beside the flake.

Locks are answered in their own loop, like archetype evaluations, so one
does not wait behind an hour-long build. The auth callout grants the
subject to the builder (subscribe) and the controller (publish) only.

Ported from c574a1694 onto the swarm-wire crate.

Refs #4857
This commit is contained in:
atlas 2026-10-22 01:23:02 +02:00
commit de323a72dd
4 changed files with 320 additions and 42 deletions

View file

@ -243,8 +243,8 @@ impl Policy {
}
}
/// What the builder may do: hear build and archetype evaluation requests
/// and answer each on its reply subject, for as long as a build may take.
/// What the builder may do: hear build, archetype evaluation and meta-flake
/// lock requests and answer each on its reply subject, for as long as a build may take.
/// Its publish list is its own inbox, for
/// [`Self::review_classifier_permissions`]' reason.
fn builder_permissions(client_id: &str) -> Permissions {
@ -253,6 +253,7 @@ impl Policy {
subscribe: Some(vec![
swarm_wire::builder::SUBJECT.to_owned(),
swarm_wire::builder::ARCHETYPES_SUBJECT.to_owned(),
swarm_wire::builder::LOCK_SUBJECT.to_owned(),
Self::inbox(client_id),
]),
responses: Some(swarm_wire::builder::REPLY_DEADLINE),
@ -693,6 +694,8 @@ impl Policy {
swarm_wire::builder::SUBJECT.to_owned(),
// An archetype evaluation, sent as a request the same way.
swarm_wire::builder::ARCHETYPES_SUBJECT.to_owned(),
// A swarm meta-flake lock, sent as a request the same way.
swarm_wire::builder::LOCK_SUBJECT.to_owned(),
// An operator's claude login step, sent as a request the same way.
swarm_wire::claude_login::SUBJECT.to_owned(),
// Verified forge deliveries, kept in the forge events stream for
@ -1614,7 +1617,7 @@ mod tests {
#[test]
fn the_builder_hears_its_subject_and_inbox_only_and_answers_by_reply() {
use swarm_queue_client::client_inbox_prefix;
use swarm_wire::builder::{ARCHETYPES_SUBJECT, REPLY_DEADLINE, SUBJECT};
use swarm_wire::builder::{ARCHETYPES_SUBJECT, LOCK_SUBJECT, REPLY_DEADLINE, SUBJECT};
let p = policy()
.with_builders(vec![BUILDER.to_owned()])
@ -1623,6 +1626,7 @@ mod tests {
let inbox = client_inbox_prefix(BUILDER);
assert!(may_subscribe(&p, SUBJECT));
assert!(may_subscribe(&p, ARCHETYPES_SUBJECT));
assert!(may_subscribe(&p, LOCK_SUBJECT));
assert!(may_subscribe(&p, &reply(&inbox)));
for refused in [
">".to_owned(),
@ -1643,11 +1647,11 @@ mod tests {
#[test]
fn only_the_reader_may_send_a_build() {
use swarm_wire::builder::{ARCHETYPES_SUBJECT, SUBJECT};
use swarm_wire::builder::{ARCHETYPES_SUBJECT, LOCK_SUBJECT, SUBJECT};
let p = with_classifier().with_builders(vec![BUILDER.to_owned()]);
let reader = p.permissions("swarm-controller").expect("the reader");
for subject in [SUBJECT, ARCHETYPES_SUBJECT] {
for subject in [SUBJECT, ARCHETYPES_SUBJECT, LOCK_SUBJECT] {
assert!(reader.publish.iter().any(|g| covers(g, subject)));
for (who, grant) in [
("a hive", p.permissions("hive-alpha").expect("a hive")),