bareloop 0.8.0 → 0.10.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 +625 -0
- package/README.md +101 -110
- package/bareloop.context.md +333 -25
- package/package.json +1 -1
- package/src/authorflow.js +1447 -0
- package/src/authoring.js +1486 -0
- package/src/authorjob.js +801 -0
- package/src/authorscout.js +767 -0
- package/src/bridges.js +16 -3
- package/src/declaredclose.js +519 -0
- package/src/index.js +75 -0
- package/src/job.js +48 -4
- package/src/kinds.js +1445 -0
- package/src/ledger.js +18 -1
- package/src/plan.js +26 -1
- package/src/planrun.js +448 -40
- package/src/ralph.js +24 -4
- package/src/reuse.js +120 -9
- package/src/root.js +8 -1
- package/src/run.js +12 -4
- package/src/text.js +199 -0
- package/src/tools.js +11 -0
- package/src/validate.js +168 -11
- package/src/workbranch.js +261 -0
- package/types/authorflow.d.ts +764 -0
- package/types/authoring.d.ts +559 -0
- package/types/authorjob.d.ts +242 -0
- package/types/authorscout.d.ts +320 -0
- package/types/declaredclose.d.ts +174 -0
- package/types/index.d.ts +9 -1
- package/types/job.d.ts +1 -0
- package/types/kinds.d.ts +395 -0
- package/types/plan.d.ts +18 -0
- package/types/planrun.d.ts +53 -3
- package/types/ralph.d.ts +1 -0
- package/types/reuse.d.ts +1 -1
- package/types/run.d.ts +10 -2
- package/types/text.d.ts +151 -0
- package/types/validate.d.ts +74 -9
- package/types/workbranch.d.ts +102 -0
package/src/ralph.js
CHANGED
|
@@ -285,6 +285,21 @@ export async function runClose(close, redact = (s) => s, { timeoutMs = 120_000,
|
|
|
285
285
|
};
|
|
286
286
|
}
|
|
287
287
|
|
|
288
|
+
/**
|
|
289
|
+
* The staged gap's header — WHICH wall the close stopped at, in front of that
|
|
290
|
+
* wall's own output. A worker handed three stages' worth of output cannot tell
|
|
291
|
+
* which one it must satisfy, and `src/trend.js` parses this exact line to bucket
|
|
292
|
+
* a grade per stage (its `STAGE_HEADER`).
|
|
293
|
+
*
|
|
294
|
+
* Exported for the one reason `GAP_KEEP_TRIM_MARKER` is: a SECOND executor now
|
|
295
|
+
* renders staged gaps (`src/declaredclose.js`, the declared close), and two
|
|
296
|
+
* spellings of this line would be two instruments — the reader would silently
|
|
297
|
+
* stop bucketing whichever one drifted. Behaviour here is unchanged; this is the
|
|
298
|
+
* same template, named.
|
|
299
|
+
* @param {string} name @param {string} gap
|
|
300
|
+
*/
|
|
301
|
+
export const stageGap = (name, gap) => `close stage "${name}" failed:\n${gap}`;
|
|
302
|
+
|
|
288
303
|
/**
|
|
289
304
|
* Run a STAGED close (PRD v1.28) — an ordered list of named command stages, run
|
|
290
305
|
* in order until one renders a verdict that is not `satisfied`. The return is
|
|
@@ -310,7 +325,7 @@ export async function runClose(close, redact = (s) => s, { timeoutMs = 120_000,
|
|
|
310
325
|
* @param {{timeoutMs?: number, cwd?: string}} [opts]
|
|
311
326
|
* @returns {Promise<any>} runClose's verdict shape, plus `stage` and `stages`
|
|
312
327
|
*/
|
|
313
|
-
export async function runStages(stages, redact = (s) => s, { timeoutMs, cwd } = {}) {
|
|
328
|
+
export async function runStages(stages, redact = (s) => s, /** @type {{timeoutMs?: number, cwd?: string}} */ { timeoutMs, cwd } = {}) {
|
|
314
329
|
const ran = [];
|
|
315
330
|
let last;
|
|
316
331
|
for (const st of stages) {
|
|
@@ -326,9 +341,9 @@ export async function runStages(stages, redact = (s) => s, { timeoutMs, cwd } =
|
|
|
326
341
|
...r,
|
|
327
342
|
stage: st.name,
|
|
328
343
|
stages: ran,
|
|
329
|
-
// The gap says WHICH wall, then the wall's own output
|
|
330
|
-
//
|
|
331
|
-
...(r.gap ? { gap:
|
|
344
|
+
// The gap says WHICH wall, then the wall's own output (`stageGap`, the
|
|
345
|
+
// one spelling — the declared executor renders the same header).
|
|
346
|
+
...(r.gap ? { gap: stageGap(st.name, r.gap) } : {}),
|
|
332
347
|
};
|
|
333
348
|
}
|
|
334
349
|
}
|
|
@@ -575,6 +590,11 @@ export async function ralph({ middle, close, judge, capRuns, ladder, emit, redac
|
|
|
575
590
|
const DECISIONS = {
|
|
576
591
|
'cap-halt': [`Budget gate tripped mid-run (${e && e.message}). Continue with a higher cap, change approach, or stop?`,
|
|
577
592
|
['raise the cap and rerun', 'change the middle/harness', 'abandon the task']],
|
|
593
|
+
// No site in this tree mints `gate-red` any more: a BA-11 denial streak is
|
|
594
|
+
// a bounded attempt now (planrun's two `ask`s), not a terminal, because the
|
|
595
|
+
// fence HOLDING is governance working, not a fault. The entry stays because
|
|
596
|
+
// this catch is a dumb passthrough — it files whatever category the thrower
|
|
597
|
+
// names, so an unlisted one would read out with the generic text.
|
|
578
598
|
'gate-red': ['The gate denied an action mid-run — the harness asked for something outside its scope.',
|
|
579
599
|
['widen the write scope deliberately', 'change the middle/harness', 'abandon the task']],
|
|
580
600
|
'provider-red': ['The provider path failed mid-run (transport, not logic) — no verdict exists and the spend for the failed call is unknown (F6).',
|
package/src/reuse.js
CHANGED
|
@@ -48,7 +48,7 @@ import { createHash } from 'node:crypto';
|
|
|
48
48
|
import { createRequire } from 'node:module';
|
|
49
49
|
import { loadRegistry, saveBridge, appendGreen, appendRed, mintBridge, registryExists } from './bridges.js';
|
|
50
50
|
import { renderListing, selectionPrompt } from './selection.js';
|
|
51
|
-
import {
|
|
51
|
+
import { closeStagesOf } from './plan.js';
|
|
52
52
|
import { MIN_WALL_MS, jobSpecHash } from './job.js';
|
|
53
53
|
import { isObj, isNonEmptyString } from './validate.js';
|
|
54
54
|
import { readGrade } from './trend.js';
|
|
@@ -498,6 +498,30 @@ function readStepCheckpoint(seen) {
|
|
|
498
498
|
};
|
|
499
499
|
}
|
|
500
500
|
|
|
501
|
+
/**
|
|
502
|
+
* The WORK BRANCH the dead leg created (PRD v1.57 §3), read off its own `work-branch`
|
|
503
|
+
* record — the one place the runner writes it down.
|
|
504
|
+
*
|
|
505
|
+
* It is read rather than re-derived, and that is the whole point. `workBranchName` is
|
|
506
|
+
* deterministic from the signed spec, so a restart computing it fresh would get the very
|
|
507
|
+
* name its predecessor already took and the collision walk would hand it
|
|
508
|
+
* `<name>-2` — a brand-new branch standing beside the work the resume exists to
|
|
509
|
+
* continue. The declared-fold precedent (`priorSpentUsd`, `priorReplans`): what the
|
|
510
|
+
* chain already did is DECLARED on the spine and inherited, never recomputed.
|
|
511
|
+
*
|
|
512
|
+
* The LAST record wins. A leg emits exactly one, but a window can reach back through an
|
|
513
|
+
* abandoned attempt of the same try, and the newest is the branch the work is actually
|
|
514
|
+
* sitting on.
|
|
515
|
+
* @param {any[]} seen the window's own events, in file order
|
|
516
|
+
* @returns {string|null} null on a spine older than the record, or a kill before 0c
|
|
517
|
+
*/
|
|
518
|
+
function readWorkBranch(seen) {
|
|
519
|
+
for (let i = seen.length - 1; i >= 0; i -= 1) {
|
|
520
|
+
if (seen[i].type === 'work-branch' && isNonEmptyString(seen[i].branch)) return String(seen[i].branch);
|
|
521
|
+
}
|
|
522
|
+
return null;
|
|
523
|
+
}
|
|
524
|
+
|
|
501
525
|
/**
|
|
502
526
|
* The dead leg's CLOSE GRADES, in order — the trend baselines a resumed leg
|
|
503
527
|
* inherits (PRD v1.46 §3, and hamr's approved #2: *a resumed run's trend judges the
|
|
@@ -641,7 +665,7 @@ function readGradeSeed(seen) {
|
|
|
641
665
|
* which is later and better evidence than the last event). Defaults to the last
|
|
642
666
|
* event's own timestamp — the last sign of life there is.
|
|
643
667
|
* @returns {any} `{ started, approvalHash, specHash, patient, job, bridgeTries,
|
|
644
|
-
* perTryBudgetUsd, perTryWallMs, ended, endOutcome, greened, completed, tried,
|
|
668
|
+
* perTryBudgetUsd, perTryWallMs, ended, endOutcome, wallDerivedHalt, greened, completed, tried,
|
|
645
669
|
* selectionCostUsd, selectionComplete, strayRounds, r1Missing, restart, deathAtKnown,
|
|
646
670
|
* spentUsd, spendComplete, carrySpentUsd, carrySpendComplete }`
|
|
647
671
|
*/
|
|
@@ -792,7 +816,50 @@ export function readResume(events, { deathAt = null, direct = false, resumableOu
|
|
|
792
816
|
* CHECKPOINT and re-entering it is the ruling, not a second payment for an answer
|
|
793
817
|
* already in hand. Empty by default, which is what keeps the reuse loop's own
|
|
794
818
|
* graded-row semantics exactly where they were. */
|
|
795
|
-
const
|
|
819
|
+
const recordedOutcome = open?.jobEnd ? String(open.jobEnd.outcome) : null;
|
|
820
|
+
/** D4a — the MISLABELLED wall stop, derived from the run's own primary records.
|
|
821
|
+
*
|
|
822
|
+
* `step-red` is a verdict-shaped terminal and stays non-resumable: a red is an
|
|
823
|
+
* answer. But on u-msmt91t3 the runner minted one 9.9 seconds AFTER its own
|
|
824
|
+
* `wall-bounded` record — a governance stop wearing a capability label, because the
|
|
825
|
+
* step-variance terminal did not re-read the clock (fixed at the source, src/planrun.js).
|
|
826
|
+
* Spines already written under the defect are not re-runnable and cannot be edited (a
|
|
827
|
+
* spine is append-only forever), so the READER derives the truth the file already
|
|
828
|
+
* holds: a run whose own record says the wall was crossed BEFORE it ended is a wall
|
|
829
|
+
* stop, and W-2's ruling applies — it keeps the grades it minted and pauses.
|
|
830
|
+
*
|
|
831
|
+
* Three constraints, each of them load-bearing:
|
|
832
|
+
* * a DERIVATION, never an override. The evidence is the run's own `wall-bounded`
|
|
833
|
+
* event, emitted by the runner at the round seam; no caller can assert this.
|
|
834
|
+
* * `step-red` ONLY, and only when `wall-halt` is itself resumable for this caller.
|
|
835
|
+
* Every other outcome — green, escalated, provider-red, a step-red with no wall
|
|
836
|
+
* record — is untouched, so "a red is an answer" survives intact.
|
|
837
|
+
* * it touches NO close verdict. `step-red` here is a GOVERNANCE stop label; the
|
|
838
|
+
* grades the close rendered are exactly what they were, and the resume seeds
|
|
839
|
+
* from them.
|
|
840
|
+
* @param {any[]} seen @param {any} jobEnd */
|
|
841
|
+
const wallCrossedBefore = (seen, jobEnd) => {
|
|
842
|
+
// strictly BEFORE the terminal: a record written after the run ended could not have
|
|
843
|
+
// been what stopped it. `indexOf` on the window's own array is the ordering there is
|
|
844
|
+
// (the reader never re-sorts), and a jobEnd somehow absent from it reads nothing.
|
|
845
|
+
const end = seen.indexOf(jobEnd);
|
|
846
|
+
if (end < 0) return false;
|
|
847
|
+
for (let i = 0; i < end; i += 1) {
|
|
848
|
+
const e = seen[i];
|
|
849
|
+
if (!isObj(e) || e.type !== 'wall-bounded' || e.bounded !== true) continue;
|
|
850
|
+
// the clock's OWN two spellings of "the deadline passed", both off `clock.report`.
|
|
851
|
+
// Never a re-derivation from timestamps: a second arithmetic for one question is
|
|
852
|
+
// how two instruments come to disagree.
|
|
853
|
+
if (e.remainingMs === 0) return true;
|
|
854
|
+
if (typeof e.elapsedMs === 'number' && typeof e.requestedMs === 'number' && e.elapsedMs >= e.requestedMs) return true;
|
|
855
|
+
}
|
|
856
|
+
return false;
|
|
857
|
+
};
|
|
858
|
+
const wallDerivedHalt = recordedOutcome === 'step-red'
|
|
859
|
+
&& resumableHalt.has('wall-halt')
|
|
860
|
+
&& wallCrossedBefore(open.seen, open.jobEnd);
|
|
861
|
+
const haltedResumable = recordedOutcome !== null
|
|
862
|
+
&& (resumableHalt.has(recordedOutcome) || wallDerivedHalt);
|
|
796
863
|
if (open) {
|
|
797
864
|
if (open.jobEnd && !haltedResumable) {
|
|
798
865
|
// GRADED: runJob returned and the close had its say. Not a restart — the row is
|
|
@@ -842,6 +909,17 @@ export function readResume(events, { deathAt = null, direct = false, resumableOu
|
|
|
842
909
|
// restart already re-graded that tree. Counting both would enter the same
|
|
843
910
|
// reading twice and flatten a chain that moved.
|
|
844
911
|
grades: readGradeSeed(open.seen),
|
|
912
|
+
// the WORK BRANCH this leg's uncommitted work is sitting on (v1.57 §3). Read
|
|
913
|
+
// from THIS window first and then from the newest abandoned window of the same
|
|
914
|
+
// try, on the checkpoint's own rule and for the checkpoint's own reason: the
|
|
915
|
+
// branch — like the plan — is a fact about the TREE, and the tree survives an
|
|
916
|
+
// attempt that died before recording anything of its own. Null is the honest
|
|
917
|
+
// answer for a leg killed before 0c (or a spine older than the record); the
|
|
918
|
+
// resume then takes the cold path and creates one, which is correct, because a
|
|
919
|
+
// leg that never branched has no work to strand.
|
|
920
|
+
branch: readWorkBranch(open.seen)
|
|
921
|
+
?? abandoned.filter((w) => w.n === open.n).map((w) => readWorkBranch(w.seen)).filter(Boolean).at(-1)
|
|
922
|
+
?? null,
|
|
845
923
|
// the REPLAN ledger, folded like the money one line below and deliberately
|
|
846
924
|
// NOT like the grades one line above. A grade seed feeds a READOUT and its
|
|
847
925
|
// documented shortening chain can only under-claim a direction; this feeds a
|
|
@@ -928,7 +1006,14 @@ export function readResume(events, { deathAt = null, direct = false, resumableOu
|
|
|
928
1006
|
perTryBudgetUsd: head?.perTryBudgetUsd ?? directHead?.budgetUsd ?? null,
|
|
929
1007
|
perTryWallMs: head?.perTryWallMs ?? null,
|
|
930
1008
|
ended: end !== null || (directEnd !== null && !haltedResumable),
|
|
1009
|
+
// the RECORDED terminal, always — the derivation below never rewrites what the run
|
|
1010
|
+
// said about itself, it only says how this reader read it
|
|
931
1011
|
endOutcome: end?.outcome ?? directEnd?.outcome ?? null,
|
|
1012
|
+
/** D4a — true when a recorded `step-red` was re-read as a wall-halt off this run's
|
|
1013
|
+
* OWN `wall-bounded` record. Surfaced rather than silent: an operator resuming a
|
|
1014
|
+
* run whose spine says `step-red` must be told which record turned it into a
|
|
1015
|
+
* checkpoint, or the gate looks like it simply stopped refusing. */
|
|
1016
|
+
wallDerivedHalt,
|
|
932
1017
|
greened: completed.some((t) => t.verdictClass === 'green'),
|
|
933
1018
|
completed,
|
|
934
1019
|
tried,
|
|
@@ -1170,8 +1255,14 @@ export async function runReuse(opts) {
|
|
|
1170
1255
|
* much of the try's allowance is gone; this says how much of its WORK is done, so the
|
|
1171
1256
|
* restart re-pays for neither the scout, the draft, nor a finished step. Null is the
|
|
1172
1257
|
* scout/draft death, where nothing paid is re-payable and the try simply restarts.
|
|
1258
|
+
* @param {string|null} [branch] RESUME (v1.57 §3) — the WORK BRANCH that attempt's work
|
|
1259
|
+
* is sitting on (`readResume`'s `restart.branch`). The name is derived from the SIGNED
|
|
1260
|
+
* spec and is therefore the same on every leg, so a restart that recomputed it would
|
|
1261
|
+
* collide with its own predecessor and be handed a fresh `-2` beside the work it came
|
|
1262
|
+
* back for. Declared and inherited, exactly like the money and the replan ledger. Null
|
|
1263
|
+
* is the cold path, and also the leg killed before it ever branched.
|
|
1173
1264
|
* @returns {Promise<any>} the try row */
|
|
1174
|
-
const runTry = async (bridge, n, prior = NO_PRIOR, seed = null) => {
|
|
1265
|
+
const runTry = async (bridge, n, prior = NO_PRIOR, seed = null, branch = null) => {
|
|
1175
1266
|
const mode = bridge ? 'bridge' : 'cold';
|
|
1176
1267
|
const name = bridge ? bridge.name : null;
|
|
1177
1268
|
const resumed = prior.spentUsd > 0 || prior.wallMs > 0;
|
|
@@ -1231,6 +1322,10 @@ export async function runReuse(opts) {
|
|
|
1231
1322
|
// a bound the operator signed, widened by a kill (PRD v1.12).
|
|
1232
1323
|
...(prior.replans > 0 ? { resumeReplans: { count: prior.replans, grantUsed: prior.replanGrantUsed } } : {}),
|
|
1233
1324
|
...(seed ? { resumeSeed: seed } : {}),
|
|
1325
|
+
// …and WHERE its work is. Same omission class as the three folds above, with its
|
|
1326
|
+
// own cost: a branch that never arrives is not a wrong number but a SECOND branch,
|
|
1327
|
+
// and the leg would carry on beside the tree it was resumed to continue.
|
|
1328
|
+
...(branch ? { resumeBranch: branch } : {}),
|
|
1234
1329
|
bridge,
|
|
1235
1330
|
});
|
|
1236
1331
|
} catch (e) {
|
|
@@ -1290,7 +1385,7 @@ export async function runReuse(opts) {
|
|
|
1290
1385
|
name: name ?? job.job,
|
|
1291
1386
|
goal: job.goal,
|
|
1292
1387
|
specHash: trySpecHash,
|
|
1293
|
-
closeStageNames: (
|
|
1388
|
+
closeStageNames: (closeStagesOf(job) ?? []).map((/** @type {any} */ s) => s.name),
|
|
1294
1389
|
toolsUsed: [...new Set((read.plan.steps ?? []).flatMap((/** @type {any} */ s) => s.tools ?? []))],
|
|
1295
1390
|
};
|
|
1296
1391
|
row.bridgeWrite = writeGreen(bridge, meta, { ...record, plan: read.plan });
|
|
@@ -1423,6 +1518,19 @@ export async function runReuse(opts) {
|
|
|
1423
1518
|
decision: 'The plan flow cannot execute this job\'s close — it runs commands whose exit codes are truth. Nothing was judged, and no workflow can be graded by a close that will not run, so the next try and the cold leg would refuse identically for $0 apiece.',
|
|
1424
1519
|
options: ['restate the close as a staged (or single predicate) close — a spec edit, whose new hash needs re-approval', 'wait for the verdict-classes rung'],
|
|
1425
1520
|
},
|
|
1521
|
+
'branch-red': {
|
|
1522
|
+
// NOT re-filed, for `interpreter-red`'s reason plus one of its own: the runner
|
|
1523
|
+
// already escalated it at the fault's own site, and the fault is the OPERATOR's
|
|
1524
|
+
// state (a patient that is not a git checkout, a crowded branch namespace),
|
|
1525
|
+
// never a library failing — the ledger excludes it for exactly that.
|
|
1526
|
+
refile: false,
|
|
1527
|
+
decision: 'The run could not create its own work branch, and no job runs on the branch it was handed (PRD v1.57 §3). That is a property of the PATIENT, not of any stored workflow: every further try and the cold leg would refuse identically, for $0 apiece.',
|
|
1528
|
+
options: [
|
|
1529
|
+
'make the patient a git checkout with at least one commit',
|
|
1530
|
+
'free the work-branch name (delete or rename the stale branches this run collided with)',
|
|
1531
|
+
'abandon the run',
|
|
1532
|
+
],
|
|
1533
|
+
},
|
|
1426
1534
|
'interpreter-red': {
|
|
1427
1535
|
// NOT re-filed as an escalation. `interpreter-red` is one of the categories the
|
|
1428
1536
|
// ledger CLASSIFIES — it aims a runtime-red at a library — and runJob already
|
|
@@ -1522,7 +1630,10 @@ export async function runReuse(opts) {
|
|
|
1522
1630
|
// `gradesInherited` is a COUNT, and only when there is one to state: the seam
|
|
1523
1631
|
// carries stage names and numbers, never a close byte — the spine is append-only,
|
|
1524
1632
|
// so a gap that crosses here crosses for good.
|
|
1525
|
-
restart: rs ? { n: rs.n, mode: rs.mode, bridge: rs.bridge ?? null, priorSpentUsd: prior.spentUsd, priorWallMs: prior.wallMs, priorSpendComplete: prior.spendComplete, remainingCapUsd, remainingWallMs, resumedAt: rs.seed ? { phase: rs.seed.phase, stepsDone: rs.seed.completedSteps.length, stepsPlanned: rs.seed.plan?.steps?.length ?? null } : null, ...(prior.grades.length ? { gradesInherited: prior.grades.length } : {})
|
|
1633
|
+
restart: rs ? { n: rs.n, mode: rs.mode, bridge: rs.bridge ?? null, priorSpentUsd: prior.spentUsd, priorWallMs: prior.wallMs, priorSpendComplete: prior.spendComplete, remainingCapUsd, remainingWallMs, resumedAt: rs.seed ? { phase: rs.seed.phase, stepsDone: rs.seed.completedSteps.length, stepsPlanned: rs.seed.plan?.steps?.length ?? null } : null, ...(prior.grades.length ? { gradesInherited: prior.grades.length } : {}),
|
|
1634
|
+
// WHERE it comes back to (v1.57 §3), stated before anything is spent rather than
|
|
1635
|
+
// discovered from the restarted leg's own `work-branch` record afterwards
|
|
1636
|
+
...(rs.branch ? { branch: rs.branch } : {}) } : null,
|
|
1526
1637
|
nextTry: rs ? null : startN,
|
|
1527
1638
|
});
|
|
1528
1639
|
|
|
@@ -1560,7 +1671,7 @@ export async function runReuse(opts) {
|
|
|
1560
1671
|
if (rs.mode === 'cold') {
|
|
1561
1672
|
// every bridge try was already spent before the cold leg started: the loop below
|
|
1562
1673
|
// has nothing left to authorize, and the restart IS the cold attempt
|
|
1563
|
-
coldRestart = { n: typeof rs.n === 'number' ? rs.n : tries.length + 1, prior, seed: rs.seed ?? null };
|
|
1674
|
+
coldRestart = { n: typeof rs.n === 'number' ? rs.n : tries.length + 1, prior, seed: rs.seed ?? null, branch: rs.branch ?? null };
|
|
1564
1675
|
startN = resolved.bridgeTries + 1;
|
|
1565
1676
|
} else {
|
|
1566
1677
|
const entry = loadRegistry(registryDir).bridges.find((/** @type {any} */ b) => b.name === rs.bridge);
|
|
@@ -1576,7 +1687,7 @@ export async function runReuse(opts) {
|
|
|
1576
1687
|
});
|
|
1577
1688
|
}
|
|
1578
1689
|
tried.add(entry.name);
|
|
1579
|
-
const stop = afterTry(await runTry(entry, rs.n, prior, rs.seed ?? null));
|
|
1690
|
+
const stop = afterTry(await runTry(entry, rs.n, prior, rs.seed ?? null, rs.branch ?? null));
|
|
1580
1691
|
if (stop) return stop;
|
|
1581
1692
|
startN = rs.n + 1;
|
|
1582
1693
|
}
|
|
@@ -1644,7 +1755,7 @@ export async function runReuse(opts) {
|
|
|
1644
1755
|
// the verdict that attempt actually earned.
|
|
1645
1756
|
const inheritedCold = coldRestart ? null : tries.find((t) => t.mode === 'cold' && t.inherited) ?? null;
|
|
1646
1757
|
const cold = coldRestart
|
|
1647
|
-
? await runTry(null, coldRestart.n, coldRestart.prior, coldRestart.seed)
|
|
1758
|
+
? await runTry(null, coldRestart.n, coldRestart.prior, coldRestart.seed, coldRestart.branch)
|
|
1648
1759
|
: inheritedCold ?? await runTry(null, tries.length + 1);
|
|
1649
1760
|
const coldStop = afterTry(cold);
|
|
1650
1761
|
if (coldStop) return coldStop;
|
package/src/root.js
CHANGED
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
// UNRELIABLE red-set for comparison. Imported (never re-spelled) so the detector
|
|
31
31
|
// keys off the exact string the shell emits — a magic string here would drift.
|
|
32
32
|
import { GAP_KEEP_TRIM_MARKER } from './ralph.js';
|
|
33
|
+
import { GAP_TRIM_MARKER } from './kinds.js';
|
|
33
34
|
|
|
34
35
|
/**
|
|
35
36
|
* Strip a trailing duration stamp from a kept-failure line. Real `node --test`
|
|
@@ -107,7 +108,13 @@ export function createRoot({ gapKeep, redact = (s) => s, writesInformative = tru
|
|
|
107
108
|
*/
|
|
108
109
|
const keptSet = (gap, pattern) => {
|
|
109
110
|
const re = pattern ? new RegExp(pattern, 'm') : keepRe;
|
|
110
|
-
|
|
111
|
+
// BOTH trim announcements, because there are now two gap renderers: ralph's
|
|
112
|
+
// keep-block cap on a command close, and the kind executor's per-stage line
|
|
113
|
+
// cap on a DECLARED one. A trim the detector cannot see is worse than a
|
|
114
|
+
// missing detector — it makes the strong "reds unchanged" claim off a window
|
|
115
|
+
// that silently dropped failures (finding 5), which is the direction that
|
|
116
|
+
// manufactures a fixation reading out of an instrument that went blind.
|
|
117
|
+
if (!re || gap.includes(GAP_KEEP_TRIM_MARKER) || gap.includes(GAP_TRIM_MARKER)) return null;
|
|
111
118
|
const lines = [...new Set(gap.split('\n').filter((l) => re.test(l)).map(normalizeRedLine))].sort();
|
|
112
119
|
return lines.length ? lines : null;
|
|
113
120
|
};
|
package/src/run.js
CHANGED
|
@@ -15,6 +15,7 @@ import { join } from 'node:path';
|
|
|
15
15
|
import { LiteCtx } from 'litectx';
|
|
16
16
|
import { validateJob, jobSpecHash, checkApproval } from './job.js';
|
|
17
17
|
import { runPlan } from './planrun.js';
|
|
18
|
+
import { SMOKE_STORE } from './kinds.js';
|
|
18
19
|
|
|
19
20
|
/** @typedef {{code: string, path: string, detail?: string}} Red */
|
|
20
21
|
|
|
@@ -29,7 +30,7 @@ import { runPlan } from './planrun.js';
|
|
|
29
30
|
*/
|
|
30
31
|
async function primitiveSmoke(workdir) {
|
|
31
32
|
try {
|
|
32
|
-
const lc = new LiteCtx({ root: join(workdir,
|
|
33
|
+
const lc = new LiteCtx({ root: join(workdir, SMOKE_STORE) });
|
|
33
34
|
await lc.remember('smoke-known-answer', 'bareloop primitive smoke: the answer is 42', { kind: 'fact' });
|
|
34
35
|
const hits = await lc.recall('bareloop primitive smoke known answer', { kind: 'fact', n: 5, body: true });
|
|
35
36
|
const ok = hits.some((h) => String(h.body ?? '').includes('the answer is 42'));
|
|
@@ -125,6 +126,13 @@ async function primitiveSmoke(workdir) {
|
|
|
125
126
|
* RUN's ("unlimited replanning launders thrash as adaptation"). It also rides onto this
|
|
126
127
|
* run's `job-start` as `priorReplans`, the declared-fold precedent `priorSpentUsd`
|
|
127
128
|
* already sets, so a resume OF a resume folds once. Absent is the cold path.
|
|
129
|
+
* @param {string|null} [opts.resumeBranch] RESUME — the WORK BRANCH the killed attempt
|
|
130
|
+
* created and recorded on its own spine (`readResume`'s `restart.branch`). The branch
|
|
131
|
+
* name is DERIVED from the signed spec and is therefore identical on every leg, so
|
|
132
|
+
* without this the restart would collide with its own predecessor's branch and the
|
|
133
|
+
* collision walk would mint a `-2` beside the work the resume exists to continue. The
|
|
134
|
+
* fold's precedent exactly: the chain's state is DECLARED, never re-derived. Forwarded
|
|
135
|
+
* verbatim to the plan flow; absent is the cold path.
|
|
128
136
|
* @param {boolean} [opts.layerRoot=false] Layer R (within-run ratchet) — shell
|
|
129
137
|
* territory, threaded to the plan flow. Defaults OFF
|
|
130
138
|
* (decided 2026-07-21): fixation is extinct on every current job (F41), so
|
|
@@ -134,10 +142,10 @@ async function primitiveSmoke(workdir) {
|
|
|
134
142
|
* binding.)
|
|
135
143
|
* @returns {Promise<string>} outcome: 'green' | 'already-green' | 'escalated' |
|
|
136
144
|
* 'unapproved-spec' | 'job-red' | 'smoke-red' | 'plan-red' | 'check-red' |
|
|
137
|
-
* 'close-red' | 'close-unsupported' | 'recipe-stale' | 'pricing-red' | 'provider-red' |
|
|
145
|
+
* 'close-red' | 'close-unsupported' | 'recipe-stale' | 'branch-red' | 'pricing-red' | 'provider-red' |
|
|
138
146
|
* 'interpreter-red' | 'cap-halt' | 'wall-halt' | 'step-stalled' | `step-red:<id>`
|
|
139
147
|
*/
|
|
140
|
-
export async function runJob(rawSpec, { approvals, workdir, provider, nativeProvider, providerFor, emit, capRuns = 3, strikeLimit, shellCapUsd = 2, closeTimeoutMs, layerRoot = false, bridge = null, priorSpentUsd = 0, priorSpendComplete = true, priorWallMs = 0, resumeSeed = null, resumeGrades = [], resumeReplans = null }) {
|
|
148
|
+
export async function runJob(rawSpec, { approvals, workdir, provider, nativeProvider, providerFor, emit, capRuns = 3, strikeLimit, shellCapUsd = 2, closeTimeoutMs, layerRoot = false, bridge = null, priorSpentUsd = 0, priorSpendComplete = true, priorWallMs = 0, resumeSeed = null, resumeGrades = [], resumeReplans = null, resumeBranch = null }) {
|
|
141
149
|
// 0. the ledger's counters, declared FIRST so that every job-end — including
|
|
142
150
|
// the pre-token reds below — can state a real figure. An omitted `spentUsd` is
|
|
143
151
|
// not a zero: a consumer reads `undefined` and either crashes or launders it
|
|
@@ -275,7 +283,7 @@ export async function runJob(rawSpec, { approvals, workdir, provider, nativeProv
|
|
|
275
283
|
// accounts it natively (F12) and the job-end money contract is unchanged.
|
|
276
284
|
{
|
|
277
285
|
const outcome = await runPlan(job, {
|
|
278
|
-
workdir, provider, nativeProvider, providerFor, emit: meter, capRuns, ...(strikeLimit !== undefined ? { strikeLimit } : {}), closeTimeoutMs, layerRoot, bridge, priorWallMs, resumeSeed, resumeGrades, resumeReplans,
|
|
286
|
+
workdir, provider, nativeProvider, providerFor, emit: meter, capRuns, ...(strikeLimit !== undefined ? { strikeLimit } : {}), closeTimeoutMs, layerRoot, bridge, priorWallMs, resumeSeed, resumeGrades, resumeReplans, resumeBranch,
|
|
279
287
|
remainingUsd: () => Math.min(shellCapUsd, job.budgetUsd - spentUsd),
|
|
280
288
|
isUnpriced: () => unpriced, // F6: let the plan flow bail in-flight, not just after it returns
|
|
281
289
|
spendComplete, // …and let its money-halt readout say whether the remaining it quotes is exact
|
package/src/text.js
CHANGED
|
@@ -4,6 +4,11 @@
|
|
|
4
4
|
// are two instruments (F2/F21, the adaptlearn instrument caveat). It replaced
|
|
5
5
|
// N0's stripFences when the fence-robust upgrade landed at N2. priceOf is the
|
|
6
6
|
// ONE spelling of the honest-null cost read for the same reason (F6).
|
|
7
|
+
// `scrubRaw` is the third: the ONE way a model's raw output becomes a persisted
|
|
8
|
+
// record, for the same reason again — a second writer that forgets the scrub is
|
|
9
|
+
// a leak on the very trail the scrub exists to protect.
|
|
10
|
+
|
|
11
|
+
import { redactSecrets } from './validate.js';
|
|
7
12
|
|
|
8
13
|
// A fence counts as the artifact's WRAPPER only when it opens within this many
|
|
9
14
|
// lines of the response — the chatty-preamble shape ("Here's the fix:\n```…").
|
|
@@ -64,3 +69,197 @@ export function priceOf(r) {
|
|
|
64
69
|
unpricedRounds: r?.metrics?.unpricedRounds ?? 0,
|
|
65
70
|
};
|
|
66
71
|
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* The F6 tally over a list of PRICED CALLS — `priceOf`'s per-call read, summed.
|
|
75
|
+
*
|
|
76
|
+
* `knownUsd` carries the priced half explicitly and `costUsd` is the honest
|
|
77
|
+
* total: null the moment anything went unpriced, never the priced half wearing a
|
|
78
|
+
* complete coat. An EMPTY list is a complete $0 and not an unknown — nothing
|
|
79
|
+
* spent is a number somebody can act on.
|
|
80
|
+
* @param {{costUsd: number|null, unpricedRounds?: number}[]|null|undefined} entries
|
|
81
|
+
* @returns {{costUsd: number|null, knownUsd: number, spendComplete: boolean,
|
|
82
|
+
* nullCostCalls: number, unpricedRounds: number}}
|
|
83
|
+
*/
|
|
84
|
+
export function tallyCalls(entries) {
|
|
85
|
+
const list = Array.isArray(entries) ? entries : [];
|
|
86
|
+
const known = list.reduce((a, e) => a + (typeof e.costUsd === 'number' ? e.costUsd : 0), 0);
|
|
87
|
+
const nullCostCalls = list.filter((e) => e.costUsd === null).length;
|
|
88
|
+
const unpricedRounds = list.reduce((a, e) => a + (e.unpricedRounds || 0), 0);
|
|
89
|
+
const spendComplete = nullCostCalls === 0 && unpricedRounds === 0;
|
|
90
|
+
return {
|
|
91
|
+
costUsd: spendComplete ? Number(known.toFixed(6)) : null,
|
|
92
|
+
knownUsd: Number(known.toFixed(6)),
|
|
93
|
+
spendComplete,
|
|
94
|
+
nullCostCalls,
|
|
95
|
+
unpricedRounds,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* THE ONE MONEY-CEILING PREDICATE, for the same reason `priceOf` above is one:
|
|
101
|
+
* the authoring pipeline meters two populations of paid call in two different
|
|
102
|
+
* accumulators (the survey's `calls[]`, the declaration loop's cost book), and
|
|
103
|
+
* two hand-spelled answers to "am I over the ceiling?" is two instruments.
|
|
104
|
+
*
|
|
105
|
+
* It is asked BETWEEN metered calls and never inside one. A cap that binds
|
|
106
|
+
* mid-call kills the row before it can be graded — F45 measured that as 3 of 7
|
|
107
|
+
* launches lost and $5.82 unreadable.
|
|
108
|
+
*
|
|
109
|
+
* Three answers, and the order between the last two is load-bearing:
|
|
110
|
+
* - NO CEILING → null, always. An absent cap is UNBOUNDED, and unbounded is a
|
|
111
|
+
* visible operator choice, never a defaulted number nobody set (the
|
|
112
|
+
* `maxWallMs` precedent). There is nothing to enforce, so unpriced spend
|
|
113
|
+
* here is merely reported.
|
|
114
|
+
* - KNOWN SPEND AT OR OVER THE CEILING → `cap-halt`, even when the total is
|
|
115
|
+
* unknown. The breach is CERTAIN on the priced half alone — whatever the
|
|
116
|
+
* unpriced remainder turns out to be, it can only be larger — so naming this
|
|
117
|
+
* `pricing-red` would send the operator to fix a meter when the fact is that
|
|
118
|
+
* the money is gone. At-or-over rather than over: the next call is the one
|
|
119
|
+
* that breaches, and it is the one being decided.
|
|
120
|
+
* - SPEND THAT CANNOT BE KNOWN → `pricing-red`. `?? 0` launders unknown into
|
|
121
|
+
* $0 (F6) and a ceiling enforced against a laundered floor is not enforced
|
|
122
|
+
* at all. If the spend cannot be seen the cap cannot govern it, and that is
|
|
123
|
+
* its own stop rather than a silent pass.
|
|
124
|
+
*
|
|
125
|
+
* A MALFORMED ceiling is the one input it refuses rather than answers: `null` and
|
|
126
|
+
* an absent field mean UNBOUNDED, and they are the ONLY way to get there. Anything
|
|
127
|
+
* else that is not a finite number (`'2.50'`, `NaN`, `Infinity`) reading as
|
|
128
|
+
* unbounded would be F6's laundering in its ceiling form — a caller who set a cap
|
|
129
|
+
* getting a paid pipeline with nothing enforcing it, which is the exact collapse
|
|
130
|
+
* the ceiling exists to prevent, wearing the caller's own typo (PRD v1.62). The
|
|
131
|
+
* CLI's `parseCeiling` guards the FLAG; this guards the LIBRARY, and `authorClose`
|
|
132
|
+
* / `runAuthorScout` / `authorCloseForJob` are documented adopter seams. It THROWS
|
|
133
|
+
* rather than clamping, unlike the attempt caps beside it, because those axes have
|
|
134
|
+
* a safe direction to clamp toward and this one does not: every candidate default
|
|
135
|
+
* is either a number nobody set or the unbounded run the caller just declined.
|
|
136
|
+
* @param {{ceilingUsd: number|null|undefined, knownUsd: number, spendComplete: boolean}} o
|
|
137
|
+
* @returns {'cap-halt'|'pricing-red'|null}
|
|
138
|
+
* @throws {Error} when the ceiling is neither absent nor a finite number
|
|
139
|
+
*/
|
|
140
|
+
export function capStop({ ceilingUsd, knownUsd, spendComplete }) {
|
|
141
|
+
if (ceilingUsd === null || ceilingUsd === undefined) return null;
|
|
142
|
+
if (typeof ceilingUsd !== 'number' || !Number.isFinite(ceilingUsd)) {
|
|
143
|
+
throw new Error(`capStop: a money ceiling must be a finite number of dollars or null — got ${typeof ceilingUsd} `
|
|
144
|
+
+ `${String(ceilingUsd)}. A malformed ceiling is an ERROR, never a silent fall back to UNBOUNDED: omit it, or `
|
|
145
|
+
+ 'pass an explicit null, to run unbounded — that is a stated operator choice and never a typo arrived at');
|
|
146
|
+
}
|
|
147
|
+
if (knownUsd >= ceilingUsd) return 'cap-halt';
|
|
148
|
+
if (!spendComplete) return 'pricing-red';
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// ── THE RAW MODEL OUTPUT, AS A PERSISTED RECORD ─────────────────────────────
|
|
153
|
+
//
|
|
154
|
+
// Run mslhn707 died on a survey that broke JSON at position 1339 and left NO
|
|
155
|
+
// copy of what the model actually said: the red named the position, the process
|
|
156
|
+
// exited, and the only artifact that could have decided whether the malformation
|
|
157
|
+
// is recurring (and therefore whether the scout must move to a structure-forced
|
|
158
|
+
// tool call) went with it. A raw that is not written down is a diagnosis nobody
|
|
159
|
+
// can make twice.
|
|
160
|
+
//
|
|
161
|
+
// Everything a persisted raw needs is HERE rather than at each writer, and the
|
|
162
|
+
// reason is the `scrubRed` reason one layer up: the scrub, the bound and the
|
|
163
|
+
// announcement are properties of PERSISTING A RAW, not of any one caller
|
|
164
|
+
// remembering them. A future writer that reaches for a raw reaches for this.
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* The ceiling on ONE persisted raw. Sized to the survey's own prompt-ingredient
|
|
168
|
+
* cap (`AUTHOR_SCOUT_BLOB_MAX`, 8000) rather than picked: the largest raw this
|
|
169
|
+
* repository produces is a survey, the archive's real surveys ran 5991-8056
|
|
170
|
+
* bytes, and a record that cannot hold one whole specimen cannot answer the
|
|
171
|
+
* question it exists for. The two constants are deliberately independent
|
|
172
|
+
* numbers — one bounds a PROMPT, this one bounds a RECORD.
|
|
173
|
+
*/
|
|
174
|
+
export const RAW_PERSIST_MAX = 8000;
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* The announced-trim marker for a persisted raw. A NEW spelling rather than a
|
|
178
|
+
* reuse of `GAP_TRIM_MARKER` ('gap trimmed:'), and that is a decision, not
|
|
179
|
+
* decoration: `src/root.js` READS `GAP_TRIM_MARKER` to mean *this gap was cut,
|
|
180
|
+
* so Layer R must not compare it*, so a second producer of those bytes in a
|
|
181
|
+
* different population is exactly the F90 hazard — a reader must know every
|
|
182
|
+
* producer of its marker. Nothing reads this one yet; it exists so the trim
|
|
183
|
+
* announces itself (F28) and so a future reader has an unambiguous key.
|
|
184
|
+
*/
|
|
185
|
+
export const RAW_TRIM_MARKER = 'raw trimmed:';
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* ONE raw model emission, ready to persist.
|
|
189
|
+
*
|
|
190
|
+
* Three things happen here and NONE of them is optional at a call site:
|
|
191
|
+
* - the text goes through the ONE redactor (`redactSecrets`, over the ONE
|
|
192
|
+
* `SECRET_PATTERNS` inventory). A raw is the single most likely record in
|
|
193
|
+
* this system to carry a live credential: it is whatever a model read out
|
|
194
|
+
* of a repository, verbatim, and it lands in a file that outlives the run;
|
|
195
|
+
* - it is BOUNDED, and the bound ANNOUNCES itself with the full size (F28 —
|
|
196
|
+
* a bound eats detail, never evidence). The cut is decoded rather than
|
|
197
|
+
* sliced so a multi-byte character is never split in half;
|
|
198
|
+
* - it carries its own address: which call produced it (`label`, the same
|
|
199
|
+
* spelling the cost book meters under) and which attempt (`attempt`), plus
|
|
200
|
+
* the typed failure that describes it when there is one.
|
|
201
|
+
*
|
|
202
|
+
* `cause`/`reason` are `null` when the emission was fine — an ABSENT failure is
|
|
203
|
+
* absent, never an empty string that reads like a diagnosis nobody wrote.
|
|
204
|
+
* @param {{label: string, attempt: number, text: unknown, cause?: string|null,
|
|
205
|
+
* reason?: string|null, cap?: number}} o
|
|
206
|
+
* @returns {{label: string, attempt: number, bytes: number, trimmed: boolean,
|
|
207
|
+
* text: string, cause: string|null, reason: string|null}}
|
|
208
|
+
*/
|
|
209
|
+
export function scrubRaw({ label, attempt, text, cause = null, reason = null, cap = RAW_PERSIST_MAX }) {
|
|
210
|
+
const scrubbed = redactSecrets(String(text ?? ''));
|
|
211
|
+
const buf = Buffer.from(scrubbed, 'utf8');
|
|
212
|
+
const trimmed = buf.length > cap;
|
|
213
|
+
// walk the cut BACK off any continuation byte (0b10xxxxxx): a byte-wise slice
|
|
214
|
+
// through a multi-byte character renders as a replacement char, and a record
|
|
215
|
+
// that garbles the evidence is a worse instrument than one that keeps a byte
|
|
216
|
+
// less of it
|
|
217
|
+
let end = cap;
|
|
218
|
+
while (trimmed && end > 0 && (buf[end] & 0xC0) === 0x80) end -= 1;
|
|
219
|
+
const kept = trimmed ? buf.subarray(0, end).toString('utf8') : scrubbed;
|
|
220
|
+
// the two diagnosis fields are rendered by `stampRaw` below, never here: the
|
|
221
|
+
// writer that stamps them LATER must go through the same one place, or the
|
|
222
|
+
// scrub becomes a property of when you knew the verdict
|
|
223
|
+
return stampRaw({
|
|
224
|
+
label,
|
|
225
|
+
attempt,
|
|
226
|
+
// the FULL size, always — the number a trim is measured against
|
|
227
|
+
bytes: buf.length,
|
|
228
|
+
trimmed,
|
|
229
|
+
// WITHHELD is measured off `end`, not off `cap`: the multi-byte walk-back above
|
|
230
|
+
// can cut up to three bytes short of the cap, and announcing `length - cap` would
|
|
231
|
+
// under-report the withheld bytes by exactly that much. An announced bound that
|
|
232
|
+
// states a number it did not withhold is the announcement failing at its one job.
|
|
233
|
+
text: trimmed
|
|
234
|
+
? `${kept}\n[${RAW_TRIM_MARKER} ${buf.length - end} of ${buf.length} bytes withheld — the cap is ${cap} per raw]`
|
|
235
|
+
: kept,
|
|
236
|
+
cause: null,
|
|
237
|
+
reason: null,
|
|
238
|
+
}, { cause, reason });
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Stamp a persisted raw with the verdict that DESCRIBES it — through the same
|
|
243
|
+
* boundary the raw's own text rode.
|
|
244
|
+
*
|
|
245
|
+
* It exists because the verdict is not known when the raw is created: a writer
|
|
246
|
+
* records the emission the moment the call returns (so a metered call can never
|
|
247
|
+
* be missing its output), and only classifies it afterwards. The obvious
|
|
248
|
+
* shape — assigning `cause`/`reason` onto the stored object at that later
|
|
249
|
+
* point — writes an UNSCRUBBED string into a record that is already past the
|
|
250
|
+
* one redactor, and `reason` is the worst field to do it to: it quotes the
|
|
251
|
+
* transport's own error prose, which nobody in this repo wrote and nobody
|
|
252
|
+
* bounds. So the stamp is a function, not an assignment, and `scrubRaw` renders
|
|
253
|
+
* its own two fields through it: ONE place decides how a diagnosis becomes
|
|
254
|
+
* persistable, exactly as ONE place decides it for the text.
|
|
255
|
+
*
|
|
256
|
+
* `cause` is enumerated (`SURVEY_CAUSES`) and passes through untouched; a mask
|
|
257
|
+
* over a closed set would only hide a bug in the set.
|
|
258
|
+
* @template {{cause: string|null, reason: string|null}} T
|
|
259
|
+
* @param {T} raw @param {{cause?: string|null, reason?: string|null}} o @returns {T}
|
|
260
|
+
*/
|
|
261
|
+
export function stampRaw(raw, { cause = null, reason = null }) {
|
|
262
|
+
raw.cause = cause ?? null;
|
|
263
|
+
raw.reason = reason === null || reason === undefined ? null : redactSecrets(reason);
|
|
264
|
+
return raw;
|
|
265
|
+
}
|
package/src/tools.js
CHANGED
|
@@ -31,6 +31,7 @@ import { extractArtifact, priceOf } from './text.js';
|
|
|
31
31
|
// caller can forget to pass, and this leak has to be inexpressible, not optional.
|
|
32
32
|
// Same ONE inventory the spine channel and the close output ride through.
|
|
33
33
|
import { redactSecrets } from './validate.js';
|
|
34
|
+
import { GATE_AUDIT_FILE, ARBITER_BOOK_STORES } from './kinds.js';
|
|
34
35
|
|
|
35
36
|
/** @typedef {{body?: string|null, text?: string|null}} RecallHit litectx recall hit — body present only with `{body: true}` */
|
|
36
37
|
|
|
@@ -45,6 +46,16 @@ const PERSONA = 'You are a senior engineer. Reply with ONLY the complete content
|
|
|
45
46
|
// cheap, wrong attempt the rational move — which is exactly what the loop wants.
|
|
46
47
|
export const PERSONA_TOOLS = 'You are a senior engineer working in a repository through file tools. '
|
|
47
48
|
+ 'ALWAYS use absolute paths — relative paths resolve against the process, not the repository, and will be denied. '
|
|
49
|
+
// F98's residual: the fence ALREADY denies these (planrun's Gate deny list), and the
|
|
50
|
+
// spine is written outside the patient entirely — but nothing ever SAID so, and a
|
|
51
|
+
// denial is a wall while a stated law is a map. The way a worker learned the rule was
|
|
52
|
+
// by spending rounds of a bounded attempt probing bookkeeping files, and the rounds a
|
|
53
|
+
// probe costs are rounds the fix does not get. Registered here, beside the other fence
|
|
54
|
+
// fact a worker cannot infer, so it renders for every worker on every grant.
|
|
55
|
+
// the names are SPELLED FROM the one home (`ARBITER_BOOK_STORES`) so the stated
|
|
56
|
+
// law can never drift from the fence that enforces it
|
|
57
|
+
+ `The repository root also holds the arbiter's own books — ${GATE_AUDIT_FILE}, ${ARBITER_BOOK_STORES.join(', ')}, and this run's spine. `
|
|
58
|
+
+ 'They are ALWAYS denied and record how you are being judged, never anything about the task: never read them, and never try to route around the denial. '
|
|
48
59
|
+ 'You are ONE attempt inside an automated loop: when you finish, a test suite runs and, if it still fails, you are called again with its output. '
|
|
49
60
|
+ 'So do not try to be certain before acting. Read only what you need to form your best hypothesis, make the change with the write tool, and stop. '
|
|
50
61
|
+ 'A wrong cheap attempt is corrected by the next round; exhaustive reading is not — every file you read is re-sent on every later round and the run has a hard budget it can exhaust before you ever write. '
|