@ogygie/core 0.11.1 → 0.11.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/README.md +1 -1
- package/package.json +1 -1
- package/src/bundle-budget.test.ts +1 -1
- package/src/bundle-budget.ts +2 -1
- package/src/contract-purity.test.ts +2 -1
- package/src/contract-purity.ts +4 -3
- package/src/decision-refs.ts +1 -1
- package/src/manifest-gateway.ts +2 -2
- package/src/manifest.schema.test.ts +5 -4
- package/src/module-purity.test.ts +1 -1
- package/src/shared-tables.test.ts +1 -1
- package/src/shared-tables.ts +2 -1
- package/src/spec.schema.test.ts +3 -3
- package/src/spec.schema.ts +11 -9
- package/src/web-lint.test.ts +4 -3
- package/src/web-lint.ts +4 -4
package/README.md
CHANGED
|
@@ -58,7 +58,7 @@ core tel quel.
|
|
|
58
58
|
4. **Zod aux frontières.** Toute entrée/sortie a un schéma Zod, source
|
|
59
59
|
unique de vérité (validation runtime + type inféré + doc) ; le graphe
|
|
60
60
|
d'un `schemas.ts` n'autorise par défaut QUE `zod` (`contract-purity`,
|
|
61
|
-
|
|
61
|
+
AGENTS.md §Structure — l'exception des CONTRATS).
|
|
62
62
|
5. **Une devise de travail par tenant.** L'argent est un entier en centimes
|
|
63
63
|
+ code ISO 4217 ; aucune conversion de change ne traverse le core. Les
|
|
64
64
|
futurs helpers monétaires naîtront ici sous cette hypothèse.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ogygie/core",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.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",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @intent Mutation tests of the bundle-budget gate logic (
|
|
2
|
+
* @intent Mutation tests of the bundle-budget gate logic (AGENTS.md workflow 7: a known
|
|
3
3
|
* violation must be DETECTED, not just the happy path).
|
|
4
4
|
*/
|
|
5
5
|
import { describe, expect, it } from "vitest";
|
package/src/bundle-budget.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @intent Testable logic of the CI gate `ogygie:verify-bundle` (
|
|
2
|
+
* @intent Testable logic of the CI gate `ogygie:verify-bundle` (AGENTS.md
|
|
3
|
+
* workflow 7: a gate is
|
|
3
4
|
* born with its mutation test — see bundle-budget.test.ts). PURE on purpose
|
|
4
5
|
* (no node imports — the core tsconfig has no node types): the script
|
|
5
6
|
* scripts/verify-bundle.ts reads the files and gzips them, this module decides
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @intent Proves the transitive contract gate AND its mutations (
|
|
2
|
+
* @intent Proves the transitive contract gate AND its mutations (AGENTS.md
|
|
3
|
+
* workflow 7): each
|
|
3
4
|
* case below is a way the server has come back — or could come back — into a
|
|
4
5
|
* contract graph, and the audit must catch every one of them without crying
|
|
5
6
|
* wolf on the erased forms. The keystone case is the historical incident
|
package/src/contract-purity.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @intent TRANSITIVE purity of the contract entry points (
|
|
2
|
+
* @intent TRANSITIVE purity of the contract entry points (AGENTS.md
|
|
3
|
+
* §Structure, the CONTRACTS exception):
|
|
3
4
|
* from a feature's `schemas.ts`, no module with a runtime effect may be
|
|
4
5
|
* REACHABLE — neither directly nor through any chain of intermediaries.
|
|
5
6
|
*
|
|
@@ -18,12 +19,12 @@
|
|
|
18
19
|
* (AGENTS.md §Structure), the exact edge of the incident above.
|
|
19
20
|
* Plus one honesty rule: a module the walk cannot READ is reported, never
|
|
20
21
|
* skipped — a gate that silently swallows what it does not understand is
|
|
21
|
-
* worse than no gate
|
|
22
|
+
* worse than no gate.
|
|
22
23
|
*
|
|
23
24
|
* Generic tooling, hence the core (D10 amended): it knows the framework's
|
|
24
25
|
* vocabulary (apps, features, schemas.ts, index.ts) and not one word of the
|
|
25
26
|
* product's domain. The filesystem is INJECTED (`readModule`), so the walk is
|
|
26
|
-
* a pure function, tested in memory with its mutations (
|
|
27
|
+
* a pure function, tested in memory with its mutations (AGENTS.md workflow 7).
|
|
27
28
|
*/
|
|
28
29
|
import { parseModuleImports } from "./module-purity.js";
|
|
29
30
|
|
package/src/decision-refs.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* eliminate — so it breaks the build instead.
|
|
10
10
|
*
|
|
11
11
|
* These functions are pure (source text in, findings out) so the negative tests
|
|
12
|
-
* required by
|
|
12
|
+
* required by AGENTS.md workflow 7 can feed them known violations directly;
|
|
13
13
|
* scripts/verify-specs.ts only reports what they return.
|
|
14
14
|
*/
|
|
15
15
|
|
package/src/manifest-gateway.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @intent GENERIC manifest-driven gateway factory
|
|
2
|
+
* @intent GENERIC manifest-driven gateway factory, framework side: this
|
|
3
3
|
* module knows ZERO product vocabulary (D10 amended — generic tooling lives in
|
|
4
4
|
* the core), and the vocabulary test next to it proves it. It derives a pure
|
|
5
5
|
* relay from an Ogygie manifest: every declared route becomes a typed relay to
|
|
@@ -382,7 +382,7 @@ export async function createManifestGateway(
|
|
|
382
382
|
file: null,
|
|
383
383
|
cause: `${request.method} ${request.url} is not declared in the manifest`,
|
|
384
384
|
suggestedFix:
|
|
385
|
-
"Declare the route in ogygie.manifest.json — an undeclared route is unreachable by design
|
|
385
|
+
"Declare the route in ogygie.manifest.json — an undeclared route is unreachable by design.",
|
|
386
386
|
}),
|
|
387
387
|
);
|
|
388
388
|
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @intent Mutation tests of the manifest front-feature extension (
|
|
2
|
+
* @intent Mutation tests of the manifest front-feature extension (AGENTS.md
|
|
3
|
+
* workflow 7: the
|
|
3
4
|
* gate evolution is born with its negative tests). The api-feature path is
|
|
4
5
|
* already covered end-to-end by verify-manifest in CI.
|
|
5
6
|
*/
|
|
@@ -140,7 +141,7 @@ describe("manifest — invalidations croisées des routes (spec manifest-client
|
|
|
140
141
|
});
|
|
141
142
|
});
|
|
142
143
|
|
|
143
|
-
// Gateway extension
|
|
144
|
+
// Gateway extension: the gateway app type exists, and
|
|
144
145
|
// a gateway feature — like a front one — OWNS no routes and no tables (it
|
|
145
146
|
// RELAYS the api's declared routes, it never declares its own).
|
|
146
147
|
function manifestWithGatewayFeature(feature: Record<string, unknown>) {
|
|
@@ -160,7 +161,7 @@ function manifestWithGatewayFeature(feature: Record<string, unknown>) {
|
|
|
160
161
|
return base;
|
|
161
162
|
}
|
|
162
163
|
|
|
163
|
-
describe("manifest — app gateway
|
|
164
|
+
describe("manifest — app gateway", () => {
|
|
164
165
|
it("une feature gateway valide (app: gateway, routes/dbTables vides) est acceptée", () => {
|
|
165
166
|
const parsed = OgygieManifestSchema.safeParse(manifestWithGatewayFeature({}));
|
|
166
167
|
expect(parsed.success).toBe(true);
|
|
@@ -317,7 +318,7 @@ describe("manifest — ogygieVersion et bloc framework (spec ogygie-standalone-r
|
|
|
317
318
|
});
|
|
318
319
|
});
|
|
319
320
|
|
|
320
|
-
describe("manifest — app gateway, suite
|
|
321
|
+
describe("manifest — app gateway, suite", () => {
|
|
321
322
|
it("MUTATION (bis) : une feature gateway qui déclare une table DB est refusée", () => {
|
|
322
323
|
const parsed = OgygieManifestSchema.safeParse(
|
|
323
324
|
manifestWithGatewayFeature({ dbTables: ["orders"] }),
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @intent Proves the purity checker AND its mutations (
|
|
2
|
+
* @intent Proves the purity checker AND its mutations (AGENTS.md workflow 7): a
|
|
3
3
|
* gate is only worth its negative test. Every case below is a source a pure
|
|
4
4
|
* module could plausibly drift into — the checker must catch each one, and
|
|
5
5
|
* must NOT cry wolf on the erased forms.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @intent Mutation tests of the shared-table gates (
|
|
2
|
+
* @intent Mutation tests of the shared-table gates (AGENTS.md workflow 7: every
|
|
3
3
|
* gate is born with its negative test). The shared-table exception is a hole
|
|
4
4
|
* in the multi-tenant rail — each way of abusing it is fed here as a KNOWN
|
|
5
5
|
* VIOLATION and must be DETECTED, never silently tolerated.
|
package/src/shared-tables.ts
CHANGED
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
* the raw-db ban unenforceable by inspection.
|
|
15
15
|
*
|
|
16
16
|
* These functions are pure (source text in, findings out) so that the negative
|
|
17
|
-
* tests required by
|
|
17
|
+
* mutation tests required by AGENTS.md workflow 7 can feed them known
|
|
18
|
+
* violations directly —
|
|
18
19
|
* scripts/lint-interdits.ts and scripts/verify-manifest.ts only report what
|
|
19
20
|
* they return. The module is GENERIC infrastructure (D10 amended): it speaks
|
|
20
21
|
* "tenant", never a word of the product's domain.
|
package/src/spec.schema.test.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @intent Mutation tests of the spec-frontmatter format (
|
|
2
|
+
* @intent Mutation tests of the spec-frontmatter format (AGENTS.md workflow 7:
|
|
3
3
|
* every gate is born — and evolves — with its negative test). Each test feeds
|
|
4
4
|
* the parser a KNOWN VIOLATION and requires DETECTION, never a silent
|
|
5
5
|
* default: the three traps consigned in FAILURES.md (2026-07-30, session 7)
|
|
@@ -92,13 +92,13 @@ status: validated
|
|
|
92
92
|
|
|
93
93
|
it("MUTATION — une virgule INTERNE à une entrée citée ne casse plus l'entrée en fragments (split conscient des guillemets)", () => {
|
|
94
94
|
const parsed = parseSpecFrontmatter(
|
|
95
|
-
'deferred: ["
|
|
95
|
+
'deferred: ["ARCHIVE_EXPORT (bulk export of archived records) blockedBy:archive"]',
|
|
96
96
|
);
|
|
97
97
|
|
|
98
98
|
expect(parsed.problems).toEqual([]);
|
|
99
99
|
// ONE element, comma preserved — the old split produced two broken shards.
|
|
100
100
|
expect(parsed.value["deferred"]).toEqual([
|
|
101
|
-
"
|
|
101
|
+
"ARCHIVE_EXPORT (bulk export of archived records) blockedBy:archive",
|
|
102
102
|
]);
|
|
103
103
|
// …and that whole entry satisfies the DeferredBehaviorSchema regex.
|
|
104
104
|
const validated = SpecFrontmatterSchema.safeParse({
|
package/src/spec.schema.ts
CHANGED
|
@@ -4,18 +4,20 @@
|
|
|
4
4
|
* machine-readable frontmatter + mandatory canonical sections. The CI
|
|
5
5
|
* validator (scripts/verify-specs.ts) checks the frontmatter AND the sections;
|
|
6
6
|
* this module owns the restricted frontmatter format — scalars and arrays of
|
|
7
|
-
* scalars, inline or multiline
|
|
7
|
+
* scalars, inline or multiline — through parseSpecFrontmatter, which REFUSES what it
|
|
8
8
|
* cannot read instead of silently swallowing it (a multiline array once read
|
|
9
9
|
* as `deferred: []` without a sound — FAILURES.md 2026-07-30, DECISIONS.md
|
|
10
|
-
*
|
|
10
|
+
* AGENTS.md workflow 7: every gate is born with its mutation test, see
|
|
11
|
+
* spec.schema.test.ts).
|
|
11
12
|
* Section titles stay FRENCH on purpose: specs are human-facing documents
|
|
12
|
-
* (
|
|
13
|
+
* (AGENTS.md principle 8 — humans of the project read and validate them in
|
|
14
|
+
* French, only the machine-readable half is English).
|
|
13
15
|
*/
|
|
14
16
|
import { z } from "zod";
|
|
15
17
|
import { FeatureNameSchema } from "./manifest.schema.js";
|
|
16
18
|
|
|
17
19
|
/**
|
|
18
|
-
* One deferred behavior
|
|
20
|
+
* One deferred behavior: a behavior the spec describes but
|
|
19
21
|
* whose implementation is blocked by a feature that does not exist yet.
|
|
20
22
|
* Format: "<stable error code or short label> blockedBy:<feature-name>".
|
|
21
23
|
* The frontmatter parser only reads arrays of STRINGS (inline or multiline),
|
|
@@ -40,7 +42,7 @@ export const SpecFrontmatterSchema = z.object({
|
|
|
40
42
|
deferred: z
|
|
41
43
|
.array(DeferredBehaviorSchema)
|
|
42
44
|
.default([])
|
|
43
|
-
.describe("Behaviors deferred to a later feature
|
|
45
|
+
.describe("Behaviors deferred to a later feature, blocked by name"),
|
|
44
46
|
});
|
|
45
47
|
export type SpecFrontmatter = z.infer<typeof SpecFrontmatterSchema>;
|
|
46
48
|
|
|
@@ -62,7 +64,7 @@ export const SECTIONS_SPEC_OBLIGATOIRES = [
|
|
|
62
64
|
* - status: draft → no implementation may exist
|
|
63
65
|
* - status: validated → implementation is allowed
|
|
64
66
|
* - status: implemented → the feature exists in the manifest with status "delivered"
|
|
65
|
-
* AND `deferred` is empty (
|
|
67
|
+
* AND `deferred` is empty (nothing left pending)
|
|
66
68
|
*/
|
|
67
69
|
|
|
68
70
|
// ----------------------------------------------------------- format parser
|
|
@@ -123,7 +125,7 @@ function unquote(raw: string, location: string, problems: string[]): string | nu
|
|
|
123
125
|
* Everything it cannot read still becomes a problem instead of a silent
|
|
124
126
|
* default: an array never closed before the end of the frontmatter, a spilled
|
|
125
127
|
* element line OUTSIDE any array, an unbalanced quote — each once made a spec
|
|
126
|
-
* lie by omission (FAILURES.md 2026-07-30
|
|
128
|
+
* lie by omission (FAILURES.md 2026-07-30).
|
|
127
129
|
*/
|
|
128
130
|
export function parseSpecFrontmatter(text: string): ParsedSpecFrontmatter {
|
|
129
131
|
const value: Record<string, unknown> = {};
|
|
@@ -135,7 +137,7 @@ export function parseSpecFrontmatter(text: string): ParsedSpecFrontmatter {
|
|
|
135
137
|
const colonIndex = trimmed.indexOf(":");
|
|
136
138
|
if (colonIndex === -1 || trimmed.startsWith('"') || trimmed.startsWith("'")) {
|
|
137
139
|
problems.push(
|
|
138
|
-
`unparseable line ${JSON.stringify(trimmed)}: expected \`key: value\` — a spilled array element outside any array? (scalars and arrays of scalars only
|
|
140
|
+
`unparseable line ${JSON.stringify(trimmed)}: expected \`key: value\` — a spilled array element outside any array? (scalars and arrays of scalars only)`,
|
|
139
141
|
);
|
|
140
142
|
continue;
|
|
141
143
|
}
|
|
@@ -153,7 +155,7 @@ export function parseSpecFrontmatter(text: string): ParsedSpecFrontmatter {
|
|
|
153
155
|
}
|
|
154
156
|
if (!closed) {
|
|
155
157
|
problems.push(
|
|
156
|
-
`key "${key}": array opened with "[" and never closed with "]" before the end of the frontmatter — a truncated array would be silently read as empty, hence refused
|
|
158
|
+
`key "${key}": array opened with "[" and never closed with "]" before the end of the frontmatter — a truncated array would be silently read as empty, hence refused`,
|
|
157
159
|
);
|
|
158
160
|
continue;
|
|
159
161
|
}
|
package/src/web-lint.test.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @intent Mutation tests of the web lint gate (
|
|
2
|
+
* @intent Mutation tests of the web lint gate (AGENTS.md workflow 7): each
|
|
3
|
+
* KNOWN violation is
|
|
3
4
|
* proven detected, and each legitimate pattern is proven accepted.
|
|
4
5
|
*/
|
|
5
6
|
import { describe, expect, it } from "vitest";
|
|
@@ -75,7 +76,7 @@ describe("web-lint — valeurs Tailwind arbitraires", () => {
|
|
|
75
76
|
});
|
|
76
77
|
});
|
|
77
78
|
|
|
78
|
-
describe("web-lint — URL absolue d'API (
|
|
79
|
+
describe("web-lint — URL absolue d'API (AGENTS.md §Sécurité 8)", () => {
|
|
79
80
|
it("MUTATION : un fetch sur une URL absolue vers /api est détecté", () => {
|
|
80
81
|
const source = `export function useOrders() { return fetch("http://localhost:3000/api/orders"); }`;
|
|
81
82
|
const violations = findAbsoluteApiUrls(source);
|
|
@@ -88,7 +89,7 @@ describe("web-lint — URL absolue d'API (D27)", () => {
|
|
|
88
89
|
expect(findAbsoluteApiUrls(source)).toHaveLength(1);
|
|
89
90
|
});
|
|
90
91
|
|
|
91
|
-
it("un appel relatif /api/... passe (la règle même
|
|
92
|
+
it("un appel relatif /api/... passe (la règle même du §Sécurité 8)", () => {
|
|
92
93
|
const source = `export function useOrders() { return fetch("/api/orders"); }`;
|
|
93
94
|
expect(findAbsoluteApiUrls(source)).toHaveLength(0);
|
|
94
95
|
});
|
package/src/web-lint.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @intent Testable logic of the front part of the `pnpm lint` gate (the web
|
|
3
3
|
* shell spec's §Périmètre: "hardcoded string in JSX" and "arbitrary Tailwind
|
|
4
|
-
* value" both break the CI —
|
|
4
|
+
* value" both break the CI — born with its mutation tests in
|
|
5
5
|
* web-lint.test.ts). Built on the TypeScript AST (not regexes): JSX text is
|
|
6
6
|
* only legitimate inside a <Trans> element (Lingui macro — fr is the source
|
|
7
7
|
* locale, so the French text IS the ICU source message), and human-facing
|
|
@@ -27,7 +27,7 @@ const HUMAN_TEXT = /\p{L}{2}/u;
|
|
|
27
27
|
const ARBITRARY_TAILWIND_VALUE = /[\w-]+-\[[^\]\s]+\]/;
|
|
28
28
|
|
|
29
29
|
/** Absolute URL whose path enters /api — the front must never know the API
|
|
30
|
-
* origin (
|
|
30
|
+
* origin (AGENTS.md §Sécurité 8): it calls `/api/...` relatively. */
|
|
31
31
|
const ABSOLUTE_API_URL = /https?:\/\/[^\s"'`]*\/api([/?#]|$)/;
|
|
32
32
|
|
|
33
33
|
function lineOf(sourceFile: ts.SourceFile, node: ts.Node): number {
|
|
@@ -84,7 +84,7 @@ export function findHardcodedJsxText(source: string, fileName = "file.tsx"): Web
|
|
|
84
84
|
return violations;
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
/** Absolute API URLs in any string or template literal (
|
|
87
|
+
/** Absolute API URLs in any string or template literal (`/api` stays
|
|
88
88
|
* relative — an absolute URL would tie the front to an API origin the
|
|
89
89
|
* gateway exists to hide). Scans .ts AND .tsx: data-access files are where
|
|
90
90
|
* such URLs appear first. */
|
|
@@ -110,7 +110,7 @@ export function findAbsoluteApiUrls(source: string, fileName = "file.ts"): WebLi
|
|
|
110
110
|
if (match !== null) {
|
|
111
111
|
violations.push({
|
|
112
112
|
line: lineOf(sourceFile, node),
|
|
113
|
-
rule: "absolute API URL — the front calls /api/... RELATIVELY, it must not know the API origin (
|
|
113
|
+
rule: "absolute API URL — the front calls /api/... RELATIVELY, it must not know the API origin (AGENTS.md §Sécurité 8)",
|
|
114
114
|
excerpt: match[0].slice(0, 60),
|
|
115
115
|
});
|
|
116
116
|
}
|