bareloop 0.40.0 → 0.42.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 (77) hide show
  1. package/CHANGELOG.md +96 -1
  2. package/README.md +13 -21
  3. package/bareloop.context.md +141 -128
  4. package/package.json +5 -4
  5. package/src/authorflow.js +346 -38
  6. package/src/authoring.js +92 -7
  7. package/src/authorjob.js +82 -20
  8. package/src/authorreadout.js +89 -5
  9. package/src/authorrun.js +39 -30
  10. package/src/authorscout.js +112 -4
  11. package/src/bundle.js +7 -4
  12. package/src/bundlerun.js +4 -1
  13. package/src/cli.js +1 -1
  14. package/src/config.js +129 -29
  15. package/src/declaredclose.js +23 -12
  16. package/src/draftspend.js +4 -4
  17. package/src/facts.js +317 -0
  18. package/src/factwords.js +129 -0
  19. package/src/index.js +2 -4
  20. package/src/inputlook.js +301 -0
  21. package/src/interviewrun.js +17 -25
  22. package/src/job.js +15 -8
  23. package/src/jobblock.js +7 -2
  24. package/src/judged.js +11 -43
  25. package/src/keysfile.js +15 -7
  26. package/src/kinds.js +86 -5
  27. package/src/labelscan.js +170 -0
  28. package/src/modellist.js +84 -0
  29. package/src/monthly.js +23 -43
  30. package/src/pagefetch.js +125 -0
  31. package/src/panel/authorroutes.js +34 -16
  32. package/src/panel/authorsession.js +161 -80
  33. package/src/panel/index.html +553 -152
  34. package/src/panel/jobplan.js +34 -11
  35. package/src/panel/runroutes.js +3 -0
  36. package/src/panel/server.js +54 -18
  37. package/src/panel/settingsroutes.js +232 -173
  38. package/src/planrun.js +5 -4
  39. package/src/providerrows.js +226 -141
  40. package/src/providers.js +58 -332
  41. package/src/providershapes.js +18 -0
  42. package/src/replay.js +1 -1
  43. package/src/rubricstep.js +804 -0
  44. package/src/source.js +166 -1
  45. package/src/thinking.js +130 -0
  46. package/src/u-readout.js +1 -1
  47. package/src/userrun.js +64 -75
  48. package/types/authorflow.d.ts +272 -18
  49. package/types/authoring.d.ts +29 -1
  50. package/types/authorjob.d.ts +9 -7
  51. package/types/authorreadout.d.ts +27 -2
  52. package/types/authorscout.d.ts +72 -2
  53. package/types/config.d.ts +23 -6
  54. package/types/declaredclose.d.ts +4 -2
  55. package/types/draftspend.d.ts +8 -8
  56. package/types/facts.d.ts +71 -0
  57. package/types/factwords.d.ts +42 -0
  58. package/types/index.d.ts +1 -1
  59. package/types/inputlook.d.ts +238 -0
  60. package/types/job.d.ts +5 -5
  61. package/types/jobblock.d.ts +8 -1
  62. package/types/judged.d.ts +5 -36
  63. package/types/keysfile.d.ts +15 -4
  64. package/types/labelscan.d.ts +38 -0
  65. package/types/modellist.d.ts +50 -0
  66. package/types/monthly.d.ts +4 -3
  67. package/types/pagefetch.d.ts +71 -0
  68. package/types/panel/authorsession.d.ts +20 -11
  69. package/types/panel/jobplan.d.ts +12 -4
  70. package/types/panel/server.d.ts +18 -6
  71. package/types/providerrows.d.ts +192 -99
  72. package/types/providers.d.ts +34 -159
  73. package/types/providershapes.d.ts +10 -0
  74. package/types/rubricstep.d.ts +286 -0
  75. package/types/source.d.ts +94 -0
  76. package/types/thinking.d.ts +90 -0
  77. package/types/userrun.d.ts +17 -1
@@ -1,47 +1,3 @@
1
- /**
2
- * The per-call options the rubric JUDGE's model calls carry for this provider instance: `{ thinkingMode: 'off' }` when
3
- * bare-agent measures the model switchable, else `{}` (F192: judge runs thinking off; a judge on a model that cannot turn it
4
- * off must not throw).
5
- * @param {any} provider a constructed bare-agent provider
6
- * @returns {Record<string, any>}
7
- */
8
- export function judgeCallOptions(provider: any): Record<string, any>;
9
- /**
10
- * Throw unless `mode` is one of {@link DRAFT_THINKING_MODES} — an operator-param guard, so a typo never silently means a mode.
11
- * @param {unknown} mode
12
- * @returns {'on'|'off'}
13
- */
14
- export function assertDraftThinking(mode: unknown): "on" | "off";
15
- /**
16
- * What the drafting calls on this provider ACTUALLY carry for thinking, for the draft log and the panel row: `'on'` (mode on,
17
- * nothing sent), `'off'` (mode off and the switch is sent), `'not-switchable'` (mode off, model measured not switchable —
18
- * nothing sent), `'unverified'` (mode off, model not measured by bare-agent — nothing sent). Never re-derives a model list.
19
- * @param {any} provider a constructed bare-agent provider
20
- * @param {unknown} [mode] 'on' | 'off' (default 'off')
21
- * @returns {'on'|'off'|'not-switchable'|'unverified'}
22
- */
23
- export function draftThinkingSent(provider: any, mode?: unknown): "on" | "off" | "not-switchable" | "unverified";
24
- /**
25
- * {@link draftThinkingSent} for a model the caller has only NAMED (provider shape, model id, baseUrl) — no key, no network. For
26
- * the panel's first drafting row, which is shown before the key is read. Constructing the provider only makes the class and
27
- * the model id available to `thinkingSupport`; it sends nothing.
28
- * @param {{providerName: string, model: string, baseUrl?: string|null}} o
29
- * @param {unknown} [mode] 'on' | 'off' (default 'off')
30
- * @returns {'on'|'off'|'not-switchable'|'unverified'}
31
- */
32
- export function draftThinkingFor({ providerName, model, baseUrl }: {
33
- providerName: string;
34
- model: string;
35
- baseUrl?: string | null;
36
- }, mode?: unknown): "on" | "off" | "not-switchable" | "unverified";
37
- /**
38
- * The ONE owner of the per-call options every DRAFTING model call carries (scout, confirm, author, revise, structure retries).
39
- * `{ thinkingMode: 'off' }` exactly when {@link draftThinkingSent} says `'off'`, else `{}`.
40
- * @param {any} provider a constructed bare-agent provider
41
- * @param {unknown} [mode] 'on' | 'off' (default 'off')
42
- * @returns {Record<string, any>}
43
- */
44
- export function draftCallOptions(provider: any, mode?: unknown): Record<string, any>;
45
1
  /**
46
2
  * Look up a provider's table entry. Throws a NAMED error on an unrecognized
47
3
  * provider — never a silent default (the `resolveRates`-style-substring-
@@ -81,80 +37,67 @@ export function resolveProvider(providerName: string): ProviderTableEntry;
81
37
  */
82
38
  export function apiKeyProblem(value: string): string | null;
83
39
  /**
84
- * Construct ONE provider instance for `providerName`/`model`, applying
85
- * `exposeErrorBody: true` (F153 — every constructed provider, unconditionally)
86
- * and the model's own request-key gating (`paramsFor`). `baseUrl` (PRD item
87
- * 28, ruling (d), 2026-09-09: admitted in v1) is forwarded only when given —
88
- * every provider constructor here defaults it on its own when absent.
40
+ * Construct ONE provider instance for `providerName`/`model`, applying `exposeErrorBody: true` (F153 — every constructed
41
+ * provider, unconditionally). `baseUrl` (PRD item 28, ruling (d), 2026-09-09: admitted in v1) is forwarded only when given.
42
+ * `legacyMaxTokens` is the Settings ROW's "old max_tokens" tick: a server that ignores `max_completion_tokens` (F149) does not
43
+ * bind the output cap until it is on. It is forwarded only for `openai-api` and only when `true` — nothing here sniffs a host
44
+ * or a model id to decide it.
89
45
  * @param {string} providerName
90
- * @param {{apiKey?: string, model?: string, baseUrl?: string}} [options]
46
+ * @param {{apiKey?: string, model?: string, baseUrl?: string, legacyMaxTokens?: boolean}} [options]
91
47
  */
92
- export function makeProvider(providerName: string, { apiKey, model, baseUrl }?: {
48
+ export function makeProvider(providerName: string, { apiKey, model, baseUrl, legacyMaxTokens }?: {
93
49
  apiKey?: string;
94
50
  model?: string;
95
51
  baseUrl?: string;
52
+ legacyMaxTokens?: boolean;
96
53
  }): any;
97
54
  /**
98
- * Build the `{provider, providerFor, judgeProvider}` triple both runners
99
- * need (`src/authorrun.js` and `src/panel/authorsession.js` call it) — ONE seam instead of two copies of the same construction and
100
- * tier-memoization logic. `model`/`tierModels` are already-RESOLVED inputs
101
- * (the caller runs `resolveWorkerModel` itself, exactly as both runners did
102
- * before this factory existed — that decision stays where each runner's own
103
- * `--model` flag/spec precedence lives, not duplicated in here).
55
+ * Build the `{provider, providerFor, judgeProvider}` triple both authoring runners need (`src/authorrun.js` and
56
+ * `src/panel/authorsession.js` call it) — ONE seam instead of two copies of the same construction. `model` is the
57
+ * already-RESOLVED worker model id (the caller picked the Settings line).
104
58
  *
105
- * THE JUDGE IS NO LONGER PINNED TO ANTHROPIC (PRD item 32.2, hamr 2026-09-10:
106
- * *"what i care about is that judge becomes llm agnostic and not set to one
107
- * model or provider"*). It is constructed through this same factory, from a
108
- * `judgeProviderName`/`judgeModel` pair the CALLER resolved (`resolveJudge`,
109
- * src/judged.js — the spec's signed `judge` override, else the job's own worker
110
- * provider and model). Item 28's pin, and item 28 part (2)'s deferral of a
111
- * signed `judge:{provider,model}` field, both close here.
112
- *
113
- * `judgeApiKey` stays a SEPARATE argument rather than being derived: the judge
114
- * may legitimately be a different provider from the worker, and reading its key
115
- * for it would put a second env-var reader in a module whose whole job is to
116
- * have exactly one. The caller reads the resolved judge provider's own `envKey`
117
- * and hands the key in — the same contract the worker key already has here.
59
+ * THE JUDGE IS NOT PINNED (PRD item 32.2): it is constructed through this same factory from a `judgeProviderName`/`judgeModel`
60
+ * pair the CALLER resolved. `judgeApiKey` stays a SEPARATE argument: the judge may be a different provider from the worker,
61
+ * and each key is read by the caller from its own row.
118
62
  * @param {object} args
119
- * @param {string} args.providerName spec.provider (or the anthropic-api default)
120
- * @param {string|undefined} args.apiKey the WORKER's key, read from the provider's own `envKey`
63
+ * @param {string} args.providerName the worker's API shape id
64
+ * @param {string|undefined} args.apiKey the WORKER's key, read from its row's env name
121
65
  * @param {string} args.model the already-resolved worker model id
122
- * @param {Readonly<Record<string, string>>} args.tierModels the already-resolved tier -> model map (spec override folded in by the caller)
123
- * @param {string|undefined} [args.baseUrl] spec.baseUrl, if any
124
- * @param {string|undefined} args.judgeApiKey the judge's own key, read by the caller from the RESOLVED judge provider's `envKey`
66
+ * @param {string|undefined} [args.baseUrl] the worker row's Base URL
67
+ * @param {boolean} [args.legacyMaxTokens] the worker row's old max_tokens tick
68
+ * @param {string|undefined} args.judgeApiKey the judge's own key
125
69
  * @param {string} args.judgeModel the resolved judge model id
126
- * @param {string} [args.judgeProviderName] the resolved judge provider name; defaults to `providerName` (the job's own worker provider), which is `resolveJudge`'s own default
127
- * @param {string|undefined} [args.judgeBaseUrl] the judge provider's endpoint, when it takes one
70
+ * @param {string} [args.judgeProviderName] the resolved judge shape; defaults to `providerName`
71
+ * @param {string|undefined} [args.judgeBaseUrl] the judge row's endpoint
72
+ * @param {boolean} [args.judgeLegacyMaxTokens] the judge row's old max_tokens tick (defaults to the worker's when the judge shares its provider)
128
73
  */
129
- export function buildRunnerProviders({ providerName, apiKey, model, tierModels, baseUrl, judgeApiKey, judgeModel, judgeProviderName, judgeBaseUrl, }: {
74
+ export function buildRunnerProviders({ providerName, apiKey, model, baseUrl, legacyMaxTokens, judgeApiKey, judgeModel, judgeProviderName, judgeBaseUrl, judgeLegacyMaxTokens, }: {
130
75
  providerName: string;
131
76
  apiKey: string | undefined;
132
77
  model: string;
133
- tierModels: Readonly<Record<string, string>>;
134
78
  baseUrl?: string | undefined;
79
+ legacyMaxTokens?: boolean | undefined;
135
80
  judgeApiKey: string | undefined;
136
81
  judgeModel: string;
137
82
  judgeProviderName?: string | undefined;
138
83
  judgeBaseUrl?: string | undefined;
84
+ judgeLegacyMaxTokens?: boolean | undefined;
139
85
  }): {
140
86
  provider: any;
141
- providerFor: (tier: string) => any;
87
+ providerFor: (_tier: string) => any;
142
88
  judgeProvider: any;
143
89
  };
144
90
  /**
145
- * $0 reachability check: a GET against `providerName`'s own models-list
146
- * endpoint, server-side, with the key in a header — NEVER a completion
147
- * request, so it never spends a token (PANEL-BUILD.md P3 build item 7: "the
148
- * person must get feedback BEFORE drafting if the chosen model can't
149
- * work"). Reused, unmodified, by the panel's model-readiness route today
150
- * (`src/panel/authorroutes.js`) and by the future Settings providers
151
- * table's own "test" column (PANEL-BUILD.md P4) — ONE function, not two
152
- * hand-typed copies of the same probe.
91
+ * $0 reachability check: ONE GET of the provider's models list (`src/modellist.js`, the one owner of
92
+ * that call), server-side, with the key in a header — NEVER a completion request, so it never spends
93
+ * a token (PANEL-BUILD.md P3 build item 7: "the person must get feedback BEFORE drafting if the
94
+ * chosen model can't work"). Used by the panel's model-readiness route (`src/panel/authorroutes.js`);
95
+ * Settings > Providers' Test and Refresh call `listModels` directly. A blank `baseUrl` makes no call.
153
96
  *
154
- * NEVER returns the key, never logs it — the key only ever appears in a
155
- * request header sent straight to the provider's own host.
97
+ * NEVER returns the key, never logs it — the key only ever appears in a request header sent
98
+ * straight to the provider's own host.
156
99
  * @param {object} o
157
- * @param {string} o.providerName
100
+ * @param {string} o.providerName an API shape id
158
101
  * @param {string} o.apiKey
159
102
  * @param {string} [o.model] when given, the response is also checked for this model id
160
103
  * @param {string} [o.baseUrl]
@@ -194,55 +137,6 @@ export function checkProviderReachable({ providerName, apiKey, model, baseUrl, f
194
137
  * @returns {string[]|null} lines to print, in order, or null when there is nothing to say
195
138
  */
196
139
  export function probeWarningLines(providerName: string): string[] | null;
197
- /**
198
- * Today's anthropic-api tier table — EXACTLY the `DEFAULT_TIER_MODELS` both
199
- * runners hardcoded before this factory existed. Any change to these two
200
- * values is a change to shipped anthropic-api behaviour, not a refactor.
201
- */
202
- export const ANTHROPIC_TIER_MODELS: Readonly<{
203
- sonnet: "claude-sonnet-5";
204
- haiku: "claude-haiku-4-5-20251001";
205
- }>;
206
- /**
207
- * DeepSeek (F149/F150) is a SINGLE model, not a tiered family — hamr's
208
- * ruling above: one secondary provider, not a menu. Both tiers map to the
209
- * same model id ON PURPOSE: `resolveWorkerModel`/a runner's `providerFor
210
- * (tier)` only ever LOOK UP a tier's model id (src/job.js:818-827,
211
- * scripts/run-u.mjs's `providerFor`) — neither assumes the tiers name
212
- * different models — so repeating the one id is the honest representation
213
- * of a single-model provider, not a second model masquerading as `haiku`.
214
- * If DeepSeek (or a future openai-api entrant) ever ships a genuinely
215
- * cheaper tier model, this is where it would earn its own `haiku` id.
216
- *
217
- * MODEL SWAP (F171, item 34 L16, hamr 2026-09-13): `deepseek-chat` is no
218
- * longer served — a live `/models` call returned only `deepseek-flash` and
219
- * `deepseek-v4-pro`. Switched to `deepseek-flash` (DeepSeek V4.1). F149's
220
- * `legacyMaxTokens` measurement (asked 64 output tokens, got 665/608) was
221
- * taken on `deepseek-chat` and is UNVERIFIED on `deepseek-flash` — kept on
222
- * below as the fail-safe (over-capping direction is safe; see F171).
223
- */
224
- export const OPENAI_TIER_MODELS: Readonly<{
225
- sonnet: "deepseek-flash";
226
- haiku: "deepseek-flash";
227
- }>;
228
- /**
229
- * Gemini (PRD item 31.3, hamr 2026-09-09: "anthropic, openai, gemini drop
230
- * ollama for now"). Two real tiers, unlike DeepSeek's single model: `flash` is
231
- * the cheap tier by design, so `haiku` maps to it honestly rather than
232
- * repeating one id.
233
- *
234
- * ADMITTED-PENDING-PROBE. The probe rule stands (PRD item 28 ruling (d),
235
- * 2026-09-09): no endpoint is in the menu without its own clean paid probe,
236
- * and `gemini-api` has ZERO runs. `anthropic-api` is the original (~167
237
- * archived runs) and `openai-api` was probed green through the shipped runner
238
- * (run mtu12vks, $1.64/$4, F157); gemini owes that fire and has not paid it.
239
- * `PROBE_STATUS` below is the machine-readable form of that debt — nothing in
240
- * here silently pretends the provider is proven.
241
- */
242
- export const GEMINI_TIER_MODELS: Readonly<{
243
- sonnet: "gemini-2.5-pro";
244
- haiku: "gemini-2.5-flash";
245
- }>;
246
140
  /**
247
141
  * Which admitted providers have paid for their own end-to-end probe, and which
248
142
  * have not. Read by the runner to print a loud marker; never read to REFUSE —
@@ -254,32 +148,13 @@ export const PROBE_STATUS: Readonly<Record<string, {
254
148
  probed: boolean;
255
149
  evidence: string;
256
150
  }>>;
257
- /**
258
- * The drafter's THINKING knob — an enumerated OPERATOR param, never model output and never read from env inside the library.
259
- * `'on'` sends nothing (the model's own thinking, byte-identical to a no-knob call); `'off'` sends `thinkingMode: 'off'` where
260
- * bare-agent measures the model switchable. The DEFAULT is `'off'` (hamr 2026-10-11, decided by the frozen ON/OFF contrast,
261
- * docs/logs/2026-10-10-drafter-thinking-contrast-prereg.md); `'on'` stays selectable for a later Settings checkbox.
262
- */
263
- export const DRAFT_THINKING_MODES: readonly string[];
264
151
  export type ProviderTableEntry = {
265
152
  /**
266
153
  * the bare-agent provider class
267
154
  */
268
155
  ctor: new (options: any) => any;
269
- /**
270
- * the environment variable this provider reads its key from
271
- */
272
- envKey: string;
273
- /**
274
- * tier name -> model id
275
- */
276
- tiers: Readonly<Record<string, string>>;
277
156
  /**
278
157
  * the CONSTRUCTOR OPTION NAME this provider reads its endpoint from — NOT assumed to be `baseUrl` (see below)
279
158
  */
280
159
  endpointKey: string;
281
- /**
282
- * per-model request-key/param gating, applied on TOP of {exposeErrorBody:true, apiKey, model, <endpointKey>?}
283
- */
284
- paramsFor: (model: string | undefined) => Record<string, any>;
285
160
  };
@@ -0,0 +1,10 @@
1
+ /**
2
+ * `id` is the value saved in config.json (and the signed spec's `provider`).
3
+ * @type {readonly {id: string, label: string}[]}
4
+ */
5
+ export const SHAPES: readonly {
6
+ id: string;
7
+ label: string;
8
+ }[];
9
+ /** @type {readonly string[]} */
10
+ export const SHAPE_IDS: readonly string[];
@@ -0,0 +1,286 @@
1
+ /**
2
+ * The one statement of the step-file shape, in plain words. The worker's goal and the composer's prompt both carry it.
3
+ * @returns {string}
4
+ */
5
+ export function stepFileContract(): string;
6
+ /**
7
+ * What the worker is told about each line, from the close that will grade it (PART 1b item 7): a FACTS line gets its
8
+ * facts and its count; a TEXT line that follows a facts line is told its numbers come from that line's quotes. Written by code from
9
+ * the signed parts, never by a model. A line with no rubric-step stage gets nothing here.
10
+ * @param {any} closeDecl @returns {{line: number, text: string}[]}
11
+ */
12
+ export function lineBriefs(closeDecl: any): {
13
+ line: number;
14
+ text: string;
15
+ }[];
16
+ /**
17
+ * The worker's goal for a rubric job on local files — written by code from the person's own numbered lines, never by a
18
+ * model. It states everything the close will judge about the FORM of the answer (the file, the keys, the quotes); what
19
+ * each line asks for stays in the person's own words, with its `~` rules beneath it.
20
+ * @param {{jobLines: {n: number, text: string, rule: string}[], files: {label: string, rel: string}[], confirmedGoal?: string|null, closeDecl?: any}} o
21
+ * `confirmedGoal` — the one goal sentence the person confirmed in the confirm turn, kept as the plan line
22
+ * `closeDecl` — the drafted close: with it, each line is briefed by what its stage checks (a facts line or a text line, `lineBriefs`)
23
+ * @returns {string}
24
+ */
25
+ export function rubricGoal({ jobLines, files, confirmedGoal, closeDecl }: {
26
+ jobLines: {
27
+ n: number;
28
+ text: string;
29
+ rule: string;
30
+ }[];
31
+ files: {
32
+ label: string;
33
+ rel: string;
34
+ }[];
35
+ confirmedGoal?: string | null;
36
+ closeDecl?: any;
37
+ }): string;
38
+ /**
39
+ * The SHAPE of a `rubric-step` stage's parameters — what a part may carry — and nothing about which class is allowed in
40
+ * this build (that is `checkRubricParts`, which also sees the whole job). Pure, never throws.
41
+ * @param {unknown} p the stage's `params`
42
+ * @param {string} at the path prefix for reds, e.g. `stages[1].params`
43
+ * @returns {ShapeRed[]}
44
+ */
45
+ export function rubricStepShapeReds(p: unknown, at: string): ShapeRed[];
46
+ /** the labels of the inputs a step's `cited` parts name (each once, in order) @param {any} params @returns {string[]} */
47
+ export function citedLabels(params: any): string[];
48
+ /**
49
+ * The rubric spec for ONE step, built from its parameters and the sha256 of each input it cites (taken from the source
50
+ * manifest at run start — a rubric is rebuilt per run because the inputs belong to the run, not to the signed job).
51
+ * `happened` is bareguard's own foundational check; the `text` floor below it stops a step file whose only key is junk
52
+ * from passing a rubric that has no countable part.
53
+ * @param {{step: number, parts: any[]}} params
54
+ * @param {Record<string, string>} inputShas label -> sha256 hex, for every label the step cites
55
+ * @returns {Record<string, any>} a spec for bareguard's `createRubric`
56
+ */
57
+ export function buildStepSpec(params: {
58
+ step: number;
59
+ parts: any[];
60
+ }, inputShas: Record<string, string>): Record<string, any>;
61
+ /**
62
+ * 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
+ * parses to nothing (reason `not-json`); text cut off INSIDE an otherwise valid JSON object cannot be told from a short
64
+ * answer — that limit is documented, not hidden.
65
+ * @param {string|null} bytes the file's text, or null when it does not exist
66
+ * @returns {{obj: Record<string, any>, why: null}|{obj: null, why: 'missing'|'not-json'|'not-object'}}
67
+ */
68
+ export function parseStepFile(bytes: string | null): {
69
+ obj: Record<string, any>;
70
+ why: null;
71
+ } | {
72
+ obj: null;
73
+ why: "missing" | "not-json" | "not-object";
74
+ };
75
+ /**
76
+ * The step file as bareguard's rubric reads it: a flat object. `text` stays `text`; each label in `cites` becomes the field
77
+ * `cites:<label>` (the name a `cited` check reads its claims from). Nothing else in the file is read.
78
+ * @param {Record<string, any>} obj
79
+ * @param {string[]} [countLabels] labels whose claim COUNT a minClaims/maxClaims part reads
80
+ * @param {any} [factPart] the step's `fact` part: its `results` become claims (text built by code) under `cites:<source>`
81
+ * @returns {Record<string, any>}
82
+ */
83
+ export function flattenStep(obj: Record<string, any>, countLabels?: string[], factPart?: any): Record<string, any>;
84
+ /**
85
+ * One bareguard gap as a sentence the worker can act on. The numbers and names are bareguard's own measurements and the
86
+ * person's signed limits, never anything this module infers; a gap this table does not know falls back to bareguard's own
87
+ * rendering, so nothing is ever dropped.
88
+ * @param {number} step @param {string} why why there was no object, when there was none
89
+ * @param {any} g a bareguard gap
90
+ * @param {any} [params] the stage's parameters (a fact step words its gaps by fact)
91
+ * @returns {string}
92
+ */
93
+ export function plainStepGap(step: number, why: string, g: any, params?: any): string;
94
+ /**
95
+ * Grade ONE step: build the rubric, run it over the step file's fields, and say what happened in the close's own terms.
96
+ * Pure of the filesystem (the runner in src/kinds.js reads the files). `stopped` = the instrument failed (never a red).
97
+ * @param {{params: {step: number, parts: any[]}, bytes: string|null, outputBytes: string|null, inputs: Record<string, string>,
98
+ * factQuotes?: Record<number, string[]>, factMissing?: number[]}} o
99
+ * `factQuotes` maps an earlier fact line's number to the quotes its (already checked) step file holds — what a prose step's numbers are tied to
100
+ * `factMissing` lists the earlier fact lines whose step file is not there (yet): a normal red, never an instrument fault
101
+ * `inputs` maps each cited label to its frozen TEXT; its sha256 is taken here from that text, and bareguard compares it to
102
+ * the manifest's recorded sha via `inputShas`.
103
+ * @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>}>}
105
+ */
106
+ export function gradeStep({ params, bytes, outputBytes, inputs, factQuotes, factMissing }: {
107
+ params: {
108
+ step: number;
109
+ parts: any[];
110
+ };
111
+ bytes: string | null;
112
+ outputBytes: string | null;
113
+ inputs: Record<string, string>;
114
+ factQuotes?: Record<number, string[]>;
115
+ factMissing?: number[];
116
+ }, inputShas: Record<string, string>): Promise<{
117
+ verdict: "green" | "red" | "stopped";
118
+ gapLines: string[];
119
+ fault: string | null;
120
+ detail: Record<string, any>;
121
+ }>;
122
+ /**
123
+ * 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).
124
+ * @param {unknown} inputs @returns {string[]|null}
125
+ */
126
+ export function inputLabelsOf(inputs: unknown): string[] | null;
127
+ /**
128
+ * R11 — the drafting-time rules for the `rubric-step` stages of a declaration, each a red in the person's plain words that
129
+ * names the job line and ends "Nothing spent." where nothing was. Pure; never throws. Judged parts are RED in this build
130
+ * (the judge arrives in the next one): the sorting is still asked for, so a part that needs the judge is named as such
131
+ * instead of being approximated by a count.
132
+ * - a stage serves exactly the one job line it names (`step` = its only `fromLine`), and no line has two stages;
133
+ * - a part with no class (`none`) cannot be counted or checked against a quote;
134
+ * - a `judged` part needs the judge; a `guard` part is about web sources and the job has no web input;
135
+ * - two countable parts of ONE line that no text can satisfy together (a minimum above a maximum) are a contradiction,
136
+ * naming both parts and both numbers;
137
+ * - a `cited` part names a label the person's inputs do not have.
138
+ * @param {any[]} stages the declaration's stages
139
+ * @param {{inputLabels?: string[]|null, red: (code: string, path: string, detail: string, extra?: object) => void}} o
140
+ * `inputLabels` absent = the label rule is not run (a caller with no inputs in hand)
141
+ */
142
+ export function checkRubricParts(stages: any[], { inputLabels, red }: {
143
+ inputLabels?: string[] | null;
144
+ red: (code: string, path: string, detail: string, extra?: object) => void;
145
+ }): void;
146
+ /** the ~ rules of one job line, one string each (`rules` when the line has the array, else the joined `rule` split on "; ") @param {any} l @returns {string[]} */
147
+ export function lineRules(l: any): string[];
148
+ /**
149
+ * PART 1b, RF16 + RF17 — the rules that need the person's own job lines. Only a close built for REQUIRED FACTS is held to them (a
150
+ * close with a fact part, or any part that says which `~` rules it carries out): an older Part 1a close is left as it was signed.
151
+ * - every `~` rule of every job line is carried out by a part (its number in the part's `rules`), or is listed in `refused` with
152
+ * its `rule` number, or its whole line is refused: a rule that is none of those was silently dropped, and that is a red;
153
+ * - a number the AI made out of the person's words (a bound the person did not write) must be marked `aiProposal`, and a time
154
+ * limit the AI proposes must be a range (`between`), never one-sided — "not a red-eye" must not let a 10 PM flight through.
155
+ * @param {any[]} stages @param {{jobLines?: any[]|null, refused?: any, red: (code: string, path: string, detail: string, extra?: object) => void}} o
156
+ */
157
+ export function checkRubricRules(stages: any[], { jobLines, refused, red }: {
158
+ jobLines?: any[] | null;
159
+ refused?: any;
160
+ red: (code: string, path: string, detail: string, extra?: object) => void;
161
+ }): void;
162
+ /**
163
+ * PART 1c L3 - the person's own words win over the AI's reading of them, settled by CODE before anything is validated or signed:
164
+ * - a limit on a fact whose bounds are all in the words the person wrote (the line, its `~` rules, an answer they gave) is the
165
+ * person's, so it is NOT marked "AI's proposal" (smv710w8c3wdh: the plan called the person's own range the AI's);
166
+ * - a time range the AI proposed while the person wrote ONE explicit positive time range ("between 07:00 and 23:00") on that line
167
+ * is replaced by exactly that range (smv710w8c3wdh: the plan said 5:00 AM). Only when it is unambiguous: one such range in the
168
+ * line's rules and one AI-proposed `between` time fact on the line; a rule with a negation ("not between ...") is never read as a range;
169
+ * - a `~` rule is a check or under Not checked, never both: a `refused` entry for a rule that a part of its own line carries is dropped.
170
+ * Pure; returns a new declaration, the input untouched. The model is told the same in the brief; this makes it true either way.
171
+ * @param {any} declaration @param {any[]|null|undefined} jobLines @returns {any}
172
+ */
173
+ export function settlePersonsWords(declaration: any, jobLines: any[] | null | undefined): any;
174
+ /**
175
+ * PART 1b — put the number tie on every prose step. A rubric-step stage with no fact part that comes AFTER one or more fact
176
+ * stages gets exactly one `numbersFromFacts` part naming those lines; one the model wrote is replaced. Pure; returns a new
177
+ * declaration (the input is untouched). The model never writes this part: it is code's, so a draft cannot omit it.
178
+ * @param {any} declaration @returns {any}
179
+ */
180
+ export function withFactBindings(declaration: any): any;
181
+ /**
182
+ * The ~ rules the drafter listed under Not checked (`refused` entries that name a `rule`), each as one line in the person's own
183
+ * words: `line N: "<the rule as written>" — <the drafter's reason>` (PART 1b, RF6 / RF16).
184
+ * @param {any} closeDecl @param {any[]|null|undefined} jobLines @returns {string[]}
185
+ */
186
+ export function ruleNotChecked(closeDecl: any, jobLines: any[] | null | undefined): string[];
187
+ /**
188
+ * The Not-checked lines code owns for the rubric-step stages of a close: a prose step's CONTENT is judged by the person at
189
+ * Accept, and the card says so (PART 1b, RF5). One line per prose step (a stage with no fact part).
190
+ * @param {any} closeDecl @returns {string[]}
191
+ */
192
+ export function proseNotChecked(closeDecl: any): string[];
193
+ /**
194
+ * 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
+ * plan card and the close are one thing seen twice.
196
+ * @param {any} part @returns {string}
197
+ */
198
+ export function partSentence(part: any): string;
199
+ /**
200
+ * The checks a signed close actually runs, per job line, in plain code-written sentences: one per countable part of every
201
+ * rubric-step stage. This is the ONE reader both the plan card and the sign-time invariant use.
202
+ * @param {any} closeDecl
203
+ * @returns {{line: number, text: string, covers: number[]}[]}
204
+ */
205
+ export function checksOfClose(closeDecl: any): {
206
+ line: number;
207
+ text: string;
208
+ covers: number[];
209
+ }[];
210
+ /**
211
+ * Reconcile the CONFIRMED plan with the close the composer actually wrote. The plan card after drafting shows exactly
212
+ * the close's own checks (`checksOfClose`); every confirmed check that no part carries out (no part lists its number in
213
+ * `covers`) is moved to Not checked, in the person's words, with the reason. A check can therefore never be shown and then
214
+ * silently dropped (run mv5bk400: "lists 3 releases" was confirmed, then left out of the signed close without a word).
215
+ * @param {{confirmed: {text: string, fromLine: number[]|null}[], notChecked: string[], closeDecl: any, jobLines?: any[]|null}} o
216
+ * `jobLines` — the person's numbered lines, so a ~ rule the drafter listed as Not checked reads in the person's own words
217
+ * @returns {{checkItems: {text: string, fromLine: number[]}[], notChecked: string[], moved: {text: string, fromLine: number[]|null}[]}}
218
+ */
219
+ export function reconcileRubricPlan({ confirmed, notChecked, closeDecl, jobLines }: {
220
+ confirmed: {
221
+ text: string;
222
+ fromLine: number[] | null;
223
+ }[];
224
+ notChecked: string[];
225
+ closeDecl: any;
226
+ jobLines?: any[] | null;
227
+ }): {
228
+ checkItems: {
229
+ text: string;
230
+ fromLine: number[];
231
+ }[];
232
+ notChecked: string[];
233
+ moved: {
234
+ text: string;
235
+ fromLine: number[] | null;
236
+ }[];
237
+ };
238
+ /**
239
+ * The sign-time invariant: the checks the person was shown (`shown`, from the plan card) are exactly the checks the signed
240
+ * close runs (`checksOfClose`), per line. Pure.
241
+ * @param {{text: string, fromLine: number[]|null}[]} shown @param {any} closeDecl
242
+ * @returns {{ok: true}|{ok: false, error: string}}
243
+ */
244
+ export function planMatchesClose(shown: {
245
+ text: string;
246
+ fromLine: number[] | null;
247
+ }[], closeDecl: any): {
248
+ ok: true;
249
+ } | {
250
+ ok: false;
251
+ error: string;
252
+ };
253
+ /**
254
+ * The frozen TEXT of the named inputs, read the way the runner reads them: the source door's record (`source.json`, one level
255
+ * above the tree) names each input's file and sha256, and a file whose bytes no longer match is not returned. Used at DRAFTING
256
+ * (the drafter reads the input, the early tests run on it) and at run start (the backstop). Never throws.
257
+ * @param {string} workdir the frozen tree @param {string[]} labels
258
+ * @returns {Promise<{ok: true, texts: Record<string, string>}|{ok: false, why: string}>}
259
+ */
260
+ export function readFrozenInputs(workdir: string, labels: string[]): Promise<{
261
+ ok: true;
262
+ texts: Record<string, string>;
263
+ } | {
264
+ ok: false;
265
+ why: string;
266
+ }>;
267
+ /** where the per-line step files live, inside the job's tree; `copyOut` delivers them with the rest of `output/` */
268
+ export const STEP_DIR: "output/steps";
269
+ export function stepFileRel(n: number): string;
270
+ /** the largest step file the close reads: a bigger one is a stop, never silently truncated (bareguard's own source cap) */
271
+ export const STEP_MAX_BYTES: number;
272
+ /** 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
+ export const PART_CLASSES: readonly string[];
274
+ /**
275
+ * The counted/quoted checks a `countable` part may name, with the fields each needs — read off bareguard's own rule table
276
+ * (`rubricVocabulary`): the names are bareguard's, so a check this build cannot run is inexpressible, not rejected late.
277
+ * (`max`/`min` need a numeric field the step file does not have, so they are not offered.)
278
+ * @type {Readonly<Record<string, readonly string[]>>}
279
+ */
280
+ export const COUNT_CHECKS: Readonly<Record<string, readonly string[]>>;
281
+ export function sha256Of(b: string | Buffer): string;
282
+ export type ShapeRed = {
283
+ code: string;
284
+ path: string;
285
+ detail: string;
286
+ };
package/types/source.d.ts CHANGED
@@ -330,6 +330,100 @@ export function proveInputFiles(inputs: {
330
330
  ok: false;
331
331
  error: string;
332
332
  }>;
333
+ /**
334
+ * Does this source manifest belong to a rubric job on local files (`proveRubricInputs` wrote it)? One spelling for both
335
+ * doors (the panel session and the CLI author run): a non-repo source that froze numbered, labelled input files.
336
+ * @param {Record<string, any>|null|undefined} manifest
337
+ * @returns {boolean}
338
+ */
339
+ export function isRubricFilesManifest(manifest: Record<string, any> | null | undefined): boolean;
340
+ /**
341
+ * PART 1a (docs/product/ITEM33-BUILD.md, "Addendum 2026-10-12"): the front door for a RUBRIC job whose inputs are the
342
+ * person's own LOCAL FILES, not a repo. Each numbered `label: /absolute/file` line is proven at $0 and frozen into the
343
+ * same hidden-git tree a plain folder gets (`prepareSource`, which also refuses a secret shape, an environment file
344
+ * and a symlink), under `input/`; the manifest then also records `inputs: [{label, rel, sha256}]` — the bytes the
345
+ * rubric's `cited` check will search a quote in, hashed once, here. A line that is a web address is fetched ONCE with
346
+ * barebrowse (PART 1c, read only) and its text frozen under `input/` like a file (the manifest row also records its `url`,
347
+ * so run start can fetch it again); a `search:` line is a named refusal (a later build); so is a relative path, a missing file, a folder, a file over
348
+ * {@link RUBRIC_INPUT_MAX_BYTES}, and text that is not valid UTF-8. Every refusal is plain words naming the line and
349
+ * ends "Nothing spent." — nothing here touches a model.
350
+ * @param {{inputs: {n: number, label: string, value: string}[], into: string, destination?: string, fetchUrl?: (url: string) => Promise<{ok: true, text: string, url: string}|{ok: false, error: string}>}} o `fetchUrl` - how a web address is read (default barebrowse via `fetchPageText`; a test injects a stub, never the network)
351
+ * @returns {Promise<{ok: true, prep: {stop: null, into: string, tree: string, manifestPath: string, manifest: Record<string, any>}}|{ok: false, error: string, code: string}>}
352
+ */
353
+ export function proveRubricInputs({ inputs, into, destination, fetchUrl }: {
354
+ inputs: {
355
+ n: number;
356
+ label: string;
357
+ value: string;
358
+ }[];
359
+ into: string;
360
+ destination?: string;
361
+ fetchUrl?: (url: string) => Promise<{
362
+ ok: true;
363
+ text: string;
364
+ url: string;
365
+ } | {
366
+ ok: false;
367
+ error: string;
368
+ }>;
369
+ }): Promise<{
370
+ ok: true;
371
+ prep: {
372
+ stop: null;
373
+ into: string;
374
+ tree: string;
375
+ manifestPath: string;
376
+ manifest: Record<string, any>;
377
+ };
378
+ } | {
379
+ ok: false;
380
+ error: string;
381
+ code: string;
382
+ }>;
383
+ /**
384
+ * Is this Inputs value a path to an existing regular FILE on this machine (not a repo folder, not a web address, not a
385
+ * missing path)? The $0 peek that tells a rubric job on local files from a rubric job on a repo before a session exists;
386
+ * `proveRubricInputs` makes the authoritative, line-by-line proof.
387
+ * @param {string} value
388
+ * @returns {boolean}
389
+ */
390
+ export function looksLikeLocalFile(value: string): boolean;
391
+ /**
392
+ * Is this Inputs value one a rubric job can work on without a repo: a regular FILE on this machine, or a web address (PART 1c)?
393
+ * @param {string} value
394
+ * @returns {boolean}
395
+ */
396
+ export function looksLikeRubricInput(value: string): boolean;
397
+ /**
398
+ * RUN START for a rubric job with web inputs (PART 1c, RF22): fetch every web input AGAIN and make that text the run's frozen input
399
+ * BEFORE the worker starts. The drafting copy was only for the look (prices change); the close grades, and the worker reads, THIS
400
+ * copy. It is written into the prepared tree, committed as the new seed (so the page is never read as a worker write), and
401
+ * `source.json` is rewritten (new sha256 per input, new seed, `draftSha256` keeps what the look saw). Nothing but the text of a
402
+ * recorded web address is ever refetched; a job with only file inputs returns at once and changes nothing.
403
+ * @param {{into: string, fetchUrl?: (url: string) => Promise<{ok: true, text: string, url: string}|{ok: false, error: string}>}} o `into` - the prepared copy (source.json + tree/)
404
+ * @returns {Promise<{ok: true, refreshed: string[], seed: string|null}|{ok: false, error: string}>}
405
+ */
406
+ export function refreezeUrlInputs({ into, fetchUrl }: {
407
+ into: string;
408
+ fetchUrl?: (url: string) => Promise<{
409
+ ok: true;
410
+ text: string;
411
+ url: string;
412
+ } | {
413
+ ok: false;
414
+ error: string;
415
+ }>;
416
+ }): Promise<{
417
+ ok: true;
418
+ refreshed: string[];
419
+ seed: string | null;
420
+ } | {
421
+ ok: false;
422
+ error: string;
423
+ }>;
424
+ /** the per-file ceiling for a rubric input: bareguard's `cited` check searches a quote in a source only up to this size
425
+ * (its `quoteSourceMaxBytes`); a bigger file could never be cited from, so the door refuses it at $0 */
426
+ export const RUBRIC_INPUT_MAX_BYTES: number;
333
427
  /**
334
428
  * a named, non-throwing stop
335
429
  */