@vantageos/backend-doctor 0.1.10 → 0.1.12

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.
@@ -116,10 +116,85 @@ export function scanR18(files) {
116
116
  // atomic invariant (the bundle imported wholesale) is implied — flagged
117
117
  // non-conforming (judgement: a human confirms the invariant truly needs
118
118
  // atomicity). An action with ≤1 runMutation conforms.
119
+ //
120
+ // WRITTEN-EXCEPTION CARVE-OUT (Eta R-29 ruling, task k173kmwbr1r8x2h7bkk7xek4z18dhtqj,
121
+ // approved 2026-08-31 — mirrors the R-23 rate-adjacent carve-out #1248/#21 and the
122
+ // R-51 write-contract marker disposition #141): the <=1-runMutation bar has NO room
123
+ // for two shapes that are legitimately more than one runMutation yet carry no cross-
124
+ // write invariant to violate —
125
+ // (1) pre-io-gate-then-single-atomic-write: a security/existence GATE runMutation
126
+ // that must precede an I/O read (e.g. ctx.storage.get) followed by ONE atomic
127
+ // write — the gate is not part of the write's transaction and cannot be folded
128
+ // into it (kb.ts storeDocumentChunked shape); and
129
+ // (2) independent-per-item-loop-writes: a loop whose per-item writes are each
130
+ // independently atomic, where NO invariant spans two of them, so collapsing
131
+ // them into one mutation "would game the regex without changing atomicity"
132
+ // (okfBundleNode importOkfBundle shape).
133
+ // An action that would be flagged non-conforming may carry an `atomicity-exception:`
134
+ // marker naming EXACTLY ONE of those two shapes. Per Eta's three conditions:
135
+ // COND 1 — the disposition is COULD-NOT-JUDGE, never conform. A marker records a
136
+ // CLAIM that reaches a human reviewer; it never closes the rule green (same as
137
+ // the R-51 marker, #141).
138
+ // COND 2 — the marker MUST name which shape it claims, because the two are checked
139
+ // differently and the reviewer must know which question to ask.
140
+ // COND 3 — the dangerous shape is (2): "unbounded loop, each write independently
141
+ // atomic" is a sentence anyone can write about a split invariant. So shape (2)
142
+ // is only honoured when the region ALSO names the loop (for/for-of/.forEach/.map)
143
+ // AND states the independence (no invariant spans two writes). A shape-(2) marker
144
+ // that names neither the loop nor the independence is INCOMPLETE — it does not buy
145
+ // could-not-judge; the site stays non-conforming until the claim is well-formed.
146
+ // A well-formed marker → could-not-judge (the reviewer verifies the per-site claim).
147
+ // No marker, an unknown shape, or an incomplete shape-(2) claim → non-conforming.
119
148
  // ---------------------------------------------------------------------------
120
149
  const ACTION_OPEN = /export\s+const\s+(\w+)\s*=\s*action\s*\(/;
121
150
  const NEXT_EXPORT = /export\s+const\s+\w+\s*=/;
122
151
  const RUN_MUTATION = /ctx\.runMutation\s*\(/;
152
+ // The carve-out marker + the two shape names it may claim (COND 2).
153
+ const ATOMICITY_MARKER = /atomicity-exception:\s*(pre-io-gate-then-single-atomic-write|independent-per-item-loop-writes|gate-then-idempotent-reingest)/i;
154
+ const SHAPE_PER_ITEM_LOOP = "independent-per-item-loop-writes";
155
+ const SHAPE_PRE_IO_GATE = "pre-io-gate-then-single-atomic-write";
156
+ // Shape (3) — gate-then-idempotent-reingest (Eta ruling #26 follow-up, 2026-08-31,
157
+ // task k1725z23c095p08q7nxp74mkhn8dg8e2). kb.ts storeDocumentChunked is a leading
158
+ // gate mutation, a conditional supersede/dedup keyed on a STABLE namespace, and a
159
+ // per-item re-insert loop — there IS an invariant between the supersede and the loop
160
+ // (so it is NOT shape 2's "no invariant spans two"), but a re-ingest of the same
161
+ // stable key converges on exactly one current set (supersede is a no-op when already
162
+ // applied; the loop re-creates the full set; residue is orphaned rows, not corruption).
163
+ // Eta accepted this as a THIRD checkable shape, NOT an atomic-ingest task (forcing one
164
+ // batched mutation over an unbounded chunk set trades a self-healing transient for a
165
+ // transaction-limit failure on large documents). Its sub-conditions a reader can check:
166
+ // (a) a leading gate runMutation, (b) a supersede/dedup keyed on a stable namespace,
167
+ // (c) the recovering re-insert loop — AND the marker MUST STATE THE CONVERGENCE
168
+ // explicitly, not merely name the shape (or the carve-out becomes a vocabulary).
169
+ const SHAPE_GATE_REINGEST = "gate-then-idempotent-reingest";
170
+ const REINGEST_SUPERSEDE = /supersede|dedup|find-?or-?create|isLatest|\.unique\s*\(/i;
171
+ const STABLE_KEY = /namespace|docId|content-?hash|by_namespace/i;
172
+ // NB: deliberately EXCLUDES the bare token "reingest" — the shape name itself is
173
+ // "gate-then-idempotent-reingest", so matching "re-?ingest" would let the token satisfy
174
+ // its own convergence sub-condition. Convergence must be stated BEYOND naming the shape.
175
+ const CONVERGENCE_STATED = /converge|one current set|full (?:current )?set|self-?heal|no-?op when already|re-?creates the full/i;
176
+ // Shape-(2) sub-conditions (COND 3): the region must NAME the loop and STATE the
177
+ // write independence — neither is inferable, both are required of the claim.
178
+ const LOOP_NAMED = /\bfor\s*\(|\bfor\s+(?:const|let|var|await)\b|\.forEach\s*\(|\.map\s*\(|\bwhile\s*\(/;
179
+ const INDEPENDENCE_STATED = /no\s+invariant\s+spans|invariant\s+spans\s+no|writes?\s+are\s+independent|independently\s+atomic|no\s+cross-write\s+invariant/i;
180
+ // Shape-(1) sub-condition (Eta REVISE #26, 2026-08-31): "a pre-I/O gate mutation
181
+ // precedes a single atomic write" describes itself CHECKABLY — it is EXACTLY TWO
182
+ // ctx.runMutation calls, the FIRST a gate (require*/assert*/ensure*/bind*). A shape-1
183
+ // marker on anything else (three calls, an unbounded loop, a first call that is not a
184
+ // gate) is INCOMPLETE and stays non-conforming, exactly as an incomplete shape-2 does
185
+ // — otherwise eleven words in a comment remove a real violation from the #1250 prod
186
+ // gate (abstentions do not block; mechanical_violations is the sole driver), and the
187
+ // mechanical count that gate rests on is no longer honest.
188
+ const GATE_CALLEE = /\b(require|assert|ensure|bind)\w*/i;
189
+ // Shape (3) narrows the gate stems: `ensure*` is the standard prefix for an UPSERT
190
+ // WRITE helper (ensureUser/ensureWorkspace/ensureRow), so on the reingest shape — whose
191
+ // FIRST runMutation is the gate — accepting `ensure*` lets a first WRITE renamed the
192
+ // usual way (e.g. _insertImportedMemory -> ensureImportedMemory) masquerade as the gate
193
+ // and buy the exemption, removing a real violation from the #1250 prod gate (Eta REVISE
194
+ // #27, probe B). `bind` STAYS (bindOrAssertStorageOwnership is kb.ts's real gate, the
195
+ // case this shape exists for); `require`/`assert` are unambiguous. Shapes 1/2 keep the
196
+ // broader GATE_CALLEE.
197
+ const GATE_CALLEE_REINGEST = /\b(require|assert|bind)\w*/i;
123
198
  export function scanR29(files) {
124
199
  const scan = emptyScan();
125
200
  for (const f of files) {
@@ -129,15 +204,91 @@ export function scanR29(files) {
129
204
  let openLine = 0;
130
205
  let count = 0;
131
206
  let firstMut = 0;
207
+ let firstMutCallee = "";
208
+ let calleeNeedsMore = false;
209
+ let region = "";
132
210
  const flush = () => {
133
211
  if (!inAction)
134
212
  return;
135
213
  if (count > 1) {
136
- scan.violate.push({
137
- rel: f.rel,
138
- n: firstMut || openLine,
139
- note: `action ${inAction} orchestrates ${count} ctx.runMutation calls — several mutations across a race window, not one atomic transaction`,
140
- });
214
+ const marker = ATOMICITY_MARKER.exec(region);
215
+ const shape = marker?.[1]?.toLowerCase();
216
+ // Shape (1) is honoured ONLY as exactly two runMutations whose first is a
217
+ // gate call; shape (2) only when the loop is named and independence stated.
218
+ const shapeGate = count === 2 && GATE_CALLEE.test(firstMutCallee);
219
+ // Shape (3) sub-conditions: a leading gate runMutation, a supersede/dedup on
220
+ // a stable namespace, a re-insert loop, AND an explicit convergence statement.
221
+ const shapeReingest = GATE_CALLEE_REINGEST.test(firstMutCallee) &&
222
+ REINGEST_SUPERSEDE.test(region) &&
223
+ STABLE_KEY.test(region) &&
224
+ LOOP_NAMED.test(region) &&
225
+ CONVERGENCE_STATED.test(region);
226
+ const shapeComplete = shape === SHAPE_PER_ITEM_LOOP
227
+ ? LOOP_NAMED.test(region) && INDEPENDENCE_STATED.test(region)
228
+ : shape === SHAPE_PRE_IO_GATE
229
+ ? shapeGate
230
+ : shape === SHAPE_GATE_REINGEST
231
+ ? shapeReingest
232
+ : false;
233
+ if (shape && shapeComplete) {
234
+ // COND 1: a well-formed marker yields COULD-NOT-JUDGE, never conform —
235
+ // the claim is deferred to the reviewer, who verifies it per site.
236
+ scan.cnj.push({
237
+ rel: f.rel,
238
+ n: firstMut || openLine,
239
+ note: `action ${inAction} issues ${count} ctx.runMutation calls AND carries an atomicity-exception marker claiming shape "${shape}" — ${shape === SHAPE_PER_ITEM_LOOP
240
+ ? "the region names its loop and states the per-item writes are independently atomic (no invariant spans two)"
241
+ : shape === SHAPE_GATE_REINGEST
242
+ ? "a leading gate runMutation, a supersede/dedup keyed on a stable namespace, and a recovering re-insert loop, with the convergence stated explicitly — a re-ingest of the same stable key converges on one current set"
243
+ : "exactly two runMutations, the first a gate (require*/assert*/ensure*/bind*) preceding a single atomic write, which R-29's <=1 bar has no room for"}. R-29 refuses (a reviewer verifies the claim per site) — never a silent green (Eta ruling 2026-08-31)`,
244
+ });
245
+ }
246
+ else if (shape === SHAPE_GATE_REINGEST) {
247
+ // Shape (3) claimed but a sub-condition is absent — an incomplete claim does
248
+ // NOT buy could-not-judge (Eta: a shape names a checkable sub-condition, or
249
+ // the carve-out becomes a vocabulary).
250
+ const missing = [
251
+ !GATE_CALLEE_REINGEST.test(firstMutCallee) && "a leading gate runMutation (require*/assert*/bind* — ensure* is an upsert-write stem, excluded here)",
252
+ !REINGEST_SUPERSEDE.test(region) && "a supersede/dedup call",
253
+ !STABLE_KEY.test(region) && "a stable namespace key (namespace/docId/contentHash)",
254
+ !LOOP_NAMED.test(region) && "a re-insert loop (for/for-of/.forEach/.map)",
255
+ !CONVERGENCE_STATED.test(region) && "an explicit convergence statement (converges on one current set / re-ingest / self-heals)",
256
+ ].filter(Boolean).join("; ");
257
+ scan.violate.push({
258
+ rel: f.rel,
259
+ n: firstMut || openLine,
260
+ note: `action ${inAction} orchestrates ${count} ctx.runMutation calls with an atomicity-exception "${SHAPE_GATE_REINGEST}" marker that is INCOMPLETE — missing: ${missing}. The marker must state the convergence, not merely name the shape`,
261
+ });
262
+ }
263
+ else if (shape === SHAPE_PRE_IO_GATE) {
264
+ // Eta REVISE #26: shape (1) claimed but NOT exactly-two-with-a-gate-first
265
+ // — an incomplete claim does NOT buy could-not-judge.
266
+ scan.violate.push({
267
+ rel: f.rel,
268
+ n: firstMut || openLine,
269
+ note: `action ${inAction} orchestrates ${count} ctx.runMutation calls with an atomicity-exception "${SHAPE_PRE_IO_GATE}" marker that is INCOMPLETE — this shape is EXACTLY TWO runMutations whose FIRST is a gate (require*/assert*/ensure*/bind*); here ${count !== 2
270
+ ? `there are ${count}, not two`
271
+ : "the first runMutation is not a gate call"}. A gate-then-single-write is checkable; a marker cannot remove a >2-call or loop site from the gate`,
272
+ });
273
+ }
274
+ else if (shape === SHAPE_PER_ITEM_LOOP) {
275
+ // COND 3: shape (2) claimed but the loop and/or independence is not stated
276
+ // — an incomplete claim does NOT buy could-not-judge.
277
+ scan.violate.push({
278
+ rel: f.rel,
279
+ n: firstMut || openLine,
280
+ note: `action ${inAction} orchestrates ${count} ctx.runMutation calls with an atomicity-exception "${SHAPE_PER_ITEM_LOOP}" marker that is INCOMPLETE — the per-item-loop shape must NAME the loop (for/for-of/.forEach/.map) AND STATE the write independence (no invariant spans two writes). Complete the claim or split the invariant into one mutation`,
281
+ });
282
+ }
283
+ else {
284
+ scan.violate.push({
285
+ rel: f.rel,
286
+ n: firstMut || openLine,
287
+ note: `action ${inAction} orchestrates ${count} ctx.runMutation calls — several mutations across a race window, not one atomic transaction${marker === null && /atomicity-exception:/i.test(region)
288
+ ? " (an atomicity-exception marker is present but names no known shape — it must name pre-io-gate-then-single-atomic-write or independent-per-item-loop-writes)"
289
+ : ""}`,
290
+ });
291
+ }
141
292
  }
142
293
  else if (count === 1) {
143
294
  scan.conform.push({
@@ -148,8 +299,6 @@ export function scanR29(files) {
148
299
  }
149
300
  };
150
301
  for (const l of f.lines) {
151
- if (!code(l))
152
- continue;
153
302
  const open = ACTION_OPEN.exec(l.raw);
154
303
  if (open) {
155
304
  flush();
@@ -157,17 +306,41 @@ export function scanR29(files) {
157
306
  openLine = l.n;
158
307
  count = 0;
159
308
  firstMut = 0;
309
+ firstMutCallee = "";
310
+ calleeNeedsMore = false;
311
+ region = l.raw;
160
312
  continue;
161
313
  }
162
- if (inAction && NEXT_EXPORT.test(l.raw) && !ACTION_OPEN.test(l.raw)) {
314
+ if (inAction && code(l) && NEXT_EXPORT.test(l.raw) && !ACTION_OPEN.test(l.raw)) {
163
315
  flush();
164
316
  inAction = "";
317
+ region = "";
165
318
  continue;
166
319
  }
167
- if (inAction && RUN_MUTATION.test(l.raw)) {
320
+ if (!inAction)
321
+ continue;
322
+ // Accumulate the WHOLE region (comments included) so the marker — which
323
+ // lives in a comment — is visible; only count runMutation on code lines.
324
+ region += `\n${l.raw}`;
325
+ // The callee of the FIRST runMutation identifies the gate (shape 1). It may
326
+ // sit on the same line (`ctx.runMutation(internal.kb.assertX, {`) or wrap to
327
+ // the next code line (`const id = await ctx.runMutation(\n internal.kb.assertX,`);
328
+ // grab the paren-tail here and one more code line if the tail was empty.
329
+ if (code(l) && calleeNeedsMore) {
330
+ firstMutCallee += ` ${l.raw.trim()}`;
331
+ calleeNeedsMore = false;
332
+ }
333
+ if (code(l) && RUN_MUTATION.test(l.raw)) {
168
334
  count++;
169
- if (!firstMut)
335
+ if (!firstMut) {
170
336
  firstMut = l.n;
337
+ const tail = l.raw.slice(l.raw.search(RUN_MUTATION)).replace(RUN_MUTATION, "");
338
+ firstMutCallee = tail;
339
+ // If the paren-tail carries no identifier yet, the callee wrapped — take
340
+ // the next code line too.
341
+ if (!/\w/.test(tail))
342
+ calleeNeedsMore = true;
343
+ }
171
344
  }
172
345
  }
173
346
  flush();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vantageos/backend-doctor",
3
- "version": "0.1.10",
3
+ "version": "0.1.12",
4
4
  "description": "A conformance instrument that measures the VantagePeers backend against the pinned backend standard. The THIRD doctor built ON the @vantageos/mcp-doctor engine — never a third engine. It consumes runDoctor/renderTable and adds only detectors.",
5
5
  "type": "module",
6
6
  "main": "./dist/cli.js",