Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 32 additions & 1 deletion resources/agents/azure-deploy.agent.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,35 @@ The phases below are **strictly ordered**. You **must not** start a later phase
4. Generate the deployment artifacts (infra, `azure.yaml`, Dockerfiles, etc.) as directed by the `azure-prepare` skill, following the **azure.yaml hook rules** below.
5. **Step C** — validate the artifacts with `azd package` before declaring the deployment ready. Mandatory.

### Production frontend-to-API topology — deploy owns the final wiring

When the project contains both a browser frontend and an HTTP backend, deployment is not complete until the browser's production request path is explicit. Read `Production API Topology`, `Frontend API Base`, and `CORS Owner` from `.azure/project-plan.md`, plus any deployment hand-off notes in `.azure/integration-plan.md`. The local dev-server proxy is not evidence of production routing.

Implement exactly one topology:

1. **Same-origin linked backend** — provision and link the backend through the frontend host so `/api` resolves in production. Keep the client base relative. Do not add browser CORS merely to compensate for a missing route.
2. **Cross-origin backend** — expose the backend endpoint as an infrastructure output, provide the absolute API URL through the framework's public build-time variable (for example, `VITE_API_BASE`) **before** the frontend production build runs, and configure CORS on the backend host to allow the deployed frontend origin. CORS belongs to the server receiving the cross-origin request, not the Static Web App. Prefer the exact frontend origin over `*`, especially when credentials are enabled.

For Vite and other statically bundled frontends, public environment variables are compiled into the JavaScript bundle. Setting an App Setting after `vite build` does not update already-built files. Ensure the provisioning/package order makes infrastructure outputs available to the frontend build, and fail packaging when a required cross-origin API base is absent. Never allow a cross-origin production build to silently fall back to `/api`.

The deployment plan and generated artifacts must identify:

- the selected topology and the resource that implements routing;
- the backend endpoint output and frontend build variable, when cross-origin;
- the backend CORS allowed origin derived from the deployed frontend hostname;
- a post-deploy check that loads the deployed frontend and proves a browser request reaches the deployed API.

When this agent executes the deployment, run that post-deploy check and capture the frontend URL, API request path, and status. A direct backend health check alone is insufficient because it does not test frontend routing or browser CORS. When this run only prepares artifacts, include the exact check in `.azure/deployment-plan.md` and state that deployed connectivity remains unverified; do not claim the application was verified in Azure.

### Provision real dependencies — no placeholders, no stubbed identity

Read the `Production Source` column of the plan's Services Required table and the **Deploy must resolve** section of `.azure/integration-plan.md`. These are the breadcrumbs telling you which values integration left dev-only. Every one of them must resolve to a real Azure value before you report success:

- **Datastore connection (`DATABASE_URL` and peers)** — provision the actual resource (e.g. PostgreSQL) and wire the connection string from an **infra output**, Key Vault reference, or keyless Managed Identity, per the plan's `Production Source`. Never ship the local default and never leave a literal placeholder that the user must `azd env set` by hand. A deployment whose documented end state is "the API returns 500 until you connect a database" is **not** done.
- **Identity transport (auth)** — when the plan's `Identity Transport` is not `Anonymous`, provision the mechanism it names (SWA auth / Entra app registration) and wire it so the deployed frontend sends a real caller identity and the backend validates it. A hardcoded stub identity that reached deploy is a defect to fix here, not to ship.

The post-deploy check must exercise these end to end: an **authenticated** browser request (when identity is required) that returns **live data from the provisioned datastore** — a `500` from a missing table/connection or a `401` from unwired auth is a FAIL, not an "expected" state.

### Step A — open the deployment plan preview (MANDATORY, do not skip)

**Trigger:** the instant the `azure-prepare` skill finishes writing `.azure/deployment-plan.md` to disk. This must happen **before** the skill's approval gate (before you summarize the plan or ask for approval).
Expand Down Expand Up @@ -66,7 +95,7 @@ hooks:

### Step C — validate the generated artifacts before declaring success (MANDATORY)

After generating `azure.yaml` and the infra, **run `azd package`** (from the workspace root) to validate the manifest and confirm the app's build output is produced (for a Functions app, that the host actually discovers functions). Do **not** report the deployment as ready — and do **not** enter a retry-`azd deploy` loop — until `azd package` succeeds.
After generating `azure.yaml` and the infra, **run `azd package`** (from the workspace root) to validate the manifest and confirm the app's build output is produced (for a Functions app, that the host actually discovers functions). For a cross-origin browser frontend, also confirm packaging received the required public API base variable and did not build a bundle that falls back to `/api`. Do **not** report the deployment as ready — and do **not** enter a retry-`azd deploy` loop — until `azd package` succeeds.

If `azd package` (or a later `azd` step) fails with a hook error such as `The '<x>' kind is not supported for hook` or `Inline scripts are only supported for shell hooks`, the fix is the `azure.yaml` hook itself (see the rules above), **not** re-running the same command. Correct the hook, then re-validate. Never retry the identical failing command more than once without changing the underlying artifact.

Expand All @@ -93,6 +122,8 @@ A workspace ready to deploy to Azure:
- `azure.yaml` for the Azure Developer CLI (`azd`)
- Dockerfiles where required
- Any environment files / parameter files referenced by the plan
- Production frontend-to-API routing, build-time endpoint injection, backend CORS, and a post-deploy verification contract when the project has both services
- Real, provisioned values for every dev-only dependency flagged in the integration hand-off — datastore connection (`DATABASE_URL` from infra output / Managed identity, never a placeholder) and the identity mechanism (SWA auth / Entra app) when the plan's `Identity Transport` is not `Anonymous`

## Interruption recovery

Expand Down
15 changes: 13 additions & 2 deletions resources/agents/azure-project-integrate/instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,15 @@ metadata:

1. **Migrations** — create the SQL / PostgreSQL schema migrations so the database tables exist. **No seed data.**
2. **Backend smoke test** — start the backend and verify every endpoint registers and responds.
3. **Wire frontend to LIVE data** — replace every mock data source in the frontend with real, typed API calls.
3. **Wire frontend to LIVE data** — replace every mock data source in the frontend with real, typed API calls while preserving the plan's production API topology.
4. **End-to-end wire-up** — run the frontend and backend together and verify they communicate.

> **Local proxy is not a production topology.** The dev server may proxy `/api` to the local backend, but that proves only local connectivity. Read `Production API Topology`, `Frontend API Base`, and `CORS Owner` from the plan's Services Required section. For a same-origin linked backend, the live client may use `/api`. For a cross-origin backend, use the framework's public build-time environment variable (for example, `VITE_API_BASE`) and make production fail clearly when it is absent; never silently fall back to `/api` in production. Integration records this contract for deployment but does not invent Azure hostnames or configure cloud CORS.

> **No stubbed identity may remain.** When the backend enforces a caller identity, wire the live client to the real source named in the plan's `Identity Transport` row and let a missing identity surface as a genuine `401`. Never invent a constant to silence an auth error (e.g. hardcoding `x-user-id: default-user`) — that is the auth equivalent of a lingering mock import: it ships a security hole and every caller collapses to one user. If the plan says `Anonymous`, send nothing. Integration wires and verifies identity locally; it does not provision the cloud identity resource (Entra app / SWA auth) — that is deploy's job, recorded in the hand-off.

> **Local infra values are dev-only.** Any connection string / endpoint you use to smoke-test (the local `DATABASE_URL`, storage emulator, cache) is a **development** value. Never present it as the production value or let it flow to deploy as a shippable default — the plan's `Production Source` column says where the real value comes from (usually `Infra output` / `Managed identity`). Flag each such value in the deploy hand-off so deploy provisions it rather than shipping a placeholder.

> ⛔ **NEVER create seed data.** No `seed`, `seeds`, `seed-data`, `fixtures`, demo rows, or any file/folder/function named after seeding. You create **schema only**. Integration is proven by the app running against an empty-but-correct schema. If the scaffold left a `seeds/` directory, ignore it — do not extend it, do not depend on it, do not run it.

## Triggers
Expand Down Expand Up @@ -133,7 +139,9 @@ Requires a scaffolded project. Verify before starting:
| Replace local types with shared types | Point `src/api/types.ts` at the shared package (e.g. `import type { PublicUser } from '@app/shared'`); delete the frontend's duplicated entity types. The `ApiClient` shape is unchanged. **No `any` types.** |
| Build the live client | Add `src/api/client.ts` — a second implementation of the **same `ApiClient` interface** (typed `: ApiClient`), method-for-method against the route inventory, base URL from env. |
| **Swap the seam (one file)** | Edit `src/api/index.ts` so `api` points at the live client (`mockClient` → `liveClient`). This single line wires every page/hook to live data — **no page or hook edits**. |
| Configure the dev proxy | Point the dev server's `/api` proxy at the backend host (e.g. `http://localhost:7071`) so the frontend reaches live endpoints in development. |
| Preserve the production topology | Same-origin linked backend: `/api` is valid. Cross-origin backend: require the framework's public API base variable in production and allow `/api` only during local development through the dev proxy. Add the variable to the frontend's `.env.example` without a deployed hostname or secret. |
| Wire identity (no stub) | If the plan's `Identity Transport` is not `Anonymous`, populate the caller identity in the live client from that real source and let a missing identity return a genuine `401`. Do NOT hardcode a constant identity (e.g. `x-user-id: default-user`) to silence an auth error. Record the cloud identity resource deploy must provision in the hand-off. |
| Configure the dev proxy | Point the dev server's `/api` proxy at the backend host (e.g. `http://localhost:7071`) so the frontend reaches live endpoints in development. This proxy is local-only and must not be treated as production routing. |
| Remove the mock layer | Delete `src/api/mockClient.ts` and `src/mocks/*` (and local types now sourced from shared). A lingering `import … from './mockClient'` or `'../mocks'` = NOT done. |
| **Remove the Mock State Switcher** | Delete the dev-only state switcher the scaffold added: `src/api/previewState.ts`, its corner-switcher component, and every `previewState` import/usage in the mock client, pages, hooks, and app shell. Live data is the only source now — the forced `loading`/`empty`/`error` override must be gone. A lingering `import … previewState` or a rendered Data/Loading/Empty/Error switcher = NOT done. |
| Keep correct file extensions | JSX (`<Component />`) MUST be `.tsx`; pure TS `.ts`. |
Expand All @@ -149,6 +157,8 @@ Requires a scaffolded project. Verify before starting:
> - The Mock State Switcher (`src/api/previewState.ts` + corner switcher component) is deleted and no longer imported anywhere.
> - The seam (`src/api/index.ts`) points at the live client; pages/hooks were not edited.
> - The dev proxy targets the backend host.
> - The client matches the plan's production API topology; a cross-origin production build cannot silently fall back to `/api`.
> - Identity is sent from the plan's real `Identity Transport` source (or nothing when `Anonymous`); no hardcoded stub identity remains.

---

Expand All @@ -163,6 +173,7 @@ Requires a scaffolded project. Verify before starting:
| Verify a live request path | Confirm the frontend successfully fetches from the backend — inspect the dev-server/host logs for a real `/api/...` request returning `200` (or load a page via the browser tool and confirm live data renders, not a mock placeholder). At least one page MUST display data that came from the running backend. |
| Verify a write path (if applicable) | If the app has a create/update flow, exercise one and confirm the backend receives it and the frontend reflects the result. |
| Capture evidence | Record the request/response (path, status) that proves frontend → backend wiring works. |
| Record the deployment hand-off | Append a **Deploy must resolve** section to `.azure/integration-plan.md` — the breadcrumb trail of everything that works locally but is NOT production-ready. Include: the production API topology + public API base variable (if any) + backend CORS owner; the identity transport the client now sends and the cloud identity resource deploy must provision; and every env var whose value is dev-only (from the plan's `Production Source` column — e.g. `DATABASE_URL` = `Infra output`). State explicitly that the local proxy + local DB evidence does not verify deployed routing, auth, or data. |
| Shut down cleanly | Stop both processes after verifying. |

**Reference**: [end-to-end.md](.github/agents/azure-project-integrate/references/end-to-end.md) for running both processes concurrently cross-platform and reading the evidence.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,14 @@ After this step, a search of the frontend `src/` for `mock` / `mockData` / `prev
import type { ApiClient } from './types';
import type { PublicUser, CreateUserRequest } from '@app/shared';

const BASE = import.meta.env.VITE_API_BASE ?? '/api';
// Read the API base from the framework's PUBLIC (client-exposed) env var — see the table below.
// The value is frozen at build time; in a cross-origin topology production MUST supply it.
// Shown in Vite syntax — substitute your framework's equivalent (e.g. Next.js `process.env.NEXT_PUBLIC_API_BASE`).
const configuredBase = import.meta.env.VITE_API_BASE;
if (import.meta.env.PROD && !configuredBase) {
throw new Error('VITE_API_BASE is required for the production build');
}
const BASE = configuredBase ?? '/api';

async function request<T>(path: string, init?: RequestInit): Promise<T> {
const res = await fetch(`${BASE}${path}`, {
Expand All @@ -54,6 +61,36 @@ After this step, a search of the frontend `src/` for `mock` / `mockData` / `prev
// ...one method per endpoint, matching the ApiClient interface exactly
};
```
The example above is for a **cross-origin backend**. For a **same-origin linked backend**, use `const BASE = '/api';` and require no build-time variable. Follow the topology recorded in the project plan; never choose based only on the fact that the local dev proxy works.

**Stay framework-agnostic.** The user may pick any frontend framework — read the API base from *that framework's* public (client-exposed) environment variable. Only variables carrying the framework's public prefix are bundled into browser code, and their values are frozen at build time, so a cross-origin deployment must inject the value **before** the production build runs, not after the static files are emitted. Map the Vite tokens above to whatever the plan selected:

| Framework | Public env var | Client access | Production check | Build-config validation |
|-----------|----------------|---------------|------------------|--------------------------|
| Vite (React/Vue/Svelte) | `VITE_API_BASE` | `import.meta.env.VITE_API_BASE` | `import.meta.env.PROD` | `vite.config.*` via `loadEnv` |
| Next.js | `NEXT_PUBLIC_API_BASE` | `process.env.NEXT_PUBLIC_API_BASE` | `process.env.NODE_ENV === 'production'` | `next.config.*` |
| SvelteKit | `PUBLIC_API_BASE` | import from `$env/static/public` | `dev` from `$app/environment` | build errors if referenced-but-unset |
| Angular | `environment.apiBase` (or `NG_APP_*` via `@ngx-env`) | `environment.apiBase` | `environment.production` | `environment.prod.ts` / builder |
| Astro / Nuxt | `PUBLIC_*` / `NUXT_PUBLIC_*` | `import.meta.env.PUBLIC_*` / `useRuntimeConfig().public` | `import.meta.env.PROD` / `import.meta.dev` | `astro.config.*` / `nuxt.config.*` |

If the framework isn't listed, consult its docs for the public-env prefix and the production flag — the principle is identical. Name the chosen variable in the plan's `Frontend API Base` field and the deploy hand-off so deploy injects it before the build.

The runtime guard above fails clearly when the deployed app starts, but most bundlers do not execute application modules while building. For a cross-origin topology, **also** validate the variable in the framework's build config so packaging itself fails before emitting a broken bundle — e.g. in Vite (`vite.config.ts`):
```ts
import { defineConfig, loadEnv } from 'vite';

export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd(), '');
if (mode === 'production' && !env.VITE_API_BASE) {
throw new Error('VITE_API_BASE is required for the production build');
}

return {
// Existing plugins and server configuration.
};
});
```
For other frameworks apply the equivalent check in their build config (`next.config.*`, `astro.config.*`, `nuxt.config.*`, an Angular builder step, etc.).
Because `liveClient` is typed `: ApiClient`, the compiler guarantees it covers every method the pages already call.
3. **Swap the seam — the one file that changes.** Edit `src/api/index.ts` so `api` points at the live client:
```ts
Expand All @@ -72,10 +109,12 @@ After this step, a search of the frontend `src/` for `mock` / `mockData` / `prev

---

## Dev proxy (so `/api` reaches the backend)
## Dev proxy (so `/api` reaches the backend locally)

Point the dev server's `/api` proxy at the backend host from Step 2.

This proxy exists only in the development server. It does not create an Azure production route and does not remove the need for a production API base URL or backend CORS when the frontend and API use different origins.

**Vite** (`vite.config.ts`):
```ts
export default defineConfig({
Expand Down
Loading
Loading