Radius Application Modeling
Use this skill to generate a Radius application definition (app.bicep) from a source code repository.
Prerequisites
This skill currently supports only repositories that already contain a Dockerfile for building the application image, so a repository without one cannot be modeled.
The extension normally screens this out before handing over the skill, so re-running that check is not your job. If you do find the application has no Dockerfile, stop: generate nothing, write nothing, and report this to the user verbatim.
A Dockerfile means a file named Dockerfile, Dockerfile.<suffix>, or <prefix>.Dockerfile, matched case-insensitively on the file name, at the repository root or in a service subdirectory. Ignore any that sits inside a vendored, generated, or tooling directory — node_modules, dist, build, coverage, .next, .turbo, venv, .venv, or any other dot-directory apart from .radius and .github. A .devcontainer image builds the development environment, not the application, so it does not count.
I could not find a Dockerfile in this repository. I can only create application definitions for containerized applications. Add a Dockerfile first, then I can create an application definition.
Identifying the application
A repository with several Dockerfiles is normally still one application. A microservices repository builds many images, and this skill models a microservices repository into a single Radius.Core/applications named after the repository, with the services wired to each other through the addressing rules in connection-conventions.md. A Dockerfile count is never decisive on its own, and it is never by itself a reason to put a question to the user. Nor does a Dockerfile prove there is a service to model: it may build a CI image, a migration or tooling image, an unused example, or an alternative to another one. A root workspace manifest such as pnpm-workspace.yaml or go.work describes how the repository is organized, not that its projects form one application — independent applications use those tools too, so weigh it against the source rather than concluding from it.
Establish from the source which directories hold application services that share a runtime and deploy together, and model those as one application. Ask the user only when, after reading the source, you cannot identify an application at all. That happens in two cases:
- the repository holds more than one independent application, which a single definition cannot represent, or
- nothing in the repository is an application — for example the Dockerfiles build only tooling or CI images.
In either case, ask exactly:
I looked through the repository but could not identify an application or application resources. Which directory contains your application source code and Dockerfile?
Then stop, and write nothing: no .radius/app.bicep, no .radius/bicepconfig.json, no origin record, no branch, no commit. Do not guess a directory on the user's behalf or model one candidate to "make progress" — a directory you chose yourself is not an answer to the question you just asked. The user replies with a directory and asks for analysis again, which scopes the next run to it; the skill already supports a subdirectory through the build.source rule.
Response
When asked to model a repository:
- Generate the application definition into a staging directory and publish it only once the whole run is complete (see Staged runs). Never write
.radius/app.bicep, .radius/bicepconfig.json, the origin record, or a custom-type artifact directly into .radius/, and never git add anything yourself: the promote script publishes and stages the run. Do NOT push, and do NOT open a pull request: modeling only writes and stages the files locally. The application graph renders from the on-disk working tree, so no push is needed to preview it, and pushing to a remote is a deployment concern handled later, not part of modeling.
- In your chat reply, give a one-line intro naming the app (e.g. "I'll create an application definition for
todo-list-app."), then a short, natural summary of the resources you identified, a brief list such as "Container: todo-list-app", "MySQL database", "Secret for DB credentials". A sentence or two of reasoning is fine; don't dump raw source analysis or the full file contents. If external-client ingress was considered but omitted, state why. Describe only what you actually did (that the run published and staged the model files in the working tree); do not claim the application graph or canvas is rendering, since you cannot observe that. If a graph view is opened and shows an error or empty state, report that honestly instead of asserting success. Keep the reply about the user's application and its resources; do not name internal skill or reference files (for example, reference examples the skill consulted).
Radius CLI execution boundary
Never invoke rad or rad.exe directly from PowerShell, a shell, a subprocess, or a delegated agent. Use node "<loaded-skill-base>/scripts/show-radius-type.mjs" to resolve predefined Radius type definitions. Compile the generated application definition with node "<loaded-skill-base>/scripts/validate-bicep.mjs" .radius/app.bicep; the checker uses only the extension-managed Bicep and fails on every compiler warning or error. Graph validation must go through the Radius canvas and its tools: open canvasId: "radius" with instanceId: "radius-panel", pass the current session repository as repo in owner/repo form, and use the current Copilot worktree branch. The extension honors an existing executable RADIUS_RAD_BINARY as a developer-owned override; otherwise it runs its managed binary from %USERPROFILE%\.radius\ai-extensions\bin\rad.exe on Windows or $HOME/.radius/ai-extensions/bin/rad on macOS/Linux, downloading it when absent and attempting a best-effort upgrade when older than the latest release (offline/API failures keep the installed binary; set RADIUS_RAD_SKIP_VERSION_CHECK to skip the version check). It does not resolve rad from PATH or .rad/bin. Diagnose graph failures only from the Radius extension log; never reproduce them with a direct CLI command or through another agent.
If show-radius-type.mjs fails while locating, querying, or validating the selected Radius CLI or while deriving its extension reference, stop the modeling run, run node "<loaded-skill-base>/scripts/promote-app-model.mjs" --abort --staging "<staging-dir>", and report the exact error. This includes a missing binary, invalid or incomplete version JSON, a noncanonical commit, and an unsupported development, edge, or pull-request version. A CLI-resolution failure is not permission to repair the toolchain: never download, install, upgrade, downgrade, copy, move, rename, back up, delete, or replace a rad binary; never change or unset RADIUS_RAD_BINARY or RADIUS_RAD_SKIP_VERSION_CHECK; and never search PATH, .rad/bin, or another location for a released fallback. The extension alone owns its managed binary lifecycle. Use a different binary only after the user explicitly changes their configuration and requests a new modeling run.
Workflow
- Start the run with
node "<loaded-skill-base>/scripts/promote-app-model.mjs" --begin, which prints the staging directory to write everything into (see Staged runs). Then select one runnable deployment profile. Treat explicit user, scenario, and target-repository deployment requirements for Radius types, resource-name parameters, workload roles/count, native configuration keys, secret bindings, provider profile, protocol values, and connection names as acceptance criteria. Verify that the pinned source supports that profile; do not silently replace it with an easier default or optional backend.
- Build and maintain an internal requirement ledger. Record every acceptance criterion and planned resource property read or write with its source evidence and consuming workload setting. After type resolution, add the exact
resources[].apiVersion and recursive resources[].schema path returned by show-radius-type.mjs, any readOnly or writeOnly restriction, and the separate Recipe and target-Environment evidence required for generated values. Use the ledger for reasoning and validation; do not print it or add it as Bicep comments. Follow runtime-contract.md.
- Inventory every executable workload and backing service in the selected profile from manifests, Dockerfiles, compose/Helm files, entrypoints, source configuration reads, client initialization, and referenced config files. Treat web, worker, producer, consumer, migration, scheduler, and sidecar roles separately. Model a backing service only when source evidence proves it is mandatory for the selected startup/configuration path; a repo-wide optional dependency, extra, adapter, test, or example does not become a resource. After completing this inventory, decide external-client ingress using only the selected profile's startup and configuration chain and the route authoring rule. Ask and stop if it is ambiguous; do not revisit the decision with files from an unselected profile.
- Extract each workload's runtime contract: image/build context and target platform, entrypoint and arguments, listener and ports, required environment/configuration including parser coercion and unset behavior, secrets, writable storage, dependencies, wire protocols, TLS, authentication/bootstrap setup, and feature-critical configuration. Inspect CLI flags and structured fields as well as environment variables.
- Map every selected backing service to a Radius type with component-catalog.md, using architecture-patterns.md only as context. Report unsupported essential components instead of substituting unrelated types.
- After the workload, backing-service, ingress, image-publishing, and secret inventory is complete, but before authoring Bicep, resolve every planned predefined type in one
show-radius-type.mjs batch as described in Resource Type Resolution. Apply the resource-specific structure rules before this call so companion predefined resources are included in the same batch. Unless target evidence explicitly proves an unauthenticated image registry, planning Radius.Compute/containerImages also plans the single Radius.Security/secrets registry-push companion described below. Preserve and inspect the complete returned JSON. Use each returned type, apiVersion, schema, and recipe, and handle every nonzero exit or notFound entry as described there. The script creates or merges the staged bicepconfig.json and owns its radius alias. The result supplies type-schema evidence and, when available, managed-release default Recipe evidence. Before authoring, apply the Credential shape check. Use an available recipe.definition as the managed-default Azure Recipe profile unless explicit target evidence selects another Recipe; use that selected Recipe to decide whether a credential exists and inspect every credential representation it exposes. For each predefined type whose resolved Recipe meets the applicability check in azure-provider-value-rules.md, apply those rules to every property that the Recipe copies into an Azure name, administrator login, or AI model setting. Do not apply those Azure rules to AWS, Kubernetes, or an unverified custom Recipe. Prefer a direct shape match. When an aggregate output is incompatible or unknown, consider schema-declared discrete outputs and safe runtime composition before reporting a blocker; when the client needs parts, consider a proven runtime decomposition path. A package name without a checked-in consumer is not evidence, but an exact pinned dependency together with the checked-in call site that passes the value to that client's configuration API identifies the parser contract and permits using that client's documented syntax. Combine that client evidence with selected-profile literals, checked-in parser code, schema descriptions, and the selected Recipe's auth and output mappings. Treat compatibility as unknown only after direct binding and every supported composition or decomposition path have been considered. Stop on missing Recipe behavior or a remaining incompatible or unknown shape. Verify target-Environment registration, Recipe declarations outside the returned definition, and other application runtime behavior separately.
- Build the application's own workloads from the repository Dockerfile via
Radius.Compute/containerImages; this is the default path, and a repository without a Dockerfile is unsupported (see Prerequisites). Require a complete, practical build context and pin build.source to the exact modeled checkout or an explicit immutable release tag. When the ref is a commit, use its full 40-character SHA in ?ref=; never use an abbreviated SHA. The image tag may remain abbreviated because it is not a Git ref. Resolve the exact containerImages Recipe: verify omitted optional inputs, set a Docker-valid immutable tag only when required by that contract, decide build.platforms per image with the Choosing build.platforms procedure, and preserve required Git metadata with schema-supported build arguments. Use a pinned published image only for a genuinely third-party/backing container, never for the application's own code. Map every runtime value using connection-conventions.md, secrets-handling.md, and bicep-structure-rules.md.
- Generate the Bicep into
<staging-dir>/app.bicep using naming-conventions.md. For each predefined type whose resolved Recipe meets the applicability check in azure-provider-value-rules.md, keep its Azure-bound names, administrator logins, and model names within those rules. Use string literals or parameters with literal defaults for these values. If the source requires a value that the selected Recipe cannot deploy, report the conflict and abort the run; do not rename the source-required database, container, topic, or model. Then run node "<loaded-skill-base>/scripts/validate-bicep.mjs" <staging-dir>/app.bicep. Repair every compiler error and warning until the checker exits successfully and prints no warnings, within the repair budget it enforces. Never make compilation pass by deleting a required backend activation, native configuration value, secret binding, or dependency edge.
- Perform the validation checklist and close every item in the requirement ledger. Compilation or process startup alone is not success.
- Only after the checker exits successfully, write the origin record into the staging directory.
- Publish the run with the promote script, which is the last step of every modeling run and the only thing that writes into
.radius/ (see Staged runs).
Repair budget
A compile error the skill cannot resolve is usually a real signal — a schema that has moved, a type the configured extension does not have, or a changed recipe contract — not something more attempts will fix. So validate-bicep.mjs bounds the repair loop itself, and the run ends by reporting rather than by editing indefinitely.
The checker enforces this whenever the model it compiles is inside a staging directory, by counting its own compiles in that run's run.json. It records the attempt before compiling, so an interrupted compile still counts, and it refuses to compile at all if that record cannot be read or updated — a budget it cannot count is one it cannot enforce. The count covers exactly one modeling run, and a later run starts fresh. You do not track attempts yourself, and you cannot compile your way past the limit.
- Five repairs per run. The first compile is free, because it is what reveals the problem; after five repair-and-recompile cycles the checker refuses to compile again and exits non-zero saying the budget is spent. That is the same number of repair cycles the deploy-failure repair loop allows after a failed deploy, so the product has one answer to "how many times do we retry a repair on
app.bicep".
- The checker tells you when a failure repeats. It fingerprints the compiler output with line numbers and diagnostic ordering normalized out, and says so when a failure is the one you just saw. Treat that as proof the last fix was wrong: make a materially different fix rather than varying it, or use the remaining budget to establish why the schema cannot express what the source needs.
- If the checker refuses to compile, stop. Whether the budget is spent or the run's bookkeeping is broken, do not edit and retry. Do not write the origin record and do not publish the run. Tell the user which resource and property the compiler rejected, quote the last compiler output verbatim, and say that no application definition was written. The raw error is the most useful thing to hand over, because it is usually pointing at something real.
Never buy a clean compile by deleting a required backend activation, native configuration value, secret binding, or dependency edge. Running out of budget is the correct outcome when the alternative is a model that compiles and does not work.
Deployment Profile and Acceptance Contract
- Explicit profile wins: If the request names a supported Radius type, provider profile, workload role, native key, protocol value, secret binding, or relationship, model it exactly when the pinned source supports it. A source default or another valid deployment profile does not satisfy that request.
- Source compatibility is still mandatory: Resolve behavior from the requested commit/tag, not a different release or the current default branch. If an acceptance criterion conflicts with that source revision, stop and report the conflict instead of inventing compatibility.
- No implicit omissions: Each required typed resource must be emitted and wired to a consumer. Each required workload role must have a runnable process and complete config. Each required native key/value must appear in the exact source-supported location and format.
- No decorative wiring: Environment variables, connections, and resources must be consumed by the selected feature path. Merely declaring a dependency or starting a process does not prove the requested database, model, storage, or messaging path works.
- Mandatory dependencies only: Model only services required by the selected runnable path. Imports, package extras, adapters, examples, tests, or alternate configurations elsewhere in the repository do not prove that a backing service is required.
- Infer only when unspecified: Without an explicit profile, prefer a complete, documented manifest/configuration that exercises the application's primary feature. If multiple materially different profiles remain valid, ask the user rather than choosing an optional backend arbitrarily.
- Fail closed on verified incompatibility: Fully implement every clearly supported criterion. Stop after evidence proves the pinned source or exact schema/Recipe cannot satisfy a requirement; do not return a partial definition as deployable, leave unresolved runtime caveats, or delete feature-critical wiring to obtain a clean compile.
Repairing an existing app.bicep
When a deploy fails because of a modeling or schema error in an existing .radius/app.bicep (unknown type or API version, unknown or missing property, invalid reference between resources, wrong credential shape, or a Bicep parse or compile error), repair that model rather than regenerating it from scratch. A repair is still a run: start it with --begin, copy the current .radius/app.bicep and .radius/bicepconfig.json into the staging directory, edit the staged copies, and publish with the promote script (see Staged runs). Editing .radius/app.bicep in place would leave a half-repaired model behind if the repair failed partway. This assumes the deploy error and any relevant logs have been provided (by the radius-deploy skill or the user); if they haven't, ask for them before attempting a fix.
- Confirm whether the failure comes from the application model. If it is an infrastructure, recipe, Environment, or cluster failure (for example, recipe download/execution or provider provisioning), stop and hand it back to the
radius-deploy skill; editing app.bicep will not fix it. A pod that never becomes ready is not enough to classify the failure: inspect events and logs to distinguish infrastructure/connectivity failures from incorrect workload configuration, listeners, credentials, or dependency wiring in app.bicep.
- Locate the implicated resource, property, or workload setting, then re-resolve the exact configured type schema and Recipe contract (see Resource Type Resolution) to confirm property names, required fields, credential shape, API version, resource reference paths, and provider restrictions. If the implicated predefined type's resolved Recipe meets the applicability check in azure-provider-value-rules.md, apply those rules. If the application requires the rejected value, report the application/Recipe conflict instead of silently changing it.
- Apply the fix using the same runtime-contract, naming, structure, and secrets rules as authoring so the repaired resource stays consistent with the rest of the file. While you are in the file, also correct any other clear schema or rule violations you notice, and report each collateral fix you made. Never clear a compile error by deleting a required binding, native value, backend activation, or dependency edge; report version drift when the configured schema cannot represent the runnable profile.
- Re-run the validation checklist against the whole file; a change in one resource can ripple to connections or references elsewhere.
- Return the corrected file with a short note of what changed and why, then hand it back to
radius-deploy to redeploy. Write a new origin record into the staging directory after the checker passes, so the repaired bytes are the ones recorded, then publish the run. If the same error recurs, treat the previous fix as insufficient and try a different fix rather than reapplying the one that just failed. If a couple of different fixes still do not resolve it, or no different fix can be found, report that to radius-deploy so it can stop the retry loop and surface the problem to the user.
Staged runs
A modeling run that stops partway must leave the repository exactly as it was. So a run never writes into .radius/ directly: it writes everything into a staging directory inside .radius/, and a script moves that output into place only once the run is complete and its application model has compiled.
Start every run — generation or repair — with:
node "<loaded-skill-base>/scripts/promote-app-model.mjs" --begin
It removes any staging directory a previous interrupted run left behind, records the fingerprints of the files in .radius/ this run may replace, and prints the staging directory. It writes nothing outside that directory, which is what lets a failed run leave .radius/ byte-identical without having to undo anything. Write every file the run produces into that directory and nowhere else: app.bicep, bicepconfig.json, the origin record, and any custom-type artifacts (custom-types.yaml, custom-types.tgz, custom-recipe-pack.bicep, and <type>-recipe.bicep) (pass the directory to radius_publish_custom_type_extension as stagingDir so its published package lands there too). Run the Bicep checker against the staged app.bicep, so what is verified is exactly what will be published.
Finish every run with:
node "<loaded-skill-base>/scripts/promote-app-model.mjs" --staging "<staging-dir>"
It refuses unless the staging directory holds a complete set of files, the origin record describes the staged app.bicep, and .radius/app.bicep is still the file the run started from. On success it moves the files into .radius/, deletes the staging directory, adds .staging-*/ to .radius/.gitignore, and stages the published files with git add — which is why you never run git add yourself. On any refusal it discards the staged run and writes nothing.
It exits 0 when the run was published and staged, 1 when it refused and nothing was written, and 2 when the files were published but git add failed. On 2 the model IS on disk: report that it was written but not staged, and do not re-run the run.
Rules:
- Never write, copy, or move a generated file into
.radius/ yourself, and never hand-write the files the script publishes. A file you place there directly is exactly the partial write staging exists to prevent.
- Never re-run modeling "to finish the job" after a refusal without starting a new run with
--begin. A retry starts from a clean slate.
- Only the files listed above are published. Anything else you leave in the staging directory is discarded with it, so never keep notes, scratch output, or intermediate files there and expect them to survive.
- If the script refuses because a file in
.radius/ changed during the run, the user edited it while you were working. Report that their version is intact, that nothing was published, and offer to re-run modeling. Do not attempt to merge, restore, or overwrite their file.
When a step fails
When any step of the run fails, discard the run with:
node "<loaded-skill-base>/scripts/promote-app-model.mjs" --abort --staging "<staging-dir>"
Use this rather than deleting the directory yourself, so a run is always discarded the same way. Then report the failure. Say plainly that nothing was written: .radius/ is exactly as it was, nothing was staged in git, and any application model the user already had is intact. Never keep the staging directory for inspection.
Do not retry on your own. Say which kind of failure it looks like and let the user decide:
- Looks transient — a network error fetching a schema, a registry timeout, an interrupted download. Offer to run modeling again.
- Looks permanent — no Dockerfile, a required backing service with no Radius type that cannot be provisioned on Azure, no source that resolves to a runnable profile. Report it and do not offer a retry, because the same run would fail the same way. If the Canvas handoff that started this run supplied
radius_report_modeling_failure arguments, call that tool once with the supplied instance, repository, branch, and attempt token plus an actionable failure summary; this lets the waiting graph stop requesting the same impossible model. Never call it for a transient failure, cancellation, user-declined action, or a run that wrote app.bicep.
Origin record (app.origin.json)
Every generation records what the model was produced from, in .radius/app.origin.json beside app.bicep. The Radius canvas reads this record before rendering a graph: without it, the only question the canvas can ask is "does app.bicep exist?", so a model whose source has since moved on is rendered as though it were current. Write it with the bundled script, never by hand, so the format stays exactly what the canvas parses:
node "<loaded-skill-base>/scripts/write-app-origin.mjs" <staging-dir>/app.bicep --skill-version "<loaded-skill-version>"
Rules:
- Write the record on every generation and every repair, as the last step, and only after
validate-bicep.mjs exits successfully with no warnings. The record holds a hash of the exact bytes that compiled, which is what lets the canvas treat the model as known-valid instead of recompiling it on every graph open. Recording a file that has not passed the checker asserts a validity that was never proven.
--skill-version is optional. Pass it when the prompt gave you a real version; if the value you were given is still the literal <loaded-skill-version>, omit the flag entirely rather than passing the placeholder through. The script reads the version from the plugin manifest itself, and a placeholder recorded in the origin record would make every later freshness check report the model as generated by an unknown generator.
- Write it into the staging directory beside the
app.bicep it describes, and let the promote script publish and stage the pair (see Staged runs). A model committed without its origin record reads as unverified on every other checkout of that branch, and a run whose record is missing or does not match its model is refused rather than published.
- Never edit
app.bicep after recording it. If you change one byte, re-run the checker and re-run the origin script.
- The script fails closed when it cannot resolve the source commit or read the model. Do not work around it by writing the JSON yourself; fix the underlying problem and re-run it.
Refreshing a stale model
The canvas asks for a refresh when an existing model is stale: its branch has moved past the recorded commit, a different generator version is installed, or there is no usable origin record at all. Regenerate from current source and write a new origin record, with one guard:
- When the model is reported as manually edited, ask the user before overwriting it. That state means the model needs regenerating and was also changed after it was generated: hand-tuned properties, a custom type, or a recipe pack reference someone added deliberately. Say what would be lost and regenerate only after the user agrees. Offer to repair the specific problem in place (see Repairing an existing app.bicep) as the alternative, since that preserves their edits.
- When the model has no origin record, regenerate it and write one. Do not ask. Nothing about a missing record shows the model was edited, so there is no decision to put to the user, and the canvas only reports it this way when the file is committed and unmodified, so git still has the version being replaced. Regenerate from current source and record it as you would for any other generation.
- A model that is stale only because the source or generator moved on carries no unproven content, so refresh it without asking.
- A manual edit is never reported on its own. Do not go looking for one, and do not raise it when the canvas has not, since an edit to a model that is otherwise current is the user's to keep.
- Refresh only the current workspace branch, where writing the working tree is enough. A model on a different branch cannot be refreshed by modeling: report the staleness to the user and let them decide, rather than committing or pushing a regenerated model to that branch.
Deterministic Naming Rules
These rules eliminate ambiguity. Apply them exactly.
Explicit profile-required resource, relationship, parameter, and app-native configuration names take precedence over the default naming rules below. Never normalize a name the selected runtime contract requires verbatim. Preserve a resource-name parameter when deployment documentation, the target Environment Recipe, or verification couples it to a provider resource name.
Symbolic names (left side of = in Bicep)
| Resource |
Symbolic name |
| Application |
<shortName>App where <shortName> is the app name without hyphens, camelCase (e.g., todo-list-app → todoApp) |
| Container |
<serviceName>Container — service short name camelCase; single-container apps use <shortName>Container (e.g., todoContainer) |
| Container image |
<serviceName>Image (e.g., todoImage) |
| Data store (database/cache/queue) |
<engine> + role suffix, camelCase: mysqlDb, postgresDb, neo4jDb, redisCache. Multiple of the same engine: prefix with the source store name (e.g., ordersPostgresDb) |
| Data store secret |
<engine>Secret when the type's schema defines a secret-reference credential property; app secrets use appSecrets |
| Route |
<serviceName>Route (e.g., todoRoute) |
Resource name properties (string values in Bicep)
| Resource |
Name value |
| Application |
Repository name in kebab-case (e.g., 'todo-list-app') |
| Container |
Service name in kebab-case; single-container apps use the app name (e.g., 'todo-list-app') |
| Container image |
'<service-name>-image' (e.g., 'todo-list-app-image') |
| Data store |
Engine short name in kebab-case ('mysql', 'postgres', 'neo4j', 'redis'); multiple of the same engine use the source store name |
| Data store secret |
'<engine>-secret' (when the schema defines a secret-reference credential property); app secrets 'app-secrets' |
Connection keys
| Connection |
Key |
| Data store |
Engine + role, lowercase: mysqldb, postgresdb, neo4jdb, rediscache. Multiple of the same engine: prefix with the source store name |
Other fixed values
| Field |
Value |
| Data store admin username |
The administrator username you author for the provisioned database. It is not derived from the source. Set it wherever the schema puts credentials — username on the resource, or USERNAME in the authored Secret when the schema takes a secret reference instead. Use myadmin when the source does not require a login; it fits the Azure PostgreSQL, MySQL, and SQL safe subsets. |
Data store database name |
Derived from source (e.g., MYSQL_DATABASE/POSTGRES_DB, or the database segment of a connection string). If the data store type's resolved Recipe meets the applicability check in azure-provider-value-rules.md, the database name must also satisfy those rules. Do not rename a source-required database to make it pass. |
Data store version |
Derived from source (e.g., the image tag mysql:8.0 → '8.0') |
Container key in containers map |
Service short name camelCase (single-container: derived from app, e.g., todo) |
Port key in ports map |
web for the primary HTTP port; additional ports derive from protocol/use (http, grpc) |
build.source for containerImages |
Repo git URL pinned to the modeled checkout: git::https://github.com/<org>/<repo>.git//<subdir>?ref=<checked-out-sha-or-explicit-immutable-tag> (//<subdir> only when the Dockerfile isn't at the repo root) |
Deterministic output
Two runs of this skill over the same source, with the same generator version and the same schema/recipe contract, must produce byte-identical .radius/app.bicep. A regeneration that reshuffles equivalent content produces a large diff that says nothing, which makes real changes hard to review and adds noise to the repository's history. Apply:
- Canonical declaration order.
extension lines first, then param declarations, then the Radius.Core/applications resource, then the remaining resources. Never order resources by discovery order, file-walk order, or the order a tool happened to return them.
- Canonical ordering within each group. Order
param declarations, and resources of the same type, by their name value using plain ASCII ordering. Resource types themselves follow the order they appear in the allow-list table under Resource Type Resolution. The one exception is a resource that must be declared after something it references.
- Canonical ordering inside a resource.
name first, then properties. Within a map whose keys you choose (env, ports, containers, connections), order keys ASCII-a
…(truncated)
1---2name: radius-app-bicep3description: Analyze a source code repository and generate a Radius application definition (.radius/app.bicep) that models the app's compute and backing services as Radius resource types. Use for: creating, generating, or updating a Radius application definition or app.bicep; modeling or onboarding an app or repo to Radius; determining which Radius resource types an app needs; repairing or fixing an app.bicep that failed to deploy because of a modeling or schema error. Do not use for: authoring generic or Azure Bicep unrelated to Radius, or deploying or running an already-modeled app. Resolves the configured Radius schemas and the application's runtime contract to produce validated, deployable output.4---56# Radius Application Modeling78Use this skill to generate a Radius application definition (`app.bicep`) from a source code repository.910## Prerequisites1112This skill currently supports only repositories that already contain a Dockerfile for building the application image, so a repository without one cannot be modeled.1314The extension normally screens this out before handing over the skill, so re-running that check is not your job. If you do find the application has no Dockerfile, stop: generate nothing, write nothing, and report this to the user verbatim.1516A Dockerfile means a file named `Dockerfile`, `Dockerfile.<suffix>`, or `<prefix>.Dockerfile`, matched case-insensitively on the file name, at the repository root or in a service subdirectory. Ignore any that sits inside a vendored, generated, or tooling directory — `node_modules`, `dist`, `build`, `coverage`, `.next`, `.turbo`, `venv`, `.venv`, or any other dot-directory apart from `.radius` and `.github`. A `.devcontainer` image builds the development environment, not the application, so it does not count.1718> I could not find a Dockerfile in this repository. I can only create application definitions for containerized applications. Add a Dockerfile first, then I can create an application definition.1920### Identifying the application2122A repository with several Dockerfiles is normally still one application. A microservices repository builds many images, and this skill models a microservices repository into a single `Radius.Core/applications` named after the repository, with the services wired to each other through the addressing rules in [connection-conventions.md](references/connection-conventions.md). A Dockerfile count is never decisive on its own, and it is never by itself a reason to put a question to the user. Nor does a Dockerfile prove there is a service to model: it may build a CI image, a migration or tooling image, an unused example, or an alternative to another one. A root workspace manifest such as `pnpm-workspace.yaml` or `go.work` describes how the repository is organized, not that its projects form one application — independent applications use those tools too, so weigh it against the source rather than concluding from it.2324Establish from the source which directories hold application services that share a runtime and deploy together, and model those as one application. Ask the user only when, after reading the source, you cannot identify an application at all. That happens in two cases:2526- the repository holds more than one **independent** application, which a single definition cannot represent, or27- nothing in the repository is an application — for example the Dockerfiles build only tooling or CI images.2829In either case, ask exactly:3031> I looked through the repository but could not identify an application or application resources. Which directory contains your application source code and Dockerfile?3233Then stop, and write nothing: no `.radius/app.bicep`, no `.radius/bicepconfig.json`, no origin record, no branch, no commit. Do not guess a directory on the user's behalf or model one candidate to "make progress" — a directory you chose yourself is not an answer to the question you just asked. The user replies with a directory and asks for analysis again, which scopes the next run to it; the skill already supports a subdirectory through the `build.source` rule.3435## Response3637When asked to model a repository:38391. Generate the application definition into a staging directory and publish it only once the whole run is complete (see [Staged runs](#staged-runs)). Never write `.radius/app.bicep`, `.radius/bicepconfig.json`, the origin record, or a custom-type artifact directly into `.radius/`, and never `git add` anything yourself: the promote script publishes and stages the run. Do NOT push, and do NOT open a pull request: modeling only writes and stages the files locally. The application graph renders from the on-disk working tree, so no push is needed to preview it, and pushing to a remote is a deployment concern handled later, not part of modeling.402. In your chat reply, give a one-line intro naming the app (e.g. "I'll create an application definition for `todo-list-app`."), then a short, natural summary of the resources you identified, a brief list such as "Container: `todo-list-app`", "MySQL database", "Secret for DB credentials". A sentence or two of reasoning is fine; don't dump raw source analysis or the full file contents. If external-client ingress was considered but omitted, state why. Describe only what you actually did (that the run published and staged the model files in the working tree); do not claim the application graph or canvas is rendering, since you cannot observe that. If a graph view is opened and shows an error or empty state, report that honestly instead of asserting success. Keep the reply about the user's application and its resources; do not name internal skill or reference files (for example, reference examples the skill consulted).4142## Radius CLI execution boundary4344Never invoke `rad` or `rad.exe` directly from PowerShell, a shell, a subprocess, or a delegated agent. Use `node "<loaded-skill-base>/scripts/show-radius-type.mjs"` to resolve predefined Radius type definitions. Compile the generated application definition with `node "<loaded-skill-base>/scripts/validate-bicep.mjs" .radius/app.bicep`; the checker uses only the extension-managed Bicep and fails on every compiler warning or error. Graph validation must go through the Radius canvas and its tools: open `canvasId: "radius"` with `instanceId: "radius-panel"`, pass the current session repository as `repo` in `owner/repo` form, and use the current Copilot worktree branch. The extension honors an existing executable `RADIUS_RAD_BINARY` as a developer-owned override; otherwise it runs its managed binary from `%USERPROFILE%\.radius\ai-extensions\bin\rad.exe` on Windows or `$HOME/.radius/ai-extensions/bin/rad` on macOS/Linux, downloading it when absent and attempting a best-effort upgrade when older than the latest release (offline/API failures keep the installed binary; set `RADIUS_RAD_SKIP_VERSION_CHECK` to skip the version check). It does not resolve `rad` from `PATH` or `.rad/bin`. Diagnose graph failures only from the Radius extension log; never reproduce them with a direct CLI command or through another agent.4546If `show-radius-type.mjs` fails while locating, querying, or validating the selected Radius CLI or while deriving its extension reference, stop the modeling run, run `node "<loaded-skill-base>/scripts/promote-app-model.mjs" --abort --staging "<staging-dir>"`, and report the exact error. This includes a missing binary, invalid or incomplete version JSON, a noncanonical commit, and an unsupported development, edge, or pull-request version. A CLI-resolution failure is not permission to repair the toolchain: never download, install, upgrade, downgrade, copy, move, rename, back up, delete, or replace a `rad` binary; never change or unset `RADIUS_RAD_BINARY` or `RADIUS_RAD_SKIP_VERSION_CHECK`; and never search `PATH`, `.rad/bin`, or another location for a released fallback. The extension alone owns its managed binary lifecycle. Use a different binary only after the user explicitly changes their configuration and requests a new modeling run.4748## Workflow49501. Start the run with `node "<loaded-skill-base>/scripts/promote-app-model.mjs" --begin`, which prints the staging directory to write everything into (see [Staged runs](#staged-runs)). Then select one runnable deployment profile. Treat explicit user, scenario, and target-repository deployment requirements for Radius types, resource-name parameters, workload roles/count, native configuration keys, secret bindings, provider profile, protocol values, and connection names as acceptance criteria. Verify that the pinned source supports that profile; do not silently replace it with an easier default or optional backend.512. Build and maintain an internal requirement ledger. Record every acceptance criterion and planned resource property read or write with its source evidence and consuming workload setting. After type resolution, add the exact `resources[].apiVersion` and recursive `resources[].schema` path returned by `show-radius-type.mjs`, any `readOnly` or `writeOnly` restriction, and the separate Recipe and target-Environment evidence required for generated values. Use the ledger for reasoning and validation; do not print it or add it as Bicep comments. Follow [runtime-contract.md](references/runtime-contract.md).523. Inventory every executable workload and backing service in the selected profile from manifests, Dockerfiles, compose/Helm files, entrypoints, source configuration reads, client initialization, and referenced config files. Treat web, worker, producer, consumer, migration, scheduler, and sidecar roles separately. Model a backing service only when source evidence proves it is mandatory for the selected startup/configuration path; a repo-wide optional dependency, extra, adapter, test, or example does not become a resource. After completing this inventory, decide external-client ingress using only the selected profile's startup and configuration chain and the [route authoring rule](#appbicep-structure-mandatory-order). Ask and stop if it is ambiguous; do not revisit the decision with files from an unselected profile.534. Extract each workload's runtime contract: image/build context and target platform, entrypoint and arguments, listener and ports, required environment/configuration including parser coercion and unset behavior, secrets, writable storage, dependencies, wire protocols, TLS, authentication/bootstrap setup, and feature-critical configuration. Inspect CLI flags and structured fields as well as environment variables.545. Map every selected backing service to a Radius type with [component-catalog.md](references/component-catalog.md), using [architecture-patterns.md](references/architecture-patterns.md) only as context. Report unsupported essential components instead of substituting unrelated types.556. After the workload, backing-service, ingress, image-publishing, and secret inventory is complete, but before authoring Bicep, resolve every planned predefined type in one `show-radius-type.mjs` batch as described in [Resource Type Resolution](#resource-type-resolution). Apply the resource-specific structure rules before this call so companion predefined resources are included in the same batch. Unless target evidence explicitly proves an unauthenticated image registry, planning `Radius.Compute/containerImages` also plans the single `Radius.Security/secrets` registry-push companion described below. Preserve and inspect the complete returned JSON. Use each returned `type`, `apiVersion`, `schema`, and `recipe`, and handle every nonzero exit or `notFound` entry as described there. The script creates or merges the staged `bicepconfig.json` and owns its `radius` alias. The result supplies type-schema evidence and, when available, managed-release default Recipe evidence. Before authoring, apply the [Credential shape](references/secrets-handling.md#credential-shape) check. Use an available `recipe.definition` as the managed-default Azure Recipe profile unless explicit target evidence selects another Recipe; use that selected Recipe to decide whether a credential exists and inspect every credential representation it exposes. For each predefined type whose resolved Recipe meets the applicability check in [azure-provider-value-rules.md](references/azure-provider-value-rules.md), apply those rules to every property that the Recipe copies into an Azure name, administrator login, or AI model setting. Do not apply those Azure rules to AWS, Kubernetes, or an unverified custom Recipe. Prefer a direct shape match. When an aggregate output is incompatible or unknown, consider schema-declared discrete outputs and safe runtime composition before reporting a blocker; when the client needs parts, consider a proven runtime decomposition path. A package name without a checked-in consumer is not evidence, but an exact pinned dependency together with the checked-in call site that passes the value to that client's configuration API identifies the parser contract and permits using that client's documented syntax. Combine that client evidence with selected-profile literals, checked-in parser code, schema descriptions, and the selected Recipe's auth and output mappings. Treat compatibility as unknown only after direct binding and every supported composition or decomposition path have been considered. Stop on missing Recipe behavior or a remaining incompatible or unknown shape. Verify target-Environment registration, Recipe declarations outside the returned definition, and other application runtime behavior separately.567. Build the application's own workloads from the repository Dockerfile via `Radius.Compute/containerImages`; this is the default path, and a repository without a Dockerfile is unsupported (see [Prerequisites](#prerequisites)). Require a complete, practical build context and pin `build.source` to the exact modeled checkout or an explicit immutable release tag. When the ref is a commit, use its full 40-character SHA in `?ref=`; never use an abbreviated SHA. The image `tag` may remain abbreviated because it is not a Git ref. Resolve the exact `containerImages` Recipe: verify omitted optional inputs, set a Docker-valid immutable `tag` only when required by that contract, decide `build.platforms` per image with the [Choosing build.platforms](references/bicep-structure-rules.md#choosing-buildplatforms) procedure, and preserve required Git metadata with schema-supported build arguments. Use a pinned published image only for a genuinely third-party/backing container, never for the application's own code. Map every runtime value using [connection-conventions.md](references/connection-conventions.md), [secrets-handling.md](references/secrets-handling.md), and [bicep-structure-rules.md](references/bicep-structure-rules.md).578. Generate the Bicep into `<staging-dir>/app.bicep` using [naming-conventions.md](references/naming-conventions.md). For each predefined type whose resolved Recipe meets the applicability check in [azure-provider-value-rules.md](references/azure-provider-value-rules.md), keep its Azure-bound names, administrator logins, and model names within those rules. Use string literals or parameters with literal defaults for these values. If the source requires a value that the selected Recipe cannot deploy, report the conflict and abort the run; do not rename the source-required database, container, topic, or model. Then run `node "<loaded-skill-base>/scripts/validate-bicep.mjs" <staging-dir>/app.bicep`. Repair every compiler error and warning until the checker exits successfully and prints no warnings, within the [repair budget](#repair-budget) it enforces. Never make compilation pass by deleting a required backend activation, native configuration value, secret binding, or dependency edge.589. Perform the [validation checklist](#validation-checklist) and close every item in the requirement ledger. Compilation or process startup alone is not success.5910. Only after the checker exits successfully, write the [origin record](#origin-record-apporiginjson) into the staging directory.6011. Publish the run with the promote script, which is the last step of every modeling run and the only thing that writes into `.radius/` (see [Staged runs](#staged-runs)).6162### Repair budget6364A compile error the skill cannot resolve is usually a real signal — a schema that has moved, a type the configured extension does not have, or a changed recipe contract — not something more attempts will fix. So `validate-bicep.mjs` bounds the repair loop itself, and the run ends by reporting rather than by editing indefinitely.6566The checker enforces this whenever the model it compiles is inside a staging directory, by counting its own compiles in that run's `run.json`. It records the attempt before compiling, so an interrupted compile still counts, and it refuses to compile at all if that record cannot be read or updated — a budget it cannot count is one it cannot enforce. The count covers exactly one modeling run, and a later run starts fresh. You do not track attempts yourself, and you cannot compile your way past the limit.6768- **Five repairs per run.** The first compile is free, because it is what reveals the problem; after five repair-and-recompile cycles the checker refuses to compile again and exits non-zero saying the budget is spent. That is the same number of repair cycles the deploy-failure repair loop allows after a failed deploy, so the product has one answer to "how many times do we retry a repair on `app.bicep`".69- **The checker tells you when a failure repeats.** It fingerprints the compiler output with line numbers and diagnostic ordering normalized out, and says so when a failure is the one you just saw. Treat that as proof the last fix was wrong: make a materially different fix rather than varying it, or use the remaining budget to establish why the schema cannot express what the source needs.70- **If the checker refuses to compile, stop.** Whether the budget is spent or the run's bookkeeping is broken, do not edit and retry. Do not write the origin record and do not publish the run. Tell the user which resource and property the compiler rejected, quote the last compiler output verbatim, and say that no application definition was written. The raw error is the most useful thing to hand over, because it is usually pointing at something real.7172Never buy a clean compile by deleting a required backend activation, native configuration value, secret binding, or dependency edge. Running out of budget is the correct outcome when the alternative is a model that compiles and does not work.7374## Deployment Profile and Acceptance Contract7576- **Explicit profile wins:** If the request names a supported Radius type, provider profile, workload role, native key, protocol value, secret binding, or relationship, model it exactly when the pinned source supports it. A source default or another valid deployment profile does not satisfy that request.77- **Source compatibility is still mandatory:** Resolve behavior from the requested commit/tag, not a different release or the current default branch. If an acceptance criterion conflicts with that source revision, stop and report the conflict instead of inventing compatibility.78- **No implicit omissions:** Each required typed resource must be emitted and wired to a consumer. Each required workload role must have a runnable process and complete config. Each required native key/value must appear in the exact source-supported location and format.79- **No decorative wiring:** Environment variables, connections, and resources must be consumed by the selected feature path. Merely declaring a dependency or starting a process does not prove the requested database, model, storage, or messaging path works.80- **Mandatory dependencies only:** Model only services required by the selected runnable path. Imports, package extras, adapters, examples, tests, or alternate configurations elsewhere in the repository do not prove that a backing service is required.81- **Infer only when unspecified:** Without an explicit profile, prefer a complete, documented manifest/configuration that exercises the application's primary feature. If multiple materially different profiles remain valid, ask the user rather than choosing an optional backend arbitrarily.82- **Fail closed on verified incompatibility:** Fully implement every clearly supported criterion. Stop after evidence proves the pinned source or exact schema/Recipe cannot satisfy a requirement; do not return a partial definition as deployable, leave unresolved runtime caveats, or delete feature-critical wiring to obtain a clean compile.8384### Repairing an existing app.bicep8586When a deploy fails because of a modeling or schema error in an existing `.radius/app.bicep` (unknown type or API version, unknown or missing property, invalid reference between resources, wrong credential shape, or a Bicep parse or compile error), repair that model rather than regenerating it from scratch. A repair is still a run: start it with `--begin`, copy the current `.radius/app.bicep` and `.radius/bicepconfig.json` into the staging directory, edit the staged copies, and publish with the promote script (see [Staged runs](#staged-runs)). Editing `.radius/app.bicep` in place would leave a half-repaired model behind if the repair failed partway. This assumes the deploy error and any relevant logs have been provided (by the `radius-deploy` skill or the user); if they haven't, ask for them before attempting a fix.87881. Confirm whether the failure comes from the application model. If it is an infrastructure, recipe, Environment, or cluster failure (for example, recipe download/execution or provider provisioning), stop and hand it back to the `radius-deploy` skill; editing `app.bicep` will not fix it. A pod that never becomes ready is not enough to classify the failure: inspect events and logs to distinguish infrastructure/connectivity failures from incorrect workload configuration, listeners, credentials, or dependency wiring in `app.bicep`.892. Locate the implicated resource, property, or workload setting, then re-resolve the exact configured type schema and Recipe contract (see [Resource Type Resolution](#resource-type-resolution)) to confirm property names, required fields, credential shape, API version, resource reference paths, and provider restrictions. If the implicated predefined type's resolved Recipe meets the applicability check in [azure-provider-value-rules.md](references/azure-provider-value-rules.md), apply those rules. If the application requires the rejected value, report the application/Recipe conflict instead of silently changing it.903. Apply the fix using the same runtime-contract, naming, structure, and secrets rules as authoring so the repaired resource stays consistent with the rest of the file. While you are in the file, also correct any other clear schema or rule violations you notice, and report each collateral fix you made. Never clear a compile error by deleting a required binding, native value, backend activation, or dependency edge; report version drift when the configured schema cannot represent the runnable profile.914. Re-run the [validation checklist](#validation-checklist) against the whole file; a change in one resource can ripple to connections or references elsewhere.925. Return the corrected file with a short note of what changed and why, then hand it back to `radius-deploy` to redeploy. Write a new [origin record](#origin-record-apporiginjson) into the staging directory after the checker passes, so the repaired bytes are the ones recorded, then publish the run. If the same error recurs, treat the previous fix as insufficient and try a different fix rather than reapplying the one that just failed. If a couple of different fixes still do not resolve it, or no different fix can be found, report that to `radius-deploy` so it can stop the retry loop and surface the problem to the user.9394## Staged runs9596A modeling run that stops partway must leave the repository exactly as it was. So a run never writes into `.radius/` directly: it writes everything into a staging directory inside `.radius/`, and a script moves that output into place only once the run is complete and its application model has compiled.9798**Start every run** — generation or repair — with:99100```text101node "<loaded-skill-base>/scripts/promote-app-model.mjs" --begin102```103104It removes any staging directory a previous interrupted run left behind, records the fingerprints of the files in `.radius/` this run may replace, and prints the staging directory. It writes nothing outside that directory, which is what lets a failed run leave `.radius/` byte-identical without having to undo anything. Write every file the run produces into that directory and nowhere else: `app.bicep`, `bicepconfig.json`, the origin record, and any custom-type artifacts (`custom-types.yaml`, `custom-types.tgz`, `custom-recipe-pack.bicep`, and `<type>-recipe.bicep`) (pass the directory to `radius_publish_custom_type_extension` as `stagingDir` so its published package lands there too). Run the Bicep checker against the staged `app.bicep`, so what is verified is exactly what will be published.105106**Finish every run** with:107108```text109node "<loaded-skill-base>/scripts/promote-app-model.mjs" --staging "<staging-dir>"110```111112It refuses unless the staging directory holds a complete set of files, the origin record describes the staged `app.bicep`, and `.radius/app.bicep` is still the file the run started from. On success it moves the files into `.radius/`, deletes the staging directory, adds `.staging-*/` to `.radius/.gitignore`, and stages the published files with `git add` — which is why you never run `git add` yourself. On any refusal it discards the staged run and writes nothing.113114It exits `0` when the run was published and staged, `1` when it refused and nothing was written, and `2` when the files were published but `git add` failed. On `2` the model IS on disk: report that it was written but not staged, and do not re-run the run.115116Rules:117118- Never write, copy, or move a generated file into `.radius/` yourself, and never hand-write the files the script publishes. A file you place there directly is exactly the partial write staging exists to prevent.119- Never re-run modeling "to finish the job" after a refusal without starting a new run with `--begin`. A retry starts from a clean slate.120- Only the files listed above are published. Anything else you leave in the staging directory is discarded with it, so never keep notes, scratch output, or intermediate files there and expect them to survive.121- If the script refuses because a file in `.radius/` changed during the run, the user edited it while you were working. Report that their version is intact, that nothing was published, and offer to re-run modeling. Do not attempt to merge, restore, or overwrite their file.122123### When a step fails124125When any step of the run fails, discard the run with:126127```text128node "<loaded-skill-base>/scripts/promote-app-model.mjs" --abort --staging "<staging-dir>"129```130131Use this rather than deleting the directory yourself, so a run is always discarded the same way. Then report the failure. Say plainly that **nothing was written**: `.radius/` is exactly as it was, nothing was staged in git, and any application model the user already had is intact. Never keep the staging directory for inspection.132133Do not retry on your own. Say which kind of failure it looks like and let the user decide:134135- **Looks transient** — a network error fetching a schema, a registry timeout, an interrupted download. Offer to run modeling again.136- **Looks permanent** — no Dockerfile, a required backing service with no Radius type that cannot be provisioned on Azure, no source that resolves to a runnable profile. Report it and do not offer a retry, because the same run would fail the same way. If the Canvas handoff that started this run supplied `radius_report_modeling_failure` arguments, call that tool once with the supplied instance, repository, branch, and attempt token plus an actionable failure summary; this lets the waiting graph stop requesting the same impossible model. Never call it for a transient failure, cancellation, user-declined action, or a run that wrote `app.bicep`.137138## Origin record (`app.origin.json`)139140Every generation records what the model was produced from, in `.radius/app.origin.json` beside `app.bicep`. The Radius canvas reads this record before rendering a graph: without it, the only question the canvas can ask is "does `app.bicep` exist?", so a model whose source has since moved on is rendered as though it were current. Write it with the bundled script, never by hand, so the format stays exactly what the canvas parses:141142```text143node "<loaded-skill-base>/scripts/write-app-origin.mjs" <staging-dir>/app.bicep --skill-version "<loaded-skill-version>"144```145146Rules:147148- Write the record on **every** generation and every repair, as the last step, and only after `validate-bicep.mjs` exits successfully with no warnings. The record holds a hash of the exact bytes that compiled, which is what lets the canvas treat the model as known-valid instead of recompiling it on every graph open. Recording a file that has not passed the checker asserts a validity that was never proven.149- `--skill-version` is optional. Pass it when the prompt gave you a real version; if the value you were given is still the literal `<loaded-skill-version>`, omit the flag entirely rather than passing the placeholder through. The script reads the version from the plugin manifest itself, and a placeholder recorded in the origin record would make every later freshness check report the model as generated by an unknown generator.150- Write it into the staging directory beside the `app.bicep` it describes, and let the promote script publish and stage the pair (see [Staged runs](#staged-runs)). A model committed without its origin record reads as unverified on every other checkout of that branch, and a run whose record is missing or does not match its model is refused rather than published.151- Never edit `app.bicep` after recording it. If you change one byte, re-run the checker and re-run the origin script.152- The script fails closed when it cannot resolve the source commit or read the model. Do not work around it by writing the JSON yourself; fix the underlying problem and re-run it.153154## Refreshing a stale model155156The canvas asks for a refresh when an existing model is stale: its branch has moved past the recorded commit, a different generator version is installed, or there is no usable origin record at all. Regenerate from current source and write a new origin record, with one guard:157158- **When the model is reported as manually edited, ask the user before overwriting it.** That state means the model needs regenerating and was also changed after it was generated: hand-tuned properties, a custom type, or a recipe pack reference someone added deliberately. Say what would be lost and regenerate only after the user agrees. Offer to repair the specific problem in place (see [Repairing an existing app.bicep](#repairing-an-existing-appbicep)) as the alternative, since that preserves their edits.159- **When the model has no origin record, regenerate it and write one. Do not ask.** Nothing about a missing record shows the model was edited, so there is no decision to put to the user, and the canvas only reports it this way when the file is committed and unmodified, so git still has the version being replaced. Regenerate from current source and record it as you would for any other generation.160- A model that is stale only because the source or generator moved on carries no unproven content, so refresh it without asking.161- A manual edit is never reported on its own. Do not go looking for one, and do not raise it when the canvas has not, since an edit to a model that is otherwise current is the user's to keep.162- Refresh only the current workspace branch, where writing the working tree is enough. A model on a **different** branch cannot be refreshed by modeling: report the staleness to the user and let them decide, rather than committing or pushing a regenerated model to that branch.163164## Deterministic Naming Rules165166These rules eliminate ambiguity. Apply them exactly.167168Explicit profile-required resource, relationship, parameter, and app-native configuration names take precedence over the default naming rules below. Never normalize a name the selected runtime contract requires verbatim. Preserve a resource-name parameter when deployment documentation, the target Environment Recipe, or verification couples it to a provider resource name.169170### Symbolic names (left side of `=` in Bicep)171172| Resource | Symbolic name |173|-----------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|174| Application | `<shortName>App` where `<shortName>` is the app name without hyphens, camelCase (e.g., `todo-list-app` → `todoApp`) |175| Container | `<serviceName>Container` — service short name camelCase; single-container apps use `<shortName>Container` (e.g., `todoContainer`) |176| Container image | `<serviceName>Image` (e.g., `todoImage`) |177| Data store (database/cache/queue) | `<engine>` + role suffix, camelCase: `mysqlDb`, `postgresDb`, `neo4jDb`, `redisCache`. Multiple of the same engine: prefix with the source store name (e.g., `ordersPostgresDb`) |178| Data store secret | `<engine>Secret` when the type's schema defines a secret-reference credential property; app secrets use `appSecrets` |179| Route | `<serviceName>Route` (e.g., `todoRoute`) |180181### Resource `name` properties (string values in Bicep)182183| Resource | Name value |184|-------------------|----------------------------------------------------------------------------------------------------------------------------------------|185| Application | Repository name in kebab-case (e.g., `'todo-list-app'`) |186| Container | Service name in kebab-case; single-container apps use the app name (e.g., `'todo-list-app'`) |187| Container image | `'<service-name>-image'` (e.g., `'todo-list-app-image'`) |188| Data store | Engine short name in kebab-case (`'mysql'`, `'postgres'`, `'neo4j'`, `'redis'`); multiple of the same engine use the source store name |189| Data store secret | `'<engine>-secret'` (when the schema defines a secret-reference credential property); app secrets `'app-secrets'` |190191### Connection keys192193| Connection | Key |194|------------|--------------------------------------------------------------------------------------------------------------------------------------------|195| Data store | Engine + role, lowercase: `mysqldb`, `postgresdb`, `neo4jdb`, `rediscache`. Multiple of the same engine: prefix with the source store name |196197### Other fixed values198199| Field | Value |200|------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|201| Data store admin username | The administrator username you author for the provisioned database. It is not derived from the source. Set it wherever the schema puts credentials — `username` on the resource, or `USERNAME` in the authored Secret when the schema takes a secret reference instead. Use `myadmin` when the source does not require a login; it fits the Azure PostgreSQL, MySQL, and SQL safe subsets. |202| Data store `database` name | Derived from source (e.g., `MYSQL_DATABASE`/`POSTGRES_DB`, or the database segment of a connection string). If the data store type's resolved Recipe meets the applicability check in [azure-provider-value-rules.md](references/azure-provider-value-rules.md), the database name must also satisfy those rules. Do not rename a source-required database to make it pass. |203| Data store `version` | Derived from source (e.g., the image tag `mysql:8.0` → `'8.0'`) |204| Container key in `containers` map | Service short name camelCase (single-container: derived from app, e.g., `todo`) |205| Port key in `ports` map | `web` for the primary HTTP port; additional ports derive from protocol/use (`http`, `grpc`) |206| `build.source` for containerImages | Repo git URL pinned to the modeled checkout: `git::https://github.com/<org>/<repo>.git//<subdir>?ref=<checked-out-sha-or-explicit-immutable-tag>` (`//<subdir>` only when the Dockerfile isn't at the repo root) |207208### Deterministic output209210Two runs of this skill over the same source, with the same generator version and the same schema/recipe contract, must produce byte-identical `.radius/app.bicep`. A regeneration that reshuffles equivalent content produces a large diff that says nothing, which makes real changes hard to review and adds noise to the repository's history. Apply:211212- **Canonical declaration order.** `extension` lines first, then `param` declarations, then the `Radius.Core/applications` resource, then the remaining resources. Never order resources by discovery order, file-walk order, or the order a tool happened to return them.213- **Canonical ordering within each group.** Order `param` declarations, and resources of the same type, by their `name` value using plain ASCII ordering. Resource types themselves follow the order they appear in the allow-list table under [Resource Type Resolution](#resource-type-resolution). The one exception is a resource that must be declared after something it references.214- **Canonical ordering inside a resource.** `name` first, then `properties`. Within a map whose keys you choose (`env`, `ports`, `containers`, `connections`), order keys ASCII-a215216…(truncated)