bareloop 0.10.0 → 0.12.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 +510 -0
- package/README.md +18 -7
- package/bareloop.context.md +456 -19
- package/package.json +3 -3
- package/src/authorflow.js +290 -49
- package/src/authoring.js +258 -46
- package/src/authorjob.js +390 -32
- package/src/authorscout.js +13 -1
- package/src/bridges.js +238 -11
- package/src/calibrate.js +597 -0
- package/src/cardauthor.js +479 -0
- package/src/clock.js +21 -3
- package/src/declaredclose.js +188 -13
- package/src/index.js +90 -7
- package/src/job.js +28 -9
- package/src/judged.js +765 -0
- package/src/kinds.js +578 -13
- package/src/ledger.js +24 -1
- package/src/planrun.js +517 -14
- package/src/ralph.js +9 -0
- package/src/reuse.js +514 -96
- package/src/reviewdoor.js +246 -0
- package/src/root.js +1 -1
- package/src/run.js +81 -8
- package/src/selection.js +23 -7
- package/types/authorflow.d.ts +233 -21
- package/types/authoring.d.ts +99 -39
- package/types/authorjob.d.ts +44 -6
- package/types/authorscout.d.ts +7 -1
- package/types/bridges.d.ts +95 -1
- package/types/calibrate.d.ts +191 -0
- package/types/cardauthor.d.ts +352 -0
- package/types/clock.d.ts +5 -2
- package/types/declaredclose.d.ts +93 -6
- package/types/index.d.ts +11 -7
- package/types/job.d.ts +22 -4
- package/types/judged.d.ts +280 -0
- package/types/kinds.d.ts +227 -8
- package/types/planrun.d.ts +73 -3
- package/types/reuse.d.ts +218 -3
- package/types/reviewdoor.d.ts +81 -0
- package/types/run.d.ts +60 -2
package/src/declaredclose.js
CHANGED
|
@@ -68,11 +68,14 @@
|
|
|
68
68
|
// once instead of once-per-invocation, but `runPlan` has a dozen return paths and
|
|
69
69
|
// a leaked worktree outlives the process that made it.
|
|
70
70
|
|
|
71
|
-
import { runStage, makeSeedTrees, STOP_FAULTS, EXIT_RED } from './kinds.js';
|
|
71
|
+
import { runStage, makeSeedTrees, STOP_FAULTS, EXIT_RED, JUDGED_FLOOR_KIND } from './kinds.js';
|
|
72
72
|
import {
|
|
73
73
|
validateDeclaration, classGuards, genreOwnedEnvNames, ungroundedGenreEnv, GENRE_LANGUAGES, TYPES_GENRE,
|
|
74
|
-
VERDICT_CLASSES, LIVE_CLASSES,
|
|
74
|
+
VERDICT_CLASSES, LIVE_CLASSES, NEVER_OFFERED_KINDS,
|
|
75
75
|
} from './authoring.js';
|
|
76
|
+
// the calibration set's own gate lives beside the rulebook it is graded against
|
|
77
|
+
// (src/judged.js) — one opinion about what a judged floor's signed artifacts are
|
|
78
|
+
import { validateCalibrationSet } from './judged.js';
|
|
76
79
|
import { stageGap } from './ralph.js';
|
|
77
80
|
import { isObj, isNonEmptyString } from './validate.js';
|
|
78
81
|
|
|
@@ -99,7 +102,14 @@ export const DECLARED_GENRES = Object.freeze([TYPES_GENRE.name]);
|
|
|
99
102
|
* constrained to the names the genre OWNS, so it can never become an environment
|
|
100
103
|
* channel of its own.
|
|
101
104
|
*/
|
|
102
|
-
export const CLOSE_DECL_FIELDS = Object.freeze(['genre', 'lang', 'stages', 'notes', 'genreEnv']);
|
|
105
|
+
export const CLOSE_DECL_FIELDS = Object.freeze(['genre', 'lang', 'stages', 'notes', 'genreEnv', 'calibration']);
|
|
106
|
+
|
|
107
|
+
/** what a stored calibration set carries, ENUMERATED: the frozen cases and the judge
|
|
108
|
+
* tier that graded them. Both are inside the spec, so `jobSpecHash` covers both — which
|
|
109
|
+
* is the whole mechanism behind "a judge-model bump forces a full recalibration"
|
|
110
|
+
* (src/judged.js, `JUDGE_MODEL`): the field moves, the hash moves, the signature dies,
|
|
111
|
+
* and the re-sign re-runs the gate. Anything else in there is a field nobody reads. */
|
|
112
|
+
export const CALIBRATION_FIELDS = Object.freeze(['cases', 'judgeModel']);
|
|
103
113
|
|
|
104
114
|
/**
|
|
105
115
|
* The prefix M1 puts on EVERY gap line of a declared stage (its `ctx.gapKeep`).
|
|
@@ -112,12 +122,75 @@ export const DECLARED_GAP_PREFIX = 'close: ';
|
|
|
112
122
|
* whole judged output rather than a hand-picked slice of it. */
|
|
113
123
|
export const DECLARED_GAP_KEEP = `^${DECLARED_GAP_PREFIX.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`;
|
|
114
124
|
|
|
115
|
-
/**
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
|
|
125
|
+
/**
|
|
126
|
+
* The close verdict word for a run WAITING on a person (N4 §1.3/§1.4). A
|
|
127
|
+
* literal, exported, because three readers key on it — the runner's pause
|
|
128
|
+
* branch, the ledger's excluded set, and the trend reader's blind rule — and
|
|
129
|
+
* three hand-spelled copies of one terminal is how they come to disagree.
|
|
130
|
+
*
|
|
131
|
+
* It is not in `CLOSE_FAULTS` and must never be added: a fault says the close
|
|
132
|
+
* could not render a verdict, and this says the close is not FINISHED rendering
|
|
133
|
+
* one. The first escalates; the second is a checkpoint the operator returns to.
|
|
134
|
+
*/
|
|
135
|
+
export const HUMAN_PAUSE = 'human-pause';
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* The RUN-level terminal names the hitl class mints one layer up from
|
|
139
|
+
* `HUMAN_PAUSE` (N4 §1.3–§1.5): the checkpoint, and the honest refusal of the
|
|
140
|
+
* operator's rerun input. Spelled once here, beside the close-verdict word they
|
|
141
|
+
* translate, for the same reason `HUMAN_PAUSE` is — the emitters (`planrun.js`),
|
|
142
|
+
* the ledger's excluded set, the resume reader (`reuse.js`), and the runner
|
|
143
|
+
* script all key on them, and hand-spelled copies of one terminal is how they
|
|
144
|
+
* come to disagree.
|
|
145
|
+
*
|
|
146
|
+
* There were three (2026-08-17): `hitl-cancel` was the signer's own terminal and
|
|
147
|
+
* is DELETED with the door it belonged to. A person who does not want to carry on
|
|
148
|
+
* now PAUSES — the same checkpoint, resumable, retired by the TTL if nobody comes
|
|
149
|
+
* back — so nothing in this tree mints that name any more. The ledger keeps the
|
|
150
|
+
* bare string in its excluded set so a spine written before this change still
|
|
151
|
+
* reads as governance rather than as a capability gap; that is a READER's
|
|
152
|
+
* backward compatibility, and deliberately not a constant a writer could reach.
|
|
153
|
+
*/
|
|
154
|
+
export const HITL_PAUSE = 'hitl-pause';
|
|
155
|
+
export const HITL_DECISION_RED = 'hitl-decision-red';
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* The terminals above that leave the run WAITING ON A PERSON — as a list, minted
|
|
159
|
+
* beside the names it holds so a reader keys on membership rather than on a
|
|
160
|
+
* spelling, and a rename here travels to every reader for free.
|
|
161
|
+
*
|
|
162
|
+
* It is deliberately NOT `reuse.js`'s `CHECKPOINT_OUTCOMES`, which is the wider
|
|
163
|
+
* RESUME list: every stop that left work on disk and an allowance unspent, money
|
|
164
|
+
* and time included. Those are governance stops the reuse loop is meant to try
|
|
165
|
+
* again under its remaining tries; this is the half no further attempt can
|
|
166
|
+
* change, because the allowance still unspent is a human answer. `CHECKPOINT_OUTCOMES`
|
|
167
|
+
* is composed FROM this list so the two can never disagree about which is which.
|
|
168
|
+
*
|
|
169
|
+
* `HITL_DECISION_RED` is not here: it is a verdict already rendered on the operator's
|
|
170
|
+
* own input, not a question still open.
|
|
171
|
+
*/
|
|
172
|
+
export const HUMAN_CHECKPOINTS = Object.freeze([HITL_PAUSE]);
|
|
173
|
+
|
|
174
|
+
/** The verdict classes a DECLARED close may claim. Named as data so the job
|
|
175
|
+
* validator reads one hierarchy table rather than hard-coding a class.
|
|
176
|
+
*
|
|
177
|
+
* `hard` is by CONSTRUCTION: every mechanical kind is a command whose measurement
|
|
178
|
+
* is the truth. `hitl` joined it at N4 slice 1, and `soft` at softgreen module 3
|
|
179
|
+
* — each only because its kind is genuinely LIVE (`human-confirms`, then
|
|
180
|
+
* `judged-floor`): the widening and the kind go in one commit, or this table
|
|
181
|
+
* admits a class the executor cannot render.
|
|
182
|
+
*
|
|
183
|
+
* `hitl` STAYS despite the class's retirement (PRD v1.71: the human stage moved
|
|
184
|
+
* out of the close and became the review door at the end of every run). It is
|
|
185
|
+
* kept because `jobs/litectx-maintainer.json` is a SIGNED spec carrying a hitl
|
|
186
|
+
* declaration, and a signed spec is untouchable — dropping the spelling here
|
|
187
|
+
* would invalidate a signature the operator already gave, which is a re-sign
|
|
188
|
+
* nobody asked for. Retiring it is a separate, deliberate deletion.
|
|
189
|
+
*
|
|
190
|
+
* The direction that matters is unchanged. This says which class a declaration
|
|
191
|
+
* MAY claim; `closeCeiling` says which class its stages actually NEED. A pick
|
|
192
|
+
* below the ceiling is still the silent upgrade `class-ceiling` refuses. */
|
|
193
|
+
export const DECLARED_CLOSE_CLASSES = Object.freeze(['hard', 'soft', 'hitl']);
|
|
121
194
|
|
|
122
195
|
/** @param {any} job @returns {boolean} */
|
|
123
196
|
export function isDeclaredClose(job) {
|
|
@@ -130,12 +203,38 @@ export function isDeclaredClose(job) {
|
|
|
130
203
|
*
|
|
131
204
|
* `offer` and `needs` pass through untouched when a declaration carries them
|
|
132
205
|
* (v1's schema does not let one), so `checkMenu`'s derivation is unchanged.
|
|
206
|
+
*
|
|
207
|
+
* THE JUDGE TIER RIDES ALONG, and it rides as a STAMP for the same reason `offer`
|
|
208
|
+
* does. The tier that graded this close's calibration set is a CLOSE-level fact
|
|
209
|
+
* (`calibration.judgeModel`), and the stage that would buy a judge call is a STAGE;
|
|
210
|
+
* `runStage` is handed stages and never the declaration they came out of. Rather than
|
|
211
|
+
* plumb a second close-level channel down through every executor seam, the one place
|
|
212
|
+
* that already turns a declaration into stages carries it across — and it carries it
|
|
213
|
+
* from the SIGNED bytes, so a stage can never name a tier the signature does not.
|
|
214
|
+
* `runJudgedFloor` refuses a mismatch; the absence of a stamp is the SPEC gate's
|
|
215
|
+
* business (`validateCloseDecl` reds a set with no model), never a runtime fall-back.
|
|
133
216
|
* @param {any} closeDecl
|
|
134
217
|
* @returns {any[]|null} null when this is not a declaration
|
|
135
218
|
*/
|
|
136
219
|
export function declaredStages(closeDecl) {
|
|
137
220
|
if (!isObj(closeDecl) || !Array.isArray(closeDecl.stages)) return null;
|
|
138
|
-
|
|
221
|
+
const calibrationJudgeModel = isObj(closeDecl.calibration) && isNonEmptyString(closeDecl.calibration.judgeModel)
|
|
222
|
+
? closeDecl.calibration.judgeModel
|
|
223
|
+
: null;
|
|
224
|
+
return closeDecl.stages.map((s) => (isObj(s)
|
|
225
|
+
// RULING 5, stamped by the ARBITER rather than trusted from the artefact:
|
|
226
|
+
// a judged or human stage is `offer: false` BY LAW, so `checkMenu` can never
|
|
227
|
+
// hand the agent `check-passes(<a person>)`. The validator refuses a
|
|
228
|
+
// declaration that says anything else (`human-stage-offered`), so this is
|
|
229
|
+
// never overwriting a signer-visible value — it is filling in a law the
|
|
230
|
+
// declaration schema deliberately gives the composer no way to state.
|
|
231
|
+
? {
|
|
232
|
+
...s,
|
|
233
|
+
gapKeep: DECLARED_GAP_KEEP,
|
|
234
|
+
...(NEVER_OFFERED_KINDS.includes(s.kind) ? { offer: false } : {}),
|
|
235
|
+
...(s.kind === JUDGED_FLOOR_KIND && calibrationJudgeModel !== null ? { calibrationJudgeModel } : {}),
|
|
236
|
+
}
|
|
237
|
+
: s));
|
|
139
238
|
}
|
|
140
239
|
|
|
141
240
|
// ── the spec-level gate ──────────────────────────────────────────────────────
|
|
@@ -310,6 +409,56 @@ export function validateCloseDecl(closeDecl, opts = {}) {
|
|
|
310
409
|
});
|
|
311
410
|
for (const r of v.reds) reds.push({ ...r, path: r.path ? `${at}.${r.path}` : at });
|
|
312
411
|
|
|
412
|
+
// ── THE FROZEN CALIBRATION SET (softgreen module 4, design §4.4). It rides
|
|
413
|
+
// the closeDecl rather than a stage's params because it calibrates the RULER,
|
|
414
|
+
// not one stage's parameters — and because it is read BEFORE any stage runs.
|
|
415
|
+
//
|
|
416
|
+
// WHAT IS CHECKED HERE is that a stored set is LEGAL: exactly the signed size,
|
|
417
|
+
// both polarities present, itemized reds naming lines THIS close's card
|
|
418
|
+
// actually carries. WHAT IS NOT is whether the pipe grades it correctly — that
|
|
419
|
+
// is module 5's gate, it costs paid calls, and a validator cannot buy them.
|
|
420
|
+
//
|
|
421
|
+
// A set with NO judged stage to calibrate is its own red: the card lives on
|
|
422
|
+
// the stage, so an orphan set is graded against nothing and would validate
|
|
423
|
+
// clean forever. The reverse (a judged stage with no set) is deliberately NOT
|
|
424
|
+
// redded here — making the set MANDATORY is the signing gate's call, and
|
|
425
|
+
// module 5 owns it.
|
|
426
|
+
if (closeDecl.calibration !== undefined) {
|
|
427
|
+
const judged = (Array.isArray(closeDecl.stages) ? closeDecl.stages : [])
|
|
428
|
+
.filter((/** @type {any} */ s) => isObj(s) && s.kind === JUDGED_FLOOR_KIND);
|
|
429
|
+
if (!isObj(closeDecl.calibration)) {
|
|
430
|
+
red('invalid-value', `${at}.calibration`, 'the frozen calibration set, as an object carrying `cases`');
|
|
431
|
+
} else if (judged.length !== 1) {
|
|
432
|
+
red('calibration-orphan', `${at}.calibration`, `this close carries a calibration set and ${judged.length} `
|
|
433
|
+
+ 'judged-floor stage(s). The set calibrates ONE judged ruler against ONE card — with no judged stage it is '
|
|
434
|
+
+ 'graded against nothing and would read clean forever, and with two there is no one card it belongs to',
|
|
435
|
+
{ judgedStages: judged.length });
|
|
436
|
+
} else {
|
|
437
|
+
for (const key of Object.keys(closeDecl.calibration)) {
|
|
438
|
+
if (!CALIBRATION_FIELDS.includes(key)) red('invalid-value', `${at}.calibration.${key}`, `a calibration set carries ${CALIBRATION_FIELDS.map((f) => `\`${f}\``).join(' and ')} and nothing else`);
|
|
439
|
+
}
|
|
440
|
+
// WHICH TIER GRADED THIS SET, and it is REQUIRED rather than optional. A
|
|
441
|
+
// stored set is only worth the judge that graded it (§4.2's safety argument
|
|
442
|
+
// is BA-20's haiku-4.5 injection evidence and nothing else), so a set with
|
|
443
|
+
// no model beside it is a set nobody can attribute — and the `JUDGE_MODEL`
|
|
444
|
+
// docstring's promise that a bump forces recalibration has nothing to fire
|
|
445
|
+
// on. Required HERE and not at the stage, for the same reason the SET's own
|
|
446
|
+
// presence is the signing gate's call and not this validator's: this gate
|
|
447
|
+
// reads a stored artifact and says whether it is legal. The RUNTIME half —
|
|
448
|
+
// refusing to grade when the stored tier is not the tier this run would buy
|
|
449
|
+
// — is the judged stage's (`runJudgedFloor`), reached through the stamp
|
|
450
|
+
// `declaredStages` puts on the stage.
|
|
451
|
+
if (closeDecl.calibration.judgeModel === undefined) {
|
|
452
|
+
red('missing-required', `${at}.calibration.judgeModel`, 'the judge tier this set was GRADED by — a calibration '
|
|
453
|
+
+ 'nobody can attribute to a model is a floor nobody can tell apart from a bumped one');
|
|
454
|
+
} else if (!isNonEmptyString(closeDecl.calibration.judgeModel)) {
|
|
455
|
+
red('invalid-value', `${at}.calibration.judgeModel`, 'the judge tier this set was graded by, as a non-empty string');
|
|
456
|
+
}
|
|
457
|
+
const cv = validateCalibrationSet(closeDecl.calibration.cases, { card: judged[0]?.params?.card ?? null });
|
|
458
|
+
for (const r of cv.reds) reds.push({ ...r, path: r.path ? `${at}.${r.path}` : `${at}.calibration` });
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
313
462
|
const ok = reds.length === 0;
|
|
314
463
|
return {
|
|
315
464
|
ok,
|
|
@@ -361,7 +510,7 @@ export function guardNames(closeDecl, verdictType) {
|
|
|
361
510
|
* @param {any} r an M1 `StageResult`
|
|
362
511
|
* @param {(s: string) => string} redact
|
|
363
512
|
* @returns {{verdict: string, notes?: string, gap?: string, detail?: string,
|
|
364
|
-
* exitCode?: number, judgedCount?: number}} `notes` is absent, never empty —
|
|
513
|
+
* exitCode?: number, judgedCount?: number, ask?: string|null}} `notes` is absent, never empty —
|
|
365
514
|
* a field that is always there says nothing; a RED carries its diagnostics
|
|
366
515
|
* inside `gap` and so has none of its own.
|
|
367
516
|
*/
|
|
@@ -370,6 +519,13 @@ function translate(r, redact) {
|
|
|
370
519
|
/** absent rather than empty: a field that is always there says nothing */
|
|
371
520
|
const notes = gapText ? { notes: gapText } : {};
|
|
372
521
|
if (r.verdict === 'green') return { verdict: 'satisfied', judgedCount: 1, ...notes };
|
|
522
|
+
// THE PAUSE (N4 §1.3) — its own verdict word, and deliberately NOT one of the
|
|
523
|
+
// three below. `satisfied` would mint a green nobody gave; `needs_revision`
|
|
524
|
+
// would hand the fix worker a gap the person has not written yet; a
|
|
525
|
+
// `CLOSE_FAULTS` word would file a working arbiter as a broken instrument.
|
|
526
|
+
// No `gap` and no `judgedCount`: nothing was judged, and every consumer guards
|
|
527
|
+
// with `if (gap)`.
|
|
528
|
+
if (r.verdict === 'pause') return { verdict: HUMAN_PAUSE, ask: r.detail?.ask ?? null, ...notes };
|
|
373
529
|
if (r.verdict === 'red') {
|
|
374
530
|
return {
|
|
375
531
|
verdict: 'needs_revision',
|
|
@@ -408,11 +564,17 @@ function translate(r, redact) {
|
|
|
408
564
|
* @param {any[]} stages declared stage descriptors, already validated
|
|
409
565
|
* @param {(s: string) => string} [redact] the scrub, applied at THIS boundary
|
|
410
566
|
* @param {{timeoutMs?: number, cwd?: string, seedRef?: string,
|
|
411
|
-
* seedTrees?: any, gapCap?: number, maxBuffer?: number, baselineMode?: 'auto'|'worktree'
|
|
567
|
+
* seedTrees?: any, gapCap?: number, maxBuffer?: number, baselineMode?: 'auto'|'worktree',
|
|
568
|
+
* humanRuling?: {decision: string, text?: string|null}|null,
|
|
569
|
+
* judgeLoop?: ((o: {system: string}) => any)|null,
|
|
570
|
+
* onJudgeCost?: ((c: any) => void)|null}} [opts]
|
|
412
571
|
* @returns {Promise<any>}
|
|
413
572
|
*/
|
|
414
573
|
export async function runDeclaredStages(stages, redact = (s) => s, opts = {}) {
|
|
415
|
-
const {
|
|
574
|
+
const {
|
|
575
|
+
timeoutMs, cwd, seedRef, seedTrees: shared, gapCap, maxBuffer, baselineMode, humanRuling = null,
|
|
576
|
+
judgeLoop = null, onJudgeCost = null,
|
|
577
|
+
} = opts;
|
|
416
578
|
if (!isNonEmptyString(cwd) || !isNonEmptyString(seedRef)) {
|
|
417
579
|
// Refuse rather than guess. A declared close measures against a SEED; a
|
|
418
580
|
// missing one is the close unable to run, not a red about the worker.
|
|
@@ -436,6 +598,19 @@ export async function runDeclaredStages(stages, redact = (s) => s, opts = {}) {
|
|
|
436
598
|
...(gapCap !== undefined ? { gapCap } : {}),
|
|
437
599
|
...(maxBuffer !== undefined ? { maxBuffer } : {}),
|
|
438
600
|
...(baselineMode !== undefined ? { baselineMode } : {}),
|
|
601
|
+
// the SIGNER's answer, when this close has a human stage and a leg is
|
|
602
|
+
// carrying one. Absent (null) is the ordinary path AND the pause path — the
|
|
603
|
+
// human stage reads the absence as "nobody has answered yet", which is the
|
|
604
|
+
// whole of what a pause means.
|
|
605
|
+
humanRuling,
|
|
606
|
+
// THE PAID SEAM and THE METER, both operator territory and both absent on
|
|
607
|
+
// every mechanical close. A judged stage with no seam STOPS as a wiring gap
|
|
608
|
+
// rather than falling back to some other model (M1 owns no provider and the
|
|
609
|
+
// judge tier is pinned), and every locate call it does buy is reported OUT
|
|
610
|
+
// through the meter so the run's ONE ledger can see it — a close has cost $0
|
|
611
|
+
// until this kind, and a budget must fund the attempt PLUS its close.
|
|
612
|
+
judgeLoop,
|
|
613
|
+
onJudgeCost,
|
|
439
614
|
seedTrees,
|
|
440
615
|
};
|
|
441
616
|
try {
|
package/src/index.js
CHANGED
|
@@ -31,23 +31,36 @@ export { validateJob, jobSpecHash, checkApproval, checkMenu, CLOSE_TYPES, CLASS_
|
|
|
31
31
|
export { validatePlan, stageClose, EXIT_TYPES, MAX_EXITS_PER_STEP, MAX_PLAN_STEPS, WRITE_VERBS } from './plan.js';
|
|
32
32
|
export { snapshotScope, evalExits } from './exits.js';
|
|
33
33
|
export { runPlan } from './planrun.js';
|
|
34
|
-
|
|
34
|
+
// `ACCOUNTED_ROUND_TYPES` rides with it: the round records the ONE ledger sums.
|
|
35
|
+
// A runner reconstructing a killed run's spend (the resume fold) must add exactly
|
|
36
|
+
// these and nothing else, and a second hand-kept list is how a paid call comes to
|
|
37
|
+
// be free (`judge-round` — the close's own spend — joined at softgreen module 2).
|
|
38
|
+
export { runJob, ACCOUNTED_ROUND_TYPES } from './run.js';
|
|
35
39
|
// Layer 3 — the REUSE registry (bridge-v1) and D3's display half. The registry is a
|
|
36
40
|
// directory of plain files at an OPERATOR-supplied path (never a default location), the
|
|
37
41
|
// load gate is the code half of D2's split, and `renderListing`/`selectionPrompt` are
|
|
38
42
|
// pure text: they send nothing, parse nothing and decide nothing. The selection CALL and
|
|
39
43
|
// the pin/shortlist/force-cold flow are the adopter's, which is why the pieces they need
|
|
40
44
|
// are exported rather than kept internal to the runner.
|
|
45
|
+
//
|
|
46
|
+
// Softgreen module 6 exports the QUARANTINE surface with them: a judged green is minted
|
|
47
|
+
// HELD and earns nothing until the signer's `accept` at the review door releases it
|
|
48
|
+
// (PRD v1.71 §3). `recordDoor` is the pure half (record the disposition, release
|
|
49
|
+
// forward-only) and `reuseEligibility`/`newestEligibleVersion` are what every consumer
|
|
50
|
+
// asks before starting from a stored plan.
|
|
41
51
|
export {
|
|
42
52
|
BRIDGE_SCHEMA, validateBridge, deriveStatus, listingRow, loadGate,
|
|
43
53
|
mintBridge, appendGreen, appendRed,
|
|
44
54
|
loadBridge, loadRegistry, saveBridge, makeRegistry, registryExists,
|
|
55
|
+
QUARANTINED_VERDICTS, QUARANTINED_CODE, quarantinesCredit, newestEligibleVersion, reuseEligibility, recordDoor,
|
|
45
56
|
} from './bridges.js';
|
|
46
57
|
export { renderListing, selectionPrompt } from './selection.js';
|
|
47
58
|
// Layer 3 modules 4+5 — the D7 envelope and the reuse runner. `runReuse` composes
|
|
48
|
-
// `runJob` under an operator-signed envelope (three explicit numbers, tighten-only)
|
|
49
|
-
// is the only
|
|
50
|
-
//
|
|
59
|
+
// `runJob` under an operator-signed envelope (three explicit numbers, tighten-only). It
|
|
60
|
+
// is not the only registry WRITER any more (2B: `writeRunGreenRow`), but the rule it
|
|
61
|
+
// enforced is unchanged and now lives in both — minting is a graded green's privilege
|
|
62
|
+
// (R1), never a caller's: both writers go through `writeGreenRow`, and neither can mint
|
|
63
|
+
// a row for anything but a green that this run actually earned. `validateEnvelope`/`resolveTrySpec` are
|
|
51
64
|
// exported because an operator runner has to show the resolved per-try spec's HASH at
|
|
52
65
|
// its approval gate: a tightened envelope is a new spec version, and the human signs it.
|
|
53
66
|
// `resolveReuse`/`reuseSpecHash` are the pair that gate must actually PRINT: the try
|
|
@@ -60,7 +73,15 @@ export { renderListing, selectionPrompt } from './selection.js';
|
|
|
60
73
|
// `resumeTreeGate` is the ruling that a resumed patient is continued dirty and never
|
|
61
74
|
// reset. Both are read by a runner BEFORE the approval gate, so neither can live behind
|
|
62
75
|
// `runReuse`'s own entry.
|
|
63
|
-
|
|
76
|
+
// `applyDoorDecision` is the registry half of the REVIEW DOOR (softgreen module 6): the
|
|
77
|
+
// door opens AFTER a run has ended, so a person's answer cannot ride the run's own return
|
|
78
|
+
// path — the runner calls this with the runid it printed at the door.
|
|
79
|
+
// `writeRunGreenRow` is that door's other half (2B), and it is exported for the same
|
|
80
|
+
// reason: a runner that drives `runJob` directly rather than through `runReuse` still ends
|
|
81
|
+
// at a review door, and a door with no row to act on can only describe a held learning
|
|
82
|
+
// credit it has no way to release. STORAGE ONLY — nothing here selects, promotes or reuses
|
|
83
|
+
// a bridge; those stay parked on `layer-3-reuse`.
|
|
84
|
+
export { validateEnvelope, resolveTrySpec, resolveReuse, reuseSpecHash, selectBridge, runReuse, REUSE_GRADED_RED, readResume, resumeTreeGate, CHECKPOINT_OUTCOMES, PAUSE_TTL_MS, checkpointAgeGate, applyDoorDecision, writeRunGreenRow } from './reuse.js';
|
|
64
85
|
export { classifyIncidents, foldLedger, ledgerDeltas, updateLedger, LEDGER_CLASSES } from './ledger.js';
|
|
65
86
|
// THE WORK BRANCH (PRD v1.57 §3). `workBranchName` is exported so an operator runner can
|
|
66
87
|
// SHOW, before the approval gate, which branch the run will work on — the same reason the
|
|
@@ -89,7 +110,66 @@ export { workBranchName, prepareWorkBranch, WORK_BRANCH_PREFIX, WORK_BRANCH_RE }
|
|
|
89
110
|
export {
|
|
90
111
|
runDeclaredClose, runStage, seedRead, seedAtHead, seedListing, changedSet, makeSeedTrees,
|
|
91
112
|
LIVE_KINDS, STOP_FAULTS, JUDGED_MARKER, EXIT_GREEN, EXIT_RED, EXIT_STOP,
|
|
113
|
+
// N4 slice 1 — the hitl surface an adopting runner needs: the three doors, the
|
|
114
|
+
// gate that reads a signer's answer (and refuses an empty one), and the seed
|
|
115
|
+
// exemption ruling 8 states.
|
|
116
|
+
HUMAN_DECISIONS, SEED_EXEMPT_KINDS, JUDGED_FLOOR_KIND, normalizeHumanRuling, resolveHumanRuling,
|
|
117
|
+
// SOFTGREEN module 2 — the judged stage's own arbiter-owned bounds. An adopter
|
|
118
|
+
// wiring `judgeProvider` needs both numbers to reason about what a judged close
|
|
119
|
+
// can cost: one paid call per artifact, at most one retry each.
|
|
120
|
+
JUDGE_ATTEMPTS, MAX_JUDGED_PATHS,
|
|
121
|
+
// SOFTGREEN module 8 — the REVIEW DOOR's vocabulary: the record type a run
|
|
122
|
+
// writes when it ends at the door, which terminals open one, which classes open
|
|
123
|
+
// one unasked, and the mechanical-only rule an `accept` re-proves against.
|
|
124
|
+
REVIEW_DOOR, DOOR_OPEN_OUTCOMES, REVIEW_DOOR_CLASSES, doorOpens, mechanicalStages,
|
|
92
125
|
} from './kinds.js';
|
|
126
|
+
// SOFTGREEN module 8 — the door's ANSWERING half. A run OPENS the door on its own
|
|
127
|
+
// spine and ends; a person answers minutes or days later, from another process, so
|
|
128
|
+
// the answer cannot ride the run's return path. `answerReviewDoor` is that seam:
|
|
129
|
+
// it re-proves the tree for an `accept`, releases a held judged green through
|
|
130
|
+
// module 6's registry half, and refuses an expired door under the same 60-day TTL a
|
|
131
|
+
// hitl checkpoint keeps. It never returns, writes or implies a verdict.
|
|
132
|
+
export { answerReviewDoor, doorRecordOf, doorAgeGate } from './reviewdoor.js';
|
|
133
|
+
// SOFTGREEN module 1 — the judged floor's core. The pieces an adopter (and an
|
|
134
|
+
// integrating UI) genuinely needs: the PIN (`JUDGE_MODEL` — what a wired judge
|
|
135
|
+
// provider must be bound to, and a bump of which forces recalibration), the
|
|
136
|
+
// rulebook a card SELECTS from, the card gate, and the two halves themselves so a
|
|
137
|
+
// calibration harness can grade the whole pipe without running a close.
|
|
138
|
+
// `defaultJudgeLoop` is the one spelling of how this repo drives a judge; the
|
|
139
|
+
// runner reaches it through `runPlan`'s `judgeProvider`, and it is exported so a
|
|
140
|
+
// caller building its own harness does not spell a second one.
|
|
141
|
+
export {
|
|
142
|
+
JUDGE_MODEL, JUDGE_MAX_TOKENS, JUDGE_RULES, JUDGE_RULE_IDS, LOCATE_AXES, LOCATE_LABEL,
|
|
143
|
+
validateCard, validateFacts, locatePrompt, runLocate, decide, defaultJudgeLoop,
|
|
144
|
+
// SOFTGREEN module 4 — what a legal SIGNED calibration set is. The SIZE is
|
|
145
|
+
// hamr's own ruling and a size change is a spec-level threshold change;
|
|
146
|
+
// `expectedOf` is the ONE reduction of a `decide()` result to the shape a case
|
|
147
|
+
// stores, so a calibration harness compares one shape rather than two.
|
|
148
|
+
CALIBRATION_SIZE, CASE_VERDICTS, validateCalibrationSet, validateJudgedArtifacts, expectedOf,
|
|
149
|
+
} from './judged.js';
|
|
150
|
+
// SOFTGREEN module 4 — the COMPILE. Q6 becomes the rubric card and Q7 becomes the
|
|
151
|
+
// frozen calibration set, both on the D5 shown-and-fixed path: the LLM proposes
|
|
152
|
+
// through `proposeJudgedArtifacts`, an integrating UI shows the proposal, the
|
|
153
|
+
// SIGNER's fix goes through `signJudgedArtifacts`, and `foldJudgedArtifacts`
|
|
154
|
+
// enumerates both into the closeDecl — where the spec hash covers them, so a card
|
|
155
|
+
// line or a case is a re-sign like every other spec edit.
|
|
156
|
+
export {
|
|
157
|
+
proposeJudgedArtifacts, signJudgedArtifacts, foldJudgedArtifacts,
|
|
158
|
+
proposalSchema, proposalTool, cardCasesPrompt,
|
|
159
|
+
PROPOSAL_TOOL_NAME, PROPOSAL_LABEL, MAX_PROPOSAL_RETRIES, COMPILE_SYSTEM,
|
|
160
|
+
} from './cardauthor.js';
|
|
161
|
+
// SOFTGREEN module 5 — THE CALIBRATION GATE. `runCalibration` grades the whole
|
|
162
|
+
// pipe (locate + decide) over the signed ten and the five arbiter-owned injection
|
|
163
|
+
// artifacts; `prepareSigning` runs it and refuses on anything short of all of
|
|
164
|
+
// them. Exported because an integrating UI has to be able to RUN and RENDER the
|
|
165
|
+
// gate — the graded rows are what a signer reads before giving a signature — and
|
|
166
|
+
// because the battery is evidence a reader may want to inspect rather than take
|
|
167
|
+
// on trust. `INJECTION_LOCATE_BATTERY` and `INJECTION_CARD` are ARBITER-OWNED
|
|
168
|
+
// constants: no signer authors them and nothing stores them in a spec.
|
|
169
|
+
export {
|
|
170
|
+
runCalibration, compareExpectation, factsResist, artifactHash,
|
|
171
|
+
INJECTION_LOCATE_BATTERY, INJECTION_CARD, CALIBRATION_LABEL, CASUALTY_AXES,
|
|
172
|
+
} from './calibrate.js';
|
|
93
173
|
// M2 — what a declaration may SAY, and whether one said it legally. The
|
|
94
174
|
// catalogue and the genre are DATA an integrating UI renders (the kind menu, the
|
|
95
175
|
// guard batteries it must show the user under D5 and cannot let them remove).
|
|
@@ -102,7 +182,9 @@ export {
|
|
|
102
182
|
DIRECTIONS, BASELINES, MAX_STAGES,
|
|
103
183
|
// the verdict-class surface (PRD v1.57 §1–§2): the radio's own menu, the guard
|
|
104
184
|
// battery keyed off it, and the ceiling that makes the pick a promise
|
|
105
|
-
VERDICT_CLASSES, LOCKED_CLASSES, LIVE_CLASSES, CLASS_BATTERIES, classGuards, closeCeiling,
|
|
185
|
+
VERDICT_CLASSES, LOCKED_CLASSES, LIVE_CLASSES, CLASS_BATTERIES, classGuards, classMenu, closeCeiling,
|
|
186
|
+
// ruling 5 as data: the kinds no agent may ever be offered as an in-run ruler
|
|
187
|
+
NEVER_OFFERED_KINDS,
|
|
106
188
|
} from './authoring.js';
|
|
107
189
|
// M3 — the looking half and the writing half. `authorClose` is the grounded
|
|
108
190
|
// loop; `runAuthorScout`/`buildSeedListing` are exported because a caller that
|
|
@@ -119,7 +201,7 @@ export {
|
|
|
119
201
|
export { scrubRaw, RAW_PERSIST_MAX, RAW_TRIM_MARKER } from './text.js';
|
|
120
202
|
export {
|
|
121
203
|
authorClose, authorPrompt, declarationSchema, makeLoopGenerate, MAX_REVISIONS,
|
|
122
|
-
QUESTION_SETS, GREEN_QUESTIONS, CLASS_STATEMENTS, questionsFor, requiredAnswersFor,
|
|
204
|
+
QUESTION_SETS, GREEN_QUESTIONS, SOFTGREEN_QUESTIONS, CLASS_STATEMENTS, questionsFor, requiredAnswersFor,
|
|
123
205
|
} from './authorflow.js';
|
|
124
206
|
// M4a — the runtime bridge. `validateCloseDecl` is the spec-level gate (the job
|
|
125
207
|
// validator's own branch calls it); `runDeclaredStages` is the executor seam a
|
|
@@ -128,6 +210,7 @@ export {
|
|
|
128
210
|
export {
|
|
129
211
|
validateCloseDecl, runDeclaredStages, declaredStages, isDeclaredClose, guardNames, closeGrade,
|
|
130
212
|
DECLARED_GENRES, DECLARED_GAP_PREFIX, DECLARED_GAP_KEEP, DECLARED_CLOSE_CLASSES, CLOSE_DECL_FIELDS,
|
|
213
|
+
HUMAN_PAUSE, HITL_PAUSE, HITL_DECISION_RED, HUMAN_CHECKPOINTS,
|
|
131
214
|
} from './declaredclose.js';
|
|
132
215
|
export { closeStagesOf } from './plan.js';
|
|
133
216
|
// M4b — the interview, the refusal, the composition, and D9's three gates.
|
package/src/job.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Job-spec validator — the OPERATOR-owned half of the config story (N1,
|
|
2
|
-
// design record: docs/
|
|
2
|
+
// design record: docs/02-features/2026-07-12-n1-job-close-schema-design.md).
|
|
3
3
|
// A job spec is the arbiter's rulebook: the close chain, the budget, the
|
|
4
4
|
// outer write fence, the escalation route. It is pure declarative data —
|
|
5
5
|
// no freeform code is expressible anywhere in it — and it validates
|
|
@@ -98,12 +98,30 @@ export const MIN_WALL_MS = 120_000;
|
|
|
98
98
|
* counts admission demand, and a generic invalid-value would bury it as a typo. */
|
|
99
99
|
export const LOCKED_TOOLS = Object.freeze(['run']);
|
|
100
100
|
/** Layer 2 (design record 2026-07-21, decision 5): the verdict-type radio of
|
|
101
|
-
* the four-field plan shape.
|
|
102
|
-
* declared-but-locked
|
|
103
|
-
*
|
|
104
|
-
*
|
|
101
|
+
* the four-field plan shape. All three are ADMITTED as of softgreen module 3;
|
|
102
|
+
* the declared-but-locked mechanism below (`LOCKED_VERDICTS`) is what carried
|
|
103
|
+
* them here one rung at a time — declaring a locked one is a `request-red` (the
|
|
104
|
+
* tool-menu pattern: disclosure ≠ admission), so the ledger counted the demand
|
|
105
|
+
* instead of the schema being reshaped when the rung finally landed. */
|
|
105
106
|
export const VERDICT_TYPES = Object.freeze(['green', 'soft-green', 'hitl']);
|
|
106
|
-
|
|
107
|
+
/** N4 slice 1 admitted `hitl`; softgreen module 3 admits `soft-green`, and the
|
|
108
|
+
* menu is now EMPTY — every class the radio names is built.
|
|
109
|
+
*
|
|
110
|
+
* `soft-green`'s lock was never a policy, it was a missing floor: a judged
|
|
111
|
+
* verdict with no ruler mints nothing honestly. Modules 1 and 2 built the ruler
|
|
112
|
+
* (a pinned judge that only LOCATES, an arbiter-owned `decide()`, a calibration
|
|
113
|
+
* gate) and wired it as the `judged-floor` kind, so the lock has nothing left to
|
|
114
|
+
* protect (design record 2026-08-17 §4, and the 2026-08-18 thresholds addendum).
|
|
115
|
+
*
|
|
116
|
+
* EMPTY here means "nothing is locked", which is a TRUE statement about a closed
|
|
117
|
+
* menu — not F59's absent-read-as-empty, where a `{}` stood in for a fact nobody
|
|
118
|
+
* gathered. The mechanism below stays exactly as it is: it is read at call time
|
|
119
|
+
* from this constant, so re-locking a class (or admitting a fourth) is one edit
|
|
120
|
+
* and the counted `request-red` path comes back with it. `LOCKED_CLASSES` in
|
|
121
|
+
* src/authoring.js is the twin and moved in the same commit; the suite pins the
|
|
122
|
+
* two identical. */
|
|
123
|
+
/** @type {readonly string[]} */
|
|
124
|
+
export const LOCKED_VERDICTS = Object.freeze([]);
|
|
107
125
|
/** which close CLASS a declared verdictType demands — the close hierarchy one
|
|
108
126
|
* level up: the same laundering guard as CLASS_BY_CLOSE, applied to the plan
|
|
109
127
|
* shape's single close (green on a rubric would be a fake-hard verdict). */
|
|
@@ -515,8 +533,9 @@ function validateStagedClose(stages, red) {
|
|
|
515
533
|
* stages instead (PRD v1.28/v1.32), and a spec that still declares it gets the
|
|
516
534
|
* `checks-derived` red below rather than a bare unknown-field.
|
|
517
535
|
*
|
|
518
|
-
*
|
|
519
|
-
*
|
|
536
|
+
* Every verdictType the radio names is admitted (softgreen module 3); a class
|
|
537
|
+
* that were locked reds as `request-red` — admission demand the ledger counts,
|
|
538
|
+
* never a grant.
|
|
520
539
|
* The close is validated one level up as well: a declared verdict demands a
|
|
521
540
|
* close CLASS, so green-on-rubric reds as `close-hierarchy` rather than
|
|
522
541
|
* laundering a soft judgement into a hard one.
|
|
@@ -536,7 +555,7 @@ function validatePlanShape(spec, red, reds) {
|
|
|
536
555
|
// the type rides as a structured field — the ledger keys admission demand
|
|
537
556
|
// on it, never on prose. `lib: 'bareloop'`: the refused catalogue is OURS
|
|
538
557
|
// (VERDICT_TYPES), so this demand is never an upstream ask.
|
|
539
|
-
reds.push({ code: 'request-red', path: 'verdictType', verb: spec.verdictType, lib: 'bareloop', detail: `"${spec.verdictType}" is declared-but-locked — not at this rung (
|
|
558
|
+
reds.push({ code: 'request-red', path: 'verdictType', verb: spec.verdictType, lib: 'bareloop', detail: `"${spec.verdictType}" is declared-but-locked — not at this rung (admitted: ${VERDICT_TYPES.filter((v) => !LOCKED_VERDICTS.includes(v)).join(', ')}); this red IS the admission evidence, never a grant` });
|
|
540
559
|
demanded = CLASS_BY_VERDICT[spec.verdictType];
|
|
541
560
|
} else if (!VERDICT_TYPES.includes(spec.verdictType)) {
|
|
542
561
|
red('invalid-value', 'verdictType', `menu: ${VERDICT_TYPES.join('|')} — an unknown type is a typo, never a request`);
|