Files
sanderling/cmd/internal-tools/campaign/manifest.go
T
pj 028534cadc feat(campaign): sweep seeds for one experiment cell
campaign.json lists the seeds a sweep intended to run and is written before
the first run, so a host that dropped runs shows up as missing seeds rather
than as a smaller sample. Seed 0 is rejected: sanderling test reads it as
"derive a seed from the clock", which is why conformance/gates.sh controls
nothing today.

Each run contributes one runs.jsonl line carrying steps to first violation by
origin step, the step that armed the failed obligation, so the survival
analysis never reopens a trace.

Claude-Session: https://claude.ai/code/session_01A5KmftdEJ49A9z5mF5ESrX
2026-08-12 18:35:23 +05:30

68 lines
2.2 KiB
Go

package main
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"time"
)
const (
manifestFileName = "campaign.json"
recordsFileName = "runs.jsonl"
seedPlaceholder = "{seed}"
devicePlaceholder = "{device}"
)
// manifest is campaign.json: what the campaign INTENDED to run, written before
// the first run so a host that dropped runs shows up as missing seeds rather
// than as a smaller sample.
type manifest struct {
Arm string `json:"arm"`
Generator string `json:"generator"`
Platform string `json:"platform"`
SpecPath string `json:"spec_path"`
BundleID string `json:"bundle_id"`
MaxSteps int `json:"max_steps"`
DurationMillis int64 `json:"duration_millis"`
Seeds []int64 `json:"seeds"`
Devices []string `json:"devices"`
Host string `json:"host"`
SanderlingPath string `json:"sanderling_path"`
SanderlingVersion string `json:"sanderling_version"`
StartedAt time.Time `json:"started_at"`
ArgumentTemplate []string `json:"argument_template"`
}
func buildManifest(configuration config, host, binaryPath, version string, startedAt time.Time) manifest {
devices := configuration.devices
if devices == nil {
devices = []string{}
}
return manifest{
Arm: configuration.arm,
Generator: configuration.generator,
Platform: configuration.platform,
SpecPath: configuration.specPath,
BundleID: configuration.bundleID,
MaxSteps: configuration.maxSteps,
DurationMillis: configuration.duration.Milliseconds(),
Seeds: configuration.seeds,
Devices: devices,
Host: host,
SanderlingPath: binaryPath,
SanderlingVersion: version,
StartedAt: startedAt,
ArgumentTemplate: runArguments(configuration, seedPlaceholder, devicePlaceholder),
}
}
func writeManifest(directory string, value manifest) error {
body, err := json.MarshalIndent(value, "", " ")
if err != nil {
return fmt.Errorf("marshal manifest: %w", err)
}
return os.WriteFile(filepath.Join(directory, manifestFileName), append(body, '\n'), 0o644)
}