@vantageos/backend-doctor 0.2.0 → 0.4.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 +57 -4
- package/dist/args.d.ts +1 -1
- package/dist/args.js +19 -1
- package/dist/build-stamp.json +2 -2
- package/dist/caller-resolution.js +11 -8
- package/dist/cli.js +164 -15
- package/dist/contract.d.ts +25 -7
- package/dist/contract.js +52 -7
- package/dist/coverage.d.ts +1 -1
- package/dist/coverage.js +2 -2
- package/dist/detectors/oracle-axis1.js +5 -4
- package/dist/detectors/oracle-axis2.d.ts +6 -0
- package/dist/detectors/oracle-axis2.js +32 -16
- package/dist/detectors/predicates.js +12 -3
- package/dist/detectors/shared.js +3 -2
- package/dist/detectors/source/_ast.d.ts +92 -0
- package/dist/detectors/source/_ast.js +353 -0
- package/dist/detectors/source/_scan.js +4 -3
- package/dist/detectors/source/backfill.js +62 -17
- package/dist/detectors/source/contract.js +21 -13
- package/dist/detectors/source/credential-hygiene.d.ts +11 -0
- package/dist/detectors/source/credential-hygiene.js +619 -0
- package/dist/detectors/source/cross-tenant-write.d.ts +46 -0
- package/dist/detectors/source/cross-tenant-write.js +1408 -0
- package/dist/detectors/source/guard-helper-flow.d.ts +22 -0
- package/dist/detectors/source/guard-helper-flow.js +49 -6
- package/dist/detectors/source/identity-by-id.d.ts +80 -0
- package/dist/detectors/source/identity-by-id.js +1783 -0
- package/dist/detectors/source/identity.d.ts +8 -1
- package/dist/detectors/source/identity.js +364 -35
- package/dist/detectors/source/jobs.js +13 -2
- package/dist/detectors/source/lifecycle.js +14 -2
- package/dist/detectors/source/local-identity.d.ts +26 -0
- package/dist/detectors/source/local-identity.js +666 -0
- package/dist/detectors/source/narrow-then-bound.d.ts +11 -0
- package/dist/detectors/source/narrow-then-bound.js +571 -0
- package/dist/detectors/source/package-principal.d.ts +9 -0
- package/dist/detectors/source/package-principal.js +177 -0
- package/dist/detectors/source/r6-shapes.d.ts +20 -0
- package/dist/detectors/source/r6-shapes.js +285 -0
- package/dist/detectors/source/readbudget.js +291 -0
- package/dist/detectors/source/recipient-by-id.d.ts +40 -0
- package/dist/detectors/source/recipient-by-id.js +1108 -0
- package/dist/detectors/source/refusal.d.ts +14 -0
- package/dist/detectors/source/refusal.js +75 -2
- package/dist/detectors/source/security.js +455 -26
- package/dist/detectors/source/service-account-claim.d.ts +15 -0
- package/dist/detectors/source/service-account-claim.js +491 -0
- package/dist/detectors/source/writesafety.js +27 -14
- package/dist/detectors/standard/_doc.d.ts +1 -1
- package/dist/detectors/standard/_doc.js +5 -4
- package/dist/detectors/standard/r41-frontier.js +3 -2
- package/dist/detectors/standard/r46-returns-validator.js +3 -2
- package/dist/detectors/structural.d.ts +6 -7
- package/dist/detectors/structural.js +75 -43
- package/dist/not-checkable.d.ts +1 -1
- package/dist/not-checkable.js +12 -0
- package/dist/registry.d.ts +1 -1
- package/dist/registry.js +55 -3
- package/dist/rule-class.d.ts +85 -0
- package/dist/rule-class.js +218 -0
- package/dist/tally.d.ts +13 -5
- package/dist/tally.js +14 -6
- package/dist/violation-count.d.ts +48 -0
- package/dist/violation-count.js +94 -0
- package/fixtures/backend-standard-2825122.md +722 -0
- package/fixtures/backend-standard-4dcd3e0.md +722 -0
- package/fixtures/backend-standard-bacc54a.md +785 -0
- package/fixtures/rule-classes-bacc54a.json +244 -0
- package/package.json +1 -1
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { hasJustificationTokenInText } from "../oracle-axis2.js";
|
|
1
2
|
import { base, code, emptyScan, isJudgeable, SOURCE_STANDARD_PIN, srcDetector, } from "./_scan.js";
|
|
2
3
|
// A schema definition file — where defineTable declarations live.
|
|
3
4
|
const SCHEMA_FILE = /schema\.ts$/;
|
|
@@ -104,7 +105,9 @@ export function scanR44(files) {
|
|
|
104
105
|
let directInsert = false;
|
|
105
106
|
let loopsSource = false;
|
|
106
107
|
let referenced = false;
|
|
108
|
+
let migText = "";
|
|
107
109
|
for (const f of migFiles) {
|
|
110
|
+
migText += `${f.lines.map((l) => l.raw).join("\n")}\n`;
|
|
108
111
|
for (const l of f.lines) {
|
|
109
112
|
if (!code(l))
|
|
110
113
|
continue;
|
|
@@ -126,18 +129,39 @@ export function scanR44(files) {
|
|
|
126
129
|
});
|
|
127
130
|
}
|
|
128
131
|
else if (directInsert) {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
132
|
+
// judgement retired (task k179jnhx, §23): flagged (no visible query/
|
|
133
|
+
// paginate of the source table); decides on the migration file's written
|
|
134
|
+
// JUSTIFIÉ:/JUSTIFIED: token, never a person's opinion of coverage.
|
|
135
|
+
if (hasJustificationTokenInText(migText)) {
|
|
136
|
+
scan.conform.push({
|
|
137
|
+
rel: d.rel,
|
|
138
|
+
n: d.n,
|
|
139
|
+
note: `derived structure ${d.table} has a migration inserting it; no query/paginate of ${d.source} is visible, but the migration file carries a written JUSTIFIÉ:/JUSTIFIED: token — accepted, not silently passed`,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
scan.violate.push({
|
|
144
|
+
rel: d.rel,
|
|
145
|
+
n: d.n,
|
|
146
|
+
note: `derived structure ${d.table} has a migration inserting it, but no query/paginate of the source table ${d.source} is visible, and no written JUSTIFIÉ:/JUSTIFIED: token in the migration file — coverage of existing source rows owes a fix or a recorded justification`,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
134
149
|
}
|
|
135
150
|
else if (referenced) {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
151
|
+
if (hasJustificationTokenInText(migText)) {
|
|
152
|
+
scan.conform.push({
|
|
153
|
+
rel: d.rel,
|
|
154
|
+
n: d.n,
|
|
155
|
+
note: `derived structure ${d.table} has a backfill migration NAMED for it, writing the structure via a helper; the migration file carries a written JUSTIFIÉ:/JUSTIFIED: token — accepted, not silently passed`,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
scan.violate.push({
|
|
160
|
+
rel: d.rel,
|
|
161
|
+
n: d.n,
|
|
162
|
+
note: `derived structure ${d.table} has a backfill migration NAMED for it, but it writes the structure via a helper (no direct ctx.db.insert("${d.table}") in the migration), and no written JUSTIFIÉ:/JUSTIFIED: token in the migration file — idempotence/coverage owes a fix or a recorded justification`,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
141
165
|
}
|
|
142
166
|
else {
|
|
143
167
|
scan.violate.push({
|
|
@@ -154,10 +178,12 @@ export const R44 = srcDetector("R-44", "R-44 a derived structure carries a same-
|
|
|
154
178
|
// R-45 — a derived structure carries a test whose fixture bypasses the write
|
|
155
179
|
// path and brackets the migration.
|
|
156
180
|
// ===========================================================================
|
|
181
|
+
const R45_TOKEN_WINDOW = 6;
|
|
157
182
|
export function scanR45(files) {
|
|
158
183
|
const scan = emptyScan();
|
|
159
184
|
const derived = derivedStructures(files);
|
|
160
185
|
const testFiles = files.filter(isTest);
|
|
186
|
+
const byRel = new Map(files.map((f) => [f.rel, f]));
|
|
161
187
|
for (const d of derived) {
|
|
162
188
|
const insertSourceRe = INSERT_OF(d.source);
|
|
163
189
|
const stem = d.table.toLowerCase();
|
|
@@ -200,13 +226,32 @@ export function scanR45(files) {
|
|
|
200
226
|
});
|
|
201
227
|
}
|
|
202
228
|
else {
|
|
203
|
-
// No test reaches the subject at all —
|
|
204
|
-
//
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
229
|
+
// No test reaches the subject at all — flagged. judgement retired
|
|
230
|
+
// (task k179jnhx, §23): the schema declaration's comment window is this
|
|
231
|
+
// site's remediation cell. No JUSTIFIÉ:/JUSTIFIED: token → non-conforming;
|
|
232
|
+
// token present → present, decided by the token's presence, never a
|
|
233
|
+
// person's opinion of whether the untested state is sound.
|
|
234
|
+
const declFile = byRel.get(d.rel);
|
|
235
|
+
const window = declFile
|
|
236
|
+
? declFile.lines
|
|
237
|
+
.slice(Math.max(0, d.n - 1 - R45_TOKEN_WINDOW), d.n - 1 + R45_TOKEN_WINDOW)
|
|
238
|
+
.map((l) => l.raw)
|
|
239
|
+
.join("\n")
|
|
240
|
+
: "";
|
|
241
|
+
if (hasJustificationTokenInText(window)) {
|
|
242
|
+
scan.conform.push({
|
|
243
|
+
rel: d.rel,
|
|
244
|
+
n: d.n,
|
|
245
|
+
note: `derived structure ${d.table}: no test constructs its source row ${d.source} without the write path, but the schema declaration's comment window carries a written JUSTIFIÉ:/JUSTIFIED: token — accepted, not silently passed`,
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
scan.violate.push({
|
|
250
|
+
rel: d.rel,
|
|
251
|
+
n: d.n,
|
|
252
|
+
note: `derived structure ${d.table}: no test constructs its source row ${d.source} without the write path (and no create-only fixture to mark red), and no written JUSTIFIÉ:/JUSTIFIED: token near its schema declaration — fixture-independence owes a test or a recorded justification`,
|
|
253
|
+
});
|
|
254
|
+
}
|
|
210
255
|
}
|
|
211
256
|
}
|
|
212
257
|
return scan;
|
|
@@ -18,6 +18,8 @@
|
|
|
18
18
|
// zero-applicable-site abstain that resolves to could-not-judge WITH a reason.
|
|
19
19
|
import { existsSync } from "node:fs";
|
|
20
20
|
import { join } from "node:path";
|
|
21
|
+
import { siteCount, withCount } from "../../violation-count.js";
|
|
22
|
+
import { hasJustificationTokenInText } from "../oracle-axis2.js";
|
|
21
23
|
/** The standard all four are pinned against (short hash a9d7877b). */
|
|
22
24
|
export const SOURCE_CONTRACT_STANDARD_PIN = "analysis/vantagepeers/backend-standard/backend-standard.md @ a9d7877b4947ed8ae61d234433debb2182a2ba44 (elpiarthera/ElPi-Corp) §15";
|
|
23
25
|
function emptyScan() {
|
|
@@ -73,12 +75,12 @@ function decide(ruleId, id, check, scan, emptyReason) {
|
|
|
73
75
|
const breakdown = `[conform=${nC} non-conforming=${nV} could-not-judge=${nJ}]`;
|
|
74
76
|
const fmt = (s) => s.slice(0, 3).map((x) => `${x.rel}:${x.n} (${x.note})`).join("; ");
|
|
75
77
|
if (nV > 0) {
|
|
76
|
-
return {
|
|
78
|
+
return withCount({
|
|
77
79
|
id,
|
|
78
80
|
anchor: `${scan.violate[0].rel}:${scan.violate[0].n}`,
|
|
79
81
|
state: "non-conforming",
|
|
80
82
|
verdict: `${ruleId}: ${nV} site(s) VIOLATE (${check}). ${breakdown} Violations: ${fmt(scan.violate)}.${nC ? ` Conform site(s): ${fmt(scan.conform)}.` : ""}`,
|
|
81
|
-
};
|
|
83
|
+
}, siteCount(scan.violate));
|
|
82
84
|
}
|
|
83
85
|
if (nJ > 0) {
|
|
84
86
|
const reason = `${ruleId}: ${nJ} site(s) could not be judged — ${fmt(scan.cnj)}. This is NOT a clean bill. ${breakdown}${nC ? ` Conform site(s): ${fmt(scan.conform)}.` : ""}`;
|
|
@@ -320,15 +322,19 @@ export function scanR25(files) {
|
|
|
320
322
|
// authority-tier / boundary-key declaration: rag has only an import comment; the
|
|
321
323
|
// agentEngine block documents durability/versioning ("long-task survival",
|
|
322
324
|
// "Pinned exactly at 0.1.0-alpha.2"), not an isolation contract. Per §8 an
|
|
323
|
-
// undeclared seam is non-conforming as an app-owned table with a blank field
|
|
324
|
-
//
|
|
325
|
-
//
|
|
326
|
-
//
|
|
327
|
-
//
|
|
328
|
-
//
|
|
325
|
+
// undeclared seam is non-conforming as an app-owned table with a blank field.
|
|
326
|
+
// judgement retired (task k179jnhx, §23): whether the library's tables are in
|
|
327
|
+
// fact used fleet-internal/master only (which would justify the silence) is no
|
|
328
|
+
// longer a human disposition — it decides on the PRESENCE of a written
|
|
329
|
+
// JUSTIFIÉ:/JUSTIFIED: token in the seam's comment window (that window IS this
|
|
330
|
+
// site's remediation cell, the same mechanism R-7/R-8 use on an oracle row). No
|
|
331
|
+
// token → non-conforming; token present → present. could-not-judge is reserved
|
|
332
|
+
// for a seam the instrument could not read, never reached here (a parsed
|
|
333
|
+
// app.use() call always resolves to one of the two).
|
|
329
334
|
// PATTERN: each `app.use(<component>)` line in a *.config.ts seam is conform iff its
|
|
330
335
|
// surrounding window (comments included — the seam contract is documented prose)
|
|
331
|
-
// declares an authority tier + boundary key
|
|
336
|
+
// declares an authority tier + boundary key, OR carries a written justification
|
|
337
|
+
// token; else non-conforming.
|
|
332
338
|
// ---------------------------------------------------------------------------
|
|
333
339
|
const MOUNT_CALL = /app\.use\s*\(\s*(\w+)/;
|
|
334
340
|
const SEAM_CONTRACT = /authority tier|boundary key|isolation contract|org-scoped|fleet-internal|master-only|namespace-bound|tenant boundary/i;
|
|
@@ -350,14 +356,16 @@ export function scanR26(files) {
|
|
|
350
356
|
continue;
|
|
351
357
|
const comp = m[1];
|
|
352
358
|
// seam contract is documented as prose — scan ALL lines in the window.
|
|
353
|
-
const
|
|
354
|
-
|
|
355
|
-
.some((x) => SEAM_CONTRACT.test(x.raw));
|
|
359
|
+
const window = f.lines.slice(Math.max(0, i - R26_WINDOW), i + R26_WINDOW);
|
|
360
|
+
const declared = window.some((x) => SEAM_CONTRACT.test(x.raw));
|
|
356
361
|
if (declared) {
|
|
357
362
|
scan.conform.push({ rel: f.rel, n: l.n, note: `component "${comp}" mount declares an authority tier + boundary key at the seam` });
|
|
358
363
|
}
|
|
364
|
+
else if (hasJustificationTokenInText(window.map((x) => x.raw).join("\n"))) {
|
|
365
|
+
scan.conform.push({ rel: f.rel, n: l.n, note: `component "${comp}" mounted with no stated authority tier / boundary key, but the seam's comment window carries a written JUSTIFIÉ:/JUSTIFIED: token — accepted, not silently passed` });
|
|
366
|
+
}
|
|
359
367
|
else {
|
|
360
|
-
scan.
|
|
368
|
+
scan.violate.push({ rel: f.rel, n: l.n, note: `component "${comp}" mounted with no stated authority tier / boundary key at the seam, and no written JUSTIFIÉ:/JUSTIFIED: token in the seam's comment window — usable fleet-internal/master only; §8 owes a fix or a recorded justification` });
|
|
361
369
|
}
|
|
362
370
|
}
|
|
363
371
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Detector } from "../../engine-types.js";
|
|
2
|
+
import { type Scan, type SourceDetectorEntry, type SrcFile } from "./_scan.js";
|
|
3
|
+
export declare const R56_ID = "R-56 credentials held as digests of generated secrets, refresh tokens single-use, bearers revocable and session-minted bearers bounded, verification tokens matched in full";
|
|
4
|
+
export declare const R56_STANDARD_PIN = "vantagepeers/backend-standard/backend-standard.md @ bacc54a53d5f6f9f20e5254908e2b46807902faa \u00A74 R-56";
|
|
5
|
+
export interface R56Scan extends Scan {
|
|
6
|
+
/** units read. */
|
|
7
|
+
units: number;
|
|
8
|
+
}
|
|
9
|
+
export declare function scanR56(files: SrcFile[]): R56Scan;
|
|
10
|
+
export declare const R56: Detector;
|
|
11
|
+
export declare const SOURCE_CREDENTIAL_HYGIENE_DETECTORS: SourceDetectorEntry[];
|