@cosmicdrift/kumiko-guards 0.284.0 → 0.285.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmicdrift/kumiko-guards",
3
- "version": "0.284.0",
3
+ "version": "0.285.1",
4
4
  "description": "AST-based security guards for Kumiko repos: direct-fs/fetch, tenant escalation, admin-API, escape hatches and related checks, run over a shared ts-morph project.",
5
5
  "license": "BUSL-1.1",
6
6
  "author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
@@ -27,7 +27,7 @@
27
27
  "kumiko-guards": "./src/cli.ts"
28
28
  },
29
29
  "dependencies": {
30
- "@cosmicdrift/kumiko-repo-manifest": "0.284.0",
30
+ "@cosmicdrift/kumiko-repo-manifest": "0.285.1",
31
31
  "ts-morph": "^28.0.0"
32
32
  },
33
33
  "publishConfig": {
@@ -9,7 +9,8 @@ export type ScanExtension = "ts" | "tsx";
9
9
 
10
10
  type ScanSpecBase = {
11
11
  readonly extensions: readonly ScanExtension[];
12
- /** Manifest roles to scan; omitted = every root. */
12
+ /** Manifest roles to scan; omitted = every root except "tooling" — a guard
13
+ * must opt in to scanning an infra/build-tooling root explicitly. */
13
14
  readonly kinds?: readonly RepoKind[];
14
15
  /** Repo-relative globs; in a kind "framework" root only files matching one of them are kept (replaces `within` there). */
15
16
  readonly frameworkWithin?: readonly string[];
@@ -141,10 +142,13 @@ function scanRoot(spec: ScanSpec, root: RepoRoot): RootScan {
141
142
  return { root, files, sourceSurface: sourceSurfaceHits.length };
142
143
  }
143
144
 
145
+ function keepsRootKind(spec: ScanSpec, root: RepoRoot): boolean {
146
+ if (spec.kinds) return spec.kinds.includes(root.kind);
147
+ return root.kind !== "tooling";
148
+ }
149
+
144
150
  export function scanRoots(spec: ScanSpec, roots: readonly RepoRoot[]): RootScan[] {
145
- return roots
146
- .filter((root) => !spec.kinds || spec.kinds.includes(root.kind))
147
- .map((root) => scanRoot(spec, root));
151
+ return roots.filter((root) => keepsRootKind(spec, root)).map((root) => scanRoot(spec, root));
148
152
  }
149
153
 
150
154
  export function scanFiles(spec: ScanSpec, roots: readonly RepoRoot[]): string[] {
package/src/changes.json CHANGED
@@ -1,4 +1,10 @@
1
1
  [
2
+ {
3
+ "version": "0.285.1",
4
+ "type": "fix",
5
+ "title": "Fix false-positive BLOCK in the Direct-Entity-Writes Guard for repos with no event store",
6
+ "detail": "An empty `esTables` set had one meaning: the scan lost track of ES definitions while table-write code still exists, so the guard reported a misconfiguration canary and blocked (the guard is `security: true`, so this always failed CI). That conflated two different situations: a repo with no event store at all (e.g. `kumiko-platform`, an Astro docs/marketing site) also has an empty `esTables` set, but there is nothing to guard against there. The guard now checks whether any table write is present at all before treating an empty `esTables` set as a misconfiguration; a repo with no ES and no table writes is green, while a repo with table writes but no resolvable ES tables still blocks as before."
7
+ },
2
8
  {
3
9
  "version": "0.284.0",
4
10
  "type": "fix",
@@ -287,6 +287,16 @@ function resolveWrite(
287
287
  return undefined;
288
288
  }
289
289
 
290
+ function hasAnyTableWrite(files: readonly SourceFile[]): boolean {
291
+ for (const sf of files) {
292
+ if (EXCLUDE.test(sf.getFilePath())) continue;
293
+ for (const call of sf.getDescendantsOfKind(SyntaxKind.CallExpression)) {
294
+ if (resolveWrite(call)) return true;
295
+ }
296
+ }
297
+ return false;
298
+ }
299
+
290
300
  export function scanDirectWrites(
291
301
  sf: SourceFile,
292
302
  esTables: ReadonlySet<TableId>,
@@ -351,13 +361,16 @@ export const guard: AstGuard = {
351
361
  ...collectEntityProjectionTables(files),
352
362
  ]);
353
363
  if (esTables.size === 0) {
364
+ // An empty set means either the repo has no event store at all, or the
365
+ // scan lost the ES definitions while table writes are still present.
366
+ if (!hasAnyTableWrite(files)) return { violations: [] };
354
367
  return {
355
368
  violations: [
356
369
  {
357
370
  file: "<scan>",
358
371
  line: 0,
359
372
  message:
360
- "BLOCKED: guard found no createEventStoreExecutor or r.entity projection tables — scan is probably misconfigured.",
373
+ "BLOCKED: guard found table writes but no createEventStoreExecutor or r.entity projection tables — scan is probably misconfigured.",
361
374
  },
362
375
  ],
363
376
  };