nix: allSwarmServices turns on the review classifier
deploy.review-classifier.enable now defaults to allSwarmServices, as the
other shared services do, but only where the host has the classifier's
store identity (deploy.bao.reviewClassifierClient{Cert,Key}File). The
classifier module asserts that pair; glue-bao-tls.nix mints it only on the
host that runs the store, so a services host with the store placed
elsewhere would otherwise fail eval. An explicit value still wins.
The operator still writes the Anthropic key by hand; until then each run
fails with "no API key…" while the service runs.
This commit is contained in:
parent
ed15105e6f
commit
34bde7a5af
5 changed files with 67 additions and 24 deletions
|
|
@ -236,17 +236,18 @@ the container and the threat model are in
|
||||||
### Review classifier
|
### Review classifier
|
||||||
|
|
||||||
The review classifier's try-out service, in a `swarm-review-classifier`
|
The review classifier's try-out service, in a `swarm-review-classifier`
|
||||||
container on the host that sets `deploy.review-classifier.enable`. It doesn't
|
container on the host that sets `deploy.review-classifier.enable`.
|
||||||
derive from `allSwarmServices`: every run spends money on an Anthropic API
|
`allSwarmServices` defaults it on wherever the host has the classifier's store
|
||||||
key, so running one is a decision of its own.
|
identity: the host that runs the store has it, and a services host with the
|
||||||
|
store elsewhere gets the classifier only once it holds a copy of the pair (see
|
||||||
|
below). Set `deploy.review-classifier.enable` directly to override either way.
|
||||||
|
|
||||||
Two steps turn it on:
|
The operator writes the Anthropic key by hand, in the secret store's UI at
|
||||||
|
`secret/swarm/services/review-classifier/anthropic-api-key`, field `api_key`.
|
||||||
1. Write the key in the secret store's UI at
|
Nothing writes it for you: until it exists the service runs and every run fails
|
||||||
`secret/swarm/services/review-classifier/anthropic-api-key`, field
|
with `no API key…`. The service reads the key on every run, so writing or
|
||||||
`api_key`. The service reads it on every run, so writing or removing it
|
removing it takes effect on the next run, with no restart. Every run spends
|
||||||
takes effect on the next run, with no restart.
|
money on that key.
|
||||||
2. Set `services.hyperhive.deploy.review-classifier.enable = true` on a host.
|
|
||||||
|
|
||||||
It needs neither the forge nor a hive on its host. It reaches the queue and the
|
It needs neither the forge nor a hive on its host. It reaches the queue and the
|
||||||
secret store by name, under its own `review-classifier` leaf. On a host
|
secret store by name, under its own `review-classifier` leaf. On a host
|
||||||
|
|
|
||||||
|
|
@ -653,9 +653,13 @@ in
|
||||||
`secret/swarm/services/review-classifier/anthropic-api-key`, field
|
`secret/swarm/services/review-classifier/anthropic-api-key`, field
|
||||||
`api_key`.
|
`api_key`.
|
||||||
|
|
||||||
Not derived from
|
On by default where
|
||||||
{option}`services.hyperhive.deploy.allSwarmServices`: every run spends
|
{option}`services.hyperhive.deploy.allSwarmServices` is set and this
|
||||||
money on that key, so running one is a decision of its own.
|
host has the classifier's store identity (the pair
|
||||||
|
{option}`services.hyperhive.deploy.bao.reviewClassifierClientCertFile`
|
||||||
|
and `reviewClassifierClientKeyFile`, minted on the store's host). The
|
||||||
|
key is never written for you: until the operator writes it the service
|
||||||
|
runs and every run fails with "no API key…".
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -105,4 +105,16 @@ in
|
||||||
# `singleHostSwarm` box, which gets here through `allSwarmServices`, would
|
# `singleHostSwarm` box, which gets here through `allSwarmServices`, would
|
||||||
# have no forge at all.
|
# have no forge at all.
|
||||||
config.services.hyperhive.deploy.forgejo.enable = lib.mkDefault deployCfg.allSwarmServices;
|
config.services.hyperhive.deploy.forgejo.enable = lib.mkDefault deployCfg.allSwarmServices;
|
||||||
|
|
||||||
|
# The review classifier. Its module asserts a store identity, which
|
||||||
|
# glue-bao-tls.nix mints only on the host that runs the store; with the store
|
||||||
|
# placed elsewhere both files stay null and an unconditional `true` here would
|
||||||
|
# fail that host's eval. So the default follows the identity, and a host
|
||||||
|
# holding a copy of the pair (`deploy.bao.reviewClassifierClient{Cert,Key}File`)
|
||||||
|
# gets the classifier too.
|
||||||
|
config.services.hyperhive.deploy.review-classifier.enable = lib.mkDefault (
|
||||||
|
deployCfg.allSwarmServices
|
||||||
|
&& deployCfg.bao.reviewClassifierClientCertFile != null
|
||||||
|
&& deployCfg.bao.reviewClassifierClientKeyFile != null
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
# The review classifier, in a `swarm-review-classifier` container on the host
|
# The review classifier, in a `swarm-review-classifier` container on the host
|
||||||
# that sets `deploy.review-classifier.enable`. It answers the swarm UI's review
|
# that sets `deploy.review-classifier.enable`, which `deploy.allSwarmServices`
|
||||||
# try-outs: swarm-controller sends a PR and the operator's classifications over
|
# defaults on where the host has the classifier's store identity. It answers the
|
||||||
|
# swarm UI's review try-outs: swarm-controller sends a PR and the operator's classifications over
|
||||||
# the queue, and the daemon runs `claude` once per classification and replies.
|
# the queue, and the daemon runs `claude` once per classification and replies.
|
||||||
# docs/swarm/review-classifier.md has the design.
|
# docs/swarm/review-classifier.md has the design.
|
||||||
#
|
#
|
||||||
|
|
|
||||||
|
|
@ -93,6 +93,12 @@ let
|
||||||
# swarm-required-services.nix documents the switch for.
|
# swarm-required-services.nix documents the switch for.
|
||||||
swarmServicesHere = hiveWithController { deploy.allSwarmServices = true; };
|
swarmServicesHere = hiveWithController { deploy.allSwarmServices = true; };
|
||||||
|
|
||||||
|
# Same, with the operator opting out of the classifier alone.
|
||||||
|
swarmServicesWithoutClassifier = hiveWithController {
|
||||||
|
deploy.allSwarmServices = true;
|
||||||
|
deploy.review-classifier.enable = false;
|
||||||
|
};
|
||||||
|
|
||||||
# Same, with one of those services placed on another host. Every derivation
|
# Same, with one of those services placed on another host. Every derivation
|
||||||
# in that module is `mkDefault` so this stays expressible.
|
# in that module is `mkDefault` so this stays expressible.
|
||||||
swarmServicesBaoElsewhere = hive {
|
swarmServicesBaoElsewhere = hive {
|
||||||
|
|
@ -158,7 +164,8 @@ let
|
||||||
lib.filter (l: swarmServiceLinkDomains ? ${l.label}) cfg.services.hyperhive.swarm.controller.links;
|
lib.filter (l: swarmServiceLinkDomains ? ${l.label}) cfg.services.hyperhive.swarm.controller.links;
|
||||||
|
|
||||||
# Every service container at once: the services-only host plus the CI
|
# Every service container at once: the services-only host plus the CI
|
||||||
# runner, the one container on a netns of its own.
|
# runner. The CI runner and the review classifier are the two containers on
|
||||||
|
# a netns of their own.
|
||||||
serviceContainersWithCi = hive {
|
serviceContainersWithCi = hive {
|
||||||
deploy.hive-controller.enable = false;
|
deploy.hive-controller.enable = false;
|
||||||
deploy.allSwarmServices = true;
|
deploy.allSwarmServices = true;
|
||||||
|
|
@ -178,15 +185,33 @@ let
|
||||||
lib.length (lib.attrNames es) == 9 && lib.all lib.id (lib.attrValues es);
|
lib.length (lib.attrNames es) == 9 && lib.all lib.id (lib.attrValues es);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
# Every run spends money on an operator's key, so hosting the shared
|
# The container is the control that the toggle reaches the module, not
|
||||||
# services does not start the classifier. The queue is the control:
|
# only the option; `nats` is the control that the switch is on at all.
|
||||||
# the same fixture does turn the switch's own services on.
|
name = "hosting the swarm's shared services turns on the review classifier";
|
||||||
name = "hosting the swarm's shared services leaves the review classifier off";
|
|
||||||
ok =
|
ok =
|
||||||
!swarmServicesHere.services.hyperhive.deploy.review-classifier.enable
|
swarmServicesHere.services.hyperhive.deploy.review-classifier.enable
|
||||||
&& !(swarmServicesHere.containers ? swarm-review-classifier)
|
&& swarmServicesHere.containers ? swarm-review-classifier
|
||||||
&& swarmServicesHere.services.hyperhive.deploy.nats.enable;
|
&& swarmServicesHere.services.hyperhive.deploy.nats.enable;
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
# The default yields to an operator who has spoken. The same fixture
|
||||||
|
# without the line is the control.
|
||||||
|
name = "an explicit false keeps the review classifier off where the shared services are hosted";
|
||||||
|
ok =
|
||||||
|
!swarmServicesWithoutClassifier.services.hyperhive.deploy.review-classifier.enable
|
||||||
|
&& !(swarmServicesWithoutClassifier.containers ? swarm-review-classifier)
|
||||||
|
&& swarmServicesWithoutClassifier.services.hyperhive.deploy.nats.enable;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
# The classifier's module asserts a store identity that only the store's
|
||||||
|
# host mints, so with the store placed elsewhere the default stays off
|
||||||
|
# rather than failing that host's eval. `nats` is the control.
|
||||||
|
name = "the review classifier stays off where the shared services are hosted without the store";
|
||||||
|
ok =
|
||||||
|
!swarmServicesBaoElsewhere.services.hyperhive.deploy.review-classifier.enable
|
||||||
|
&& !(swarmServicesBaoElsewhere.containers ? swarm-review-classifier)
|
||||||
|
&& swarmServicesBaoElsewhere.services.hyperhive.deploy.nats.enable;
|
||||||
|
}
|
||||||
{
|
{
|
||||||
# The switch fills in for an operator who has not spoken and yields to
|
# The switch fills in for an operator who has not spoken and yields to
|
||||||
# one who has — that is what keeps a shared service placeable on a host
|
# one who has — that is what keeps a shared service placeable on a host
|
||||||
|
|
@ -431,7 +456,7 @@ let
|
||||||
# that lost a container can't pass by comparing fewer of them.
|
# that lost a container can't pass by comparing fewer of them.
|
||||||
name = "every service container restates its host-side privateNetwork";
|
name = "every service container restates its host-side privateNetwork";
|
||||||
ok =
|
ok =
|
||||||
lib.length (lib.attrNames serviceContainers) == 10
|
lib.length (lib.attrNames serviceContainers) == 11
|
||||||
&& lib.all (
|
&& lib.all (
|
||||||
c: c.privateNetwork == (c.config.services.hyperhive.swarmContainer.privateNetwork or null)
|
c: c.privateNetwork == (c.config.services.hyperhive.swarmContainer.privateNetwork or null)
|
||||||
) (lib.attrValues serviceContainers);
|
) (lib.attrValues serviceContainers);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue