Skip to content
This repository was archived by the owner on Oct 26, 2026. It is now read-only.

Commit cbf53d3

Browse files
committed
PROTO(objectui#5403): read the equivalence numbers back through artifact NAMES [verdict]
Three channels have now failed from the measuring session: the Actions log CDN, artifact bodies, and a branch push with the workflow token. Artifact metadata is readable, so the comparison encodes its numbers in the artifact name it uploads. Two comparisons: merged vs unsharded on the same commit (run 32388915404), and merged vs merged across two identical sharded rounds (the noise floor).
1 parent 790bd17 commit cbf53d3

2 files changed

Lines changed: 111 additions & 6 deletions

File tree

‎.github/workflows/proto-5403-shard-coverage.yml‎

Lines changed: 68 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ jobs:
3939
# ── A: the shipped recipe, 4 ways ─────────────────────────────────────────
4040
shard:
4141
name: PROTO shard ${{ matrix.shard }}/4
42-
if: ${{ !contains(github.event.head_commit.message, '[threshold-probe]') && !contains(github.event.head_commit.message, '[diag]') }}
42+
if: ${{ !contains(github.event.head_commit.message, '[threshold-probe]') && !contains(github.event.head_commit.message, '[diag]') && !contains(github.event.head_commit.message, '[verdict]') }}
4343
runs-on: ubuntu-latest
4444
timeout-minutes: 40
4545
strategy:
@@ -82,7 +82,7 @@ jobs:
8282
# and that has to be said out loud rather than shipped as decoration.
8383
probe-no-override:
8484
name: PROTO shard 1/4 WITHOUT the threshold override
85-
if: ${{ !contains(github.event.head_commit.message, '[threshold-probe]') && !contains(github.event.head_commit.message, '[diag]') }}
85+
if: ${{ !contains(github.event.head_commit.message, '[threshold-probe]') && !contains(github.event.head_commit.message, '[diag]') && !contains(github.event.head_commit.message, '[verdict]') }}
8686
runs-on: ubuntu-latest
8787
timeout-minutes: 40
8888
outputs:
@@ -112,7 +112,7 @@ jobs:
112112
# Measurement-only relaxation: the SHIPPED job refuses to merge unless every
113113
# shard is green. Here the point is to obtain a merged report to compare
114114
# against the unsharded one, so a red shard must not abort the measurement.
115-
if: ${{ always() && !contains(github.event.head_commit.message, '[threshold-probe]') && !contains(github.event.head_commit.message, '[diag]') }}
115+
if: ${{ always() && !contains(github.event.head_commit.message, '[threshold-probe]') && !contains(github.event.head_commit.message, '[diag]') && !contains(github.event.head_commit.message, '[verdict]') }}
116116
runs-on: ubuntu-latest
117117
timeout-minutes: 20
118118
outputs:
@@ -185,7 +185,7 @@ jobs:
185185
# ── B: the status quo on the same commit — the baseline to compare against ─
186186
baseline:
187187
name: PROTO unsharded coverage baseline
188-
if: ${{ !contains(github.event.head_commit.message, '[threshold-probe]') && !contains(github.event.head_commit.message, '[diag]') && !contains(github.event.head_commit.message, '[merge-only]') }}
188+
if: ${{ !contains(github.event.head_commit.message, '[threshold-probe]') && !contains(github.event.head_commit.message, '[diag]') && !contains(github.event.head_commit.message, '[merge-only]') && !contains(github.event.head_commit.message, '[verdict]') }}
189189
runs-on: ubuntu-latest
190190
# Instrument, not a gate: production `test-coverage` keeps its 40 minutes.
191191
# A timeout here would truncate the measurement, which is the one thing
@@ -225,7 +225,7 @@ jobs:
225225
compare:
226226
name: PROTO compare merged vs unsharded
227227
needs: [merge, baseline]
228-
if: ${{ (always()) && !contains(github.event.head_commit.message, '[threshold-probe]') && !contains(github.event.head_commit.message, '[diag]') && !contains(github.event.head_commit.message, '[merge-only]') }}
228+
if: ${{ (always()) && !contains(github.event.head_commit.message, '[threshold-probe]') && !contains(github.event.head_commit.message, '[diag]') && !contains(github.event.head_commit.message, '[merge-only]') && !contains(github.event.head_commit.message, '[verdict]') }}
229229
runs-on: ubuntu-latest
230230
timeout-minutes: 10
231231
outputs:
@@ -325,7 +325,7 @@ jobs:
325325
report:
326326
name: PROTO write results
327327
needs: [shard, probe-no-override, merge, baseline, compare, red-arm-shard, red-arm-merge, red-arm-merge-without-verdict]
328-
if: ${{ (always()) && !contains(github.event.head_commit.message, '[threshold-probe]') && !contains(github.event.head_commit.message, '[diag]') && !contains(github.event.head_commit.message, '[merge-only]') }}
328+
if: ${{ (always()) && !contains(github.event.head_commit.message, '[threshold-probe]') && !contains(github.event.head_commit.message, '[diag]') && !contains(github.event.head_commit.message, '[merge-only]') && !contains(github.event.head_commit.message, '[verdict]') }}
329329
runs-on: ubuntu-latest
330330
timeout-minutes: 10
331331
steps:
@@ -534,3 +534,65 @@ jobs:
534534
git add -f proto-results/diag-shard3.md
535535
git commit -m "PROTO diag shard3 output ${GITHUB_RUN_ID}"
536536
git push origin "HEAD:refs/heads/proto-5403-diag-shard3-${GITHUB_RUN_ID}"
537+
538+
# [verdict]: the equivalence numbers, encoded in ARTIFACT NAMES because that
539+
# is the one channel out of a run this session can actually read.
540+
verdict-equivalence:
541+
name: PROTO verdict - merged vs unsharded (same commit)
542+
if: ${{ contains(github.event.head_commit.message, '[verdict]') }}
543+
runs-on: ubuntu-latest
544+
timeout-minutes: 15
545+
steps:
546+
- uses: actions/checkout@v7
547+
- uses: actions/download-artifact@v7
548+
with:
549+
name: proto-coverage-unsharded
550+
path: unsharded
551+
run-id: 32388915404
552+
github-token: ${{ secrets.GITHUB_TOKEN }}
553+
- uses: actions/download-artifact@v7
554+
with:
555+
name: proto-coverage-merged
556+
path: merged
557+
run-id: 32388915404
558+
github-token: ${{ secrets.GITHUB_TOKEN }}
559+
- id: cmp
560+
run: node scripts-proto/verdict.mjs unsharded/coverage-final.json merged/coverage-final.json EQUIV
561+
- run: 'echo "$RESULT" > result.txt'
562+
env:
563+
RESULT: ${{ steps.cmp.outputs.name }}
564+
- uses: actions/upload-artifact@v7
565+
with:
566+
name: ${{ steps.cmp.outputs.name }}
567+
path: result.txt
568+
retention-days: 1
569+
570+
verdict-noise-floor:
571+
name: PROTO verdict - merged vs merged (two identical sharded rounds)
572+
if: ${{ contains(github.event.head_commit.message, '[verdict]') }}
573+
runs-on: ubuntu-latest
574+
timeout-minutes: 15
575+
steps:
576+
- uses: actions/checkout@v7
577+
- uses: actions/download-artifact@v7
578+
with:
579+
name: proto-coverage-merged
580+
path: roundA
581+
run-id: 32388915404
582+
github-token: ${{ secrets.GITHUB_TOKEN }}
583+
- uses: actions/download-artifact@v7
584+
with:
585+
name: proto-coverage-merged
586+
path: roundB
587+
run-id: 32391890554
588+
github-token: ${{ secrets.GITHUB_TOKEN }}
589+
- id: cmp
590+
run: node scripts-proto/verdict.mjs roundA/coverage-final.json roundB/coverage-final.json NOISE
591+
- run: 'echo "$RESULT" > result.txt'
592+
env:
593+
RESULT: ${{ steps.cmp.outputs.name }}
594+
- uses: actions/upload-artifact@v7
595+
with:
596+
name: ${{ steps.cmp.outputs.name }}
597+
path: result.txt
598+
retention-days: 1

‎scripts-proto/verdict.mjs‎

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// THROWAWAY — objectui#5403 measurement instrument. Not to be merged.
2+
// Compares two Istanbul coverage-final.json files and encodes the answer in a
3+
// string safe to use as an ARTIFACT NAME. The measuring session can read
4+
// artifact names through the REST API; it cannot read Actions logs, artifact
5+
// bodies, or (as this instrument found) push a results branch with the
6+
// workflow token. So the name is the channel.
7+
import { readFileSync, existsSync, appendFileSync } from 'node:fs';
8+
9+
const [aPath, bPath, label] = process.argv.slice(2);
10+
const out = (k, v) => { if (process.env.GITHUB_OUTPUT) appendFileSync(process.env.GITHUB_OUTPUT, `${k}=${v}\n`); };
11+
for (const p of [aPath, bPath]) {
12+
if (!existsSync(p)) { out('name', `${label}__MISSING-INPUT`); console.log('missing', p); process.exit(1); }
13+
}
14+
const norm = (o) => { const r = {}; for (const k of Object.keys(o)) r[k.replace(/^.*?\/objectui[^/]*\//, '')] = o[k]; return r; };
15+
const A = norm(JSON.parse(readFileSync(aPath, 'utf8')));
16+
const B = norm(JSON.parse(readFileSync(bPath, 'utf8')));
17+
const ka = new Set(Object.keys(A)), kb = new Set(Object.keys(B));
18+
const onlyA = [...ka].filter((k) => !kb.has(k));
19+
const onlyB = [...kb].filter((k) => !ka.has(k));
20+
const tot = (o) => { let s = 0, c = 0; for (const e of Object.values(o)) for (const v of Object.values(e.s ?? {})) { s++; if (v > 0) c++; } return { s, c }; };
21+
const ta = tot(A), tb = tot(B);
22+
const pct = (x, t) => (t ? (100 * x) / t : 0);
23+
const deltaPP = (pct(tb.c, tb.s) - pct(ta.c, ta.s)).toFixed(4);
24+
let regressed = 0;
25+
for (const k of [...ka].filter((x) => kb.has(x))) {
26+
const ca = Object.values(A[k].s ?? {}).filter((v) => v > 0).length;
27+
const cb = Object.values(B[k].s ?? {}).filter((v) => v > 0).length;
28+
if (cb < ca) regressed++;
29+
}
30+
const name = [
31+
label,
32+
`filesA-${ka.size}`,
33+
`filesB-${kb.size}`,
34+
`onlyA-${onlyA.length}`,
35+
`onlyB-${onlyB.length}`,
36+
`stmtA-${ta.c}of${ta.s}`,
37+
`stmtB-${tb.c}of${tb.s}`,
38+
`stmtDiff-${tb.c - ta.c}`,
39+
`deltaPP-${deltaPP}`.replace('.', 'p').replace('-0p', 'minus0p'),
40+
`regressed-${regressed}`,
41+
].join('__');
42+
console.log(name);
43+
out('name', name);

0 commit comments

Comments
 (0)