@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 +1 -1
- package/src/impact.test.ts +25 -0
- package/src/impact.ts +20 -2
- package/src/manifest-owners.test.ts +9 -1
- package/src/manifest-owners.ts +13 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ogygie/core",
|
|
3
|
-
"version": "0.10.
|
|
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",
|
package/src/impact.test.ts
CHANGED
|
@@ -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
|
-
{
|
|
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
|
|
package/src/manifest-owners.ts
CHANGED
|
@@ -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
|
|