@cosmicdrift/kumiko-guards 0.388.0 → 0.389.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 CHANGED
@@ -77,8 +77,9 @@ workspace it runs a tracked `scripts/check-wt.sh` if present, otherwise the
77
77
  worktree's own `package.json` scripts `typecheck`, `lint`, `test`, and
78
78
  `test:dom` (whichever are declared), directly in the worktree — the parent's
79
79
  `bun check` would otherwise check the main checkout instead. There is no
80
- bypass switch. A `check-wt.sh` that wraps this hook sets `KUMIKO_CHECK_WT=1`
81
- so the hook does not call it again.
80
+ bypass switch. A `check-wt.sh` that wraps this hook (via `exec` or as a child
81
+ process) is recognised by process ancestry, so the hook does not call it
82
+ again; an exported variable cannot skip it.
82
83
 
83
84
  Extension point: a tracked, executable `scripts/pre-push-extra.sh` runs
84
85
  before the main check in every branch (worktree, parent-scoped, and
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmicdrift/kumiko-guards",
3
- "version": "0.388.0",
3
+ "version": "0.389.0",
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": "MIT",
6
6
  "author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
@@ -51,7 +51,7 @@
51
51
  "LICENSE"
52
52
  ],
53
53
  "peerDependencies": {
54
- "@cosmicdrift/kumiko-framework": "0.388.0"
54
+ "@cosmicdrift/kumiko-framework": "0.389.0"
55
55
  },
56
56
  "peerDependenciesMeta": {
57
57
  "@cosmicdrift/kumiko-framework": {
@@ -59,6 +59,6 @@
59
59
  }
60
60
  },
61
61
  "devDependencies": {
62
- "@cosmicdrift/kumiko-framework": "0.388.0"
62
+ "@cosmicdrift/kumiko-framework": "0.389.0"
63
63
  }
64
64
  }
package/src/changes.json CHANGED
@@ -1,4 +1,16 @@
1
1
  [
2
+ {
3
+ "version": "0.389.0",
4
+ "type": "improvement",
5
+ "title": "Guard against hand-made event and membership SQL in tests",
6
+ "detail": "Flags INSERT INTO / UPDATE kumiko_events and CREATE TABLE read_tenant_memberships in test files. Seed through append() or runSeedWritesAt() and unsafeCreateEntityTable with seedRow instead. Perf bulk seeds and the ordering tests that hold a transaction open are allowlisted."
7
+ },
8
+ {
9
+ "version": "0.389.0",
10
+ "type": "fix",
11
+ "title": "pre-push re-entry detection no longer an env switch",
12
+ "detail": "Before delegating to a tracked scripts/check-wt.sh the hook records its own PID in KUMIKO_CHECK_WT_OWNER and treats a run as re-entry only when that PID is an ancestor of the current process, whether check-wt.sh execs or spawns the hook. KUMIKO_CHECK_WT=1 is ignored, so it cannot skip check-wt.sh; wrappers that still set it need no change."
13
+ },
2
14
  {
3
15
  "version": "0.388.0",
4
16
  "type": "improvement",
@@ -101,13 +101,13 @@ const ALLOWLIST = [
101
101
  // Number.isNaN(d.getTime()) as a validity check.
102
102
  /packages\/framework\/src\/pipeline\/entity-cache\.ts$/,
103
103
  // Event-store insertSubsequentEvent: raw SQL row.created_at can be Date|string
104
- // (postgres-js driver-config dependent), normalised to Temporal.Instant via
104
+ // (driver-config dependent), normalised to Temporal.Instant via
105
105
  // .getTime() bridge.
106
106
  /packages\/framework\/src\/event-store\/event-store\.ts$/,
107
107
  // HTTP Last-Modified/ETag header: Bun/Node Response API expects a native
108
108
  // Date object from fs.Stats.mtimeMs — wire boundary, not a Temporal format.
109
109
  /packages\/server-runtime\/src\/run-prod-app-static-files\.ts$/,
110
- // postgres-js timestamptz columns arrive as native Date (driver wire format,
110
+ // timestamptz columns arrive as native Date (driver wire format,
111
111
  // same reason as packages/framework/src/db/ above).
112
112
  /packages\/publish\/src\/rollback\.ts$/,
113
113
  ];
@@ -115,6 +115,12 @@ const ALLOWLIST: readonly AllowEntry[] = [
115
115
  repo: "kumiko-framework",
116
116
  pattern: /^packages\/server-runtime\/src\/run-prod-app-static-files\.ts$/,
117
117
  },
118
+ // Package lookup for the build and the dev server: existence check for
119
+ // node_modules/<pkg>/package.json above a directory, no request input as a path.
120
+ {
121
+ repo: "kumiko-framework",
122
+ pattern: /^packages\/server-runtime\/src\/resolve-installed-package\.ts$/,
123
+ },
118
124
 
119
125
  // create-kumiko-app: Scaffolding-CLI.
120
126
  { repo: "kumiko-framework", pattern: /^packages\/create-kumiko-app\/src\/manifest\.ts$/ },
@@ -52,6 +52,8 @@ const ALLOWLIST = [
52
52
  /^packages\/framework\/src\/event-store\/index\.ts$/,
53
53
  /^packages\/framework\/src\/event-store\/tenant-db-append\.ts$/,
54
54
  /^packages\/bundled-features\/src\/cap-counter\/cap-reservation\.ts$/,
55
+ // Catch-up of a missing terminal extract event in the caller tenant's own stream; JobContext has no appendEvent.
56
+ /^packages\/bundled-features\/src\/document-ingest-foundation\/append-missing-extract-event\.ts$/,
55
57
  /^packages\/bundled-features\/src\/user-data-rights-defaults\/hooks\/delivery-attempt\.userdata-hook\.ts$/,
56
58
  /^packages\/framework\/src\/db\/tenant-db\.ts$/,
57
59
  /^packages\/framework\/src\/db\/index\.ts$/,
@@ -0,0 +1,83 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * Guard: tests seed event-sourced state through the event store, not through
4
+ * hand-written SQL. Flags, in test files, string/template literals with
5
+ * (a) INSERT INTO / UPDATE against kumiko_events (use append / runSeedWritesAt)
6
+ * (b) CREATE TABLE ... read_tenant_memberships (use unsafeCreateEntityTable with
7
+ * tenantMembershipEntity and seedRow, so the schema cannot drift from the entity)
8
+ *
9
+ * Usage:
10
+ * bun packages/guards/src/guard-test-es-fixtures.ts
11
+ */
12
+ import type { SourceFile } from "ts-morph";
13
+ import { SyntaxKind } from "ts-morph";
14
+ import {
15
+ type AstGuard,
16
+ type GuardViolation,
17
+ relFromRepoRoot,
18
+ runStandalone,
19
+ type ScanSpec,
20
+ } from "./_lib/guard-kit";
21
+ import { type RepoRoot, resolveRepoRoots } from "./_lib/roots";
22
+
23
+ const SCAN: ScanSpec = { scope: "tests", extensions: ["ts"] };
24
+
25
+ // Bulk seeding of thousands of events through append() would take minutes; these
26
+ // perf tests measure the read path on a finished stream, not the seed phase.
27
+ // The concurrent-commit ordering tests hold a transaction open and need explicit event
28
+ // ids/versions, which append() does not expose.
29
+ // The guards' own tests carry the forbidden SQL as fixture strings.
30
+ const ALLOWLIST: readonly RegExp[] = [
31
+ /^packages\/framework\/src\/event-store\/__tests__\/perf\.integration\.test\.ts$/,
32
+ /^packages\/framework\/src\/pipeline\/__tests__\/perf-rebuild\.integration\.test\.ts$/,
33
+ /^packages\/framework\/src\/pipeline\/__tests__\/event-dispatcher-commit-order\.integration\.test\.ts$/,
34
+ /^packages\/framework\/src\/pipeline\/__tests__\/event-dispatcher-start-from\.integration\.test\.ts$/,
35
+ /^packages\/framework\/src\/pipeline\/__tests__\/msp-rebuild\.integration\.test\.ts$/,
36
+ /^packages\/framework\/src\/pipeline\/__tests__\/projection-rebuild\.integration\.test\.ts$/,
37
+ /^packages\/guards\/src\/__tests__\//,
38
+ ];
39
+
40
+ const EVENT_WRITE_RE = /\b(INSERT\s+INTO|UPDATE)\s+["`]?kumiko_events\b/i;
41
+ const MEMBERSHIP_DDL_RE = /\bCREATE\s+TABLE\b[^;]*?\bread_tenant_memberships\b/i;
42
+
43
+ const LITERAL_KINDS = [
44
+ SyntaxKind.StringLiteral,
45
+ SyntaxKind.NoSubstitutionTemplateLiteral,
46
+ SyntaxKind.TemplateExpression,
47
+ ];
48
+
49
+ function violationMessage(text: string): string | undefined {
50
+ const write = EVENT_WRITE_RE.exec(text);
51
+ if (write)
52
+ return `raw ${write[0].replace(/\s+/g, " ")} in a test: seed via append() or runSeedWritesAt()`;
53
+ if (MEMBERSHIP_DDL_RE.test(text)) {
54
+ return "hand-written read_tenant_memberships DDL in a test: use unsafeCreateEntityTable(db, tenantMembershipEntity)";
55
+ }
56
+ return undefined;
57
+ }
58
+
59
+ function collect(sourceFile: SourceFile, roots: readonly RepoRoot[]): GuardViolation[] {
60
+ const file = relFromRepoRoot(sourceFile.getFilePath(), roots);
61
+ if (ALLOWLIST.some((re) => re.test(file))) return [];
62
+ const violations: GuardViolation[] = [];
63
+ for (const kind of LITERAL_KINDS) {
64
+ for (const node of sourceFile.getDescendantsOfKind(kind)) {
65
+ const message = violationMessage(node.getText());
66
+ if (message) violations.push({ file, line: node.getStartLineNumber(), message });
67
+ }
68
+ }
69
+ return violations;
70
+ }
71
+
72
+ export const guard: AstGuard = {
73
+ name: "Test-ES-Fixtures Guard",
74
+ scan: SCAN,
75
+ hint:
76
+ "Seed events with append() (runSeedWritesAt for backdating) and membership rows with " +
77
+ "seedRow on a table made by unsafeCreateEntityTable; bulk perf seeds belong in the guard allowlist with a Why.",
78
+ run(files, roots: readonly RepoRoot[] = resolveRepoRoots()) {
79
+ return { violations: files.flatMap((sf) => collect(sf, roots)) };
80
+ },
81
+ };
82
+
83
+ if (import.meta.main) runStandalone(guard);
package/src/pre-push.sh CHANGED
@@ -151,13 +151,25 @@ fi
151
151
  # canonical sibling path instead of this worktree — checking the wrong
152
152
  # code. Run the worktree-local check only when scripts/check-wt.sh is
153
153
  # tracked and executable (untracked/symlink stubs must not hijack push).
154
- # A check-wt.sh that wraps this hook sets KUMIKO_CHECK_WT=1 so it is not re-entered.
155
- if [ "${KUMIKO_CHECK_WT:-0}" != "1" ] \
154
+ # Re-entry from the wrapping check-wt.sh is detected by process ancestry: the
155
+ # hook records its own PID in KUMIKO_CHECK_WT_OWNER before delegating, so a
156
+ # freely exported variable cannot skip the tracked check-wt.sh.
157
+ is_reentry_from_check_wt() {
158
+ reentry_pid=$$
159
+ case "${KUMIKO_CHECK_WT_OWNER:-}" in '' | *[!0-9]*) return 1 ;; esac
160
+ while [ -n "$reentry_pid" ] && [ "$reentry_pid" -gt 1 ]; do
161
+ [ "$reentry_pid" = "$KUMIKO_CHECK_WT_OWNER" ] && return 0
162
+ reentry_pid=$(ps -o ppid= -p "$reentry_pid" 2>/dev/null | tr -d ' ')
163
+ done
164
+ return 1
165
+ }
166
+
167
+ if ! is_reentry_from_check_wt \
156
168
  && [ "$(git rev-parse --path-format=absolute --git-dir)" != "$GIT_COMMON_DIR" ] \
157
169
  && git -C "$REPO_ROOT" ls-files --error-unmatch scripts/check-wt.sh >/dev/null 2>&1 \
158
170
  && [ -x "$REPO_ROOT/scripts/check-wt.sh" ]; then
159
171
  echo "[pre-push] worktree detected — worktree check (see scripts/check-wt.sh)…"
160
- cd "$REPO_ROOT" && exec "$REPO_ROOT/scripts/check-wt.sh"
172
+ cd "$REPO_ROOT" && KUMIKO_CHECK_WT_OWNER=$$ exec "$REPO_ROOT/scripts/check-wt.sh"
161
173
  fi
162
174
 
163
175
  # Runs a guard bin from the worktree's or the parent's node_modules/.bin
package/src/run-guards.ts CHANGED
@@ -47,6 +47,7 @@ import { guard as restrictedSymbols } from "./guard-restricted-symbols";
47
47
  import { guard as screenConventions } from "./guard-screen-conventions";
48
48
  import { guard as silentSkip } from "./guard-silent-skip";
49
49
  import { guard as tenantEscalation } from "./guard-tenant-escalation";
50
+ import { guard as testEsFixtures } from "./guard-test-es-fixtures";
50
51
  import { guard as testTemplateDrift } from "./guard-test-template-drift";
51
52
  import { guard as testTimeouts } from "./guard-test-timeouts";
52
53
  import { guard as unsafeJsonParse } from "./guard-unsafe-json-parse";
@@ -81,6 +82,7 @@ export const GUARDS = [
81
82
  appFeatureStructure,
82
83
  libTestCoverage,
83
84
  testTimeouts,
85
+ testEsFixtures,
84
86
  testTemplateDrift,
85
87
  fileSize,
86
88
  commentIssueRefOnly,