Skip to content

test(release): bind lifecycle evidence to attested runs - #61

Merged
ahrav merged 5 commits into
feat/mc-host-daemon-lifecyclefrom
stack/mc-host-14-release-evidence
Aug 29, 2026
Merged

test(release): bind lifecycle evidence to attested runs#61
ahrav merged 5 commits into
feat/mc-host-daemon-lifecyclefrom
stack/mc-host-14-release-evidence

Conversation

@ahrav

@ahrav ahrav commented Aug 26, 2026

Copy link
Copy Markdown
Owner

Summary

Release checks now bind proof bytes, target reports, the installed evidence record, checkout commit, signer workflow, and workflow run into one attested chain. Cross-harness smoke exercises two owners, stop plus demand-start, real request completion, replay, and cleanup.

Verification

  • bun run test:release: 135 tests passed
  • Attestation mutation coverage for commit, run, workflow, artifact, and target-report drift

Remaining Qualification

The gate stays fail-closed until the protected qualification workflow, exact Linux/macOS campaigns, installed registry flows, and production Synapse evidence exist. npm publication remains intentionally out of scope.

Stack

  1. fix(mc-host): authenticate daemon version transcripts #58 authentication transcript
  2. fix(mc-host): bind runtime execution to verified identity #59 native runtime identity
  3. fix(plugin): fence lifecycle compatibility by daemon #60 client compatibility
  4. test(release): bind lifecycle evidence to attested runs #61 release evidence
  5. fix(ci): restore portable lifecycle source checks #62 CI portability

@coderabbitai

coderabbitai Bot commented Aug 26, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: cc500ff8-e1c3-4ce7-96fb-6b8d68027cef

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Comment @coderabbitai help to get the list of available commands.

Comment on lines +644 to +646
if (
!/^https:\/\/github\.com\/ahrav\/magic-context\/actions\/runs\/\d+$/.test(runUrl) ||
repository !== "ahrav/magic-context" ||

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor nit: "ahrav/magic-context" is now hardcoded twice here (the runUrl regex and this repository check), in addition to the existing `https://github.com/${source.repository}` templating used elsewhere in attestationCertificateMatches. Consider hoisting it into a single EXPECTED_REPOSITORY constant next to QUALIFICATION_WORKFLOW_PATH so a future repo rename/fork can't leave one occurrence stale.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in a0fe3e0. Hoisted EXPECTED_REPOSITORY next to QUALIFICATION_WORKFLOW_PATH.

There were in fact two occurrences, not one. The second is easy to miss because the regex escapes the slash (ahrav\/magic-context), so it does not match a plain grep for the repo string.

Rather than let the new constant appear twice, the run-URL check now reuses the claimedRunId parser, so the URL shape is defined in exactly one place:

repository !== EXPECTED_REPOSITORY ||
claimedRunId({ runUrl, repository, headSha, workflow }) === undefined ||

That turned out to matter for more than drift. The old inline regex hardcoded the repository, but claimedRunId did not, and claimedRunId is the parser feeding the workflow API path. Details in the kilo anchoring threads on this file.

@claude

claude Bot commented Aug 26, 2026

Copy link
Copy Markdown

Review

Scope: scripts/build-mc-host-payload.ts, scripts/verify-mc-host-release-evidence.ts, scripts/qualify-mc-host-production-inputs.ts, and scripts/smoke-mc-host-cross-harness.ts (plus their tests). Reviewed by reading the full diff and the surrounding source, and tracing the attestation/verification chain end to end. (bun test required interactive approval that wasn't available in this sandbox, so I couldn't execute the suite — static review only.)

Overall: this is a solid, well-scoped hardening of the release-evidence gate. Highlights:

  • attestationCertificateMatches/attestationMatchesWorkflowSource binds proof and installed-evidence attestations to the exact artifact digest, commit SHA, repo, workflow path, and run ID — closing what looks like a real gap where the old code only ran gh attestation verify --repo ahrav/magic-context without checking the returned certificate fields at all.
  • test_report_path/test_report_sha256 observations are now actually re-hashed against on-disk bytes (sha256File) instead of trusting an arbitrary claimed hash — good, this was previously unverified ("b".repeat(64) in the old test fixture).
  • proof_artifacts[].path is now pinned to tmp/mc-host-release-proofs/ in addition to the existing traversal check — reasonable extra containment.
  • The "all qualified proofs must share one workflow source" check (qualifiedSource comparison) and the separate "installed evidence bytes match what's on disk and is itself attested" check are good anti-tampering additions — they stop a caller from validating an in-memory object while the on-disk/attested file says something else.
  • loadReleaseContext's evidence-citation refactor in build-mc-host-payload.ts looks behavior-preserving (just deduplicating the synthesized-vs-loaded evidence path) — didn't find a regression there.
  • Test changes (freshRoot() instead of repoRoot in several build-mc-host-payload.test.ts cases) look like a legitimate test-isolation fix rather than a behavior change.

Nits (posted inline where applicable):

  • "ahrav/magic-context" is hardcoded in a couple of places in verify-mc-host-release-evidence.ts (the runUrl regex and the repository equality check) alongside the templated `https://github.com/${source.repository}` used elsewhere — worth a shared constant to avoid drift.

Things I couldn't fully verify from this diff alone (not blocking, just flagging for awareness):

  • QUALIFICATION_WORKFLOW_PATH (.github/workflows/mc-host-release-qualification.yml) doesn't exist in this repo yet, and the default verifyWorkflowRun/verifyAttestation paths don't check the triggering event/actor (e.g. fork-safety) — but this matches the PR description's stated remaining work ("gate stays fail-closed until the protected qualification workflow ... exist"), so it reads as an intentional, disclosed gap rather than an oversight in this diff.

No correctness or security bugs found in the changed logic itself.

@ahrav
ahrav force-pushed the stack/mc-host-14-release-evidence branch from eab2181 to aeecf53 Compare August 28, 2026 19:03
@ahrav
ahrav force-pushed the stack/mc-host-14-release-evidence branch from aeecf53 to f06ce60 Compare August 29, 2026 00:28
@ahrav
ahrav marked this pull request as ready for review August 29, 2026 00:41
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for security reviews. Please try again later.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f06ce60e7a

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +134 to 139
const demandStart = await runNativeLifecycle(
{ kind: "retained-fd", fd: retained.fd },
{
command: "start",
envelope: mergedEnvelope(opencodeEnvelope, piEnvelope),
deadlineMs: 60_000,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Exercise the managed demand-start path

After the explicit stop, this calls runNativeLifecycle with command: "start" directly and even supplies the merged startup envelope itself. It therefore bypasses the actual managed-demand implementation in McHostLifecyclePolicy.demandStart and the module transport readiness/reconnect path; if that product path is broken, this release smoke still reports a successful demandStart and can provide false qualification evidence. Trigger the restart through a real managed consumer instead of invoking the native launcher directly.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not taking this one in this PR, but I want to be precise about what I verified rather than wave it off.

Your reading of the code is correct, and so is the symbol reference — I checked, because McHostLifecyclePolicy.demandStart does exist (packages/plugin/src/shared/mc-host-lifecycle/policy.ts:257) and it is not what this script calls. The smoke drives runNativeLifecycle with command: "start" and supplies the merged envelope itself, so it does bypass demandStart's coalescing of concurrent demands (shared = this.start(request.startupEnvelope)) and the createLazyManagedDemandStart path in module-transport.ts that builds the envelope via buildManagedStartupEnvelope.

Where I stopped short of your conclusion: the field that would carry the false claim, product_flows[].managed_demand_passed, has no producer wired to this script's demandStart result anywhere in the tree. It appears only in the verifier, the verifier's tests, and the template default of false. So as it stands this is an under-coverage gap in the canary, not a live path that mints false qualification evidence. That distinction is why I am not treating it as a blocker on this PR.

Why not simply fix it here: routing the restart through a real managed consumer means driving the policy through the retained-fd ownership model inside the owner subprocess, and I cannot validate that change in this environment — the script needs two live providers, real credentials, and a running daemon (MC_HOST_CANARY_BROCA_MODEL plus the Broca credential set). Landing an unvalidated rewrite of the canary's restart path would trade a known coverage gap for an unverified one, in the script whose entire job is to be trustworthy.

So I am tracking it separately as a coverage item, with this note attached: if managed_demand_passed is ever populated from this script's output, the gap becomes a correctness bug at that moment and has to be closed first. Happy to be pushed if you think it is worth blocking on now.

Comment on lines +125 to +129
const runInvocationUri = fields.runInvocationURI;
const attestedRunId =
typeof runInvocationUri === "string"
? runInvocationUri.match(/\/actions\/runs\/(\d+)(?:\/attempts\/\d+)?$/)?.[1]
: undefined;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Bind attestations to one workflow attempt

For a re-run of the same GitHub Actions workflow run, runInvocationURI distinguishes attempts using /attempts/<n>, but this regex discards that component and compares only the run ID. Consequently, proof artifacts and the installed evidence can come from different attempts—including artifacts from a failed attempt combined with the latest successful run returned by the API—while still satisfying the claimed single workflow source. Preserve the attempt number and verify every attestation and the workflow-run result against that exact attempt.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Already addressed in cd48362, which landed before this review round. The code you are quoting is the a57976b revision.

matchedAttestationAttempt now requires the attempt and returns it, and the run check resolves that specific attempt rather than the run:

  • the certificate match requires /actions/runs/<id>/attempts/<n>, and the attempt is the function's return value;
  • the API call is GET repos/{repo}/actions/runs/{id}/attempts/{n}, asserting conclusion === "success", head_sha, path, and run_attempt === n;
  • every qualified proof is forced to share one workflow source and one attempt, so proof artifacts cannot be mixed across attempts.

Your reasoning about the run-level conclusion reflecting only the latest attempt is exactly what that function's docstring now records.

One residual gap your comment implies did survive, and I fixed it in a0fe3e0: the attempt regex was anchored on the trailing path only, so it never checked that the invocation URI belonged to this repository. It is now anchored on the full expected prefix including the claimed run id. Regression test: "run urls and invocation uris are anchored to the expected repository".

Comment on lines +284 to +288
return (
control.type === "harness_dispatch" &&
control.run_id === runId &&
control.harness === harness
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Assert replayed assistant output

The smoke asks each real provider to return OK, but the replay assertion accepts any stream containing only the harness_dispatch control unit. If either adapter drops assistant text, or replay loses all response units while retaining dispatch metadata, run.status can still become completed and this production canary passes even though users receive no answer. Require the matching assistant_message for this run and validate its text before accepting the replay.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed and fixed in a0fe3e0.

The assertion did accept a stream whose only matching unit was the harness_dispatch control unit, so a replay that kept dispatch metadata while losing every response unit passed with run.status === "completed" and no answer for the user.

Added a second assertion requiring the assistant text for this specific run, walking the producer's actual envelope shape ({kind: "control", unit: {type: "assistant_message", run_id, message: {content: [{type: "text", text}]}}}, per crates/mc-host/src/broca/protocol.rs):

const assistantText = assistantTextForRun(replay, runId);
assert.ok(
    assistantText.trim() !== "",
    `${harness} replay carried no assistant text for run ${runId}`,
);

One deliberate deviation from your suggestion. You asked to validate the text, and the prompt is "Reply with exactly OK." — but I assert non-empty text rather than equality with OK. Asserting the exact string against two live providers would make the canary flake on trailing punctuation or whitespace, and a canary that cries wolf gets muted, which costs more than the extra precision buys. Non-empty assistant text is what separates "the user got an answer" from "the user got nothing", which is the failure you identified.

Also factored the envelope narrowing into a shared controlUnit helper so both assertions agree on the shape.

}

export function workflowRunApiPath(source: WorkflowSource): string | null {
const runId = source.runUrl.match(/\/actions\/runs\/(\d+)$/)?.[1];

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: workflowRunApiPath extracts run ID without validating repository prefix or supporting attempt suffixes

workflowRunApiPath matches //actions/runs/(\d+)$/ against source.runUrl without verifying that the URL origin and path begin with https://github.com/${source.repository}/actions/runs/. If passed a URL referencing a different repository or host, it discards the target origin and constructs a path under repos/${source.repository}/actions/runs/<id>. Additionally, if runUrl contains an /attempts/<num> suffix, the trailing regex fails to match and returns null.

Validate that source.runUrl starts with https://github.com/${source.repository}/actions/runs/ and permit optional /attempts/\d+ trailing segments.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed and fixed in a0fe3e0. This was the more serious of your two sub-claims.

I verified the exploit shape concretely rather than reasoning about it. With the old parser:

workflowRunApiPath({
    runUrl: "https://github.com/evil/fork/actions/runs/123456",
    repository: "ahrav/magic-context",
    ...
})
// => "repos/ahrav/magic-context/actions/runs/123456"

A run id belonging to another repository was silently requalified as ours, exactly as you describe: the parser matched only the trailing segment while the API path was rebuilt under source.repository. claimedRunId now requires the full https://github.com/${source.repository}/actions/runs/ prefix, and that is the single definition of the run-URL shape — the proof loop's duplicate inline regex is gone.

On the attempt suffix I am declining, deliberately. runUrl is the human-facing run URL and must not carry /attempts/<n>: the attempt is signed into the certificate's runInvocationURI, and accepting an attempt-suffixed runUrl would let evidence assert an attempt that nothing verified. The trailing $ is load-bearing there, so the new test asserts an attempt-suffixed runUrl returns null.

const runInvocationUri = fields.runInvocationURI;
const attestedRunId =
typeof runInvocationUri === "string"
? runInvocationUri.match(/\/actions\/runs\/(\d+)(?:\/attempts\/\d+)?$/)?.[1]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: runInvocationURI matching is unanchored, permitting cross-repository URI matching

runInvocationUri.match(/\/actions\/runs\/(\d+)(?:\/attempts\/\d+)?$/)?.[1] extracts the trailing run ID from any string without checking the repository or host prefix. While sourceRepositoryURI is checked separately, runInvocationURI in the attestation certificate should be strictly verified to start with https://github.com/${source.repository}/actions/runs/${claimedRunId} so that attestations referencing run IDs across foreign repositories cannot satisfy this check.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in a0fe3e0. I agree with the defense-in-depth argument, though I could not construct a live exploit.

Now anchored on the full expected prefix including the claimed run id, instead of matching the trailing path:

const invocationPrefix =
    `https://github.com/${source.repository}/actions/runs/${runId}/attempts/`;

Why it was not exploitable in practice: sourceRepositoryURI and buildConfigURI are both checked against source.repository within the same certificate, and GitHub's issuer will not mint a certificate whose sourceRepositoryURI is our repository while runInvocationURI names another. But that argument rests on the issuer's behaviour rather than on anything this code enforces, which is a poor thing for a release gate to depend on — so it is now enforced here. The regression test covers a foreign-repository runInvocationURI carrying a matching trailing /attempts/1.

!/^https:\/\/github\.com\/ahrav\/magic-context\/actions\/runs\/\d+$/.test(runUrl) ||
repository !== "ahrav/magic-context" ||
!/^[0-9a-f]{40}$/.test(headSha) ||
headSha !== expectedHeadSha ||

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Unconditional headSha !== expectedHeadSha check causes non-qualified validation to fail

In validateInstalledReleaseEvidenceAgainstArtifacts, the requirement that expectedHeadSha be a valid 40-character SHA is conditioned on requireQualified (line 585). However, inside the proof artifact verification loop, headSha !== expectedHeadSha is evaluated unconditionally. When validating schema only (--check-schema) or running in an environment without the qualified checkout commit, expectedHeadSha is "" or differs from the proof artifact commit, causing --check-schema to throw an error.

Condition the commit match check on requireQualified: (requireQualified && headSha !== expectedHeadSha).


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed empirically and fixed in a0fe3e0.

I reproduced this rather than taking the reading on faith: I wrote the regression test first, then reverted just the one-line gate. Without the fix, schema-only validation over evidence that carries proof artifacts fails with

mc-host installed release evidence: proof artifact
registry_package:@cortexkit/mc-host-darwin-arm64 has no immutable workflow run

Your diagnosis is exact. The SHA-validity requirement is gated on requireQualified while the proof-loop comparison was not, so --check-schema at any commit other than the one the proofs name failed closed. Applied your suggested shape:

(requireQualified && headSha !== expectedHeadSha) ||

Two notes on scope. It was latent rather than live: the committed template ships proof_artifacts: [], so CI's --check-schema never entered the loop — it only bites on real evidence. And it is not a weakening of the gate: --check still binds proofs to the checkout commit, and the pre-existing test "qualified evidence is bound to the release checkout commit" still proves a mismatched commit cannot gate GA.

New test: "schema-only validation accepts proofs from another release commit". Worth noting no test previously exercised requireQualified = false with proof artifacts present, which is how this got through.

Comment thread scripts/smoke-mc-host-cross-harness.ts Outdated
method: "run.status",
params: { run_id: runId },
},
{ timeoutMs: Math.max(1, deadline - Date.now()) },

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Polling loop timeout math triggers spurious 1ms timeout errors upon deadline expiration

In verifyBrocaRoutes, the run.status polling loop computes timeout with { timeoutMs: Math.max(1, deadline - Date.now()) }. When the deadline is reached or exceeded, Math.max(1, ...) clamps the timeout to 1 ms. The request is dispatched with a 1 ms timeout and fails with a client timeout error, throwing an exception before the deadline check assert.ok(Date.now() < deadline, ...) at line 261 is evaluated.

Check Date.now() < deadline before making the request or assert that remaining time is strictly positive before dispatching client.request.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed and fixed in a0fe3e0. Traced the loop body order:

  1. client.request(..., { timeoutMs: Math.max(1, deadline - Date.now()) }) — throws here on expiry
  2. if (status.state === "completed") break;
  3. assert.ok(state === "queued" || state === "running", ...)
  4. assert.ok(Date.now() < deadline, "did not complete before deadline") — unreachable

So an over-deadline run surfaced an opaque 1 ms transport timeout instead of the deadline failure. For a release-qualification canary that is worse than the raw flake, because it misattributes why qualification failed.

Took your first option, checking before dispatching, since it also removes the clamp entirely:

const remainingMs = deadline - Date.now();
assert.ok(remainingMs > 0, `${harness} did not complete before deadline`);

The trailing assertion is now redundant — the loop re-enters through the same check after the 50 ms sleep — so it was removed.

@kilo-code-bot

kilo-code-bot Bot commented Aug 29, 2026

Copy link
Copy Markdown

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Files Reviewed (7 files)
  • scripts/build-mc-host-payload.test.ts
  • scripts/build-mc-host-payload.ts
  • scripts/qualify-mc-host-production-inputs.test.ts
  • scripts/qualify-mc-host-production-inputs.ts
  • scripts/smoke-mc-host-cross-harness.ts
  • scripts/verify-mc-host-release-evidence.test.ts
  • scripts/verify-mc-host-release-evidence.ts
Previous Review Summaries (5 snapshots, latest commit 618119e)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit 618119e)

Status: No Issues Found | Recommendation: Merge

Files Reviewed (7 files)
  • scripts/build-mc-host-payload.test.ts
  • scripts/build-mc-host-payload.ts
  • scripts/qualify-mc-host-production-inputs.test.ts
  • scripts/qualify-mc-host-production-inputs.ts
  • scripts/smoke-mc-host-cross-harness.ts
  • scripts/verify-mc-host-release-evidence.test.ts
  • scripts/verify-mc-host-release-evidence.ts

Previous review (commit a0fe3e0)

Status: No Issues Found | Recommendation: Merge

Files Reviewed (7 files)
  • scripts/build-mc-host-payload.test.ts
  • scripts/build-mc-host-payload.ts
  • scripts/qualify-mc-host-production-inputs.test.ts
  • scripts/qualify-mc-host-production-inputs.ts
  • scripts/smoke-mc-host-cross-harness.ts
  • scripts/verify-mc-host-release-evidence.test.ts
  • scripts/verify-mc-host-release-evidence.ts

Previous review (commit cd48362)

Status: 2 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 2
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
scripts/verify-mc-host-release-evidence.ts 838 Unconditional headSha !== expectedHeadSha check causes non-qualified validation to fail
scripts/smoke-mc-host-cross-harness.ts 254 Polling loop timeout math triggers spurious 1ms timeout errors upon deadline expiration
Files Reviewed (7 files)
  • scripts/build-mc-host-payload.test.ts - 0 issues
  • scripts/build-mc-host-payload.ts - 0 issues
  • scripts/qualify-mc-host-production-inputs.test.ts - 0 issues
  • scripts/qualify-mc-host-production-inputs.ts - 0 issues
  • scripts/smoke-mc-host-cross-harness.ts - 1 issue
  • scripts/verify-mc-host-release-evidence.test.ts - 0 issues
  • scripts/verify-mc-host-release-evidence.ts - 1 issue

Fix these issues in Kilo Cloud

Previous review (commit a57976b)

Status: 4 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 4
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
scripts/verify-mc-host-release-evidence.ts 71 workflowRunApiPath extracts run ID without validating repository prefix or supporting attempt suffixes
scripts/verify-mc-host-release-evidence.ts 128 runInvocationURI matching is unanchored, permitting cross-repository URI matching
scripts/verify-mc-host-release-evidence.ts 648 Unconditional headSha !== expectedHeadSha check causes non-qualified validation to fail
scripts/smoke-mc-host-cross-harness.ts 254 Polling loop timeout math triggers spurious 1ms timeout errors upon deadline expiration
Files Reviewed (7 files)
  • scripts/build-mc-host-payload.test.ts - 0 issues
  • scripts/build-mc-host-payload.ts - 0 issues
  • scripts/qualify-mc-host-production-inputs.test.ts - 0 issues
  • scripts/qualify-mc-host-production-inputs.ts - 0 issues
  • scripts/smoke-mc-host-cross-harness.ts - 1 issue
  • scripts/verify-mc-host-release-evidence.test.ts - 0 issues
  • scripts/verify-mc-host-release-evidence.ts - 3 issues

Fix these issues in Kilo Cloud

Previous review (commit f06ce60)

Status: 4 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 4
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
scripts/verify-mc-host-release-evidence.ts 71 workflowRunApiPath extracts run ID without validating repository prefix or supporting attempt suffixes
scripts/verify-mc-host-release-evidence.ts 128 runInvocationURI matching is unanchored, permitting cross-repository URI matching
scripts/verify-mc-host-release-evidence.ts 648 Unconditional headSha !== expectedHeadSha check causes non-qualified validation to fail
scripts/smoke-mc-host-cross-harness.ts 254 Polling loop timeout math triggers spurious 1ms timeout errors upon deadline expiration
Files Reviewed (7 files)
  • scripts/build-mc-host-payload.test.ts - 0 issues
  • scripts/build-mc-host-payload.ts - 0 issues
  • scripts/qualify-mc-host-production-inputs.test.ts - 0 issues
  • scripts/qualify-mc-host-production-inputs.ts - 0 issues
  • scripts/smoke-mc-host-cross-harness.ts - 1 issue
  • scripts/verify-mc-host-release-evidence.test.ts - 0 issues
  • scripts/verify-mc-host-release-evidence.ts - 3 issues

Fix these issues in Kilo Cloud


Reviewed by gemini-3.7-flash · Input: 76.7K · Output: 7.1K · Cached: 199.9K

Base automatically changed from stack/mc-host-13-client-compatibility to feat/mc-host-daemon-lifecycle August 29, 2026 03:29
@ahrav
ahrav force-pushed the stack/mc-host-14-release-evidence branch from f06ce60 to a57976b Compare August 29, 2026 03:29
ahrav added 2 commits August 29, 2026 04:38
The target test report binding derived its expected observations from the
observations it was checking, so a proof that cited `test_report_path` and
`test_report_sha256` as null produced an expected object identical to
itself and cleared the GA gate with no report at all. The citation is now
mandatory for a target proof, confined to tmp/mc-host-test-reports/,
distinct per target, and its bytes must parse as a passing report for that
target, so an unrelated repository file can no longer stand in.

The injected verifier seams used `??`, which treats a verifier that
declines with null the same as an absent override and fell through to the
real `gh attestation verify`. The negative test therefore passed only
because the subprocess failed, and the unit suite shelled out to a
credentialed, network-dependent binary. Presence of the option now selects
the seam.

A run-level conclusion reflects only the latest attempt, so re-running a
failed run blessed artifacts signed by the attempt that failed. The
attestation now yields the signing attempt, every proof and the installed
evidence must share it, and the attempt endpoint confirms that attempt
succeeded. Verification is memoized per attempt, replacing one identical
gh api call per proof, and a failed check reports the gh exit status and
stderr so a missing actions: read scope is not misread as tampering.

An unreadable cited report degrades to a domain failure instead of a raw
ENOENT, and the GA proof matrix asserts against the schema entry point it
actually exercises rather than staging artifacts and stubs that the
earlier schema rejection never consulted.
Address review findings on the release-evidence gate and the cross-harness
canary.

Anchor run identity to the expected repository. `claimedRunId` matched a
bare trailing `/actions/runs/<id>`, so a run id borrowed from another
repository or host parsed successfully and was then rebuilt as a path under
`source.repository` - silently requalifying a foreign run as one of ours.
The run URL and the certificate's `runInvocationURI` are now both matched
against the full expected origin, repository, and run prefix. The proof loop
reuses that single parser instead of repeating the pattern inline, which
also removes the duplicated `ahrav/magic-context` literal in favour of one
`EXPECTED_REPOSITORY` constant.

Scope the release-commit binding to the GA gate. The proof loop compared
`head_sha` against the checked-out commit unconditionally, while the
requirement that the commit even be a valid SHA is gated on
`requireQualified`. Schema-only validation of real evidence therefore failed
closed whenever it ran at any other commit, which contradicts what
`--check-schema` promises. The comparison is now gated on `requireQualified`;
`--check` keeps the binding, and the existing test still proves a mismatched
commit cannot gate GA.

In the canary, prove the polling deadline before dispatching. An expired
budget was clamped to 1 ms, so the request failed as a client timeout and
threw before the deadline assertion could name the real cause. Also require
the replayed assistant text for the run: the previous assertion accepted a
stream carrying only the `harness_dispatch` control unit, so a replay that
kept dispatch metadata but lost every response unit still passed while the
user received no answer.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a0fe3e0c10

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

`https://github.com/${source.repository}/${source.workflow}` &&
artifactMatches
) {
return attempt;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Consider every matching attestation attempt

When the same workflow run is re-run, the proof bytes remain identical because their source URL deliberately omits the attempt, so a digest can have attestations from both a failed attempt and a later successful one. This returns the first matching array entry and then verifies only that attempt; therefore a successful rerun is rejected whenever the failed attestation appears first, or when different artifacts return the same attempts in different orders. The inspected gh attestation verify --help only promises one JSON-array entry per verified attestation and specifies no ordering, so collect the matching attempts and choose a common successful attempt rather than depending on array order.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed and fixed in 618119e. Good catch — and the mechanism you identified is a direct consequence of a decision I defended earlier in this same review round, which makes it worth spelling out.

In the kilo thread on workflowRunApiPath I argued that runUrl must not carry /attempts/<n>, because the attempt belongs in the certificate. That is exactly what makes your scenario reachable: because the proof's run_url omits the attempt, a re-run produces byte-identical proof bytes, so one subject digest ends up attested in the failed attempt and again in the successful one. The two facts are the same fact.

Both failure modes you named were real:

  • the failed attempt appearing first was chosen and then rejected by the run check;
  • two artifacts whose arrays arrived in different orders selected different attempts and tripped "must share one workflow run attempt", appearing to disagree about their source.

You are also right about the contract: gh attestation verify promises one array entry per verified attestation and specifies no ordering, so depending on position was unfounded.

matchedAttestationAttempts now returns every matching attempt, and the gate intersects those sets across all proof artifacts and the installed evidence, then requires one shared attempt to have concluded successfully. Candidates are tried in attempt order rather than arrival order.

That forced the run check to move after the proof loop, which I think is the more honest structure anyway: no single artifact can tell which of several shared attempts is the one that succeeded, so that decision does not belong in a per-artifact step. It still runs once per distinct attempt via the existing cache — the "verified once for one shared attempt" test still holds at exactly one call — and still reports workflow run is unverified when no shared attempt passes.

Regression test: "a re-run qualifies on the successful attempt whatever the attestation order". It feeds both ["1","2"] and ["2","1"] with only attempt 2 succeeding, and asserts qualification succeeds either way while the run check is consulted for attempt 1 first. I verified it fails against the old first-match behaviour:

attested workflow run is unverified (injected verifier declined)

One scope note for the record: both symptoms fail closed, so this blocked good releases rather than admitting bad evidence. It was still worth fixing at P1, because it would have surfaced as an unreproducible qualification failure right after a re-run — the worst possible time to be debugging the gate.

A digest can legitimately be attested in more than one run attempt. Re-running
a workflow leaves the proof bytes unchanged, because a proof's `run_url`
deliberately omits the attempt, so the same subject digest is signed in the
attempt that failed and again in the attempt that succeeded.

`matchedAttestationAttempt` returned the first matching array entry and the
gate then verified only that attempt. Since `gh attestation verify` promises
one entry per verified attestation and documents no ordering, qualification
depended on array order: a successful re-run was rejected whenever the failed
attempt happened to appear first, and two artifacts whose arrays disagreed on
order looked like they disagreed about their workflow source. Both outcomes
fail closed, so this blocked good releases rather than admitting bad ones, but
it did so unreproducibly.

Collect every matching attempt instead, intersect the sets across all proof
artifacts and the installed evidence, and then require one shared attempt to
have concluded successfully. Candidates are tried in attempt order rather than
in the order the attestation array happened to arrive.

The run check therefore moves after the proof loop: no single artifact can
tell which of several shared attempts is the one that succeeded. It still runs
once per distinct attempt through the existing cache, and it still reports
`workflow run is unverified` when no shared attempt passes.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 618119ec47

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

}
if (
observed.head_sha !== source.headSha ||
observed.path !== source.workflow ||

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Normalize the workflow run path before comparison

When --check uses the real gh api response rather than the test override, this exact comparison rejects legitimate workflow attempts: GitHub's Get a workflow run attempt response represents path with its ref suffix (for example, .github/workflows/main.yaml@refs/heads/main), while source.workflow is constrained to the bare .github/workflows/mc-host-release-qualification.yml. Consequently every otherwise successful attested attempt reaches does not match the claimed source, leaving the GA evidence gate unable to pass; compare the path component before @ while separately retaining the existing commit binding.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 75a4b3c. Your conclusion is right and the fix is applied, but the evidence is more interesting than a straight confirmation, so I want to record what I actually found.

I checked the live API before changing anything, because the failure you describe would be total — the gate could never pass — and that is a strong claim. Against this repository, both endpoints return the bare path:

$ gh api repos/ahrav/magic-context/actions/runs/33235772296/attempts/1 --jq .path
.github/workflows/ci.yml

Across roughly a thousand recent runs spanning three workflows and both pull_request and schedule events, not one path contains @. So the symptom is not occurring today.

But your citation is substantively correct, and that is what settles it. GitHub's documented example response for "Get a workflow run attempt" is:

"path": ".github/workflows/build.yml@main"

(@main rather than @refs/heads/main, but the same shape.) So the documented contract and the observed behaviour disagree, and the gate was written to accept exactly one of them.

That is decisive here for a reason specific to this PR: this comparison has never executed against a real response. QUALIFICATION_WORKFLOW_PATH does not exist yet, so no gh api result has ever reached it, and every test replaces verifyWorkflowRun wholesale. A fail-closed gate with an unexercised equality check straddling a documented/observed discrepancy is exactly the thing that should be normalized before it ever runs in anger — the failure mode is a release that cannot be qualified, diagnosed under time pressure.

Applied your suggested shape, comparing the component before @ and keeping the commit binding separate and exact. Two things worth noting:

  • This makes the file self-consistent. buildConfigURI was already normalized the same way (.split("@", 1)[0]) when the certificate is checked, so the two places that compare a workflow path now agree.
  • Nothing else is relaxed. The ref is a mutable label; the immutable binding is head_sha, still compared exactly, alongside run_attempt. Dropping the ref costs no real strength.

I also pulled the comparison out into workflowRunAttemptMatchesSource so it is reachable from a test at all — it previously sat behind the spawnSync call to gh. New test "a run attempt matches whether or not its path carries a ref suffix" accepts the bare, @main, and @refs/heads/main forms and still rejects a different workflow path, a mismatched head_sha, and a mismatched attempt. Verified it fails against the old exact comparison.

GitHub's documented example response for "Get a workflow run attempt" reports
`path` with a ref appended (`.github/workflows/build.yml@main`), while observed
responses for a directly triggered workflow in this repository return the bare
path. The attempt check compared `path` exactly, so it accepted only one of the
two documented shapes.

That matters because the gate fails closed and this path has never been
exercised end to end: the qualification workflow does not exist yet, so no real
`gh api` response has ever reached the comparison. Had GitHub returned the
documented suffixed form, every otherwise valid attempt would have been
rejected as not matching the claimed source and the GA gate could never pass.

Compare the path component before `@`, which is how `buildConfigURI` is already
normalized when the certificate is checked. Nothing else is relaxed: the ref is
a mutable label, and the immutable binding remains `head_sha`, still compared
exactly, alongside the attempt number.

The comparison moves into `workflowRunAttemptMatchesSource` so it can be tested
directly. It previously sat behind a `spawnSync` call to `gh`, which the test
override replaces wholesale, so no test could reach it.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 75a4b3cb25

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

const PAYLOAD_INDEX_PATH = "release/mc-host-payload-index.json";
const STOP_PROVENANCE_PATH = "release/mc-host-n-minus-one-stop.json";
const SHA256_RE = /^[0-9a-f]{64}$/;
const QUALIFICATION_WORKFLOW_PATH = ".github/workflows/mc-host-release-qualification.yml";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Add the qualification workflow before requiring it

The reviewed tree does not contain .github/workflows/mc-host-release-qualification.yml: git ls-tree -r 60a2cfd lists only the four existing workflows, and a repo-wide search finds this path only in the verifier and its tests. Because qualified proofs must name this exact path and gh attestation verify is invoked with it as --signer-workflow, no workflow at the commit being qualified can produce an acceptable attestation, so the GA evidence gate cannot pass. Add the workflow that emits these proofs or bind this constant to an existing committed signer workflow.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct on the facts, and intentional. The tree really does not contain .github/workflows/mc-host-release-qualification.yml — only ci.yml, claude-code-review.yml, retrieval-benchmark.yml, and shm-hardening-optin.yml — and the path appears nowhere outside the verifier and its tests. Your inference follows: with QUALIFICATION_WORKFLOW_PATH required on every qualified proof and passed as --signer-workflow, no attestation produced at this commit can satisfy the gate.

That is the declared state of this PR rather than an oversight. From the description, under "Remaining Qualification":

The gate stays fail-closed until the protected qualification workflow, exact Linux/macOS campaigns, installed registry flows, and production Synapse evidence exist.

So the gate cannot pass yet by construction, and that is the intended posture: the verifier lands first, fail-closed, and the workflow that emits the proofs follows. claude[bot] reached the same reading independently earlier in this round.

Declining both suggested remedies, for the same reason. Adding the workflow is a separate change with its own review surface — it needs branch protection, OIDC permissions, and the campaign definitions, none of which belong in a verifier PR. And binding the constant to an existing committed signer workflow would be worse than the current state: it would make the gate passable by attestations from ci.yml, which is not a protected release-qualification workflow, so the gate would report qualified on evidence no release campaign produced. A gate that cannot pass yet is safe; a gate that passes for the wrong signer is not.

Worth noting the one thing this does cost, which your comment implicitly identifies: the verifyWorkflowRunAttempt path has never executed against a real API response, because nothing has ever produced a run for this workflow. That is exactly how the ref-suffix mismatch you flagged separately (#3885687028) survived to review, and it is now fixed. If you spot other checks on that same unexercised path, they are worth raising for the same reason.

Comment on lines +2608 to +2609
if (key === "evidence") {
continue;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep validating present qualification evidence

When a production-qualified lock is accompanied by an evidence file whose artifact citations and production_qualified value still match but whose test_only or unqualified fields are stale or tampered, this unconditional skip makes release:qualify:check accept it. The later loadReleaseContext check validates only schema/release, the qualification boolean, and artifact citations, while the installed-release verifier merely hashes this file, so the full release:check can qualify evidence that requireQualificationEvidence would explicitly reject. Continue allowing the file to be absent where intended, but compare and validate it whenever it is present.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I verified this end to end and your mechanism is real. I am not applying the fix unilaterally, because doing so reverses a deliberate decision this PR made explicitly — so this needs an author call rather than a bot-driven flip. Everything needed to make that call is below.

The chain holds. This PR changed the --check loop from "evidence may be absent, but must match when present" to "evidence is never compared":

 if (options.check) {
+    if (key === "evidence") {
+        continue;
+    }
     if (!existsSync(path)) {
-        if (key !== "evidence") drift.push(`${relative}: missing`);
+        drift.push(`${relative}: missing`);
     } else if (readFileSync(path, "utf8") !== expected) {

And your reading of the downstream coverage is exact. In build-mc-host-payload.ts, the present-file branch validates only schema, release_contract_sha256, release.id/release.version, lock.production_qualified === evidence.production_qualified, and then consumes evidence.artifacts. It never looks at test_only or unqualified. The installed-release verifier only hashes the file into qualification_sha256. So with the skip in place, nothing compares those two fields against the canonical derivation, and a stale or edited file that keeps its citations and production_qualified intact passes the full release:check.

That matters specifically because those are the fields the script's own header names as the rejection signal:

entries release engineering has not qualified yet are explicitly qualified: false, which propagates to non-production evidence that requireQualificationEvidence (the U2/U6 build gate) rejects

I also confirmed the comparison is feasible: evidenceText is derived inside the same generate() call from the contract, manifest, and digests, so expected is exactly what that invocation would write. A mismatch means stale, deterministically — this is not machine-dependent. (The unrelated closure source node size changed drift comes from closureCatalog, a different output.)

Why I stopped. The PR does not merely skip the file, it added a test asserting the skip:

test("check mode ignores local evidence whether absent or present", ...)
    writeFileSync(join(root, OUTPUT_PATHS.evidence), '{"stale":true}\n');
    expect(generate(root, { check: true }).drift).toEqual([]);

So "a present-but-stale evidence file is not drift" is a stated intent with a test behind it, not an accident. Your fix cannot land without rewriting that test, and I am not willing to silently invert an intentional decision on a release-security gate. I did apply and then revert the change locally to confirm it is a clean one-line restoration.

The decision. These two cannot both hold:

  1. --check tolerates a stale local tmp/ evidence file, so a developer's leftover artifact never fails the check (the new test's intent); or
  2. --check is the only thing comparing that file's test_only/unqualified against the canonical derivation, so skipping it leaves those fields unvalidated everywhere (your finding).

If the goal was only to stop a developer's leftover file failing the check, your formulation gets both — absence stays tolerated, drift is caught — and the test should become "absent is not drift, stale is". If instead the file is genuinely expected to diverge legitimately in some flow I have not found, then the skip is right and the gap should be closed where those fields are actually consumed, by validating test_only/unqualified in loadReleaseContext rather than by hashing alone.

I lean toward your version plus the amended test, but it is the author's call. Flagging rather than deciding.

@ahrav
ahrav merged commit 1f2e42f into feat/mc-host-daemon-lifecycle Aug 29, 2026
16 of 19 checks passed
@ahrav
ahrav deleted the stack/mc-host-14-release-evidence branch August 29, 2026 06:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant