Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -680,7 +680,7 @@ jobs:
# Windows bills at twice the Linux rate, and this job needs the artifact
# `rust` produces, so it follows the same gate.
if: >-
always() && needs.rust.result == 'success'
!cancelled() && needs.rust.result == 'success'
&& needs.changes.outputs.docs_only != 'true'
&& ((github.event_name == 'push' && github.ref == 'refs/heads/main')
|| contains(github.event.pull_request.labels.*.name, 'ci:full'))
Expand Down
22 changes: 20 additions & 2 deletions docs/phase1-conformance.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,8 +163,8 @@ assignment, RPC decoder and primary/secondary exception pipeline. This fixture
correction does not establish the protected installation failure's cause or
relax the round-trip assertions.

The lock now selects reviewed source `2dd79ec1505d9c12051552109244067b35813357`,
tree `5187dc5f385bb07f2a5c2f637ffe58898741017b`, including all 25 governed files
The lock now selects reviewed source `ffa954c2678bd933d7057d526b5fc4ed67a16f32`,
tree `3d667c7d816fe3fac088b6865b46756a84581beb`, including all 25 governed files
and ten production deltas. The checkout regression exercises all five labels
from that immutable revision. SDK rebinding, both scope rotations and fresh
protected validation remain required; this binding alone is not acceptance.
Expand Down Expand Up @@ -2874,3 +2874,21 @@ independent profile-probe job lifetimes described at the top of this document.
Candidate, consumer, Cave, Coven, and supervisor executable authorities are unchanged.
Native Windows checks and an SDK binding to the eventual delivery remain required
before protected conformance can establish acceptance.


### Cancelling superseded pull-request Windows CI

The ordinary `Windows supervisor behavior` job uses `!cancelled()` together
with its existing Rust-success, docs-only, main-push and `ci:full` gates. This
lets a superseded pull-request run stop instead of retaining a Windows runner
through `always()`. Main concurrency still completes each merge, and independent
conformance scan, keychain cleanup and artifact-retention steps keep their
existing conditions. The job name, 20-minute limit, and supervisor containment
are unchanged.

A cancelled run is not cleanup or conformance acceptance evidence. Its replacement
must pass all required checks. Issue #306 tracks this change and the remaining
hosted supersession/replacement validation. The motivating old-head run
`35117564510` was cancelled while its Windows job continued to completion; the
replacement run `35118809575` subsequently passed all eleven jobs. That history
does not validate the new cancellation guard.
10 changes: 5 additions & 5 deletions phase1-conformance.lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@
},
"harness": {
"repository": "OpenCoven/chat",
"revision": "2dd79ec1505d9c12051552109244067b35813357"
"revision": "ffa954c2678bd933d7057d526b5fc4ed67a16f32"
},
"harnessAuthority": {
"revision": "2dd79ec1505d9c12051552109244067b35813357",
"tree": "5187dc5f385bb07f2a5c2f637ffe58898741017b",
"revision": "ffa954c2678bd933d7057d526b5fc4ed67a16f32",
"tree": "3d667c7d816fe3fac088b6865b46756a84581beb",
"files": [
{
"path": "scripts/phase1-conformance.mjs",
Expand Down Expand Up @@ -141,8 +141,8 @@
},
{
"path": ".github/workflows/ci.yml",
"blob": "742d13f7a75faa0c7f7064328d0c1f1d4877c6c4",
"sha256": "f21eb5af2925a6d721ba45f73f51718bb9f43c36f9230e03655210251df38911"
"blob": "a18a00fc1e8dda0c427cd1a4864d896044d7b3df",
"sha256": "b28f4700104c70690b0068edfdd435f4b4810043cdd7ba23e868b5e782cf500a"
},
{
"path": ".github/workflows/client-v1-conformance.yml",
Expand Down
8 changes: 4 additions & 4 deletions src/phase1-conformance-lock.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,8 @@ const committedHarnessAuthority = JSON.parse(
readFileSync(resolve(projectRoot, 'phase1-conformance.lock.json'), 'utf8'),
).harnessAuthority;
const expectedBehaviorAuthority = {
revision: '2dd79ec1505d9c12051552109244067b35813357',
tree: '5187dc5f385bb07f2a5c2f637ffe58898741017b',
revision: 'ffa954c2678bd933d7057d526b5fc4ed67a16f32',
tree: '3d667c7d816fe3fac088b6865b46756a84581beb',
files: [
{
path: 'scripts/phase1-conformance.mjs',
Expand Down Expand Up @@ -125,8 +125,8 @@ const expectedBehaviorAuthority = {
},
{
path: '.github/workflows/ci.yml',
blob: '742d13f7a75faa0c7f7064328d0c1f1d4877c6c4',
sha256: 'f21eb5af2925a6d721ba45f73f51718bb9f43c36f9230e03655210251df38911',
blob: 'a18a00fc1e8dda0c427cd1a4864d896044d7b3df',
sha256: 'b28f4700104c70690b0068edfdd435f4b4810043cdd7ba23e868b5e782cf500a',
},
{
path: '.github/workflows/client-v1-conformance.yml',
Expand Down
76 changes: 76 additions & 0 deletions src/specification-guards.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { execFileSync } from 'node:child_process';
import { createHash } from 'node:crypto';
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
import { resolve } from 'node:path';
import { runInNewContext } from 'node:vm';

type CapabilityFile = {
$schema?: string;
Expand Down Expand Up @@ -1239,6 +1240,81 @@ describe('Phase 1 specification guards', () => {
expect(workflow).toContain('cancel-in-progress: ${' + "{ github.ref_name != 'main' }}");
});

it('cancels superseded Windows supervision without dropping its eligibility gates', () => {
const workflow = readText('.github/workflows/ci.yml');
const job = workflow.split('\n windows-supervisor-behavior:\n')[1];
expect(job).toBeDefined();
const condition = job?.match(/\n {4}if: >-\n([\s\S]*?)\n {4}timeout-minutes:/)?.[1];
expect(condition?.trim()).toBe(
"!cancelled() && needs.rust.result == 'success'\n" +
" && needs.changes.outputs.docs_only != 'true'\n" +
" && ((github.event_name == 'push' && github.ref == 'refs/heads/main')\n" +
" || contains(github.event.pull_request.labels.*.name, 'ci:full'))",
);
// Cancellation must not remove the independent conformance cleanup steps.
expect(workflow).toContain(
"if: always() && hashFiles('test-results/phase1-conformance/report.json') != ''",
);
expect(workflow).toContain(
"if: always() && steps.secret-scan.outcome == 'success' && steps.phase1-keychain-cleanup.outcome == 'success'",
);
expect(workflow.match(/^ {8}if: always\(\)$/gm)).toHaveLength(2);
});

it.each([
['labelled PR', 'success', false, false, 'pull_request', 'refs/pull/308/merge', true, true],
['failed Rust', 'failure', false, false, 'pull_request', 'refs/pull/308/merge', true, false],
['skipped Rust', 'skipped', false, false, 'pull_request', 'refs/pull/308/merge', true, false],
[
'cancelled Rust',
'cancelled',
false,
false,
'pull_request',
'refs/pull/308/merge',
true,
false,
],
['cancelled PR', 'success', true, false, 'pull_request', 'refs/pull/308/merge', true, false],
['docs-only PR', 'success', false, true, 'pull_request', 'refs/pull/308/merge', true, false],
['unlabelled PR', 'success', false, false, 'pull_request', 'refs/pull/308/merge', false, false],
['main push', 'success', false, false, 'push', 'refs/heads/main', false, true],
['main with failed Rust', 'failure', false, false, 'push', 'refs/heads/main', false, false],
['cancelled main', 'success', true, false, 'push', 'refs/heads/main', false, false],
['feature push', 'success', false, false, 'push', 'refs/heads/feature', false, false],
] as const)(
'evaluates Windows supervision eligibility for %s',
(_name, rust, cancelled, docsOnly, eventName, ref, full, expected) => {
const workflow = readText('.github/workflows/ci.yml');
const job = workflow.split('\n windows-supervisor-behavior:\n')[1];
const condition = job?.match(/\n {4}if: >-\n([\s\S]*?)\n {4}timeout-minutes:/)?.[1];
expect(condition).toBeDefined();
if (!condition) throw new Error('Missing Windows supervision condition');
// Evaluate this boolean/string subset, not GitHub cancellation scheduling.
// The exact-expression guard above prevents unsupported syntax drifting in.
const expression = condition.replace(
'github.event.pull_request.labels.*.name',
'github.event.pull_request.labels.map(label => label.name)',
);
const result: unknown = runInNewContext(
expression,
{
always: () => true,
cancelled: () => cancelled,
contains: (values: string[], value: string) => values.includes(value),
needs: { rust: { result: rust }, changes: { outputs: { docs_only: String(docsOnly) } } },
github: {
event_name: eventName,
ref,
event: { pull_request: { labels: full ? [{ name: 'ci:full' }] : [] } },
},
},
{ timeout: 1000 },
);
expect(result).toBe(expected);
},
);

it('reads counterpart repositories with a token that does not depend on their visibility', () => {
// The default GITHUB_TOKEN is scoped to this repository, so it can only
// read a counterpart that happens to be public. Relying on that has taken
Expand Down
Loading