@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.
Files changed (70) hide show
  1. package/README.md +57 -4
  2. package/dist/args.d.ts +1 -1
  3. package/dist/args.js +19 -1
  4. package/dist/build-stamp.json +2 -2
  5. package/dist/caller-resolution.js +11 -8
  6. package/dist/cli.js +164 -15
  7. package/dist/contract.d.ts +25 -7
  8. package/dist/contract.js +52 -7
  9. package/dist/coverage.d.ts +1 -1
  10. package/dist/coverage.js +2 -2
  11. package/dist/detectors/oracle-axis1.js +5 -4
  12. package/dist/detectors/oracle-axis2.d.ts +6 -0
  13. package/dist/detectors/oracle-axis2.js +32 -16
  14. package/dist/detectors/predicates.js +12 -3
  15. package/dist/detectors/shared.js +3 -2
  16. package/dist/detectors/source/_ast.d.ts +92 -0
  17. package/dist/detectors/source/_ast.js +353 -0
  18. package/dist/detectors/source/_scan.js +4 -3
  19. package/dist/detectors/source/backfill.js +62 -17
  20. package/dist/detectors/source/contract.js +21 -13
  21. package/dist/detectors/source/credential-hygiene.d.ts +11 -0
  22. package/dist/detectors/source/credential-hygiene.js +619 -0
  23. package/dist/detectors/source/cross-tenant-write.d.ts +46 -0
  24. package/dist/detectors/source/cross-tenant-write.js +1408 -0
  25. package/dist/detectors/source/guard-helper-flow.d.ts +22 -0
  26. package/dist/detectors/source/guard-helper-flow.js +49 -6
  27. package/dist/detectors/source/identity-by-id.d.ts +80 -0
  28. package/dist/detectors/source/identity-by-id.js +1783 -0
  29. package/dist/detectors/source/identity.d.ts +8 -1
  30. package/dist/detectors/source/identity.js +364 -35
  31. package/dist/detectors/source/jobs.js +13 -2
  32. package/dist/detectors/source/lifecycle.js +14 -2
  33. package/dist/detectors/source/local-identity.d.ts +26 -0
  34. package/dist/detectors/source/local-identity.js +666 -0
  35. package/dist/detectors/source/narrow-then-bound.d.ts +11 -0
  36. package/dist/detectors/source/narrow-then-bound.js +571 -0
  37. package/dist/detectors/source/package-principal.d.ts +9 -0
  38. package/dist/detectors/source/package-principal.js +177 -0
  39. package/dist/detectors/source/r6-shapes.d.ts +20 -0
  40. package/dist/detectors/source/r6-shapes.js +285 -0
  41. package/dist/detectors/source/readbudget.js +291 -0
  42. package/dist/detectors/source/recipient-by-id.d.ts +40 -0
  43. package/dist/detectors/source/recipient-by-id.js +1108 -0
  44. package/dist/detectors/source/refusal.d.ts +14 -0
  45. package/dist/detectors/source/refusal.js +75 -2
  46. package/dist/detectors/source/security.js +455 -26
  47. package/dist/detectors/source/service-account-claim.d.ts +15 -0
  48. package/dist/detectors/source/service-account-claim.js +491 -0
  49. package/dist/detectors/source/writesafety.js +27 -14
  50. package/dist/detectors/standard/_doc.d.ts +1 -1
  51. package/dist/detectors/standard/_doc.js +5 -4
  52. package/dist/detectors/standard/r41-frontier.js +3 -2
  53. package/dist/detectors/standard/r46-returns-validator.js +3 -2
  54. package/dist/detectors/structural.d.ts +6 -7
  55. package/dist/detectors/structural.js +75 -43
  56. package/dist/not-checkable.d.ts +1 -1
  57. package/dist/not-checkable.js +12 -0
  58. package/dist/registry.d.ts +1 -1
  59. package/dist/registry.js +55 -3
  60. package/dist/rule-class.d.ts +85 -0
  61. package/dist/rule-class.js +218 -0
  62. package/dist/tally.d.ts +13 -5
  63. package/dist/tally.js +14 -6
  64. package/dist/violation-count.d.ts +48 -0
  65. package/dist/violation-count.js +94 -0
  66. package/fixtures/backend-standard-2825122.md +722 -0
  67. package/fixtures/backend-standard-4dcd3e0.md +722 -0
  68. package/fixtures/backend-standard-bacc54a.md +785 -0
  69. package/fixtures/rule-classes-bacc54a.json +244 -0
  70. 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
- scan.cnj.push({
130
- rel: d.rel,
131
- n: d.n,
132
- note: `derived structure ${d.table} has a migration inserting it, but no query/paginate of the source table ${d.source} is visible — coverage of existing source rows is not statically decidable (name the migration site)`,
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
- scan.cnj.push({
137
- rel: d.rel,
138
- n: d.n,
139
- 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) — its idempotence/coverage of existing source rows is not statically decidable (human verifies)`,
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 — the test-independence property
204
- // cannot be judged. A could-not-judge work-item, never a silent green.
205
- scan.cnj.push({
206
- rel: d.rel,
207
- n: d.n,
208
- 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) — its fixture-independence is not statically decidable; a human must verify a test brackets the migration before AND after`,
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
- // but the DISPOSITION (whether the library's tables are in fact used
325
- // fleet-internal/master only, which would justify the silence) is human. So the
326
- // detector flags an undeclared mount as could-not-judge WITH a reason (never a
327
- // silent pass); a mount whose window declares an authority tier + boundary key
328
- // is conform.
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; else could-not-judge.
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 declared = f.lines
354
- .slice(Math.max(0, i - R26_WINDOW), i + R26_WINDOW)
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.cnj.push({ rel: f.rel, n: l.n, note: `component "${comp}" mounted with no stated authority tier / boundary key at the seam — usable fleet-internal/master only; org-scoped disposition needs a human ruling (§8)` });
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[];