Skip to content

Commit d0cb8fa

Browse files
committed
fix(step7): a declaration of false is evidence, not damage
S-C, found by the review of the previous repair — and introduced by it. Closing the artifact-boundary hole, I folded value judgements into the shape validator, so `dedicated_to_p022: false` was refused exactly like a forged document. The honest case that used to produce a record with `qualified: false` and `single_tenant: fail` produced nothing at all: one line on stderr and no artifact. That erases negative attempts, which is how a laboratory ends up with machines that pass on the first try because the other tries were never artifacts. The boundary now separates the two questions it had merged: artifact validity is this the artifact it claims to be — kind, schema, types, applicability shape. Malformed is refused before any record exists. predicate outcome do the declared values satisfy the predicate. Every failure reaches a real record. So the string "false" is malformed, a missing key is malformed, `is_vm` answered with "n/a" is malformed, a VM omitting a VM-only field is malformed, and a physical host answering those with bare booleans is malformed — while the boolean `false`, anywhere it is allowed, is a valid declaration that this host does not qualify. Exit codes now say which class occurred, and are written down rather than implied: 0 valid artifact, positive outcome 1 valid artifact, NEGATIVE outcome — the record exists and says why 2 malformed input or operational misuse — no record is produced The same split applies to the session declaration: an operator who truthfully records that a prohibited job is running gets `eligible: false` with the reason, not an error message and no evidence. Three controls added, driven through the command line so the exit codes are part of the proof: an honest provisioning negative, an honest VM negative and an honest session negative each leave an artifact naming the failed check, while `"false"` as a string leaves none and exits 2. step 7 host qualification controls: 24 passed, 0 failed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018xhcg5opoFbSTdYHpkSXCh
1 parent 830c241 commit d0cb8fa

2 files changed

Lines changed: 216 additions & 47 deletions

File tree

scripts/step7/hostqual.py

Lines changed: 67 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,17 @@
1414
utility that checks a CPU governor must not become the root of campaign
1515
identity.
1616
17+
Artifact validity is not predicate outcome, and the exit codes say which is
18+
which:
19+
20+
0 a valid artifact, and the answer is yes
21+
1 a valid artifact, and the answer is no — the record EXISTS and says so
22+
2 a malformed input or an operational misuse; no record is produced
23+
24+
A declaration of `false` is evidence, not damage. Only a document that is not the
25+
artifact it claims to be — wrong kind, wrong schema, a missing key, the string
26+
"false" where a boolean belongs — is refused before a record exists.
27+
1728
Two classes of evidence, kept apart because only one of them is proof:
1829
1930
DECLARED provisioning and per-session operator facts. A guest OS
@@ -273,8 +284,17 @@ def _declared_na(value: object) -> bool:
273284

274285

275286
def validate_provisioning(doc: dict) -> list[str]:
276-
"""Shape AND value. A structurally perfect declaration of the wrong facts is
277-
not a valid declaration, it is a refusal written politely."""
287+
"""SHAPE only.
288+
289+
Artifact validity is not predicate outcome. `dedicated_to_p022: false` is not
290+
a damaged document — it is a perfectly good declaration that this host does
291+
not qualify, and it has to survive long enough to become a record. Discarding
292+
it before the artifact exists erases the negative attempts, and a laboratory
293+
where every machine passes first time because the other tries "were not
294+
artifacts" is not one anybody should trust.
295+
296+
So the string "false" is malformed and the boolean `false` is evidence.
297+
"""
278298
problems: list[str] = []
279299
if doc.get("kind") != PROVISIONING_SCHEMA:
280300
problems.append(f"kind is {doc.get('kind')!r}, not {PROVISIONING_SCHEMA!r}")
@@ -283,12 +303,9 @@ def validate_provisioning(doc: dict) -> list[str]:
283303
for key in PROVISIONING_STRINGS:
284304
if not isinstance(doc.get(key), str) or not doc.get(key):
285305
problems.append(f"{key} is missing or not a non-empty string")
286-
for key in PROVISIONING_REQUIRED_TRUE:
287-
if doc.get(key) is not True:
288-
problems.append(f"{key} must be declared true, got {doc.get(key, '<missing>')!r}")
289-
for key in PROVISIONING_REQUIRED_FALSE:
290-
if doc.get(key) is not False:
291-
problems.append(f"{key} must be declared false, got {doc.get(key, '<missing>')!r}")
306+
for key in PROVISIONING_REQUIRED_TRUE + PROVISIONING_REQUIRED_FALSE:
307+
if not isinstance(doc.get(key), bool):
308+
problems.append(f"{key} must be a boolean, got {doc.get(key, '<missing>')!r}")
292309

293310
virt = doc.get("virtualization")
294311
if not isinstance(virt, dict):
@@ -297,18 +314,37 @@ def validate_provisioning(doc: dict) -> list[str]:
297314
if not isinstance(is_vm, bool):
298315
return problems + [f"virtualization.is_vm must be a real boolean, got {is_vm!r}: "
299316
"whether this is a VM is not a question a host may decline"]
317+
# Applicability is shape; the answers themselves are the predicate's business.
300318
for key in PROVISIONING_VM_BOOLEANS:
301319
value = virt.get(key, "<missing>")
302-
if is_vm:
303-
if value is not True:
304-
problems.append(f"virtualization.{key} must be true on a VM, got {value!r}")
305-
elif not _declared_na(value):
320+
if is_vm and not isinstance(value, bool):
321+
problems.append(f"virtualization.{key} must be a boolean on a VM, got {value!r}")
322+
elif not is_vm and not _declared_na(value):
306323
problems.append(f"virtualization.{key} must be an explicit 'n/a: <reason>' on a "
307324
f"physical host, got {value!r}")
308325
return problems
309326

310327

328+
def provisioning_predicate(doc: dict) -> list[str]:
329+
"""VALUE. Every failure here becomes `qualified: false` in a real artifact."""
330+
failures: list[str] = []
331+
for key in PROVISIONING_REQUIRED_TRUE:
332+
if doc.get(key) is not True:
333+
failures.append(f"{key} is declared false")
334+
for key in PROVISIONING_REQUIRED_FALSE:
335+
if doc.get(key) is not False:
336+
failures.append(f"{key} is declared true")
337+
virt = doc.get("virtualization") or {}
338+
if virt.get("is_vm") is True:
339+
for key in PROVISIONING_VM_BOOLEANS:
340+
if virt.get(key) is not True:
341+
failures.append(f"virtualization.{key} is declared false; a VM that cannot "
342+
"promise it is not a measurement host")
343+
return failures
344+
345+
311346
def validate_declaration(doc: dict) -> list[str]:
347+
"""SHAPE only, for the same reason as the provisioning declaration."""
312348
problems: list[str] = []
313349
if doc.get("kind") != DECLARATION_SCHEMA:
314350
problems.append(f"kind is {doc.get('kind')!r}, not {DECLARATION_SCHEMA!r}")
@@ -318,11 +354,18 @@ def validate_declaration(doc: dict) -> list[str]:
318354
if not isinstance(doc.get(key), str) or not doc.get(key):
319355
problems.append(f"{key} is missing or not a non-empty string")
320356
for key in DECLARATION_REQUIRED_TRUE:
321-
if doc.get(key) is not True:
322-
problems.append(f"{key} must be declared true, got {doc.get(key, '<missing>')!r}")
357+
if not isinstance(doc.get(key), bool):
358+
problems.append(f"{key} must be a boolean, got {doc.get(key, '<missing>')!r}")
323359
return problems
324360

325361

362+
def declaration_predicate(doc: dict) -> list[str]:
363+
"""VALUE. An operator who truthfully says a workload is running gets an
364+
`eligible: false` record, not an error message and no evidence at all."""
365+
return [f"{key} is declared false" for key in DECLARATION_REQUIRED_TRUE
366+
if doc.get(key) is not True]
367+
368+
326369
ARTIFACT_VALIDATORS = {
327370
"environment manifest": validate_manifest,
328371
"host qualification": validate_qualification,
@@ -353,10 +396,11 @@ def load_artifact(path: Path, kind: str) -> dict:
353396

354397

355398
def check_single_tenant(provisioning: dict, manifest: dict) -> dict[str, object]:
356-
problems = validate_provisioning(provisioning)
357-
if problems:
399+
failures = provisioning_predicate(provisioning)
400+
if failures:
358401
return check("single_tenant", False,
359-
"the provisioning declaration does not validate: " + "; ".join(problems))
402+
"the provisioning declaration is valid evidence that this host does not "
403+
"qualify: " + "; ".join(failures))
360404
identity = manifest.get("identity") if isinstance(manifest.get("identity"), dict) else {}
361405
mismatch = [k for k in ("environment_id", "host_fingerprint")
362406
if observed(identity, k) != provisioning.get(k)]
@@ -619,7 +663,7 @@ def session_eligibility(binding_path: Path, qualification_path: Path, manifest_p
619663
binding = load_artifact(binding_path, "execution binding")
620664
qualification = load_artifact(qualification_path, "host qualification")
621665
manifest = load_artifact(manifest_path, "environment manifest")
622-
load_artifact(declaration_path, "session declaration")
666+
declaration = load_artifact(declaration_path, "session declaration")
623667
stratum = str(qualification["stratum"])
624668
bound = binding[stratum]
625669
identity = manifest["identity"]
@@ -633,6 +677,11 @@ def session_eligibility(binding_path: Path, qualification_path: Path, manifest_p
633677
if bound.get("memory_metric") != qualification.get("memory_metric"):
634678
reasons.append("the binding and the qualification disagree about the memory metric")
635679

680+
declared = declaration_predicate(declaration)
681+
if declared:
682+
reasons.append("the session declaration says this moment is not measurable: "
683+
+ "; ".join(declared))
684+
636685
ci = check_ci(manifest)
637686
snapshot = power_snapshot()
638687
power = check_power_policy(snapshot)

tests/test_step7_hostqual.py

Lines changed: 149 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
hostqual-artifact-boundary every consumed artifact is proved before it is read
77
hostqual-t0-versioned a qualification is versioned by the T0 it claims
88
hostqual-ci-predicate ci == false, not "the field is absent"
9-
hostqual-provisioning-values shape AND value; a VM that promises nothing fails
9+
hostqual-provisioning-shape malformed is malformed; a boolean false is not
10+
hostqual-provisioning-predicate values are judged by the predicate, not the parser
11+
hostqual-negative-evidence an honest no leaves a record, not a stderr line
1012
hostqual-one-environment-id one identity, not three strings that usually agree
1113
hostqual-quiesce-window 120 s means 120 s, of which 60 s is measured
1214
hostqual-quiesce-arithmetic quiet passes; spike, mean, gap and rewind do not
@@ -418,37 +420,68 @@ def control_ci_predicate() -> None:
418420
ok("hostqual-ci-predicate", "ci == false; absent or non-boolean is refused")
419421

420422

421-
def control_provisioning_values() -> None:
422-
if hq.validate_provisioning(provisioning()):
423-
fail("hostqual-provisioning-values", "a valid physical-host declaration was refused")
424-
return
425-
if hq.validate_provisioning(vm_provisioning()):
426-
fail("hostqual-provisioning-values", "a valid VM declaration was refused")
427-
return
428-
cases = [
429-
("dedicated_to_p022 false", provisioning(dedicated_to_p022=False)),
430-
("no_concurrent_user_workload false", provisioning(no_concurrent_user_workload=False)),
431-
("hosted_ci_runner true", provisioning(hosted_ci_runner=True)),
432-
("VM fixed_vcpu false", vm_provisioning(fixed_vcpu=False)),
433-
("VM fixed_ram false", vm_provisioning(fixed_ram=False)),
434-
("VM live_migration_disabled false", vm_provisioning(live_migration_disabled=False)),
435-
("VM dynamic_memory_disabled false", vm_provisioning(dynamic_memory_disabled=False)),
436-
("VM answering n/a", vm_provisioning(fixed_vcpu="n/a: do not ask")),
437-
("is_vm as n/a", provisioning(virtualization={"is_vm": "n/a: unclear"})),
423+
def control_provisioning_shape() -> None:
424+
"""Malformed is malformed; `false` is not malformed."""
425+
for label, doc in (("a physical-host declaration", provisioning()),
426+
("a VM declaration", vm_provisioning()),
427+
("an honest negative", provisioning(dedicated_to_p022=False)),
428+
("an honest VM negative", vm_provisioning(fixed_vcpu=False))):
429+
if hq.validate_provisioning(doc):
430+
fail("hostqual-provisioning-shape",
431+
f"{label} was refused as malformed: {hq.validate_provisioning(doc)}")
432+
return
433+
malformed = [
434+
('the string "false" where a boolean belongs', provisioning(dedicated_to_p022="false")),
435+
("a missing required boolean",
436+
{k: v for k, v in provisioning().items() if k != "dedicated_to_p022"}),
437+
("is_vm answered with n/a", provisioning(virtualization={"is_vm": "n/a: unclear"})),
438+
("a VM leaving a VM-only field out",
439+
provisioning(virtualization={"is_vm": True, "fixed_vcpu": True, "fixed_ram": True,
440+
"live_migration_disabled": True})),
441+
("a VM answering a VM-only field with n/a", vm_provisioning(fixed_vcpu="n/a: do not ask")),
438442
]
439-
for label, doc in cases:
443+
physical_bare = provisioning()
444+
physical_bare["virtualization"]["fixed_vcpu"] = True
445+
malformed.append(("a physical host answering VM questions with bare booleans", physical_bare))
446+
for label, doc in malformed:
440447
if not hq.validate_provisioning(doc):
441-
fail("hostqual-provisioning-values", f"{label} was accepted")
448+
fail("hostqual-provisioning-shape", f"{label} was accepted as a valid artifact")
449+
return
450+
ok("hostqual-provisioning-shape",
451+
"wrong types, missing keys and inapplicable answers are malformed; a boolean `false` is "
452+
"not, because a declaration that this host does not qualify is still a declaration")
453+
454+
455+
def control_provisioning_predicate() -> None:
456+
if hq.provisioning_predicate(provisioning()) or hq.provisioning_predicate(vm_provisioning()):
457+
fail("hostqual-provisioning-predicate", "a compliant declaration failed the predicate")
458+
return
459+
for label, doc in (("dedicated_to_p022 false", provisioning(dedicated_to_p022=False)),
460+
("no_concurrent_user_workload false",
461+
provisioning(no_concurrent_user_workload=False)),
462+
("hosted_ci_runner true", provisioning(hosted_ci_runner=True)),
463+
("VM fixed_vcpu false", vm_provisioning(fixed_vcpu=False)),
464+
("VM fixed_ram false", vm_provisioning(fixed_ram=False)),
465+
("VM live_migration_disabled false",
466+
vm_provisioning(live_migration_disabled=False)),
467+
("VM dynamic_memory_disabled false",
468+
vm_provisioning(dynamic_memory_disabled=False))):
469+
if not hq.provisioning_predicate(doc):
470+
fail("hostqual-provisioning-predicate", f"{label} satisfied the predicate")
471+
return
472+
if hq.check_single_tenant(doc, manifest())["result"] != "fail":
473+
fail("hostqual-provisioning-predicate", f"{label} still qualified the host")
442474
return
443-
physical_hole = provisioning()
444-
physical_hole["virtualization"]["fixed_vcpu"] = True
445-
if not hq.validate_provisioning(physical_hole):
446-
fail("hostqual-provisioning-values",
447-
"a physical host answering the VM questions with bare booleans was accepted")
475+
if hq.declaration_predicate(declaration()):
476+
fail("hostqual-provisioning-predicate", "a compliant session declaration failed")
448477
return
449-
ok("hostqual-provisioning-values",
450-
"every required boolean is checked by VALUE: a VM that cannot promise fixed vCPU, fixed "
451-
"RAM, no live migration or no dynamic memory fails, and 'n/a' is unavailable to it")
478+
for key in hq.DECLARATION_REQUIRED_TRUE:
479+
if not hq.declaration_predicate(declaration(**{key: False})):
480+
fail("hostqual-provisioning-predicate", f"session declaration {key} false passed")
481+
return
482+
ok("hostqual-provisioning-predicate",
483+
"every required value is judged by the predicate rather than by the parser, so each "
484+
"failure can reach a record instead of a stream of errors")
452485

453486

454487
def control_one_environment_id() -> None:
@@ -831,6 +864,91 @@ def control_execbinding_no_overwrite() -> None:
831864
# --- the template and this file itself ---------------------------------------
832865

833866

867+
def control_negative_evidence() -> None:
868+
"""A refused host leaves a record saying so. Erasing negative attempts is how
869+
a laboratory ends up with machines that pass on the first try because the
870+
other tries were never artifacts."""
871+
with tempfile.TemporaryDirectory() as raw:
872+
tmp = Path(raw)
873+
repo, commit, _digest, t0_path = instrument_repo(tmp)
874+
mpath = write(tmp, "m.json", manifest())
875+
876+
def qualify_cli(name: str, prov: dict) -> tuple[int, Path]:
877+
ppath = write(tmp, f"{name}-p.json", prov)
878+
out = tmp / f"{name}-q.json"
879+
with fixed_power(COMPLIANT_POWER):
880+
rc = hq.main(["--qualify", "--stratum", "linux", "--repo", str(repo),
881+
"--t0-path", t0_path, "--t0-commit", commit,
882+
"--provisioning", str(ppath), "--manifest", str(mpath),
883+
"--emit", str(out)])
884+
return rc, out
885+
886+
# N1 and N2: honest provisioning negatives
887+
for label, prov, expect_key in (
888+
("n1", provisioning(dedicated_to_p022=False), "single_tenant"),
889+
("n1b", provisioning(no_concurrent_user_workload=False), "single_tenant"),
890+
("n2", vm_provisioning(fixed_vcpu=False), "single_tenant")):
891+
rc, out = qualify_cli(label, prov)
892+
if not out.is_file():
893+
fail("hostqual-negative-evidence",
894+
f"{label}: a valid negative declaration produced no artifact at all")
895+
return
896+
record = json.loads(out.read_text(encoding="utf-8"))
897+
if record.get("qualified") is not False:
898+
fail("hostqual-negative-evidence", f"{label}: the record does not say qualified "
899+
f"false: {record.get('qualified')!r}")
900+
return
901+
if record.get("predicate", {}).get(expect_key) != "fail":
902+
fail("hostqual-negative-evidence",
903+
f"{label}: {expect_key} is {record.get('predicate', {}).get(expect_key)!r}, "
904+
"so the record does not say WHY")
905+
return
906+
if rc != 1:
907+
fail("hostqual-negative-evidence",
908+
f"{label}: exit code {rc}; a valid artifact with a negative outcome is 1")
909+
return
910+
911+
# N4 and N5: malformed input produces no artifact and a different code
912+
rc, out = qualify_cli("n4", provisioning(dedicated_to_p022="false"))
913+
if out.is_file():
914+
fail("hostqual-negative-evidence", "a malformed declaration produced an artifact")
915+
return
916+
if rc != 2:
917+
fail("hostqual-negative-evidence",
918+
f"a malformed declaration exited {rc}; malformed input is 2, not 1")
919+
return
920+
921+
# N3: an honest session negative still records eligibility
922+
qpath = write(tmp, "q.json", qualification())
923+
wpath = write(tmp, "qw.json", qualification("windows"))
924+
bpath = write(tmp, "b.json", binding_doc(hq.sha256_file(qpath), hq.sha256_file(wpath)))
925+
dpath = write(tmp, "d.json", declaration(no_prohibited_background_job_active=False))
926+
cpath = tmp / "cand.bin"
927+
cpath.write_bytes(LINUX_CANDIDATE)
928+
quiet = {"eligible": True, "reason": "", "samples": [0.01], "mean": 0.01, "max": 0.01}
929+
with fixed_power(COMPLIANT_POWER):
930+
record = hq.session_eligibility(bpath, qpath, mpath, dpath, cpath,
931+
quiesce_result=quiet)
932+
if record["eligible"]:
933+
fail("hostqual-negative-evidence", "a declared prohibited job left the session eligible")
934+
return
935+
if not any("declaration" in r for r in record["reasons"]):
936+
fail("hostqual-negative-evidence",
937+
f"the eligibility record does not say why: {record['reasons']}")
938+
return
939+
malformed_d = write(tmp, "d-bad.json", declaration(no_campaign_workload="false"))
940+
with fixed_power(COMPLIANT_POWER):
941+
message = refuses(lambda: hq.session_eligibility(bpath, qpath, mpath, malformed_d,
942+
cpath, quiesce_result=quiet))
943+
if message is None:
944+
fail("hostqual-negative-evidence", "a malformed session declaration was consumed")
945+
return
946+
ok("hostqual-negative-evidence",
947+
"an honest negative — host, VM or session — leaves a real artifact saying qualified/"
948+
"eligible false and naming the reason, and exits 1; malformed input leaves nothing and "
949+
"exits 2. The two classes never share a code")
950+
951+
834952
def control_provisioning_example() -> None:
835953
if not EXAMPLE.is_file():
836954
fail("provisioning-example-validates", f"{EXAMPLE} is missing")
@@ -899,7 +1017,9 @@ def control_tools_do_not_import_harness() -> None:
8991017
("hostqual-artifact-boundary", control_artifact_boundary),
9001018
("hostqual-t0-versioned", control_t0_versioned),
9011019
("hostqual-ci-predicate", control_ci_predicate),
902-
("hostqual-provisioning-values", control_provisioning_values),
1020+
("hostqual-provisioning-shape", control_provisioning_shape),
1021+
("hostqual-provisioning-predicate", control_provisioning_predicate),
1022+
("hostqual-negative-evidence", control_negative_evidence),
9031023
("hostqual-one-environment-id", control_one_environment_id),
9041024
("hostqual-quiesce-window", control_quiesce_window),
9051025
("hostqual-quiesce-arithmetic", control_quiesce_arithmetic),

0 commit comments

Comments
 (0)