bareloop 0.9.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.
@@ -78,6 +78,42 @@
78
78
  * @returns {string} the labelled block, or '' when there is nothing to show
79
79
  */
80
80
  export function closeGapBlock(gap: string | null | undefined): string;
81
+ /**
82
+ * What the NEXT attempt is told about the bound that cut the previous one.
83
+ *
84
+ * Two different bounds used to set the same bare `attemptBounded = roundIteration`
85
+ * and therefore render the same sentence. For a round-bound cutoff that sentence is
86
+ * true. For BA-11's deny streak it is false twice over — the rounds did NOT run out
87
+ * (the fence short-circuited the attempt with budget left), and the count it quotes
88
+ * is the cap, not what was spent — and it aims the worker at the READ BUDGET when the
89
+ * thing that stopped it was the FENCE. The reason was already recorded on the spine;
90
+ * nothing carried it to the one reader who could act on it.
91
+ *
92
+ * The denial note is MECHANICAL in the strict sense: it quotes the terminal the
93
+ * provider actually returned and names which bound fired. It invents nothing —
94
+ * no streak count and no denied path, because bare-agent's return (`error:
95
+ * 'denied:<tool>'`, loop.js) carries neither, and a number nobody measured is a
96
+ * bug with a confident voice. Every other cause keeps the frozen wording byte for
97
+ * byte: a wall-bounded attempt reads as a round bound exactly as it did before
98
+ * (and is unreachable anyway — the step head refuses to fund an attempt past the
99
+ * deadline, W-2), so this change can only ever speak where it has something true
100
+ * to add.
101
+ *
102
+ * The reason arrives ALREADY SCRUBBED (the capture seam in `mkWorker` runs it
103
+ * through the one `SECRET_PATTERNS` inventory), so this renderer only bounds it —
104
+ * and announces the trim with the repo's ONE marker rather than a second spelling
105
+ * of "this text was cut" (F90.2).
106
+ *
107
+ * @param {{iteration?: number|string|undefined, cause: string, reason: string|null}|null|undefined} bounded
108
+ * the previous attempt's bound state, or nullish when it was not bounded
109
+ * @param {number} rounds the step's round bound, as quoted by the frozen sentence
110
+ * @returns {string|null} the note, or null when there is nothing to say
111
+ */
112
+ export function boundedNote(bounded: {
113
+ iteration?: number | string | undefined;
114
+ cause: string;
115
+ reason: string | null;
116
+ } | null | undefined, rounds: number): string | null;
81
117
  /**
82
118
  * The plan-drafting prompt: a schema DESCRIPTION built from the live validator
83
119
  * menus — never a copyable example (the drafter must author, not echo; the
@@ -297,6 +333,13 @@ export function runPlan(job: any, { workdir, provider, nativeProvider, providerF
297
333
  now?: (() => number) | undefined;
298
334
  resumeBranch?: string | null | undefined;
299
335
  }): Promise<string>;
336
+ /**
337
+ * The recorded bound-reason's ceiling in the WORKER-facing note. The reason is a
338
+ * provider string (`denied:<tool>` today, ~20 bytes), not a gap, so this is a
339
+ * backstop against a future terminal that arrives long — never an envelope the
340
+ * shipped path routinely rides through.
341
+ */
342
+ export const BOUND_REASON_MAX: 200;
300
343
  export type CategorizedError = Error & {
301
344
  category?: string;
302
345
  lib?: string;
package/types/reuse.d.ts CHANGED
@@ -221,7 +221,7 @@ export function selectBridge({ registry, job, ask, provider, pinned, shortlist,
221
221
  * which is later and better evidence than the last event). Defaults to the last
222
222
  * event's own timestamp — the last sign of life there is.
223
223
  * @returns {any} `{ started, approvalHash, specHash, patient, job, bridgeTries,
224
- * perTryBudgetUsd, perTryWallMs, ended, endOutcome, greened, completed, tried,
224
+ * perTryBudgetUsd, perTryWallMs, ended, endOutcome, wallDerivedHalt, greened, completed, tried,
225
225
  * selectionCostUsd, selectionComplete, strayRounds, r1Missing, restart, deathAtKnown,
226
226
  * spentUsd, spendComplete, carrySpentUsd, carrySpendComplete }`
227
227
  */
package/types/text.d.ts CHANGED
@@ -33,6 +33,73 @@ export function priceOf(r: any): {
33
33
  costUsd: number | null;
34
34
  unpricedRounds: number;
35
35
  };
36
+ /**
37
+ * The F6 tally over a list of PRICED CALLS — `priceOf`'s per-call read, summed.
38
+ *
39
+ * `knownUsd` carries the priced half explicitly and `costUsd` is the honest
40
+ * total: null the moment anything went unpriced, never the priced half wearing a
41
+ * complete coat. An EMPTY list is a complete $0 and not an unknown — nothing
42
+ * spent is a number somebody can act on.
43
+ * @param {{costUsd: number|null, unpricedRounds?: number}[]|null|undefined} entries
44
+ * @returns {{costUsd: number|null, knownUsd: number, spendComplete: boolean,
45
+ * nullCostCalls: number, unpricedRounds: number}}
46
+ */
47
+ export function tallyCalls(entries: {
48
+ costUsd: number | null;
49
+ unpricedRounds?: number;
50
+ }[] | null | undefined): {
51
+ costUsd: number | null;
52
+ knownUsd: number;
53
+ spendComplete: boolean;
54
+ nullCostCalls: number;
55
+ unpricedRounds: number;
56
+ };
57
+ /**
58
+ * THE ONE MONEY-CEILING PREDICATE, for the same reason `priceOf` above is one:
59
+ * the authoring pipeline meters two populations of paid call in two different
60
+ * accumulators (the survey's `calls[]`, the declaration loop's cost book), and
61
+ * two hand-spelled answers to "am I over the ceiling?" is two instruments.
62
+ *
63
+ * It is asked BETWEEN metered calls and never inside one. A cap that binds
64
+ * mid-call kills the row before it can be graded — F45 measured that as 3 of 7
65
+ * launches lost and $5.82 unreadable.
66
+ *
67
+ * Three answers, and the order between the last two is load-bearing:
68
+ * - NO CEILING → null, always. An absent cap is UNBOUNDED, and unbounded is a
69
+ * visible operator choice, never a defaulted number nobody set (the
70
+ * `maxWallMs` precedent). There is nothing to enforce, so unpriced spend
71
+ * here is merely reported.
72
+ * - KNOWN SPEND AT OR OVER THE CEILING → `cap-halt`, even when the total is
73
+ * unknown. The breach is CERTAIN on the priced half alone — whatever the
74
+ * unpriced remainder turns out to be, it can only be larger — so naming this
75
+ * `pricing-red` would send the operator to fix a meter when the fact is that
76
+ * the money is gone. At-or-over rather than over: the next call is the one
77
+ * that breaches, and it is the one being decided.
78
+ * - SPEND THAT CANNOT BE KNOWN → `pricing-red`. `?? 0` launders unknown into
79
+ * $0 (F6) and a ceiling enforced against a laundered floor is not enforced
80
+ * at all. If the spend cannot be seen the cap cannot govern it, and that is
81
+ * its own stop rather than a silent pass.
82
+ *
83
+ * A MALFORMED ceiling is the one input it refuses rather than answers: `null` and
84
+ * an absent field mean UNBOUNDED, and they are the ONLY way to get there. Anything
85
+ * else that is not a finite number (`'2.50'`, `NaN`, `Infinity`) reading as
86
+ * unbounded would be F6's laundering in its ceiling form — a caller who set a cap
87
+ * getting a paid pipeline with nothing enforcing it, which is the exact collapse
88
+ * the ceiling exists to prevent, wearing the caller's own typo (PRD v1.62). The
89
+ * CLI's `parseCeiling` guards the FLAG; this guards the LIBRARY, and `authorClose`
90
+ * / `runAuthorScout` / `authorCloseForJob` are documented adopter seams. It THROWS
91
+ * rather than clamping, unlike the attempt caps beside it, because those axes have
92
+ * a safe direction to clamp toward and this one does not: every candidate default
93
+ * is either a number nobody set or the unbounded run the caller just declined.
94
+ * @param {{ceilingUsd: number|null|undefined, knownUsd: number, spendComplete: boolean}} o
95
+ * @returns {'cap-halt'|'pricing-red'|null}
96
+ * @throws {Error} when the ceiling is neither absent nor a finite number
97
+ */
98
+ export function capStop({ ceilingUsd, knownUsd, spendComplete }: {
99
+ ceilingUsd: number | null | undefined;
100
+ knownUsd: number;
101
+ spendComplete: boolean;
102
+ }): "cap-halt" | "pricing-red" | null;
36
103
  /**
37
104
  * ONE raw model emission, ready to persist.
38
105
  *
@@ -86,16 +86,40 @@ export function sweepSecretLiterals(root: any, red: (code: string, path: string,
86
86
  * So the reject targets the dominant exponential class and fails it as a
87
87
  * mechanical gap at the validation gate, before any tokens burn. A full ReDoS
88
88
  * analyzer needs a real regex engine (an external native dep we do not take for
89
- * a LOW issue); exotic overlapping-alternation blowup is out of scope by
90
- * decision (PARKED to the close-authoring rung — PRD v1.55).
89
+ * a LOW issue).
90
+ *
91
+ * SECOND CLASS, added 2026-08-12 — OVERLAPPING ALTERNATION under an unbounded
92
+ * repeat (`(a|aa)+`, `(a|a)+`, `(\d|\d\d)*`). F49 recorded this as a genuine
93
+ * false NEGATIVE of the shape approach and PARKED the widening, because changing
94
+ * the detector changes which SIGNED specs are admissible. It is closed here on
95
+ * measurement, one fresh process per point (a hot RegExp tiers up out of the V8
96
+ * interpreter, and a single-process sweep read a LONGER body as faster —
97
+ * backwards for an exponential, and the harness rather than the regex).
98
+ * `(a|aa)+$` against a run of `a` with one non-matching tail character:
99
+ *
100
+ * 27ch 218ms · 31ch 1,879ms · 35ch 12,082ms · 39ch 52,563ms · 41ch 189,420ms
101
+ *
102
+ * ~6.8× per 4 characters — the Fibonacci decomposition count, φ⁴. `(a|a)+$` (the
103
+ * 2ⁿ equal-branch case) took 52,256ms on 27ch, and `((a|aa))+$` — the redundant
104
+ * wrapper the `((a+))+` entry above exists for — 15,426ms on 35ch, which is why
105
+ * the overlap propagates up through a wrapper exactly as the inner repeat does.
106
+ * Input-bounding is theater here for F49's own reason: tens of characters suffice.
107
+ *
108
+ * The detected core is the PREFIX RELATION between top-level branches (one branch
109
+ * a prefix of another, equality included — `(a|a)+` is the 2ⁿ case). Whether a
110
+ * given prefix overlap can actually be DRIVEN exponentially depends on what
111
+ * follows the group, and deciding that needs the real engine this file declined
112
+ * to take as a dependency — so the reject is conservative by construction, which
113
+ * is the fail-safe direction.
114
+ *
115
+ * MONOTONIC, and that is the rule this change lives under: it only ever ADDS
116
+ * rejections. Nothing above is narrowed, and no previously-rejected shape became
117
+ * admissible (asserted over the whole corpus in tests/plan.test.js). F49's
118
+ * "never sharpen the detector" bars chasing false POSITIVES; closing a false
119
+ * NEGATIVE by adding rejections is the opposite, allowed direction — distinct
120
+ * rules, not in tension (the `((a+))+` widening set the precedent).
91
121
  *
92
122
  * SCOPE IS ASYMMETRIC, both directions named on purpose:
93
- * - false NEGATIVE: overlapping-alternation blowup (`(a|ab)+`-class) is not
94
- * detected — there is no inner QUANTIFIER to find, so the shape scan cannot
95
- * see it; the blowup comes from overlapping alternation BRANCHES under a
96
- * repeat. A genuine limit of the approach, not a bug in it. Widening the
97
- * detector changes which SIGNED specs are admissible, which is
98
- * arbiter-adjacent — parked, not chased (PRD v1.55).
99
123
  * - false POSITIVE: a group whose repetitions are disambiguated by a literal
100
124
  * anchor/delimiter (`(?:^- .+$\n?)+`, `(?:CHANGELOG:.+\n)+`) is FLAGGED even
101
125
  * though a real engine runs it linearly — the scan sees the nested-quantifier
@@ -106,10 +130,51 @@ export function sweepSecretLiterals(root: any, red: (code: string, path: string,
106
130
  * and guessing it wrong would ADMIT an exponential pattern — so the shape
107
131
  * reject stands, and the over-rejection is a named, accepted limitation.
108
132
  *
133
+ * The alternation class has its OWN instance of the same limit, measured and
134
+ * locked in `REDOS_OVERREJECTED_ALT`: `(ab|abc)+$`, `(x|xy|xyz)+$` and
135
+ * `(x(a|aa))+$` carry the prefix relation and did NOT blow up on the bodies
136
+ * measured (0.3–0.4ms at 41–91 characters). "Did not blow up on the body
137
+ * measured" is the honest claim — no adversarial search was run for a worse
138
+ * input — and rejecting them is again the direction that never admits an
139
+ * exponential pattern.
140
+ *
109
141
  * The input is guaranteed to compile as a RegExp (checked first by the caller),
110
142
  * so this scan assumes valid, balanced JS regex syntax.
111
143
  * @param {string} src a compiled regex source string
112
- * @returns {boolean} true iff a nested unbounded quantifier is present
144
+ * @returns {boolean} true iff a nested unbounded quantifier, or an unboundedly
145
+ * repeated group with overlapping alternation branches, is present
113
146
  */
114
147
  export function hasNestedQuantifier(src: string): boolean;
148
+ /**
149
+ * The catastrophic-backtracking reject, applied to a WHOLE signed document by
150
+ * tree walk — `sweepSecretLiterals`'s twin, and for its reason: some signed
151
+ * documents carry their regexes at a depth no per-field check enumerates.
152
+ *
153
+ * WHY IT EXISTS (PRD v1.60 §1, an OPEN GAP now closed). v1.55 took F49's reject
154
+ * to the operator's own regex fields, and the admissibility sweep that backed it
155
+ * walked `jobs/*.json`. That is not the population of signed specs: a REGISTRY
156
+ * BRIDGE is signed the same way, ships its plans VERBATIM (reuse stores the
157
+ * whole plan, never a template), and lives outside `jobs/`, so a pathological
158
+ * pattern arriving through a bridge was never swept. Nothing had decided
159
+ * bridge-borne patterns were safe — the sweep was written against the directory
160
+ * that held every spec at the time and was never re-aimed.
161
+ *
162
+ * WHAT is rejected is UNCHANGED: `hasNestedQuantifier`, one inventory, shared
163
+ * with job.js and plan.js. Only WHERE it looks is wider. Widening the DETECTOR
164
+ * is arbiter-adjacent and stays parked (F49); widening the POPULATION is this.
165
+ *
166
+ * KEY-SCOPED, deliberately. `pattern` and `gapKeep` are the exactly-three regex
167
+ * fields F49 names (`artifact-written.pattern`, `judged.pattern`, `gapKeep`);
168
+ * every other string in a signed document is prose, a path or a command, and
169
+ * running a regex-shape scan over prose would red goals for containing `(a+)+`
170
+ * in a sentence. A field carrying a regex under a NEW key is not swept until
171
+ * this list names it — stated so the limit is read rather than rediscovered.
172
+ *
173
+ * COMPILE-FIRST, like both per-field callers: the scan's contract is that its
174
+ * input is valid regex syntax, and a string that does not compile is the OTHER
175
+ * validator's red. Two reds on one string bury the real one.
176
+ * @param {any} root
177
+ * @param {(code: string, path: string, detail?: string) => void} red
178
+ */
179
+ export function sweepNestedQuantifiers(root: any, red: (code: string, path: string, detail?: string) => void): void;
115
180
  export const SECRET_PATTERNS: RegExp[];