bareloop 0.11.0 → 0.13.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 +252 -0
- package/README.md +3 -2
- package/bareloop.context.md +387 -9
- package/package.json +1 -1
- package/src/authorflow.js +176 -33
- package/src/authoring.js +170 -25
- package/src/authorjob.js +340 -26
- package/src/bridges.js +237 -10
- package/src/calibrate.js +597 -0
- package/src/cardauthor.js +479 -0
- package/src/declaredclose.js +108 -10
- package/src/index.js +100 -9
- package/src/job.js +27 -13
- package/src/judged.js +765 -0
- package/src/kinds.js +390 -6
- package/src/ledger.js +102 -0
- package/src/plan.js +43 -2
- package/src/planrun.js +492 -26
- package/src/readshim.js +538 -0
- package/src/reuse.js +322 -90
- package/src/reviewdoor.js +246 -0
- package/src/run.js +95 -6
- package/src/selection.js +22 -6
- package/src/tools.js +34 -2
- package/types/authorflow.d.ts +147 -9
- package/types/authoring.d.ts +54 -8
- package/types/authorjob.d.ts +36 -6
- package/types/bridges.d.ts +95 -1
- package/types/calibrate.d.ts +191 -0
- package/types/cardauthor.d.ts +352 -0
- package/types/declaredclose.d.ts +34 -5
- package/types/index.d.ts +12 -7
- package/types/job.d.ts +22 -9
- package/types/judged.d.ts +280 -0
- package/types/kinds.d.ts +151 -4
- package/types/ledger.d.ts +58 -0
- package/types/plan.d.ts +19 -2
- package/types/planrun.d.ts +91 -2
- package/types/readshim.d.ts +117 -0
- package/types/reuse.d.ts +146 -3
- package/types/reviewdoor.d.ts +81 -0
- package/types/run.d.ts +63 -1
- package/types/tools.d.ts +17 -1
package/types/authorflow.d.ts
CHANGED
|
@@ -32,12 +32,21 @@ export function schemaCoverage(catalogue?: Record<string, any>): {
|
|
|
32
32
|
* and they arrive as validation reds through the revise loop — a schema that
|
|
33
33
|
* tried to restate them would be a second validator.
|
|
34
34
|
*
|
|
35
|
+
* `verdictType` narrows the branches to the kinds the picked class may express
|
|
36
|
+
* (softgreen module 3, `classMenu`): a green job cannot SPELL `judged-floor`,
|
|
37
|
+
* rather than spelling it and being told afterwards. COVERAGE is still computed
|
|
38
|
+
* over the WHOLE catalogue — the class filter must never make a parameter look
|
|
39
|
+
* orphaned and turn a menu narrowing into a catalogue-drift alarm.
|
|
40
|
+
*
|
|
35
41
|
* THROWS on a catalogue it cannot cover: a malformed catalogue is a bareloop bug,
|
|
36
42
|
* and failing loudly beats emitting a schema that quietly cannot say something
|
|
37
43
|
* the validator will then demand.
|
|
38
44
|
* @param {Record<string, any>} [catalogue]
|
|
45
|
+
* @param {{verdictType?: string|null}} [o]
|
|
39
46
|
*/
|
|
40
|
-
export function declarationSchema(catalogue?: Record<string, any
|
|
47
|
+
export function declarationSchema(catalogue?: Record<string, any>, { verdictType }?: {
|
|
48
|
+
verdictType?: string | null;
|
|
49
|
+
}): {
|
|
41
50
|
type: string;
|
|
42
51
|
properties: {
|
|
43
52
|
stages: {
|
|
@@ -86,12 +95,13 @@ export function declarationSchema(catalogue?: Record<string, any>): {
|
|
|
86
95
|
* The output channel. It records and acknowledges; it takes no action, which is
|
|
87
96
|
* why handing it to a toolless authoring call does not reopen D3.
|
|
88
97
|
* @param {{calls: any[]}} box
|
|
89
|
-
* @param {{catalogue?: Record<string, any
|
|
98
|
+
* @param {{catalogue?: Record<string, any>, verdictType?: string|null}} [o]
|
|
90
99
|
*/
|
|
91
100
|
export function declarationTool(box: {
|
|
92
101
|
calls: any[];
|
|
93
|
-
}, { catalogue }?: {
|
|
102
|
+
}, { catalogue, verdictType }?: {
|
|
94
103
|
catalogue?: Record<string, any>;
|
|
104
|
+
verdictType?: string | null;
|
|
95
105
|
}): {
|
|
96
106
|
name: string;
|
|
97
107
|
description: string;
|
|
@@ -146,9 +156,19 @@ export function declarationTool(box: {
|
|
|
146
156
|
* The catalogue, as the authoring call sees it — rendered FROM the catalogue, so
|
|
147
157
|
* the prompt and the validator judge one set of facts. `shape` and `asserts` are
|
|
148
158
|
* M2's own strings.
|
|
159
|
+
*
|
|
160
|
+
* `verdictType` narrows it to the picked class's menu, exactly as it narrows the
|
|
161
|
+
* schema — the prompt and the tool must offer ONE set of kinds, or the composer
|
|
162
|
+
* reads about a kind it cannot call. A kind above the ceiling is omitted rather
|
|
163
|
+
* than rendered as unavailable: a LOCKED row says "this does not run in v1",
|
|
164
|
+
* which is a fact about the build; "not for your class" is a fact about a choice
|
|
165
|
+
* the person already made, and printing it invites the composer to argue with it.
|
|
149
166
|
* @param {Record<string, any>} [catalogue]
|
|
167
|
+
* @param {{verdictType?: string|null}} [o]
|
|
150
168
|
*/
|
|
151
|
-
export function catalogueBlock(catalogue?: Record<string, any
|
|
169
|
+
export function catalogueBlock(catalogue?: Record<string, any>, { verdictType }?: {
|
|
170
|
+
verdictType?: string | null;
|
|
171
|
+
}): string;
|
|
152
172
|
/**
|
|
153
173
|
* HOW THE GENRE'S OWN TOOLS PRINT — rendered FROM `genreInstruments`, so the
|
|
154
174
|
* pattern the author is handed and the pattern the tree already grades with are
|
|
@@ -433,6 +453,48 @@ export function makeLoopGenerate(provider: any, { system, maxTokens }?: {
|
|
|
433
453
|
system?: string;
|
|
434
454
|
maxTokens?: number;
|
|
435
455
|
}): (messages: any[], tools: any[], opts?: any) => Promise<any>;
|
|
456
|
+
/**
|
|
457
|
+
* ONE structured ask, with the malformed-emission retry bounded on its OWN axis.
|
|
458
|
+
* A reply that carries no artifact is an ARTIFACT-RED — name the axis, retry
|
|
459
|
+
* under a cap, write nothing — and it never consumes a revision, because a
|
|
460
|
+
* transport fault is not a defect in a close.
|
|
461
|
+
*
|
|
462
|
+
* THE CHANNEL IS A PARAMETER, and that is softgreen module 4's only change here:
|
|
463
|
+
* the rubric card and the calibration set are a SECOND schema-forced emission
|
|
464
|
+
* with the same failure modes, and a second ladder is a second place for the cap,
|
|
465
|
+
* the reject block and the re-ask to drift. The declaration channel below is the
|
|
466
|
+
* one this loop has always used, spelled out rather than inlined — nothing about
|
|
467
|
+
* the authoring path's behaviour moves.
|
|
468
|
+
* @param {{messages: any[], generate: Function, mode: 'tool'|'text', retries: number,
|
|
469
|
+
* label: string, book: ReturnType<typeof makeCostBook>,
|
|
470
|
+
* channel: {name: string, instruction: string, textPath?: string, tool: (box: {calls: any[]}) => any}}} o
|
|
471
|
+
* @returns {Promise<{artifact: any, attempts: number, convo: any[], raw: string,
|
|
472
|
+
* providerError: string|null, red: Red|null, budget: 'cap-halt'|'pricing-red'|null}>}
|
|
473
|
+
*/
|
|
474
|
+
export function askStructured({ messages, generate, mode, retries, label, book, channel }: {
|
|
475
|
+
messages: any[];
|
|
476
|
+
generate: Function;
|
|
477
|
+
mode: "tool" | "text";
|
|
478
|
+
retries: number;
|
|
479
|
+
label: string;
|
|
480
|
+
book: ReturnType<typeof makeCostBook>;
|
|
481
|
+
channel: {
|
|
482
|
+
name: string;
|
|
483
|
+
instruction: string;
|
|
484
|
+
textPath?: string;
|
|
485
|
+
tool: (box: {
|
|
486
|
+
calls: any[];
|
|
487
|
+
}) => any;
|
|
488
|
+
};
|
|
489
|
+
}): Promise<{
|
|
490
|
+
artifact: any;
|
|
491
|
+
attempts: number;
|
|
492
|
+
convo: any[];
|
|
493
|
+
raw: string;
|
|
494
|
+
providerError: string | null;
|
|
495
|
+
red: Red | null;
|
|
496
|
+
budget: "cap-halt" | "pricing-red" | null;
|
|
497
|
+
}>;
|
|
436
498
|
/**
|
|
437
499
|
* THE AUTHORING FLOW.
|
|
438
500
|
*
|
|
@@ -615,11 +677,50 @@ export const HITL_QUESTIONS: Readonly<{
|
|
|
615
677
|
5: "What would make you say this came back worse than before?";
|
|
616
678
|
}>;
|
|
617
679
|
/**
|
|
618
|
-
*
|
|
619
|
-
*
|
|
620
|
-
*
|
|
621
|
-
*
|
|
622
|
-
*
|
|
680
|
+
* THE SOFTGREEN SET (module 3, design record §4.6) — the green questions BYTE
|
|
681
|
+
* FOR BYTE, plus TWO.
|
|
682
|
+
*
|
|
683
|
+
* Same discipline as the hitl set, and for the same reason: a softgreen close is
|
|
684
|
+
* *mechanical stages first, the judged stage after* (§4.5), so a softgreen job
|
|
685
|
+
* needs every mechanical fact a green one does. No renumbering of the green set,
|
|
686
|
+
* no genre question, nothing reworded.
|
|
687
|
+
*
|
|
688
|
+
* The two additions are the only things the green set cannot supply, and neither
|
|
689
|
+
* is a flourish — they are the judged floor's two REQUIRED inputs:
|
|
690
|
+
* - Q6 becomes the RUBRIC CARD (§4.3): the person's own pass/fail lines,
|
|
691
|
+
* compiled into enumerated items, signed, and enumerated in the spec hash.
|
|
692
|
+
* The documented ceiling rides with it — the judge catches violations of
|
|
693
|
+
* STATED card items only, and the fix for a miss is a new card line and a
|
|
694
|
+
* re-sign, never a smarter judge.
|
|
695
|
+
* - Q7 becomes the frozen CALIBRATION SET (§4.4): the whole pipe must grade it
|
|
696
|
+
* correctly before the close is signable.
|
|
697
|
+
* Both are asked in the person's own terms — an example they would pass and one
|
|
698
|
+
* they would fail, not a rubric they are asked to write — because compiling a lay
|
|
699
|
+
* answer into extractable items is the LLM's job (D5, proposed-and-signed) and
|
|
700
|
+
* asking the person to do it is the SWE tax this product refuses.
|
|
701
|
+
*
|
|
702
|
+
* What the two answers COMPILE INTO is module 4. This module carries them
|
|
703
|
+
* exactly as the hitl set carries its own last answer: they ride in `answers`,
|
|
704
|
+
* they reach the composer verbatim through the prompt's interview block, and
|
|
705
|
+
* nothing here derives anything from them.
|
|
706
|
+
*/
|
|
707
|
+
export const SOFTGREEN_QUESTIONS: Readonly<{
|
|
708
|
+
6: "When you judge the result yourself, what separates a pass from a fail? Name the few things you actually look for.";
|
|
709
|
+
7: "Give one example you'd pass and one you'd fail, and say why.";
|
|
710
|
+
1: "What do you want done?";
|
|
711
|
+
2: "Which files or folders should change? And which files should the work read or draw from (they stay untouched)?";
|
|
712
|
+
3: "Is there anything that must not change?";
|
|
713
|
+
4: "How do you check today whether it's working?";
|
|
714
|
+
5: "What would make you say this came back worse than before?";
|
|
715
|
+
}>;
|
|
716
|
+
/**
|
|
717
|
+
* The three sets, all LIVE as of softgreen module 3. The locked shape stays
|
|
718
|
+
* spelled out in the readers below rather than deleted with the last locked
|
|
719
|
+
* class: `questions: null` is ABSENT, never `{}` — an empty set would read as
|
|
720
|
+
* "this class needs no questions", a different and false statement (F59's
|
|
721
|
+
* distinction, at the interview layer) — and a locked class refuses at ADMISSION,
|
|
722
|
+
* before its questions run, so the two readers throw rather than hand back
|
|
723
|
+
* nothing. A fourth class arrives locked, and the machinery is here for it.
|
|
623
724
|
* @type {Record<string, {locked: boolean, questions: Record<string|number, string>|null, required: readonly number[]|null}>}
|
|
624
725
|
*/
|
|
625
726
|
export const QUESTION_SETS: Record<string, {
|
|
@@ -823,6 +924,43 @@ export const PARAM_SCHEMAS: Readonly<{
|
|
|
823
924
|
requireNonEmpty: {
|
|
824
925
|
const: boolean;
|
|
825
926
|
};
|
|
927
|
+
card: {
|
|
928
|
+
type: string;
|
|
929
|
+
description: string;
|
|
930
|
+
properties: {
|
|
931
|
+
items: {
|
|
932
|
+
type: string;
|
|
933
|
+
minItems: number;
|
|
934
|
+
items: {
|
|
935
|
+
type: string;
|
|
936
|
+
properties: {
|
|
937
|
+
rule: {
|
|
938
|
+
enum: string[];
|
|
939
|
+
description: string;
|
|
940
|
+
};
|
|
941
|
+
text: {
|
|
942
|
+
type: string;
|
|
943
|
+
minLength: number;
|
|
944
|
+
description: string;
|
|
945
|
+
};
|
|
946
|
+
};
|
|
947
|
+
required: string[];
|
|
948
|
+
additionalProperties: boolean;
|
|
949
|
+
};
|
|
950
|
+
};
|
|
951
|
+
};
|
|
952
|
+
required: string[];
|
|
953
|
+
additionalProperties: boolean;
|
|
954
|
+
};
|
|
955
|
+
paths: {
|
|
956
|
+
type: string;
|
|
957
|
+
minItems: number;
|
|
958
|
+
maxItems: number;
|
|
959
|
+
items: {
|
|
960
|
+
type: string;
|
|
961
|
+
};
|
|
962
|
+
description: string;
|
|
963
|
+
};
|
|
826
964
|
ask: {
|
|
827
965
|
type: string;
|
|
828
966
|
minLength: number;
|
package/types/authoring.d.ts
CHANGED
|
@@ -9,10 +9,11 @@
|
|
|
9
9
|
* `CLASS_BY_VERDICT` exist to stop, defeated from ABOVE by never letting the
|
|
10
10
|
* illegal pair be formed.
|
|
11
11
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
12
|
+
* NO LONGER INERT: `human-confirms` (N4 slice 1) and `judged-floor` (softgreen
|
|
13
|
+
* module 2) are both live and both above green, so a green declaration naming
|
|
14
|
+
* one is a real red rather than a hypothetical. `classMenu` below is the other
|
|
15
|
+
* half of the same rule, applied one step earlier — the composer is never handed
|
|
16
|
+
* a kind this would refuse.
|
|
16
17
|
*
|
|
17
18
|
* A kind the catalogue does not know contributes NOTHING — it already carries an
|
|
18
19
|
* `unknown-kind` red, and guessing a class for it would be deriving the promise
|
|
@@ -28,6 +29,31 @@ export function closeCeiling(declaration: any, catalogue?: Record<string, KindSp
|
|
|
28
29
|
stage: string | null;
|
|
29
30
|
locked: boolean;
|
|
30
31
|
} | null;
|
|
32
|
+
/**
|
|
33
|
+
* THE MENU A JOB OF THIS CLASS MAY EXPRESS — the ceiling rule, moved one step
|
|
34
|
+
* EARLIER, to the moment the composer is handed its vocabulary.
|
|
35
|
+
*
|
|
36
|
+
* `closeCeiling` refuses an above-ceiling kind AFTER a paid call has composed a
|
|
37
|
+
* declaration around it. That is a late red for an illegal pick the composer was
|
|
38
|
+
* shown in its own catalogue and its own tool schema — and the standing rule is
|
|
39
|
+
* the opposite: wherever only a closed set can satisfy a field, hand over the
|
|
40
|
+
* enumerated set, so the illegal value is INEXPRESSIBLE rather than rejected
|
|
41
|
+
* after the fact (the `check-passes(name)` model). A green job is therefore never
|
|
42
|
+
* shown `judged-floor` or `human-confirms` at all.
|
|
43
|
+
*
|
|
44
|
+
* ONLY THE MENU NARROWS. The VALIDATOR keeps the whole catalogue, deliberately:
|
|
45
|
+
* a model that names a kind it was never offered must be told which RULE it
|
|
46
|
+
* broke (`class-ceiling`), not that the kind does not exist (`unknown-kind`) —
|
|
47
|
+
* one is the truth and the other invites it to invent a spelling.
|
|
48
|
+
*
|
|
49
|
+
* `null` means UNFILTERED and exists for callers inspecting the catalogue itself
|
|
50
|
+
* (the schema/coverage suite). Every composing call site passes the user's own
|
|
51
|
+
* picked class; `authorPrompt` already throws without one.
|
|
52
|
+
* @param {string|null} verdictType
|
|
53
|
+
* @param {Record<string, KindSpec>} [catalogue]
|
|
54
|
+
* @returns {string[]} kind names at or below the picked class, in catalogue order
|
|
55
|
+
*/
|
|
56
|
+
export function classMenu(verdictType: string | null, catalogue?: Record<string, KindSpec>): string[];
|
|
31
57
|
/**
|
|
32
58
|
* Which kinds can carry an environment at all, read off the CATALOGUE rather
|
|
33
59
|
* than a hand-kept list. ONE spelling, because two consumers must agree
|
|
@@ -291,8 +317,14 @@ export function normalizeDeclaration(declaration: any): any;
|
|
|
291
317
|
/** the whole menu, in ascending order of what it takes to render the verdict */
|
|
292
318
|
export const VERDICT_CLASSES: readonly string[];
|
|
293
319
|
/** declared-but-locked: selecting one is COUNTED demand, never an admission.
|
|
294
|
-
* `hitl` LEFT this list at N4 slice 1
|
|
295
|
-
*
|
|
320
|
+
* `hitl` LEFT this list at N4 slice 1 and `soft-green` at softgreen module 3
|
|
321
|
+
* (see `src/job.js`'s twin, `LOCKED_VERDICTS`, which moved in the same commit) —
|
|
322
|
+
* the judged floor its lock was waiting on is built, wired and calibrated.
|
|
323
|
+
*
|
|
324
|
+
* EMPTY is a true statement about a closed menu, not F59's absent-as-empty: the
|
|
325
|
+
* mechanism is read at call time from this constant, so re-locking a class is
|
|
326
|
+
* one edit and every counted refusal comes back with it. */
|
|
327
|
+
/** @type {readonly string[]} */
|
|
296
328
|
export const LOCKED_CLASSES: readonly string[];
|
|
297
329
|
/** the classes v1 actually builds */
|
|
298
330
|
export const LIVE_CLASSES: readonly string[];
|
|
@@ -530,8 +562,22 @@ export const CLASS_BATTERIES: Readonly<{
|
|
|
530
562
|
}>)[];
|
|
531
563
|
}>;
|
|
532
564
|
'soft-green': Readonly<{
|
|
533
|
-
locked:
|
|
534
|
-
guards:
|
|
565
|
+
locked: false;
|
|
566
|
+
guards: readonly (Readonly<{
|
|
567
|
+
name: "changed-from-seed";
|
|
568
|
+
kind: "files-changed";
|
|
569
|
+
params: Readonly<{
|
|
570
|
+
requireNonEmpty: true;
|
|
571
|
+
}>;
|
|
572
|
+
compose: readonly never[];
|
|
573
|
+
fill: readonly string[];
|
|
574
|
+
}> | Readonly<{
|
|
575
|
+
name: "no-suppressions";
|
|
576
|
+
kind: "pattern-absent-in-diff";
|
|
577
|
+
params: Readonly<{}>;
|
|
578
|
+
compose: readonly string[];
|
|
579
|
+
fill: readonly never[];
|
|
580
|
+
}>)[];
|
|
535
581
|
}>;
|
|
536
582
|
hitl: Readonly<{
|
|
537
583
|
locked: false;
|
package/types/authorjob.d.ts
CHANGED
|
@@ -85,12 +85,13 @@ export function refuseLockedKind(kind: string): Refusal;
|
|
|
85
85
|
* onPhase?: (phase: string, data?: any) => void,
|
|
86
86
|
* onCall?: (call: {label: string, costUsd: number|null, unpricedRounds: number}) => void,
|
|
87
87
|
* seedFn?: Function, scoutFn?: Function, listingFn?: Function,
|
|
88
|
-
* authorFn?: Function, authorOpts?: object
|
|
88
|
+
* authorFn?: Function, authorOpts?: object,
|
|
89
|
+
* signerFix?: Function|any, proposeFn?: Function, compileOpts?: object}} o
|
|
89
90
|
* @returns {Promise<{ok: boolean, refusal: Refusal|null, verdictType: string|null,
|
|
90
|
-
* closeDecl: any, seedRef: string|null, authoring: any, reds: Red[],
|
|
91
|
-
* cost: any, interview: any}>}
|
|
91
|
+
* closeDecl: any, seedRef: string|null, authoring: any, judged: any, reds: Red[],
|
|
92
|
+
* stop: string|null, cost: any, interview: any}>}
|
|
92
93
|
*/
|
|
93
|
-
export function authorCloseForJob({ answers, repoPath, lang, verdictType, questions, generate, provider, seedRef, scout, listing, ceilingUsd, onPhase, onCall, seedFn, scoutFn, listingFn, authorFn, authorOpts, }: {
|
|
94
|
+
export function authorCloseForJob({ answers, repoPath, lang, verdictType, questions, generate, provider, seedRef, scout, listing, ceilingUsd, onPhase, onCall, seedFn, scoutFn, listingFn, authorFn, authorOpts, signerFix, proposeFn, compileOpts, }: {
|
|
94
95
|
answers: Record<string | number, any>;
|
|
95
96
|
repoPath?: string | null;
|
|
96
97
|
lang: string;
|
|
@@ -113,6 +114,9 @@ export function authorCloseForJob({ answers, repoPath, lang, verdictType, questi
|
|
|
113
114
|
listingFn?: Function;
|
|
114
115
|
authorFn?: Function;
|
|
115
116
|
authorOpts?: object;
|
|
117
|
+
signerFix?: Function | any;
|
|
118
|
+
proposeFn?: Function;
|
|
119
|
+
compileOpts?: object;
|
|
116
120
|
}): Promise<{
|
|
117
121
|
ok: boolean;
|
|
118
122
|
refusal: Refusal | null;
|
|
@@ -120,6 +124,7 @@ export function authorCloseForJob({ answers, repoPath, lang, verdictType, questi
|
|
|
120
124
|
closeDecl: any;
|
|
121
125
|
seedRef: string | null;
|
|
122
126
|
authoring: any;
|
|
127
|
+
judged: any;
|
|
123
128
|
reds: Red[];
|
|
124
129
|
stop: string | null;
|
|
125
130
|
cost: any;
|
|
@@ -171,6 +176,24 @@ export function assembleSpec(specDraft: any, { closeDecl, verdictType }: {
|
|
|
171
176
|
* A close with NO work stage red at seed has nothing to do and is refused
|
|
172
177
|
* decision-ready — gate-2 round 2's arm B died exactly there, on a mypy that
|
|
173
178
|
* scanned nothing and therefore read clean.
|
|
179
|
+
* 4. THE CALIBRATION GATE (softgreen module 5) — for a close carrying a
|
|
180
|
+
* `judged-floor` stage ONLY. The whole judged pipe runs over the signed ten
|
|
181
|
+
* and the five arbiter-owned injection artifacts, and anything short of
|
|
182
|
+
* 10/10 with itemized reds and 5/5 resisted is an unsignable close. It is
|
|
183
|
+
* the ONE gate here that spends money, so it runs LAST — every free refusal
|
|
184
|
+
* has already had its chance, and nobody pays to discover a broken
|
|
185
|
+
* declaration.
|
|
186
|
+
*
|
|
187
|
+
* D9.3 AND THE JUDGED-ONLY CLOSE (2026-08-18 second addendum, ruling 3; hamr:
|
|
188
|
+
* *"fix it now, we are delivering softgreen, isn't that the whole point?"*). Gate
|
|
189
|
+
* 3 protects against a close that cannot fail — one that scans nothing and reads
|
|
190
|
+
* green at seed against a true red. A judged stage SKIPS the seed read by ruling
|
|
191
|
+
* 8, so a close whose only work stage is judged has no seed red to show and used
|
|
192
|
+
* to be unsignable: the one job shape softgreen exists for. For that shape, and
|
|
193
|
+
* ONLY that shape, a PASSED calibration gate plays the seed-red role — it is that
|
|
194
|
+
* close's own proof of failability, held to the same all-or-nothing bar. A close
|
|
195
|
+
* with a mechanical work stage is UNCHANGED: it still needs a seed red, whatever
|
|
196
|
+
* its calibration says.
|
|
174
197
|
*
|
|
175
198
|
* Gates 2 and 3 share ONE execution, and that is stated rather than hidden: at
|
|
176
199
|
* job creation the tree IS the seed, so "does every stage run against the real
|
|
@@ -183,11 +206,13 @@ export function assembleSpec(specDraft: any, { closeDecl, verdictType }: {
|
|
|
183
206
|
* comes back is the resolved spec, its hash, and the evidence to sign against.
|
|
184
207
|
*
|
|
185
208
|
* @param {{spec: any, workdir: string, seedRef?: string|null, shellCapUsd?: number,
|
|
186
|
-
* timeoutMs?: number, seedFn?: Function, listingFn?: Function, seedReadFn?: Function
|
|
209
|
+
* timeoutMs?: number, seedFn?: Function, listingFn?: Function, seedReadFn?: Function,
|
|
210
|
+
* judgeLoop?: Function|null, onJudgeCost?: ((c: any) => void)|null, calibrateFn?: Function,
|
|
211
|
+
* ceilingUsd?: number|null, priorCalls?: any[]}} o
|
|
187
212
|
* @returns {Promise<{ok: boolean, specHash: string|null, seedRef: string|null,
|
|
188
213
|
* gates: any, work: any[], guards: any[], stops: any[], reds: Red[], refusal: Refusal|null}>}
|
|
189
214
|
*/
|
|
190
|
-
export function prepareSigning({ spec, workdir, seedRef, shellCapUsd, timeoutMs, seedFn, listingFn, seedReadFn, }: {
|
|
215
|
+
export function prepareSigning({ spec, workdir, seedRef, shellCapUsd, timeoutMs, seedFn, listingFn, seedReadFn, judgeLoop, onJudgeCost, calibrateFn, ceilingUsd, priorCalls, }: {
|
|
191
216
|
spec: any;
|
|
192
217
|
workdir: string;
|
|
193
218
|
seedRef?: string | null;
|
|
@@ -196,6 +221,11 @@ export function prepareSigning({ spec, workdir, seedRef, shellCapUsd, timeoutMs,
|
|
|
196
221
|
seedFn?: Function;
|
|
197
222
|
listingFn?: Function;
|
|
198
223
|
seedReadFn?: Function;
|
|
224
|
+
judgeLoop?: Function | null;
|
|
225
|
+
onJudgeCost?: ((c: any) => void) | null;
|
|
226
|
+
calibrateFn?: Function;
|
|
227
|
+
ceilingUsd?: number | null;
|
|
228
|
+
priorCalls?: any[];
|
|
199
229
|
}): Promise<{
|
|
200
230
|
ok: boolean;
|
|
201
231
|
specHash: string | null;
|
package/types/bridges.d.ts
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/** does a green under this verdict class earn its learning credit at the close, or
|
|
2
|
+
* does it wait for a person? Absent/unknown means green's behaviour, byte for byte
|
|
3
|
+
* — the pre-softgreen path is the default and stays untouched.
|
|
4
|
+
* @param {unknown} verdictType @returns {boolean} */
|
|
5
|
+
export function quarantinesCredit(verdictType: unknown): boolean;
|
|
1
6
|
/**
|
|
2
7
|
* D6's status ladder, walked in history order. Pure, total, and never throws —
|
|
3
8
|
* it is called on whatever a file happens to contain.
|
|
@@ -13,6 +18,10 @@
|
|
|
13
18
|
* the entry on the first repeat green.
|
|
14
19
|
* - a red on a CANDIDATE changes nothing: there is nothing below the entry bar, and
|
|
15
20
|
* the entry stays in the record with the red beside it.
|
|
21
|
+
* - a **QUARANTINED green is not evidence** (softgreen module 6). The row exists and
|
|
22
|
+
* the run happened, but a judged green earns nothing until the signer's `accept`
|
|
23
|
+
* releases it, so it neither clears the entry bar nor adds a distinct patient. An
|
|
24
|
+
* entry whose only greens are held derives `null` — below the bar, not demoted.
|
|
16
25
|
* - a **CASUALTY is not a red.** Only the literal outcome `'red'` demotes; every
|
|
17
26
|
* other non-green outcome (`provider-red`, `wall-halt`, `close-crashed`, …) is a
|
|
18
27
|
* casualty, and casualties are never evidence in either direction.
|
|
@@ -74,6 +83,68 @@ export function appendRed(bridge: unknown, record: unknown): {
|
|
|
74
83
|
reds: Red[];
|
|
75
84
|
bridge: any;
|
|
76
85
|
};
|
|
86
|
+
/**
|
|
87
|
+
* The version that would INHERIT — the newest one whose credit has actually been
|
|
88
|
+
* released. `versions` is oldest-first, so this is normally the last of them; it
|
|
89
|
+
* differs only when a judged green sits on top of a released one, and in that case
|
|
90
|
+
* the RELEASED plan is what a reuse starts from. A held version is not a worse
|
|
91
|
+
* recipe, it is an unjudged one: nobody has said yet whether the ruler that passed
|
|
92
|
+
* it was right, and starting the next run from it would spend the credit before it
|
|
93
|
+
* was granted.
|
|
94
|
+
*
|
|
95
|
+
* Total and never throws — it is read at selection time and at the load door.
|
|
96
|
+
* @param {unknown} bridge
|
|
97
|
+
* @returns {any|null} null when every version is held (or there are none)
|
|
98
|
+
*/
|
|
99
|
+
export function newestEligibleVersion(bridge: unknown): any | null;
|
|
100
|
+
/**
|
|
101
|
+
* May this workflow be offered for reuse at all? A held entry is SKIPPED WITH A
|
|
102
|
+
* REASON everywhere it is skipped — the same visible-skip discipline an unreadable
|
|
103
|
+
* registry file already gets, because a shelf that quietly shrinks teaches the
|
|
104
|
+
* operator that the registry is empty when it is merely holding.
|
|
105
|
+
* @param {unknown} bridge
|
|
106
|
+
* @returns {{ok: boolean, reason: string}} `reason` is '' when ok
|
|
107
|
+
*/
|
|
108
|
+
export function reuseEligibility(bridge: unknown): {
|
|
109
|
+
ok: boolean;
|
|
110
|
+
reason: string;
|
|
111
|
+
};
|
|
112
|
+
/**
|
|
113
|
+
* THE REVIEW DOOR, written down (PRD v1.71 §3). The person read the finished run
|
|
114
|
+
* and said one of three things; this records that on the run's own row, and — for
|
|
115
|
+
* `accept` on a HELD green — releases the learning credit that green has been
|
|
116
|
+
* carrying unspent.
|
|
117
|
+
*
|
|
118
|
+
* Three properties, and each is a ruling rather than a convenience:
|
|
119
|
+
*
|
|
120
|
+
* - **the door never changes the loop's verdict.** A green row stays a green row,
|
|
121
|
+
* whatever the person then does with it. `rerun` and `pause` are dispositions,
|
|
122
|
+
* recorded and nothing more; a rerun's own outcome is a NEW run's row.
|
|
123
|
+
* - **release is FORWARD-ONLY.** Nothing here can set `quarantined` back to true,
|
|
124
|
+
* so a signer who accepts and later reruns has recorded a disagreement — which
|
|
125
|
+
* is a datum worth keeping — without un-granting credit already granted. There
|
|
126
|
+
* is deliberately no re-hold path at all: it would be a way to walk the ledger
|
|
127
|
+
* backwards, which the ruling forbids.
|
|
128
|
+
* - **releasing is not minting.** A door aimed at a run with no green row of its
|
|
129
|
+
* own is a named red — never an invented entry. That is slice 1's already-green
|
|
130
|
+
* rule holding from the other side: `accept` confirms a verdict, it never
|
|
131
|
+
* creates one.
|
|
132
|
+
*
|
|
133
|
+
* Idempotent by shape: a decision identical to the last one recorded is a no-op
|
|
134
|
+
* that returns the entry unchanged, so a double-click at the door cannot inflate
|
|
135
|
+
* the report card (and `released` says whether THIS call was the one that freed it).
|
|
136
|
+
*
|
|
137
|
+
* Pure — returns a NEW entry; the input is never mutated.
|
|
138
|
+
* @param {unknown} bridge a validated bridge entry
|
|
139
|
+
* @param {unknown} door `{ runid, decision, at }` — `at` may be an explicit null
|
|
140
|
+
* @returns {{ ok: boolean, reds: Red[], bridge: any, released: boolean }}
|
|
141
|
+
*/
|
|
142
|
+
export function recordDoor(bridge: unknown, door: unknown): {
|
|
143
|
+
ok: boolean;
|
|
144
|
+
reds: Red[];
|
|
145
|
+
bridge: any;
|
|
146
|
+
released: boolean;
|
|
147
|
+
};
|
|
77
148
|
/**
|
|
78
149
|
* One row of D3's listing: what the user (and the selecting LLM) sees per bridge —
|
|
79
150
|
* name, the job sentence it greened, status, greens/reds, and the cost/time BAND of
|
|
@@ -86,7 +157,7 @@ export function appendRed(bridge: unknown, record: unknown): {
|
|
|
86
157
|
* Total and never throws: the listing is a display surface, and one malformed
|
|
87
158
|
* entry must not take the whole listing down (loadRegistry already reports it).
|
|
88
159
|
* @param {unknown} bridge
|
|
89
|
-
* @returns {{name: string|null, goal: string|null, status: 'candidate'|'proven'|null, greens: number, reds: number, lastOutcome: string|null, greenCost: {minUsd: number|null, maxUsd: number|null, minWallMs: number|null, maxWallMs: number|null, unpricedCount: number, untimedCount: number}}}
|
|
160
|
+
* @returns {{name: string|null, goal: string|null, status: 'candidate'|'proven'|null, greens: number, reds: number, quarantinedGreens: number, doorDecisions: {runid: string|null, decision: string|null, at: string|null}[], lastOutcome: string|null, greenCost: {minUsd: number|null, maxUsd: number|null, minWallMs: number|null, maxWallMs: number|null, unpricedCount: number, untimedCount: number}}}
|
|
90
161
|
*/
|
|
91
162
|
export function listingRow(bridge: unknown): {
|
|
92
163
|
name: string | null;
|
|
@@ -94,6 +165,12 @@ export function listingRow(bridge: unknown): {
|
|
|
94
165
|
status: "candidate" | "proven" | null;
|
|
95
166
|
greens: number;
|
|
96
167
|
reds: number;
|
|
168
|
+
quarantinedGreens: number;
|
|
169
|
+
doorDecisions: {
|
|
170
|
+
runid: string | null;
|
|
171
|
+
decision: string | null;
|
|
172
|
+
at: string | null;
|
|
173
|
+
}[];
|
|
97
174
|
lastOutcome: string | null;
|
|
98
175
|
greenCost: {
|
|
99
176
|
minUsd: number | null;
|
|
@@ -191,6 +268,23 @@ export function makeRegistry(dir: string): string;
|
|
|
191
268
|
/** @param {string} dir @returns {boolean} does a registry directory exist */
|
|
192
269
|
export function registryExists(dir: string): boolean;
|
|
193
270
|
export const BRIDGE_SCHEMA: "bridge-v1";
|
|
271
|
+
/**
|
|
272
|
+
* SOFTGREEN MODULE 6 — the verdict classes whose greens are minted QUARANTINED.
|
|
273
|
+
*
|
|
274
|
+
* The standing ruling (PRD v1.53, given a mechanism by v1.71 §3): *softgreen
|
|
275
|
+
* passes are quarantined from learning credit until the judged floor is proven*,
|
|
276
|
+
* and **the signer's `accept` at the review door is what releases it**.
|
|
277
|
+
*
|
|
278
|
+
* `soft-green` and NOT `hitl`: the hold is about the young JUDGE, not about every
|
|
279
|
+
* class that is not `green`. A hitl close is rendered by a PERSON at close time —
|
|
280
|
+
* there is no unproven ruler in it to distrust. A green earns its credit at its
|
|
281
|
+
* own close, exactly as it always has.
|
|
282
|
+
*/
|
|
283
|
+
export const QUARANTINED_VERDICTS: readonly string[];
|
|
284
|
+
/** the red CODE a hold travels under — the gate emits it, the listing splits the
|
|
285
|
+
* held rows out of the broken ones on it, and the runner reads it back. One
|
|
286
|
+
* spelling, because three literals is how a hold silently becomes a breakage. */
|
|
287
|
+
export const QUARANTINED_CODE: "quarantined";
|
|
194
288
|
export type Red = {
|
|
195
289
|
code: string;
|
|
196
290
|
path: string;
|