bareloop 0.9.0 → 0.11.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 +555 -0
- package/README.md +111 -117
- package/bareloop.context.md +314 -24
- package/package.json +3 -3
- package/src/authorflow.js +300 -35
- package/src/authoring.js +290 -33
- package/src/authorjob.js +59 -7
- package/src/authorscout.js +83 -10
- package/src/bridges.js +15 -2
- package/src/clock.js +21 -3
- package/src/declaredclose.js +88 -11
- package/src/index.js +11 -1
- package/src/job.js +7 -2
- package/src/kinds.js +292 -20
- package/src/ledger.js +29 -2
- package/src/planrun.js +404 -29
- package/src/ralph.js +14 -0
- package/src/reuse.js +247 -9
- package/src/root.js +1 -1
- package/src/run.js +12 -5
- package/src/selection.js +1 -1
- package/src/text.js +79 -0
- package/src/tools.js +11 -0
- package/src/validate.js +168 -11
- package/types/authorflow.d.ts +135 -17
- package/types/authoring.d.ts +161 -35
- package/types/authorjob.d.ts +11 -2
- package/types/authorscout.d.ts +22 -3
- package/types/clock.d.ts +5 -2
- package/types/declaredclose.d.ts +64 -6
- package/types/index.d.ts +4 -4
- package/types/job.d.ts +5 -0
- package/types/kinds.d.ts +134 -10
- package/types/planrun.d.ts +61 -2
- package/types/reuse.d.ts +75 -1
- package/types/run.d.ts +12 -2
- package/types/text.d.ts +67 -0
- package/types/validate.d.ts +74 -9
package/types/kinds.d.ts
CHANGED
|
@@ -36,15 +36,25 @@ export function seedListing(workdir: string, seedRef: string): Promise<{
|
|
|
36
36
|
files: string[];
|
|
37
37
|
}>;
|
|
38
38
|
/**
|
|
39
|
-
* The
|
|
39
|
+
* The arbiter books, and the shape of each exclusion is the whole point.
|
|
40
40
|
*
|
|
41
|
-
* `.litectx/`
|
|
42
|
-
* arbiter. `gate-audit.jsonl` is an EXACT repo-relative path and
|
|
43
|
-
* basename or a pattern: the gate audit is written to
|
|
41
|
+
* `.litectx/` and `.smoke/` are PREFIXES — everything beneath either store
|
|
42
|
+
* belongs to the arbiter. `gate-audit.jsonl` is an EXACT repo-relative path and
|
|
43
|
+
* never a basename or a pattern: the gate audit is written to
|
|
44
44
|
* `join(workdir, 'gate-audit.jsonl')` and nowhere else, so a worker-authored
|
|
45
45
|
* `src/gate-audit.jsonl` is NOT an arbiter book and must still count as work.
|
|
46
46
|
* Matching by basename would swallow it — and swallowing a worker's real
|
|
47
|
-
* write is the failure direction that reads as a clean tree.
|
|
47
|
+
* write is the failure direction that reads as a clean tree. `src/smoke.js` and
|
|
48
|
+
* `tests/smoke.test.js` are that same decoy for the store below.
|
|
49
|
+
*
|
|
50
|
+
* `.smoke/` — F98's parked residual, closed here. The fence has always denied
|
|
51
|
+
* `join(workdir, '.smoke')` on every worker (src/planrun.js, src/authorscout.js)
|
|
52
|
+
* and the persona now states the rule; this reader, which decides what COUNTS AS
|
|
53
|
+
* WORK, was the one place that never knew the name. On patients whose .gitignore
|
|
54
|
+
* default-denies dot-directories the store never reaches `ls-files --others` and
|
|
55
|
+
* the gap is masked — by the PATIENT's config, not by anything here. Incidental
|
|
56
|
+
* masking is not an exclusion: on a patient without that line the arbiter's own
|
|
57
|
+
* store would enter the changed set as the worker's writing.
|
|
48
58
|
* @param {string} rel a repo-relative POSIX path
|
|
49
59
|
* @returns {string|null} the book it belongs to, or null
|
|
50
60
|
*/
|
|
@@ -146,13 +156,36 @@ export function normalizeParser(parser: any, at?: string): {
|
|
|
146
156
|
* that matched BEFORE the filter judged them — kept, unattributable and dropped
|
|
147
157
|
* alike — and it is the only evidence of liveness a caller has once the scope
|
|
148
158
|
* has removed the whole population. It is never the number being read.
|
|
159
|
+
*
|
|
160
|
+
* `matched` is the KEPT lines themselves, PER TERM and index-aligned with
|
|
161
|
+
* `breakdown`: entry i holds, in output order, exactly the lines term i's
|
|
162
|
+
* subtotal was computed FROM. Run u-msn227nq paid for the echo — the executor
|
|
163
|
+
* counted 8 real `error TS…` lines and told the worker "8 match(es)", discarding
|
|
164
|
+
* every address, and a worker with a number and no file to open went looking for
|
|
165
|
+
* the arbiter's own books instead.
|
|
166
|
+
*
|
|
167
|
+
* TWO EXCLUSIONS, both of them "this line is not what the count is made of":
|
|
168
|
+
* - a scope-DROPPED line is never in here. It was never counted, so aiming the
|
|
169
|
+
* worker at it would aim it outside its own population.
|
|
170
|
+
* - under `first`, only the line the aggregate actually READ. The later matches
|
|
171
|
+
* are real matches (`breakdown[i].matches` still counts them) but the value
|
|
172
|
+
* never contained them, and echoing them points the worker at a number that
|
|
173
|
+
* is not in the reading.
|
|
174
|
+
*
|
|
175
|
+
* And the GROUPING is the third: a term's sign lives in `breakdown[i]`, so lines
|
|
176
|
+
* pooled flat across terms lose the one fact that says what they DID to the
|
|
177
|
+
* number. A subtracted term's line rendered beside an added one reads as a wall
|
|
178
|
+
* to go fix, and fixing it moves the count the wrong way. Deduplication is
|
|
179
|
+
* therefore within a term only — one line matched by two terms plays two
|
|
180
|
+
* arithmetic roles and is named under each. The count is computed from `values`,
|
|
181
|
+
* never from this list.
|
|
149
182
|
* @param {string} output @param {any[]} terms normalised terms
|
|
150
183
|
* Every stop here is `crashed` in the arbiter's vocabulary — the command RAN,
|
|
151
184
|
* came back, and the number could not be read out of it. Nothing in this
|
|
152
185
|
* function can observe a spawn failure, a timeout or a signal, so it never
|
|
153
186
|
* claims one.
|
|
154
187
|
* @param {{scope?: any, workdir?: string}} [o]
|
|
155
|
-
* @returns {{stop: string, fault: string, notes?: string[]}|{stop: null, value: number, breakdown: any[], notes: string[], preScopeMatches: number}}
|
|
188
|
+
* @returns {{stop: string, fault: string, notes?: string[]}|{stop: null, value: number, breakdown: any[], notes: string[], preScopeMatches: number, matched: string[][]}}
|
|
156
189
|
*/
|
|
157
190
|
export function parseValue(output: string, terms: any[], { scope, workdir }?: {
|
|
158
191
|
scope?: any;
|
|
@@ -167,6 +200,7 @@ export function parseValue(output: string, terms: any[], { scope, workdir }?: {
|
|
|
167
200
|
breakdown: any[];
|
|
168
201
|
notes: string[];
|
|
169
202
|
preScopeMatches: number;
|
|
203
|
+
matched: string[][];
|
|
170
204
|
};
|
|
171
205
|
/**
|
|
172
206
|
* A caller-scoped holder for detached seed worktrees. Scoped rather than
|
|
@@ -177,6 +211,30 @@ export function parseValue(output: string, terms: any[], { scope, workdir }?: {
|
|
|
177
211
|
* @returns {SeedTrees}
|
|
178
212
|
*/
|
|
179
213
|
export function makeSeedTrees(): SeedTrees;
|
|
214
|
+
/**
|
|
215
|
+
* THE SIGNER'S ANSWER, normalised at the seam that accepts it — the library half
|
|
216
|
+
* of the three-button gate (2026-08-12 §1).
|
|
217
|
+
*
|
|
218
|
+
* `null` is not a refusal: no ruling at all IS the pause path, which is how every
|
|
219
|
+
* ordinary run and every first leg of a hitl run arrives here. What is refused is
|
|
220
|
+
* an answer that is not one of the three doors, and — the case the POC's negative
|
|
221
|
+
* control measured — a `rerun` with no text. Ruling 3 makes the human's words the
|
|
222
|
+
* gap the worker converts from; an empty one launches a fix worker that re-runs
|
|
223
|
+
* as though nothing had been said, which is the failure this refusal exists for.
|
|
224
|
+
*
|
|
225
|
+
* It never throws and never repairs: a decision that cannot be read is handed
|
|
226
|
+
* back as a refusal with its reason, and the caller decides what to do about it.
|
|
227
|
+
* @param {any} ruling
|
|
228
|
+
* @returns {{ok: boolean, ruling: {decision: string, text: string|null}|null, why: string|null}}
|
|
229
|
+
*/
|
|
230
|
+
export function normalizeHumanRuling(ruling: any): {
|
|
231
|
+
ok: boolean;
|
|
232
|
+
ruling: {
|
|
233
|
+
decision: string;
|
|
234
|
+
text: string | null;
|
|
235
|
+
} | null;
|
|
236
|
+
why: string | null;
|
|
237
|
+
};
|
|
180
238
|
/**
|
|
181
239
|
* Run ONE declared stage against a workdir at a seed.
|
|
182
240
|
*
|
|
@@ -220,13 +278,14 @@ export function seedRead(declaration: any, ctx: Ctx): Promise<StageResult[]>;
|
|
|
220
278
|
* room would break a documented adopter contract for nothing; the new name says
|
|
221
279
|
* exactly what differs. `src/declaredclose.js` is what bridges the two shapes.
|
|
222
280
|
* @param {any} declaration @param {Ctx} ctx
|
|
223
|
-
* @returns {Promise<{verdict: 'green'|'red'|'instrument-stop', exitCode: number, judged: boolean, firstRed: string|null, stages: StageResult[]}>}
|
|
281
|
+
* @returns {Promise<{verdict: 'green'|'red'|'instrument-stop'|'pause', exitCode: number|null, judged: boolean, firstRed: string|null, pausedAt: string|null, stages: StageResult[]}>}
|
|
224
282
|
*/
|
|
225
283
|
export function runDeclaredClose(declaration: any, ctx: Ctx): Promise<{
|
|
226
|
-
verdict: "green" | "red" | "instrument-stop";
|
|
227
|
-
exitCode: number;
|
|
284
|
+
verdict: "green" | "red" | "instrument-stop" | "pause";
|
|
285
|
+
exitCode: number | null;
|
|
228
286
|
judged: boolean;
|
|
229
287
|
firstRed: string | null;
|
|
288
|
+
pausedAt: string | null;
|
|
230
289
|
stages: StageResult[];
|
|
231
290
|
}>;
|
|
232
291
|
/** the close's two judgment exits — a stage's verdict, in the shape the
|
|
@@ -296,6 +355,46 @@ export const SIGNS: readonly number[];
|
|
|
296
355
|
* counted demand (the request-red path), and reaching one at runtime is a
|
|
297
356
|
* stop — never a red, and never a silent pass. */
|
|
298
357
|
export const LIVE_KINDS: readonly string[];
|
|
358
|
+
/** RULING 8 — the stages the seed-verdict read (D12) must NOT run. A judged
|
|
359
|
+
* number at seed is unstable and a person at seed is an interview, not a
|
|
360
|
+
* measurement; their bar comes from calibration (soft-green) or from the person
|
|
361
|
+
* themselves (hitl), never from the seed. The skip is RECORDED by name rather
|
|
362
|
+
* than taken in silence (F59: absent is not empty), so `seedRead` still returns
|
|
363
|
+
* one row per declared stage. */
|
|
364
|
+
export const SEED_EXEMPT_KINDS: readonly string[];
|
|
365
|
+
/** the one kind a PERSON renders — named once, because the runner keys three
|
|
366
|
+
* decisions on it (is there a door to answer, which stage paused, whose ruling
|
|
367
|
+
* this is) and three literals is how they come to disagree */
|
|
368
|
+
export const HUMAN_KIND: "human-confirms";
|
|
369
|
+
/** the three doors the signer is offered at a hitl pause (2026-08-12 §1, doors
|
|
370
|
+
* re-cut 2026-08-17). No fourth door, and no free-text-only variant: a red that
|
|
371
|
+
* is not one of these two is not a ruling the run can act on.
|
|
372
|
+
*
|
|
373
|
+
* `cancel` was the third door and is DELETED as a concept (hamr: *"what's the
|
|
374
|
+
* point of cancel anyways? pause can resume — that would be more honest"*). The
|
|
375
|
+
* case it served — a person who looks and does not want to carry on — is a pause
|
|
376
|
+
* nobody returns to, which the 60-day checkpoint TTL retires on its own. Nothing
|
|
377
|
+
* here forces a forever-decision at the one moment the person has least reason
|
|
378
|
+
* to make one. */
|
|
379
|
+
export const HUMAN_DECISIONS: readonly string[];
|
|
380
|
+
/**
|
|
381
|
+
* The arbiter's book NAMES, one home. F98's mechanism was exactly this list
|
|
382
|
+
* living as independent literals: the fence deny arrays (src/planrun.js,
|
|
383
|
+
* src/authorscout.js) knew `.smoke` while `isArbiterBook` — the reader that
|
|
384
|
+
* decides what COUNTS AS WORK — did not, and the drift was masked by the
|
|
385
|
+
* patient's own .gitignore. Every consumer (the fence deny lists, this file's
|
|
386
|
+
* reader, the persona's stated law, run.js's smoke-store root) now spells the
|
|
387
|
+
* names from here, so adding or renaming a book is one edit that cannot leave
|
|
388
|
+
* a site behind.
|
|
389
|
+
*
|
|
390
|
+
* `ARBITER_BOOK_STORES` are directory PREFIXES; `GATE_AUDIT_FILE` is an EXACT
|
|
391
|
+
* repo-relative path (see `isArbiterBook`'s docstring for why never a
|
|
392
|
+
* basename). Order is the fence deny-list order and the persona's prose order.
|
|
393
|
+
*/
|
|
394
|
+
export const SMOKE_STORE: ".smoke";
|
|
395
|
+
export const LITECTX_STORE: ".litectx";
|
|
396
|
+
export const GATE_AUDIT_FILE: "gate-audit.jsonl";
|
|
397
|
+
export const ARBITER_BOOK_STORES: readonly string[];
|
|
299
398
|
export type Red = {
|
|
300
399
|
code: string;
|
|
301
400
|
path: string;
|
|
@@ -304,6 +403,13 @@ export type Red = {
|
|
|
304
403
|
/**
|
|
305
404
|
* The arbiter's half of a stage run. Every field here is operator/runner
|
|
306
405
|
* territory: a declaration parameterises kinds, never the contracts.
|
|
406
|
+
* `humanRuling` is the SIGNER's answer at a hitl pause, carried in from the
|
|
407
|
+
* runner — never authored, never defaulted, and absent on every ordinary run.
|
|
408
|
+
* `onStage` is a REPORTING seam and nothing else: the seed read spawns a real
|
|
409
|
+
* toolchain per stage (a suite is the slow one) and a whole read is one opaque
|
|
410
|
+
* await, so the shell gets told as each stage lands. It decides nothing, it is
|
|
411
|
+
* never consulted, and no verdict depends on it — the house pattern is
|
|
412
|
+
* `runJob`'s `emit` (the library reports, the shell prints).
|
|
307
413
|
*/
|
|
308
414
|
export type Ctx = {
|
|
309
415
|
workdir: string;
|
|
@@ -314,9 +420,27 @@ export type Ctx = {
|
|
|
314
420
|
gapCap?: number;
|
|
315
421
|
maxBuffer?: number;
|
|
316
422
|
seedTrees?: SeedTrees;
|
|
423
|
+
humanRuling?: {
|
|
424
|
+
decision: string;
|
|
425
|
+
text?: string | null;
|
|
426
|
+
} | null;
|
|
427
|
+
onStage?: (row: {
|
|
428
|
+
stage: string | null;
|
|
429
|
+
kind: string | null;
|
|
430
|
+
verdict: string;
|
|
431
|
+
durationMs: number;
|
|
432
|
+
}) => void;
|
|
317
433
|
};
|
|
434
|
+
/**
|
|
435
|
+
* Six outcomes, and only two of them are verdicts. `pause` is N4's fifth: the
|
|
436
|
+
* close reached a stage a machine cannot render and is WAITING on a person —
|
|
437
|
+
* neither green nor red, and never graded (the F17 forbidden-zone rule applied
|
|
438
|
+
* to a non-verdict). `skipped` is the sixth and says a stage was deliberately
|
|
439
|
+
* not run at THIS reading (ruling 8's seed exemption), which is a different
|
|
440
|
+
* fact from `not-reached` (an earlier stage decided first).
|
|
441
|
+
*/
|
|
318
442
|
export type StageResult = {
|
|
319
|
-
verdict: "green" | "red" | "instrument-stop" | "not-reached";
|
|
443
|
+
verdict: "green" | "red" | "instrument-stop" | "not-reached" | "pause" | "skipped";
|
|
320
444
|
exitCode: number | null;
|
|
321
445
|
value: number | null;
|
|
322
446
|
baseline: number | null;
|
package/types/planrun.d.ts
CHANGED
|
@@ -78,6 +78,42 @@
|
|
|
78
78
|
* @returns {string} the labelled block, or '' when there is nothing to show
|
|
79
79
|
*/
|
|
80
80
|
export function closeGapBlock(gap: string | null | undefined): string;
|
|
81
|
+
/**
|
|
82
|
+
* What the NEXT attempt is told about the bound that cut the previous one.
|
|
83
|
+
*
|
|
84
|
+
* Two different bounds used to set the same bare `attemptBounded = roundIteration`
|
|
85
|
+
* and therefore render the same sentence. For a round-bound cutoff that sentence is
|
|
86
|
+
* true. For BA-11's deny streak it is false twice over — the rounds did NOT run out
|
|
87
|
+
* (the fence short-circuited the attempt with budget left), and the count it quotes
|
|
88
|
+
* is the cap, not what was spent — and it aims the worker at the READ BUDGET when the
|
|
89
|
+
* thing that stopped it was the FENCE. The reason was already recorded on the spine;
|
|
90
|
+
* nothing carried it to the one reader who could act on it.
|
|
91
|
+
*
|
|
92
|
+
* The denial note is MECHANICAL in the strict sense: it quotes the terminal the
|
|
93
|
+
* provider actually returned and names which bound fired. It invents nothing —
|
|
94
|
+
* no streak count and no denied path, because bare-agent's return (`error:
|
|
95
|
+
* 'denied:<tool>'`, loop.js) carries neither, and a number nobody measured is a
|
|
96
|
+
* bug with a confident voice. Every other cause keeps the frozen wording byte for
|
|
97
|
+
* byte: a wall-bounded attempt reads as a round bound exactly as it did before
|
|
98
|
+
* (and is unreachable anyway — the step head refuses to fund an attempt past the
|
|
99
|
+
* deadline, W-2), so this change can only ever speak where it has something true
|
|
100
|
+
* to add.
|
|
101
|
+
*
|
|
102
|
+
* The reason arrives ALREADY SCRUBBED (the capture seam in `mkWorker` runs it
|
|
103
|
+
* through the one `SECRET_PATTERNS` inventory), so this renderer only bounds it —
|
|
104
|
+
* and announces the trim with the repo's ONE marker rather than a second spelling
|
|
105
|
+
* of "this text was cut" (F90.2).
|
|
106
|
+
*
|
|
107
|
+
* @param {{iteration?: number|string|undefined, cause: string, reason: string|null}|null|undefined} bounded
|
|
108
|
+
* the previous attempt's bound state, or nullish when it was not bounded
|
|
109
|
+
* @param {number} rounds the step's round bound, as quoted by the frozen sentence
|
|
110
|
+
* @returns {string|null} the note, or null when there is nothing to say
|
|
111
|
+
*/
|
|
112
|
+
export function boundedNote(bounded: {
|
|
113
|
+
iteration?: number | string | undefined;
|
|
114
|
+
cause: string;
|
|
115
|
+
reason: string | null;
|
|
116
|
+
} | null | undefined, rounds: number): string | null;
|
|
81
117
|
/**
|
|
82
118
|
* The plan-drafting prompt: a schema DESCRIPTION built from the live validator
|
|
83
119
|
* menus — never a copyable example (the drafter must author, not echo; the
|
|
@@ -258,12 +294,24 @@ export function planPrompt(job: any, scoutBlob: string, reds: any[] | null, maxS
|
|
|
258
294
|
* it the deterministic name would collide with that very branch and the collision walk
|
|
259
295
|
* would mint a `-2` beside the progress the resume exists to keep. Absent is the cold
|
|
260
296
|
* path. A recorded branch that no longer exists is a STOP, never a fresh start.
|
|
297
|
+
* @param {{decision: string, text?: string}|null} [opts.humanRuling] N4 — the SIGNER's
|
|
298
|
+
* answer at a hitl pause (2026-08-12 §1, re-cut 2026-08-17: accept | rerun <text> | pause), carried in
|
|
299
|
+
* by the runner on the leg that RESUMES a paused run. Absent on every ordinary run and
|
|
300
|
+
* on the leg that pauses; it is never authored, never defaulted, and never inferred.
|
|
301
|
+
*
|
|
302
|
+
* It is spent ONCE, on this leg's close readings up to the moment the fix loop opens:
|
|
303
|
+
* `accept` greens the human stage on fresh evidence (OPEN-3 — the mechanical stages
|
|
304
|
+
* re-run first, because the tree can change while a run is paused), `rerun` reds it
|
|
305
|
+
* with the human's own words as the gap, and `pause` keeps the checkpoint exactly as it
|
|
306
|
+
* is, before anything is read at all. After the fix loop opens the ruling is gone, so once the mechanical
|
|
307
|
+
* stages pass again the run PAUSES for a second review rather than converting the same
|
|
308
|
+
* sentence forever.
|
|
261
309
|
* @returns {Promise<string>} 'green' | 'already-green' | 'escalated' | 'plan-red' |
|
|
262
310
|
* 'check-red' | 'close-red' | 'close-unsupported' | 'recipe-stale' | 'pricing-red' |
|
|
263
311
|
* 'branch-red' | 'cap-halt' | 'wall-halt' | 'provider-red' | 'interpreter-red' |
|
|
264
|
-
* 'step-stalled' | `step-red:<id>`
|
|
312
|
+
* 'step-stalled' | 'hitl-pause' | 'hitl-decision-red' | `step-red:<id>`
|
|
265
313
|
*/
|
|
266
|
-
export function runPlan(job: any, { workdir, provider, nativeProvider, providerFor, emit, remainingUsd, isUnpriced, spendComplete, capRuns, strikeLimit, closeTimeoutMs, maxStepRounds, layerRoot, scoutRounds, bridge, now, priorWallMs, resumeSeed, resumeGrades, resumeReplans, resumeBranch }: {
|
|
314
|
+
export function runPlan(job: any, { workdir, provider, nativeProvider, providerFor, emit, remainingUsd, isUnpriced, spendComplete, capRuns, strikeLimit, closeTimeoutMs, maxStepRounds, layerRoot, scoutRounds, bridge, now, priorWallMs, resumeSeed, resumeGrades, resumeReplans, resumeBranch, humanRuling }: {
|
|
267
315
|
workdir: string;
|
|
268
316
|
provider: any;
|
|
269
317
|
nativeProvider?: ((o: {
|
|
@@ -296,7 +344,18 @@ export function runPlan(job: any, { workdir, provider, nativeProvider, providerF
|
|
|
296
344
|
spendComplete?: (() => boolean) | undefined;
|
|
297
345
|
now?: (() => number) | undefined;
|
|
298
346
|
resumeBranch?: string | null | undefined;
|
|
347
|
+
humanRuling?: {
|
|
348
|
+
decision: string;
|
|
349
|
+
text?: string;
|
|
350
|
+
} | null | undefined;
|
|
299
351
|
}): Promise<string>;
|
|
352
|
+
/**
|
|
353
|
+
* The recorded bound-reason's ceiling in the WORKER-facing note. The reason is a
|
|
354
|
+
* provider string (`denied:<tool>` today, ~20 bytes), not a gap, so this is a
|
|
355
|
+
* backstop against a future terminal that arrives long — never an envelope the
|
|
356
|
+
* shipped path routinely rides through.
|
|
357
|
+
*/
|
|
358
|
+
export const BOUND_REASON_MAX: 200;
|
|
300
359
|
export type CategorizedError = Error & {
|
|
301
360
|
category?: string;
|
|
302
361
|
lib?: string;
|
package/types/reuse.d.ts
CHANGED
|
@@ -221,7 +221,7 @@ export function selectBridge({ registry, job, ask, provider, pinned, shortlist,
|
|
|
221
221
|
* which is later and better evidence than the last event). Defaults to the last
|
|
222
222
|
* event's own timestamp — the last sign of life there is.
|
|
223
223
|
* @returns {any} `{ started, approvalHash, specHash, patient, job, bridgeTries,
|
|
224
|
-
* perTryBudgetUsd, perTryWallMs, ended, endOutcome, greened, completed, tried,
|
|
224
|
+
* perTryBudgetUsd, perTryWallMs, ended, endOutcome, wallDerivedHalt, greened, completed, tried,
|
|
225
225
|
* selectionCostUsd, selectionComplete, strayRounds, r1Missing, restart, deathAtKnown,
|
|
226
226
|
* spentUsd, spendComplete, carrySpentUsd, carrySpendComplete }`
|
|
227
227
|
*/
|
|
@@ -230,6 +230,43 @@ export function readResume(events: any[], { deathAt, direct, resumableOutcomes }
|
|
|
230
230
|
direct?: boolean;
|
|
231
231
|
resumableOutcomes?: string[];
|
|
232
232
|
}): any;
|
|
233
|
+
/**
|
|
234
|
+
* The AGE gate for a paused checkpoint — the sibling of `resumeTreeGate`, and it
|
|
235
|
+
* asks the other question a 60-day-old checkpoint raises. The tree gate asks
|
|
236
|
+
* *is this still the tree the run left?*; this asks *is this still a decision
|
|
237
|
+
* anyone is waiting on?*
|
|
238
|
+
*
|
|
239
|
+
* THREE things it deliberately does NOT do:
|
|
240
|
+
*
|
|
241
|
+
* - it does not widen any other resume rule. The 2026-08-12 ruling is about a
|
|
242
|
+
* person deciding, so `applies: false` on every other terminal: a cap-halt
|
|
243
|
+
* checkpoint is a different stop under a different allowance, and quietly
|
|
244
|
+
* ageing it out would be a governance change nobody ruled.
|
|
245
|
+
* - it does not treat an unreadable stamp as young (F6). A checkpoint that
|
|
246
|
+
* cannot say WHEN it paused has an unknown age, and unknown is refused rather
|
|
247
|
+
* than rounded down to fresh — the direction that fails safe.
|
|
248
|
+
* - it does not skip silently. An expired checkpoint comes back as a refusal
|
|
249
|
+
* that NAMES the age and the TTL, because the operator's next move (start
|
|
250
|
+
* fresh) depends on knowing which of the two it hit.
|
|
251
|
+
*
|
|
252
|
+
* The clock is injected for the same reason `runPlan`'s is: a rule measured in
|
|
253
|
+
* months cannot otherwise be tested at all.
|
|
254
|
+
*
|
|
255
|
+
* @param {any[]} events the paused run's own spine, parsed, in file order
|
|
256
|
+
* @param {{now?: () => number, ttlMs?: number}} [opts]
|
|
257
|
+
* @returns {{ok: boolean, applies: boolean, ageMs: number|null, ttlMs: number, pausedAt: string|null, detail: string|null}}
|
|
258
|
+
*/
|
|
259
|
+
export function checkpointAgeGate(events: any[], { now, ttlMs }?: {
|
|
260
|
+
now?: () => number;
|
|
261
|
+
ttlMs?: number;
|
|
262
|
+
}): {
|
|
263
|
+
ok: boolean;
|
|
264
|
+
applies: boolean;
|
|
265
|
+
ageMs: number | null;
|
|
266
|
+
ttlMs: number;
|
|
267
|
+
pausedAt: string | null;
|
|
268
|
+
detail: string | null;
|
|
269
|
+
};
|
|
233
270
|
/**
|
|
234
271
|
* The PATIENT gate for a resume, which is deliberately NOT the fresh-launch one.
|
|
235
272
|
*
|
|
@@ -384,6 +421,43 @@ export function runReuse(opts: {
|
|
|
384
421
|
* started.
|
|
385
422
|
*/
|
|
386
423
|
export const REUSE_GRADED_RED: readonly string[];
|
|
424
|
+
/**
|
|
425
|
+
* WHICH TERMINALS ARE A CHECKPOINT — the list a runner hands `readResume` as
|
|
426
|
+
* `resumableOutcomes`, minted HERE rather than in whichever script happens to
|
|
427
|
+
* launch a run.
|
|
428
|
+
*
|
|
429
|
+
* `readResume` deliberately takes it as a parameter and defaults to empty, and
|
|
430
|
+
* that stays true: the reuse loop's own semantics depend on the empty default.
|
|
431
|
+
* What did not exist was a canonical ANSWER, so `scripts/run-u.mjs` kept its own
|
|
432
|
+
* copy — and the exported bundle (PRD v1.44 §2: a thin runner with bareloop as a
|
|
433
|
+
* dependency) would have had to keep a third. The same reasoning that put the
|
|
434
|
+
* pause TTL in the library (OPEN-2, hamr, 2026-08-13) puts this here.
|
|
435
|
+
*
|
|
436
|
+
* Every entry is a stop that left WORK ON DISK and an allowance unspent — money,
|
|
437
|
+
* time, a self-healed stall, and now a person who has not answered yet. A verdict
|
|
438
|
+
* already rendered is never on this list: `green`/`escalated` are graded rows, and
|
|
439
|
+
* re-buying one would pay twice for an answer already in hand. (The signer's own
|
|
440
|
+
* `hitl-cancel` terminal used to be the other example; the door was deleted on
|
|
441
|
+
* 2026-08-17 — a person who does not want to carry on pauses, and the checkpoint
|
|
442
|
+
* they keep is retired by the TTL rather than by a terminal.)
|
|
443
|
+
*
|
|
444
|
+
* The human half is SPLICED IN from `declaredclose.js` rather than spelled again,
|
|
445
|
+
* because the try loop below reads the two halves differently and a second copy is
|
|
446
|
+
* how they would come to disagree about which member is which: the first three are
|
|
447
|
+
* allowances the loop's remaining tries are meant to spend against, and the human
|
|
448
|
+
* ones are the stop no further try can change (see `hardStop`).
|
|
449
|
+
*/
|
|
450
|
+
export const CHECKPOINT_OUTCOMES: readonly string[];
|
|
451
|
+
/**
|
|
452
|
+
* THE PAUSE TTL (2026-08-12 §2): a hitl checkpoint is kept for 60 days.
|
|
453
|
+
*
|
|
454
|
+
* OPEN-2, RULED (hamr, in-turn, 2026-08-13): **the TTL lives in the LIBRARY**, so
|
|
455
|
+
* the exported bundle (a headless runner with bareloop as a dependency, PRD v1.44
|
|
456
|
+
* §2) inherits it instead of every runner re-implementing it. A script-only TTL
|
|
457
|
+
* would vanish on export — the one place the rule is most needed and least
|
|
458
|
+
* likely to be re-typed.
|
|
459
|
+
*/
|
|
460
|
+
export const PAUSE_TTL_MS: number;
|
|
387
461
|
export type Red = {
|
|
388
462
|
code: string;
|
|
389
463
|
path: string;
|
package/types/run.d.ts
CHANGED
|
@@ -97,12 +97,18 @@
|
|
|
97
97
|
* a manufactured-fixation probe). `true` is the ON/experimental arm; the
|
|
98
98
|
* default is the OFF arm. (Shell-owned seam, same doctrine as the provider
|
|
99
99
|
* binding.)
|
|
100
|
+
* @param {{decision: string, text?: string}|null} [opts.humanRuling] N4 — the SIGNER's
|
|
101
|
+
* answer at a hitl pause (accept | rerun <text> | pause), carried by the leg that
|
|
102
|
+
* RESUMES a paused run and forwarded verbatim to the plan flow. Absent on every
|
|
103
|
+
* ordinary run; never authored and never defaulted (a defaulted answer to "is this
|
|
104
|
+
* done?" is the rubber-stamp the class exists to prevent, 2026-08-12 §4).
|
|
100
105
|
* @returns {Promise<string>} outcome: 'green' | 'already-green' | 'escalated' |
|
|
101
106
|
* 'unapproved-spec' | 'job-red' | 'smoke-red' | 'plan-red' | 'check-red' |
|
|
102
107
|
* 'close-red' | 'close-unsupported' | 'recipe-stale' | 'branch-red' | 'pricing-red' | 'provider-red' |
|
|
103
|
-
* 'interpreter-red' | 'cap-halt' | 'wall-halt' | 'step-stalled' |
|
|
108
|
+
* 'interpreter-red' | 'cap-halt' | 'wall-halt' | 'step-stalled' |
|
|
109
|
+
* 'hitl-pause' | 'hitl-decision-red' | `step-red:<id>`
|
|
104
110
|
*/
|
|
105
|
-
export function runJob(rawSpec: object | string, { approvals, workdir, provider, nativeProvider, providerFor, emit, capRuns, strikeLimit, shellCapUsd, closeTimeoutMs, layerRoot, bridge, priorSpentUsd, priorSpendComplete, priorWallMs, resumeSeed, resumeGrades, resumeReplans, resumeBranch }: {
|
|
111
|
+
export function runJob(rawSpec: object | string, { approvals, workdir, provider, nativeProvider, providerFor, emit, capRuns, strikeLimit, shellCapUsd, closeTimeoutMs, layerRoot, bridge, priorSpentUsd, priorSpendComplete, priorWallMs, resumeSeed, resumeGrades, resumeReplans, resumeBranch, humanRuling }: {
|
|
106
112
|
approvals: unknown;
|
|
107
113
|
workdir: string;
|
|
108
114
|
provider: any;
|
|
@@ -133,6 +139,10 @@ export function runJob(rawSpec: object | string, { approvals, workdir, provider,
|
|
|
133
139
|
} | null | undefined;
|
|
134
140
|
resumeBranch?: string | null | undefined;
|
|
135
141
|
layerRoot?: boolean | undefined;
|
|
142
|
+
humanRuling?: {
|
|
143
|
+
decision: string;
|
|
144
|
+
text?: string;
|
|
145
|
+
} | null | undefined;
|
|
136
146
|
}): Promise<string>;
|
|
137
147
|
export type Red = {
|
|
138
148
|
code: string;
|
package/types/text.d.ts
CHANGED
|
@@ -33,6 +33,73 @@ export function priceOf(r: any): {
|
|
|
33
33
|
costUsd: number | null;
|
|
34
34
|
unpricedRounds: number;
|
|
35
35
|
};
|
|
36
|
+
/**
|
|
37
|
+
* The F6 tally over a list of PRICED CALLS — `priceOf`'s per-call read, summed.
|
|
38
|
+
*
|
|
39
|
+
* `knownUsd` carries the priced half explicitly and `costUsd` is the honest
|
|
40
|
+
* total: null the moment anything went unpriced, never the priced half wearing a
|
|
41
|
+
* complete coat. An EMPTY list is a complete $0 and not an unknown — nothing
|
|
42
|
+
* spent is a number somebody can act on.
|
|
43
|
+
* @param {{costUsd: number|null, unpricedRounds?: number}[]|null|undefined} entries
|
|
44
|
+
* @returns {{costUsd: number|null, knownUsd: number, spendComplete: boolean,
|
|
45
|
+
* nullCostCalls: number, unpricedRounds: number}}
|
|
46
|
+
*/
|
|
47
|
+
export function tallyCalls(entries: {
|
|
48
|
+
costUsd: number | null;
|
|
49
|
+
unpricedRounds?: number;
|
|
50
|
+
}[] | null | undefined): {
|
|
51
|
+
costUsd: number | null;
|
|
52
|
+
knownUsd: number;
|
|
53
|
+
spendComplete: boolean;
|
|
54
|
+
nullCostCalls: number;
|
|
55
|
+
unpricedRounds: number;
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* THE ONE MONEY-CEILING PREDICATE, for the same reason `priceOf` above is one:
|
|
59
|
+
* the authoring pipeline meters two populations of paid call in two different
|
|
60
|
+
* accumulators (the survey's `calls[]`, the declaration loop's cost book), and
|
|
61
|
+
* two hand-spelled answers to "am I over the ceiling?" is two instruments.
|
|
62
|
+
*
|
|
63
|
+
* It is asked BETWEEN metered calls and never inside one. A cap that binds
|
|
64
|
+
* mid-call kills the row before it can be graded — F45 measured that as 3 of 7
|
|
65
|
+
* launches lost and $5.82 unreadable.
|
|
66
|
+
*
|
|
67
|
+
* Three answers, and the order between the last two is load-bearing:
|
|
68
|
+
* - NO CEILING → null, always. An absent cap is UNBOUNDED, and unbounded is a
|
|
69
|
+
* visible operator choice, never a defaulted number nobody set (the
|
|
70
|
+
* `maxWallMs` precedent). There is nothing to enforce, so unpriced spend
|
|
71
|
+
* here is merely reported.
|
|
72
|
+
* - KNOWN SPEND AT OR OVER THE CEILING → `cap-halt`, even when the total is
|
|
73
|
+
* unknown. The breach is CERTAIN on the priced half alone — whatever the
|
|
74
|
+
* unpriced remainder turns out to be, it can only be larger — so naming this
|
|
75
|
+
* `pricing-red` would send the operator to fix a meter when the fact is that
|
|
76
|
+
* the money is gone. At-or-over rather than over: the next call is the one
|
|
77
|
+
* that breaches, and it is the one being decided.
|
|
78
|
+
* - SPEND THAT CANNOT BE KNOWN → `pricing-red`. `?? 0` launders unknown into
|
|
79
|
+
* $0 (F6) and a ceiling enforced against a laundered floor is not enforced
|
|
80
|
+
* at all. If the spend cannot be seen the cap cannot govern it, and that is
|
|
81
|
+
* its own stop rather than a silent pass.
|
|
82
|
+
*
|
|
83
|
+
* A MALFORMED ceiling is the one input it refuses rather than answers: `null` and
|
|
84
|
+
* an absent field mean UNBOUNDED, and they are the ONLY way to get there. Anything
|
|
85
|
+
* else that is not a finite number (`'2.50'`, `NaN`, `Infinity`) reading as
|
|
86
|
+
* unbounded would be F6's laundering in its ceiling form — a caller who set a cap
|
|
87
|
+
* getting a paid pipeline with nothing enforcing it, which is the exact collapse
|
|
88
|
+
* the ceiling exists to prevent, wearing the caller's own typo (PRD v1.62). The
|
|
89
|
+
* CLI's `parseCeiling` guards the FLAG; this guards the LIBRARY, and `authorClose`
|
|
90
|
+
* / `runAuthorScout` / `authorCloseForJob` are documented adopter seams. It THROWS
|
|
91
|
+
* rather than clamping, unlike the attempt caps beside it, because those axes have
|
|
92
|
+
* a safe direction to clamp toward and this one does not: every candidate default
|
|
93
|
+
* is either a number nobody set or the unbounded run the caller just declined.
|
|
94
|
+
* @param {{ceilingUsd: number|null|undefined, knownUsd: number, spendComplete: boolean}} o
|
|
95
|
+
* @returns {'cap-halt'|'pricing-red'|null}
|
|
96
|
+
* @throws {Error} when the ceiling is neither absent nor a finite number
|
|
97
|
+
*/
|
|
98
|
+
export function capStop({ ceilingUsd, knownUsd, spendComplete }: {
|
|
99
|
+
ceilingUsd: number | null | undefined;
|
|
100
|
+
knownUsd: number;
|
|
101
|
+
spendComplete: boolean;
|
|
102
|
+
}): "cap-halt" | "pricing-red" | null;
|
|
36
103
|
/**
|
|
37
104
|
* ONE raw model emission, ready to persist.
|
|
38
105
|
*
|
package/types/validate.d.ts
CHANGED
|
@@ -86,16 +86,40 @@ export function sweepSecretLiterals(root: any, red: (code: string, path: string,
|
|
|
86
86
|
* So the reject targets the dominant exponential class and fails it as a
|
|
87
87
|
* mechanical gap at the validation gate, before any tokens burn. A full ReDoS
|
|
88
88
|
* analyzer needs a real regex engine (an external native dep we do not take for
|
|
89
|
-
* a LOW issue)
|
|
90
|
-
*
|
|
89
|
+
* a LOW issue).
|
|
90
|
+
*
|
|
91
|
+
* SECOND CLASS, added 2026-08-12 — OVERLAPPING ALTERNATION under an unbounded
|
|
92
|
+
* repeat (`(a|aa)+`, `(a|a)+`, `(\d|\d\d)*`). F49 recorded this as a genuine
|
|
93
|
+
* false NEGATIVE of the shape approach and PARKED the widening, because changing
|
|
94
|
+
* the detector changes which SIGNED specs are admissible. It is closed here on
|
|
95
|
+
* measurement, one fresh process per point (a hot RegExp tiers up out of the V8
|
|
96
|
+
* interpreter, and a single-process sweep read a LONGER body as faster —
|
|
97
|
+
* backwards for an exponential, and the harness rather than the regex).
|
|
98
|
+
* `(a|aa)+$` against a run of `a` with one non-matching tail character:
|
|
99
|
+
*
|
|
100
|
+
* 27ch 218ms · 31ch 1,879ms · 35ch 12,082ms · 39ch 52,563ms · 41ch 189,420ms
|
|
101
|
+
*
|
|
102
|
+
* ~6.8× per 4 characters — the Fibonacci decomposition count, φ⁴. `(a|a)+$` (the
|
|
103
|
+
* 2ⁿ equal-branch case) took 52,256ms on 27ch, and `((a|aa))+$` — the redundant
|
|
104
|
+
* wrapper the `((a+))+` entry above exists for — 15,426ms on 35ch, which is why
|
|
105
|
+
* the overlap propagates up through a wrapper exactly as the inner repeat does.
|
|
106
|
+
* Input-bounding is theater here for F49's own reason: tens of characters suffice.
|
|
107
|
+
*
|
|
108
|
+
* The detected core is the PREFIX RELATION between top-level branches (one branch
|
|
109
|
+
* a prefix of another, equality included — `(a|a)+` is the 2ⁿ case). Whether a
|
|
110
|
+
* given prefix overlap can actually be DRIVEN exponentially depends on what
|
|
111
|
+
* follows the group, and deciding that needs the real engine this file declined
|
|
112
|
+
* to take as a dependency — so the reject is conservative by construction, which
|
|
113
|
+
* is the fail-safe direction.
|
|
114
|
+
*
|
|
115
|
+
* MONOTONIC, and that is the rule this change lives under: it only ever ADDS
|
|
116
|
+
* rejections. Nothing above is narrowed, and no previously-rejected shape became
|
|
117
|
+
* admissible (asserted over the whole corpus in tests/plan.test.js). F49's
|
|
118
|
+
* "never sharpen the detector" bars chasing false POSITIVES; closing a false
|
|
119
|
+
* NEGATIVE by adding rejections is the opposite, allowed direction — distinct
|
|
120
|
+
* rules, not in tension (the `((a+))+` widening set the precedent).
|
|
91
121
|
*
|
|
92
122
|
* SCOPE IS ASYMMETRIC, both directions named on purpose:
|
|
93
|
-
* - false NEGATIVE: overlapping-alternation blowup (`(a|ab)+`-class) is not
|
|
94
|
-
* detected — there is no inner QUANTIFIER to find, so the shape scan cannot
|
|
95
|
-
* see it; the blowup comes from overlapping alternation BRANCHES under a
|
|
96
|
-
* repeat. A genuine limit of the approach, not a bug in it. Widening the
|
|
97
|
-
* detector changes which SIGNED specs are admissible, which is
|
|
98
|
-
* arbiter-adjacent — parked, not chased (PRD v1.55).
|
|
99
123
|
* - false POSITIVE: a group whose repetitions are disambiguated by a literal
|
|
100
124
|
* anchor/delimiter (`(?:^- .+$\n?)+`, `(?:CHANGELOG:.+\n)+`) is FLAGGED even
|
|
101
125
|
* though a real engine runs it linearly — the scan sees the nested-quantifier
|
|
@@ -106,10 +130,51 @@ export function sweepSecretLiterals(root: any, red: (code: string, path: string,
|
|
|
106
130
|
* and guessing it wrong would ADMIT an exponential pattern — so the shape
|
|
107
131
|
* reject stands, and the over-rejection is a named, accepted limitation.
|
|
108
132
|
*
|
|
133
|
+
* The alternation class has its OWN instance of the same limit, measured and
|
|
134
|
+
* locked in `REDOS_OVERREJECTED_ALT`: `(ab|abc)+$`, `(x|xy|xyz)+$` and
|
|
135
|
+
* `(x(a|aa))+$` carry the prefix relation and did NOT blow up on the bodies
|
|
136
|
+
* measured (0.3–0.4ms at 41–91 characters). "Did not blow up on the body
|
|
137
|
+
* measured" is the honest claim — no adversarial search was run for a worse
|
|
138
|
+
* input — and rejecting them is again the direction that never admits an
|
|
139
|
+
* exponential pattern.
|
|
140
|
+
*
|
|
109
141
|
* The input is guaranteed to compile as a RegExp (checked first by the caller),
|
|
110
142
|
* so this scan assumes valid, balanced JS regex syntax.
|
|
111
143
|
* @param {string} src a compiled regex source string
|
|
112
|
-
* @returns {boolean} true iff a nested unbounded quantifier
|
|
144
|
+
* @returns {boolean} true iff a nested unbounded quantifier, or an unboundedly
|
|
145
|
+
* repeated group with overlapping alternation branches, is present
|
|
113
146
|
*/
|
|
114
147
|
export function hasNestedQuantifier(src: string): boolean;
|
|
148
|
+
/**
|
|
149
|
+
* The catastrophic-backtracking reject, applied to a WHOLE signed document by
|
|
150
|
+
* tree walk — `sweepSecretLiterals`'s twin, and for its reason: some signed
|
|
151
|
+
* documents carry their regexes at a depth no per-field check enumerates.
|
|
152
|
+
*
|
|
153
|
+
* WHY IT EXISTS (PRD v1.60 §1, an OPEN GAP now closed). v1.55 took F49's reject
|
|
154
|
+
* to the operator's own regex fields, and the admissibility sweep that backed it
|
|
155
|
+
* walked `jobs/*.json`. That is not the population of signed specs: a REGISTRY
|
|
156
|
+
* BRIDGE is signed the same way, ships its plans VERBATIM (reuse stores the
|
|
157
|
+
* whole plan, never a template), and lives outside `jobs/`, so a pathological
|
|
158
|
+
* pattern arriving through a bridge was never swept. Nothing had decided
|
|
159
|
+
* bridge-borne patterns were safe — the sweep was written against the directory
|
|
160
|
+
* that held every spec at the time and was never re-aimed.
|
|
161
|
+
*
|
|
162
|
+
* WHAT is rejected is UNCHANGED: `hasNestedQuantifier`, one inventory, shared
|
|
163
|
+
* with job.js and plan.js. Only WHERE it looks is wider. Widening the DETECTOR
|
|
164
|
+
* is arbiter-adjacent and stays parked (F49); widening the POPULATION is this.
|
|
165
|
+
*
|
|
166
|
+
* KEY-SCOPED, deliberately. `pattern` and `gapKeep` are the exactly-three regex
|
|
167
|
+
* fields F49 names (`artifact-written.pattern`, `judged.pattern`, `gapKeep`);
|
|
168
|
+
* every other string in a signed document is prose, a path or a command, and
|
|
169
|
+
* running a regex-shape scan over prose would red goals for containing `(a+)+`
|
|
170
|
+
* in a sentence. A field carrying a regex under a NEW key is not swept until
|
|
171
|
+
* this list names it — stated so the limit is read rather than rediscovered.
|
|
172
|
+
*
|
|
173
|
+
* COMPILE-FIRST, like both per-field callers: the scan's contract is that its
|
|
174
|
+
* input is valid regex syntax, and a string that does not compile is the OTHER
|
|
175
|
+
* validator's red. Two reds on one string bury the real one.
|
|
176
|
+
* @param {any} root
|
|
177
|
+
* @param {(code: string, path: string, detail?: string) => void} red
|
|
178
|
+
*/
|
|
179
|
+
export function sweepNestedQuantifiers(root: any, red: (code: string, path: string, detail?: string) => void): void;
|
|
115
180
|
export const SECRET_PATTERNS: RegExp[];
|