@ogygie/core 0.6.0 → 0.7.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/package.json +1 -1
- package/src/manifest.schema.test.ts +42 -0
- package/src/manifest.schema.ts +43 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ogygie/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
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",
|
|
@@ -456,3 +456,45 @@ describe("manifest — packages du produit (spec manifest-packages)", () => {
|
|
|
456
456
|
expect(parsed.success).toBe(false);
|
|
457
457
|
});
|
|
458
458
|
});
|
|
459
|
+
|
|
460
|
+
describe("manifest — bloc tenancy (spec ogygie-standalone-repo v3)", () => {
|
|
461
|
+
const socle = featureNamed("espaces-et-comptes");
|
|
462
|
+
|
|
463
|
+
it("un bloc tenancy valide est accepté, rawDbAllowed a un défaut vide", () => {
|
|
464
|
+
const parsed = OgygieManifestSchema.safeParse(
|
|
465
|
+
manifestWithPackages(undefined, [socle]),
|
|
466
|
+
);
|
|
467
|
+
expect(parsed.success).toBe(true);
|
|
468
|
+
const withTenancy = OgygieManifestSchema.safeParse({
|
|
469
|
+
...manifestWithPackages(undefined, [socle]),
|
|
470
|
+
tenancy: { column: "workspace_id" },
|
|
471
|
+
});
|
|
472
|
+
expect(withTenancy.success && withTenancy.data.tenancy?.rawDbAllowed).toEqual([]);
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
it("le socle peut nommer les features autorisées à la base brute", () => {
|
|
476
|
+
const parsed = OgygieManifestSchema.safeParse({
|
|
477
|
+
...manifestWithPackages(undefined, [socle]),
|
|
478
|
+
tenancy: { column: "workspace_id", rawDbAllowed: ["espaces-et-comptes"] },
|
|
479
|
+
});
|
|
480
|
+
expect(parsed.success).toBe(true);
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
it("MUTATION : un socle nommant une feature INEXISTANTE est refusé", () => {
|
|
484
|
+
const parsed = OgygieManifestSchema.safeParse({
|
|
485
|
+
...manifestWithPackages(undefined, [socle]),
|
|
486
|
+
tenancy: { column: "workspace_id", rawDbAllowed: ["fantome"] },
|
|
487
|
+
});
|
|
488
|
+
expect(parsed.success).toBe(false);
|
|
489
|
+
const issue = parsed.success ? undefined : parsed.error.issues[0];
|
|
490
|
+
expect(issue?.message).toContain("matches no declared feature");
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
it("MUTATION : une colonne tenant vide est refusée — elle ne garderait rien", () => {
|
|
494
|
+
const parsed = OgygieManifestSchema.safeParse({
|
|
495
|
+
...manifestWithPackages(undefined, [socle]),
|
|
496
|
+
tenancy: { column: "" },
|
|
497
|
+
});
|
|
498
|
+
expect(parsed.success).toBe(false);
|
|
499
|
+
});
|
|
500
|
+
});
|
package/src/manifest.schema.ts
CHANGED
|
@@ -183,6 +183,34 @@ export const PackageManifestSchema = z.object({
|
|
|
183
183
|
});
|
|
184
184
|
export type PackageManifest = z.infer<typeof PackageManifestSchema>;
|
|
185
185
|
|
|
186
|
+
/**
|
|
187
|
+
* The two product values the framework's own gates need to do their job.
|
|
188
|
+
*
|
|
189
|
+
* They used to live as constants INSIDE the gate scripts, which is what kept
|
|
190
|
+
* those scripts out of governance: a governed file is identical in every
|
|
191
|
+
* Ogygie project, and these are not. Declared here, the scripts become
|
|
192
|
+
* identical everywhere and can finally be governed — so a corrected gate
|
|
193
|
+
* reaches every project through `ogygie upgrade` instead of waiting for each
|
|
194
|
+
* one to recopy it (spec ogygie-standalone-repo v3).
|
|
195
|
+
*
|
|
196
|
+
* Absent, the checks that depend on them simply do not run: a project without
|
|
197
|
+
* tenancy has nothing to guard, and saying so explicitly beats guarding
|
|
198
|
+
* nothing while pretending otherwise.
|
|
199
|
+
*/
|
|
200
|
+
export const TenancyManifestSchema = z.object({
|
|
201
|
+
column: z
|
|
202
|
+
.string()
|
|
203
|
+
.min(1)
|
|
204
|
+
.describe("SQL name of the tenant column, e.g. workspace_id — the one AGENTS.product.md announces"),
|
|
205
|
+
rawDbAllowed: z
|
|
206
|
+
.array(FeatureNameSchema)
|
|
207
|
+
.default([])
|
|
208
|
+
.describe(
|
|
209
|
+
"Features allowed to reach the raw database: the tenancy socle, which BUILDS the contexts and therefore predates them. Every other feature goes through tenantDb",
|
|
210
|
+
),
|
|
211
|
+
});
|
|
212
|
+
export type TenancyManifest = z.infer<typeof TenancyManifestSchema>;
|
|
213
|
+
|
|
186
214
|
export const OgygieManifestSchema = z.object({
|
|
187
215
|
version: z.literal(1),
|
|
188
216
|
product: z.object({
|
|
@@ -213,6 +241,9 @@ export const OgygieManifestSchema = z.object({
|
|
|
213
241
|
}),
|
|
214
242
|
),
|
|
215
243
|
features: z.array(FeatureManifestSchema),
|
|
244
|
+
tenancy: TenancyManifestSchema.optional().describe(
|
|
245
|
+
"Product values the framework gates need — absent, the tenancy checks do not run",
|
|
246
|
+
),
|
|
216
247
|
packages: z
|
|
217
248
|
.array(PackageManifestSchema)
|
|
218
249
|
.optional()
|
|
@@ -280,6 +311,18 @@ export const OgygieManifestSchema = z.object({
|
|
|
280
311
|
checkPackageDependencies(packageEntry.dependsOn, ["packages", index, "dependsOn"]);
|
|
281
312
|
});
|
|
282
313
|
|
|
314
|
+
// A socle that names no existing feature guards nothing, and nobody would
|
|
315
|
+
// notice: the lint would simply allow one fewer folder.
|
|
316
|
+
(manifest.tenancy?.rawDbAllowed ?? []).forEach((featureName, index) => {
|
|
317
|
+
if (!featureNames.has(featureName)) {
|
|
318
|
+
context.addIssue({
|
|
319
|
+
code: "custom",
|
|
320
|
+
path: ["tenancy", "rawDbAllowed", index],
|
|
321
|
+
message: `"${featureName}" matches no declared feature`,
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
|
|
283
326
|
// Vocabulary coherence (B2): one concept = one term. A canonical declared
|
|
284
327
|
// twice, an alias claimed by two entries, or an alias that IS someone's
|
|
285
328
|
// canonical would make the gate's suggested fix ambiguous — refused here,
|