@augmenting-integrations/deploy-tools 8.8.0 → 8.9.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
@@ -41,7 +41,21 @@ read. Specifically:
41
41
  - When `features.billing`: `PaymentMethod`, `CreditTransaction`
42
42
  - When `features.impersonation` or `features.invitations`: `ActivityLog`
43
43
 
44
- Within each model, the required field names match what the library
45
- handlers query. Missing fields fail validation by name (the validator
46
- does not deep-check Prisma types -- the type system catches drift via
47
- the platform adapter in `src/platform/repositories.ts`).
44
+ Within each model, the validator checks:
45
+
46
+ 1. **Field name** must match the canonical name (e.g. `credit_balance`,
47
+ not `creditBalance` or `credits_balance`).
48
+ 2. **Scalar type** must be in the compatible set
49
+ (`User.id` is `Int | BigInt`, `User.credit_balance` is
50
+ `Decimal | Float`, etc.). Native database types (`@db.VarChar`,
51
+ `@db.Decimal(...)`) are not validated.
52
+ 3. **Single-column unique constraint** on canonical lookup keys:
53
+ `User.email`, `Invitation.token`, `PaymentMethod.stripe_payment_method_id`,
54
+ `CreditTransaction.stripe_payment_intent_id`. Composite `@@unique([...])`
55
+ does not satisfy this (the library handlers do
56
+ `findUnique({ where: { token: ... } })`).
57
+
58
+ Structural casts that bridge the generated Prisma client to the library
59
+ handler factories are isolated to `src/platform/repositories.ts` +
60
+ `src/platform/app-user.ts`. Product wiring (`src/lib/*.ts`, route
61
+ handlers) MUST NOT cast.
@@ -1,4 +1,15 @@
1
1
  import type { AppManifest } from "@augmenting-integrations/platform/manifest";
2
+ /**
3
+ * One of more compatible Prisma scalar types. We model compatibility as a
4
+ * set so a spoke can choose `Int` or `BigInt` for ids, `Decimal` or `Float`
5
+ * for money, etc.
6
+ *
7
+ * `*` means "any scalar type" -- used when the canonical contract only
8
+ * cares about the field's NAME existing (e.g. `metadata Json?` is the only
9
+ * supported shape but a spoke that picks `String` for a stringified-JSON
10
+ * column would still satisfy the handler).
11
+ */
12
+ export type CompatibleTypes = readonly string[];
2
13
  export type RequiredField = {
3
14
  name: string;
4
15
  /**
@@ -7,6 +18,15 @@ export type RequiredField = {
7
18
  * found missing.
8
19
  */
9
20
  reason: string;
21
+ /**
22
+ * Set of compatible Prisma scalar types. Empty means "any scalar".
23
+ */
24
+ types: CompatibleTypes;
25
+ /**
26
+ * Must this field carry a single-column unique constraint? Implies
27
+ * the field is a stable lookup key for library handlers.
28
+ */
29
+ unique?: boolean;
10
30
  };
11
31
  export type RequiredModel = {
12
32
  name: string;
@@ -1 +1 @@
1
- {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/canonical/schema.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4CAA4C,CAAC;AAE9E,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,aAAa,EAAE,CAAC;CACzB,CAAC;AAEF;;;;;;;;GAQG;AACH,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,WAAW,GAAG,aAAa,EAAE,CAwGhF"}
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/canonical/schema.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4CAA4C,CAAC;AAE9E;;;;;;;;;GASG;AACH,MAAM,MAAM,eAAe,GAAG,SAAS,MAAM,EAAE,CAAC;AAEhD,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,MAAM,EAAE,MAAM,CAAC;IACf;;OAEG;IACH,KAAK,EAAE,eAAe,CAAC;IACvB;;;OAGG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,aAAa,EAAE,CAAC;CACzB,CAAC;AAYF;;;;;;;;GAQG;AACH,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,WAAW,GAAG,aAAa,EAAE,CAgOhF"}
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ requiredModelsForManifest
4
+ } from "../chunk-DN2IENY7.js";
5
+ export {
6
+ requiredModelsForManifest
7
+ };
8
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,234 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/canonical/schema.ts
4
+ var ID = ["Int", "BigInt"];
5
+ var ID_NULLABLE = ID;
6
+ var MONEY = ["Decimal", "Float"];
7
+ var STRING = ["String"];
8
+ var BOOL = ["Boolean"];
9
+ var TIME = ["DateTime"];
10
+ var JSONISH = ["Json", "String"];
11
+ var ANY = [];
12
+ function requiredModelsForManifest(manifest) {
13
+ const out = [];
14
+ const userFields = [
15
+ {
16
+ name: "id",
17
+ reason: "JIT lookup + impersonation token claims",
18
+ types: ID
19
+ },
20
+ {
21
+ name: "email",
22
+ reason: "JIT email lookup + auto-promote-to-admin",
23
+ types: STRING,
24
+ unique: true
25
+ },
26
+ { name: "name", reason: "/api/auth/me, UserMenu display", types: STRING },
27
+ { name: "role", reason: "JIT role resolution + admin gating", types: STRING },
28
+ { name: "is_active", reason: "/api/auth/me response shape", types: BOOL },
29
+ {
30
+ name: "credit_balance",
31
+ reason: "JIT initial balance + /api/auth/me",
32
+ types: MONEY
33
+ },
34
+ {
35
+ name: "password",
36
+ reason: "schema-inherited not-null filler at JIT create",
37
+ types: STRING
38
+ },
39
+ {
40
+ name: "parent_id",
41
+ reason: "invitation auto-accept sets parent_id from invite",
42
+ types: ID_NULLABLE
43
+ },
44
+ { name: "created_at", reason: "audit / ordering", types: TIME },
45
+ { name: "updated_at", reason: "audit / ordering", types: TIME }
46
+ ];
47
+ if (manifest.features.billing) {
48
+ userFields.push(
49
+ {
50
+ name: "stripe_customer_id",
51
+ reason: "createBillingHandlers user shape",
52
+ types: STRING
53
+ },
54
+ {
55
+ name: "stripe_default_payment_method",
56
+ reason: "createBillingHandlers user shape",
57
+ types: STRING
58
+ },
59
+ {
60
+ name: "stripe_default_payment_method_display",
61
+ reason: "createBillingHandlers user shape",
62
+ types: STRING
63
+ },
64
+ {
65
+ name: "auto_recharge_enabled",
66
+ reason: "/api/billing/auto-recharge",
67
+ types: BOOL
68
+ },
69
+ {
70
+ name: "auto_recharge_threshold",
71
+ reason: "/api/billing/auto-recharge",
72
+ types: MONEY
73
+ },
74
+ {
75
+ name: "auto_recharge_amount",
76
+ reason: "/api/billing/auto-recharge",
77
+ types: MONEY
78
+ }
79
+ );
80
+ }
81
+ out.push({ name: "User", fields: userFields });
82
+ if (manifest.features.billing) {
83
+ out.push({
84
+ name: "PaymentMethod",
85
+ fields: [
86
+ { name: "id", reason: "createBillingHandlers row shape", types: ID },
87
+ {
88
+ name: "user_id",
89
+ reason: "createBillingHandlers row shape",
90
+ types: ID
91
+ },
92
+ {
93
+ name: "stripe_payment_method_id",
94
+ reason: "createBillingHandlers row shape (unique key)",
95
+ types: STRING,
96
+ unique: true
97
+ },
98
+ { name: "brand", reason: "createBillingHandlers row shape", types: STRING },
99
+ { name: "last4", reason: "createBillingHandlers row shape", types: STRING },
100
+ {
101
+ name: "exp_month",
102
+ reason: "createBillingHandlers row shape",
103
+ types: ["Int"]
104
+ },
105
+ {
106
+ name: "exp_year",
107
+ reason: "createBillingHandlers row shape",
108
+ types: ["Int"]
109
+ },
110
+ {
111
+ name: "is_default",
112
+ reason: "createBillingHandlers row shape",
113
+ types: BOOL
114
+ },
115
+ {
116
+ name: "created_at",
117
+ reason: "createBillingHandlers row shape",
118
+ types: TIME
119
+ }
120
+ ]
121
+ });
122
+ out.push({
123
+ name: "CreditTransaction",
124
+ fields: [
125
+ { name: "id", reason: "createBillingHandlers row shape", types: ID },
126
+ {
127
+ name: "user_id",
128
+ reason: "createBillingHandlers row shape",
129
+ types: ID
130
+ },
131
+ // `type` is commonly an enum -- accept ANY scalar so spokes can use
132
+ // `String` or a Prisma enum interchangeably.
133
+ { name: "type", reason: "createBillingHandlers row shape", types: ANY },
134
+ {
135
+ name: "amount",
136
+ reason: "createBillingHandlers row shape",
137
+ types: MONEY
138
+ },
139
+ {
140
+ name: "description",
141
+ reason: "createBillingHandlers row shape",
142
+ types: STRING
143
+ },
144
+ {
145
+ name: "stripe_payment_intent_id",
146
+ reason: "createBillingHandlers webhook idempotency",
147
+ types: STRING,
148
+ unique: true
149
+ },
150
+ {
151
+ name: "payment_method_display",
152
+ reason: "createBillingHandlers row shape",
153
+ types: STRING
154
+ },
155
+ {
156
+ name: "created_at",
157
+ reason: "createBillingHandlers row shape",
158
+ types: TIME
159
+ }
160
+ ]
161
+ });
162
+ }
163
+ if (manifest.features.invitations) {
164
+ out.push({
165
+ name: "Invitation",
166
+ fields: [
167
+ {
168
+ name: "id",
169
+ reason: "createInvitationHandlers + JIT invitation accept",
170
+ types: ID
171
+ },
172
+ {
173
+ name: "token",
174
+ reason: "createInvitationHandlers findUnique",
175
+ types: STRING,
176
+ unique: true
177
+ },
178
+ { name: "email", reason: "JIT invitation lookup", types: STRING },
179
+ {
180
+ name: "intended_role",
181
+ reason: "JIT role inheritance",
182
+ types: STRING
183
+ },
184
+ {
185
+ name: "parent_id",
186
+ reason: "JIT parent_id inheritance",
187
+ types: ID_NULLABLE
188
+ },
189
+ { name: "expires_at", reason: "JIT expiry filter", types: TIME },
190
+ {
191
+ name: "accepted_at",
192
+ reason: "JIT acceptance toggle",
193
+ types: TIME
194
+ },
195
+ {
196
+ name: "accepted_by_user_id",
197
+ reason: "JIT writes acceptance with new user id",
198
+ types: ID_NULLABLE
199
+ },
200
+ { name: "created_at", reason: "ordering", types: TIME }
201
+ ]
202
+ });
203
+ }
204
+ if (manifest.features.impersonation || manifest.features.invitations) {
205
+ out.push({
206
+ name: "ActivityLog",
207
+ fields: [
208
+ { name: "id", reason: "audit row", types: ID },
209
+ {
210
+ name: "user_id",
211
+ reason: "createImpersonateHandlers audit writes",
212
+ types: ID_NULLABLE
213
+ },
214
+ {
215
+ name: "action",
216
+ reason: "createImpersonateHandlers audit writes",
217
+ types: STRING
218
+ },
219
+ {
220
+ name: "metadata",
221
+ reason: "createImpersonateHandlers audit writes",
222
+ types: JSONISH
223
+ },
224
+ { name: "created_at", reason: "audit row", types: TIME }
225
+ ]
226
+ });
227
+ }
228
+ return out;
229
+ }
230
+
231
+ export {
232
+ requiredModelsForManifest
233
+ };
234
+ //# sourceMappingURL=chunk-DN2IENY7.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/canonical/schema.ts"],"sourcesContent":["// =============================================================================\n// Canonical schema contract.\n//\n// Encodes the Prisma model + field names + scalar types the library handler\n// factories read at runtime. The validator (`augint validate-spoke`) parses\n// the spoke's prisma/schema.prisma and asserts that every required model +\n// field for the manifest's enabled features is present with a compatible\n// scalar type. Required uniqueness is enforced for lookup keys.\n//\n// Drift in canonical naming AND type is the most common class of integration\n// bug. The validator catches it before `next build` runs.\n// =============================================================================\n\nimport type { AppManifest } from \"@augmenting-integrations/platform/manifest\";\n\n/**\n * One of more compatible Prisma scalar types. We model compatibility as a\n * set so a spoke can choose `Int` or `BigInt` for ids, `Decimal` or `Float`\n * for money, etc.\n *\n * `*` means \"any scalar type\" -- used when the canonical contract only\n * cares about the field's NAME existing (e.g. `metadata Json?` is the only\n * supported shape but a spoke that picks `String` for a stringified-JSON\n * column would still satisfy the handler).\n */\nexport type CompatibleTypes = readonly string[];\n\nexport type RequiredField = {\n name: string;\n /**\n * Reason this field is required (shown in the error message). Helps\n * developers understand WHY the validator cares, not just WHAT it\n * found missing.\n */\n reason: string;\n /**\n * Set of compatible Prisma scalar types. Empty means \"any scalar\".\n */\n types: CompatibleTypes;\n /**\n * Must this field carry a single-column unique constraint? Implies\n * the field is a stable lookup key for library handlers.\n */\n unique?: boolean;\n};\n\nexport type RequiredModel = {\n name: string;\n fields: RequiredField[];\n};\n\n// Common type aliases -- keep the per-field declarations terse.\nconst ID = [\"Int\", \"BigInt\"] as const;\nconst ID_NULLABLE = ID;\nconst MONEY = [\"Decimal\", \"Float\"] as const;\nconst STRING = [\"String\"] as const;\nconst BOOL = [\"Boolean\"] as const;\nconst TIME = [\"DateTime\"] as const;\nconst JSONISH = [\"Json\", \"String\"] as const;\nconst ANY: readonly string[] = [];\n\n/**\n * Returns the list of required models for a given manifest, in priority\n * order. Each model lists the fields its library consumers read.\n *\n * `User` is always required.\n * `Invitation` is required when features.invitations.\n * `PaymentMethod` + `CreditTransaction` are required when features.billing.\n * `ActivityLog` is required when features.impersonation or features.invitations.\n */\nexport function requiredModelsForManifest(manifest: AppManifest): RequiredModel[] {\n const out: RequiredModel[] = [];\n\n const userFields: RequiredField[] = [\n {\n name: \"id\",\n reason: \"JIT lookup + impersonation token claims\",\n types: ID,\n },\n {\n name: \"email\",\n reason: \"JIT email lookup + auto-promote-to-admin\",\n types: STRING,\n unique: true,\n },\n { name: \"name\", reason: \"/api/auth/me, UserMenu display\", types: STRING },\n { name: \"role\", reason: \"JIT role resolution + admin gating\", types: STRING },\n { name: \"is_active\", reason: \"/api/auth/me response shape\", types: BOOL },\n {\n name: \"credit_balance\",\n reason: \"JIT initial balance + /api/auth/me\",\n types: MONEY,\n },\n {\n name: \"password\",\n reason: \"schema-inherited not-null filler at JIT create\",\n types: STRING,\n },\n {\n name: \"parent_id\",\n reason: \"invitation auto-accept sets parent_id from invite\",\n types: ID_NULLABLE,\n },\n { name: \"created_at\", reason: \"audit / ordering\", types: TIME },\n { name: \"updated_at\", reason: \"audit / ordering\", types: TIME },\n ];\n\n if (manifest.features.billing) {\n userFields.push(\n {\n name: \"stripe_customer_id\",\n reason: \"createBillingHandlers user shape\",\n types: STRING,\n },\n {\n name: \"stripe_default_payment_method\",\n reason: \"createBillingHandlers user shape\",\n types: STRING,\n },\n {\n name: \"stripe_default_payment_method_display\",\n reason: \"createBillingHandlers user shape\",\n types: STRING,\n },\n {\n name: \"auto_recharge_enabled\",\n reason: \"/api/billing/auto-recharge\",\n types: BOOL,\n },\n {\n name: \"auto_recharge_threshold\",\n reason: \"/api/billing/auto-recharge\",\n types: MONEY,\n },\n {\n name: \"auto_recharge_amount\",\n reason: \"/api/billing/auto-recharge\",\n types: MONEY,\n },\n );\n }\n\n out.push({ name: \"User\", fields: userFields });\n\n if (manifest.features.billing) {\n out.push({\n name: \"PaymentMethod\",\n fields: [\n { name: \"id\", reason: \"createBillingHandlers row shape\", types: ID },\n {\n name: \"user_id\",\n reason: \"createBillingHandlers row shape\",\n types: ID,\n },\n {\n name: \"stripe_payment_method_id\",\n reason: \"createBillingHandlers row shape (unique key)\",\n types: STRING,\n unique: true,\n },\n { name: \"brand\", reason: \"createBillingHandlers row shape\", types: STRING },\n { name: \"last4\", reason: \"createBillingHandlers row shape\", types: STRING },\n {\n name: \"exp_month\",\n reason: \"createBillingHandlers row shape\",\n types: [\"Int\"],\n },\n {\n name: \"exp_year\",\n reason: \"createBillingHandlers row shape\",\n types: [\"Int\"],\n },\n {\n name: \"is_default\",\n reason: \"createBillingHandlers row shape\",\n types: BOOL,\n },\n {\n name: \"created_at\",\n reason: \"createBillingHandlers row shape\",\n types: TIME,\n },\n ],\n });\n out.push({\n name: \"CreditTransaction\",\n fields: [\n { name: \"id\", reason: \"createBillingHandlers row shape\", types: ID },\n {\n name: \"user_id\",\n reason: \"createBillingHandlers row shape\",\n types: ID,\n },\n // `type` is commonly an enum -- accept ANY scalar so spokes can use\n // `String` or a Prisma enum interchangeably.\n { name: \"type\", reason: \"createBillingHandlers row shape\", types: ANY },\n {\n name: \"amount\",\n reason: \"createBillingHandlers row shape\",\n types: MONEY,\n },\n {\n name: \"description\",\n reason: \"createBillingHandlers row shape\",\n types: STRING,\n },\n {\n name: \"stripe_payment_intent_id\",\n reason: \"createBillingHandlers webhook idempotency\",\n types: STRING,\n unique: true,\n },\n {\n name: \"payment_method_display\",\n reason: \"createBillingHandlers row shape\",\n types: STRING,\n },\n {\n name: \"created_at\",\n reason: \"createBillingHandlers row shape\",\n types: TIME,\n },\n ],\n });\n }\n\n if (manifest.features.invitations) {\n out.push({\n name: \"Invitation\",\n fields: [\n {\n name: \"id\",\n reason: \"createInvitationHandlers + JIT invitation accept\",\n types: ID,\n },\n {\n name: \"token\",\n reason: \"createInvitationHandlers findUnique\",\n types: STRING,\n unique: true,\n },\n { name: \"email\", reason: \"JIT invitation lookup\", types: STRING },\n {\n name: \"intended_role\",\n reason: \"JIT role inheritance\",\n types: STRING,\n },\n {\n name: \"parent_id\",\n reason: \"JIT parent_id inheritance\",\n types: ID_NULLABLE,\n },\n { name: \"expires_at\", reason: \"JIT expiry filter\", types: TIME },\n {\n name: \"accepted_at\",\n reason: \"JIT acceptance toggle\",\n types: TIME,\n },\n {\n name: \"accepted_by_user_id\",\n reason: \"JIT writes acceptance with new user id\",\n types: ID_NULLABLE,\n },\n { name: \"created_at\", reason: \"ordering\", types: TIME },\n ],\n });\n }\n\n if (manifest.features.impersonation || manifest.features.invitations) {\n out.push({\n name: \"ActivityLog\",\n fields: [\n { name: \"id\", reason: \"audit row\", types: ID },\n {\n name: \"user_id\",\n reason: \"createImpersonateHandlers audit writes\",\n types: ID_NULLABLE,\n },\n {\n name: \"action\",\n reason: \"createImpersonateHandlers audit writes\",\n types: STRING,\n },\n {\n name: \"metadata\",\n reason: \"createImpersonateHandlers audit writes\",\n types: JSONISH,\n },\n { name: \"created_at\", reason: \"audit row\", types: TIME },\n ],\n });\n }\n\n return out;\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,142 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ modelHasSingleColumnUnique,
4
+ parsePrismaSchema
5
+ } from "./chunk-ZQE3CYBR.js";
6
+ import {
7
+ requiredModelsForManifest
8
+ } from "./chunk-DN2IENY7.js";
9
+
10
+ // src/validate-spoke.ts
11
+ import { readFileSync, existsSync } from "fs";
12
+ import { resolve, join } from "path";
13
+ import process from "process";
14
+ import { loadManifest } from "@augmenting-integrations/platform/manifest";
15
+ function formatFailure(f) {
16
+ switch (f.kind) {
17
+ case "missing_model":
18
+ return ` missing model: ${f.model}`;
19
+ case "missing_field":
20
+ return ` ${f.model}.${f.field} missing (required by: ${f.reason})`;
21
+ case "wrong_type":
22
+ return ` ${f.model}.${f.field} has type ${f.actual}, expected one of: ${f.expected.join(" | ")} (required by: ${f.reason})`;
23
+ case "missing_unique":
24
+ return ` ${f.model}.${f.field} missing single-column @unique (required by: ${f.reason})`;
25
+ }
26
+ }
27
+ function validateSpokeAgainstParsed(manifest, parsed) {
28
+ const required = requiredModelsForManifest(manifest);
29
+ const failures = [];
30
+ for (const req of required) {
31
+ const found = parsed.find((m) => m.name === req.name);
32
+ if (!found) {
33
+ failures.push({ kind: "missing_model", model: req.name });
34
+ continue;
35
+ }
36
+ const byName = new Map(found.fields.map((f) => [f.name, f]));
37
+ for (const reqField of req.fields) {
38
+ const actual = byName.get(reqField.name);
39
+ if (!actual) {
40
+ failures.push({
41
+ kind: "missing_field",
42
+ model: req.name,
43
+ field: reqField.name,
44
+ reason: reqField.reason
45
+ });
46
+ continue;
47
+ }
48
+ if (reqField.types.length > 0 && !reqField.types.includes(actual.type)) {
49
+ failures.push({
50
+ kind: "wrong_type",
51
+ model: req.name,
52
+ field: reqField.name,
53
+ actual: actual.type,
54
+ expected: reqField.types,
55
+ reason: reqField.reason
56
+ });
57
+ }
58
+ if (reqField.unique && !modelHasSingleColumnUnique(found, reqField.name)) {
59
+ failures.push({
60
+ kind: "missing_unique",
61
+ model: req.name,
62
+ field: reqField.name,
63
+ reason: reqField.reason
64
+ });
65
+ }
66
+ }
67
+ }
68
+ return failures;
69
+ }
70
+ async function runValidateSpoke(argv) {
71
+ let cwd = process.cwd();
72
+ for (let i = 0; i < argv.length; i++) {
73
+ const a = argv[i];
74
+ if (a === "--cwd" || a === "--root") {
75
+ const next = argv[i + 1];
76
+ if (next) {
77
+ cwd = resolve(next);
78
+ i++;
79
+ }
80
+ }
81
+ }
82
+ let manifest;
83
+ try {
84
+ manifest = loadManifest({ cwd });
85
+ } catch (err) {
86
+ process.stderr.write(`validate-spoke: ${err.message}
87
+ `);
88
+ process.exit(1);
89
+ }
90
+ if (manifest.dataPlane.type === "none") {
91
+ process.stdout.write(
92
+ `validate-spoke: ${manifest.appSlug} has dataPlane.type=none; nothing to validate.
93
+ `
94
+ );
95
+ return;
96
+ }
97
+ const schemaPath = join(cwd, "prisma", "schema.prisma");
98
+ if (!existsSync(schemaPath)) {
99
+ process.stderr.write(
100
+ `validate-spoke: prisma/schema.prisma not found at ${schemaPath}
101
+ Manifest declares a data plane but the schema file is missing.
102
+ `
103
+ );
104
+ process.exit(1);
105
+ }
106
+ const schemaSrc = readFileSync(schemaPath, "utf8");
107
+ const parsed = parsePrismaSchema(schemaSrc);
108
+ const failures = validateSpokeAgainstParsed(manifest, parsed);
109
+ if (failures.length > 0) {
110
+ process.stderr.write(
111
+ `validate-spoke: ${failures.length} canonical schema violation(s) in ${schemaPath}:
112
+ `
113
+ );
114
+ for (const f of failures) {
115
+ process.stderr.write(`${formatFailure(f)}
116
+ `);
117
+ }
118
+ process.stderr.write(
119
+ "\nThe app.manifest.json features list expects the listed canonical models,\nfields, scalar types, and unique constraints. Either fix prisma/schema.prisma\nor turn the feature off in the manifest. See\n@augmenting-integrations/deploy-tools README for the contract.\n"
120
+ );
121
+ process.exit(1);
122
+ }
123
+ process.stdout.write(
124
+ `validate-spoke: OK -- ${manifest.appSlug} schema satisfies the canonical contract for features: ` + Object.entries(manifest.features).filter(([, v]) => v).map(([k]) => k).join(", ") + "\n"
125
+ );
126
+ }
127
+ var isDirectInvocation = import.meta.url === `file://${process.argv[1]}` || process.argv[1]?.endsWith("validate-spoke.js");
128
+ if (isDirectInvocation) {
129
+ runValidateSpoke(process.argv.slice(2)).catch((err) => {
130
+ process.stderr.write(
131
+ `validate-spoke: ${err instanceof Error ? err.message : String(err)}
132
+ `
133
+ );
134
+ process.exit(1);
135
+ });
136
+ }
137
+
138
+ export {
139
+ validateSpokeAgainstParsed,
140
+ runValidateSpoke
141
+ };
142
+ //# sourceMappingURL=chunk-SYUDHJI7.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/validate-spoke.ts"],"sourcesContent":["import { readFileSync, existsSync } from \"node:fs\";\nimport { resolve, join } from \"node:path\";\nimport process from \"node:process\";\n\nimport { loadManifest } from \"@augmenting-integrations/platform/manifest\";\n\nimport { requiredModelsForManifest } from \"./canonical/schema.js\";\nimport {\n parsePrismaSchema,\n modelHasSingleColumnUnique,\n type ParsedModel,\n} from \"./prisma-parser.js\";\n\n// =============================================================================\n// `augint validate-spoke`\n//\n// Inputs:\n// - app.manifest.json (validated by platform/manifest loader)\n// - prisma/schema.prisma (parsed locally)\n//\n// Output:\n// - Exit 0 on success with a one-line \"OK\" summary.\n// - Exit 1 on validation failure with a field-by-field list of what's\n// missing/wrong and WHY each requirement is enforced.\n//\n// Apex apps with `dataPlane.type === \"none\"` skip Prisma validation\n// entirely (no schema file expected). Spokes always validate.\n// =============================================================================\n\ntype Failure =\n | { kind: \"missing_model\"; model: string }\n | { kind: \"missing_field\"; model: string; field: string; reason: string }\n | {\n kind: \"wrong_type\";\n model: string;\n field: string;\n actual: string;\n expected: readonly string[];\n reason: string;\n }\n | { kind: \"missing_unique\"; model: string; field: string; reason: string };\n\nfunction formatFailure(f: Failure): string {\n switch (f.kind) {\n case \"missing_model\":\n return ` missing model: ${f.model}`;\n case \"missing_field\":\n return ` ${f.model}.${f.field} missing (required by: ${f.reason})`;\n case \"wrong_type\":\n return (\n ` ${f.model}.${f.field} has type ${f.actual}, expected one of: ` +\n `${f.expected.join(\" | \")} (required by: ${f.reason})`\n );\n case \"missing_unique\":\n return (\n ` ${f.model}.${f.field} missing single-column @unique` +\n ` (required by: ${f.reason})`\n );\n }\n}\n\nexport function validateSpokeAgainstParsed(\n manifest: ReturnType<typeof loadManifest>,\n parsed: ParsedModel[],\n): Failure[] {\n const required = requiredModelsForManifest(manifest);\n const failures: Failure[] = [];\n\n for (const req of required) {\n const found = parsed.find((m) => m.name === req.name);\n if (!found) {\n failures.push({ kind: \"missing_model\", model: req.name });\n continue;\n }\n const byName = new Map(found.fields.map((f) => [f.name, f]));\n\n for (const reqField of req.fields) {\n const actual = byName.get(reqField.name);\n if (!actual) {\n failures.push({\n kind: \"missing_field\",\n model: req.name,\n field: reqField.name,\n reason: reqField.reason,\n });\n continue;\n }\n\n if (reqField.types.length > 0 && !reqField.types.includes(actual.type)) {\n failures.push({\n kind: \"wrong_type\",\n model: req.name,\n field: reqField.name,\n actual: actual.type,\n expected: reqField.types,\n reason: reqField.reason,\n });\n }\n\n if (reqField.unique && !modelHasSingleColumnUnique(found, reqField.name)) {\n failures.push({\n kind: \"missing_unique\",\n model: req.name,\n field: reqField.name,\n reason: reqField.reason,\n });\n }\n }\n }\n\n return failures;\n}\n\nexport async function runValidateSpoke(argv: string[]): Promise<void> {\n let cwd = process.cwd();\n for (let i = 0; i < argv.length; i++) {\n const a = argv[i];\n if (a === \"--cwd\" || a === \"--root\") {\n const next = argv[i + 1];\n if (next) {\n cwd = resolve(next);\n i++;\n }\n }\n }\n\n let manifest: ReturnType<typeof loadManifest>;\n try {\n manifest = loadManifest({ cwd });\n } catch (err) {\n process.stderr.write(`validate-spoke: ${(err as Error).message}\\n`);\n process.exit(1);\n }\n\n // Apex auth-broker has no data plane -- nothing to validate.\n if (manifest.dataPlane.type === \"none\") {\n process.stdout.write(\n `validate-spoke: ${manifest.appSlug} has dataPlane.type=none; nothing to validate.\\n`,\n );\n return;\n }\n\n const schemaPath = join(cwd, \"prisma\", \"schema.prisma\");\n if (!existsSync(schemaPath)) {\n process.stderr.write(\n `validate-spoke: prisma/schema.prisma not found at ${schemaPath}\\n` +\n \"Manifest declares a data plane but the schema file is missing.\\n\",\n );\n process.exit(1);\n }\n\n const schemaSrc = readFileSync(schemaPath, \"utf8\");\n const parsed = parsePrismaSchema(schemaSrc);\n const failures = validateSpokeAgainstParsed(manifest, parsed);\n\n if (failures.length > 0) {\n process.stderr.write(\n `validate-spoke: ${failures.length} canonical schema violation(s) in ${schemaPath}:\\n`,\n );\n for (const f of failures) {\n process.stderr.write(`${formatFailure(f)}\\n`);\n }\n process.stderr.write(\n \"\\nThe app.manifest.json features list expects the listed canonical models,\\n\" +\n \"fields, scalar types, and unique constraints. Either fix prisma/schema.prisma\\n\" +\n \"or turn the feature off in the manifest. See\\n\" +\n \"@augmenting-integrations/deploy-tools README for the contract.\\n\",\n );\n process.exit(1);\n }\n\n process.stdout.write(\n `validate-spoke: OK -- ${manifest.appSlug} schema satisfies the canonical contract for features: ` +\n Object.entries(manifest.features)\n .filter(([, v]) => v)\n .map(([k]) => k)\n .join(\", \") +\n \"\\n\",\n );\n}\n\nconst isDirectInvocation =\n import.meta.url === `file://${process.argv[1]}` ||\n process.argv[1]?.endsWith(\"validate-spoke.js\");\nif (isDirectInvocation) {\n runValidateSpoke(process.argv.slice(2)).catch((err) => {\n process.stderr.write(\n `validate-spoke: ${err instanceof Error ? err.message : String(err)}\\n`,\n );\n process.exit(1);\n });\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,70 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/prisma-parser.ts
4
+ var MODEL_BLOCK = /model\s+([A-Za-z_][A-Za-z0-9_]*)\s*\{([\s\S]*?)\n\s*\}/g;
5
+ var FIELD_HEAD = /^([A-Za-z_][A-Za-z0-9_]*)\s+([A-Za-z_][A-Za-z0-9_]*)(\?|\[\])?/;
6
+ var MODEL_ATTR_LIST = /\(\s*\[([^\]]+)\]/;
7
+ function parseAttributeList(line) {
8
+ const m = line.match(MODEL_ATTR_LIST);
9
+ if (!m) return [];
10
+ return m[1].split(",").map((s) => s.trim()).filter((s) => s.length > 0).map((s) => s.split(":")[0].trim());
11
+ }
12
+ function parsePrismaSchema(source) {
13
+ const stripped = source.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/[^\n]*/g, "");
14
+ const out = [];
15
+ for (const match of stripped.matchAll(MODEL_BLOCK)) {
16
+ const name = match[1];
17
+ const body = match[2];
18
+ const fields = [];
19
+ const uniqueGroups = [];
20
+ const indexGroups = [];
21
+ for (const rawLine of body.split("\n")) {
22
+ const line = rawLine.trim();
23
+ if (line === "") continue;
24
+ if (line.startsWith("@@")) {
25
+ if (line.startsWith("@@unique")) {
26
+ const fieldsInGroup = parseAttributeList(line);
27
+ if (fieldsInGroup.length > 0) uniqueGroups.push(fieldsInGroup);
28
+ } else if (line.startsWith("@@index")) {
29
+ const fieldsInGroup = parseAttributeList(line);
30
+ if (fieldsInGroup.length > 0) indexGroups.push(fieldsInGroup);
31
+ } else if (line.startsWith("@@id")) {
32
+ const fieldsInGroup = parseAttributeList(line);
33
+ if (fieldsInGroup.length > 0) uniqueGroups.push(fieldsInGroup);
34
+ }
35
+ continue;
36
+ }
37
+ if (line.startsWith("@")) continue;
38
+ const head = line.match(FIELD_HEAD);
39
+ if (!head) continue;
40
+ const fieldName = head[1];
41
+ const fieldType = head[2];
42
+ const tail = head[3] ?? "";
43
+ const optional = tail === "?";
44
+ const list = tail === "[]";
45
+ const attributes = /* @__PURE__ */ new Set();
46
+ if (/\s@id(\s|$|\()/.test(line)) attributes.add("id");
47
+ if (/\s@unique(\s|$|\()/.test(line)) attributes.add("unique");
48
+ if (attributes.has("unique")) uniqueGroups.push([fieldName]);
49
+ if (attributes.has("id")) uniqueGroups.push([fieldName]);
50
+ fields.push({
51
+ name: fieldName,
52
+ type: fieldType,
53
+ optional,
54
+ list,
55
+ attributes
56
+ });
57
+ }
58
+ out.push({ name, fields, uniqueGroups, indexGroups });
59
+ }
60
+ return out;
61
+ }
62
+ function modelHasSingleColumnUnique(model, field) {
63
+ return model.uniqueGroups.some((g) => g.length === 1 && g[0] === field);
64
+ }
65
+
66
+ export {
67
+ parsePrismaSchema,
68
+ modelHasSingleColumnUnique
69
+ };
70
+ //# sourceMappingURL=chunk-ZQE3CYBR.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/prisma-parser.ts"],"sourcesContent":["// =============================================================================\n// Minimal Prisma schema parser. Extracts model declarations, per-field\n// metadata (name + scalar type + optional/list marker + inline @unique /\n// @id), and model-level attributes (@@unique, @@index, @@id).\n//\n// Scope: only what `augint validate-spoke` needs to enforce the canonical\n// library contract. We intentionally do NOT validate Prisma-side native\n// type mappings (`@db.VarChar`, `@db.Decimal(...)`); those are out of the\n// contract surface. Type drift at the scalar level (`String` vs `Int`,\n// `Decimal` vs `String`) is what library handlers actually break on.\n// =============================================================================\n\nexport type ParsedFieldAttribute = \"id\" | \"unique\";\n\nexport type ParsedField = {\n /** Field name as it appears in the model body. */\n name: string;\n /** Scalar type, e.g. `String`, `BigInt`, `Decimal`, `Json`, `DateTime`, `Boolean`, `Int`. */\n type: string;\n /** True if the type carried a trailing `?`. */\n optional: boolean;\n /** True if the type carried a trailing `[]` (relation list or scalar list). */\n list: boolean;\n /** Inline field attributes (`@id`, `@unique`). Relation attributes are ignored. */\n attributes: Set<ParsedFieldAttribute>;\n};\n\nexport type ParsedModel = {\n name: string;\n fields: ParsedField[];\n /**\n * Field names participating in a single-column or composite `@@unique([...])`.\n * Each inner array is one composite. A single-column `@unique` is folded in\n * automatically as `[fieldName]` so callers can ask one question.\n */\n uniqueGroups: string[][];\n /**\n * Field names participating in `@@index([...])`. Each inner array is one\n * index.\n */\n indexGroups: string[][];\n};\n\nconst MODEL_BLOCK = /model\\s+([A-Za-z_][A-Za-z0-9_]*)\\s*\\{([\\s\\S]*?)\\n\\s*\\}/g;\nconst FIELD_HEAD = /^([A-Za-z_][A-Za-z0-9_]*)\\s+([A-Za-z_][A-Za-z0-9_]*)(\\?|\\[\\])?/;\nconst MODEL_ATTR_LIST = /\\(\\s*\\[([^\\]]+)\\]/;\n\nfunction parseAttributeList(line: string): string[] {\n const m = line.match(MODEL_ATTR_LIST);\n if (!m) return [];\n return (\n m[1]!\n .split(\",\")\n .map((s) => s.trim())\n .filter((s) => s.length > 0)\n // Drop named-group qualifiers like `name: ...` -- we only care about field names.\n .map((s) => s.split(\":\")[0]!.trim())\n );\n}\n\n/**\n * Parse a Prisma schema file. Comments are stripped first; the result\n * preserves model declaration order.\n */\nexport function parsePrismaSchema(source: string): ParsedModel[] {\n // Strip line comments and block comments before pattern-matching so an\n // attribute embedded in a comment doesn't get treated as real.\n const stripped = source.replace(/\\/\\*[\\s\\S]*?\\*\\//g, \"\").replace(/\\/\\/[^\\n]*/g, \"\");\n\n const out: ParsedModel[] = [];\n for (const match of stripped.matchAll(MODEL_BLOCK)) {\n const name = match[1]!;\n const body = match[2]!;\n const fields: ParsedField[] = [];\n const uniqueGroups: string[][] = [];\n const indexGroups: string[][] = [];\n\n for (const rawLine of body.split(\"\\n\")) {\n const line = rawLine.trim();\n if (line === \"\") continue;\n\n if (line.startsWith(\"@@\")) {\n if (line.startsWith(\"@@unique\")) {\n const fieldsInGroup = parseAttributeList(line);\n if (fieldsInGroup.length > 0) uniqueGroups.push(fieldsInGroup);\n } else if (line.startsWith(\"@@index\")) {\n const fieldsInGroup = parseAttributeList(line);\n if (fieldsInGroup.length > 0) indexGroups.push(fieldsInGroup);\n } else if (line.startsWith(\"@@id\")) {\n const fieldsInGroup = parseAttributeList(line);\n if (fieldsInGroup.length > 0) uniqueGroups.push(fieldsInGroup);\n }\n continue;\n }\n\n if (line.startsWith(\"@\")) continue;\n\n const head = line.match(FIELD_HEAD);\n if (!head) continue;\n\n const fieldName = head[1]!;\n const fieldType = head[2]!;\n const tail = head[3] ?? \"\";\n const optional = tail === \"?\";\n const list = tail === \"[]\";\n\n const attributes = new Set<ParsedFieldAttribute>();\n if (/\\s@id(\\s|$|\\()/.test(line)) attributes.add(\"id\");\n if (/\\s@unique(\\s|$|\\()/.test(line)) attributes.add(\"unique\");\n\n // Single-column @unique folds into uniqueGroups for uniform lookup.\n if (attributes.has(\"unique\")) uniqueGroups.push([fieldName]);\n // Single-column @id (e.g. `id Int @id`) also counts as unique.\n if (attributes.has(\"id\")) uniqueGroups.push([fieldName]);\n\n fields.push({\n name: fieldName,\n type: fieldType,\n optional,\n list,\n attributes,\n });\n }\n\n out.push({ name, fields, uniqueGroups, indexGroups });\n }\n return out;\n}\n\n/**\n * True if the model has a single-column unique constraint on `field`.\n * Composite uniques never count -- the canonical contract requires\n * single-column lookup keys (the library handlers do `findUnique({ where:\n * { token: ... } })`, which a composite wouldn't satisfy).\n */\nexport function modelHasSingleColumnUnique(model: ParsedModel, field: string): boolean {\n return model.uniqueGroups.some((g) => g.length === 1 && g[0] === field);\n}\n"],"mappings":";;;AA2CA,IAAM,cAAc;AACpB,IAAM,aAAa;AACnB,IAAM,kBAAkB;AAExB,SAAS,mBAAmB,MAAwB;AAClD,QAAM,IAAI,KAAK,MAAM,eAAe;AACpC,MAAI,CAAC,EAAG,QAAO,CAAC;AAChB,SACE,EAAE,CAAC,EACA,MAAM,GAAG,EACT,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EACnB,OAAO,CAAC,MAAM,EAAE,SAAS,CAAC,EAE1B,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,EAAE,CAAC,EAAG,KAAK,CAAC;AAEzC;AAMO,SAAS,kBAAkB,QAA+B;AAG/D,QAAM,WAAW,OAAO,QAAQ,qBAAqB,EAAE,EAAE,QAAQ,eAAe,EAAE;AAElF,QAAM,MAAqB,CAAC;AAC5B,aAAW,SAAS,SAAS,SAAS,WAAW,GAAG;AAClD,UAAM,OAAO,MAAM,CAAC;AACpB,UAAM,OAAO,MAAM,CAAC;AACpB,UAAM,SAAwB,CAAC;AAC/B,UAAM,eAA2B,CAAC;AAClC,UAAM,cAA0B,CAAC;AAEjC,eAAW,WAAW,KAAK,MAAM,IAAI,GAAG;AACtC,YAAM,OAAO,QAAQ,KAAK;AAC1B,UAAI,SAAS,GAAI;AAEjB,UAAI,KAAK,WAAW,IAAI,GAAG;AACzB,YAAI,KAAK,WAAW,UAAU,GAAG;AAC/B,gBAAM,gBAAgB,mBAAmB,IAAI;AAC7C,cAAI,cAAc,SAAS,EAAG,cAAa,KAAK,aAAa;AAAA,QAC/D,WAAW,KAAK,WAAW,SAAS,GAAG;AACrC,gBAAM,gBAAgB,mBAAmB,IAAI;AAC7C,cAAI,cAAc,SAAS,EAAG,aAAY,KAAK,aAAa;AAAA,QAC9D,WAAW,KAAK,WAAW,MAAM,GAAG;AAClC,gBAAM,gBAAgB,mBAAmB,IAAI;AAC7C,cAAI,cAAc,SAAS,EAAG,cAAa,KAAK,aAAa;AAAA,QAC/D;AACA;AAAA,MACF;AAEA,UAAI,KAAK,WAAW,GAAG,EAAG;AAE1B,YAAM,OAAO,KAAK,MAAM,UAAU;AAClC,UAAI,CAAC,KAAM;AAEX,YAAM,YAAY,KAAK,CAAC;AACxB,YAAM,YAAY,KAAK,CAAC;AACxB,YAAM,OAAO,KAAK,CAAC,KAAK;AACxB,YAAM,WAAW,SAAS;AAC1B,YAAM,OAAO,SAAS;AAEtB,YAAM,aAAa,oBAAI,IAA0B;AACjD,UAAI,iBAAiB,KAAK,IAAI,EAAG,YAAW,IAAI,IAAI;AACpD,UAAI,qBAAqB,KAAK,IAAI,EAAG,YAAW,IAAI,QAAQ;AAG5D,UAAI,WAAW,IAAI,QAAQ,EAAG,cAAa,KAAK,CAAC,SAAS,CAAC;AAE3D,UAAI,WAAW,IAAI,IAAI,EAAG,cAAa,KAAK,CAAC,SAAS,CAAC;AAEvD,aAAO,KAAK;AAAA,QACV,MAAM;AAAA,QACN,MAAM;AAAA,QACN;AAAA,QACA;AAAA,QACA;AAAA,MACF,CAAC;AAAA,IACH;AAEA,QAAI,KAAK,EAAE,MAAM,QAAQ,cAAc,YAAY,CAAC;AAAA,EACtD;AACA,SAAO;AACT;AAQO,SAAS,2BAA2B,OAAoB,OAAwB;AACrF,SAAO,MAAM,aAAa,KAAK,CAAC,MAAM,EAAE,WAAW,KAAK,EAAE,CAAC,MAAM,KAAK;AACxE;","names":[]}
package/dist/cli.js CHANGED
@@ -1,13 +1,15 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  runValidateSpoke
4
- } from "./chunk-A7IIB34G.js";
4
+ } from "./chunk-SYUDHJI7.js";
5
5
  import {
6
6
  runValidateAppRoster
7
7
  } from "./chunk-ZOKDIN2F.js";
8
8
  import {
9
9
  runPackageNextLambda
10
10
  } from "./chunk-RYVRSM25.js";
11
+ import "./chunk-ZQE3CYBR.js";
12
+ import "./chunk-DN2IENY7.js";
11
13
 
12
14
  // src/cli.ts
13
15
  import process from "process";
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/cli.ts"],"sourcesContent":["import process from \"node:process\";\nimport { runValidateSpoke } from \"./validate-spoke.js\";\nimport { runPackageNextLambda } from \"./package-next-lambda.js\";\nimport { runValidateAppRoster } from \"./validate-app-roster.js\";\n\nconst USAGE = `Usage: augint <command> [args]\n\nCommands:\n validate-spoke Validate Prisma schema against canonical contract\n validate-app-roster Validate tenant app roster (infra YAML + apex JSON + optional spoke mirrors)\n package-next-lambda Package Next standalone build for Lambda\n`;\n\nasync function main(): Promise<void> {\n const [, , cmd, ...rest] = process.argv;\n if (!cmd || cmd === \"--help\" || cmd === \"-h\") {\n process.stdout.write(USAGE);\n return;\n }\n switch (cmd) {\n case \"validate-spoke\":\n await runValidateSpoke(rest);\n return;\n case \"validate-app-roster\":\n await runValidateAppRoster(rest);\n return;\n case \"package-next-lambda\":\n await runPackageNextLambda(rest);\n return;\n default:\n process.stderr.write(`augint: unknown command '${cmd}'\\n${USAGE}`);\n process.exit(2);\n }\n}\n\nmain().catch((err) => {\n process.stderr.write(`augint: ${err instanceof Error ? err.message : String(err)}\\n`);\n process.exit(1);\n});\n"],"mappings":";;;;;;;;;;;;AAAA,OAAO,aAAa;AAKpB,IAAM,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAQd,eAAe,OAAsB;AACnC,QAAM,CAAC,EAAE,EAAE,KAAK,GAAG,IAAI,IAAI,QAAQ;AACnC,MAAI,CAAC,OAAO,QAAQ,YAAY,QAAQ,MAAM;AAC5C,YAAQ,OAAO,MAAM,KAAK;AAC1B;AAAA,EACF;AACA,UAAQ,KAAK;AAAA,IACX,KAAK;AACH,YAAM,iBAAiB,IAAI;AAC3B;AAAA,IACF,KAAK;AACH,YAAM,qBAAqB,IAAI;AAC/B;AAAA,IACF,KAAK;AACH,YAAM,qBAAqB,IAAI;AAC/B;AAAA,IACF;AACE,cAAQ,OAAO,MAAM,4BAA4B,GAAG;AAAA,EAAM,KAAK,EAAE;AACjE,cAAQ,KAAK,CAAC;AAAA,EAClB;AACF;AAEA,KAAK,EAAE,MAAM,CAAC,QAAQ;AACpB,UAAQ,OAAO,MAAM,WAAW,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,CAAI;AACpF,UAAQ,KAAK,CAAC;AAChB,CAAC;","names":[]}
1
+ {"version":3,"sources":["../src/cli.ts"],"sourcesContent":["import process from \"node:process\";\nimport { runValidateSpoke } from \"./validate-spoke.js\";\nimport { runPackageNextLambda } from \"./package-next-lambda.js\";\nimport { runValidateAppRoster } from \"./validate-app-roster.js\";\n\nconst USAGE = `Usage: augint <command> [args]\n\nCommands:\n validate-spoke Validate Prisma schema against canonical contract\n validate-app-roster Validate tenant app roster (infra YAML + apex JSON + optional spoke mirrors)\n package-next-lambda Package Next standalone build for Lambda\n`;\n\nasync function main(): Promise<void> {\n const [, , cmd, ...rest] = process.argv;\n if (!cmd || cmd === \"--help\" || cmd === \"-h\") {\n process.stdout.write(USAGE);\n return;\n }\n switch (cmd) {\n case \"validate-spoke\":\n await runValidateSpoke(rest);\n return;\n case \"validate-app-roster\":\n await runValidateAppRoster(rest);\n return;\n case \"package-next-lambda\":\n await runPackageNextLambda(rest);\n return;\n default:\n process.stderr.write(`augint: unknown command '${cmd}'\\n${USAGE}`);\n process.exit(2);\n }\n}\n\nmain().catch((err) => {\n process.stderr.write(`augint: ${err instanceof Error ? err.message : String(err)}\\n`);\n process.exit(1);\n});\n"],"mappings":";;;;;;;;;;;;;;AAAA,OAAO,aAAa;AAKpB,IAAM,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAQd,eAAe,OAAsB;AACnC,QAAM,CAAC,EAAE,EAAE,KAAK,GAAG,IAAI,IAAI,QAAQ;AACnC,MAAI,CAAC,OAAO,QAAQ,YAAY,QAAQ,MAAM;AAC5C,YAAQ,OAAO,MAAM,KAAK;AAC1B;AAAA,EACF;AACA,UAAQ,KAAK;AAAA,IACX,KAAK;AACH,YAAM,iBAAiB,IAAI;AAC3B;AAAA,IACF,KAAK;AACH,YAAM,qBAAqB,IAAI;AAC/B;AAAA,IACF,KAAK;AACH,YAAM,qBAAqB,IAAI;AAC/B;AAAA,IACF;AACE,cAAQ,OAAO,MAAM,4BAA4B,GAAG;AAAA,EAAM,KAAK,EAAE;AACjE,cAAQ,KAAK,CAAC;AAAA,EAClB;AACF;AAEA,KAAK,EAAE,MAAM,CAAC,QAAQ;AACpB,UAAQ,OAAO,MAAM,WAAW,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,CAAI;AACpF,UAAQ,KAAK,CAAC;AAChB,CAAC;","names":[]}
@@ -1,11 +1,41 @@
1
+ export type ParsedFieldAttribute = "id" | "unique";
2
+ export type ParsedField = {
3
+ /** Field name as it appears in the model body. */
4
+ name: string;
5
+ /** Scalar type, e.g. `String`, `BigInt`, `Decimal`, `Json`, `DateTime`, `Boolean`, `Int`. */
6
+ type: string;
7
+ /** True if the type carried a trailing `?`. */
8
+ optional: boolean;
9
+ /** True if the type carried a trailing `[]` (relation list or scalar list). */
10
+ list: boolean;
11
+ /** Inline field attributes (`@id`, `@unique`). Relation attributes are ignored. */
12
+ attributes: Set<ParsedFieldAttribute>;
13
+ };
1
14
  export type ParsedModel = {
2
15
  name: string;
3
- fields: string[];
16
+ fields: ParsedField[];
17
+ /**
18
+ * Field names participating in a single-column or composite `@@unique([...])`.
19
+ * Each inner array is one composite. A single-column `@unique` is folded in
20
+ * automatically as `[fieldName]` so callers can ask one question.
21
+ */
22
+ uniqueGroups: string[][];
23
+ /**
24
+ * Field names participating in `@@index([...])`. Each inner array is one
25
+ * index.
26
+ */
27
+ indexGroups: string[][];
4
28
  };
5
29
  /**
6
- * Parse a Prisma schema file. Returns one ParsedModel per `model X { ... }`
7
- * block in declaration order. Field names are extracted from the start of
8
- * each non-blank, non-comment, non-attribute line inside the body.
30
+ * Parse a Prisma schema file. Comments are stripped first; the result
31
+ * preserves model declaration order.
9
32
  */
10
33
  export declare function parsePrismaSchema(source: string): ParsedModel[];
34
+ /**
35
+ * True if the model has a single-column unique constraint on `field`.
36
+ * Composite uniques never count -- the canonical contract requires
37
+ * single-column lookup keys (the library handlers do `findUnique({ where:
38
+ * { token: ... } })`, which a composite wouldn't satisfy).
39
+ */
40
+ export declare function modelHasSingleColumnUnique(model: ParsedModel, field: string): boolean;
11
41
  //# sourceMappingURL=prisma-parser.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"prisma-parser.d.ts","sourceRoot":"","sources":["../src/prisma-parser.ts"],"names":[],"mappings":"AAaA,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,WAAW,EAAE,CAmB/D"}
1
+ {"version":3,"file":"prisma-parser.d.ts","sourceRoot":"","sources":["../src/prisma-parser.ts"],"names":[],"mappings":"AAYA,MAAM,MAAM,oBAAoB,GAAG,IAAI,GAAG,QAAQ,CAAC;AAEnD,MAAM,MAAM,WAAW,GAAG;IACxB,kDAAkD;IAClD,IAAI,EAAE,MAAM,CAAC;IACb,6FAA6F;IAC7F,IAAI,EAAE,MAAM,CAAC;IACb,+CAA+C;IAC/C,QAAQ,EAAE,OAAO,CAAC;IAClB,+EAA+E;IAC/E,IAAI,EAAE,OAAO,CAAC;IACd,mFAAmF;IACnF,UAAU,EAAE,GAAG,CAAC,oBAAoB,CAAC,CAAC;CACvC,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB;;;;OAIG;IACH,YAAY,EAAE,MAAM,EAAE,EAAE,CAAC;IACzB;;;OAGG;IACH,WAAW,EAAE,MAAM,EAAE,EAAE,CAAC;CACzB,CAAC;AAmBF;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,WAAW,EAAE,CA+D/D;AAED;;;;;GAKG;AACH,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAErF"}
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ modelHasSingleColumnUnique,
4
+ parsePrismaSchema
5
+ } from "./chunk-ZQE3CYBR.js";
6
+ export {
7
+ modelHasSingleColumnUnique,
8
+ parsePrismaSchema
9
+ };
10
+ //# sourceMappingURL=prisma-parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -1,2 +1,27 @@
1
+ import { loadManifest } from "@augmenting-integrations/platform/manifest";
2
+ import { type ParsedModel } from "./prisma-parser.js";
3
+ type Failure = {
4
+ kind: "missing_model";
5
+ model: string;
6
+ } | {
7
+ kind: "missing_field";
8
+ model: string;
9
+ field: string;
10
+ reason: string;
11
+ } | {
12
+ kind: "wrong_type";
13
+ model: string;
14
+ field: string;
15
+ actual: string;
16
+ expected: readonly string[];
17
+ reason: string;
18
+ } | {
19
+ kind: "missing_unique";
20
+ model: string;
21
+ field: string;
22
+ reason: string;
23
+ };
24
+ export declare function validateSpokeAgainstParsed(manifest: ReturnType<typeof loadManifest>, parsed: ParsedModel[]): Failure[];
1
25
  export declare function runValidateSpoke(argv: string[]): Promise<void>;
26
+ export {};
2
27
  //# sourceMappingURL=validate-spoke.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"validate-spoke.d.ts","sourceRoot":"","sources":["../src/validate-spoke.ts"],"names":[],"mappings":"AA0BA,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CA+FpE"}
1
+ {"version":3,"file":"validate-spoke.d.ts","sourceRoot":"","sources":["../src/validate-spoke.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,YAAY,EAAE,MAAM,4CAA4C,CAAC;AAG1E,OAAO,EAGL,KAAK,WAAW,EACjB,MAAM,oBAAoB,CAAC;AAkB5B,KAAK,OAAO,GACR;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACxC;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACvE;IACE,IAAI,EAAE,YAAY,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5B,MAAM,EAAE,MAAM,CAAC;CAChB,GACD;IAAE,IAAI,EAAE,gBAAgB,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAqB7E,wBAAgB,0BAA0B,CACxC,QAAQ,EAAE,UAAU,CAAC,OAAO,YAAY,CAAC,EACzC,MAAM,EAAE,WAAW,EAAE,GACpB,OAAO,EAAE,CA+CX;AAED,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAkEpE"}
@@ -1,8 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- runValidateSpoke
4
- } from "./chunk-A7IIB34G.js";
3
+ runValidateSpoke,
4
+ validateSpokeAgainstParsed
5
+ } from "./chunk-SYUDHJI7.js";
6
+ import "./chunk-ZQE3CYBR.js";
7
+ import "./chunk-DN2IENY7.js";
5
8
  export {
6
- runValidateSpoke
9
+ runValidateSpoke,
10
+ validateSpokeAgainstParsed
7
11
  };
8
12
  //# sourceMappingURL=validate-spoke.js.map
package/package.json CHANGED
@@ -1,12 +1,26 @@
1
1
  {
2
2
  "name": "@augmenting-integrations/deploy-tools",
3
- "version": "8.8.0",
3
+ "version": "8.9.0",
4
4
  "description": "Augint platform deploy tooling. `augint validate-spoke` checks Prisma canonical schema against the app manifest; `augint package-next-lambda` packages a Next standalone build for Lambda. Shared across every spoke + apex repo so deploy logic isn't copy/pasted into each app's CI workflow.",
5
5
  "license": "MIT",
6
6
  "publishConfig": {
7
7
  "access": "public"
8
8
  },
9
9
  "type": "module",
10
+ "exports": {
11
+ "./prisma-parser": {
12
+ "types": "./dist/prisma-parser.d.ts",
13
+ "default": "./dist/prisma-parser.js"
14
+ },
15
+ "./validate-spoke": {
16
+ "types": "./dist/validate-spoke.d.ts",
17
+ "default": "./dist/validate-spoke.js"
18
+ },
19
+ "./canonical/schema": {
20
+ "types": "./dist/canonical/schema.d.ts",
21
+ "default": "./dist/canonical/schema.js"
22
+ }
23
+ },
10
24
  "bin": {
11
25
  "augint": "./dist/cli.js",
12
26
  "augint-validate-spoke": "./dist/validate-spoke.js",
@@ -19,14 +33,14 @@
19
33
  "README.md"
20
34
  ],
21
35
  "dependencies": {
22
- "@augmenting-integrations/platform": "8.8.0"
36
+ "@augmenting-integrations/platform": "8.9.0"
23
37
  },
24
38
  "devDependencies": {
25
39
  "@types/node": "^20.17.6",
26
40
  "tsup": "^8.3.5",
27
41
  "typescript": "^5.7.2",
28
42
  "vitest": "^4.1.5",
29
- "@augmenting-integrations/platform": "8.8.0"
43
+ "@augmenting-integrations/platform": "8.9.0"
30
44
  },
31
45
  "scripts": {
32
46
  "build": "tsup",
@@ -1,230 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- // src/validate-spoke.ts
4
- import { readFileSync, existsSync } from "fs";
5
- import { resolve, join } from "path";
6
- import process from "process";
7
- import { loadManifest } from "@augmenting-integrations/platform/manifest";
8
-
9
- // src/canonical/schema.ts
10
- function requiredModelsForManifest(manifest) {
11
- const out = [];
12
- out.push({
13
- name: "User",
14
- fields: [
15
- { name: "id", reason: "JIT lookup + impersonation token claims" },
16
- { name: "email", reason: "JIT email lookup + auto-promote-to-admin" },
17
- { name: "name", reason: "/api/auth/me, UserMenu display" },
18
- { name: "role", reason: "JIT role resolution + admin gating" },
19
- { name: "is_active", reason: "/api/auth/me response shape" },
20
- { name: "credit_balance", reason: "JIT initial balance + /api/auth/me" },
21
- { name: "password", reason: "schema-inherited not-null filler at JIT create" },
22
- { name: "parent_id", reason: "invitation auto-accept sets parent_id from invite" },
23
- { name: "created_at", reason: "audit / ordering" },
24
- { name: "updated_at", reason: "audit / ordering" }
25
- ]
26
- });
27
- if (manifest.features.billing) {
28
- out[0].fields.push(
29
- { name: "stripe_customer_id", reason: "createBillingHandlers user shape" },
30
- {
31
- name: "stripe_default_payment_method",
32
- reason: "createBillingHandlers user shape"
33
- },
34
- {
35
- name: "stripe_default_payment_method_display",
36
- reason: "createBillingHandlers user shape"
37
- },
38
- { name: "auto_recharge_enabled", reason: "/api/billing/auto-recharge" },
39
- { name: "auto_recharge_threshold", reason: "/api/billing/auto-recharge" },
40
- { name: "auto_recharge_amount", reason: "/api/billing/auto-recharge" }
41
- );
42
- out.push({
43
- name: "PaymentMethod",
44
- fields: [
45
- { name: "id", reason: "createBillingHandlers row shape" },
46
- { name: "user_id", reason: "createBillingHandlers row shape" },
47
- {
48
- name: "stripe_payment_method_id",
49
- reason: "createBillingHandlers row shape (unique key)"
50
- },
51
- { name: "brand", reason: "createBillingHandlers row shape" },
52
- { name: "last4", reason: "createBillingHandlers row shape" },
53
- { name: "exp_month", reason: "createBillingHandlers row shape" },
54
- { name: "exp_year", reason: "createBillingHandlers row shape" },
55
- { name: "is_default", reason: "createBillingHandlers row shape" },
56
- { name: "created_at", reason: "createBillingHandlers row shape" }
57
- ]
58
- });
59
- out.push({
60
- name: "CreditTransaction",
61
- fields: [
62
- { name: "id", reason: "createBillingHandlers row shape" },
63
- { name: "user_id", reason: "createBillingHandlers row shape" },
64
- { name: "type", reason: "createBillingHandlers row shape" },
65
- { name: "amount", reason: "createBillingHandlers row shape" },
66
- { name: "description", reason: "createBillingHandlers row shape" },
67
- {
68
- name: "stripe_payment_intent_id",
69
- reason: "createBillingHandlers webhook idempotency"
70
- },
71
- { name: "payment_method_display", reason: "createBillingHandlers row shape" },
72
- { name: "created_at", reason: "createBillingHandlers row shape" }
73
- ]
74
- });
75
- }
76
- if (manifest.features.invitations) {
77
- out.push({
78
- name: "Invitation",
79
- fields: [
80
- { name: "id", reason: "createInvitationHandlers + JIT invitation accept" },
81
- { name: "token", reason: "createInvitationHandlers findUnique" },
82
- { name: "email", reason: "JIT invitation lookup" },
83
- { name: "intended_role", reason: "JIT role inheritance" },
84
- { name: "parent_id", reason: "JIT parent_id inheritance" },
85
- { name: "expires_at", reason: "JIT expiry filter" },
86
- { name: "accepted_at", reason: "JIT acceptance toggle" },
87
- {
88
- name: "accepted_by_user_id",
89
- reason: "JIT writes acceptance with new user id"
90
- },
91
- { name: "created_at", reason: "ordering" }
92
- ]
93
- });
94
- }
95
- if (manifest.features.impersonation || manifest.features.invitations) {
96
- out.push({
97
- name: "ActivityLog",
98
- fields: [
99
- { name: "id", reason: "audit row" },
100
- { name: "user_id", reason: "createImpersonateHandlers audit writes" },
101
- { name: "action", reason: "createImpersonateHandlers audit writes" },
102
- { name: "metadata", reason: "createImpersonateHandlers audit writes" },
103
- { name: "created_at", reason: "audit row" }
104
- ]
105
- });
106
- }
107
- return out;
108
- }
109
-
110
- // src/prisma-parser.ts
111
- function parsePrismaSchema(source) {
112
- const out = [];
113
- const stripped = source.replace(/\/\/[^\n]*/g, "");
114
- const modelRegex = /model\s+([A-Za-z_][A-Za-z0-9_]*)\s*\{([\s\S]*?)\}/g;
115
- for (const match of stripped.matchAll(modelRegex)) {
116
- const name = match[1];
117
- const body = match[2];
118
- const fields = [];
119
- for (const rawLine of body.split("\n")) {
120
- const line = rawLine.trim();
121
- if (line === "") continue;
122
- if (line.startsWith("@@")) continue;
123
- if (line.startsWith("@")) continue;
124
- const fieldMatch = /^([A-Za-z_][A-Za-z0-9_]*)\s+/.exec(line);
125
- if (fieldMatch) fields.push(fieldMatch[1]);
126
- }
127
- out.push({ name, fields });
128
- }
129
- return out;
130
- }
131
-
132
- // src/validate-spoke.ts
133
- async function runValidateSpoke(argv) {
134
- let cwd = process.cwd();
135
- for (let i = 0; i < argv.length; i++) {
136
- const a = argv[i];
137
- if (a === "--cwd" || a === "--root") {
138
- const next = argv[i + 1];
139
- if (next) {
140
- cwd = resolve(next);
141
- i++;
142
- }
143
- }
144
- }
145
- let manifest;
146
- try {
147
- manifest = loadManifest({ cwd });
148
- } catch (err) {
149
- process.stderr.write(`validate-spoke: ${err.message}
150
- `);
151
- process.exit(1);
152
- }
153
- if (manifest.dataPlane.type === "none") {
154
- process.stdout.write(
155
- `validate-spoke: ${manifest.appSlug} has dataPlane.type=none; nothing to validate.
156
- `
157
- );
158
- return;
159
- }
160
- const schemaPath = join(cwd, "prisma", "schema.prisma");
161
- if (!existsSync(schemaPath)) {
162
- process.stderr.write(
163
- `validate-spoke: prisma/schema.prisma not found at ${schemaPath}
164
- Manifest declares a data plane but the schema file is missing.
165
- `
166
- );
167
- process.exit(1);
168
- }
169
- const schemaSrc = readFileSync(schemaPath, "utf8");
170
- const parsed = parsePrismaSchema(schemaSrc);
171
- const required = requiredModelsForManifest(manifest);
172
- const failures = [];
173
- for (const req of required) {
174
- const found = parsed.find((m) => m.name === req.name);
175
- if (!found) {
176
- failures.push({ kind: "missing_model", model: req.name });
177
- continue;
178
- }
179
- const has = new Set(found.fields);
180
- for (const field of req.fields) {
181
- if (!has.has(field.name)) {
182
- failures.push({
183
- kind: "missing_field",
184
- model: req.name,
185
- field: field.name,
186
- reason: field.reason
187
- });
188
- }
189
- }
190
- }
191
- if (failures.length > 0) {
192
- process.stderr.write(
193
- `validate-spoke: ${failures.length} canonical schema violation(s) in ${schemaPath}:
194
- `
195
- );
196
- for (const f of failures) {
197
- if (f.kind === "missing_model") {
198
- process.stderr.write(` missing model: ${f.model}
199
- `);
200
- } else {
201
- process.stderr.write(
202
- ` ${f.model}.${f.field} missing (required by: ${f.reason})
203
- `
204
- );
205
- }
206
- }
207
- process.stderr.write(
208
- "\nThe app.manifest.json features list expects the listed canonical fields.\nEither add them to prisma/schema.prisma or turn the feature off in the\nmanifest. See @augmenting-integrations/deploy-tools README for the contract.\n"
209
- );
210
- process.exit(1);
211
- }
212
- process.stdout.write(
213
- `validate-spoke: OK -- ${manifest.appSlug} schema satisfies the canonical contract for features: ` + Object.entries(manifest.features).filter(([, v]) => v).map(([k]) => k).join(", ") + "\n"
214
- );
215
- }
216
- var isDirectInvocation = import.meta.url === `file://${process.argv[1]}` || process.argv[1]?.endsWith("validate-spoke.js");
217
- if (isDirectInvocation) {
218
- runValidateSpoke(process.argv.slice(2)).catch((err) => {
219
- process.stderr.write(
220
- `validate-spoke: ${err instanceof Error ? err.message : String(err)}
221
- `
222
- );
223
- process.exit(1);
224
- });
225
- }
226
-
227
- export {
228
- runValidateSpoke
229
- };
230
- //# sourceMappingURL=chunk-A7IIB34G.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/validate-spoke.ts","../src/canonical/schema.ts","../src/prisma-parser.ts"],"sourcesContent":["import { readFileSync, existsSync } from \"node:fs\";\nimport { resolve, join } from \"node:path\";\nimport process from \"node:process\";\n\nimport { loadManifest } from \"@augmenting-integrations/platform/manifest\";\n\nimport { requiredModelsForManifest } from \"./canonical/schema.js\";\nimport { parsePrismaSchema } from \"./prisma-parser.js\";\n\n// =============================================================================\n// `augint validate-spoke`\n//\n// Inputs:\n// - app.manifest.json (validated by platform/manifest loader)\n// - prisma/schema.prisma (parsed locally)\n//\n// Output:\n// - Exit 0 on success with a one-line \"OK\" summary.\n// - Exit 1 on validation failure with a field-by-field list of what's\n// missing and WHY each missing field is required by the library\n// handlers it serves.\n//\n// Apex apps with `dataPlane.type === \"none\"` skip Prisma validation\n// entirely (no schema file expected). Spokes always validate.\n// =============================================================================\n\nexport async function runValidateSpoke(argv: string[]): Promise<void> {\n let cwd = process.cwd();\n for (let i = 0; i < argv.length; i++) {\n const a = argv[i];\n if (a === \"--cwd\" || a === \"--root\") {\n const next = argv[i + 1];\n if (next) {\n cwd = resolve(next);\n i++;\n }\n }\n }\n\n let manifest: ReturnType<typeof loadManifest>;\n try {\n manifest = loadManifest({ cwd });\n } catch (err) {\n process.stderr.write(`validate-spoke: ${(err as Error).message}\\n`);\n process.exit(1);\n }\n\n // Apex auth-broker has no data plane -- nothing to validate.\n if (manifest.dataPlane.type === \"none\") {\n process.stdout.write(\n `validate-spoke: ${manifest.appSlug} has dataPlane.type=none; nothing to validate.\\n`,\n );\n return;\n }\n\n const schemaPath = join(cwd, \"prisma\", \"schema.prisma\");\n if (!existsSync(schemaPath)) {\n process.stderr.write(\n `validate-spoke: prisma/schema.prisma not found at ${schemaPath}\\n` +\n \"Manifest declares a data plane but the schema file is missing.\\n\",\n );\n process.exit(1);\n }\n\n const schemaSrc = readFileSync(schemaPath, \"utf8\");\n const parsed = parsePrismaSchema(schemaSrc);\n const required = requiredModelsForManifest(manifest);\n\n type Failure =\n | { kind: \"missing_model\"; model: string }\n | { kind: \"missing_field\"; model: string; field: string; reason: string };\n const failures: Failure[] = [];\n\n for (const req of required) {\n const found = parsed.find((m) => m.name === req.name);\n if (!found) {\n failures.push({ kind: \"missing_model\", model: req.name });\n continue;\n }\n const has = new Set(found.fields);\n for (const field of req.fields) {\n if (!has.has(field.name)) {\n failures.push({\n kind: \"missing_field\",\n model: req.name,\n field: field.name,\n reason: field.reason,\n });\n }\n }\n }\n\n if (failures.length > 0) {\n process.stderr.write(\n `validate-spoke: ${failures.length} canonical schema violation(s) in ${schemaPath}:\\n`,\n );\n for (const f of failures) {\n if (f.kind === \"missing_model\") {\n process.stderr.write(` missing model: ${f.model}\\n`);\n } else {\n process.stderr.write(\n ` ${f.model}.${f.field} missing (required by: ${f.reason})\\n`,\n );\n }\n }\n process.stderr.write(\n \"\\nThe app.manifest.json features list expects the listed canonical fields.\\n\" +\n \"Either add them to prisma/schema.prisma or turn the feature off in the\\n\" +\n \"manifest. See @augmenting-integrations/deploy-tools README for the contract.\\n\",\n );\n process.exit(1);\n }\n\n process.stdout.write(\n `validate-spoke: OK -- ${manifest.appSlug} schema satisfies the canonical contract for features: ` +\n Object.entries(manifest.features)\n .filter(([, v]) => v)\n .map(([k]) => k)\n .join(\", \") +\n \"\\n\",\n );\n}\n\nconst isDirectInvocation =\n import.meta.url === `file://${process.argv[1]}` ||\n process.argv[1]?.endsWith(\"validate-spoke.js\");\nif (isDirectInvocation) {\n runValidateSpoke(process.argv.slice(2)).catch((err) => {\n process.stderr.write(\n `validate-spoke: ${err instanceof Error ? err.message : String(err)}\\n`,\n );\n process.exit(1);\n });\n}\n","// =============================================================================\n// Canonical schema contract.\n//\n// Encodes the Prisma model + field names that the library handler factories\n// read at runtime. The validator (`augint validate-spoke`) parses the\n// spoke's prisma/schema.prisma and asserts that every required model + field\n// for the manifest's enabled features is present by name.\n//\n// Drift in canonical naming is the most common class of integration bug\n// (e.g. `password` vs `password_hash`). The validator catches it before\n// `next build` runs.\n// =============================================================================\n\nimport type { AppManifest } from \"@augmenting-integrations/platform/manifest\";\n\nexport type RequiredField = {\n name: string;\n /**\n * Reason this field is required (shown in the error message). Helps\n * developers understand WHY the validator cares, not just WHAT it\n * found missing.\n */\n reason: string;\n};\n\nexport type RequiredModel = {\n name: string;\n fields: RequiredField[];\n};\n\n/**\n * Returns the list of required models for a given manifest, in priority\n * order. Each model lists the fields its library consumers read.\n *\n * `User` is always required.\n * `Invitation` is required when features.invitations.\n * `PaymentMethod` + `CreditTransaction` are required when features.billing.\n * `ActivityLog` is required when features.impersonation or features.invitations.\n */\nexport function requiredModelsForManifest(manifest: AppManifest): RequiredModel[] {\n const out: RequiredModel[] = [];\n\n out.push({\n name: \"User\",\n fields: [\n { name: \"id\", reason: \"JIT lookup + impersonation token claims\" },\n { name: \"email\", reason: \"JIT email lookup + auto-promote-to-admin\" },\n { name: \"name\", reason: \"/api/auth/me, UserMenu display\" },\n { name: \"role\", reason: \"JIT role resolution + admin gating\" },\n { name: \"is_active\", reason: \"/api/auth/me response shape\" },\n { name: \"credit_balance\", reason: \"JIT initial balance + /api/auth/me\" },\n { name: \"password\", reason: \"schema-inherited not-null filler at JIT create\" },\n { name: \"parent_id\", reason: \"invitation auto-accept sets parent_id from invite\" },\n { name: \"created_at\", reason: \"audit / ordering\" },\n { name: \"updated_at\", reason: \"audit / ordering\" },\n ],\n });\n\n if (manifest.features.billing) {\n out[0]!.fields.push(\n { name: \"stripe_customer_id\", reason: \"createBillingHandlers user shape\" },\n {\n name: \"stripe_default_payment_method\",\n reason: \"createBillingHandlers user shape\",\n },\n {\n name: \"stripe_default_payment_method_display\",\n reason: \"createBillingHandlers user shape\",\n },\n { name: \"auto_recharge_enabled\", reason: \"/api/billing/auto-recharge\" },\n { name: \"auto_recharge_threshold\", reason: \"/api/billing/auto-recharge\" },\n { name: \"auto_recharge_amount\", reason: \"/api/billing/auto-recharge\" },\n );\n\n out.push({\n name: \"PaymentMethod\",\n fields: [\n { name: \"id\", reason: \"createBillingHandlers row shape\" },\n { name: \"user_id\", reason: \"createBillingHandlers row shape\" },\n {\n name: \"stripe_payment_method_id\",\n reason: \"createBillingHandlers row shape (unique key)\",\n },\n { name: \"brand\", reason: \"createBillingHandlers row shape\" },\n { name: \"last4\", reason: \"createBillingHandlers row shape\" },\n { name: \"exp_month\", reason: \"createBillingHandlers row shape\" },\n { name: \"exp_year\", reason: \"createBillingHandlers row shape\" },\n { name: \"is_default\", reason: \"createBillingHandlers row shape\" },\n { name: \"created_at\", reason: \"createBillingHandlers row shape\" },\n ],\n });\n out.push({\n name: \"CreditTransaction\",\n fields: [\n { name: \"id\", reason: \"createBillingHandlers row shape\" },\n { name: \"user_id\", reason: \"createBillingHandlers row shape\" },\n { name: \"type\", reason: \"createBillingHandlers row shape\" },\n { name: \"amount\", reason: \"createBillingHandlers row shape\" },\n { name: \"description\", reason: \"createBillingHandlers row shape\" },\n {\n name: \"stripe_payment_intent_id\",\n reason: \"createBillingHandlers webhook idempotency\",\n },\n { name: \"payment_method_display\", reason: \"createBillingHandlers row shape\" },\n { name: \"created_at\", reason: \"createBillingHandlers row shape\" },\n ],\n });\n }\n\n if (manifest.features.invitations) {\n out.push({\n name: \"Invitation\",\n fields: [\n { name: \"id\", reason: \"createInvitationHandlers + JIT invitation accept\" },\n { name: \"token\", reason: \"createInvitationHandlers findUnique\" },\n { name: \"email\", reason: \"JIT invitation lookup\" },\n { name: \"intended_role\", reason: \"JIT role inheritance\" },\n { name: \"parent_id\", reason: \"JIT parent_id inheritance\" },\n { name: \"expires_at\", reason: \"JIT expiry filter\" },\n { name: \"accepted_at\", reason: \"JIT acceptance toggle\" },\n {\n name: \"accepted_by_user_id\",\n reason: \"JIT writes acceptance with new user id\",\n },\n { name: \"created_at\", reason: \"ordering\" },\n ],\n });\n }\n\n if (manifest.features.impersonation || manifest.features.invitations) {\n out.push({\n name: \"ActivityLog\",\n fields: [\n { name: \"id\", reason: \"audit row\" },\n { name: \"user_id\", reason: \"createImpersonateHandlers audit writes\" },\n { name: \"action\", reason: \"createImpersonateHandlers audit writes\" },\n { name: \"metadata\", reason: \"createImpersonateHandlers audit writes\" },\n { name: \"created_at\", reason: \"audit row\" },\n ],\n });\n }\n\n return out;\n}\n","// =============================================================================\n// Minimal Prisma schema parser. Extracts model names and the field names\n// declared inside each model body. Sufficient to validate that the spoke's\n// schema has the canonical models + fields the library handler factories\n// read.\n//\n// We deliberately do NOT validate Prisma TYPES (BigInt vs Int, Decimal vs\n// Int, etc.). The platform adapter in src/platform/repositories.ts widens\n// to structural shapes; runtime correctness depends on the canonical names\n// being present. Type drift is caught by the spoke's own `tsc` pass when\n// it tries to construct adapters against an actual generated client.\n// =============================================================================\n\nexport type ParsedModel = {\n name: string;\n fields: string[];\n};\n\n/**\n * Parse a Prisma schema file. Returns one ParsedModel per `model X { ... }`\n * block in declaration order. Field names are extracted from the start of\n * each non-blank, non-comment, non-attribute line inside the body.\n */\nexport function parsePrismaSchema(source: string): ParsedModel[] {\n const out: ParsedModel[] = [];\n const stripped = source.replace(/\\/\\/[^\\n]*/g, \"\"); // line comments\n const modelRegex = /model\\s+([A-Za-z_][A-Za-z0-9_]*)\\s*\\{([\\s\\S]*?)\\}/g;\n for (const match of stripped.matchAll(modelRegex)) {\n const name = match[1]!;\n const body = match[2]!;\n const fields: string[] = [];\n for (const rawLine of body.split(\"\\n\")) {\n const line = rawLine.trim();\n if (line === \"\") continue;\n if (line.startsWith(\"@@\")) continue;\n if (line.startsWith(\"@\")) continue;\n const fieldMatch = /^([A-Za-z_][A-Za-z0-9_]*)\\s+/.exec(line);\n if (fieldMatch) fields.push(fieldMatch[1]!);\n }\n out.push({ name, fields });\n }\n return out;\n}\n"],"mappings":"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","names":[]}