Package-level declarations

Types

Link copied to clipboard
data class Accepted(val id: String, val outcome: Outcome, val control: Counts, val treatment: Counts, val p: PValue)
Link copied to clipboard
data class AddedPackage(val name: String, val manager: String, val reason: String)

A package the arm installs that the capsule does not record.

Link copied to clipboard
data class AlreadyIngested(val file: String, val id: String) : IngestProblem
Link copied to clipboard
object Archive

The .driftcase file: a deterministic tar of a solved case directory. The arm folders move from arms/ to reproduce/, confirmed/minimal-set.json records the minimal causal set when the verdict is a CONFIRMED one, report.html is generated from the certificate, and the manifest is rebuilt over what the archive holds.

Link copied to clipboard
sealed interface ArchiveOpen
Link copied to clipboard
sealed interface ArchivePack
Link copied to clipboard
data class ArmBase(val capsule: Capsule, val command: String, val environment: Environment, val digests: Map<String, String> = emptyMap(), val envNames: Set<String> = emptySet())

The configuration both arms start from: a capsule, the failing command, where it runs, and the env names run.sh mirrors beyond the allowlist (so a variable under test is in the control).

Link copied to clipboard
data class ArmDiff(val changed: List<String>, val ignored: List<String>, val cochanging: List<String>)

The attributes that differ between two arms. ArmDiff.changed holds every non-volatile one, sorted, and includes ArmDiff.cochanging, the members a base image swap is known to bring along; ArmDiff.ignored holds the volatile and noisy ones, which never count.

Link copied to clipboard
data class ArmMismatch(val file: String, val found: List<String>) : IngestProblem
Link copied to clipboard
class ArmRun(val role: String, val label: String, val trials: List<TrialRecord>, val seconds: Long, val capsule: Capsule?)

Every trial of one arm; the counts are derived from ArmRun.trials, never stored apart.

Link copied to clipboard
object Arms
Link copied to clipboard
class ArmSet

Two arms, the diff predicted from what their interventions changed, and the verdict that diff allows. ArmSet.primaries are the paths an intervention set directly. executable is false when any member could not be set or the experiment is of a manual class.

Link copied to clipboard
sealed interface ArmSetResult
Link copied to clipboard
data class ArmSpec(val role: String, val label: String, val command: String?, val interventions: List<Intervention>, val files: Map<String, String?>, val capsuleSha256: String?, val dockerfileSha256: String?, val runScriptSha256: String?, val manifestSha256: String?)

One arm of a spec; the hashes are null for an arm nobody can run from here.

Link copied to clipboard
data class BadCounts(val file: String, val detail: String) : IngestProblem
Link copied to clipboard
data class Blocker(val member: String, val reason: String)

A member the arms could not set, and why; a blocker makes the experiment non-executable.

Link copied to clipboard
data class Budget(val maxTrials: Int, val maxMinutes: Long)

The most an experiment may spend: Budget.maxTrials over both arms and Budget.maxMinutes of waiting.

Link copied to clipboard
sealed interface BundleVerdict

What an arm-to-arm ArmDiff allows the result to claim. mechanism is filled only when a rule or probe for that specific attribute agreed; otherwise it is null and the effect is reported without a mechanism.

Link copied to clipboard
data class CaseNotIntact(val file: String, val detail: String) : IngestProblem
Link copied to clipboard
data class Check(val id: String, val status: CheckStatus, val detail: String, val expected: String? = null, val recorded: String? = null)

One check: Check.expected is what the data derives, Check.recorded what the file says.

Link copied to clipboard
Link copied to clipboard
sealed interface ConfigResult
Link copied to clipboard

Sorts a candidate attribute into automatic-local, automatic-CI or manual. It starts from the planner's Controls table, so a dimension the planner calls manual is never promoted here, then uses the reproduce manifest to demote what a container cannot mirror, and treats the members that only travel with a base image as manual because nothing sets them alone.

Link copied to clipboard
data class ControlVerdict(val path: String, val kind: InterventionClass, val reason: String, val via: String?)

Whether one attribute can be flipped, ControlVerdict.via the intervention type that does it (null for a manual dimension), and one sentence of why.

Link copied to clipboard
data class Counts(val failures: Int, val trials: Int)

Counts.failures of Counts.trials trials of one arm.

Link copied to clipboard
data class DecisionRule(val perArm: Int, val threshold: Int, val alpha: Long, val rate: Long)

The decision rule, fixed before any trial runs. The treatment arm is SUPPORTED when it fails more often than the control with an exact one-sided Fisher p at most DecisionRule.alpha; otherwise it is REFUTED when the chance that a treatment arm failing at the DecisionRule.rate seen on the failing side would fail as seldom as observed is at most DecisionRule.alpha (a binomial lower tail, floored, so it can only be too high and never calls a refutation early); otherwise it is INCONCLUSIVE. SUPPORTED is decided first. DecisionRule.perArm and DecisionRule.threshold are the planned trials and the failures that a clean control needs.

Link copied to clipboard
data class Entry(val path: String, val status: Status, val detail: String, val bundle: Boolean = false)

What became of one capsule attribute; Entry.bundle marks one that changes with the base image.

Link copied to clipboard
interface Executor

Runs the arms of an experiment. The loop above it is deterministic given these outcomes.

Link copied to clipboard
class ExperimentRun(val seq: Int, val id: String, val kind: String, val flips: List<String>, val spec: ExperimentSpec?, val control: ArmRun, val treatment: ArmRun, val outcome: Outcome?, val p: PValue?, val diff: MeasuredDiff?, val schedule: TrialSchedule? = null)

One executed experiment. ExperimentRun.kind is pilot, forward, minimal (a subset tried by the minimal-set search), reverse, removal (one bundle member reverted on the failing side), replicate or split. A reverse or removal run is evaluated the other way round: its outcome is SUPPORTED when the unchanged failing arm (the control role) fails more than the reverted one.

Link copied to clipboard

Everything 6d3 needs to run one experiment, and everything a reader needs to check it was decided in advance: the arms with their interventions and file hashes, the predicted diff and the verdict it allows, the trials, and the rule with its hash. It holds no result, runs nothing and touches no network. A manual experiment is the same file with no runnable arms and the printed ExperimentSpec.instructions. executable is false unless every member can be set and the budget reaches significance.

Link copied to clipboard
sealed interface FailWhen

What counts as a failing trial: a nonzero exit, or output that matches a pattern.

Link copied to clipboard
class FakeExecutor(capsules: (RunConfig) -> Capsule? = { it.capsule }, problem: (RunConfig) -> String? = { null }, behave: (RunConfig, Int) -> RawTrial) : Executor

A scripted executor for tests and offline demos. behave decides each trial from the arm and the trial index; capsules decides the capture (the arm's own capsule by default).

Link copied to clipboard
object Ingest

Feeds results someone else produced (a CI workflow variant, a run on another host) into a case through the same decision rule and belief update as solve. A result is accepted only when it names a preregistered experiment, the sha256 of that experiment's rule and both arm ids, and its trial counts equal the preregistered count; the rule is derived from the case's plan, never from the result. An ingested result can narrow the case, never confirm it: confirmation needs the minimal-set search and the reverse arm, which are separate preregistered experiments.

Link copied to clipboard
sealed interface IngestOutcome
Link copied to clipboard
sealed interface IngestProblem

Why an externally produced result was not accepted; nothing is applied when any is raised.

Link copied to clipboard
sealed class Intervention

A typed change to one run configuration. Applying it edits the capsule the container is synthesized from, so the Dockerfile, the run.sh flags and the manifest all follow from the same code as reproduce. The attributes it changes are recorded by the edit, not declared by hand. Every value is the capsule's own spelling (120000 100000 for a cpu limit).

Link copied to clipboard
sealed interface InterventionProblem
Link copied to clipboard

What the interventions say about a capsule path, shared by controllability and the arms.

Link copied to clipboard
data class MalformedResult(val file: String, val detail: String) : IngestProblem
Link copied to clipboard
data class MeasuredDiff(val source: String, val changed: List<String>, val ignored: List<String>, val probes: List<String>, val compared: Int = 0, val unreached: List<String> = emptyList())

The attributes that differ between two arms: MeasuredDiff.source is measured when both arms were captured inside their containers and the treatment capsule shows every requested value, predicted when an arm has no capsule, and void when both were captured but the treatment capsule lacks a requested value (MeasuredDiff.unreached names those paths). MeasuredDiff.compared counts the attribute paths of the two capsules; MeasuredDiff.probes lists the probe ids whose transcripts differ.

Link copied to clipboard
data class MissingRule(val file: String, val id: String, val why: String) : IngestProblem
Link copied to clipboard
class NextContext(val reason: StuckWhy, val detail: String, val options: SolveOptions, val caseDir: String, val unmirrored: List<String>, val candidates: List<String>)

What SolveNext needs to know about a solve that could not settle the cause.

Link copied to clipboard
data class NextStep(val kind: String, val text: String, val command: String?, val reply: String?, val ask: Ask? = null)

One thing that would settle the cause. kind names the family of step, text says what and why, command is the exact command to run (null when the step is something to change by hand) and reply is the form that feeds the answer back (null when no answer is parseable). ask is set when the step is a check from the ask table that drift why prints too; its command and reply are then the ask's command and piped form, and the JSON adds the ask's family and piped.

Link copied to clipboard
Link copied to clipboard
Link copied to clipboard
object PilotRule

The stopping rule of the pilot, in integers. The pilot runs SolveOptions.pilot trials per arm and then goes on until the failing baseline fails once or the cap is reached; the cap never takes more than a quarter of the trial budget. With no failure in n trials the failure rate is below 3/n at 95% (the rule of three), reported in whole percent rounded up.

Link copied to clipboard
data class RawTrial(val exit: Int, val output: String, val seconds: Long, val infra: String? = null)

One finished trial as the executor saw it. RawTrial.infra names a problem that is not the test's (a docker error, a timeout); such a trial counts as neither pass nor fail.

Link copied to clipboard
data class Removal(val member: String, val status: String, val experiment: String?, val detail: String)

What the single-member experiments say about one member of a bundle. Removal.status is sufficient (setting only this member on the passing side reproduced the failure), necessary (reverting only it on the failing side removed the failure), not-shown (the arm ran and the failure did not follow) or untested (no arm sets it alone, or the arm changed more than the member); Removal.experiment is the run id when an arm ran, Removal.detail says why or what.

Link copied to clipboard
object ReportHtml

One static HTML page from a certificate: no script, no external resource, no timestamp, and the same bytes for the same certificate. Every detail is in the page; <details> elements collapse what a first read does not need.

Link copied to clipboard
object Reproduce
Link copied to clipboard
sealed interface ReproduceProblem
Link copied to clipboard

Plain text for a Reproduction at three levels; presentation only, the files hold the data.

Link copied to clipboard
sealed interface ReproduceResult
Link copied to clipboard

A container that mirrors what a capsule can honestly say about its environment, and the account of every attribute it could not. Nothing here runs docker.

Link copied to clipboard
data class RuleMismatch(val file: String, val id: String, val expected: String, val found: String?) : IngestProblem
Link copied to clipboard
class RunConfig

One arm as a full run configuration. RunConfig.reproduction holds the Dockerfile, run.sh and manifest of a local arm; a CI arm has none and is carried out as a workflow variant from edits. RunConfig.edited lists, per intervention id, the paths it changed; RunConfig.extras and RunConfig.files hold what has no capsule path (workspace files, workflow steps). Nothing here runs docker.

Link copied to clipboard
Link copied to clipboard
sealed interface Sizing

Whether an arm pair can reach significance within a budget. Sufficient carries the planner's own trial plan; Insufficient carries the numbers it would take, never a guess.

Link copied to clipboard

Turns a solve into a case directory: the 6b2 files, the preregistered specs, the arms, a hash-chained results/ that continues the observation chain, and certificate.json. 6d4 packs these files into a .driftcase and re-derives the posteriors and statistics from them.

Link copied to clipboard
object SolveNext

Maps the reason a solve stopped to the steps that would settle the cause. A solve that stops without a verdict on the cause says what to run next; STUCK is kept for a certificate that has no step to give, and SolveNext.none says why.

Link copied to clipboard
data class SolveOptions(val command: String, val failWhen: FailWhen, val budget: Budget, val name: String? = null, val trialMinutes: Long = 1, val pilot: Int = 8, val maxRounds: Int = 12, val digests: Map<String, String> = emptyMap(), val passing: String = "passing.json", val failing: String = "failing.json")

How a solve runs. SolveOptions.budget caps the whole run: trials over every arm and wall minutes. SolveOptions.pilot trials per arm check that the failure reproduces and give the failure rate the planner sizes later arms from. SolveOptions.trialMinutes is the planner's assumed wall time of a trial.

Link copied to clipboard
class Solver(executor: Executor, files: CaseFiles, dir: String, options: SolveOptions)

The closed loop, one use per instance: pilot, rank, plan, run the preregistered arms, update the belief with the exact test, repeat. It never claims an experiment it did not run; every result holds its trials. The first experiment that moves the failure rate is taken through the minimal-set search and the reverse arm before anything is called CONFIRMED.

Link copied to clipboard

Plain text for a SolveResult at three levels; the case directory holds the data.

Link copied to clipboard
class SolveResult(val verdict: SolveVerdict, val case: CaseFile, val runs: List<ExperimentRun>, val trials: Int, val seconds: Long, val hypotheses: Int)

What a solve produced: the verdict, the case directory with its certificate, and the spend.

Link copied to clipboard
data class SolveVerdict(val kind: VerdictKind, val why: StuckWhy?, val statement: String, val set: List<String> = emptyList(), val members: List<String> = emptyList(), val notIsolated: List<String> = emptyList(), val mechanism: String? = null, val candidates: List<String> = emptyList(), val next: String? = null, val necessary: List<String> = emptyList(), val sufficient: List<String> = emptyList(), val steps: List<NextStep> = emptyList())

The outcome of a solve. SolveVerdict.set is the minimal causal set (hypothesis ids), SolveVerdict.members the attributes the arms differ in, SolveVerdict.notIsolated the members of a bundle that no arm has pulled apart; SolveVerdict.steps what would settle a NEEDS verdict; SolveVerdict.necessary and SolveVerdict.sufficient the members of a bundle that a removal or a single-member experiment showed the failure needs or follows; SolveVerdict.candidates and SolveVerdict.next are for NARROWED.

Link copied to clipboard
data class SplitProposal(val members: List<String>, val interventions: List<Intervention>, val verdict: BundleVerdict)

A smaller arm that sets only some of a bundle's members, with the verdict it would allow.

Link copied to clipboard
sealed interface SplitResult
Link copied to clipboard
object Splits
Link copied to clipboard
enum Status : Enum<Status>
Link copied to clipboard
Link copied to clipboard
data class TrialGate(val permille: Int, val salt: Long, val variable: String)

Arms a fault on a fixed share of trials. Trial i sets variable to 1 when hash of i and salt is under permille, and to 0 otherwise; the arms of one experiment see the same pattern, so an intermittent failure keeps its rate. The hash is the one bench computes.

Link copied to clipboard
data class TrialRecord(val index: Int, val status: TrialStatus, val exit: Int, val tail: String)

One trial with its raw exit code and the last line of its output.

Link copied to clipboard
class TrialSchedule(val seed: String, val perArm: Int)

The order of the trials of one experiment. Each trial index runs both arms, so the schedule says which arm runs first at every index. Blocks of two indices put one arm first once and second once (control, treatment, treatment, control), which cancels a linear drift over time; a hash bit of the seed picks which arm opens a block. The seed comes from the two capsules and the experiment id, so verify derives the same order.

Link copied to clipboard

Connects the planner's trial count to a failure rate, an alpha and a budget.

Link copied to clipboard
Link copied to clipboard
data class UnknownExperiment(val file: String, val id: String) : IngestProblem
Link copied to clipboard
data class UnsupportedResultSchema(val file: String, val found: Long?) : IngestProblem
Link copied to clipboard
Link copied to clipboard
object Verify

Re-derives what a solved case records from its own data: the file hashes and both hash chains, every exact Fisher p from the recorded trials, the outcome of each experiment under its preregistered rule, the posterior sequence, the ranking and plan, and that the verdict follows from the recorded experiments. It does not re-run anything.

Link copied to clipboard
class VerifyReport(val checks: List<Check>, val verdict: String?)
Link copied to clipboard

The project attributes that a container can carry as files of the workspace: a version file for a tool (project.pins.node is .nvmrc), a requirements pin (project.python.<name> is a line of requirements.txt) and a checksum pin (project.checksums.<path> is a line of SHA256SUMS). The arm writes them into a copy of the workspace.