bareloop 0.38.0 → 0.40.0
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/CHANGELOG.md +59 -0
- package/bareloop.context.md +87 -24
- package/package.json +3 -3
- package/src/authorflow.js +335 -59
- package/src/authoring.js +125 -3
- package/src/authorjob.js +93 -32
- package/src/authorreadout.js +498 -3
- package/src/authorrun.js +26 -6
- package/src/authorscout.js +9 -6
- package/src/bridges.js +1 -1
- package/src/calibrate.js +85 -11
- package/src/cardauthor.js +8 -2
- package/src/declaredclose.js +12 -5
- package/src/draftspend.js +6 -1
- package/src/interviewrun.js +56 -27
- package/src/job.js +38 -5
- package/src/jobblock.js +140 -0
- package/src/judged.js +323 -29
- package/src/kinds.js +13 -1
- package/src/ledger.js +1 -1
- package/src/panel/authorroutes.js +17 -11
- package/src/panel/authorsession.js +202 -75
- package/src/panel/index.html +302 -312
- package/src/panel/jobplan.js +107 -0
- package/src/panel/server.js +67 -13
- package/src/planrun.js +6 -5
- package/src/promptregisters.js +1 -1
- package/src/providers.js +111 -2
- package/src/reuse.js +1 -1
- package/src/root.js +1 -1
- package/src/run.js +3 -3
- package/src/selection.js +1 -1
- package/src/source.js +64 -1
- package/src/text.js +13 -0
- package/src/transport.js +1 -1
- package/src/trend.js +1 -0
- package/src/userrun.js +3 -3
- package/types/authorflow.d.ts +258 -39
- package/types/authoring.d.ts +30 -1
- package/types/authorjob.d.ts +52 -13
- package/types/authorreadout.d.ts +123 -1
- package/types/authorscout.d.ts +5 -3
- package/types/calibrate.d.ts +4 -1
- package/types/declaredclose.d.ts +5 -1
- package/types/draftspend.d.ts +8 -2
- package/types/job.d.ts +1 -1
- package/types/jobblock.d.ts +102 -0
- package/types/judged.d.ts +21 -2
- package/types/panel/authorsession.d.ts +57 -7
- package/types/panel/jobplan.d.ts +58 -0
- package/types/panel/server.d.ts +20 -0
- package/types/planrun.d.ts +1 -1
- package/types/providers.d.ts +52 -1
- package/types/run.d.ts +1 -1
- package/types/source.d.ts +26 -0
- package/types/trend.d.ts +1 -0
package/src/authoring.js
CHANGED
|
@@ -516,6 +516,32 @@ export const TYPES_GENRE_TEMPLATE = `1. The STRICT form of the language's type c
|
|
|
516
516
|
|
|
517
517
|
export const GENRE_LANGUAGES = Object.freeze(['js', 'python']);
|
|
518
518
|
|
|
519
|
+
/**
|
|
520
|
+
* The js `any` suppression pattern's regex, ONE spelling: the TYPES js battery
|
|
521
|
+
* below composes it into the signed guard, and `anySuppressionWarning` keys on
|
|
522
|
+
* it, so the warning and the guard cannot drift. The guard scans every ADDED
|
|
523
|
+
* line, comments included, so plain prose like ", any" trips it.
|
|
524
|
+
*/
|
|
525
|
+
const JS_ANY_REGEX = '(?:[{<|(\\[:,]|\\bas)\\s*any\\b|\\bany\\[\\]';
|
|
526
|
+
|
|
527
|
+
/** The worker-facing line that rides with the js `any` guard (run mv13ery3). */
|
|
528
|
+
export const ANY_SUPPRESSION_WARNING = '\nThe no-suppressions check also reads comments: never write the word any right after a comma, colon, bracket, brace, < or | '
|
|
529
|
+
+ "(e.g. ', any', ': any', '(any'), even in plain English — write 'every', start the clause differently, or put it in backticks.";
|
|
530
|
+
|
|
531
|
+
/**
|
|
532
|
+
* The warning line for a job whose ACTUAL stages carry the js `any` pattern, else
|
|
533
|
+
* ''. Derived from the stages' patterns (never goal prose), so a job without the
|
|
534
|
+
* guard is told nothing about it.
|
|
535
|
+
* @param {any[]|null|undefined} stages
|
|
536
|
+
* @returns {string}
|
|
537
|
+
*/
|
|
538
|
+
export function anySuppressionWarning(stages) {
|
|
539
|
+
const has = Array.isArray(stages) && stages.some((s) => isObj(s) && s.kind === 'pattern-absent-in-diff'
|
|
540
|
+
&& isObj(s.params) && Array.isArray(s.params.patterns)
|
|
541
|
+
&& s.params.patterns.some((/** @type {any} */ q) => isObj(q) && q.regex === JS_ANY_REGEX));
|
|
542
|
+
return has ? ANY_SUPPRESSION_WARNING : '';
|
|
543
|
+
}
|
|
544
|
+
|
|
519
545
|
/**
|
|
520
546
|
* THE GENRE, as data. The suppression batteries are the hand-written closes'
|
|
521
547
|
* own `SUPPRESSIONS` tables (`scripts/u-pulselog-close.mjs:35`,
|
|
@@ -541,7 +567,7 @@ export const TYPES_GENRE = Object.freeze({
|
|
|
541
567
|
Object.freeze({ id: 'ts-expect-error', regex: '@ts-expect-error' }),
|
|
542
568
|
Object.freeze({ id: 'ts-nocheck', regex: '@ts-nocheck' }),
|
|
543
569
|
Object.freeze({ id: 'eslint-disable', regex: 'eslint-disable' }),
|
|
544
|
-
Object.freeze({ id: 'any', regex:
|
|
570
|
+
Object.freeze({ id: 'any', regex: JS_ANY_REGEX }),
|
|
545
571
|
Object.freeze({ id: 'any-star', regex: '@\\w+\\s*\\{\\s*[*?]\\s*\\}' }),
|
|
546
572
|
Object.freeze({ id: 'cast', regex: '@type\\s*\\{.*\\}\\s*\\*\\/\\s*\\(' }),
|
|
547
573
|
]),
|
|
@@ -1057,6 +1083,25 @@ function scopeOfJob(declaration, idx, guards) {
|
|
|
1057
1083
|
return { scoped: false, via: null };
|
|
1058
1084
|
}
|
|
1059
1085
|
|
|
1086
|
+
/**
|
|
1087
|
+
* Which of a judged stage's paths lie OUTSIDE the job's write fence? One owner of the rule "a check must be
|
|
1088
|
+
* free to edit every file it can report on", called from every door that validates a declaration. It uses
|
|
1089
|
+
* the shipped fence spelling (`globToPrefix`, the same containment reading plan.js's `insideFence` has) and
|
|
1090
|
+
* returns `[]` when there is no fence to compare against or the paths are not a string list (shape is the
|
|
1091
|
+
* schema walk's business).
|
|
1092
|
+
* @param {unknown} paths @param {string[]|null|undefined} writeScope
|
|
1093
|
+
* @returns {string[]}
|
|
1094
|
+
*/
|
|
1095
|
+
export function judgedOutsideFence(paths, writeScope) {
|
|
1096
|
+
if (!Array.isArray(writeScope) || writeScope.length === 0 || !Array.isArray(paths)) return [];
|
|
1097
|
+
const fence = writeScope.filter(isNonEmptyString).map(globToPrefix);
|
|
1098
|
+
if (fence.length === 0) return [];
|
|
1099
|
+
return paths.filter(isNonEmptyString).filter((raw) => {
|
|
1100
|
+
const n = normPrefix(raw);
|
|
1101
|
+
return !fence.some((f) => f === '.' || n === f || n.startsWith(`${f}/`));
|
|
1102
|
+
});
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1060
1105
|
/**
|
|
1061
1106
|
* Kinds a declaration may carry AT MOST ONCE, as data beside the catalogue.
|
|
1062
1107
|
*
|
|
@@ -1154,7 +1199,12 @@ function readPath(p, dotted) {
|
|
|
1154
1199
|
* @param {{catalogue?: Record<string, KindSpec>, listing?: string[]|null,
|
|
1155
1200
|
* guards?: {name: string, kind: string, params: Record<string, any>, fill: string[]}[]|null,
|
|
1156
1201
|
* envOwned?: string[]|null, envInjected?: Record<string, string>|null, deferListing?: boolean,
|
|
1157
|
-
* verdictType?: string|null}} [opts]
|
|
1202
|
+
* verdictType?: string|null, writeScope?: string[]|null, jobLines?: {n: number}[]|null}} [opts]
|
|
1203
|
+
* `jobLines` — the person's numbered job lines (P7). Present, every stage must name the line it serves and every
|
|
1204
|
+
* line needs a stage or a refusal ({@link checkJobLines}); absent (every spec that predates the job block), the
|
|
1205
|
+
* rule is not run.
|
|
1206
|
+
* `writeScope` — the job's signed write fence. Every path a judged stage judges must sit inside it
|
|
1207
|
+
* ({@link judgedOutsideFence}); absent, the rule is not run (the plain-folder deferral has no fence).
|
|
1158
1208
|
* `listing` — repo-relative paths at the seed (`git ls-tree -r --name-only`).
|
|
1159
1209
|
* `guards` — the picked class's injected guards (`classGuards`).
|
|
1160
1210
|
* `verdictType` — the class the USER picked (`VERDICT_CLASSES`).
|
|
@@ -1171,7 +1221,7 @@ function readPath(p, dotted) {
|
|
|
1171
1221
|
export function validateDeclaration(declaration, opts = {}) {
|
|
1172
1222
|
const {
|
|
1173
1223
|
catalogue = KIND_CATALOGUE, listing = null, guards = null, envOwned = null, envInjected = null,
|
|
1174
|
-
verdictType = null,
|
|
1224
|
+
verdictType = null, writeScope = null, jobLines = null,
|
|
1175
1225
|
} = opts;
|
|
1176
1226
|
const deferListing = opts.deferListing === true;
|
|
1177
1227
|
/** @type {Red[]} */
|
|
@@ -1314,6 +1364,18 @@ export function validateDeclaration(declaration, opts = {}) {
|
|
|
1314
1364
|
} else kindOwners.set(s.kind, label);
|
|
1315
1365
|
}
|
|
1316
1366
|
|
|
1367
|
+
// A judged stage may only judge what the run can fix: a path outside the write fence is a
|
|
1368
|
+
// finding the worker can never act on (run mv13ery3: bin/pulselog.js, fence src/**).
|
|
1369
|
+
if (s.kind === JUDGED_FLOOR_KIND && isObj(s.params)) {
|
|
1370
|
+
const outside = judgedOutsideFence(s.params.paths, writeScope);
|
|
1371
|
+
if (outside.length > 0) {
|
|
1372
|
+
red('judged-outside-write-scope', `${at}.params.paths`, `stage "${label}" judges ${outside.join(', ')}, but the `
|
|
1373
|
+
+ `job may only edit ${writeScope?.join(', ')}. A check that reports on a file the run is fenced out of can `
|
|
1374
|
+
+ 'never be satisfied — every finding on it is one the worker cannot fix. Name files inside the write fence',
|
|
1375
|
+
{ stage: label, outside, fence: (writeScope ?? []).map(globToPrefix) });
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1317
1379
|
// ── RULING 5, AS LAW: judged and human stages are `offer: false`, and the
|
|
1318
1380
|
// law is checked rather than applied. `checkMenu` filters `offer !== false`,
|
|
1319
1381
|
// so a declaration that says nothing is silent about a rule the runner
|
|
@@ -1384,11 +1446,71 @@ export function validateDeclaration(declaration, opts = {}) {
|
|
|
1384
1446
|
});
|
|
1385
1447
|
|
|
1386
1448
|
if (haveGuards) checkGuards({ declaration, guards: /** @type {any[]} */ (guards), red });
|
|
1449
|
+
checkJobLines({ declaration, stages, jobLines, guards: haveGuards ? /** @type {any[]} */ (guards) : [], red });
|
|
1387
1450
|
|
|
1388
1451
|
const ok = reds.length === 0;
|
|
1389
1452
|
return { ok, reds, declaration: ok ? normalizeDeclaration(declaration) : null, grounded: haveListing, scoped, ceiling };
|
|
1390
1453
|
}
|
|
1391
1454
|
|
|
1455
|
+
/**
|
|
1456
|
+
* P7 rules 1-3 of the job block, at $0, in the gate that can still revise: every `fromLine` is a non-empty, duplicate-free array of real job line numbers (one check may serve several lines);
|
|
1457
|
+
* every numbered job line has at least one stage OR a refusal reason in `refused` (a note in `notes` does NOT cover a
|
|
1458
|
+
* line); the mandatory guards serve no line and carry no `fromLine`. The SHAPE of `fromLine` / `refused` is checked
|
|
1459
|
+
* whenever they appear; the coverage half runs only when the person's `jobLines` were handed in.
|
|
1460
|
+
* @param {{declaration: any, stages: any[], jobLines: {n: number}[]|null|undefined,
|
|
1461
|
+
* guards: {name: string}[], red: (c: string, p: string, d: string, e?: object) => void}} o
|
|
1462
|
+
*/
|
|
1463
|
+
function checkJobLines({ declaration, stages, jobLines, guards, red }) {
|
|
1464
|
+
const known = Array.isArray(jobLines) && jobLines.length > 0 ? jobLines.map((l) => l.n) : null;
|
|
1465
|
+
const guardNames = new Set(guards.map((g) => g.name));
|
|
1466
|
+
/** @type {Set<number>} */
|
|
1467
|
+
const served = new Set();
|
|
1468
|
+
stages.forEach((/** @type {any} */ s, /** @type {number} */ i) => {
|
|
1469
|
+
if (!isObj(s)) return;
|
|
1470
|
+
const at = `stages[${i}].fromLine`;
|
|
1471
|
+
const label = isNonEmptyString(s.name) ? s.name : `stages[${i}]`;
|
|
1472
|
+
const guard = isNonEmptyString(s.name) && guardNames.has(s.name);
|
|
1473
|
+
if (Object.hasOwn(s, 'fromLine')) {
|
|
1474
|
+
if (guard) {
|
|
1475
|
+
red('invalid-value', at, `stage "${label}" is a mandatory guard and serves no job line — remove its fromLine`);
|
|
1476
|
+
} else if (!Array.isArray(s.fromLine) || s.fromLine.length === 0 || !s.fromLine.every((/** @type {any} */ x) => Number.isInteger(x) && x >= 1)) {
|
|
1477
|
+
red('invalid-value', at, `stage "${label}" has fromLine ${JSON.stringify(s.fromLine)} — a non-empty array of job line numbers, e.g. [1] or [1, 3]`);
|
|
1478
|
+
} else if (new Set(s.fromLine).size !== s.fromLine.length) {
|
|
1479
|
+
red('invalid-value', at, `stage "${label}" lists a job line twice in fromLine ${JSON.stringify(s.fromLine)}`);
|
|
1480
|
+
} else if (known !== null && s.fromLine.some((/** @type {number} */ x) => !known.includes(x))) {
|
|
1481
|
+
red('invalid-value', at, `stage "${label}" has fromLine ${JSON.stringify(s.fromLine)}, but the job has lines ${known.join(', ')} only`);
|
|
1482
|
+
} else for (const x of s.fromLine) served.add(x);
|
|
1483
|
+
} else if (known !== null && !guard) {
|
|
1484
|
+
red('missing-field', at, `stage "${label}" does not say which job lines it serves — give it fromLine: [N, ...] (${known.join(', ')}); `
|
|
1485
|
+
+ 'only a mandatory guard carries none');
|
|
1486
|
+
}
|
|
1487
|
+
});
|
|
1488
|
+
/** @type {Set<number>} */
|
|
1489
|
+
const refused = new Set();
|
|
1490
|
+
if (declaration.refused !== undefined) {
|
|
1491
|
+
if (!Array.isArray(declaration.refused)) {
|
|
1492
|
+
red('invalid-value', 'refused', 'an array of {line, reason} — the job lines no stage can check, each with a reason');
|
|
1493
|
+
} else {
|
|
1494
|
+
declaration.refused.forEach((/** @type {any} */ r, /** @type {number} */ j) => {
|
|
1495
|
+
const at = `refused[${j}]`;
|
|
1496
|
+
if (!isObj(r) || !Number.isInteger(r.line) || r.line < 1 || !isNonEmptyString(r.reason)) {
|
|
1497
|
+
red('invalid-value', at, '{line, reason}: a job line number and a plain-words reason');
|
|
1498
|
+
} else if (known !== null && !known.includes(r.line)) {
|
|
1499
|
+
red('invalid-value', `${at}.line`, `refused line ${r.line}, but the job has lines ${known.join(', ')} only`);
|
|
1500
|
+
} else refused.add(r.line);
|
|
1501
|
+
});
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
if (known !== null) {
|
|
1505
|
+
for (const n of known) {
|
|
1506
|
+
if (!served.has(n) && !refused.has(n)) {
|
|
1507
|
+
red('job-line-uncovered', 'stages', `job line ${n} has no stage and no refusal. Give it a stage whose fromLine lists ${n}, or list `
|
|
1508
|
+
+ `it in "refused" as {"line": ${n}, "reason": "..."} — a note does not cover a line`, { line: n });
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
|
|
1392
1514
|
/**
|
|
1393
1515
|
* COMPLETENESS — the deletion half of the owned-env rule, and the half a
|
|
1394
1516
|
* by-VALUE comparison structurally cannot cover.
|
package/src/authorjob.js
CHANGED
|
@@ -99,7 +99,7 @@
|
|
|
99
99
|
// in the stored declaration (forward-compat point 3), and the suite pins it.
|
|
100
100
|
|
|
101
101
|
import { jobSpecHash, validateJob } from './job.js';
|
|
102
|
-
import { seedAtHead, seedListing, seedRead as runSeedRead, makeSeedTrees,
|
|
102
|
+
import { seedAtHead, seedListing, seedRead as runSeedRead, makeSeedTrees, judgedStages, closeJudges } from './kinds.js';
|
|
103
103
|
// SOFTGREEN modules 4 and 5 — the compile the interview's Q6/Q7 answers land in,
|
|
104
104
|
// and the gate that decides whether the ruler they describe is a ruler.
|
|
105
105
|
import { proposeJudgedArtifacts, signJudgedArtifacts, foldJudgedArtifacts } from './cardauthor.js';
|
|
@@ -113,6 +113,7 @@ import {
|
|
|
113
113
|
QUESTION_SETS, questionsFor, requiredAnswersFor, authorClose, makeCostBook,
|
|
114
114
|
runConfirmTurn, LANGUAGE_PICK_FIELD,
|
|
115
115
|
} from './authorflow.js';
|
|
116
|
+
import { draftCallOptions, assertDraftThinking } from './providers.js';
|
|
116
117
|
import { runAuthorScout, buildSeedListing, SCOUT_ATTEMPTS } from './authorscout.js';
|
|
117
118
|
import {
|
|
118
119
|
DECLARED_GAP_PREFIX, DECLARED_GENRES, guardNames, isDeclaredClose, validateCloseDecl,
|
|
@@ -416,16 +417,19 @@ function composerRefusal(reds) {
|
|
|
416
417
|
* one-genre-at-a-time law is untouched.
|
|
417
418
|
*
|
|
418
419
|
* @param {{answers: Record<string|number, any>, repoPath?: string|null, lang: string,
|
|
420
|
+
* jobLines?: {n: number, text: string}[]|null, inputs?: {n: number, label: string, value: string}[]|null,
|
|
419
421
|
* verdictType?: string|null,
|
|
420
422
|
* questions?: Record<string|number, string>|null, generate?: Function, provider?: any,
|
|
421
423
|
* seedRef?: string|null, scout?: any, listing?: any, ceilingUsd?: number|null, judgeModel?: string|null,
|
|
422
|
-
* rates?: {in: number, out: number}|null,
|
|
424
|
+
* rates?: {in: number, out: number}|null, draftThinking?: 'on'|'off',
|
|
423
425
|
* onPhase?: (phase: string, data?: any) => void,
|
|
424
|
-
* onCall?: (call: {label: string, costUsd: number|null, unpricedRounds: number}) => void,
|
|
426
|
+
* onCall?: (call: {label: string, costUsd: number|null, unpricedRounds: number, thinkingApplied?: boolean|null}) => void,
|
|
425
427
|
* seedFn?: Function, scoutFn?: Function, listingFn?: Function,
|
|
426
428
|
* authorFn?: Function, authorOpts?: object, writeScope?: string[]|null,
|
|
427
429
|
* signerFix?: Function|any, proposeFn?: Function, compileOpts?: object,
|
|
428
430
|
* ask?: ((step: {kind: string, [k: string]: any}) => Promise<string|null>)|null,
|
|
431
|
+
* answerQuestions?: ((o: {questions: {line: number, question: string}[], dropped: {reason: string, raw: any}[], confirmed: any}) =>
|
|
432
|
+
* Promise<{jobLines: {n: number, text: string, rule?: string}[], answers: Record<string|number, any>}|null>)|null,
|
|
429
433
|
* confirmGenerate?: Function|null, isRepo?: boolean,
|
|
430
434
|
* langResult?: {kind: string, candidates?: string[], [k: string]: any}|null}} o
|
|
431
435
|
* @returns {Promise<{ok: boolean, refusal: Refusal|null, verdictType: string|null,
|
|
@@ -434,9 +438,15 @@ function composerRefusal(reds) {
|
|
|
434
438
|
*/
|
|
435
439
|
export async function authorCloseForJob({
|
|
436
440
|
answers, repoPath = null, lang, verdictType = null, questions = null,
|
|
441
|
+
// P7: the person's numbered job lines and numbered inputs (src/jobblock.js). Absent = every caller that predates the
|
|
442
|
+
// job block (no line tagging, no coverage rule).
|
|
443
|
+
jobLines = null, inputs = null,
|
|
437
444
|
generate, provider = null, seedRef = null, scout = null, listing = null,
|
|
438
445
|
ceilingUsd = null,
|
|
439
446
|
rates = null,
|
|
447
|
+
// The drafter's THINKING knob ('on' | 'off', default 'off'; an operator param, never read from env here). It reaches the scout through
|
|
448
|
+
// `callOptions`; the `generate`/`confirmGenerate` the caller passes must be built with the SAME mode (`makeLoopGenerate`).
|
|
449
|
+
draftThinking = 'off',
|
|
440
450
|
// Q2 IS GONE from the numbered interview (PRD item 33 M3 piece 3) — the
|
|
441
451
|
// fence it used to describe in prose is now Destination's own proven
|
|
442
452
|
// `writeScope`, and this is what carries it into the composer prompt
|
|
@@ -479,7 +489,14 @@ export async function authorCloseForJob({
|
|
|
479
489
|
// shape matters here (a $0 language pick before the scout,
|
|
480
490
|
// D7); every other kind leaves `lang` exactly as given.
|
|
481
491
|
ask = null, confirmGenerate = null, isRepo = false, langResult = null,
|
|
492
|
+
// P7 item 5 (fwdloop am24/25): the seam that can really ASK the person. Present = the last drafting try, and only after
|
|
493
|
+
// the two before it both failed, may ask (`offerQuestions` below); the seam takes the machine-filtered questions, gets
|
|
494
|
+
// each answer (required, no skip), and hands back the job lines with the answers added as `~` rules plus the matching
|
|
495
|
+
// answers text. ABSENT (the CLI, every older caller) = nothing is ever offered and this file is byte-identical.
|
|
496
|
+
answerQuestions = null,
|
|
482
497
|
}) {
|
|
498
|
+
// an operator-param guard: a typo never silently means "default" (throws before any work, even when the survey is supplied)
|
|
499
|
+
assertDraftThinking(draftThinking);
|
|
483
500
|
/** @type {any} */
|
|
484
501
|
const base = {
|
|
485
502
|
ok: false, refusal: null, verdictType: null, closeDecl: null, seedRef: null,
|
|
@@ -580,7 +597,8 @@ export async function authorCloseForJob({
|
|
|
580
597
|
// real tree. `attempts` is the ceiling the scout enforces on itself, so the
|
|
581
598
|
// reader knows how many the line could repeat for.
|
|
582
599
|
onPhase('scout', { attempts: SCOUT_ATTEMPTS });
|
|
583
|
-
|
|
600
|
+
const scoutCallOptions = draftCallOptions(provider, draftThinking);
|
|
601
|
+
survey = await scoutFn({ workdir, provider, ceilingUsd, onCall, ...(rates ? { rates } : {}), ...(Object.keys(scoutCallOptions).length ? { callOptions: scoutCallOptions } : {}) });
|
|
584
602
|
// …and the survey's own VERDICT, said the moment it lands. `state` is what
|
|
585
603
|
// every downstream refusal keys on (F59: absent is never "no facts needed"),
|
|
586
604
|
// and the fact count is the one number that says whether the ladder came
|
|
@@ -627,7 +645,7 @@ export async function authorCloseForJob({
|
|
|
627
645
|
onPhase('confirm', {});
|
|
628
646
|
const confirm = await runConfirmTurn({
|
|
629
647
|
verdictType: picked, answers: interview.answers, questions: questions ?? questionsFor(picked),
|
|
630
|
-
facts: survey.facts, listing: null, writeScope, isRepo, lang, worseThanBefore,
|
|
648
|
+
facts: survey.facts, listing: null, writeScope, isRepo, lang, worseThanBefore, inputs,
|
|
631
649
|
generate: confirmGenerate, book: confirmBook, ask, onPhase,
|
|
632
650
|
});
|
|
633
651
|
// DISTINCT from `runConfirmTurn`'s own per-round 'confirm-done' (fired once
|
|
@@ -671,13 +689,38 @@ export async function authorCloseForJob({
|
|
|
671
689
|
// loop obeys — `author-call` carries the enforced cap instead, from where it
|
|
672
690
|
// is computed.
|
|
673
691
|
onPhase('author', {});
|
|
674
|
-
|
|
692
|
+
/** the answers text the later stages read; the try-3 redraft replaces it (the person's answers are new `~` rules) @type {Record<string|number, any>} */
|
|
693
|
+
let stageAnswers = interview.answers;
|
|
694
|
+
let authored = await authorFn({
|
|
675
695
|
workdir, seedRef: seed, lang, verdictType: picked,
|
|
676
|
-
answers:
|
|
696
|
+
answers: stageAnswers, questions: questions ?? questionsFor(picked),
|
|
677
697
|
scout: survey, listing: seeds, generate, ceilingUsd, onPhase, onCall, writeScope,
|
|
678
|
-
priorCalls: confirmPriorCalls, priorRaws: confirmPriorRaws, confirmed,
|
|
698
|
+
priorCalls: confirmPriorCalls, priorRaws: confirmPriorRaws, confirmed, jobLines, inputs,
|
|
699
|
+
...(answerQuestions ? { offerQuestions: true } : {}),
|
|
679
700
|
...authorOpts,
|
|
680
701
|
});
|
|
702
|
+
// P7 item 5: the drafter, on the last try after two failed ones, asked. The person answers (required, no skip); each
|
|
703
|
+
// answer becomes a `~` rule on its job line; the draft then runs ONCE more over the same survey, listing and confirmed
|
|
704
|
+
// plan, and may not ask again. The first leg's calls are carried as prior spend so the one ceiling still binds.
|
|
705
|
+
if (!authored.ok && authored.stop === 'questions-open' && answerQuestions && Array.isArray(authored.questions) && authored.questions.length) {
|
|
706
|
+
const dropped = (authored.iterations ?? []).flatMap((/** @type {any} */ it) => it?.droppedQuestions ?? []);
|
|
707
|
+
const updated = await answerQuestions({ questions: authored.questions, dropped, confirmed });
|
|
708
|
+
if (updated === null) return { ...base, interview, authoring: authored, reds: [], stop: 'confirm-abandoned', cost: authored.cost };
|
|
709
|
+
const scoutCalls = (survey?.calls ?? []).length;
|
|
710
|
+
const scoutRaws = (survey?.raws ?? []).length;
|
|
711
|
+
jobLines = updated.jobLines;
|
|
712
|
+
stageAnswers = updated.answers;
|
|
713
|
+
onPhase('author', {});
|
|
714
|
+
authored = await authorFn({
|
|
715
|
+
workdir, seedRef: seed, lang, verdictType: picked,
|
|
716
|
+
answers: stageAnswers, questions: questions ?? questionsFor(picked),
|
|
717
|
+
scout: survey, listing: seeds, generate, ceilingUsd, onPhase, onCall, writeScope,
|
|
718
|
+
priorCalls: (authored.cost?.calls ?? []).slice(scoutCalls), priorRaws: (authored.raws ?? []).slice(scoutRaws),
|
|
719
|
+
confirmed, jobLines, inputs,
|
|
720
|
+
...authorOpts,
|
|
721
|
+
offerQuestions: false,
|
|
722
|
+
});
|
|
723
|
+
}
|
|
681
724
|
|
|
682
725
|
if (!authored.ok) {
|
|
683
726
|
return {
|
|
@@ -724,6 +767,11 @@ export async function authorCloseForJob({
|
|
|
724
767
|
...(authored.declaration.notes?.length
|
|
725
768
|
? { notes: authored.declaration.notes.map((/** @type {any} */ n) => (typeof n === 'string' ? redactSecrets(n) : n)) }
|
|
726
769
|
: {}),
|
|
770
|
+
// P7: the job lines the drafter could not turn into a stage, each with its reason — the Job tab shows them. Free
|
|
771
|
+
// text the model wrote, so it rides the same redactor `notes` does.
|
|
772
|
+
...(authored.declaration.refused?.length
|
|
773
|
+
? { refused: authored.declaration.refused.map((/** @type {any} */ r) => ({ line: r.line, reason: redactSecrets(String(r.reason)) })) }
|
|
774
|
+
: {}),
|
|
727
775
|
};
|
|
728
776
|
|
|
729
777
|
// ── THE TWO SIGNED ARTIFACTS (softgreen module 4, wired here by module 5) ──
|
|
@@ -750,7 +798,7 @@ export async function authorCloseForJob({
|
|
|
750
798
|
book.absorb(authored.cost?.calls ?? []);
|
|
751
799
|
onPhase('rubric', { size: CALIBRATION_SIZE });
|
|
752
800
|
const proposed = await proposeFn({
|
|
753
|
-
answers:
|
|
801
|
+
answers: stageAnswers,
|
|
754
802
|
questions: questions ?? questionsFor(picked),
|
|
755
803
|
generate,
|
|
756
804
|
book,
|
|
@@ -950,6 +998,17 @@ const scrubRed = (r) => /** @type {any} */ (Object.fromEntries(
|
|
|
950
998
|
Object.entries(r).map(([k, v]) => [k, typeof v === 'string' ? redactSecrets(v) : v]),
|
|
951
999
|
));
|
|
952
1000
|
|
|
1001
|
+
/**
|
|
1002
|
+
* THE one spelling of "a passed calibration gate stands in for the seed red": the
|
|
1003
|
+
* close carries a judged stage AND the gate ran and passed. A failed or missing
|
|
1004
|
+
* calibration never satisfies it, and a close with no judged stage never reaches it.
|
|
1005
|
+
* @param {any[]} judged `judgedStages(closeDecl)` @param {any} calibration `gates.calibration`
|
|
1006
|
+
* @returns {boolean}
|
|
1007
|
+
*/
|
|
1008
|
+
export function seedProofIsCalibration(judged, calibration) {
|
|
1009
|
+
return judged.length > 0 && calibration?.ok === true;
|
|
1010
|
+
}
|
|
1011
|
+
|
|
953
1012
|
/**
|
|
954
1013
|
* GATE 4 — the calibration gate, and the only gate here that spends money.
|
|
955
1014
|
*
|
|
@@ -1033,7 +1092,10 @@ async function calibrationGate({ spec, judgedStages, judgeLoop, judgeModel, onJu
|
|
|
1033
1092
|
|
|
1034
1093
|
/** what the signing evidence KEEPS about this gate. The per-case artifacts are
|
|
1035
1094
|
* deliberately not in it — they are already in the signed spec, and a second
|
|
1036
|
-
* copy in the evidence file is a second thing to keep in step.
|
|
1095
|
+
* copy in the evidence file is a second thing to keep in step. Each graded row's
|
|
1096
|
+
* `diag` (F192) keeps the judge's own facts and the decision's reds — scrubbed
|
|
1097
|
+
* and bounded quotes, never an artifact body — so a refusal reads from its own
|
|
1098
|
+
* record. What IS kept is
|
|
1037
1099
|
* WHICH BYTES were certified (`cardHash`/`casesHash`/`setHash`) and BY WHICH
|
|
1038
1100
|
* MODEL (`judgeModel`), which is what makes a judge-model bump detectable at
|
|
1039
1101
|
* all. */
|
|
@@ -1135,16 +1197,15 @@ async function calibrationGate({ spec, judgedStages, judgeLoop, judgeModel, onJu
|
|
|
1135
1197
|
* has already had its chance, and nobody pays to discover a broken
|
|
1136
1198
|
* declaration.
|
|
1137
1199
|
*
|
|
1138
|
-
* D9.3 AND THE JUDGED
|
|
1139
|
-
*
|
|
1140
|
-
*
|
|
1141
|
-
*
|
|
1142
|
-
*
|
|
1143
|
-
*
|
|
1144
|
-
*
|
|
1145
|
-
*
|
|
1146
|
-
*
|
|
1147
|
-
* its calibration says.
|
|
1200
|
+
* D9.3 AND THE JUDGED CLOSE (2026-08-18 second addendum, ruling 3; hamr: *"fix it now,
|
|
1201
|
+
* we are delivering softgreen, isn't that the whole point?"*; widened 2026-10-09 to mixed
|
|
1202
|
+
* closes, hamr: *"a rubric job should check if judge can judge (calibration) and judge
|
|
1203
|
+
* rules can apply properly to pass ... we can't close everything upfront"*). Gate 3
|
|
1204
|
+
* protects against a close that cannot fail. A judged stage SKIPS the seed read by ruling
|
|
1205
|
+
* 8, so it can never show red there. For ANY close carrying a judged stage (pure or
|
|
1206
|
+
* mixed with mechanical stages), a PASSED calibration gate plays the seed-red role
|
|
1207
|
+
* (`seedProofIsCalibration`). A close with NO judged stage is UNCHANGED: some work stage
|
|
1208
|
+
* must be red at the seed. A failed or missing calibration never satisfies it.
|
|
1148
1209
|
*
|
|
1149
1210
|
* Gates 2 and 3 share ONE execution, and that is stated rather than hidden: at
|
|
1150
1211
|
* job creation the tree IS the seed, so "does every stage run against the real
|
|
@@ -1240,7 +1301,7 @@ export async function prepareSigning({
|
|
|
1240
1301
|
}
|
|
1241
1302
|
// the class rides from the SPEC (PRD v1.57 §2) — gate 1a already refused a spec
|
|
1242
1303
|
// whose class is unknown or locked, so this is the class D5's battery hangs off
|
|
1243
|
-
const dv = validateCloseDecl(spec.closeDecl, { at: 'closeDecl', listing: listed.files, verdictType: spec.verdictType });
|
|
1304
|
+
const dv = validateCloseDecl(spec.closeDecl, { at: 'closeDecl', listing: listed.files, verdictType: spec.verdictType, writeScope: spec.writeScope, jobLines: Array.isArray(spec.jobLines) ? spec.jobLines : null });
|
|
1244
1305
|
// the grounded gate's reds quote BOTH untrusted sources — the declaration, and
|
|
1245
1306
|
// the seed listing itself (the listing rule names what really sits beside an
|
|
1246
1307
|
// invented path). The listing half is a channel gate 1a structurally cannot
|
|
@@ -1349,17 +1410,17 @@ export async function prepareSigning({
|
|
|
1349
1410
|
// the close-authoring layer, and this gate is what refuses it BEFORE a
|
|
1350
1411
|
// signature rather than after a paid run.
|
|
1351
1412
|
//
|
|
1352
|
-
// THE JUDGED
|
|
1353
|
-
//
|
|
1354
|
-
//
|
|
1355
|
-
//
|
|
1356
|
-
//
|
|
1357
|
-
//
|
|
1358
|
-
//
|
|
1359
|
-
//
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
if (
|
|
1413
|
+
// THE JUDGED ROUTE (ruling 3, widened 2026-10-09 by hamr: "a rubric job should
|
|
1414
|
+
// check if judge can judge (calibration) and judge rules can apply properly to
|
|
1415
|
+
// pass ... we can't close everything upfront"). A close carrying ANY judged stage
|
|
1416
|
+
// has a work-bearing stage that ruling 8 exempts from the seed read, so the seed
|
|
1417
|
+
// cannot show it red — not because it measures nothing, but because it is not
|
|
1418
|
+
// measured there. Its proof of failability is the calibration gate, which has
|
|
1419
|
+
// just run and passed (a failed one returned above), and which contains signed
|
|
1420
|
+
// cases the pipe must RED as well as ones it must pass. That holds for a PURE
|
|
1421
|
+
// judged close and a MIXED one alike; a close with NO judged stage takes the
|
|
1422
|
+
// original path unchanged (some work stage must be red at the seed).
|
|
1423
|
+
if (workRed.length === 0 && seedProofIsCalibration(judged, base.gates.calibration)) {
|
|
1363
1424
|
// said out loud in the record: this close passed gate 3 on a DIFFERENT proof,
|
|
1364
1425
|
// and a reader must never have to infer which one from an empty workRed list
|
|
1365
1426
|
base.gates.seedVerdict.satisfiedBy = 'calibration';
|