@vantageos/backend-doctor 0.1.7 → 0.1.8
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.
|
@@ -120,7 +120,9 @@ function discoverBuilders(files) {
|
|
|
120
120
|
function definitionOpener(raw, set) {
|
|
121
121
|
// longest builder names first so `authedQuery` wins over a `query` substring.
|
|
122
122
|
const names = [...set].sort((a, z) => z.length - a.length);
|
|
123
|
-
const alt = names
|
|
123
|
+
const alt = names
|
|
124
|
+
.map((n) => n.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"))
|
|
125
|
+
.join("|");
|
|
124
126
|
const re = new RegExp(`(?:export\\s+)?const\\s+([A-Za-z0-9_]+)\\s*=\\s*(${alt})\\s*\\(`);
|
|
125
127
|
const m = re.exec(raw);
|
|
126
128
|
return m ? { name: m[1], builder: m[2] } : null;
|
|
@@ -234,6 +236,12 @@ export function scanR50(files) {
|
|
|
234
236
|
// Pass 1: discover the tree's query/mutation builders so a customQuery wrapper
|
|
235
237
|
// (authedQuery, orgQuery, …) is recognised, not just the bare `query` base.
|
|
236
238
|
const builders = discoverBuilders(files);
|
|
239
|
+
// Public queries that matched a definition opener but exhibit NO org-throw, NO
|
|
240
|
+
// fail-closed resolver, and NO isolation-contract marker. Not gated subjects, so
|
|
241
|
+
// historically SKIPPED — which, when EVERY public query is such a bare read,
|
|
242
|
+
// produced a false "no public query matched" abstain. Recorded for the
|
|
243
|
+
// otherwise-empty branch below.
|
|
244
|
+
const ungated = [];
|
|
237
245
|
// A customQuery wrapper whose base is neither query nor internalQuery leaves the
|
|
238
246
|
// public-query surface unknowable — REFUSE (could-not-judge), never a silent green.
|
|
239
247
|
for (const a of builders.ambiguous) {
|
|
@@ -321,16 +329,21 @@ export function scanR50(files) {
|
|
|
321
329
|
// so a commented-out throw does not count). The contract marker is, by
|
|
322
330
|
// definition, a COMMENT — so it is read from the full block text INCLUDING
|
|
323
331
|
// comment lines, not the code-only blockText.
|
|
324
|
-
const blockTextWithComments = block.lines
|
|
325
|
-
.map((l) => l.raw)
|
|
326
|
-
.join("\n");
|
|
332
|
+
const blockTextWithComments = block.lines.map((l) => l.raw).join("\n");
|
|
327
333
|
const throwsOnNoOrg = ORG_THROW.test(blockText) || ORG_THROW_HELPER.test(blockText);
|
|
328
334
|
const hasResolver = SAFE_RESOLVER.test(blockText);
|
|
329
335
|
const hasMarker = CONTRACT_MARKER.test(blockTextWithComments);
|
|
330
336
|
if (!throwsOnNoOrg && !hasResolver && !hasMarker) {
|
|
331
337
|
// query does neither org-isolation throw nor fail-closed resolve nor
|
|
332
338
|
// declare a contract — not R-50's subject (too ambiguous to judge
|
|
333
|
-
// mechanically; never a violation).
|
|
339
|
+
// mechanically; never a violation). Recorded so a tree whose ENTIRE
|
|
340
|
+
// public-read surface is bare is judged (CNJ-with-sites) rather than
|
|
341
|
+
// falsely abstained as "no public query matched".
|
|
342
|
+
ungated.push({
|
|
343
|
+
rel: f.rel,
|
|
344
|
+
n: block.lines[0].n,
|
|
345
|
+
note: `${fn}: public query with no org-throw, no fail-closed resolver, and no isolation-contract marker — no org-isolation handling is visible in convex/, and whether a subscribing client can reach it before an org exists is not statically decidable; a reviewer must confirm this read isolates by org (never a silent clean bill)`,
|
|
346
|
+
});
|
|
334
347
|
continue;
|
|
335
348
|
}
|
|
336
349
|
// PRECEDENCE (Eta #13 REVISE seam): the THROW wins over a fail-closed
|
|
@@ -390,6 +403,18 @@ export function scanR50(files) {
|
|
|
390
403
|
}
|
|
391
404
|
}
|
|
392
405
|
}
|
|
406
|
+
// A tree whose ENTIRE public-read surface is ungated (no throw / resolver / marker
|
|
407
|
+
// on ANY query) leaves the scan otherwise empty. The legacy abstain then claimed
|
|
408
|
+
// "no public query matched" — FALSE when bare public queries exist, and a silent
|
|
409
|
+
// pass over a fully-ungated backend. Surface those reads as could-not-judge sites
|
|
410
|
+
// so the verdict is CNJ-with-sites, not a misleading empty abstain. Gated on "scan
|
|
411
|
+
// otherwise empty" so a tree that already produced subjects (VP: 20 conform) is
|
|
412
|
+
// byte-for-byte unchanged.
|
|
413
|
+
if (scan.conform.length + scan.violate.length + scan.cnj.length === 0 &&
|
|
414
|
+
ungated.length > 0) {
|
|
415
|
+
for (const u of ungated)
|
|
416
|
+
scan.cnj.push(u);
|
|
417
|
+
}
|
|
393
418
|
return scan;
|
|
394
419
|
}
|
|
395
420
|
export const R50 = srcDetector("R-50", "R-50 org isolation on a reactively-subscribed read returns a typed empty/unauthorized result, never an uncaught throw", "a public query that resolves org via a fail-closed non-throwing path or documents its client contract — it never throws on the missing-org path a subscribing client cannot catch", scanR50, "R-50: no public query matched in the scanned tree — no reactive read to certify. NOT a clean bill.");
|
|
@@ -141,7 +141,9 @@ function discoverWriteBuilders(files) {
|
|
|
141
141
|
}
|
|
142
142
|
function definitionOpener(raw, set) {
|
|
143
143
|
const names = [...set].sort((a, z) => z.length - a.length);
|
|
144
|
-
const alt = names
|
|
144
|
+
const alt = names
|
|
145
|
+
.map((n) => n.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"))
|
|
146
|
+
.join("|");
|
|
145
147
|
const re = new RegExp(`(?:export\\s+)?const\\s+([A-Za-z0-9_]+)\\s*=\\s*(${alt})\\s*\\(`);
|
|
146
148
|
const m = re.exec(raw);
|
|
147
149
|
return m ? { name: m[1], builder: m[2] } : null;
|
|
@@ -169,6 +171,13 @@ function mutationBlock(lines, i) {
|
|
|
169
171
|
export function scanR51(files) {
|
|
170
172
|
const scan = emptyScan();
|
|
171
173
|
const builders = discoverWriteBuilders(files);
|
|
174
|
+
// Public writes that matched a definition opener but exhibit NO org-throw, NO
|
|
175
|
+
// typed-unauthorized return, and NO write-contract marker. They are not gated
|
|
176
|
+
// subjects, so they were historically SKIPPED — which, when EVERY public write
|
|
177
|
+
// in the tree is such a bare write, produced a false "no public mutation
|
|
178
|
+
// matched" abstain (a silent pass over a fully-ungated public-write surface).
|
|
179
|
+
// Recorded here so the otherwise-empty branch below can surface them.
|
|
180
|
+
const ungated = [];
|
|
172
181
|
for (const a of builders.ambiguous) {
|
|
173
182
|
scan.cnj.push({
|
|
174
183
|
rel: a.rel,
|
|
@@ -236,8 +245,17 @@ export function scanR51(files) {
|
|
|
236
245
|
const throwsOnNoOrg = ORG_THROW.test(blockText) || WRITE_GATE_HELPER.test(blockText);
|
|
237
246
|
const typedUnauth = TYPED_UNAUTH_RETURN.test(blockText);
|
|
238
247
|
const hasMarker = CONTRACT_MARKER.test(blockAll);
|
|
239
|
-
if (!throwsOnNoOrg && !typedUnauth && !hasMarker)
|
|
240
|
-
|
|
248
|
+
if (!throwsOnNoOrg && !typedUnauth && !hasMarker) {
|
|
249
|
+
// A bare public write with no visible org isolation. Not a gated
|
|
250
|
+
// subject — but recorded so a tree whose ENTIRE public-write surface
|
|
251
|
+
// is bare is judged (CNJ-with-sites) rather than falsely abstained.
|
|
252
|
+
ungated.push({
|
|
253
|
+
rel: f.rel,
|
|
254
|
+
n: block.lines[0].n,
|
|
255
|
+
note: `${fn}: public mutation with no org-throw, no typed-unauthorized return, and no write-contract marker — no org-isolation handling is visible in convex/, and whether a client can reach it before an org exists is not statically decidable; a reviewer must confirm this write isolates by org (never a silent clean bill)`,
|
|
256
|
+
});
|
|
257
|
+
continue; // not a gated subject
|
|
258
|
+
}
|
|
241
259
|
if (typedUnauth) {
|
|
242
260
|
// The write returns a shape on the no-org path — a pre-org client can render
|
|
243
261
|
// it, not an uncaught Server Error. Green.
|
|
@@ -295,6 +313,19 @@ export function scanR51(files) {
|
|
|
295
313
|
}
|
|
296
314
|
}
|
|
297
315
|
}
|
|
316
|
+
// A tree whose ENTIRE public-write surface is ungated (no throw / typed-unauth /
|
|
317
|
+
// marker on ANY write) leaves the scan otherwise empty. The legacy abstain then
|
|
318
|
+
// claimed "no public mutation matched" — FALSE when bare public writes exist, and
|
|
319
|
+
// a silent pass over a fully-ungated backend. Surface those writes as
|
|
320
|
+
// could-not-judge sites so the verdict is CNJ-with-sites, not a misleading empty
|
|
321
|
+
// abstain. Gated on "scan otherwise empty" so a tree that already produced
|
|
322
|
+
// subjects (VP: 7 marker CNJ) is byte-for-byte unchanged — the bare-write
|
|
323
|
+
// disposition in a mixed tree is out of this fix's scope.
|
|
324
|
+
if (scan.conform.length + scan.violate.length + scan.cnj.length === 0 &&
|
|
325
|
+
ungated.length > 0) {
|
|
326
|
+
for (const u of ungated)
|
|
327
|
+
scan.cnj.push(u);
|
|
328
|
+
}
|
|
298
329
|
return scan;
|
|
299
330
|
}
|
|
300
331
|
export const R51 = srcDetector("R-51", "R-51 a public mutation returns a typed unauthorized result on the no-org path, never an uncaught throw a pre-org client shell cannot render", "a public write either returns a typed unauthorized result / documents its client contract, or a human confirms a pre-org client cannot reach it — it never throws NO_ORG on a path an ordinary render can hit", scanR51, "R-51: no public mutation matched in the scanned tree — no write to certify. NOT a clean bill.");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vantageos/backend-doctor",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
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",
|