bareloop 0.43.0 → 0.45.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.
Files changed (64) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/bareloop.context.md +53 -13
  3. package/package.json +2 -2
  4. package/src/authorflow.js +27 -12
  5. package/src/authoring.js +1 -1
  6. package/src/authorjob.js +52 -16
  7. package/src/authorreadout.js +12 -6
  8. package/src/authorrun.js +8 -2
  9. package/src/declaredclose.js +4 -1
  10. package/src/draftspend.js +20 -2
  11. package/src/facts.js +54 -9
  12. package/src/factwords.js +58 -6
  13. package/src/fitcheck.js +166 -0
  14. package/src/inputlook.js +38 -3
  15. package/src/job.js +1 -1
  16. package/src/kinds.js +5 -0
  17. package/src/pagedropwords.js +110 -0
  18. package/src/pagedry.js +125 -0
  19. package/src/pagemap.js +269 -0
  20. package/src/pagerules.js +318 -0
  21. package/src/panel/authorroutes.js +34 -30
  22. package/src/panel/authorsession.js +66 -11
  23. package/src/panel/index.html +469 -65
  24. package/src/panel/jobplan.js +14 -7
  25. package/src/panel/runroutes.js +46 -22
  26. package/src/panel/server.js +792 -28
  27. package/src/panel/status.js +33 -0
  28. package/src/panelmoney.js +29 -0
  29. package/src/planrun.js +53 -4
  30. package/src/promptregisters.js +8 -0
  31. package/src/replay.js +118 -7
  32. package/src/rubricstep.js +157 -15
  33. package/src/runlist.js +2 -1
  34. package/src/schedulewords.js +6 -3
  35. package/src/source.js +18 -0
  36. package/src/thinking.js +63 -2
  37. package/src/userrun.js +28 -14
  38. package/types/authorflow.d.ts +42 -6
  39. package/types/authorjob.d.ts +20 -4
  40. package/types/declaredclose.d.ts +2 -0
  41. package/types/draftspend.d.ts +27 -1
  42. package/types/facts.d.ts +15 -0
  43. package/types/factwords.d.ts +26 -3
  44. package/types/fitcheck.d.ts +113 -0
  45. package/types/inputlook.d.ts +15 -0
  46. package/types/kinds.d.ts +5 -0
  47. package/types/pagedropwords.d.ts +39 -0
  48. package/types/pagedry.d.ts +37 -0
  49. package/types/pagemap.d.ts +154 -0
  50. package/types/pagerules.d.ts +86 -0
  51. package/types/panel/authorroutes.d.ts +17 -14
  52. package/types/panel/authorsession.d.ts +4 -2
  53. package/types/panel/jobplan.d.ts +5 -2
  54. package/types/panel/runroutes.d.ts +2 -2
  55. package/types/panel/server.d.ts +156 -2
  56. package/types/panel/status.d.ts +51 -0
  57. package/types/panelmoney.d.ts +5 -0
  58. package/types/replay.d.ts +21 -1
  59. package/types/rubricstep.d.ts +62 -1
  60. package/types/runlist.d.ts +4 -1
  61. package/types/schedulewords.d.ts +4 -2
  62. package/types/source.d.ts +11 -0
  63. package/types/thinking.d.ts +39 -0
  64. package/types/userrun.d.ts +4 -0
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Which job line, and which `~` rule under it, a map fact carries out. The map writes `from` as "line N", "line N rule M", "rule M" or
3
+ * "job line"; anything else is the first job line.
4
+ * @param {unknown} from @param {{n: number, text?: string, rule?: string, rules?: string[]}[]} jobLines
5
+ * @returns {{line: number, rule?: number}}
6
+ */
7
+ export function tieOf(from: unknown, jobLines: {
8
+ n: number;
9
+ text?: string;
10
+ rule?: string;
11
+ rules?: string[];
12
+ }[]): {
13
+ line: number;
14
+ rule?: number;
15
+ };
16
+ /**
17
+ * Is this a legal `reading` for a fact of this shape? Only a time or money fact carries one, with the op set and the bound types of a signed
18
+ * fact rule (src/facts.js): no other key, no missing bound. A malformed one makes the whole map answer malformed (no repair).
19
+ * @param {unknown} shape @param {unknown} r @returns {boolean}
20
+ */
21
+ export function validReading(shape: unknown, r: unknown): boolean;
22
+ /**
23
+ * Apply the rules to the AI's map over one saved page. Returns the kept facts and, for everything a rule removed, one `dropped` entry.
24
+ * The order of the rules is the POC's (apply-v3.mjs): found-on-page, then the rules that read the page. A place is read as a phrase.
25
+ * @param {string} text the frozen page text @param {PageMap} mapping @param {{n: number, text?: string, rule?: string, rules?: string[]}[]} jobLines
26
+ * @returns {{facts: KeptFact[], dropped: Dropped[]}}
27
+ */
28
+ export function applyPageRules(text: string, mapping: PageMap, jobLines: {
29
+ n: number;
30
+ text?: string;
31
+ rule?: string;
32
+ rules?: string[];
33
+ }[]): {
34
+ facts: KeptFact[];
35
+ dropped: Dropped[];
36
+ };
37
+ export function linesOf(text: string): string[];
38
+ /**
39
+ * what a `~` rule means as a limit (PRD v1.107 Part 5b): the same shape as a signed fact rule
40
+ */
41
+ export type Reading = {
42
+ op: string;
43
+ value?: string | number;
44
+ lo?: string | number;
45
+ hi?: string | number;
46
+ };
47
+ /**
48
+ * the AI's map, one fact
49
+ */
50
+ export type MapFact = {
51
+ name: string;
52
+ from?: string;
53
+ shape: string;
54
+ words?: string[];
55
+ currency?: string[];
56
+ unit?: string[];
57
+ reading?: Reading;
58
+ };
59
+ export type PageMap = {
60
+ facts: MapFact[];
61
+ };
62
+ /**
63
+ * `words` = what the page proves: the labels of a money/number/time fact, or the alternative phrases of a phrase fact (a place is a phrase)
64
+ */
65
+ export type KeptFact = {
66
+ line: number;
67
+ name: string;
68
+ shape: string;
69
+ from: string;
70
+ words: string[];
71
+ unit?: string[];
72
+ currency?: string[];
73
+ forRule?: number;
74
+ reading?: Reading;
75
+ };
76
+ /**
77
+ * `other` = what the drop is compared to: the first label (R1), the primary form (R1L), the fact it would have repeated (S1)
78
+ */
79
+ export type Dropped = {
80
+ line: number;
81
+ rule?: number;
82
+ fact: string;
83
+ word?: string;
84
+ reason: string;
85
+ other?: string;
86
+ };
@@ -33,6 +33,19 @@ export function checkHumanGuard(req: import("node:http").IncomingMessage, { toke
33
33
  ok: false;
34
34
  reason: string;
35
35
  };
36
+ /**
37
+ * The one-at-a-time authoring session store (build spec: "one authoring
38
+ * session at a time — a second Start is refused while one is live"). A
39
+ * session is "live" from creation until its phase settles into a terminal
40
+ * one; a terminal session is kept (so its chat/hash stay readable) but no
41
+ * longer counts against the one-at-a-time rule.
42
+ */
43
+ /**
44
+ * PRD v1.106 Part 5 Q2: the "fetching the page..." step is done once the run's own refetch has written `refrozenAt` (source.json, written last).
45
+ * Settled when the state is read, so nothing polls and the step never stays running after the fetch.
46
+ * @param {any} state
47
+ */
48
+ export function settleFetchingStep(state: any): void;
36
49
  /**
37
50
  * Builds this file's own request handler, holding its session store in a
38
51
  * closure (one per `createPanelServer` call, exactly like `handleRequest`'s
@@ -79,7 +92,6 @@ export function createAuthorRoutes(opts: {
79
92
  workflowKey: string;
80
93
  } | null, inputs: import("../jobblock.js").JobInput[], o?: {
81
94
  headless?: boolean;
82
- filesJob?: boolean;
83
95
  }) => Promise<{
84
96
  ok: true;
85
97
  session: ReturnType<typeof createSession>;
@@ -94,19 +106,6 @@ export function createAuthorRoutes(opts: {
94
106
  /** a headless session the starter has removed from disk leaves its map too @param {string} id */
95
107
  forgetHeadless: (id: string) => void;
96
108
  };
97
- /**
98
- * The panel's OWN 2-decimal money render for a chat line — hamr's ruling
99
- * 2026-09-28 (2nd addendum, panel money 2-decimals): a positive amount below
100
- * a cent reads `<$0.01`, never `$0.00` (a real cost must never read as
101
- * free). This is the server-side (Node, chat-text) sibling of
102
- * `src/panel/index.html`'s own inline `panelMoney` — the two can't share
103
- * code (one runs in the browser as inlined script, the other in Node), the
104
- * same duplication `src/replay.js`'s `money()`/`index.html`'s `money()`
105
- * already carry, so both apply the identical rule rather than drift.
106
- * @param {number} n a non-negative dollar amount
107
- * @returns {string}
108
- */
109
- export function panelMoney2(n: number): string;
110
109
  /**
111
110
  * THE ONLY function in this file (or anywhere in the panel) that spawns a
112
111
  * run. Refuses unless the session is `prepared` AND the hash the request
@@ -136,5 +135,9 @@ export function signRun(session: ReturnType<typeof createSession>, claimedHash:
136
135
  export const OLD_JOB_REUSE_WORDS: "This run was made by an older bareloop. Its saved job doesn't pass today's checks, so it can't be reused. Start a new job instead.";
137
136
  export const OLD_JOB_RESUME_WORDS: "This run was made by an older bareloop. Its saved job doesn't pass today's checks, so it can't be resumed. Use Reuse workflow on a newer run, or start a new job.";
138
137
  export const OLD_JOB_ACCEPT_WORDS: "This run was made by an older bareloop. Its saved job doesn't pass today's checks, so it can't be accepted. Use Reuse workflow on a newer run, or start a new job.";
138
+ export const OLD_JOB_RERUN_WORDS: "This run was made by an older bareloop. Its saved job doesn't pass today's checks, so it can't be rerun. Use Reuse workflow on a newer run, or start a new job.";
139
+ export const OLD_JOB_PAUSE_WORDS: "This run was made by an older bareloop. Its saved job doesn't pass today's checks, so it can't be paused from here. Use Reuse workflow on a newer run, or start a new job.";
140
+ export { panelMoney2 };
139
141
  import { spawn as realSpawn } from 'node:child_process';
140
142
  import { createSession } from './authorsession.js';
143
+ import { panelMoney2 } from '../panelmoney.js';
@@ -14,7 +14,7 @@ export function advanceSteps(steps: StepLine[], id: string, detail?: string, lab
14
14
  * The confirm turn's plan as a chat message (P7): a STRUCTURED `{role:'bot', kind:'plan', plan}` the page draws with the
15
15
  * same renderer the Job tab uses — each numbered job line, its `~` rule, its drafted checks folded; the AI's own "not
16
16
  * checked"; the always-on guards. `questions` (what the plan still asks) ride inside the plan. One owner of the shape.
17
- * @param {{goal?: string, checks?: string[], checkItems?: {text: string, fromLine: number[]|null}[], notChecked?: string[], questions?: string[], protections?: string[]}} p the confirm turn's plan
17
+ * @param {{goal?: string, checks?: string[], checkItems?: {text: string, fromLine: number[]|null}[], notChecked?: string[], questions?: string[], protections?: string[], signedFacts?: string[]}} p the confirm turn's plan
18
18
  * @param {{n: number, text: string, rule: string}[]|null} jobLines
19
19
  * @returns {{role: 'bot', kind: 'plan', text: string, plan: ReturnType<typeof planFromConfirm> & {goal: string, questions: string[]}}}
20
20
  */
@@ -28,6 +28,7 @@ export function planMessage(p: {
28
28
  notChecked?: string[];
29
29
  questions?: string[];
30
30
  protections?: string[];
31
+ signedFacts?: string[];
31
32
  }, jobLines: {
32
33
  n: number;
33
34
  text: string;
@@ -165,7 +166,7 @@ export function validateJobCard(card: any, opts?: {
165
166
  * @param {any} card the validated job card (see {@link validateJobCard})
166
167
  * @param {{env?: Record<string,string|undefined>, sessionsRoot?: string, home?: string, timeoutMs?: number,
167
168
  * scout?: any, generate?: Function, confirmGenerate?: Function, authorFn?: Function,
168
- * prepareSigningFn?: Function, npmCiFn?: Function, lookFn?: Function|null, fetchUrl?: (url: string) => Promise<{ok: true, text: string, url: string}|{ok: false, error: string}>, draftThinking?: "on"|"off", reuse?: {spec: any, workflowKey: string}}} [deps] `scout`..`prepareSigningFn` are
169
+ * prepareSigningFn?: Function, npmCiFn?: Function, lookFn?: Function|null, fitFn?: Function|null, fetchUrl?: (url: string) => Promise<{ok: true, text: string, url: string}|{ok: false, error: string}>, draftThinking?: "on"|"off", reuse?: {spec: any, workflowKey: string}}} [deps] `scout`..`prepareSigningFn` are
169
170
  * TEST SEAMS ONLY — see the note just above where each is read, below. `reuse` (Reuse workflow) is NOT a seam: the
170
171
  * caller (`/api/author/start`) sets it only when the server's own same-job rule held.
171
172
  * @returns {any} the session object
@@ -182,6 +183,7 @@ export function createSession(card: any, deps?: {
182
183
  prepareSigningFn?: Function;
183
184
  npmCiFn?: Function;
184
185
  lookFn?: Function | null;
186
+ fitFn?: Function | null;
185
187
  fetchUrl?: (url: string) => Promise<{
186
188
  ok: true;
187
189
  text: string;
@@ -1,6 +1,6 @@
1
1
  /** @typedef {{name: string, cls: 'machine check'|'judge'|null}} PlanCheck */
2
2
  /** @typedef {{n: number, text: string, rule: string, rules: string[], checks: PlanCheck[], refused: string[]}} PlanLine */
3
- /** @typedef {{lines: PlanLine[], loose: PlanCheck[], notChecked: string[], alwaysOn: string[], older: boolean, goal?: string}} JobPlan */
3
+ /** @typedef {{lines: PlanLine[], loose: PlanCheck[], notChecked: string[], readNotes: string[], alwaysOn: string[], older: boolean, goal?: string}} JobPlan */
4
4
  /**
5
5
  * `machine check` for a kind a command decides, `judge` for one that needs judgement; `null` for a stage whose kind is
6
6
  * unknown (a hand-written command close has no kind: it is a command, so a machine check).
@@ -27,7 +27,8 @@ export function planFromSpec(spec: any): JobPlan;
27
27
  * The plan of the confirm turn, before anything is drafted: the person's own job lines, the model's checks under the
28
28
  * line each names, its own "not checked" words, and the real protections (code-owned) as "Always checked".
29
29
  * @param {{n: number, text: string, rule: string, rules?: string[]}[]|null|undefined} jobLines
30
- * @param {{checkItems?: {text: string, fromLine: number[]|null}[], checks?: string[], notChecked?: string[], protections?: string[]}} plan
30
+ * @param {{checkItems?: {text: string, fromLine: number[]|null}[], checks?: string[], notChecked?: string[], protections?: string[], signedFacts?: string[]}} plan
31
+ * `signedFacts` = the names of the facts the look signs (so an S1 copy of a signed fact reads as a note, not Not checked)
31
32
  * @returns {JobPlan}
32
33
  */
33
34
  export function planFromConfirm(jobLines: {
@@ -43,6 +44,7 @@ export function planFromConfirm(jobLines: {
43
44
  checks?: string[];
44
45
  notChecked?: string[];
45
46
  protections?: string[];
47
+ signedFacts?: string[];
46
48
  }): JobPlan;
47
49
  export type PlanCheck = {
48
50
  name: string;
@@ -60,6 +62,7 @@ export type JobPlan = {
60
62
  lines: PlanLine[];
61
63
  loose: PlanCheck[];
62
64
  notChecked: string[];
65
+ readNotes: string[];
63
66
  alwaysOn: string[];
64
67
  older: boolean;
65
68
  goal?: string;
@@ -4,12 +4,12 @@
4
4
  * recorded...`, `CHECKPOINT EXPIRED — ...`); anything else (validator reds, exit statuses, stack traces, argv) becomes ONE fixed
5
5
  * sentence naming the log. The one owner for the Resume and Accept routes.
6
6
  * @param {string} tail the log tail
7
- * @param {'resume'|'accept'} kind
7
+ * @param {'resume'|'accept'|'rerun'|'pause'} kind
8
8
  * @param {string} logFile
9
9
  * @param {(stage: string) => string|null} [questionOf] stage name to the panel's plain question (`null` = none)
10
10
  * @returns {string}
11
11
  */
12
- export function engineRefusalWords(tail: string, kind: "resume" | "accept", logFile: string, questionOf?: (stage: string) => string | null): string;
12
+ export function engineRefusalWords(tail: string, kind: "resume" | "accept" | "rerun" | "pause", logFile: string, questionOf?: (stage: string) => string | null): string;
13
13
  /**
14
14
  * The run routes' handler. `getResumeContext(runid)` is the server's own reader (the same
15
15
  * `resumePlanFor` the Ended block uses, so the page never offers what this refuses):
@@ -303,6 +303,70 @@ export function getAcceptContext(runid: string, opts?: {
303
303
  specPath: string | null;
304
304
  specHash: string | null;
305
305
  } | null;
306
+ /**
307
+ * The files a run delivered: its own `destination-written` spine records (userrun.js, on a minted green only), one per path, the last record of a
308
+ * path winning. This is the ONLY source of a delivered path anywhere in the panel — no request ever names one.
309
+ * @param {any[]} records
310
+ * @returns {{path: string, bytes: number|null, sha256: string|null}[]}
311
+ */
312
+ export function deliveredOf(records: any[]): {
313
+ path: string;
314
+ bytes: number | null;
315
+ sha256: string | null;
316
+ }[];
317
+ /**
318
+ * `GET /api/runs/:runid/result` — the Result tab: for each rubric job line, its words and `~` rules from the signed spec, the facts it requires
319
+ * (worded by {@link factSentence}), and — read from the run's own delivered step file, sha256-checked against its `destination-written`
320
+ * record — one row per result with each fact's cell. `passed` is read from the run's own `check-run` record; the tab never re-decides. A red run
321
+ * delivers nothing, so it has no rows: it carries the check's own first gap instead. `null` = no such run or no result tab for it.
322
+ * @param {string} runid @param {{ home?: string }} opts
323
+ */
324
+ export function getRunResult(runid: string, opts?: {
325
+ home?: string;
326
+ sessionsRoot?: string;
327
+ }): {
328
+ runid: string;
329
+ lines: any[];
330
+ pages: string[];
331
+ pageNote: string | null;
332
+ notChecked: string[];
333
+ readNotes: string[];
334
+ door: {
335
+ head: "waiting for you";
336
+ ask: string;
337
+ };
338
+ delivered: {
339
+ i: number;
340
+ path: string;
341
+ name: string;
342
+ bytes: number | null;
343
+ state: "ok" | "changed" | "gone";
344
+ words: string | null;
345
+ }[];
346
+ gap: {
347
+ line: string;
348
+ more: number;
349
+ moreText: string | null;
350
+ } | null;
351
+ } | null;
352
+ /**
353
+ * `GET /api/runs/:runid/download?i=<n>` — delivered file `n` (an index into the run's own `destination-written` list), or why not. The path is
354
+ * the record's, never the request's; the sha256 must still match the record.
355
+ * @param {string} runid @param {unknown} index @param {{ home?: string }} opts
356
+ * @returns {{ok: true, name: string, type: string, buf: Buffer}|{ok: false, code: number, error: string}}
357
+ */
358
+ export function getRunDownload(runid: string, index: unknown, opts?: {
359
+ home?: string;
360
+ }): {
361
+ ok: true;
362
+ name: string;
363
+ type: string;
364
+ buf: Buffer;
365
+ } | {
366
+ ok: false;
367
+ code: number;
368
+ error: string;
369
+ };
306
370
  /**
307
371
  * `GET /api/runs/:runid` — the full replay for the Run tab: steps (or
308
372
  * iterations), counters, summary-box fields. Looks `runid` up in the run
@@ -387,11 +451,12 @@ export function getRunAudit(runid: string, opts?: {
387
451
  * resolve to a real window, or the spine file is missing — the caller
388
452
  * renders 404, same posture as {@link getRunAudit}/{@link getRunJob}.
389
453
  * @param {string} runid
390
- * @param {{ part?: number, attempt?: number, offset?: number, limit?: number }} query
454
+ * @param {{ all?: boolean, part?: number, attempt?: number, offset?: number, limit?: number }} query
391
455
  * @param {{ home?: string }} [opts]
392
456
  * @returns {any|null}
393
457
  */
394
458
  export function getRunRounds(runid: string, query?: {
459
+ all?: boolean;
395
460
  part?: number;
396
461
  attempt?: number;
397
462
  offset?: number;
@@ -399,6 +464,78 @@ export function getRunRounds(runid: string, query?: {
399
464
  }, opts?: {
400
465
  home?: string;
401
466
  }): any | null;
467
+ /** the Round table's check row: the sign and the plain words, ONE place (PRD v1.106 Part 5 C) @param {number} passed @param {number} failed */
468
+ export function checkRowWords(passed: number, failed: number): {
469
+ sign: string;
470
+ text: string;
471
+ };
472
+ /**
473
+ * "71 model · 91 tool · 0 denied" — the Run summary's `calls:` row (PRD v1.106 Part 5 F). Model calls are the run's own spend records
474
+ * (`worker-round`, `judge-round`; the attempt-level `worker-result` echoes are not rounds and drafting is not the run's), tool and denied come
475
+ * from the gate-audit rows scoped to this run (the same figure `tools:` shows). No tool log saved says so, never a zero.
476
+ * @param {any[]} records the run's raw spine @param {{totalCalls: number, denied: number}|null} behaviour
477
+ * @returns {string}
478
+ */
479
+ export function callsLineOf(records: any[], behaviour: {
480
+ totalCalls: number;
481
+ denied: number;
482
+ } | null): string;
483
+ /**
484
+ * "16 run · 8 passed · 8 failed (write-flight-facts tries 1-5, then tries 1-3)" — counts of what ran, never a fraction (the "checks N/M"
485
+ * headline stays withdrawn). Every `exit-eval` result plus every graded `outer-close` stage; the parenthesis names the steps and try ranges
486
+ * that failed, from the same `tries[]` the Audit reads. A run with neither says `not recorded`.
487
+ * @param {any[]} records the run's raw spine @param {any[]|null|undefined} parts the enriched parts
488
+ * @returns {string}
489
+ */
490
+ export function checksLineOf(records: any[], parts: any[] | null | undefined): string;
491
+ /**
492
+ * The step card's extra line (PRD v1.106 Part 5 G): "5 tries · last try: 1 check passed, 1 failed", with the cut-off try named when there is one
493
+ * ("4 tries (the 4th cut off before its checks ran) · last try: no checks ran"). Words for what passed and failed, never a fraction. `null`
494
+ * when the step has no graded try (an old spine).
495
+ * @param {Array<{n: number, cutOff: boolean, passed: number, failed: number, ran: number}>} tries
496
+ * @returns {string|null}
497
+ */
498
+ export function triesLineOf(tries: Array<{
499
+ n: number;
500
+ cutOff: boolean;
501
+ passed: number;
502
+ failed: number;
503
+ ran: number;
504
+ }>): string | null;
505
+ /**
506
+ * "tools: read 16 · grep 22 · write 5 · denied 0" — the step's tool calls by kind, then how many the gate denied (PRD v1.106 Part 5 C). The
507
+ * `shell_`/`ctx_` prefixes are stripped like the rest of the page. `null` when no tool log was saved (nothing honest to count).
508
+ * @param {Record<string, number>|null|undefined} byTool @param {number|null|undefined} blocked
509
+ * @returns {string|null}
510
+ */
511
+ export function toolsTallyLine(byTool: Record<string, number> | null | undefined, blocked: number | null | undefined): string | null;
512
+ /**
513
+ * The try line's gap: the first failing reason, and how many more there are (PRD v1.106 Part 5 C). ONE function, beside {@link stageQuestionText}.
514
+ * @param {string|null|undefined} detail
515
+ * @returns {{ line: string, more: number }|null}
516
+ */
517
+ export function gapOneLine(detail: string | null | undefined): {
518
+ line: string;
519
+ more: number;
520
+ } | null;
521
+ /**
522
+ * "from line 1 ~ under $450; not a red-eye" — the job line(s) a check serves and the `~` rules written under them (the signed `jobLines`).
523
+ * `null` when the stage serves no job line (a mandatory guard).
524
+ * @param {number[]} fromLine @param {any} spec
525
+ * @returns {string|null}
526
+ */
527
+ export function fromLineText(fromLine: number[], spec: any): string | null;
528
+ /**
529
+ * "output judged: output/steps/1.json · 2.1 KB · sha256 674b…30" — the file a try's check read (PRD v1.106 Part 5 choice 14). ONE place owns
530
+ * these words. `size` is bytes under 1 KB, else KB with one decimal; the hash shows its first 4 and last 2 characters.
531
+ * @param {{path: string|null, bytes: number|null, sha256: string|null}} o
532
+ * @returns {string}
533
+ */
534
+ export function judgedFileLine(o: {
535
+ path: string | null;
536
+ bytes: number | null;
537
+ sha256: string | null;
538
+ }): string;
402
539
  /**
403
540
  * The Signed row of the Job tab: `YYYY-MM-DD HH:MM · <first 6 of specHash>`, the time being the run's FIRST `job-start`
404
541
  * in local time (hamr, P7). `null` when either part is not on record — never a made-up stamp.
@@ -627,10 +764,11 @@ export function getStartFromImport(id: string, opts?: {
627
764
  * only `createPanelServer` below always supplies one.
628
765
  * @param {import('node:http').IncomingMessage} req
629
766
  * @param {import('node:http').ServerResponse} res
630
- * @param {{ home?: string, packageJsonPath?: string, port: number, token?: string, authorRoutes?: ReturnType<typeof createAuthorRoutes>, settingsRoutes?: ReturnType<typeof createSettingsRoutes>, runRoutes?: ReturnType<typeof createRunRoutes>, importRoutes?: ReturnType<typeof createImportRoutes>, schedules?: ReturnType<typeof createScheduleDoor> }} opts
767
+ * @param {{ home?: string, sessionsRoot?: string, packageJsonPath?: string, port: number, token?: string, authorRoutes?: ReturnType<typeof createAuthorRoutes>, settingsRoutes?: ReturnType<typeof createSettingsRoutes>, runRoutes?: ReturnType<typeof createRunRoutes>, importRoutes?: ReturnType<typeof createImportRoutes>, schedules?: ReturnType<typeof createScheduleDoor> }} opts
631
768
  */
632
769
  export function handleRequest(req: import("node:http").IncomingMessage, res: import("node:http").ServerResponse, opts: {
633
770
  home?: string;
771
+ sessionsRoot?: string;
634
772
  packageJsonPath?: string;
635
773
  port: number;
636
774
  token?: string;
@@ -731,11 +869,27 @@ export const RUNS_LIST_LIMITS: Readonly<{
731
869
  olderRuns: 7;
732
870
  needsYouDays: 7;
733
871
  }>;
872
+ /** what the Result tab's open door asks, after the door's own head words */
873
+ export const RESULT_DOOR_ASK: "the checks passed; is this what you wanted?";
874
+ /** the two sentences for a delivered file that is no longer what the run wrote (code-owned, one spelling) */
875
+ export const RESULT_FILE_GONE: "the saved file is gone";
876
+ export const RESULT_FILE_CHANGED: "the saved file changed after the run";
877
+ /** the same two states for a red run's file, which is the last one the check read (not a delivered file) */
878
+ export const RESULT_JUDGED_GONE: "the file is gone";
879
+ export const RESULT_JUDGED_CHANGED: "the file changed after the check";
880
+ /** what a red run's rows are, said once above them */
881
+ export const RESULT_JUDGED_NOTE: "a red run delivers nothing; these rows are the last file the check read";
882
+ /** the words beside a Result-tab page link: the page is read again from the web, so it can differ from what the run read */
883
+ export const RESULT_PAGE_MAY_CHANGE: "The page may have changed since this run.";
884
+ /** the words after a fact whose limit the person agreed to in a Part 5b look ask */
885
+ export const RESULT_YOU_CONFIRMED: "(you confirmed)";
734
886
  /** The default/max page size for {@link getRunRounds} — tighten-only,
735
887
  * named so a future change is a deliberate, visible edit (item 5,
736
888
  * 2026-09-25). */
737
889
  export const ROUNDS_PAGE_DEFAULT: 50;
738
890
  export const ROUNDS_PAGE_MAX: 200;
891
+ /** the Audit step table's page size (PRD v1.106 Part 5 choice 3): rows, not rounds */
892
+ export const STEP_ROWS_PAGE: 20;
739
893
  import { createScheduleDoor } from './schedules.js';
740
894
  import { createAuthorRoutes } from './authorroutes.js';
741
895
  import { createSettingsRoutes } from './settingsroutes.js';
@@ -34,6 +34,26 @@ export function partStatusFor(part: {
34
34
  sign: string;
35
35
  word: string;
36
36
  };
37
+ /**
38
+ * The words one door wears: header phrase, the `last answer:` line (with the time `HH:MM` once decided) and the table rows.
39
+ * @param {{at: string|null, paths: string[], decision: string|null, text: string|null, decidedAt: string|null}} door
40
+ * @returns {{ head: string, last: string, rows: Array<{path: string|null, text: string, time: string|null}> }}
41
+ */
42
+ export function doorViewOf(door: {
43
+ at: string | null;
44
+ paths: string[];
45
+ decision: string | null;
46
+ text: string | null;
47
+ decidedAt: string | null;
48
+ }): {
49
+ head: string;
50
+ last: string;
51
+ rows: Array<{
52
+ path: string | null;
53
+ text: string;
54
+ time: string | null;
55
+ }>;
56
+ };
37
57
  /** the sentence a passed run carries as its reason (the Ended block's `line`) */
38
58
  export const GOAL_MET_LINE: "goal met";
39
59
  /** @typedef {'running'|'waiting'|'passed'|'failed'|'capped'|'stopped'|'died'} StatusKey */
@@ -68,4 +88,35 @@ export const STATUS: Readonly<{
68
88
  word: "died";
69
89
  }>;
70
90
  }>;
91
+ /**
92
+ * PRD v1.106 Part 5 (C, choice 9): the review door's words, ONE table. The door is the only human step bareloop has; there is no "you
93
+ * cancelled" (cancel is deleted). `decision` is the spine's `door-decision.decision`, or null while nothing has been decided.
94
+ */
95
+ export const DOOR_WORDS: Readonly<{
96
+ waiting: Readonly<{
97
+ head: "waiting for you";
98
+ row: "paused for you";
99
+ last: "none yet";
100
+ }>;
101
+ accept: Readonly<{
102
+ head: "you accepted";
103
+ row: "you accepted";
104
+ last: "you accepted";
105
+ }>;
106
+ rerun: Readonly<{
107
+ head: "you said rerun";
108
+ row: "you said rerun";
109
+ last: "you said rerun";
110
+ }>;
111
+ pause: Readonly<{
112
+ head: "paused";
113
+ row: "paused";
114
+ last: "paused";
115
+ }>;
116
+ }>;
117
+ /** the marker a part wears for who did its work (PRD v1.106 Part 5 C/G): the model, or a person asked */
118
+ export const ACTOR_MARKER: Readonly<{
119
+ model: "[model]";
120
+ human: "[human · ask]";
121
+ }>;
71
122
  export type StatusKey = "running" | "waiting" | "passed" | "failed" | "capped" | "stopped" | "died";
@@ -0,0 +1,5 @@
1
+ /**
2
+ * @param {number|null|undefined} n a dollar amount
3
+ * @returns {string}
4
+ */
5
+ export function panelMoney2(n: number | null | undefined): string;
package/types/replay.d.ts CHANGED
@@ -180,7 +180,26 @@ export function replayRun(spineEvents: any[], auditEvents?: any[], { runId, audi
180
180
  startTs: number | null;
181
181
  endTs: number | null;
182
182
  exitEvalDetail: string | null;
183
+ checks: Array<{
184
+ type: string;
185
+ pass: boolean;
186
+ detail: string | null;
187
+ name: string | null;
188
+ }>;
189
+ outputJudged: Array<{
190
+ path: string | null;
191
+ bytes: number | null;
192
+ sha256: string | null;
193
+ }>;
183
194
  }[];
195
+ cutOffTry: {
196
+ n: number;
197
+ iteration: any;
198
+ startSeq: any;
199
+ endSeq: any;
200
+ startTs: number | null;
201
+ endTs: number | null;
202
+ } | null;
184
203
  stepStartSeq: any;
185
204
  stepEndSeq: any;
186
205
  stepStartTs: number | null;
@@ -291,9 +310,10 @@ export function replayRun(spineEvents: any[], auditEvents?: any[], { runId, audi
291
310
  * @param {boolean|null} [draftSpendComplete] whether the drafting fold above was EXACT;
292
311
  * `null`/`undefined`/`true` all read as exact (never a new enum value elsewhere) —
293
312
  * only an explicit `false` triggers the floor wording.
313
+ * @param {number|null} [runUsd] the run's own spend as a number (excluding drafting), so the sum can be written
294
314
  * @returns {string}
295
315
  */
296
- export function moneyWithDraft(spendCore: string, draftSpentUsd: number | null, budgetUsd?: number | null, draftSpendComplete?: boolean | null): string;
316
+ export function moneyWithDraft(spendCore: string, draftSpentUsd: number | null, budgetUsd?: number | null, draftSpendComplete?: boolean | null, runUsd?: number | null): string;
297
317
  /**
298
318
  * Render a {@link replayRun} summary as one printable page: a header block
299
319
  * (goal/shape/signed/branch/outcome/spent), a timeline (steps or iterations,
@@ -35,6 +35,13 @@ export function rubricGoal({ jobLines, files, confirmedGoal, closeDecl }: {
35
35
  confirmedGoal?: string | null;
36
36
  closeDecl?: any;
37
37
  }): string;
38
+ /**
39
+ * The sentence the person confirmed, out of a goal text: for a rubric brief the rest of the `CONFIRMED_GOAL_PREFIX` line,
40
+ * or null when the brief has none; any other goal (a code job's) comes back unchanged.
41
+ * @param {string} goal
42
+ * @returns {string|null}
43
+ */
44
+ export function confirmedGoalOf(goal: string): string | null;
38
45
  /**
39
46
  * The SHAPE of a `rubric-step` stage's parameters — what a part may carry — and nothing about which class is allowed in
40
47
  * this build (that is `checkRubricParts`, which also sees the whole job). Pure, never throws.
@@ -58,6 +65,28 @@ export function buildStepSpec(params: {
58
65
  step: number;
59
66
  parts: any[];
60
67
  }, inputShas: Record<string, string>): Record<string, any>;
68
+ /**
69
+ * The files one named check READS, tree-relative: a `rubric-step` stage's own step file (`output/steps/N.json`). Any other kind reads
70
+ * none that a try's output can be told by, so `[]`. From the signed declaration, never guessed.
71
+ * @param {any} closeDecl @param {string|null|undefined} checkName
72
+ * @returns {string[]}
73
+ */
74
+ export function judgedFilesOf(closeDecl: any, checkName: string | null | undefined): string[];
75
+ /**
76
+ * PRD v1.106 Part 5: what each file a try's `check-passes` checks read looked like WHEN it was judged: `{path, bytes, sha256}`. The step
77
+ * file is overwritten by the next try, so the spine's `output-judged` record is the only trace; the content itself is never written (hard
78
+ * line). A missing, non-regular or over-size file is skipped — nothing was judged there.
79
+ * @param {any} closeDecl @param {string} workdir @param {Array<{type?: string, name?: string}>} exits a step's declared exits
80
+ * @returns {Promise<Array<{path: string, bytes: number, sha256: string}>>}
81
+ */
82
+ export function outputJudgedRecords(closeDecl: any, workdir: string, exits: Array<{
83
+ type?: string;
84
+ name?: string;
85
+ }>): Promise<Array<{
86
+ path: string;
87
+ bytes: number;
88
+ sha256: string;
89
+ }>>;
61
90
  /**
62
91
  * The step file's bytes as the close reads them: the parsed JSON object, or WHY there is none. A file cut off mid-write
63
92
  * parses to nothing (reason `not-json`); text cut off INSIDE an otherwise valid JSON object cannot be told from a short
@@ -101,7 +130,9 @@ export function plainStepGap(step: number, why: string, g: any, params?: any): s
101
130
  * `inputs` maps each cited label to its frozen TEXT; its sha256 is taken here from that text, and bareguard compares it to
102
131
  * the manifest's recorded sha via `inputShas`.
103
132
  * @param {Record<string, string>} inputShas the manifest's recorded sha256 per cited label
104
- * @returns {Promise<{verdict: 'green'|'red'|'stopped', gapLines: string[], fault: string|null, detail: Record<string, any>}>}
133
+ * @returns {Promise<{verdict: 'green'|'red'|'stopped', gapLines: string[], fault: string|null, detail: Record<string, any>,
134
+ * evidence?: any[], evidenceTotal?: number}>} `evidence`/`evidenceTotal` are bareguard's `full.evidence` (what each `cited` check read), carried out
135
+ * for the report-only `rubric-evidence` spine record; no verdict, gap or count above is derived from them
105
136
  */
106
137
  export function gradeStep({ params, bytes, outputBytes, inputs, factQuotes, factMissing }: {
107
138
  params: {
@@ -118,6 +149,8 @@ export function gradeStep({ params, bytes, outputBytes, inputs, factQuotes, fact
118
149
  gapLines: string[];
119
150
  fault: string | null;
120
151
  detail: Record<string, any>;
152
+ evidence?: any[];
153
+ evidenceTotal?: number;
121
154
  }>;
122
155
  /**
123
156
  * The labels of a job's numbered inputs, or null when it carries none (a caller that has no inputs in hand skips the label rule).
@@ -178,6 +211,17 @@ export function settlePersonsWords(declaration: any, jobLines: any[] | null | un
178
211
  * @param {any} declaration @returns {any}
179
212
  */
180
213
  export function withFactBindings(declaration: any): any;
214
+ /**
215
+ * NO SILENT DROP (idea A core, spec 4.4): code writes the Not checked rows for what the page rules removed, before the declaration is validated.
216
+ * - a dropped FACT that carries a `~` rule no part carries and no `refused` entry names -> `closeDecl.refused` gets {line, rule, reason}
217
+ * (shown under that line in the person's own rule words by {@link ruleNotChecked});
218
+ * - any other drop (a fact from the job line itself, a rule another fact carries, a word dropped from a fact that is still checked, so a
219
+ * narrower check is still said) -> one line in `closeDecl.notes`, shown as Not checked (the AI's own reading).
220
+ * Never a line-level `refused` entry without `rule` (that would silence every other rule red on the line). A rule the drafter already listed
221
+ * itself is never listed twice, and running this twice adds nothing. Pure; the input is untouched.
222
+ * @param {any} declaration @param {import('./pagerules.js').Dropped[]|null|undefined} dropped @param {any[]|null|undefined} jobLines @returns {any}
223
+ */
224
+ export function withDropNotes(declaration: any, dropped: import("./pagerules.js").Dropped[] | null | undefined, jobLines: any[] | null | undefined): any;
181
225
  /**
182
226
  * The ~ rules the drafter listed under Not checked (`refused` entries that name a `rule`), each as one line in the person's own
183
227
  * words: `line N: "<the rule as written>" — <the drafter's reason>` (PART 1b, RF6 / RF16).
@@ -190,6 +234,11 @@ export function ruleNotChecked(closeDecl: any, jobLines: any[] | null | undefine
190
234
  * @param {any} closeDecl @returns {string[]}
191
235
  */
192
236
  export function proseNotChecked(closeDecl: any): string[];
237
+ /** the same items as {@link proseNotChecked} with the job line each belongs to (the stage's own `step`) @param {any} closeDecl @returns {{line: number, text: string}[]} */
238
+ export function proseNotCheckedItems(closeDecl: any): {
239
+ line: number;
240
+ text: string;
241
+ }[];
193
242
  /**
194
243
  * One countable part as a plain sentence, written by code from the part's signed fields (never from the model's words), so the
195
244
  * plan card and the close are one thing seen twice.
@@ -207,6 +256,11 @@ export function checksOfClose(closeDecl: any): {
207
256
  text: string;
208
257
  covers: number[];
209
258
  }[];
259
+ /**
260
+ * The names of the facts a signed close checks (every fact part of every rubric-step stage). Pure.
261
+ * @param {any} closeDecl @returns {string[]}
262
+ */
263
+ export function signedFactNames(closeDecl: any): string[];
210
264
  /**
211
265
  * Reconcile the CONFIRMED plan with the close the composer actually wrote. The plan card after drafting shows exactly
212
266
  * the close's own checks (`checksOfClose`); every confirmed check that no part carries out (no part lists its number in
@@ -269,6 +323,11 @@ export const STEP_DIR: "output/steps";
269
323
  export function stepFileRel(n: number): string;
270
324
  /** the largest step file the close reads: a bigger one is a stop, never silently truncated (bareguard's own source cap) */
271
325
  export const STEP_MAX_BYTES: number;
326
+ /**
327
+ * What a rubric job's goal text is: the worker brief `rubricGoal` builds. The Job tab's "AI's summary" is only the one
328
+ * sentence the person confirmed, never this brief, so the first line and the confirmed-sentence prefix have ONE spelling.
329
+ */
330
+ export const RUBRIC_GOAL_HEAD: "Do the job below from the input files, then write one JSON file per numbered line.";
272
331
  /** the closed set of classes a part is sorted into; `none` = "I could not sort this one" (a red, in plain words, never a guess) */
273
332
  export const PART_CLASSES: readonly string[];
274
333
  /**
@@ -279,6 +338,8 @@ export const PART_CLASSES: readonly string[];
279
338
  */
280
339
  export const COUNT_CHECKS: Readonly<Record<string, readonly string[]>>;
281
340
  export function sha256Of(b: string | Buffer): string;
341
+ export function boundInWords(b: unknown, said: string): boolean;
342
+ export function wordsSaidOn(jl: any): string;
282
343
  export type ShapeRed = {
283
344
  code: string;
284
345
  path: string;