Repository navigation
Add optional fail-closed KTH qualification gate - #1672
jatseng-ai wants to merge 3 commits into
Conversation
With HYPERLOOM_KTH_ENABLE set, each applied Controller patch is qualified by an independent `kth-qualify` subprocess (Kernel Trust Harness, contract at 49154fb80508bb37d008f87f8bfae8759209742f) before the performance validator runs. Only a validated `Eligible for performance evaluation` attestation bound to the exact applied bytes reaches the validator; Blocked, Inconclusive, timeouts, provider failures and unbound or malformed attestations revert the patch as reverted_kth_*. The applied bytes are re-checked before the KEEP commit. Host-mapped kernels use KTH's reviewed-plan request; others send a KernelCandidateEnvelope to KTH's adaptive path. KTH owns AutoSpec, plans, detectors, policy and verdict; Hyperloom only builds the request, validates binding, and enforces the result. Off by default; behaviour is unchanged when unset. Supersedes #1566 and #1550. Co-authored-by: Cursor <cursoragent@cursor.com>
…helpers Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
chaojhou
left a comment
There was a problem hiding this comment.
Requesting changes for one must-fix; I found no blockers. The gate itself is careful: every failure path I traced reverts before the benchmark, the validator measures the same landed bytes KTH qualified (the pre-applied snapshot is copied from the worktree), the digest is re-checked before the KEEP commit, and a malformed HYPERLOOM_KTH_* setting stops integration instead of silently disabling the gate.
Must-fix: kth-qualify runs on the coordinator event loop
integrate_controller_patches is a coroutine awaited directly by the kernel phase (phases/kernel.py:3811), but the gate calls _admit_to_performance synchronously:
# controller_patch_integration.py:469
qualified = _admit_to_performance(
kth_provider,
publication,
...
)That reaches subprocess.run(..., timeout=self.timeout_s) (kth_qualification.py:256-261), so the event-loop thread is held for the whole qualification: up to HYPERLOOM_KTH_TIMEOUT_S (default 300 s, and operators can raise it) per candidate. A real-mode KTH run executes the kernel against its mandatory oracles, so this is not a short call.
The codebase already guards against this: test_setup_replay_runs_off_the_event_loop_thread (actions/executors/tests/test_integrate_patch_executor.py:1881) exists because a blocking subprocess.run on the loop thread freezes "concurrent in-flight LLM streams, the dispatcher's re-scan poll, and cancel grace". The rest of this flow follows that convention: the Controller runs under asyncio.to_thread (phases/kernel.py:3764), and the E2E validator is awaited.
The fix is one line:
qualified = await asyncio.to_thread(
_admit_to_performance,
kth_provider,
publication,
repo=repo,
head=head_before,
touched=touched,
session_dir=Path(session_dir),
)_still_qualified is a single git diff and can stay synchronous like the neighbouring Git calls. A thread-identity test in the style of the one above would keep this from regressing.
|
@jatseng-ai this PR conflicts with current main. Could you rebase it? Files that conflict: #1977, #1992, #2051 and #2090 reworked Please address the must-fix from the 2026-09-27 review in the same push. Once it merges cleanly I'll review it. |
Description
This adds an optional, fail-closed gate that runs Kernel Trust Harness (KTH) qualification on every Controller-integrated kernel patch before it can reach the benchmark or KEEP path.
It integrates the consolidated KTH mainline from AMD-AI/kernel-trust-harness#9, merge SHA
49154fb80508bb37d008f87f8bfae8759209742f. The KTH revision is not built in: operators pin it withHYPERLOOM_KTH_EXPECTED_SHA.This PR replaces #1566 (the primary source) and #1550 (safeguards only). #1540 stays closed.
Responsibility split
Hyperloom has no kernel-specific, issue-specific, threshold or detector logic.
Where the gate sits
In
integrate_controller_patches(src/hyperloom/orchestrator/kernel/controller_patch_integration.py), in this order:apply_patch_resolving_conflicts(...)lands the candidate in the working tree.git diffagainst HEAD built with a temporary index, not the publishedchange.patch. Any result other than Eligible reverts the candidate with statusreverted_kth_<status>. The performance validator is never called.validate(publication)runs the existing performance validator (Eligible only).reverted_kth_failed("the candidate changed after it was qualified").record_keep(...).phases/kernel.pypassesKthQualificationProvider.from_env(). When KTH is disabled this isNone, and the loop takes exactly the path it took before.Verdict and action
eligibleblockedinconclusivefailedfailedGEAK observations (
mean_case_speedup,best_wall_ms,iteration,correctness_passed) are never read as qualification input and cannot override a KTH verdict.Security and trust
request_id. KTH's answer must bind back to that exact request, the applied patch bytes, the base commit, the plan (reviewed path) or the normalized envelope (adaptive path), and the pinned KTH revision. The digest framing (kth-subject-v1/kth-subject-v2) is recomputed on the Hyperloom side and checked against golden values produced by real KTH.HYPERLOOM_KTH_PLANS(kernel path to KTH plan ID). A publication that declares its own plan, verdict, attestation or other control field is rejected. The changed paths must fall inside the plan'sallowed_paths, and an empty change set is rejected.unknownrevision or one that differs fromHYPERLOOM_KTH_EXPECTED_SHAis rejected.execution_modemust berealorsimulatedand is recorded; KTH decides what it means for the verdict.env_safety, and the logged output is redacted. The request, attestation andresult.jsonare written atomically under the session directory.Linked issue(s)
None; there are no Jira keys or GitHub issues on #1566, #1550 or #1540. Supersedes #1566 and #1550.
Inclusion matrix
Retained from #1566
HYPERLOOM_KTH_ENABLE,..._QUALIFY_EXECUTABLE,..._TIMEOUT_Sand..._EXPECTED_SHA.kth-qualify --request --outsubprocess contract and the exit codes 0/1/2/3.fully_verified, no uncertainty, nothing uncovered.performance_admitted).kthin the integration result.Uniquely retained from #1550 (strengthened where noted)
allowed_paths.Deliberately dropped
kth_qualification.plan_idin the publication. A candidate must not choose its own plan.HYPERLOOM_KTH_ADAPTIVE. Adaptive is simply the path for any kernel without a host plan.change.patch. After a conflict-resolving merge it is not what landed.unknownKTH revision.HYPERLOOM_KTH_ROOT.OPERATION_PLANSname mapping.HYPERLOOM_KTH_ALLOWED_PATHS. The scope comes from the KTH plan.[HYPERLOOM]banner logging.except Exception.needs_reviewandkth_*statuses, replaced byreverted_kth_*.runtime_observationartifact provenance. It claimed a runtime source that did not exist.Demo material excluded
examples/kth_qualification_gate/(run_demo.py,README,expected_terminal.svg).Current APIs that replaced old assumptions
apply_patch_resolving_conflictsmerges. Qualification runs on the landed diff.record_keep/Coordinator writeback._settle_apply_manifest.envhelpers.env_safetyscrub and redact functions.atomic_write.changed_files.Tests
The new tests cover these 20 cases:
correctness_passed, cannot override Blocked.There are also golden digests shared with KTH and opt-in live tests against an installed
kth-qualify.Commands run (all passing):
None of the 17 full-suite failures are in KTH or Controller-integration code. They fall into two groups:
origin/maincheckout on this host. They involve host-environment assumptions: missing-file, multi-node state ownership and git worktree discovery tests.test_grid_runner.py::...test_the_budget_is_re_checked_after_the_uncapped_server_restart) uses a 6-second wall-clock budget. It fails only under-n 16load and passes alone on both this branch and main.CI on this PR is the authoritative run.
CPU end-to-end run against KTH
49154fbThis used unmodified KTH, a host plan registered through a wrapper, and a real git repository:
keptNUMERICAL_POLICY)reverted_kth_blockedreverted_kth_inconclusiveSize/complexity
integrate_controller_patches: ruff C901 is 19 on main and 20 here. The gate adds one branch; the qualification and admission work lives in_admit_to_performanceand_still_qualified.KthQualificationProvider._check_reviewed(13) and_check_adaptive(14) are flat sequences of one-field attestation comparisons, each with its own rejection reason. Splitting them would scatter the contract without removing any decision.kth_qualification.py533 lines,controller_patch_integration.py633 lines,kth_contract.py264 lines. All are under the 800-line trigger.qualifyis under 60 lines since the subprocess step was extracted into_run.If this simplifies or refactors
This does not refactor existing behaviour; it is additive behind
HYPERLOOM_KTH_ENABLE.Observable effect
kth_qualification/<request_id>/(request.json,attestation.json,result.jsonand the redacted KTH logs) under the session directory. The integration result carries akthrecord. Non-eligible candidates showreverted_kth_blocked,reverted_kth_inconclusiveorreverted_kth_failed, with a revert note and the KTH reason as repair feedback.Documentation is in
docs/reference/kth-qualification.md. It covers how to enable the gate, where qualification sits, what each verdict means, how to pin the revision, where artifacts land and the responsibility split, and links KTH's limitations and capability states.Known limitations
HYPERLOOM_KTH_PLANSgo down the adaptive path. KTH currently returns Inconclusive there when no host-owned adapter exists, so those candidates are reverted (fail closed).execution_binding, which the KTH CLI emits and Hyperloom validates. That is a KTH-side schema gap.KTH_SHA. An installation without either reportsunknown, which Hyperloom rejects.Breaking changes
No. The gate is off unless
HYPERLOOM_KTH_ENABLE=1.PR addresses single concern
Yes.
Root cause is upstream
Not applicable. The KTH schema gap above is a KTH issue, not a Hyperloom defect.