@ogygie/core 0.10.0 → 0.10.2

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": "@ogygie/core",
3
- "version": "0.10.0",
3
+ "version": "0.10.2",
4
4
  "description": "Ogygie framework core: manifest/spec/error schemas, generic tenancy and gate tooling. Ships TypeScript sources — Ogygie products run tsx/vitest by construction (D1).",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -217,6 +217,31 @@ describe("B5 — les quatre findings, chacun avec sa mutation", () => {
217
217
  expect(report.tableReadersOutsideOwner[0]?.feature).toBe("invoices");
218
218
  });
219
219
 
220
+ it("MUTATION : le nom d'une table dans une feature FRONT ne compte pas — elle ne parle pas à la base", async () => {
221
+ // Found on a real repo: widening the scan to other apps turned every
222
+ // `queryKey: ["orders", "list"]` into a boundary violation — fifteen in one
223
+ // report. A front feature holds no database client; the string is a query
224
+ // key or a route, and a rule applied where its premise does not hold makes
225
+ // noise, which is how a report stops being read.
226
+ const files = {
227
+ ...FILES,
228
+ "apps/web/src/features/orders-screen/queries.ts":
229
+ 'import { ListOrdersOutputSchema } from "@acme/api/features/orders/schemas";\nexport const key = ["orders", "list"];\nexport const s = ListOrdersOutputSchema;\n',
230
+ };
231
+ const report = await analyze(files);
232
+ expect(codesOf(report)).not.toContain("TABLE_REFERENCED_OUTSIDE_OWNER");
233
+ // …et la dérogation se DIT, elle ne s'applique pas en silence (D12).
234
+ expect(report.scope.outOfReach.join(" ")).toContain("front/gateway");
235
+ });
236
+
237
+ it("une feature d'API touchant la table d'une autre reste signalée — la règle garde sa portée", async () => {
238
+ const files = {
239
+ ...FILES,
240
+ "apps/api/src/features/invoices/repository.ts": 'export const t = "orders";\n',
241
+ };
242
+ expect(codesOf(await analyze(files))).toContain("TABLE_REFERENCED_OUTSIDE_OWNER");
243
+ });
244
+
220
245
  it("MUTATION : la même table dans un TEST ne compte pas — c'est le patron de fixture", async () => {
221
246
  const files = {
222
247
  ...FILES,
package/src/impact.ts CHANGED
@@ -237,6 +237,14 @@ async function buildReport(
237
237
  // feature's table as a fixture is the canonical bench pattern, and
238
238
  // flagging it would make every run red.
239
239
  if (isTest(file.path)) continue;
240
+ // So is anything that cannot touch a database. A front feature consumes
241
+ // contracts and a gateway relays — neither holds Drizzle — so the string
242
+ // a table name in their code is a query key or a route, not a table. Found
243
+ // on a real repo: widening the scan to other apps turned every
244
+ // `queryKey: ["orders", "list"]` into a boundary violation (15 of them in
245
+ // one report). A rule applied where its premise does not hold produces
246
+ // noise, and noise is how a report stops being read.
247
+ if (entry.appType === "front" || entry.appType === "gateway") continue;
240
248
  for (const table of ownedTables) {
241
249
  if (!content.includes(`"${table}"`)) continue;
242
250
  tableReadersOutsideOwner.push({ table, feature: entry.name, file: filePath });
@@ -278,7 +286,7 @@ async function buildReport(
278
286
  scope: {
279
287
  scannedFolders: scannedFolders.sort(),
280
288
  coveredKinds: [...new Set(entries.map((entry) => entry.kind))],
281
- outOfReach: [...outOfReach, ...outOfReachOf(target)],
289
+ outOfReach: [...outOfReach, ...outOfReachOf(target, entries)],
282
290
  },
283
291
  };
284
292
  return ImpactReportSchema.parse(report);
@@ -296,11 +304,21 @@ function tablesOf(manifest: OgygieManifest, featureName: string): string[] {
296
304
  }
297
305
 
298
306
  /** What no analysis of this repo can reach, named rather than implied (D12). */
299
- function outOfReachOf(target: MapEntry): string[] {
307
+ function outOfReachOf(target: MapEntry, entries: readonly MapEntry[]): string[] {
300
308
  const limits = [
301
309
  "code that no manifest entry declares — the map is the only chart",
302
310
  "anything outside this repo: no network, no npm registry",
303
311
  ];
312
+ // Said rather than silently applied: a table rule that skips some folders
313
+ // has to name them, or its silence reads as "nothing found there".
314
+ const spared = entries.filter(
315
+ (entry) => entry.appType === "front" || entry.appType === "gateway",
316
+ );
317
+ if (spared.length > 0 && target.kind === "feature") {
318
+ limits.push(
319
+ `table ownership was NOT checked in ${spared.length} front/gateway entr${spared.length === 1 ? "y" : "ies"} — that code holds no database client, so a table name there is a query key or a route`,
320
+ );
321
+ }
304
322
  if (target.kind === "framework-package" || target.kind === "package") {
305
323
  limits.unshift(
306
324
  `consumers of "${target.name}" OUTSIDE this repo — it is a package, and a published package is imported by projects this analysis cannot see`,
@@ -234,7 +234,15 @@ describe("declaredEntries — tout ce que la carte déclare, à un seul endroit"
234
234
  }),
235
235
  );
236
236
  expect(entries).toEqual([
237
- { kind: "feature", name: "orders", status: "delivered", folder: null, specId: "orders" },
237
+ {
238
+ kind: "feature",
239
+ name: "orders",
240
+ status: "delivered",
241
+ folder: null,
242
+ specId: "orders",
243
+ // No app carries it, so nothing can be said of what it may touch.
244
+ appType: null,
245
+ },
238
246
  ]);
239
247
  });
240
248
 
@@ -56,6 +56,16 @@ export interface SpecOwner {
56
56
  export interface MapEntry extends SpecOwner {
57
57
  /** The spec this entry owns, or null when it owns none. */
58
58
  specId: string | null;
59
+ /**
60
+ * Type of the app carrying this entry — null for a package, which lives
61
+ * under no app.
62
+ *
63
+ * It answers a question no tool can guess from a folder: WHAT this code is
64
+ * allowed to touch. A front feature never speaks to the database (it
65
+ * consumes contracts), a gateway only relays — so a rule about database
66
+ * tables has no business examining either.
67
+ */
68
+ appType: "api" | "front" | "gateway" | null;
59
69
  }
60
70
 
61
71
  /**
@@ -90,6 +100,7 @@ export function declaredEntries(manifest: OgygieManifest): MapEntry[] {
90
100
  status: feature.status,
91
101
  folder: owningApp === undefined ? null : `${owningApp.path}/${feature.folder}`,
92
102
  specId: feature.name,
103
+ appType: owningApp?.type ?? null,
93
104
  });
94
105
  }
95
106
 
@@ -100,6 +111,7 @@ export function declaredEntries(manifest: OgygieManifest): MapEntry[] {
100
111
  status: productPackage.status,
101
112
  folder: productPackage.path,
102
113
  specId: productPackage.name,
114
+ appType: null,
103
115
  });
104
116
  }
105
117
 
@@ -111,6 +123,7 @@ export function declaredEntries(manifest: OgygieManifest): MapEntry[] {
111
123
  folder: frameworkPackage.path,
112
124
  // A published npm name is not a spec id: the link is DECLARED.
113
125
  specId: frameworkPackage.spec === null ? null : specIdOf(frameworkPackage.spec),
126
+ appType: null,
114
127
  });
115
128
  }
116
129