Skip to content

Commit cfe3833

Browse files
committed
Trigger the standalone Website workflow at the end of Benchmarks
1 parent b58fab1 commit cfe3833

15 files changed

Lines changed: 169 additions & 35 deletions

File tree

‎.github/workflows/AGENTS.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,3 +76,7 @@
7676
## Separate Website workflow, owner correction 2026-10-06
7777

7878
- The owner's explicit correction supersedes the earlier three-workflow limit and every CI/Benchmarks website placement: `Build and Tests` (`ci.yml`) owns full solution build, repository checks and ordinary tests; `Website` (`website.yml`) independently owns site qualification/build/Pages on trusted main pushes, manual dispatch and completed Benchmarks events. `Benchmarks` and manual `Release` retain their existing scopes. Website uses ready authenticated metrics when available and publishes the fully qualified content-only site when none exist; all applicable source, browser, coverage, freshness and permission gates remain mandatory.
79+
80+
## Final Benchmarks Website trigger, owner clarification 2026-10-06
81+
82+
- Benchmarks ends with only a bounded dispatch of `website.yml` on main, after its aggregation dependencies settle even when benchmark work fails. Keep site building, tests and Pages entirely in Website; remove its `workflow_run` subscription. Only the final trigger job receives `actions: write`. Website authenticates an optional triggering run against GitHub and waits boundedly for completion before its existing newest-ready selection; no input supplies trusted measurements. Push/manual Website publication and all original qualification/freshness gates remain independent.

‎.github/workflows/benchmarks.yml‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -439,3 +439,21 @@ jobs:
439439
path: ${{ runner.temp }}/open-loop-aggregate/**
440440
if-no-files-found: warn
441441
retention-days: 90
442+
website-trigger:
443+
name: Trigger Website
444+
needs: [comparison-aggregate]
445+
if: >-
446+
always() && !cancelled() &&
447+
github.repository == 'managedcode/KeyLoad' && github.event.repository.id == 477801965 &&
448+
github.ref == 'refs/heads/main' &&
449+
(github.event_name == 'push' || github.event_name == 'workflow_dispatch')
450+
runs-on: ubuntu-latest
451+
timeout-minutes: 5
452+
permissions: {actions: write}
453+
env:
454+
GH_TOKEN: ${{ github.token }}
455+
GH_REPO: ${{ github.repository }}
456+
BENCHMARK_RUN_ID: ${{ github.run_id }}
457+
steps:
458+
- name: Start the separate Website workflow
459+
run: timeout 120s gh api --method POST "repos/$GH_REPO/actions/workflows/website.yml/dispatches" -f ref=main -f "inputs[benchmark_run_id]=$BENCHMARK_RUN_ID"

‎.github/workflows/website.yml‎

Lines changed: 44 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,12 @@ on:
33
push:
44
branches: [main]
55
workflow_dispatch:
6-
workflow_run:
7-
workflows: [Benchmarks]
8-
types: [completed]
6+
inputs:
7+
benchmark_run_id:
8+
description: Benchmark run to await (optional)
9+
required: false
10+
type: string
11+
default: ''
912
permissions:
1013
contents: read
1114
jobs:
@@ -16,16 +19,8 @@ jobs:
1619
cancel-in-progress: false
1720
if: >-
1821
github.repository == 'managedcode/KeyLoad' && github.event.repository.id == 477801965 &&
19-
((github.ref == 'refs/heads/main' &&
20-
(github.event_name == 'push' || github.event_name == 'workflow_dispatch')) ||
21-
(github.event_name == 'workflow_run' &&
22-
github.event.workflow_run.head_repository.id == 477801965 &&
23-
github.event.workflow_run.head_branch == 'main' &&
24-
github.event.workflow_run.name == 'Benchmarks' &&
25-
github.event.workflow_run.path == '.github/workflows/benchmarks.yml' &&
26-
github.event.workflow_run.status == 'completed' &&
27-
(github.event.workflow_run.event == 'push' || github.event.workflow_run.event == 'workflow_dispatch') &&
28-
(github.event.workflow_run.conclusion == 'success' || github.event.workflow_run.conclusion == 'failure')))
22+
github.ref == 'refs/heads/main' &&
23+
(github.event_name == 'push' || github.event_name == 'workflow_dispatch')
2924
runs-on: ubuntu-latest
3025
timeout-minutes: 180
3126
permissions: {contents: read, actions: read}
@@ -37,6 +32,42 @@ jobs:
3732
GH_TOKEN: ${{ github.token }}
3833
GH_REPO: ${{ github.repository }}
3934
steps:
35+
- name: Await the triggering benchmark run
36+
if: github.event_name == 'workflow_dispatch' && inputs.benchmark_run_id != ''
37+
timeout-minutes: 5
38+
env:
39+
BENCHMARK_RUN_ID: ${{ inputs.benchmark_run_id }}
40+
run: |
41+
[[ "$BENCHMARK_RUN_ID" =~ ^[1-9][0-9]*$ && ${#BENCHMARK_RUN_ID} -le 20 ]]
42+
benchmark_deadline=$((SECONDS + 270))
43+
benchmark_poll=0
44+
while ((SECONDS < benchmark_deadline)); do
45+
benchmark_remaining=$((benchmark_deadline - SECONDS))
46+
((benchmark_remaining > 0)) || break
47+
benchmark_request_timeout=$((benchmark_remaining < 10 ? benchmark_remaining : 10))
48+
if ! benchmark_metadata=$(timeout "${benchmark_request_timeout}s" gh api "repos/$GH_REPO/actions/runs/$BENCHMARK_RUN_ID"); then
49+
printf '%s\n' 'Unable to read the triggering benchmark run within the bounded request.' >&2
50+
exit 1
51+
fi
52+
jq -e --arg run "$BENCHMARK_RUN_ID" '
53+
(.id | tostring) == $run and
54+
.repository.id == 477801965 and .repository.full_name == "managedcode/KeyLoad" and
55+
.head_repository.id == 477801965 and .head_repository.full_name == "managedcode/KeyLoad" and
56+
.head_branch == "main" and .name == "Benchmarks" and
57+
.path == ".github/workflows/benchmarks.yml" and
58+
(.event == "push" or .event == "workflow_dispatch") and
59+
(.head_sha | test("^[a-f0-9]{40}$"))' <<< "$benchmark_metadata" > /dev/null
60+
if [[ "$(jq -r .status <<< "$benchmark_metadata")" == completed ]]; then
61+
exit 0
62+
fi
63+
benchmark_poll=$((benchmark_poll + 1))
64+
printf 'Waiting for benchmark completion (poll %s).\n' "$benchmark_poll"
65+
benchmark_remaining=$((benchmark_deadline - SECONDS))
66+
((benchmark_remaining > 0)) || break
67+
sleep "$((benchmark_remaining < 5 ? benchmark_remaining : 5))"
68+
done
69+
printf '%s\n' 'The triggering benchmark run did not complete within the bounded wait.' >&2
70+
exit 1
4071
- name: Download source code
4172
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
4273
with:

‎AGENTS.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -519,3 +519,7 @@ A bounded website qualification candidate contains the20-project historical runt
519519

520520
- GitHub Actions MUST expose four plainly named workflows: `Build and Tests` builds the complete solution and runs repository checks and ordinary tests; `Benchmarks` produces authenticated database-comparison JSON; `Website` independently builds, qualifies and deploys the site; `Release` remains the prepared manual product-release workflow. Website jobs and Benchmarks completion triggers MUST NOT be part of Build and Tests. This explicit correction supersedes the earlier three-workflow limit, the CI display name and every placement of website publication inside CI or Benchmarks.
521521
- Website MUST publish after trusted main source changes or manual dispatch independently of Build and Tests and benchmark success. Consume the newest ready authenticated Benchmarks aggregate when available; otherwise qualify and publish the site without benchmark figures. New ready benchmark data triggers a fresh Website run. Preserve source freshness, original metric provenance, complete applicable website qualification and least-privilege Pages deployment; absence of measurements is not a website failure.
522+
523+
## Final Benchmarks Website trigger, owner clarification 2026-10-06
524+
525+
- Benchmarks MUST finish with only a bounded dispatch of the separate Website workflow; it MUST NOT build, test or deploy the site itself. This supersedes Website's `workflow_run` completion subscription. Confine dispatch permission to that final trigger job, keep main/manual Website publication independent, and authenticate any supplied producer run through GitHub before bounded completion waiting and newest-ready selection. A trigger input is not metric provenance or permission to bypass website qualification.

‎docs/ADR/ADR-112-independent-website-publication.md‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,3 +105,37 @@ workflow name to Build and Tests and its four actual readable job labels. It
105105
retains ci.yml, the complete exact-source/current-attempt/success requirement,
106106
unique jobs and immutable release protections. Static contract/AST review is the
107107
allowed preparation evidence; no product Release is dispatched to verify it.
108+
109+
## Final Benchmarks dispatch, owner clarification 2026-10-06
110+
111+
REQ/AC-BC-WEB-007 supersedes the `workflow_run` subscription. Benchmarks' final
112+
`website-trigger` job depends on the settled `comparison-aggregate` join and uses
113+
`always() && !cancelled()` plus trusted own-main repository/event admission.
114+
Its only effect is dispatching the separate Website workflow on main; all site
115+
source, tests, builds and Pages remain Website-owned. Confine `actions: write` to
116+
that trigger job, keep every database job read-only, and dispatch after successful
117+
or failed benchmark work. Trigger failure remains visible and is never fabricated
118+
as publication success. Build and Tests gains no Website dependency.
119+
120+
The dispatch passes optional `benchmark_run_id` only to resolve the brief race
121+
before its producer workflow becomes completed. Website authenticates that run
122+
against original GitHub metadata (own repository/id, main, Benchmarks name/path,
123+
push/manual event and valid source) and waits at most five minutes before existing
124+
newest-ready selection. Failure/cancellation does not supply metrics; invalid
125+
metadata, API errors and timeout fail closed. Empty manual input skips waiting.
126+
The run input neither selects authoritative metrics nor bypasses archive or
127+
source/tuple freshness. Current executor events are push and workflow_dispatch;
128+
retired workflow_run admission is rejected while immutable event validation
129+
fixtures remain history.
130+
131+
TASK-WEB-TRIGGER-001 lead updates root/local policy and this REQ/AC contract first.
132+
TASK-WEB-TRIGGER-002 lead owns benchmarks.yml final dispatch, website.yml input,
133+
bounded admission/wait and native executor-event contract; tests cover manual
134+
admission/retired event rejection using unchanged controlled objects and a healthy
135+
follow-up through the actual context API. TASK-WEB-TRIGGER-003 read-only reviewer
136+
audits the trust/permission/race contract; lead joins static actionlint/YAML graph,
137+
Aspire regressions, canonical build/format and genuine final dispatch plus separate
138+
Website/Pages evidence. Preserve unrelated checkout work. No package/data change;
139+
rollback reverts this coherent dispatch contract to the earlier completion hook
140+
without rewriting evidence or metric archives. Remain Accepted until original
141+
required qualification/provider gates have passed.

‎docs/Architecture.md‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1135,12 +1135,13 @@ flowchart LR
11351135
## Independent optional benchmark website publication
11361136

11371137
[ADR-112](ADR/ADR-112-independent-website-publication.md) and
1138-
[REQ/AC-BC-WEB-001..006](Features/BenchmarkComparisons.md) let Website publish website
1138+
[REQ/AC-BC-WEB-001..007](Features/BenchmarkComparisons.md) let Website publish website
11391139
source independently. The latest ready authenticated benchmark aggregate enriches
11401140
the website when available; absence emits no metric catalog or figures. Content
11411141
and measured artifacts have distinct complete applicable qualification, and
11421142
predeploy rechecks actual website/control source plus ready-data identity or null.
1143-
Completed own-main benchmarks trigger another independent Website consumer.
1143+
The final own-main Benchmarks job only dispatches the independent Website workflow;
1144+
Website authenticates and awaits that run's completion before selecting ready data.
11441145

11451146
```mermaid
11461147
flowchart LR

‎docs/Features/BenchmarkComparisons.md‎

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1253,8 +1253,8 @@ REQ-BC-WEB-006 / AC-BC-WEB-006: expose Build and Tests, Benchmarks, Website and
12531253
Release. Build and Tests runs the complete solution build and every existing
12541254
ordinary/analyzer/scalar/recovery/Aspire RF3 test gate with no website jobs or
12551255
Benchmarks completion trigger. Website alone runs the existing qualified site
1256-
build/deployment on trusted main push/manual and completed own-main Benchmarks
1257-
producers, with no dependency on Build and Tests or successful benchmarks.
1256+
build/deployment on trusted main push/manual, including the final Benchmarks
1257+
dispatch, with no dependency on Build and Tests or successful benchmarks.
12581258
Optional metrics, rejection of invalid selected evidence and the no-data path
12591259
remain AC-BC-WEB-001..005. Native executor authentication must accept only Website
12601260
at `.github/workflows/website.yml` and reject the former CI executor.
@@ -1379,3 +1379,25 @@ Align that exact frozen count with the existing complete manifest, retaining byt
13791379
comparison, sorted uniqueness, regular-path and per-file hash checks. This is a
13801380
static workflow contract repair under AC-BC-WEB-004/005; no file or coverage source
13811381
is removed and no provider pass is claimed until the next genuine run succeeds.
1382+
1383+
### Final Benchmarks trigger, owner clarification 2026-10-06
1384+
1385+
REQ-BC-WEB-007 / AC-BC-WEB-007: the final Benchmarks job only dispatches the
1386+
separate Website workflow on main after aggregation dependencies settle. It runs
1387+
on trusted own-main push/manual even when benchmark checks or aggregation fail,
1388+
and never builds/tests/deploys the website. Scope `actions: write` to that job;
1389+
retain read-only database jobs and unchanged workloads. Website uses only push
1390+
and workflow_dispatch events, with its independent source publication intact.
1391+
1392+
Dispatch supplies only an optional `benchmark_run_id` for completion waiting.
1393+
Website authenticates the actual original run's repository, branch, workflow
1394+
name/path, event and source through GitHub; bounded waiting resolves the race
1395+
between dispatch and producer completion before newest-ready selection. Reject
1396+
invalid/foreign metadata and timeout, preserve failure/absence handling and
1397+
source/tuple freshness. The input never selects metric authority or replaces
1398+
archive provenance. Empty manual input needs no producer wait. Existing native
1399+
TUnit executor operations cover manual admission and retired executor-event
1400+
rejection, unchanged inputs and a healthy follow-up. Static actionlint and YAML
1401+
graph review verify infrastructure; a genuine final dispatch and separate
1402+
workflow_dispatch Website/Pages run supply provider evidence. Ordered tasks and
1403+
ownership are in ADR-112 TASK-WEB-TRIGGER-001..003.

‎docs/Features/ReleaseDelivery.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,8 @@ flowchart LR
5757
Main[Own main push or manual] --> Benchmarks[All native load comparisons]
5858
Benchmarks --> Aggregate[Complete authenticated JSON aggregate]
5959
WebSource[Trusted main source or manual] --> Website[Separate Website workflow]
60-
Aggregate --> Website
60+
Aggregate --> Trigger[Dispatch Website only]
61+
Trigger --> Website
6162
Website --> Qualify[Applicable site tests browser coverage]
6263
Qualify --> Deploy[Publish with ready metrics or without figures]
6364
Manual[Manual own-main Release] --> Version[UTC dated reservation]

‎docs/implementation/status.json‎

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4884,7 +4884,7 @@
48844884
"siteTestsFormat": "passed",
48854885
"canonicalBuild": "passed_zero_warnings_zero_errors_final_source",
48864886
"canonicalFormat": "failed_unrelated_BackupRestore_AtomicPartitionRosterFixture_whitespace",
4887-
"staticWorkflowGovernance": "passed",
4887+
"staticWorkflowGovernance": "workflow_graph_actionlint_passed; working_tree_governance_failed_unrelated_owner_migration_rule_changed_preserved_root_prefix; scoped_index_prefix_unchanged",
48884888
"contentOnlyAspire": {
48894889
"status": "passed_development_only",
48904890
"tests": 5,
@@ -4901,11 +4901,11 @@
49014901
"alwaysRunExecutorRegressions": {
49024902
"status": "passed_development_only",
49034903
"suite": "Aspire_unit_SiteOptionalBenchmarkSelectionTests",
4904-
"tests": 22,
4905-
"passed": 22,
4904+
"tests": 24,
4905+
"passed": 24,
49064906
"skipped": 0,
4907-
"executorCases": 3,
4908-
"trxSha256": "df3991c8bd94b90a3a95a9ba952e68478111e3f09764f6787b4a4cdb46ddb94d"
4907+
"executorCases": 5,
4908+
"trxSha256": "2ed45090c82db5fd2748973794b574932d54ee062f9bc5a013b60054b9516206"
49094909
},
49104910
"firstNativeRun": {
49114911
"source": "6c20cf4fba946afc123fd2fd29960d7995c248f4",
@@ -4929,6 +4929,26 @@
49294929
"selectedBenchmarkRun": 37184989107,
49304930
"selectedMeasuredSource": "73aebfd3f72695357834599e813aba77b9e274ad",
49314931
"qualification": "capture_only_not_site_or_current_database_qualification"
4932+
},
4933+
"finalBenchmarkTrigger": {
4934+
"requirement": "REQ-BC-WEB-007",
4935+
"producerJob": "website-trigger",
4936+
"dependsOn": "comparison-aggregate",
4937+
"onlyEffect": "dispatch_Website_main",
4938+
"dispatchAfterFailureOrSkip": true,
4939+
"dispatchAfterWholeWorkflowCancellation": false,
4940+
"actionsWrite": "only_final_trigger_job",
4941+
"websiteEvents": [
4942+
"push",
4943+
"workflow_dispatch"
4944+
],
4945+
"workflowRunSubscription": false,
4946+
"producerInput": "benchmark_run_id_wait_only_not_metric_authority",
4947+
"authenticatedWaitDeadlineSeconds": 270,
4948+
"stepTimeoutMinutes": 5,
4949+
"staticContract": "passed",
4950+
"nativeManualAdmissionAndRetiredEventRejection": "passed_Aspire_unit",
4951+
"provider": "pending_genuine_final_dispatch_and_separate_Pages_run"
49324952
}
49334953
}
49344954
}

‎scripts/Features/BenchmarkComparisons/site-isolated-github-capture.mjs‎

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import path from 'node:path';
2-
import { isDeepStrictEqual } from 'node:util';
32
import { absolutePath, existingPath } from './aggregate-files.mjs';
43
import { createDirectory } from './image-bundle-files.mjs';
54
import { GH } from './isolated-github-contract.mjs';
@@ -17,11 +16,6 @@ import { siteMetadataFiles } from './site-isolated-github-files.mjs';
1716
import { captureHistoricalContract } from './historical-site-evidence.mjs';
1817

1918
async function captureSelectionRuns(directory, context, workflow) {
20-
if (context.trigger !== null) {
21-
const trigger = await captureApi(`${GH.api}/runs/${context.trigger.runId}/attempts/${context.trigger.attempt}`,
22-
path.join(directory, 'trigger-run.json'), false, context);
23-
requireSite(isDeepStrictEqual(selectLatestSiteProducer([trigger], workflow), context.trigger));
24-
}
2519
if (context.requestedRun !== null) {
2620
const pinned = await captureApi(`${GH.api}/runs/${context.requestedRun}`,
2721
path.join(directory, SITE_GH.pinnedRunCapture), false, context);

0 commit comments

Comments
 (0)