fix(yardstick): use counts an order only when a Voyage record names its id (green)

The slug join (voyageSlugs: every slug in every stats file against the order's
`slug:`, which 0 of 690 orders carried) is replaced by an order-id join:
orderRuns() indexes trekbrief-stats.jsonl's order_id <-> slug, readOrders()
keeps each order's file stem and its own-line arbeidsform marker. Per new-work
order: arbeidsform: ordre = declared NO; one record under one slug that names
no other order = YES; no record or disputed = not measured, never a no
(PM 04.10, point 1). value = YES, denominator = YES + declared NO.

USE_COUNTING_SINCE = null (operator 04.10: the tool now, the counting after
the work is declared done): until it is set the row is NOT COUNTABLE and every
order not measured; once set, the window starts at the latest of cutoff,
MARKER_SINCE and that date. facts.ordersNamedByARecord is the known-positive
diagnostic. The header names the measured-share bias and the false-positive
class (Goal iii) next to the number. The CLI guard compares real paths.

Real queue 2026-10-04: use NOT COUNTABLE, 695 of 695 orders not measured,
0 named by a record. yardstick.test.mjs 43/43.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-10-04 19:53:31 +02:00
commit 5dac5b59be
Signed by: ktg
SSH key fingerprint: SHA256:JakMjO6FTBBzN0Bhfj9saOoEjaFxlSdYuZQQpM/lF9Q

View file

@ -2,13 +2,18 @@
// scripts/yardstick.mjs
// The yardstick - Voyage's three numbers, each with a denominator read from its SOURCE:
//
// use new work that went through Voyage (the order queue: orders dated on or after
// the later of the cutoff and MARKER_SINCE
// whose `<!-- loesningsvalg: plan|prove -->`
// marker says new work; counted = those with
// a Voyage run whose slug equals the order's
// `slug:`. Unmarked or earlier orders are
// not measured, never a no)
// use new work that went through Voyage (the order queue joined to trekbrief-stats.jsonl
// by order id. New work = an order whose
// `<!-- loesningsvalg: plan|prove -->` marker
// says so, dated on or after the latest of the
// cutoff, MARKER_SINCE and USE_COUNTING_SINCE.
// YES = a /trekbrief record names the order's
// id (its file stem) under one slug that names
// no other order. NO = the order's own
// `<!-- arbeidsform: ordre -->` line. No record,
// or a disputed one, is not measured, never a
// no. USE_COUNTING_SINCE unset = counting has
// not started: not countable)
// trust reviews in a row with nothing that (trekreview-stats.jsonl, read as JSON and
// stops a release ordered by `ts` - file order is not
// chronological; value = the current ALLOW streak)
@ -23,6 +28,13 @@
// checks `typeof`. The gate fells it when it
// refuses to close the probe on that tree.)
//
// What `use` can and cannot see (PM 04.10): the "no" side is only what orders
// declare, so undeclared non-Voyage work is not measured and the value covers the
// measured share only. Known false-positive class: an `arbeidsform: voyage`
// order claimed while an unrelated /trekbrief runs would be named by that
// record; PM chose no repo-mailbox change (the record must itself name the id) -
// a strong link would need the claim to name the claiming session.
//
// A number the source cannot give is NOT COUNTABLE - never 0. The verdict over the first five
// deliveries is not judged by anything yet, and says so. Exit 0 = every number countable, every
// mutant felled and the verdict judged and met; 1 = anything short of that; 2 = usage error.
@ -36,7 +48,7 @@ import {
realpathSync, rmSync,
} from 'node:fs';
import { homedir, tmpdir } from 'node:os';
import { join, resolve, dirname, relative, sep } from 'node:path';
import { join, resolve, dirname, relative, sep, basename } from 'node:path';
import { fileURLToPath } from 'node:url';
import { spawnSync } from 'node:child_process';
import { evaluateCheck, loadRegistry, TEST_TIMEOUT_MS } from './end-state-gate.mjs';
@ -46,11 +58,15 @@ const REPO_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..');
export const DEFAULT_CUTOFF = '2026-09-21';
// The day pre-order-loesningsvalg.sh began refusing an order without the marker (PM 02.10).
export const MARKER_SINCE = '2026-09-28';
// The day `use` starts counting. Unset until the operator declares the work done
// (operator 04.10): until then `use` is NOT COUNTABLE and every order not measured.
export const USE_COUNTING_SINCE = null;
export const DEFAULT_COORD = join(homedir(), '.claude', 'coord');
export const DEFAULT_DATA = process.env.CLAUDE_PLUGIN_DATA
|| join(homedir(), '.claude', 'plugins', 'data', 'voyage-ktg-plugin-marketplace');
const REVIEW_FILE = 'trekreview-stats.jsonl';
const BRIEF_FILE = 'trekbrief-stats.jsonl';
const toPosix = (p) => p.split(sep).join('/');
function walk(dir) {
@ -95,15 +111,23 @@ export function loadStats(dataDir) {
return { dir: dataDir, missing: false, files };
}
// The slugs of every Voyage run the stats know about.
function voyageSlugs(stats) {
const slugs = new Set();
for (const f of stats.files) {
for (const r of f.records) {
if (r && typeof r.slug === 'string' && r.slug !== '') slugs.add(r.slug);
}
/**
* The orders /trekbrief runs named: order id -> its slugs, and slug -> its order
* ids. Only trekbrief-stats.jsonl - the one record that carries order_id.
*/
export function orderRuns(stats) {
const byOrder = new Map();
const bySlug = new Map();
const file = stats.files.find((f) => f.name === BRIEF_FILE);
for (const r of file ? file.records : []) {
if (!r || typeof r.order_id !== 'string' || r.order_id === '') continue;
const slug = typeof r.slug === 'string' ? r.slug : '';
if (!byOrder.has(r.order_id)) byOrder.set(r.order_id, new Set());
byOrder.get(r.order_id).add(slug);
if (!bySlug.has(slug)) bySlug.set(slug, new Set());
bySlug.get(slug).add(r.order_id);
}
return slugs;
return { byOrder, bySlug };
}
const notCountable = (detail, extra = {}) => ({ countable: false, value: null, detail, ...extra });
@ -172,6 +196,8 @@ function frontmatter(text) {
const MARKER = /^<!--\s*loesningsvalg:\s*([^;\s>]+)/m;
const NEW_WORK = new Set(['plan', 'prove']);
const NOT_NEW = new Set(['ikke-nytt']);
// The work-form marker dispatch writes on its own line; `ordre` declares the work is not done through Voyage.
const ARBEIDSFORM = /^<!--\s*arbeidsform:\s*([^;\s>]+)/m;
/** Every order file under `<coord>/<repo>/orders/` - pending, `archive/` and `claimed/`. */
export function readOrders(coordDir) {
@ -186,7 +212,12 @@ export function readOrders(coordDir) {
files++;
const text = readFileSync(path, 'utf8');
const fm = frontmatter(text);
if (fm && fm['order-id']) orders.push({ path, repo, fm, marker: text.match(MARKER)?.[1] });
if (fm && fm['order-id']) {
orders.push({
path, repo, fm, id: basename(path, '.md'),
marker: text.match(MARKER)?.[1], arbeidsform: text.match(ARBEIDSFORM)?.[1],
});
}
else unreadable.push(path);
}
}
@ -194,48 +225,75 @@ export function readOrders(coordDir) {
return { dir: coordDir, files, orders, unreadable };
}
export function measureUse({ queue, slugs, cutoff }) {
const from = cutoff > MARKER_SINCE ? cutoff : MARKER_SINCE;
export function measureUse({ queue, runs, cutoff, countingSince }) {
const started = typeof countingSince === 'string' && countingSince !== '';
const from = [cutoff, MARKER_SINCE, ...(started ? [countingSince] : [])].reduce((a, b) => (b > a ? b : a));
const glob = `${join(queue.dir, '*', 'orders')}/**/*.md`;
const base = {
id: 'use', label: 'new work that went through Voyage',
source: `${join(queue.dir, '*', 'orders')}/**/*.md (loesningsvalg marker, from ${from})`,
source: started
? `${glob} joined to ${BRIEF_FILE} by order id (loesningsvalg marker, from ${from})`
: `${glob} joined to ${BRIEF_FILE} by order id (counting not started - USE_COUNTING_SINCE is not set)`,
};
const named = queue.orders.filter((o) => runs.byOrder.has(o.id)).length;
const withMarker = queue.orders.filter((o) => o.marker !== undefined);
const perMailbox = {};
for (const o of queue.orders) perMailbox[o.repo] = (perMailbox[o.repo] ?? 0) + 1;
const dot = Object.entries(perMailbox).filter(([k]) => k.startsWith('.'));
const files = `${queue.files} order files in ${Object.keys(perMailbox).length} mailboxes`
+ (dot.length ? ` (dot-mailboxes ${dot.map(([k, v]) => `${k} ${v}`).join(' · ')} - a shell \`*\` skips them)` : '');
const unreadableNote = queue.unreadable.length ? `; ${queue.unreadable.length} order file(s) without frontmatter` : '';
if (!started) {
const facts = { scanned: queue.orders.length, files: queue.files, unreadable: queue.unreadable.length,
withMarker: withMarker.length, notMeasured: queue.orders.length, withVoyageRun: 0,
ordersNamedByARecord: named, countingSince: null };
return { ...base, denominator: 0, facts, ...notCountable(
`${files}${unreadableNote}; counting has not started (USE_COUNTING_SINCE is not set) - all ${queue.orders.length} orders not measured`
+ `; ${named} order(s) already named by a trekbrief record`) };
}
const inWindow = queue.orders.filter((o) => String(o.fm.date ?? '').slice(0, 10) >= from);
const newWork = inWindow.filter((o) => NEW_WORK.has(o.marker));
const notNew = inWindow.filter((o) => NOT_NEW.has(o.marker));
const unmarked = inWindow.length - newWork.length - notNew.length;
const beforeMarker = queue.orders.length - inWindow.length;
const through = newWork.filter((o) => o.fm.slug && slugs.has(o.fm.slug));
const withoutSlug = newWork.filter((o) => !o.fm.slug).length;
const yes = [];
let declaredNo = 0;
let noRecord = 0;
let disputed = 0;
for (const o of newWork) {
if (o.arbeidsform === 'ordre') { declaredNo++; continue; }
const slugs = runs.byOrder.get(o.id);
if (!slugs) { noRecord++; continue; }
const [slug] = slugs;
if (slugs.size !== 1 || runs.bySlug.get(slug).size !== 1) { disputed++; continue; }
yes.push(o.id);
}
const measured = yes.length + declaredNo;
const facts = {
scanned: queue.orders.length, files: queue.files, unreadable: queue.unreadable.length,
withMarker: withMarker.length, newWork: newWork.length, notNew: notNew.length,
unmarked, beforeMarker, notMeasured: unmarked + beforeMarker, withVoyageRun: through.length, withoutSlug,
unmarked, beforeMarker, notMeasured: unmarked + beforeMarker + noRecord + disputed,
withVoyageRun: yes.length, declaredNo, noRecord, disputed, matched: yes,
ordersNamedByARecord: named, countingSince,
};
const perMailbox = {};
for (const o of queue.orders) perMailbox[o.repo] = (perMailbox[o.repo] ?? 0) + 1;
const dot = Object.entries(perMailbox).filter(([k]) => k.startsWith('.'));
const census = `${queue.files} order files in ${Object.keys(perMailbox).length} mailboxes`
+ (dot.length ? ` (dot-mailboxes ${dot.map(([k, v]) => `${k} ${v}`).join(' · ')} - a shell \`*\` skips them)` : '')
+ `; ${withMarker.length} of ${queue.orders.length} orders carry a loesningsvalg marker`
const census = `${files}; ${withMarker.length} of ${queue.orders.length} orders carry a loesningsvalg marker`
+ `; from ${from}: ${newWork.length} new (plan|prove) · ${notNew.length} not new (ikke-nytt)`
+ `; ${facts.notMeasured} not measured (${beforeMarker} before ${from} · ${unmarked} unmarked or unknown value)`
+ (queue.unreadable.length ? `; ${queue.unreadable.length} order file(s) without frontmatter` : '');
+ `; ${facts.notMeasured} not measured (${beforeMarker} before ${from} · ${unmarked} unmarked or unknown value`
+ ` · ${noRecord} new without a trekbrief record · ${disputed} disputed)${unreadableNote}`;
if (newWork.length === 0) {
return { ...base, denominator: 0, facts, ...notCountable(
`${census}; 0 new-work orders dated >= ${from} - the denominator is empty`) };
}
// The numerator joins an order to a Voyage run by its `slug:`. An order without one cannot
// match anything, so its "no" would be the missing key talking - the numerator is not measured.
if (withoutSlug > 0) {
return { ...base, denominator: newWork.length, facts, ...notCountable(
`${census}; ${withoutSlug} of ${newWork.length} new-work orders carry no slug - the numerator is not measured`
+ ` (${through.length} matched a Voyage run)`) };
if (measured === 0) {
return { ...base, denominator: 0, facts, ...notCountable(
`${census}; none of ${newWork.length} new-work orders is measured - a yes needs a trekbrief record naming the order,`
+ ' a no the order\'s own arbeidsform: ordre') };
}
return {
...base, countable: true, value: through.length, denominator: newWork.length, facts,
detail: `${census}; ${through.length} of ${newWork.length} new-work orders have a Voyage run in the stats with the order's slug`,
...base, countable: true, value: yes.length, denominator: measured, facts,
detail: `${census}; of ${newWork.length} new-work orders: ${yes.length} through Voyage (a trekbrief record names the order)`
+ ` · ${declaredNo} declared outside Voyage (arbeidsform: ordre); value = ${yes.length} of ${measured} measured`
+ ' - the measured share only: undeclared non-Voyage work is not measured, never a no',
};
}
@ -425,10 +483,10 @@ const VERDICT = {
detail: 'the verdict needs the first five deliveries through the new Voyage; nothing counts a delivery yet',
};
export function measureAll({ root = REPO_ROOT, coordDir = DEFAULT_COORD, dataDir = DEFAULT_DATA, cutoff = DEFAULT_CUTOFF, mutantOptions = {} } = {}) {
export function measureAll({ root = REPO_ROOT, coordDir = DEFAULT_COORD, dataDir = DEFAULT_DATA, cutoff = DEFAULT_CUTOFF, countingSince = USE_COUNTING_SINCE, mutantOptions = {} } = {}) {
const stats = loadStats(dataDir);
const numbers = [
measureUse({ queue: readOrders(coordDir), slugs: voyageSlugs(stats), cutoff }),
measureUse({ queue: readOrders(coordDir), runs: orderRuns(stats), cutoff, countingSince }),
measureTrust(stats),
measureInterrupts({ stats, callSites: emitterCallSites(root) }),
];
@ -493,6 +551,15 @@ export function main(argv) {
return result.green ? 0 : 1;
}
if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
// Run as a CLI? Compared as real paths, so a symlink or a space in the path still runs it.
function isMain() {
try {
return realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url));
} catch {
return false;
}
}
if (isMain()) {
process.exitCode = main(process.argv.slice(2));
}