swallowkit 2.0.0-alpha.4 → 2.0.0-alpha.6
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/dist/cli/commands/add-auth.js +37 -6
- package/dist/cli/commands/add-auth.js.map +1 -1
- package/dist/cli/commands/init.d.ts +2 -0
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +3 -1
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/provision.d.ts +2 -1
- package/dist/cli/commands/provision.d.ts.map +1 -1
- package/dist/cli/commands/provision.js +66 -4
- package/dist/cli/commands/provision.js.map +1 -1
- package/dist/cli/commands/scaffold.d.ts.map +1 -1
- package/dist/cli/commands/scaffold.js +45 -1
- package/dist/cli/commands/scaffold.js.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +15 -2
- package/dist/cli/index.js.map +1 -1
- package/dist/core/operations/create-model.d.ts +2 -0
- package/dist/core/operations/create-model.d.ts.map +1 -1
- package/dist/core/operations/create-model.js +60 -8
- package/dist/core/operations/create-model.js.map +1 -1
- package/dist/core/operations/provision-deployment.d.ts +45 -0
- package/dist/core/operations/provision-deployment.d.ts.map +1 -0
- package/dist/core/operations/provision-deployment.js +151 -0
- package/dist/core/operations/provision-deployment.js.map +1 -0
- package/dist/core/operations/provision-operations.d.ts +39 -5
- package/dist/core/operations/provision-operations.d.ts.map +1 -1
- package/dist/core/operations/provision-operations.js +609 -103
- package/dist/core/operations/provision-operations.js.map +1 -1
- package/dist/core/project/boundaries.js +3 -3
- package/dist/core/project/boundaries.js.map +1 -1
- package/dist/core/project/capabilities.d.ts +9 -0
- package/dist/core/project/capabilities.d.ts.map +1 -1
- package/dist/core/project/capabilities.js +20 -6
- package/dist/core/project/capabilities.js.map +1 -1
- package/dist/core/project/react-project.js +1 -1
- package/dist/core/project/state.d.ts +7 -0
- package/dist/core/project/state.d.ts.map +1 -1
- package/dist/core/project/state.js +86 -0
- package/dist/core/project/state.js.map +1 -1
- package/dist/core/project/workflows.d.ts.map +1 -1
- package/dist/core/project/workflows.js +43 -7
- package/dist/core/project/workflows.js.map +1 -1
- package/dist/core/scaffold/auth-generator.d.ts.map +1 -1
- package/dist/core/scaffold/auth-generator.js +18 -19
- package/dist/core/scaffold/auth-generator.js.map +1 -1
- package/dist/core/scaffold/native-schema-generator.d.ts +2 -0
- package/dist/core/scaffold/native-schema-generator.d.ts.map +1 -1
- package/dist/core/scaffold/native-schema-generator.js +16 -1
- package/dist/core/scaffold/native-schema-generator.js.map +1 -1
- package/dist/core/verify/index.d.ts +4 -3
- package/dist/core/verify/index.d.ts.map +1 -1
- package/dist/core/verify/index.js +98 -4
- package/dist/core/verify/index.js.map +1 -1
- package/dist/machine/contracts.d.ts +1 -0
- package/dist/machine/contracts.d.ts.map +1 -1
- package/dist/machine/errors.d.ts +3 -2
- package/dist/machine/errors.d.ts.map +1 -1
- package/dist/machine/errors.js +2 -1
- package/dist/machine/errors.js.map +1 -1
- package/dist/machine/index.d.ts.map +1 -1
- package/dist/machine/index.js +20 -13
- package/dist/machine/index.js.map +1 -1
- package/dist/mcp/index.d.ts +3 -2
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +76 -15
- package/dist/mcp/index.js.map +1 -1
- package/dist/utils/azure-cli.d.ts +16 -4
- package/dist/utils/azure-cli.d.ts.map +1 -1
- package/dist/utils/azure-cli.js +166 -4
- package/dist/utils/azure-cli.js.map +1 -1
- package/dist/utils/azure-diagnostics.d.ts +16 -0
- package/dist/utils/azure-diagnostics.d.ts.map +1 -0
- package/dist/utils/azure-diagnostics.js +68 -0
- package/dist/utils/azure-diagnostics.js.map +1 -0
- package/package.json +6 -3
- package/src/__tests__/__snapshots__/boundaries.test.ts.snap +6 -6
- package/src/__tests__/agent-instructions.test.ts +3 -0
- package/src/__tests__/agent-loop-phase34.test.ts +114 -8
- package/src/__tests__/azure-cli.test.ts +5 -2
- package/src/__tests__/fixtures/az wrapper.cmd +2 -0
- package/src/__tests__/fixtures/azure-cli.cjs +130 -0
- package/src/__tests__/fixtures/provision-runner.cjs +28 -0
- package/src/__tests__/generated-project-matrix.test.ts +789 -0
- package/src/__tests__/mcp.test.ts +71 -0
- package/src/__tests__/multi-auth.test.ts +115 -4
- package/src/__tests__/plan-apply.test.ts +111 -0
- package/src/__tests__/provision-runtime.test.ts +562 -0
- package/src/cli/commands/add-auth.ts +36 -6
- package/src/cli/commands/init.ts +4 -4
- package/src/cli/commands/provision.ts +69 -5
- package/src/cli/commands/scaffold.ts +65 -1
- package/src/cli/index.ts +12 -2
- package/src/core/operations/create-model.ts +51 -13
- package/src/core/operations/provision-deployment.ts +167 -0
- package/src/core/operations/provision-operations.ts +597 -145
- package/src/core/project/agent-templates/AGENTS.md +46 -3
- package/src/core/project/agent-templates/CLAUDE.md +9 -0
- package/src/core/project/agent-templates/copilot-instructions.md +10 -0
- package/src/core/project/agent-templates/shared-models.instructions.md +11 -3
- package/src/core/project/boundaries.ts +3 -3
- package/src/core/project/capabilities.ts +29 -6
- package/src/core/project/react-project.ts +1 -1
- package/src/core/project/state.ts +70 -0
- package/src/core/project/workflows.ts +43 -7
- package/src/core/scaffold/auth-generator.ts +18 -19
- package/src/core/scaffold/native-schema-generator.ts +16 -0
- package/src/core/verify/index.ts +95 -5
- package/src/machine/contracts.ts +1 -0
- package/src/machine/errors.ts +4 -2
- package/src/machine/index.ts +23 -16
- package/src/mcp/index.ts +68 -20
- package/src/utils/azure-cli.ts +167 -6
- package/src/utils/azure-diagnostics.ts +75 -0
|
@@ -319,6 +319,20 @@ custom endpoints that derive the scoping key (e.g. `learnerId`) from the
|
|
|
319
319
|
verified principal — never from the request body — and restrict the generated
|
|
320
320
|
write endpoints to admin roles.
|
|
321
321
|
|
|
322
|
+
Configure authentication with `plan auth` / `apply auth` **before** declaring
|
|
323
|
+
`authPolicy`. Without authentication, an explicit policy
|
|
324
|
+
blocks plan/apply/scaffold with `auth-policy-without-auth` before file writes;
|
|
325
|
+
follow `nextActions`, then re-plan. Do not remove a required policy just to pass
|
|
326
|
+
generation.
|
|
327
|
+
An empty `authPolicy = {}` means no explicit policy; it does not enable authentication.
|
|
328
|
+
|
|
329
|
+
With named `auth.schemes`, define `auth.authorization.policies` selecting the
|
|
330
|
+
required schemes and roles, then use `authPolicy = { policy: 'adminOnly' }` (or
|
|
331
|
+
read/write policy names). Role-only declarations do not select a named scheme
|
|
332
|
+
and block with `auth-policy-requires-named-policy`. Conversely, single-provider
|
|
333
|
+
`auth.provider` supports role lists only; a named policy (including a named
|
|
334
|
+
`defaultPolicy`) blocks with `auth-named-policy-requires-named-scheme`.
|
|
335
|
+
|
|
322
336
|
## Verification Is a Completion Gate
|
|
323
337
|
|
|
324
338
|
Before treating an agent-driven implementation as complete, run SwallowKit
|
|
@@ -330,8 +344,17 @@ verification:
|
|
|
330
344
|
|
|
331
345
|
or the equivalent MCP operation.
|
|
332
346
|
|
|
333
|
-
|
|
334
|
-
|
|
347
|
+
Default checks are `structure`, `drift`, `typecheck`, `backend-build`, plus
|
|
348
|
+
configured project checks. `backend-build` checks standalone `functions/`:
|
|
349
|
+
C# uses `dotnet build`, TypeScript uses the project's `tsc --noEmit`, and Python
|
|
350
|
+
uses Python 3 `compileall` (syntax only, not imports or type checking), preferring
|
|
351
|
+
`functions/.venv` over PATH. Missing toolchains are `skip` with an explanation;
|
|
352
|
+
a skipped backend check is **not** evidence that the backend works. Restore the
|
|
353
|
+
toolchain and re-run it before claiming backend validation.
|
|
354
|
+
|
|
355
|
+
Root `build` scripts do not replace standalone backend verification. To include
|
|
356
|
+
frontend production builds, use `--checks "structure,drift,typecheck,backend-build,build"`.
|
|
357
|
+
Quote comma-separated values in PowerShell.
|
|
335
358
|
|
|
336
359
|
When verification fails, use the structured failure explanation:
|
|
337
360
|
|
|
@@ -378,6 +401,24 @@ Use the `swallowkit-provision` Agent Skill or runbook and never bypass its
|
|
|
378
401
|
approval gate. A broad request to deploy does not itself grant the explicit
|
|
379
402
|
approval required by that workflow.
|
|
380
403
|
|
|
404
|
+
Provision applies Bicep once and tracks the original Azure deployment with a
|
|
405
|
+
bounded wait. A local timeout/error, output truncation, or Ctrl+C is not proof
|
|
406
|
+
that Azure failed. `in-progress` means Azure is Running/Accepted; `unknown`
|
|
407
|
+
means its result could not be read; `partial` means Azure succeeded but
|
|
408
|
+
tag/ledger completion is pending. Never re-submit Bicep, reconcile, adopt,
|
|
409
|
+
cancel, or delete just to repair waiting or migration bookkeeping.
|
|
410
|
+
|
|
411
|
+
Retain `.swallowkit/state/provision/` execution checkpoints. Use
|
|
412
|
+
`{{runCmd}} swallowkit machine inspect provision --plan <runId> --wait-seconds 300`
|
|
413
|
+
for read-only status/wait, and after explicit human approval use
|
|
414
|
+
`{{runCmd}} swallowkit machine apply provision --plan <runId> --approve`
|
|
415
|
+
to resume. `runId` equals the original `planId`; fingerprints, subscription,
|
|
416
|
+
deployment identity and migration checks remain in force. Only
|
|
417
|
+
`status: "complete"` / `outcome: "succeeded"` means Azure Succeeded plus verified
|
|
418
|
+
tags and local ledger. Do not print deployment output secrets or raw `az --debug`.
|
|
419
|
+
For older untracked baselines use `plan provision --resume-deployment <name>`
|
|
420
|
+
with original target/regions; it blocks unless ARM/parameter evidence matches.
|
|
421
|
+
|
|
381
422
|
## SwallowKit Agent Skills and Runbooks
|
|
382
423
|
|
|
383
424
|
SwallowKit installs task-specific Agent Skills under:
|
|
@@ -474,7 +515,9 @@ same generated structure.
|
|
|
474
515
|
- Model schema files use `shared/models/{kebab-case}.ts`.
|
|
475
516
|
- The Zod schema constant and inferred TypeScript type use the same PascalCase
|
|
476
517
|
name: `export const Todo = z.object(...); export type Todo = z.infer<typeof Todo>`.
|
|
477
|
-
- Re-export models from `shared/index.ts`.
|
|
518
|
+
- Re-export models from `shared/index.ts`. create-model and plan/apply scaffold
|
|
519
|
+
append missing exports, including manually authored models. Manual exports
|
|
520
|
+
are allowed; this file has no managed markers and existing content is preserved.
|
|
478
521
|
- `id` is a required `z.string()`; only `createdAt` / `updatedAt` are optional
|
|
479
522
|
backend-managed fields.
|
|
480
523
|
- Models may declare `partitionKey`, `authPolicy`, `displayName`,
|
|
@@ -24,7 +24,16 @@ Always-on rules:
|
|
|
24
24
|
after apply).
|
|
25
25
|
- Use plan/apply for deterministic generation.
|
|
26
26
|
- Respect all `requires-human` states and provisioning approval gates.
|
|
27
|
+
- Provision timeouts/Ctrl+C do not prove Azure failed. Inspect the original
|
|
28
|
+
execution checkpoint with `machine inspect provision --plan <runId>`; resume
|
|
29
|
+
`machine apply provision --plan <runId> --approve` only after explicit human
|
|
30
|
+
approval. Preserve checkpoints; never re-submit Bicep/reconcile/cancel/delete
|
|
31
|
+
to repair waiting or tag/ledger failures. Do not print deployment output secrets.
|
|
27
32
|
- Run SwallowKit verification before considering implementation complete.
|
|
33
|
+
- Default verification includes `backend-build` for standalone Functions.
|
|
34
|
+
Skipped toolchains are not validation; Python compilation checks syntax only.
|
|
35
|
+
- Configure auth via plan/apply auth before adding `authPolicy`; otherwise
|
|
36
|
+
scaffold blocks before file writes. Scaffold appends missing shared model exports.
|
|
28
37
|
|
|
29
38
|
Task-specific SwallowKit Agent Skills are available under `.github/skills/`.
|
|
30
39
|
Equivalent agent-agnostic runbooks are available under
|
|
@@ -23,7 +23,17 @@ Key rules:
|
|
|
23
23
|
after apply).
|
|
24
24
|
- Use plan/apply for deterministic generation.
|
|
25
25
|
- Respect `requires-human` states and other human approval gates.
|
|
26
|
+
- Provision timeouts/Ctrl+C do not prove Azure failed. Read `status`, `outcome`,
|
|
27
|
+
and `nextActions`; inspect the original run with `machine inspect provision
|
|
28
|
+
--plan <runId>`, then resume `machine apply provision --plan <runId> --approve`
|
|
29
|
+
only after explicit human approval. Preserve execution checkpoints; never
|
|
30
|
+
re-submit Bicep or use reconcile/cancel/delete to repair waiting or tag/ledger
|
|
31
|
+
failures. Do not print deployment output secrets.
|
|
26
32
|
- Run SwallowKit verification before completion.
|
|
33
|
+
- Default verification includes `backend-build` for standalone Functions.
|
|
34
|
+
Skipped toolchains are not validation; Python compilation checks syntax only.
|
|
35
|
+
- Configure auth via plan/apply auth before adding `authPolicy`; otherwise
|
|
36
|
+
scaffold blocks before file writes. Scaffold appends missing shared model exports.
|
|
27
37
|
|
|
28
38
|
Task-specific SwallowKit Agent Skills live under `.github/skills/`.
|
|
29
39
|
Agent-agnostic equivalents live under `.swallowkit/workflows/`.
|
|
@@ -18,8 +18,9 @@ source-of-truth models, subject to the current result of
|
|
|
18
18
|
- `createdAt` and `updatedAt` are **optional** string fields
|
|
19
19
|
(`z.string().optional()`). The backend manages their values.
|
|
20
20
|
- Export a `displayName` string for UI display.
|
|
21
|
-
-
|
|
22
|
-
|
|
21
|
+
- create-model and plan/apply scaffold append missing model exports to
|
|
22
|
+
`shared/index.ts`, including manually authored models. Manual exports are
|
|
23
|
+
allowed; there are no managed markers and existing content is preserved.
|
|
23
24
|
- For relationships, use nested schemas rather than duplicating model shapes.
|
|
24
25
|
|
|
25
26
|
## Optional model declarations (exact formats)
|
|
@@ -30,7 +31,7 @@ These are the only declarations SwallowKit parses from a model file:
|
|
|
30
31
|
// Cosmos DB partition key (default '/id'). Must reference a schema field.
|
|
31
32
|
export const partitionKey = '/learnerId';
|
|
32
33
|
|
|
33
|
-
// Role-based access control on generated CRUD.
|
|
34
|
+
// Role-based access control on generated CRUD (requires plan/apply auth first).
|
|
34
35
|
export const authPolicy = { roles: ['admin'] };
|
|
35
36
|
// or: { read: ['user'], write: ['admin'] } or: { policy: 'adminOnly' }
|
|
36
37
|
|
|
@@ -48,6 +49,13 @@ export const connectorConfig = {
|
|
|
48
49
|
The full machine-readable contract:
|
|
49
50
|
`{{runCmd}} swallowkit machine inspect capabilities`
|
|
50
51
|
|
|
52
|
+
An explicit `authPolicy` without authentication blocks plan/apply/scaffold with
|
|
53
|
+
`auth-policy-without-auth` before writing files. Follow `nextActions` to configure
|
|
54
|
+
auth, then re-plan. Do not drop a required guard to bypass the prerequisite.
|
|
55
|
+
For named `auth.schemes`, use named authorization policies selecting schemes
|
|
56
|
+
and roles, referenced by `policy` or read/write names. Role-only declarations
|
|
57
|
+
block with `auth-policy-requires-named-policy` rather than emitting missing helpers.
|
|
58
|
+
|
|
51
59
|
After changing a model, do not manually patch generated artifacts.
|
|
52
60
|
|
|
53
61
|
## After changing a model
|
|
@@ -86,7 +86,7 @@ export const OWNERSHIP_POLICIES: OwnershipPolicy[] = [
|
|
|
86
86
|
zone: "shared",
|
|
87
87
|
editPolicy: "edit-outside-markers",
|
|
88
88
|
howToChange:
|
|
89
|
-
"SwallowKit appends
|
|
89
|
+
"SwallowKit appends at the file-specific extension point: marked sections (e.g. `# SwallowKit scaffold registrations`) or append-only files such as shared/index.ts. Follow the path rule; preserve existing generated entries and any markers.",
|
|
90
90
|
},
|
|
91
91
|
{
|
|
92
92
|
ownership: "metadata",
|
|
@@ -134,8 +134,8 @@ const COMMON_LEADING_RULES: BoundaryPathRule[] = [
|
|
|
134
134
|
{
|
|
135
135
|
pattern: "shared/index.ts",
|
|
136
136
|
zone: "shared",
|
|
137
|
-
editPolicy: "edit
|
|
138
|
-
description: "
|
|
137
|
+
editPolicy: "free-edit",
|
|
138
|
+
description: "create-model and scaffold append missing model exports while preserving existing content. No managed markers; manual exports are allowed. Keep exports needed by generated code.",
|
|
139
139
|
},
|
|
140
140
|
];
|
|
141
141
|
|
|
@@ -61,10 +61,19 @@ export interface CapabilitiesContract {
|
|
|
61
61
|
notes: string[];
|
|
62
62
|
};
|
|
63
63
|
machineCommands: string[];
|
|
64
|
+
provisioning: {
|
|
65
|
+
execution: string;
|
|
66
|
+
completion: string;
|
|
67
|
+
tracking: string;
|
|
68
|
+
recovery: string;
|
|
69
|
+
diagnostics: string;
|
|
70
|
+
approval: string;
|
|
71
|
+
};
|
|
64
72
|
verifyChecks: {
|
|
65
73
|
defaultChecks: string[];
|
|
66
74
|
optInChecks: string[];
|
|
67
75
|
customChecks: string;
|
|
76
|
+
backendBuild: string;
|
|
68
77
|
};
|
|
69
78
|
}
|
|
70
79
|
|
|
@@ -109,6 +118,11 @@ export function inspectCapabilities(): CapabilitiesContract {
|
|
|
109
118
|
"Role-based access control metadata applied to the generated CRUD Functions. `roles` guards all operations; `read`/`write` guard per-operation; `policy` references a named authorization policy from swallowkit.config auth.authorization.policies.",
|
|
110
119
|
format:
|
|
111
120
|
"export const authPolicy = { roles: ['admin'] };\n// or per-operation:\nexport const authPolicy = { read: ['user'], write: ['admin'] };\n// or a named policy:\nexport const authPolicy = { policy: 'adminOnly' };",
|
|
121
|
+
notes: [
|
|
122
|
+
"An explicit authPolicy without configured authentication blocks plan/apply/generate scaffold with auth-policy-without-auth before any artifacts are written. Run plan auth, apply auth, then re-plan scaffold. An empty {} declaration keeps the existing behavior: no explicit policy.",
|
|
123
|
+
"With named auth.schemes, define auth.authorization.policies selecting schemes and roles, then reference them via policy or read/write names. A role-only declaration cannot select a named scheme and blocks with auth-policy-requires-named-policy; it is supported by legacy single-provider authentication.",
|
|
124
|
+
"Conversely, single-provider authentication (auth.provider) supports role lists only. Referencing a named policy (authPolicy.policy, read/write names, or a named auth.authorization.defaultPolicy) blocks with auth-named-policy-requires-named-scheme; add a named scheme first.",
|
|
125
|
+
],
|
|
112
126
|
},
|
|
113
127
|
{
|
|
114
128
|
name: "references",
|
|
@@ -186,7 +200,7 @@ export function inspectCapabilities(): CapabilitiesContract {
|
|
|
186
200
|
"Without authentication, plan scaffold reports user-scoped-data-without-auth when the partition key or an owner-like field (userId, learnerId, ownerId, ...) suggests per-user data.",
|
|
187
201
|
],
|
|
188
202
|
guidance: [
|
|
189
|
-
"For multi-user data where users may only touch their own records, add ai-authored custom endpoints that derive the scoping key (e.g. learnerId) from the verified principal — never from the request body — and restrict generated write endpoints to admin roles or disable them.",
|
|
203
|
+
"Configure authentication via plan/apply auth before declaring authPolicy. For multi-user data where users may only touch their own records, add ai-authored custom endpoints that derive the scoping key (e.g. learnerId) from the verified principal — never from the request body — and restrict generated write endpoints to admin roles or disable them.",
|
|
190
204
|
],
|
|
191
205
|
},
|
|
192
206
|
sharedModels: {
|
|
@@ -200,7 +214,7 @@ export function inspectCapabilities(): CapabilitiesContract {
|
|
|
200
214
|
"String formats (email/uuid/url), refinements, and transforms are not re-implemented natively; the BFF Zod schemas remain the authoritative validation.",
|
|
201
215
|
],
|
|
202
216
|
buildNote:
|
|
203
|
-
"verify typecheck resolves the shared package from its built dist/. SwallowKit rebuilds shared automatically during verify; when typechecking manually, build
|
|
217
|
+
"create-model and plan/apply scaffold append missing model exports to shared/index.ts, including manually authored models. Manual exports are allowed (no markers); existing content is preserved. verify typecheck resolves the shared package from its built dist/. SwallowKit rebuilds shared automatically during verify; when typechecking manually, build shared first.",
|
|
204
218
|
},
|
|
205
219
|
seeding: {
|
|
206
220
|
createTemplates: "swallowkit create-dev-seeds <environment> — writes dev-seeds/<environment>/<model>.json templates.",
|
|
@@ -208,20 +222,29 @@ export function inspectCapabilities(): CapabilitiesContract {
|
|
|
208
222
|
notes: ["Editing dev-seeds/*.json alone does nothing; seeds are only applied via dev --seed-env."],
|
|
209
223
|
},
|
|
210
224
|
machineCommands: [
|
|
211
|
-
"inspect project | entities | routes | artifacts | boundaries | infra | drift | capabilities",
|
|
225
|
+
"inspect project | entities | routes | artifacts | boundaries | infra | drift | capabilities; inspect provision --plan <runId> [--wait-seconds <0-3600>]",
|
|
212
226
|
"validate project",
|
|
213
227
|
"generate model <names...> | generate scaffold <model>",
|
|
214
|
-
"plan scaffold <models...> [--api-only] [--skip-infra] | plan auth --provider <p> [--scheme <name>] [--allowed-providers <csv>] | plan provision -g <rg> --location <loc> --swa-location <loc> [--adopt-existing --baseline 0 --what-if] [--reconcile --what-if]",
|
|
215
|
-
"apply scaffold [models...] --plan <id> [--approve] [--skip-infra] | apply auth --plan <id> [--approve] | apply provision --plan <id> --approve",
|
|
228
|
+
"plan scaffold <models...> [--api-only] [--skip-infra] | plan auth --provider <p> [--scheme <name>] [--allowed-providers <csv>] | plan provision -g <rg> --location <loc> --swa-location <loc> [--adopt-existing --baseline 0 --what-if] [--reconcile --what-if] [--resume-deployment <name>]",
|
|
229
|
+
"apply scaffold [models...] --plan <id> [--approve] [--skip-infra] | apply auth --plan <id> [--approve] | apply provision --plan <id> --approve [--wait-seconds <0-3600>] (also resumes the same execution checkpoint)",
|
|
216
230
|
"verify project [--checks <ids>] [--compact]",
|
|
217
231
|
"explain failure [--check <id>]",
|
|
218
232
|
"(regular CLI) migrations add-model <model> [--dry-run] — generate only the container migration after scaffold --skip-infra",
|
|
219
233
|
"(regular CLI) configure-mcp [--version auto|latest|<semver>] — re-select the MCP launcher version in .mcp.json",
|
|
220
234
|
],
|
|
235
|
+
provisioning: {
|
|
236
|
+
execution: "Submit Bicep once with az deployment group create --no-wait, then bounded state polling (default 300 seconds, configurable 0-3600). Azure CLI invocations are bounded separately. Warning-only stderr with exit 0 is not a failure; output capture limits do not terminate az.",
|
|
237
|
+
completion: "status complete/outcome succeeded requires Azure Succeeded plus verified migration tags and local ledger completion. Running/Accepted -> in-progress; unreadable/not-found state -> blocked/outcome unknown; bookkeeping interruption after Azure Succeeded -> blocked/outcome partial; Failed/Canceled -> failed with Azure causes.",
|
|
238
|
+
tracking: "Approved runs persist before Azure writes under .swallowkit/state/provision/. runId equals the original planId and preserves the subscription, RG, deployment ID/correlation, fingerprints and migration state. inspect provision is read-only; apply provision --plan resumes without re-submitting attempted deployments. Ctrl+C/termination may leave Azure running; never cancel/delete without explicit consent.",
|
|
239
|
+
recovery: "For an older untracked baseline, plan provision --resume-deployment <name> compares the existing ARM template and non-secure parameters to the local Bicep/parameters. It observes that deployment and only tags/records it after Succeeded. Mismatches, unreadable evidence, secure parameters or non-baseline migrations block; do not use reconcile just to repair bookkeeping.",
|
|
240
|
+
diagnostics: "Results include bounded/redacted stdout/stderr, omitted-output flags, exit code, signal, failure kind, elapsed time and deployment identity. Deployment output values are not returned. Never print --debug output, keys, tokens or connection strings.",
|
|
241
|
+
approval: "Initial apply and every resume require explicit human approval. A passing local command or an ok envelope is not proof of completed Azure provisioning; inspect status, outcome and nextActions.",
|
|
242
|
+
},
|
|
221
243
|
verifyChecks: {
|
|
222
|
-
defaultChecks: ["structure", "drift", "typecheck"],
|
|
244
|
+
defaultChecks: ["structure", "drift", "typecheck", "backend-build"],
|
|
223
245
|
optInChecks: ["build", "lint", "test"],
|
|
224
246
|
customChecks: "swallowkit.config verify.checks: [{ id, command, title?, timeoutMs? }]",
|
|
247
|
+
backendBuild: "Runs dotnet build in functions/ (C#), the project tsc --noEmit (TypeScript), or Python 3 compileall (syntax only, using functions/.venv before PATH). Missing toolchains are skip with a reason, not proof of validation. Root build scripts do not replace backend-build; use --checks \"structure,drift,typecheck,backend-build,build\" for both.",
|
|
225
248
|
},
|
|
226
249
|
};
|
|
227
250
|
}
|
|
@@ -407,7 +407,7 @@ live in \`functions/local.settings.json\`. Keep their secrets out of source cont
|
|
|
407
407
|
\`\`\`sh
|
|
408
408
|
${pm} run build
|
|
409
409
|
${cmd.exec} swallowkit machine verify project
|
|
410
|
-
${cmd.exec} swallowkit machine verify project --checks structure,drift,typecheck,build
|
|
410
|
+
${cmd.exec} swallowkit machine verify project --checks "structure,drift,typecheck,backend-build,build"
|
|
411
411
|
\`\`\`
|
|
412
412
|
|
|
413
413
|
The build compiles shared schemas, checks the SPA, builds static assets, and
|
|
@@ -57,6 +57,76 @@ export function deletePlanState(planId: string, projectRoot: string = process.cw
|
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
function provisionRunPath(runId: string, projectRoot: string): string {
|
|
61
|
+
if (!/^[a-f0-9]{12}$/.test(runId)) throw new Error("Invalid provision run ID.");
|
|
62
|
+
return path.join(projectRoot, SWALLOWKIT_STATE_DIR, "provision", `${runId}.json`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function saveProvisionRun<T extends { runId: string }>(run: T, projectRoot: string = process.cwd()): void {
|
|
66
|
+
ensureStateDir(projectRoot);
|
|
67
|
+
const file = provisionRunPath(run.runId, projectRoot);
|
|
68
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
69
|
+
const temporary = `${file}.tmp-${process.pid}`;
|
|
70
|
+
fs.writeFileSync(temporary, `${JSON.stringify(run, null, 2)}\n`, { encoding: "utf-8", mode: 0o600 });
|
|
71
|
+
fs.renameSync(temporary, file);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function loadProvisionRun<T>(runId: string, projectRoot: string = process.cwd()): T | null {
|
|
75
|
+
const file = provisionRunPath(runId, projectRoot);
|
|
76
|
+
if (!fs.existsSync(file)) return null;
|
|
77
|
+
// Unlike a preview, discarding a corrupt execution checkpoint could replay Azure writes.
|
|
78
|
+
return JSON.parse(fs.readFileSync(file, "utf-8")) as T;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function listProvisionRuns<T>(projectRoot: string = process.cwd()): T[] {
|
|
82
|
+
const directory = path.join(projectRoot, SWALLOWKIT_STATE_DIR, "provision");
|
|
83
|
+
if (!fs.existsSync(directory)) return [];
|
|
84
|
+
return fs.readdirSync(directory).filter((name) => /^[a-f0-9]{12}\.json$/.test(name))
|
|
85
|
+
.map((name) => loadProvisionRun<T>(name.slice(0, -5), projectRoot)!);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** A live local executor must not share an Azure target with another executor. */
|
|
89
|
+
export function lockProvisionTarget(key: string, projectRoot: string = process.cwd()): () => void {
|
|
90
|
+
if (!/^[a-f0-9]{64}$/.test(key)) throw new Error("Invalid provision lock key.");
|
|
91
|
+
const directory = path.join(ensureStateDir(projectRoot), "provision");
|
|
92
|
+
fs.mkdirSync(directory, { recursive: true });
|
|
93
|
+
const file = path.join(directory, `${key}.lock`);
|
|
94
|
+
const create = () => {
|
|
95
|
+
const descriptor = fs.openSync(file, "wx", 0o600);
|
|
96
|
+
try { fs.writeFileSync(descriptor, String(process.pid)); } finally { fs.closeSync(descriptor); }
|
|
97
|
+
};
|
|
98
|
+
try {
|
|
99
|
+
create();
|
|
100
|
+
} catch (error) {
|
|
101
|
+
if ((error as NodeJS.ErrnoException).code !== "EEXIST") throw error;
|
|
102
|
+
const recoveryFile = `${file}.recovery`;
|
|
103
|
+
let descriptor: number;
|
|
104
|
+
try {
|
|
105
|
+
descriptor = fs.openSync(recoveryFile, "wx", 0o600);
|
|
106
|
+
} catch (recoveryError) {
|
|
107
|
+
if ((recoveryError as NodeJS.ErrnoException).code !== "EEXIST") throw recoveryError;
|
|
108
|
+
throw new Error("Provision lock recovery is already in progress. Inspect the checkpoint before retrying.");
|
|
109
|
+
}
|
|
110
|
+
fs.closeSync(descriptor);
|
|
111
|
+
try {
|
|
112
|
+
const pid = Number(fs.readFileSync(file, "utf-8"));
|
|
113
|
+
if (!Number.isInteger(pid) || pid < 1) throw new Error("Invalid provision lock; inspect the checkpoint before removing the lock.");
|
|
114
|
+
try {
|
|
115
|
+
process.kill(pid, 0);
|
|
116
|
+
} catch (probeError) {
|
|
117
|
+
if ((probeError as NodeJS.ErrnoException).code !== "ESRCH") throw probeError;
|
|
118
|
+
fs.unlinkSync(file);
|
|
119
|
+
create();
|
|
120
|
+
return () => fs.unlinkSync(file);
|
|
121
|
+
}
|
|
122
|
+
throw new Error("Another local provision executor is using this Azure target. Inspect its run; do not start a second deployment.");
|
|
123
|
+
} finally {
|
|
124
|
+
fs.unlinkSync(recoveryFile);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return () => fs.unlinkSync(file);
|
|
128
|
+
}
|
|
129
|
+
|
|
60
130
|
const LAST_VERIFY_FILE = "last-verify.json";
|
|
61
131
|
|
|
62
132
|
export function saveLastVerifyState(result: unknown, projectRoot: string = process.cwd()): void {
|
|
@@ -62,6 +62,13 @@ Goal: add a data model and derive CRUD/BFF/UI/infra deterministically.
|
|
|
62
62
|
the plan warns \`infra-skipped\`; later run \`${runCmd} swallowkit migrations add-model <name>\` to add only the container migration.
|
|
63
63
|
- Read \`data.warnings\`. \`user-scoped-data-without-auth\` means generated CRUD would expose every user's documents;
|
|
64
64
|
add auth (\`swallowkit-add-auth\`) and owner-scoped ai-authored endpoints before exposing the model.
|
|
65
|
+
- \`auth-policy-without-auth\` blocks generation before file writes: configure authentication with plan/apply auth,
|
|
66
|
+
then re-plan. Do not remove a required policy to bypass this prerequisite.
|
|
67
|
+
For named \`auth.schemes\`, define named authorization policies selecting schemes/roles and reference them from
|
|
68
|
+
\`authPolicy.policy\` or read/write names; role-only declarations block with \`auth-policy-requires-named-policy\`.
|
|
69
|
+
Single-provider \`auth.provider\` supports role lists only; named policies block with \`auth-named-policy-requires-named-scheme\`.
|
|
70
|
+
- Scaffold appends missing exports to \`shared/index.ts\` for hand-authored models too.
|
|
71
|
+
Manual exports are allowed; no managed markers exist in that file.
|
|
65
72
|
4. Apply:
|
|
66
73
|
\`${runCmd} swallowkit machine apply scaffold --plan <planId>\`
|
|
67
74
|
5. Verify:
|
|
@@ -160,7 +167,7 @@ the scoping key from the verified principal, never from the request body.
|
|
|
160
167
|
title: "Verify and repair loop",
|
|
161
168
|
skillName: "swallowkit-verify-repair",
|
|
162
169
|
skillDescription:
|
|
163
|
-
"Converge a SwallowKit project to a verified state: run structure/drift/typecheck (and build/lint/test) checks, read evidence with explain failure, repair, and repeat. Use after any code or schema change, when verification fails, when the build/lint/tests are broken, or before finishing a task.",
|
|
170
|
+
"Converge a SwallowKit project to a verified state: run structure/drift/typecheck/backend-build (and build/lint/test) checks, read evidence with explain failure, repair, and repeat. Use after any code or schema change, when verification fails, when the build/lint/tests are broken, or before finishing a task.",
|
|
164
171
|
content: `# Workflow: Verify and repair
|
|
165
172
|
|
|
166
173
|
Goal: converge the project to a verified state after any change.
|
|
@@ -168,14 +175,16 @@ Goal: converge the project to a verified state after any change.
|
|
|
168
175
|
## Loop
|
|
169
176
|
|
|
170
177
|
1. \`${runCmd} swallowkit machine verify project\`
|
|
171
|
-
- default checks: structure, drift, typecheck (+ project-specific checks from swallowkit.config \`verify.checks\`)
|
|
172
|
-
-
|
|
178
|
+
- default checks: structure, drift, typecheck, backend-build (+ project-specific checks from swallowkit.config \`verify.checks\`)
|
|
179
|
+
- backend-build: standalone C# \`dotnet build\`, TypeScript \`tsc --noEmit\`, Python 3 \`compileall\` (syntax only).
|
|
180
|
+
Missing toolchains are skipped with a reason, not proof of a working backend. Restore them and re-run.
|
|
181
|
+
- opt-in heavier checks: \`--checks "structure,drift,typecheck,backend-build,build,lint,test"\` (quote CSV in PowerShell)
|
|
173
182
|
2. If \`summary.done\` is false:
|
|
174
183
|
- \`${runCmd} swallowkit machine explain failure\` → evidence (logTail, findings, violations) and suggestedActions per check.
|
|
175
184
|
- Repair:
|
|
176
185
|
- \`drift\` failures → follow each finding's \`repairAction\` (usually re-run plan/apply scaffold).
|
|
177
186
|
- \`structure\` failures → follow \`suggestedFix\` per violation.
|
|
178
|
-
- \`typecheck\`/\`build\`/\`lint\`/\`test\` failures → fix the code
|
|
187
|
+
- \`typecheck\`/\`backend-build\`/\`build\`/\`lint\`/\`test\` failures → fix the source code, not generated artifacts.
|
|
179
188
|
3. Repeat until \`summary.done: true\`.
|
|
180
189
|
|
|
181
190
|
## Exit criteria
|
|
@@ -205,6 +214,9 @@ Goal: bootstrap Azure once, then apply only pending versioned infrastructure mig
|
|
|
205
214
|
- Inspect infra locally first — no \`az\` calls are needed for inspection.
|
|
206
215
|
- Never edit an applied migration. Create a corrective migration instead.
|
|
207
216
|
- Never use \`--reconcile\` for an ordinary model or resource addition.
|
|
217
|
+
- A local az error/timeout or Ctrl+C does not prove Azure failed. Never
|
|
218
|
+
re-submit, cancel, or delete a deployment to repair a wait or bookkeeping failure.
|
|
219
|
+
- Respect \`status\`, \`outcome\`, and \`nextActions\`, not just \`ok\`.
|
|
208
220
|
|
|
209
221
|
## Steps
|
|
210
222
|
|
|
@@ -212,7 +224,7 @@ Goal: bootstrap Azure once, then apply only pending versioned infrastructure mig
|
|
|
212
224
|
\`${runCmd} swallowkit machine inspect infra\`
|
|
213
225
|
- review parameters, modules, outputs, and container wiring warnings.
|
|
214
226
|
2. Compute the provisioning plan (no resources are created):
|
|
215
|
-
\`${runCmd} swallowkit machine plan provision -g <resource-group> --location <region>\`
|
|
227
|
+
\`${runCmd} swallowkit machine plan provision -g <resource-group> --location <region> --swa-location <swa-region>\`
|
|
216
228
|
- the plan reports \`bootstrap\`, \`migrate\`, \`noop\`, or \`adoption-required\`.
|
|
217
229
|
- it lists only pending migrations and the exact \`az\` commands that would run.
|
|
218
230
|
- for an older untagged environment, first run \`${runCmd} swallowkit migrations init --legacy-baseline\`, then plan with \`--adopt-existing --baseline 0 --what-if\`.
|
|
@@ -220,12 +232,35 @@ Goal: bootstrap Azure once, then apply only pending versioned infrastructure mig
|
|
|
220
232
|
3. Present the plan to the human and wait for explicit approval.
|
|
221
233
|
4. Only after approval:
|
|
222
234
|
\`${runCmd} swallowkit machine apply provision --plan <planId> --approve\`
|
|
223
|
-
5.
|
|
235
|
+
5. If \`in-progress\`, \`unknown\`, or \`partial\`, retain the execution
|
|
236
|
+
checkpoint and read/wait for the original deployment:
|
|
237
|
+
\`${runCmd} swallowkit machine inspect provision --plan <runId> --wait-seconds 300\`.
|
|
238
|
+
Inspection never writes Azure tags or the local ledger.
|
|
239
|
+
6. After fresh explicit human approval, resume:
|
|
240
|
+
\`${runCmd} swallowkit machine apply provision --plan <runId> --approve\`.
|
|
241
|
+
Attempted deployments are not re-submitted; remaining reviewed steps and
|
|
242
|
+
tags/ledger finish only after Azure \`Succeeded\`.
|
|
243
|
+
7. Report completion only when \`status: "complete"\` and
|
|
244
|
+
\`outcome: "succeeded"\`. Never report deployment output secrets.
|
|
245
|
+
|
|
246
|
+
For an older untracked bootstrap, plan with \`--resume-deployment <name>\`
|
|
247
|
+
and the original RG, subscription and regions. Recovery compares its ARM
|
|
248
|
+
template and non-secure parameters to local Bicep before approval. Mismatch,
|
|
249
|
+
unreadable evidence or secure parameters block; do not work around this by
|
|
250
|
+
using reconcile/adoption or replacing the migration manifest.
|
|
224
251
|
|
|
225
252
|
## Terminal states
|
|
226
253
|
|
|
227
254
|
\`apply provision\` without \`--approve\` fails with \`approval-required\` —
|
|
228
255
|
this is the guardrail working as intended, not an error to work around.
|
|
256
|
+
|
|
257
|
+
Azure \`Running\`/\`Accepted\` means \`in-progress\`. An unreadable or missing
|
|
258
|
+
deployment means \`unknown\`, not Azure failure. Azure \`Failed\`/\`Canceled\`
|
|
259
|
+
returns sanitized Azure and operation causes. Azure \`Succeeded\` with incomplete
|
|
260
|
+
tag/ledger writes means \`partial\`; resume bookkeeping without redeploying.
|
|
261
|
+
The default wait is 300 seconds (0-3600 via \`--wait-seconds\`), not unlimited.
|
|
262
|
+
Warning-only stderr with exit 0 is accepted. Diagnostics report local exit,
|
|
263
|
+
signal, elapsed time and truncated/redacted output separately from Azure state.
|
|
229
264
|
`,
|
|
230
265
|
};
|
|
231
266
|
|
|
@@ -254,7 +289,8 @@ under \`.github/skills/\` so skills-aware agents load them automatically.
|
|
|
254
289
|
|
|
255
290
|
- **ai-authored** (write freely): \`shared/models/\`, business logic, custom pages/components, tests.
|
|
256
291
|
- **deterministic** (never hand-edit): artifacts generated by SwallowKit (see \`.swallowkit/artifacts.json\`) and \`.swallowkit/\` metadata. Change models/config and regenerate via plan/apply.
|
|
257
|
-
- **shared
|
|
292
|
+
- **shared**: \`shared/index.ts\` allows manual exports (no markers; SwallowKit appends missing entries).
|
|
293
|
+
For Python \`functions/function_app.py\`, edit outside the registration marker.
|
|
258
294
|
- **infrastructure**: baseline \`infra/\` files are checksum-locked by \`infra/migrations/manifest.json\`; add resources as new migrations (\`swallowkit create-migration <slug>\`, \`swallowkit migrations add-model <model>\`).
|
|
259
295
|
|
|
260
296
|
Full machine-readable contract: \`${runCmd} swallowkit machine inspect boundaries\`
|
|
@@ -117,8 +117,7 @@ export function generateNamedAuthRouterCSharp(config: NormalizedAuthConfig): str
|
|
|
117
117
|
using System.Text;
|
|
118
118
|
using System.Text.Json;
|
|
119
119
|
using Microsoft.Azure.Functions.Worker.Http;
|
|
120
|
-
using Functions.Auth.Schemes;
|
|
121
|
-
namespace Functions.Auth;
|
|
120
|
+
${externalCases ? "using Functions.Auth.Schemes;\n" : ""}namespace Functions.Auth;
|
|
122
121
|
public record AuthPrincipal(string Subject, string Scheme, string Issuer, string[] Roles, IReadOnlyDictionary<string, object?> Claims)
|
|
123
122
|
{
|
|
124
123
|
[Obsolete("Use Subject")] public string UserId => Subject;
|
|
@@ -171,15 +170,15 @@ ${externalCases}
|
|
|
171
170
|
export function generateNamedAuthRouterPython(config: NormalizedAuthConfig): string {
|
|
172
171
|
const external = Object.entries(config.schemes).filter(([,s])=>s.provider === "external-token");
|
|
173
172
|
const imports = external.map(([n])=>`from auth.schemes.${schemeSlug(n).replace(/-/g,"_")}.verifier import verify_external_token as verify_${safeIdentifier(n)}, ExternalTokenVerificationError as Error_${safeIdentifier(n)}`).join("\n");
|
|
174
|
-
const cases = external.map(([n], index)=>`
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
173
|
+
const cases = external.map(([n], index)=>` ${index === 0 ? "if" : "elif"} scheme == ${JSON.stringify(n)}:
|
|
174
|
+
try:
|
|
175
|
+
value = verify_${safeIdentifier(n)}(bearer)
|
|
176
|
+
except Error_${safeIdentifier(n)} as exc:
|
|
177
|
+
raise AuthError(503 if exc.identity_provider_unavailable else 401, "Identity provider unavailable" if exc.identity_provider_unavailable else "Invalid token")
|
|
178
|
+
except Exception:
|
|
179
|
+
raise AuthError(503, "External token verifier unavailable")
|
|
180
|
+
if not value.get("subject") or not isinstance(value.get("roles"), list): raise AuthError(401, "Invalid external principal")
|
|
181
|
+
principal = {"subject": value["subject"], "scheme": scheme, "issuer": value.get("issuer", scheme), "roles": value["roles"], "claims": value.get("claims", {}), "userId": value["subject"]}`).join("\n");
|
|
183
182
|
return `import base64
|
|
184
183
|
import json
|
|
185
184
|
import azure.functions as func
|
|
@@ -199,14 +198,14 @@ def require_auth(req: func.HttpRequest, policy_name: str):
|
|
|
199
198
|
if len(candidates) != 1: raise AuthError(401, "Authentication required" if not candidates else "Ambiguous credentials")
|
|
200
199
|
scheme = candidates[0]
|
|
201
200
|
${cases}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
201
|
+
${external.length ? "elif" : "if"} PROVIDERS[scheme] == "swa":
|
|
202
|
+
try:
|
|
203
|
+
value=json.loads(base64.b64decode(swa).decode("utf-8")); roles=[r for r in value.get("userRoles",[]) if isinstance(r,str)]
|
|
204
|
+
if not value.get("userId") or "authenticated" not in roles: raise ValueError()
|
|
205
|
+
if ALLOWED_PROVIDERS[scheme] and value.get("identityProvider") not in ALLOWED_PROVIDERS[scheme]: raise ValueError()
|
|
206
|
+
principal={"subject":value["userId"],"scheme":scheme,"issuer":"swa:"+value.get("identityProvider", "unknown"),"roles":roles,"claims":value,"userId":value["userId"]}
|
|
207
|
+
except Exception: raise AuthError(401,"Invalid SWA client principal")
|
|
208
|
+
else: raise AuthError(401,"Authentication adapter is not configured")
|
|
210
209
|
require_roles(principal, policy.get("roles", [])); return principal
|
|
211
210
|
def require_roles(principal, roles):
|
|
212
211
|
if roles and not any(role in principal.get("roles",[]) for role in roles): raise AuthError(403,"Insufficient permissions")
|
|
@@ -939,6 +939,21 @@ function ensurePythonCodegenProjectFiles(
|
|
|
939
939
|
return requirementsPath;
|
|
940
940
|
}
|
|
941
941
|
|
|
942
|
+
/** Generated backend_models import pydantic v2 at runtime, so the deployed Functions app must install it. */
|
|
943
|
+
export const PYTHON_SCHEMA_RUNTIME_REQUIREMENT = "pydantic>=2.6,<3";
|
|
944
|
+
|
|
945
|
+
function ensurePythonRuntimeRequirement(functionsRoot: string, session: FileOperationSession): void {
|
|
946
|
+
const requirementsPath = path.join(functionsRoot, "requirements.txt");
|
|
947
|
+
const current = session.fileExists(requirementsPath) ? session.readFile(requirementsPath) : "";
|
|
948
|
+
const declared = current.split(/\r?\n/).some((line) => line.trim().split(/[<>=!~;[ ]/)[0].toLowerCase() === "pydantic");
|
|
949
|
+
if (declared) return;
|
|
950
|
+
const prefix = current && !current.endsWith("\n") ? `${current}\n` : current;
|
|
951
|
+
session.writeFile(requirementsPath, `${prefix}${PYTHON_SCHEMA_RUNTIME_REQUIREMENT}\n`, {
|
|
952
|
+
ownership: "extension-point",
|
|
953
|
+
generator: "native-schema",
|
|
954
|
+
});
|
|
955
|
+
}
|
|
956
|
+
|
|
942
957
|
async function ensureProjectLocalUvCommand(projectRoot: string): Promise<{ command: string; env: NodeJS.ProcessEnv }> {
|
|
943
958
|
const uvEnv = { ...process.env };
|
|
944
959
|
const { localUvExecutable } = getProjectLocalUvPaths(projectRoot);
|
|
@@ -1082,6 +1097,7 @@ async function generatePythonSchemaArtifacts(
|
|
|
1082
1097
|
sourceModel: string
|
|
1083
1098
|
): Promise<void> {
|
|
1084
1099
|
ensurePythonCodegenProjectFiles(functionsRoot, session, sourceModel);
|
|
1100
|
+
ensurePythonRuntimeRequirement(functionsRoot, session);
|
|
1085
1101
|
|
|
1086
1102
|
const packageRoot = path.join(outputDir, "backend_models");
|
|
1087
1103
|
const modelsRoot = path.join(packageRoot, "models");
|