ruvnet-brain 4.3.37 → 4.3.38
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/bin/install.mjs +32 -9
- package/kb/forge-update.mjs +1867 -0
- package/package.json +3 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/hooks/hook-contracts.json +5 -5
- package/plugin/scripts/capability-claim-evidence.mjs +11 -2
- package/plugin/scripts/capacity-aware-parallel-work.mjs +71 -15
- package/plugin/scripts/completion-claim-evidence.mjs +262 -0
- package/plugin/scripts/continuation-gate.mjs +105 -21
- package/plugin/scripts/continuation-objective.mjs +15 -0
- package/plugin/scripts/decision-gate.mjs +22 -2
- package/plugin/scripts/duplicate-gate.mjs +503 -0
- package/plugin/scripts/grounding-turn-evidence.mjs +339 -0
- package/plugin/scripts/grounding-turn-gate.mjs +77 -25
- package/plugin/scripts/grounding-turn-mark.mjs +61 -14
- package/plugin/scripts/hook-input.mjs +15 -0
- package/plugin/scripts/host-update.mjs +45 -0
- package/plugin/scripts/nightly-scheduler.mjs +34 -0
- package/plugin/scripts/session-start-budget.mjs +1 -0
- package/plugin/scripts/session-start-core.mjs +15 -2
- package/plugin/scripts/session-start-health.mjs +101 -1
- package/plugin/scripts/session-start-update-plane.mjs +71 -1
- package/scripts/completion-claim-replay.mjs +114 -0
- package/scripts/corpus-canary.mjs +399 -0
- package/scripts/corpus-dispatch-decision.mjs +2 -2
- package/scripts/corpus-promotion.mjs +49 -0
- package/scripts/corpus-reconcile.mjs +27 -3
- package/scripts/corpus-watchdog.mjs +45 -6
- package/scripts/duplicate-gate-replay.mjs +98 -0
- package/scripts/grounding-turn-replay.mjs +131 -0
- package/scripts/nightly-watchdog.mjs +3 -3
- package/scripts/release.mjs +177 -74
- package/scripts/wired-check.mjs +2 -0
package/scripts/release.mjs
CHANGED
|
@@ -65,7 +65,9 @@ const recallNotes = (receipt) => {
|
|
|
65
65
|
];
|
|
66
66
|
};
|
|
67
67
|
import { verifyBundle } from './verify-bundle.mjs';
|
|
68
|
-
import {
|
|
68
|
+
import {
|
|
69
|
+
CORPUS_GENERATION_FIELD, CORPUS_TAG_PATTERN, evaluateCanaryVerdict, evaluateCorpusPromotion, parseCorpusGeneration,
|
|
70
|
+
} from './corpus-promotion.mjs';
|
|
69
71
|
import { bindCoverageToReceipt, writeCoverageAssets } from './corpus-coverage-sidecar.mjs';
|
|
70
72
|
import { degradedPublication } from './corpus-store-failure.mjs';
|
|
71
73
|
import { assertNoNewerCorpusGeneration } from './code-release-corpus.mjs';
|
|
@@ -139,6 +141,68 @@ const compareCodeTags = (left, right) => {
|
|
|
139
141
|
return Math.sign(a[0] - b[0] || a[1] - b[1] || a[2] - b[2]);
|
|
140
142
|
};
|
|
141
143
|
|
|
144
|
+
/** The one gh seam both corpus entry points use (RUVNET_GH_COMMAND / RUVNET_GH_SCRIPT for tests). */
|
|
145
|
+
function corpusGh(env, run) {
|
|
146
|
+
const ghCommand = env.RUVNET_GH_COMMAND || 'gh';
|
|
147
|
+
const ghPrefix = env.RUVNET_GH_SCRIPT ? [env.RUVNET_GH_SCRIPT] : [];
|
|
148
|
+
const gh = (args) => run(ghCommand, [...ghPrefix, ...args], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] });
|
|
149
|
+
const ghJson = (args, label) => {
|
|
150
|
+
const result = gh(args);
|
|
151
|
+
if (result.error || result.status !== 0) {
|
|
152
|
+
corpusFailure(`cannot read ${label} (${String(result.error?.message || result.stderr || result.stdout || '').trim() || `gh exited ${result.status}`})`);
|
|
153
|
+
}
|
|
154
|
+
try { return JSON.parse(String(result.stdout || 'null')); }
|
|
155
|
+
catch (error) { corpusFailure(`cannot parse ${label} (${error.message})`); }
|
|
156
|
+
};
|
|
157
|
+
return { gh, ghJson };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* PUBLISH-TIME RE-RESOLVE, before anything is written. Preparation takes hours and the customer canary
|
|
162
|
+
* adds more; a code release may have been published meanwhile. The newest code release is selected
|
|
163
|
+
* exactly as scripts/approved-runtime.mjs selects it (non-draft, non-prerelease vX.Y.Z, highest).
|
|
164
|
+
*/
|
|
165
|
+
function assertApprovedRuntimeIsNewest({ ghJson, repo, approvedTag, target }) {
|
|
166
|
+
const listed = ghJson(['release', 'list', '--repo', repo, '--limit', '200', '--json', 'tagName,isDraft,isPrerelease'], 'the code release list');
|
|
167
|
+
const [newest] = (Array.isArray(listed) ? listed : [])
|
|
168
|
+
.filter((row) => !row?.isDraft && !row?.isPrerelease && CODE_TAG.test(String(row?.tagName || '')))
|
|
169
|
+
.map((row) => row.tagName).sort((a, b) => compareCodeTags(b, a));
|
|
170
|
+
if (!newest) corpusFailure(`no published code release is listed on ${repo}; the approved runtime ${approvedTag} cannot be confirmed`);
|
|
171
|
+
const order = compareCodeTags(newest, approvedTag);
|
|
172
|
+
if (order > 0) {
|
|
173
|
+
throw new CorpusSuperseded(`code release ${newest} was published after this corpus was built at ${approvedTag}; `
|
|
174
|
+
+ 'promoting it would put an older runtime over the live code release. The next night builds at the newer runtime.');
|
|
175
|
+
}
|
|
176
|
+
if (order < 0) corpusFailure(`approved runtime ${approvedTag} is newer than every published code release (newest ${newest})`);
|
|
177
|
+
const commit = ghJson(['api', `repos/${repo}/commits/${approvedTag}`], `the commit of ${approvedTag}`);
|
|
178
|
+
if (String(commit?.sha || '').toLowerCase() !== target) {
|
|
179
|
+
corpusFailure(`target ${target} is not the source of the approved runtime ${approvedTag} (${commit?.sha || 'unknown'})`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** What releases/latest is right now, or null when the repository has none. */
|
|
184
|
+
function readCurrentLatest(gh, repo) {
|
|
185
|
+
const latestView = gh(['release', 'view', '--json', 'tagName,body', '--repo', repo]);
|
|
186
|
+
if (!latestView.error && latestView.status === 0) {
|
|
187
|
+
let currentLatest;
|
|
188
|
+
try { currentLatest = JSON.parse(String(latestView.stdout || 'null')); }
|
|
189
|
+
catch (error) { corpusFailure(`cannot read the current latest release (${error.message})`); }
|
|
190
|
+
if (!currentLatest || typeof currentLatest.tagName !== 'string') corpusFailure('current latest release carries no tag name');
|
|
191
|
+
return currentLatest;
|
|
192
|
+
}
|
|
193
|
+
const latestError = String(latestView.error?.message || latestView.stderr || latestView.stdout || '');
|
|
194
|
+
if (!/(release not found|no release found)/i.test(latestError)) {
|
|
195
|
+
corpusFailure(`cannot determine the current latest release (${latestError.trim() || `gh exited ${latestView.status}`})`);
|
|
196
|
+
}
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function latestTagNow(gh, repo) {
|
|
201
|
+
const latestNow = gh(['api', `repos/${repo}/releases/latest`]);
|
|
202
|
+
if (latestNow.error || latestNow.status !== 0) return null;
|
|
203
|
+
try { return JSON.parse(String(latestNow.stdout || 'null'))?.tag_name ?? null; } catch { return null; }
|
|
204
|
+
}
|
|
205
|
+
|
|
142
206
|
export async function runProtectedCorpusSeed({
|
|
143
207
|
argv = process.argv.slice(2),
|
|
144
208
|
env = process.env,
|
|
@@ -155,7 +219,15 @@ export async function runProtectedCorpusSeed({
|
|
|
155
219
|
// construction; this refuses the combined invocation outright so the two modes can never share one
|
|
156
220
|
// process even if a future workflow edit put them in the same job.
|
|
157
221
|
if (argv.includes('--publish')) corpusFailure('--corpus-seed cannot be combined with --publish; corpus routing must never enter product publication');
|
|
158
|
-
|
|
222
|
+
// THE PRODUCER CANNOT DECLARE SUCCESS UNLESS THE CONSUMER ACCEPTED (customer canary). There is no
|
|
223
|
+
// longer any single invocation that both publishes a corpus and moves releases/latest: a customer
|
|
224
|
+
// candidate is STAGED (--stage-candidate: a signed, public, non-latest prerelease), a clean customer
|
|
225
|
+
// install applies it (scripts/corpus-canary.mjs), and only --promote-staged with that verdict moves
|
|
226
|
+
// latest. The old one-shot flag is refused outright so no stale workflow text can bypass the canary.
|
|
227
|
+
if (argv.includes('--promote-latest')) {
|
|
228
|
+
corpusFailure('--promote-latest was removed: stage with --stage-candidate, then promote with --promote-staged and the customer canary verdict');
|
|
229
|
+
}
|
|
230
|
+
const customerCandidate = argv.includes('--stage-candidate');
|
|
159
231
|
|
|
160
232
|
const tag = cliArg(argv, '--corpus-tag');
|
|
161
233
|
const bundleFile = cliArg(argv, '--corpus-bundle');
|
|
@@ -208,7 +280,7 @@ export async function runProtectedCorpusSeed({
|
|
|
208
280
|
});
|
|
209
281
|
if (ancestry.error || ancestry.status !== 0) corpusFailure(`target ${target} is not an ancestor of this run's GITHUB_SHA ${env.GITHUB_SHA}`);
|
|
210
282
|
const approvedTag = cliArg(argv, '--approved-tag');
|
|
211
|
-
if (
|
|
283
|
+
if (customerCandidate) {
|
|
212
284
|
if (!CODE_TAG.test(String(approvedTag || ''))) corpusFailure('customer promotion requires --approved-tag vX.Y.Z (the approved runtime this corpus was built at)');
|
|
213
285
|
if (receipt.archiveManifestReleaseTag !== approvedTag) {
|
|
214
286
|
corpusFailure(`the archive ships runtime ${receipt.archiveManifestReleaseTag}, not the approved runtime ${approvedTag}`);
|
|
@@ -366,7 +438,7 @@ export async function runProtectedCorpusSeed({
|
|
|
366
438
|
const signatureFile = `${bundleFile}.sig`;
|
|
367
439
|
const digestFile = `${bundleFile}.sha256`;
|
|
368
440
|
const generation = String(receipt.createdAt || '');
|
|
369
|
-
if (
|
|
441
|
+
if (customerCandidate) {
|
|
370
442
|
for (const [label, file] of [['detached signature', signatureFile], ['sha256 sidecar', digestFile]]) {
|
|
371
443
|
if (!fs.existsSync(file) || !fs.statSync(file).isFile()) {
|
|
372
444
|
corpusFailure(`customer corpus promotion requires a ${label} beside the archive (${path.basename(file)} missing) — the updater fails closed without it`);
|
|
@@ -380,40 +452,11 @@ export async function runProtectedCorpusSeed({
|
|
|
380
452
|
if (!Number.isFinite(Date.parse(generation))) corpusFailure('corpus receipt createdAt is not a readable generation timestamp');
|
|
381
453
|
}
|
|
382
454
|
|
|
383
|
-
const
|
|
384
|
-
const ghPrefix = env.RUVNET_GH_SCRIPT ? [env.RUVNET_GH_SCRIPT] : [];
|
|
385
|
-
const gh = (args) => run(ghCommand, [...ghPrefix, ...args], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] });
|
|
386
|
-
const ghJson = (args, label) => {
|
|
387
|
-
const result = gh(args);
|
|
388
|
-
if (result.error || result.status !== 0) {
|
|
389
|
-
corpusFailure(`cannot read ${label} (${String(result.error?.message || result.stderr || result.stdout || '').trim() || `gh exited ${result.status}`})`);
|
|
390
|
-
}
|
|
391
|
-
try { return JSON.parse(String(result.stdout || 'null')); }
|
|
392
|
-
catch (error) { corpusFailure(`cannot parse ${label} (${error.message})`); }
|
|
393
|
-
};
|
|
455
|
+
const { gh, ghJson } = corpusGh(env, run);
|
|
394
456
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
// scripts/approved-runtime.mjs selects it (non-draft, non-prerelease vX.Y.Z, highest version).
|
|
399
|
-
// Its signed install aggregate was re-verified for --approved-tag by the workflow step that built
|
|
400
|
-
// the runtime pin moments ago; what can change after that is only WHICH release is newest.
|
|
401
|
-
const listed = ghJson(['release', 'list', '--repo', repo, '--limit', '200', '--json', 'tagName,isDraft,isPrerelease'], 'the code release list');
|
|
402
|
-
const [newest] = (Array.isArray(listed) ? listed : [])
|
|
403
|
-
.filter((row) => !row?.isDraft && !row?.isPrerelease && CODE_TAG.test(String(row?.tagName || '')))
|
|
404
|
-
.map((row) => row.tagName).sort((a, b) => compareCodeTags(b, a));
|
|
405
|
-
if (!newest) corpusFailure(`no published code release is listed on ${repo}; the approved runtime ${approvedTag} cannot be confirmed`);
|
|
406
|
-
const order = compareCodeTags(newest, approvedTag);
|
|
407
|
-
if (order > 0) {
|
|
408
|
-
throw new CorpusSuperseded(`code release ${newest} was published after this corpus was built at ${approvedTag}; `
|
|
409
|
-
+ 'promoting it would put an older runtime over the live code release. The next night builds at the newer runtime.');
|
|
410
|
-
}
|
|
411
|
-
if (order < 0) corpusFailure(`approved runtime ${approvedTag} is newer than every published code release (newest ${newest})`);
|
|
412
|
-
const commit = ghJson(['api', `repos/${repo}/commits/${approvedTag}`], `the commit of ${approvedTag}`);
|
|
413
|
-
if (String(commit?.sha || '').toLowerCase() !== target) {
|
|
414
|
-
corpusFailure(`target ${target} is not the source of the approved runtime ${approvedTag} (${commit?.sha || 'unknown'})`);
|
|
415
|
-
}
|
|
416
|
-
}
|
|
457
|
+
// Its signed install aggregate was re-verified for --approved-tag by the workflow step that built the
|
|
458
|
+
// runtime pin moments ago; what can change after that is only WHICH release is newest.
|
|
459
|
+
if (customerCandidate) assertApprovedRuntimeIsNewest({ ghJson, repo, approvedTag, target });
|
|
417
460
|
|
|
418
461
|
const viewArgs = ['release', 'view', tag, '--json', 'tagName', '--repo', repo];
|
|
419
462
|
const view = gh(viewArgs);
|
|
@@ -423,7 +466,7 @@ export async function runProtectedCorpusSeed({
|
|
|
423
466
|
|
|
424
467
|
const receiptSha256 = sha256File(receiptFile);
|
|
425
468
|
|
|
426
|
-
if (!
|
|
469
|
+
if (!customerCandidate) {
|
|
427
470
|
// BOOTSTRAP/RECOVERY seeds stay exactly as ADR-086's original contract left them: an immutable
|
|
428
471
|
// prerelease that never touches releases/latest. Dual's C4 resolution (S1) narrows the change to
|
|
429
472
|
// CUSTOMER releases — "Bootstrap-only releases may remain prereleases."
|
|
@@ -465,23 +508,16 @@ export async function runProtectedCorpusSeed({
|
|
|
465
508
|
// (kb/forge-update.mjs:1275 fetches `${url}.sig`, :1284-1285 exits 4 when verification fails).
|
|
466
509
|
// scripts/verify-channels.mjs checks exactly these two things (checks 3 and 4) against the live
|
|
467
510
|
// endpoints, and is the owner's post-publish acceptance gate.
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
if (!currentLatest || typeof currentLatest.tagName !== 'string') corpusFailure('current latest release carries no tag name');
|
|
474
|
-
} else {
|
|
475
|
-
const latestError = String(latestView.error?.message || latestView.stderr || latestView.stdout || '');
|
|
476
|
-
if (!/(release not found|no release found)/i.test(latestError)) {
|
|
477
|
-
corpusFailure(`cannot determine the current latest release (${latestError.trim() || `gh exited ${latestView.status}`})`);
|
|
478
|
-
}
|
|
479
|
-
}
|
|
511
|
+
//
|
|
512
|
+
// STAGED, NOT PROMOTED (customer canary). This path ends with a signed, public PRERELEASE that
|
|
513
|
+
// releases/latest cannot resolve to. The ordering check still runs here so a candidate that could
|
|
514
|
+
// never be promoted is not staged at all; it runs again at promotion time.
|
|
515
|
+
const currentLatest = readCurrentLatest(gh, repo);
|
|
480
516
|
const promotion = evaluateCorpusPromotion({ tag, generation, currentLatest });
|
|
481
517
|
if (!promotion.allowed) corpusFailure(promotion.reason);
|
|
482
518
|
|
|
483
519
|
const notes = [
|
|
484
|
-
'RuvNet Brain corpus generation — signed
|
|
520
|
+
'RuvNet Brain corpus generation — signed and content-addressed. Staged as a prerelease; promoted to latest only after a clean customer install applied it.',
|
|
485
521
|
`${CORPUS_GENERATION_FIELD} ${generation}`,
|
|
486
522
|
`Archive SHA-256: ${archiveSha256}`,
|
|
487
523
|
`Receipt SHA-256: ${receiptSha256}`,
|
|
@@ -495,8 +531,9 @@ export async function runProtectedCorpusSeed({
|
|
|
495
531
|
// ASSETS COMPLETE BEFORE PROMOTION. `gh release create` uploads assets AFTER the release exists, so
|
|
496
532
|
// creating a non-draft release directly opens a window in which releases/latest resolves to a
|
|
497
533
|
// release with no archive — every polling client in that window fails or, worse, half-downloads.
|
|
498
|
-
// Create as a draft (invisible to releases/latest), prove
|
|
499
|
-
//
|
|
534
|
+
// Create as a draft (invisible to releases/latest), prove every asset landed, and only then flip
|
|
535
|
+
// draft off AS A PRERELEASE — public, so an anonymous customer install can download it exactly as it
|
|
536
|
+
// downloads latest, and never latest, so no customer receives it until the canary has applied it.
|
|
500
537
|
// Both reports ride with every corpus release for the same reason they ride with a seed: a customer
|
|
501
538
|
// (or the next night's dispatcher) that downloads the archive must be able to reverify it against
|
|
502
539
|
// the identity it was actually measured under — the blocking recall gate AND the C3 diagnostic it
|
|
@@ -524,43 +561,109 @@ export async function runProtectedCorpusSeed({
|
|
|
524
561
|
catch (error) { corpusFailure(`cannot read the draft corpus release (${error.message})`); }
|
|
525
562
|
const uploaded = (draft?.assets || []).filter((asset) => asset?.state === 'uploaded' && Number.isSafeInteger(asset.size) && asset.size > 0);
|
|
526
563
|
if (draft?.isDraft !== true || JSON.stringify(uploaded.map((asset) => asset.name).sort()) !== JSON.stringify(expectedAssets)) {
|
|
527
|
-
corpusFailure(`refusing to
|
|
564
|
+
corpusFailure(`refusing to stage an incomplete corpus release; expected ${expectedAssets.join(', ')} fully uploaded on a draft`);
|
|
528
565
|
}
|
|
529
566
|
|
|
530
|
-
const
|
|
531
|
-
if (
|
|
532
|
-
corpusFailure(`corpus
|
|
567
|
+
const stage = gh(['release', 'edit', tag, '--repo', repo, '--draft=false', '--prerelease', '--latest=false']);
|
|
568
|
+
if (stage.error || stage.status !== 0) {
|
|
569
|
+
corpusFailure(`corpus staging failed (${String(stage.error?.message || stage.stderr || stage.stdout || '').trim()})`);
|
|
533
570
|
}
|
|
534
571
|
|
|
535
572
|
// `isLatest` is NOT a `gh release view` field (gh 2.101.0: "Unknown JSON field"; it exists only on
|
|
536
|
-
// `gh release list`), so
|
|
537
|
-
//
|
|
538
|
-
|
|
573
|
+
// `gh release list`), so latest-ness is read from the one authoritative endpoint: releases/latest
|
|
574
|
+
// must NOT be this tag. tests/unit/gh-json-fields.test.mjs checks every --json field list.
|
|
575
|
+
const finalView = gh(['release', 'view', tag, '--json', 'tagName,isDraft,isPrerelease,assets', '--repo', repo]);
|
|
576
|
+
if (finalView.error || finalView.status !== 0) corpusFailure('cannot confirm the staged corpus release');
|
|
577
|
+
let staged;
|
|
578
|
+
try { staged = JSON.parse(String(finalView.stdout || 'null')); }
|
|
579
|
+
catch (error) { corpusFailure(`cannot read the staged corpus release (${error.message})`); }
|
|
580
|
+
const stagedAssets = (staged?.assets || []).map((asset) => asset?.name).sort();
|
|
581
|
+
if (staged?.tagName !== tag || staged.isDraft !== false || staged.isPrerelease !== true
|
|
582
|
+
|| latestTagNow(gh, repo) === tag || JSON.stringify(stagedAssets) !== JSON.stringify(expectedAssets)) {
|
|
583
|
+
corpusFailure('corpus release did not reach a complete, public, non-latest prerelease state');
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
return {
|
|
587
|
+
tag, target, repository: repo, archiveSha256, receiptSha256, staged: true, promoted: false,
|
|
588
|
+
generation, currentLatest: currentLatest?.tagName || null,
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
/**
|
|
593
|
+
* PROMOTION — the only code path that moves releases/latest to a corpus generation, and it requires
|
|
594
|
+
* the consumer's consent: a PASS verdict from scripts/corpus-canary.mjs for THIS run, over exactly the
|
|
595
|
+
* asset digests still on the staged prerelease. Everything that could have changed since staging is
|
|
596
|
+
* re-proved: the protected environment, the checkout, the approved runtime still being the newest code
|
|
597
|
+
* release (else CorpusSuperseded), the release still being the staged prerelease, and the ordering
|
|
598
|
+
* against whatever is latest now.
|
|
599
|
+
*/
|
|
600
|
+
export async function runProtectedCorpusPromotion({
|
|
601
|
+
argv = process.argv.slice(2),
|
|
602
|
+
env = process.env,
|
|
603
|
+
root = ROOT,
|
|
604
|
+
run = (command, args, options) => spawnSync(command, args, { encoding: 'utf8', ...options }),
|
|
605
|
+
} = {}) {
|
|
606
|
+
const environmentFailures = validateProtectedPublishEnvironment(env);
|
|
607
|
+
if (environmentFailures.length) corpusFailure(environmentFailures.join('; '));
|
|
608
|
+
if (argv.includes('--publish')) corpusFailure('--corpus-seed cannot be combined with --publish; corpus routing must never enter product publication');
|
|
609
|
+
const tag = cliArg(argv, '--corpus-tag');
|
|
610
|
+
const verdictFile = cliArg(argv, '--canary-verdict');
|
|
611
|
+
const target = cliArg(argv, '--target');
|
|
612
|
+
const approvedTag = cliArg(argv, '--approved-tag');
|
|
613
|
+
const repo = cliArg(argv, '--repo') || env.GITHUB_REPOSITORY;
|
|
614
|
+
if (!CORPUS_TAG_PATTERN.test(String(tag || ''))) corpusFailure('corpus tag must be corpus-sha256- followed by 64 lowercase hex characters');
|
|
615
|
+
if (repo !== env.GITHUB_REPOSITORY || repo !== 'stuinfla/ruvnet-brain') corpusFailure('repository does not match the protected workflow');
|
|
616
|
+
if (!CODE_TAG.test(String(approvedTag || ''))) corpusFailure('promotion requires --approved-tag vX.Y.Z (the approved runtime this corpus was built at)');
|
|
617
|
+
const head = String(run('git', ['rev-parse', 'HEAD'], { cwd: root, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }).stdout || '').trim();
|
|
618
|
+
if (!isHex(target, 40) || target !== head || !isHex(env.GITHUB_SHA, 40)) corpusFailure('target must exactly equal HEAD (and GITHUB_SHA must be a commit)');
|
|
619
|
+
const ancestry = run('git', ['merge-base', '--is-ancestor', target, env.GITHUB_SHA], { cwd: root, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] });
|
|
620
|
+
if (ancestry.error || ancestry.status !== 0) corpusFailure(`target ${target} is not an ancestor of this run's GITHUB_SHA ${env.GITHUB_SHA}`);
|
|
621
|
+
let verdict;
|
|
622
|
+
try {
|
|
623
|
+
if (!verdictFile || !path.isAbsolute(verdictFile)) throw new Error('--canary-verdict must be an absolute file');
|
|
624
|
+
verdict = JSON.parse(fs.readFileSync(verdictFile, 'utf8'));
|
|
625
|
+
} catch (error) {
|
|
626
|
+
corpusFailure(`no customer consent: the canary verdict is unreadable (${error.message})`);
|
|
627
|
+
}
|
|
628
|
+
// Local refusal first: a FAIL verdict never reaches the network at all.
|
|
629
|
+
if (verdict?.verdict !== 'PASS') corpusFailure(`no customer consent: the customer canary reported ${verdict?.verdict || '(no verdict)'}`);
|
|
630
|
+
|
|
631
|
+
const { gh, ghJson } = corpusGh(env, run);
|
|
632
|
+
assertApprovedRuntimeIsNewest({ ghJson, repo, approvedTag, target });
|
|
633
|
+
const release = ghJson(['release', 'view', tag, '--json', 'tagName,isDraft,isPrerelease,assets,body', '--repo', repo], `the staged release ${tag}`);
|
|
634
|
+
if (release?.tagName !== tag || release.isDraft !== false || release.isPrerelease !== true) {
|
|
635
|
+
corpusFailure(`${tag} is not a staged (public, non-draft) prerelease; refusing to promote it`);
|
|
636
|
+
}
|
|
637
|
+
const consent = evaluateCanaryVerdict({ verdict, tag, runId: env.GITHUB_RUN_ID, runAttempt: env.GITHUB_RUN_ATTEMPT,
|
|
638
|
+
approvedTag, releaseAssets: (release.assets || []).map((asset) => ({ name: asset?.name, digest: asset?.digest ?? null })) });
|
|
639
|
+
if (!consent.allowed) corpusFailure(consent.reason);
|
|
640
|
+
const generation = parseCorpusGeneration(release.body)?.value;
|
|
641
|
+
if (!generation) corpusFailure(`${tag} carries no readable "${CORPUS_GENERATION_FIELD}" ordering key`);
|
|
642
|
+
const currentLatest = readCurrentLatest(gh, repo);
|
|
643
|
+
const promotion = evaluateCorpusPromotion({ tag, generation, currentLatest });
|
|
644
|
+
if (!promotion.allowed) corpusFailure(promotion.reason);
|
|
645
|
+
|
|
646
|
+
const promote = gh(['release', 'edit', tag, '--repo', repo, '--prerelease=false', '--latest']);
|
|
647
|
+
if (promote.error || promote.status !== 0) {
|
|
648
|
+
corpusFailure(`corpus promotion to latest failed (${String(promote.error?.message || promote.stderr || promote.stdout || '').trim()})`);
|
|
649
|
+
}
|
|
539
650
|
const finalView = gh(['release', 'view', tag, '--json', 'tagName,isDraft,isPrerelease,assets', '--repo', repo]);
|
|
540
651
|
if (finalView.error || finalView.status !== 0) corpusFailure('cannot confirm the promoted corpus release');
|
|
541
652
|
let promoted;
|
|
542
653
|
try { promoted = JSON.parse(String(finalView.stdout || 'null')); }
|
|
543
654
|
catch (error) { corpusFailure(`cannot read the promoted corpus release (${error.message})`); }
|
|
544
|
-
const
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
try { latestTag = JSON.parse(String(latestNow.stdout || 'null'))?.tag_name ?? null; } catch { latestTag = null; }
|
|
548
|
-
}
|
|
549
|
-
const promotedAssets = (promoted?.assets || []).map((asset) => asset?.name).sort();
|
|
550
|
-
if (promoted?.tagName !== tag || promoted.isDraft !== false || latestTag !== tag
|
|
551
|
-
|| promoted.isPrerelease !== false || JSON.stringify(promotedAssets) !== JSON.stringify(expectedAssets)) {
|
|
655
|
+
const names = (assets) => (assets || []).map((asset) => asset?.name).sort();
|
|
656
|
+
if (promoted?.tagName !== tag || promoted.isDraft !== false || promoted.isPrerelease !== false
|
|
657
|
+
|| latestTagNow(gh, repo) !== tag || JSON.stringify(names(promoted.assets)) !== JSON.stringify(names(release.assets))) {
|
|
552
658
|
corpusFailure('corpus release did not reach a complete, non-draft, non-prerelease latest state');
|
|
553
659
|
}
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
tag, target, repository: repo, archiveSha256, receiptSha256, promoted: true,
|
|
557
|
-
generation, supersededLatest: currentLatest?.tagName || null,
|
|
558
|
-
};
|
|
660
|
+
return { tag, target, repository: repo, promoted: true, generation, consent: consent.reason,
|
|
661
|
+
supersededLatest: currentLatest?.tagName || null };
|
|
559
662
|
}
|
|
560
663
|
|
|
561
664
|
if (CORPUS_SEED) {
|
|
562
665
|
try {
|
|
563
|
-
const result = await runProtectedCorpusSeed();
|
|
666
|
+
const result = process.argv.includes('--promote-staged') ? await runProtectedCorpusPromotion() : await runProtectedCorpusSeed();
|
|
564
667
|
console.log(JSON.stringify({ ok: true, mode: 'corpus-seed', ...result }, null, 2));
|
|
565
668
|
} catch (error) {
|
|
566
669
|
console.error(error.message);
|
package/scripts/wired-check.mjs
CHANGED
|
@@ -77,6 +77,8 @@ const STANDALONE = [
|
|
|
77
77
|
['gate', 'retired automatic-hook helper and manual benchmark retained for explicit human use; no workflow or scheduler invokes this expensive command'],
|
|
78
78
|
['dream-issue-gate', 'pure Dream Cycle disposition policy; invoked by the external issue adapter, never a GitHub writer'],
|
|
79
79
|
['sync-census', 'explicit maintainer census writer; a destructive source-to-surface refresh is never scheduled'],
|
|
80
|
+
['grounding-turn-replay', 'human-run measurement harness for grounding-turn-gate.mjs (ADR-0030 #1 and shadow #2/#3): replays real transcripts read-only through the same pure functions the hooks call, sharing completion-claim-replay.mjs\'s turn walkers; nothing to schedule'],
|
|
81
|
+
['duplicate-gate-replay','human-run tuning harness for plugin/scripts/duplicate-gate.mjs: replays the gate over git history, read-only, when its thresholds are re-tuned; it imports the gate\'s own scoring functions, so there is no second copy to drift and nothing to schedule'],
|
|
80
82
|
['sync-commands', 'explicit maintainer alias synchronizer; run deliberately before release, never from a lifecycle hook'],
|
|
81
83
|
['project-progression-checkpoint', 'the body of the shipped `/ruvnet-brain:checkpoint` command '
|
|
82
84
|
+ '(plugin/commands/checkpoint.md:39; added a7167b6b 2026-09-11). The command host executes that '
|