@vantageos/backend-doctor 0.1.13 → 0.2.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.
- package/README.md +19 -2
- package/dist/args.d.ts +16 -0
- package/dist/args.js +54 -0
- package/dist/build-stamp.d.ts +13 -0
- package/dist/build-stamp.js +76 -0
- package/dist/build-stamp.json +4 -0
- package/dist/caller-resolution.d.ts +222 -0
- package/dist/caller-resolution.js +1195 -0
- package/dist/cli.js +217 -10
- package/dist/coverage-cli.js +8 -0
- package/dist/crud-coverage.d.ts +81 -0
- package/dist/crud-coverage.js +375 -0
- package/dist/detectors/oracle-axis1.d.ts +5 -3
- package/dist/detectors/oracle-axis1.js +24 -15
- package/dist/detectors/oracle-axis2.js +5 -6
- package/dist/detectors/predicates.d.ts +3 -0
- package/dist/detectors/predicates.js +81 -16
- package/dist/detectors/shared.js +5 -5
- package/dist/detectors/source/_scan.js +6 -1
- package/dist/detectors/source/backfill.js +9 -1
- package/dist/detectors/source/guard-helper-flow.d.ts +112 -0
- package/dist/detectors/source/guard-helper-flow.js +1173 -0
- package/dist/detectors/source/identity.d.ts +51 -2
- package/dist/detectors/source/identity.js +815 -14
- package/dist/detectors/source/isolation.d.ts +11 -1
- package/dist/detectors/source/isolation.js +106 -25
- package/dist/detectors/source/jobs.js +165 -2
- package/dist/detectors/source/readbudget.d.ts +1 -0
- package/dist/detectors/source/readbudget.js +67 -4
- package/dist/detectors/source/security.d.ts +16 -0
- package/dist/detectors/source/security.js +225 -7
- package/dist/detectors/source/write-isolation.d.ts +8 -1
- package/dist/detectors/source/write-isolation.js +64 -30
- package/dist/detectors/standard/r46-returns-validator.d.ts +8 -0
- package/dist/detectors/standard/r46-returns-validator.js +45 -14
- package/dist/detectors/structural.js +14 -10
- package/dist/oracle.d.ts +92 -3
- package/dist/oracle.js +229 -12
- package/dist/registry.d.ts +7 -0
- package/dist/registry.js +48 -6
- package/dist/report.d.ts +11 -1
- package/dist/report.js +15 -2
- package/dist/state-rank.d.ts +5 -0
- package/dist/state-rank.js +9 -0
- package/dist/tally.d.ts +35 -0
- package/dist/tally.js +76 -8
- package/dist/target.d.ts +32 -0
- package/dist/target.js +173 -0
- package/fixtures/cr1-classes/_generated/api.d.ts +24 -0
- package/fixtures/cr1-classes/surface.ts +206 -0
- package/fixtures/cr1-empty/nothing.ts +1 -0
- package/fixtures/cr1-refusal-on-resolved/_generated/api.d.ts +24 -0
- package/fixtures/cr1-refusal-on-resolved/doors.ts +180 -0
- package/fixtures/cr1-refusal-on-resolved/lib/auth.ts +104 -0
- package/fixtures/r34-vp-auth-30e821b/auth-805-812.ts.txt +8 -0
- package/fixtures/r34-vp-auth-30e821b/auth-878-890.ts.txt +13 -0
- package/fixtures/vp-writer-tier-1e4ab36.csv +55 -0
- package/package.json +2 -2
package/dist/detectors/shared.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isInventoryRow, readOracle } from "../oracle.js";
|
|
1
|
+
import { isInventoryRow, oracleLabel, readOracle, refuseWithoutOracle, } from "../oracle.js";
|
|
2
2
|
/** Run a predicate over a row set, tallying per-kind. */
|
|
3
3
|
export function tally(spec, rows) {
|
|
4
4
|
const t = { conform: 0, violate: 0, cnj: 0, na: 0, violations: [], unjudged: [] };
|
|
@@ -52,7 +52,7 @@ export function evaluate(spec, rows) {
|
|
|
52
52
|
.join("; ")}. This is NOT a clean bill. ${breakdown}`;
|
|
53
53
|
return {
|
|
54
54
|
id: spec.id,
|
|
55
|
-
anchor: anchorFor(t.unjudged,
|
|
55
|
+
anchor: anchorFor(t.unjudged, oracleLabel()),
|
|
56
56
|
state: "could-not-judge",
|
|
57
57
|
verdict: reason,
|
|
58
58
|
reason,
|
|
@@ -65,14 +65,14 @@ export function evaluate(spec, rows) {
|
|
|
65
65
|
.join("; ");
|
|
66
66
|
return {
|
|
67
67
|
id: spec.id,
|
|
68
|
-
anchor: anchorFor(t.violations,
|
|
68
|
+
anchor: anchorFor(t.violations, oracleLabel()),
|
|
69
69
|
state: "non-conforming",
|
|
70
70
|
verdict: `${spec.ruleId}: ${t.violate} of ${applicable} applicable oracle rows VIOLATE (${spec.check}). ${breakdown} Examples: ${examples}.`,
|
|
71
71
|
};
|
|
72
72
|
}
|
|
73
73
|
return {
|
|
74
74
|
id: spec.id,
|
|
75
|
-
anchor:
|
|
75
|
+
anchor: oracleLabel(),
|
|
76
76
|
state: "present",
|
|
77
77
|
verdict: `${spec.ruleId}: ${t.conform} applicable oracle rows conform (${spec.check}); 0 violations. ${breakdown}`,
|
|
78
78
|
};
|
|
@@ -85,7 +85,7 @@ export function evaluate(spec, rows) {
|
|
|
85
85
|
export function makeOracleDetector(spec, rowsProvider = readOracle) {
|
|
86
86
|
return {
|
|
87
87
|
id: spec.id,
|
|
88
|
-
run: () => evaluate(spec, rowsProvider()),
|
|
88
|
+
run: () => refuseWithoutOracle(spec.id, () => evaluate(spec, rowsProvider())),
|
|
89
89
|
};
|
|
90
90
|
}
|
|
91
91
|
// ---- small shared classifiers over oracle vocabulary --------------------
|
|
@@ -40,11 +40,16 @@ export function decide(ruleId, id, check, scan, emptyReason) {
|
|
|
40
40
|
.map((x) => `${x.rel}:${x.n} (${x.note})`)
|
|
41
41
|
.join("; ");
|
|
42
42
|
if (nV > 0) {
|
|
43
|
+
// A non-conforming verdict must NAME its could-not-judge sites, not drop them
|
|
44
|
+
// to a bare integer in the breakdown — the one disposition that owes a human
|
|
45
|
+
// verdict is otherwise the only one printed without an address (Eta REVISE
|
|
46
|
+
// blocker 1: R-51 CRM could-not-judge=1 was counted but never located). The
|
|
47
|
+
// data is already in scan.cnj; list it under its own label whenever non-empty.
|
|
43
48
|
return {
|
|
44
49
|
id,
|
|
45
50
|
anchor: `${scan.violate[0].rel}:${scan.violate[0].n}`,
|
|
46
51
|
state: "non-conforming",
|
|
47
|
-
verdict: `${ruleId}: ${nV} site(s) VIOLATE (${check}). ${breakdown} Violations: ${fmt(scan.violate)}.${nC ? ` Conform site(s): ${fmt(scan.conform)}.` : ""}`,
|
|
52
|
+
verdict: `${ruleId}: ${nV} site(s) VIOLATE (${check}). ${breakdown} Violations: ${fmt(scan.violate)}.${nC ? ` Conform site(s): ${fmt(scan.conform)}.` : ""}${nJ ? ` could-not-judge sites (human verdict owed): ${fmt(scan.cnj)}.` : ""}`,
|
|
48
53
|
};
|
|
49
54
|
}
|
|
50
55
|
if (nJ > 0) {
|
|
@@ -73,7 +73,15 @@ export function derivedStructures(files) {
|
|
|
73
73
|
if (FIELD_LINE.test(l.raw))
|
|
74
74
|
fields += 1;
|
|
75
75
|
const id = ID_REF.exec(l.raw);
|
|
76
|
-
|
|
76
|
+
// A SYSTEM table (`_storage`, `_scheduled_functions` — Convex reserves the
|
|
77
|
+
// leading underscore) is not a source a migration can backfill FROM: its
|
|
78
|
+
// rows are platform objects, not domain rows, and there is no
|
|
79
|
+
// `ctx.db.query("_storage")` that enumerates what the write path would
|
|
80
|
+
// have bound. A table whose only foreign key points at a system table
|
|
81
|
+
// (a TOFU ownership binding of uploaded blobs, kbUploads) is therefore
|
|
82
|
+
// not a derived structure in R-44's sense. The first NON-system FK is
|
|
83
|
+
// the source; a table with none is skipped.
|
|
84
|
+
if (id && !fkTarget && !id[1].startsWith("_"))
|
|
77
85
|
fkTarget = id[1];
|
|
78
86
|
if (HAS_INDEX.test(l.raw))
|
|
79
87
|
hasIndex = true;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import * as ts from "typescript";
|
|
2
|
+
export type GuardHelperKind = "binds-arg" | "master-only";
|
|
3
|
+
export type HelperVerdict = {
|
|
4
|
+
verdict: "honoured";
|
|
5
|
+
} | {
|
|
6
|
+
verdict: "refused";
|
|
7
|
+
why: string;
|
|
8
|
+
} | {
|
|
9
|
+
verdict: "could-not-judge";
|
|
10
|
+
why: string;
|
|
11
|
+
};
|
|
12
|
+
/** A callee name, as seen from the function being judged, resolved to its
|
|
13
|
+
* definition in the tree (and to a resolver for names seen from THAT file). */
|
|
14
|
+
export type CallResolver = (name: string) => {
|
|
15
|
+
fn: ts.FunctionLikeDeclaration;
|
|
16
|
+
key: string;
|
|
17
|
+
resolve: CallResolver;
|
|
18
|
+
} | undefined;
|
|
19
|
+
/** Judge one helper function on its control flow. */
|
|
20
|
+
export declare function judgeHelperFunction(fn: ts.FunctionLikeDeclaration, kind: GuardHelperKind): HelperVerdict;
|
|
21
|
+
export interface FoundHelper {
|
|
22
|
+
name: string;
|
|
23
|
+
line: number;
|
|
24
|
+
exported: boolean;
|
|
25
|
+
judgement: HelperVerdict;
|
|
26
|
+
}
|
|
27
|
+
/** Every definition, in `source`, of a helper named in `kinds`. */
|
|
28
|
+
export declare function findHelperDefinitions(fileName: string, source: string, kinds: Readonly<Record<string, GuardHelperKind>>): FoundHelper[];
|
|
29
|
+
/** The arguments of each `await <helper>(…)` that is a DIRECT statement of the
|
|
30
|
+
* handler `name` (`export const name = mutation({ handler: … })`) and starts
|
|
31
|
+
* before `beforeLine`. A helper call nested in a branch, a try, or a closure of
|
|
32
|
+
* the handler is not a statement every path runs, so it is not returned.
|
|
33
|
+
* `null` = the handler's body could not be located in the file. */
|
|
34
|
+
export declare function topLevelHelperCalls(fileName: string, source: string, handlerName: string,
|
|
35
|
+
/** `null` = any identifier callee (judged by the caller on its body). */
|
|
36
|
+
helpers: ReadonlySet<string> | null, beforeLine: number): {
|
|
37
|
+
name: string;
|
|
38
|
+
args: string;
|
|
39
|
+
argNodes: readonly ts.Expression[];
|
|
40
|
+
line: number;
|
|
41
|
+
}[] | null;
|
|
42
|
+
export interface TopLevelFunction {
|
|
43
|
+
name: string;
|
|
44
|
+
line: number;
|
|
45
|
+
exported: boolean;
|
|
46
|
+
fn: ts.FunctionLikeDeclaration;
|
|
47
|
+
}
|
|
48
|
+
/** Every top-level function declaration and `const f = (…) => {…}` /
|
|
49
|
+
* `function (…) {…}` of a file. */
|
|
50
|
+
export declare function topLevelFunctions(fileName: string, source: string): TopLevelFunction[];
|
|
51
|
+
/** Judge a function as an IDENTITY RESOLVER (shape 1 above). */
|
|
52
|
+
export declare function judgeResolverFunction(fn: ts.FunctionLikeDeclaration): HelperVerdict;
|
|
53
|
+
export type ValidatorSummary = {
|
|
54
|
+
ok: false;
|
|
55
|
+
why: string;
|
|
56
|
+
} | {
|
|
57
|
+
ok: true;
|
|
58
|
+
/** every normal exit binds the target parameter (or is the master's) */
|
|
59
|
+
exitsBound: boolean;
|
|
60
|
+
/** every normal exit is the fleet master's */
|
|
61
|
+
exitsMaster: boolean;
|
|
62
|
+
/** no normal exit at all: it refuses every caller */
|
|
63
|
+
never: boolean;
|
|
64
|
+
};
|
|
65
|
+
export declare function summarizeValidator(fn: ts.FunctionLikeDeclaration, o: {
|
|
66
|
+
/** the parameter whose binding is asked; undefined = master-only asked */
|
|
67
|
+
boundIdx: number | undefined;
|
|
68
|
+
/** the parameter receiving the caller's verified scope, if any */
|
|
69
|
+
identityIdx: number | undefined;
|
|
70
|
+
/** the call's arguments (object literals become constants) */
|
|
71
|
+
callArgs: readonly ts.Expression[];
|
|
72
|
+
resolve: CallResolver;
|
|
73
|
+
key: string;
|
|
74
|
+
depth: number;
|
|
75
|
+
}): ValidatorSummary;
|
|
76
|
+
/** Judge a function as a REFUSING PREDICATE (shape 2 above): parameter
|
|
77
|
+
* `identityIdx` receives the caller's verified scope, `boundIdx` the value the
|
|
78
|
+
* caller then writes. */
|
|
79
|
+
export declare function judgePredicateFunction(fn: ts.FunctionLikeDeclaration, identityIdx: number, boundIdx: number): HelperVerdict;
|
|
80
|
+
/** What a mutation handler's own body declares and refuses before `beforeLine`. */
|
|
81
|
+
export interface HandlerGuards {
|
|
82
|
+
/** Top-level `const` locals: whether bound straight to an identity read, or
|
|
83
|
+
* to `await <callee>(…)` (the callee is then judged by the caller). */
|
|
84
|
+
consts: Map<string, {
|
|
85
|
+
identityRead: boolean;
|
|
86
|
+
awaitedCallee?: string;
|
|
87
|
+
}>;
|
|
88
|
+
/** `if (!P(a, b, …)) { …throw }` statements: the predicate and its args. */
|
|
89
|
+
refusals: {
|
|
90
|
+
predicate: string;
|
|
91
|
+
args: {
|
|
92
|
+
text: string;
|
|
93
|
+
ident?: string;
|
|
94
|
+
memberBase?: string;
|
|
95
|
+
}[];
|
|
96
|
+
line: number;
|
|
97
|
+
}[];
|
|
98
|
+
}
|
|
99
|
+
/** Read the direct statements of handler `name` before `beforeLine`. `null`
|
|
100
|
+
* when the handler's body cannot be located. */
|
|
101
|
+
export declare function readHandlerGuards(fileName: string, source: string, handlerName: string, beforeLine: number): HandlerGuards | null;
|
|
102
|
+
/** A CONDITIONAL boundary value at a write site (`orgId: c ? a : b`): which arm
|
|
103
|
+
* reads the caller's `args`, and whether the condition is a positive
|
|
104
|
+
* `<local>.isMaster` test (the fleet carve-out, which may write what the
|
|
105
|
+
* master names). Read with the TypeScript parser on the write-site text. */
|
|
106
|
+
export interface ConditionalBoundary {
|
|
107
|
+
field: string;
|
|
108
|
+
masterBase?: string;
|
|
109
|
+
trueReadsArgs: boolean;
|
|
110
|
+
falseReadsArgs: boolean;
|
|
111
|
+
}
|
|
112
|
+
export declare function conditionalBoundaries(text: string, fields: ReadonlySet<string>): ConditionalBoundary[];
|