Djong1 adaptive apps microhack - #487
Open
Dylan de Jong (djong1) wants to merge 41 commits into
Open
Conversation
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Validated challenges 00-04 end to end from the devcontainer against a live Azure subscription, on both the AKS and K3s VM paths. Five blocking script bugs were found and fixed, plus the prerequisite and capacity gaps that made first-run failures hard to diagnose. Blocking fixes: - prepare-k3s-azure-vm.sh: "az network bastion wait --custom provisioningState=='Succeeded'" never matches in the bastion extension, so every run burned the full 30-minute --timeout before continuing. Use the built-in --created condition, which returns as soon as Bastion is ready. - prepare-k3s-azure-vm.sh: disconnect only stopped the az Bash wrapper. /usr/bin/az runs its Python entry point as a child instead of exec'ing it, so the process that owns localhost:16443 survived. disconnect reported a false success and every later connect aborted with "Local port already in use", breaking the reconnect workflow used by challenges 02-08. Match both processes by full signature, stop all of them, and reclaim orphans in ensure_tunnel. - prepare-k3s-azure-vm.sh: register the k3s-azure-vm context in the default kubeconfig during provision and connect. rad 0.60.0 honours KUBECONFIG for the Helm phase but reads ~/.kube/config when configuring the Contour gateway, so deploy-radius-k3s.sh failed with "context was not found". The active context is left unchanged. - deploy-radius-aks.sh and deploy-radius-k3s.sh: the health check queried the CRD applications.radapp.io, which Radius 0.60.0 does not install. Both scripts exited 1 despite a healthy control plane. Check the CRDs Radius actually creates: recipes, deploymenttemplates, and deploymentresources. - configure-resource-types-aks.sh and configure-resource-types-k3s.sh: "kubectl rollout status deployment --all" is not valid, since kubectl rollout status takes a single object. Both scripts failed with "unknown flag: --all" right after the Helm install. Iterate over the objects. VM capacity handling: - prepare-aks.sh and prepare-k3s-azure-vm.sh resolve the node or VM size before creating anything, using one az vm list-skus call per run. Zone-only restrictions are ignored because the deployment is regional. If allocation still fails on capacity or quota, the failed resource is removed and the next candidate is tried; authorization failures are not retried. Override the ordered list with AKS_NODE_VM_SIZE_CANDIDATES or K3S_VM_SIZE_CANDIDATES. Documentation: - challenge-00.md: complete zero-experience host setup. WSL 2 enablement, three supported container runtimes including a Docker Desktop licence note and a Docker Engine in WSL 2 alternative, the Dev Containers extension ID, host Git, runtime sizing, arm64 support, and device-code sign-in. - challenge-00.md: resource provider registration and a public IP preflight, with the repair for the SubscriptionNotRegisteredForFeature AllowBringYourOwnPublicIpAddress error seen on a fresh subscription. - challenge-00.md: regional VM capacity and quota preflight commands. - solution-00.md, solution-01.md: runtime options, arm64 support, tunnel lifecycle, kubeconfig registration, and size fallback behaviour. - solution-04.md: state that challenge 03 must run first, because the environment templates register recipes for the types it creates. - challenge-02.md, solution-02.md, solution-03.md: correct the CRD and rollout commands shown to participants. - post-create.sh: "local readonly VAR=" does not make a variable readonly (shellcheck SC2316). - .gitignore: ignore the generated devcontainer-lock.json. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The private K3s VM has no inbound exposure but still needs outbound access to download the K3s installer and pull images. It relied on implicit default outbound access, which Azure is retiring: for API versions released after 2026-03-31, new virtual networks default to private subnets with no outbound path. The failure therefore depends on the Azure CLI version that creates the subnet rather than on the calendar date, and would have appeared as a silent regression on a future CLI. Measured on 2026-08-21 against a live subscription: - a subnet created by az 2.89.1 has defaultOutboundAccess=null, so the platform default still applies and outbound works - a private subnet blocks the installer: curl https://get.k3s.io fails - setting defaultOutboundAccess=true does not take effect on a running VM; outbound is restored only after a deallocate and start - a NAT gateway restores outbound on a private subnet within seconds and needs no restart prepare-k3s-azure-vm.sh now sets the outbound posture explicitly instead of inheriting the platform default: - by default it creates snet-k3s with defaultOutboundAccess=true, which keeps the current zero-cost topology working under any CLI version - K3S_ENABLE_NAT_GATEWAY=true leaves the subnet private and attaches natgw-adaptive-apps with a Standard public IP, which is required when policy forbids default outbound access and is the durable long-term option - an existing private subnet without a NAT gateway is reopened, and the VM is deallocated and started so the change applies - the guest installer reports a dedicated no-outbound marker, so the script fails with actionable guidance instead of a generic K3s error Documented the postures, their cost, and NAT gateway cleanup in docs/prepare-k3s.md and walkthrough/challenge-01/solution-01.md. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Handle Azure default outbound access retirement for the K3s VM
Default outbound access is itself scheduled for retirement, so relying on it only defers the problem. The NAT gateway is now the default: it is explicit, survives the retirement, and gives a deterministic egress IP that a firewall can allowlist. K3S_ENABLE_NAT_GATEWAY=false keeps the previous zero-cost posture where policy still permits default outbound access. The K3s subnet is now kept explicitly private when the NAT gateway is used. Azure can still assign a platform-owned fallback outbound IP to a nonprivate subnet even when a NAT gateway is attached, which would defeat the deterministic egress address. Reconciling an existing nonprivate subnet also flags the VM for a deallocate and start, because removing an already-assigned fallback IP only takes effect after that cycle. Verified against a live subscription: - default path creates the subnet private, attaches the NAT gateway, and needs no restart - re-running is idempotent - a nonprivate subnet is converted to private and flagged for restart - opting out with an existing NAT gateway leaves it in place rather than removing an in-use egress path - egress resolves to the NAT public IP Updated the prerequisites, walkthrough, and K3s reference for the new default, including NAT gateway permissions, the second Standard public IP, and cleanup. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Make the NAT gateway the default outbound path for the K3s VM
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Initialize Azure PostgreSQL for the trading app
Removed instructions for upgrading an existing workshop and redeploying Challenge 05.
`az network public-ip show --query "[ipAddress,publicIPAllocationMethod,sku.name]" --output tsv` returns a JMESPath multi-select list, which Azure CLI renders as one value per line rather than as tab-separated columns. `read -r ip allocation sku` therefore consumed only the first line, leaving `allocation` and `sku` empty, and a valid Standard/Static AKS outbound IP was rejected with "must be a Standard, statically allocated public IP". The failure aborted configure-recipes.sh after the recipes were published to ACR but before `rad deploy iac/aks-env.bicep`, so env-azure-prod was left with no recipes and Challenge 05 failed with RecipeNotFoundFailure for every portable resource type. - Read the egress IP fields with mapfile (one value per row) and strip CR so CRLF `az` output cannot reintroduce the same class of failure. Apply the same handling to the outboundType and outbound resource ID lookups. - Include the observed sku and allocation in the rejection message so a future failure is self-diagnosing. - Replace the informational `rad recipe list` with an assert_recipes helper that fails the run when an expected type has no default recipe. `rad recipe list` prints an empty table and exits 0, so a broken run previously reported success. - Apply the identical parsing fix to the manual tutorial in solution-04.md, which carried the same defect. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Azure rejects tag names containing '/', so the 'radapp.io/environment', 'radapp.io/resource' and 'radapp.io/application' tags applied to the PostgreSQL flexible server and the AVM SQL server caused challenge 05 to fail with InvalidTagNameCharacters when the postgreSqlDatabases recipe ran. Switch those Azure tag names to a hyphenated form. Kubernetes label keys legitimately allow '/', so the labels in postgres-kubernetes.bicep and the Kubernetes resources inside postgres-azure-flex.bicep are left unchanged. Because the recipes are published to ACR under immutable version tags, correcting the source alone is not enough. Bump the default WORKSHOP_RECIPE_VERSION to 1.0.1 so the corrected recipes are republished and re-registered, and stop sql-server from hardcoding :1.0.0 in the publish and register paths so it can never drift from the other recipes. Add an assertion to validate-postgres-recipes.sh so this class of defect cannot return. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Fix AKS egress IP parsing that silently skipped Challenge 04 recipe registration
Fix InvalidTagNameCharacters on Azure resources in Radius recipes
Azure Database for PostgreSQL Flexible Server serializes control-plane operations per server, but Bicep infers dependencies only from `parent:`. The database, both server configurations and the firewall rules therefore depended solely on the server, so ARM fanned all of them out in parallel as soon as it was created and the losers failed with: ServerIsBusy: Cannot complete operation while server 'pg-...' is busy processing another operation. Try again later. Which operation loses the race is non-deterministic, so challenge 05 failed intermittently and a retry could appear to succeed. Chain the children explicitly - database, then require_secure_transport, then ssl_min_protocol_version, then the firewall rules with @batchsize(1) so the loop is applied one rule at a time. This also fixes a latent bug: the schema initializer job never depended on the database resource, and now does so transitively. Bump WORKSHOP_RECIPE_VERSION to 1.0.2 so the corrected recipe is republished and re-registered, and assert the serialization in validate-postgres-recipes.sh. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Serialize PostgreSQL flexible server child resources
Radius surfaces a recipe's `values` map as the resource's properties. The three database recipes returned `secrets` as a sibling of `values` instead of nested inside it, which is not part of that contract - so dynamic-rp discarded the entire output payload. Deploying the application then failed with: InvalidTemplate: The language expression property 'secrets' doesn't exist, available properties are 'application, environment, provisioningState, recipe, size, status'. Note that host, port, database and username were missing too, not just the password: the unexpected sibling key cost every output, not only the secret. Confirmed against a working control in the same deployment: backend-identity, provisioned by an upstream recipe, surfaced all seven of its read-only outputs, and the registered postgreSqlDatabases schema already declared host, port, database, username and secrets.password correctly. The two ai-model recipes in this repository already nested secrets inside values, so the three database recipes were the outliers. Move `secrets` inside `values` in postgres-azure-flex, postgres-kubernetes and sql-server, matching both the upstream recipes and the ai-model recipes here. Bump WORKSHOP_RECIPE_VERSION to 1.0.3 so the corrected recipes are republished and re-registered, and assert the output shape in validate-postgres-recipes.sh. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Nest recipe secrets inside the values map
The Azure PostgreSQL recipe returned every value the postgreSqlDatabases type
declares, yet trading-db came back with only application, environment,
provisioningState, recipe, size and status. app.bicep then failed with
InvalidTemplate: the language expression property 'secrets' doesn't exist.
The result output could not be evaluated. Bicep compiles
map(aksFirewallRules, rule => rule.id) to references('aksFirewallRules', 'full'),
which the Radius deployment engine resolves to the template's resource metadata.
That object exposes resourceId, not id, so evaluation threw:
Azure.Deployments.Core.Exceptions.ExpressionException: The language expression
property 'id' doesn't exist, available properties are '..., resourceId,
symbolicName, ...'
bicep-de logs that only at WARN. The deployment still reported Succeeded, but it
returned no outputs, and prepareRecipeResponse skips the whole block when the
outputs map is empty, so Radius stored none of the recipe's values without
surfacing an error. The missing Kubernetes entries in status.outputResources were
the same failure: non-ARM resources arrive only via result.resources.
Precompute the firewall rule IDs with resourceId() and build the Kubernetes IDs
from the compile-time name variables, leaving the server FQDN as the only runtime
reference in the result. Verified against the compiled template: references() is
gone entirely and the values map still carries host, port, database, username and
secrets.
Bump the recipe version so the corrected template is republished, since ACR tags
are immutable, and guard the result block in validate-postgres-recipes.sh.
Also correct the rationale on the nested-secrets guard. Nesting is required
because a schema-declared secrets property is populated from the values map,
whereas a top-level sibling is materialized into a managed Radius.Security/secrets
resource that leaves only properties.secrets.name behind.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Keep the PostgreSQL recipe result evaluable so Radius records its values
`rad deploy iac/app.bicep` failed at the backend container with:
InvalidTemplate: Unable to process template language expressions for
resource 'Applications.Core/containers/backend' ... 'The language
expression property 'secrets' doesn't exist, available properties are
'application, database, environment, host, port, provisioningState,
recipe, size, status, username'.'
The preceding fix made the recipe's `values` land correctly (host, port,
database and username are now present), which isolated the remaining failure
to `secrets` alone.
Radius treats `secrets` as a framework-owned property. It is listed in
`pkg/resourceutil.BasicProperties`, which is documented as excluded from
generic recipe-output copying and from connection-based environment variable
injection. That means neither shape of recipe output can populate
`properties.secrets.password`:
* nested inside `values`, the map becomes a computed value and is then
dropped by `addComputedValuesToResourceProperties`, silently;
* as a top-level sibling of `values`, `materializeRecipeSecrets` creates a
managed `Radius.Security/secrets` resource and writes back only
`properties.secrets.name`, because secret values are never persisted on
the owner resource.
The second route is additionally unavailable here: consumers bind managed
secrets through the reserved `secrets.name` sub-property, and the pinned
upstream `postgreSqlDatabases` type declares only `secrets.password`.
Bind the Kubernetes Secret directly instead. The recipes already create a
Secret holding the password for their own schema-initializer Job, so give it
the stable name `<resource-name>-credentials` and have the backend read it
with `valueFrom.secretRef`. A container `secretRef.source` that is not an
`Applications.Core/secretStores` resource ID is passed through verbatim as a
`secretKeyRef.name`, so a plain Secret name resolves without any additional
resource.
The Secret name is derived from a new compile-time `tradingDbName` variable.
Using `tradingDb.name` would compile to `reference('tradingDb').name`, and the
resource's runtime property bag has no `name` key, which would have reproduced
the same class of failure this change fixes.
The Kubernetes recipe gets the same treatment so that both environments keep
sharing one `app.bicep`, and its `result` block is rewritten to be
reference-free while it is being touched.
Changes:
* postgres-azure-flex.bicep: stable `credentialsName`, drop the nested
`secrets` output.
* postgres-kubernetes.bicep: matching `credentialsName`, add
`initializerName`, drop the nested `secrets` output, and build the
`result` block from compile-time variables instead of
`<resource>.metadata.name`.
* app.bicep: add `tradingDbName` and bind the password via
`valueFrom.secretRef`.
* validate-postgres-recipes.sh: replace the previous guard, which wrongly
required a nested `secrets` block, with guards covering the new contract,
and extend the existing output guards to both PostgreSQL recipes.
* configure-recipes.sh, solution-04.md: bump the recipe version to 1.0.5 and
document both `result` constraints.
Every new guard was verified to fail when its defect is reintroduced. All three
templates compile against the pinned extension, and the compiled `app.bicep`
resolves the Secret name at build time.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Fix recipe secret consumption so the backend container can deploy
…kout
The schema initializer Job has been failing since it was introduced, and the
failure is invisible: the recipe does not wait for the Job, so `rad deploy`
reports success while the trading database is left completely empty. On the
cluster the Job sits at `Failed 0/1`, having burned through its backoff limit
with seven pods that each died in seconds.
The cause is line endings. `.gitattributes` pins `eol=lf` through a per-file
allowlist that covers `app.bicep`, `ai.bicep`, `ai-model-*.bicep` and `**/*.sh`
but omits `iac/recipes/postgres-*.bicep`. Those files are stored as LF and
checked out as CRLF on Windows. Bicep preserves on-disk line endings verbatim
through `loadTextContent()` and `'''…'''` multi-line strings, so the carriage
returns end up inside the Job's shell script. `sh` does not treat CR as
whitespace, so `do<CR>` stops being the `do` keyword and the container exits
immediately:
sh: syntax error: unexpected word (expecting "do")
Linux and macOS checkouts are unaffected, which is why this went unnoticed.
Fix the root cause by replacing the allowlist with globs covering `*.bicep`,
`*.sql`, `*.yaml` and `*.yml`, so files added later are pinned by default rather
than silently omitted. Additionally strip carriage returns in the recipes
themselves with `replace(..., '\r', '')`, which keeps them correct on a checkout
configured any other way. ARM evaluates that at deployment time; a probe
deployment confirmed `length('a\r\nb')` is 4 while the replaced value is 3.
Fixing the script alone is not deployable, because it exposes a second defect.
A Job's `spec.template` is immutable, so a Job must be replaced rather than
updated when its pod template changes, and that only happens if its name
changes. The name was derived from `uniqueString(schemaSql)`, which tracks the
schema and nothing else. Renaming the credentials Secret in the previous change
therefore altered the pod template while leaving the name identical, and the
deployment failed with:
Job.batch "postgres-...-schema-c6pwzgiu" is invalid:
spec.template: Invalid value: ...: field is immutable
Changing the script would have hit exactly the same wall. Hash every input the
pod template embeds — the script, the credentials Secret name, the user, the
database, the port and the client image tag — so any pod template change yields
a new Job name. Radius creates the new Job and garbage-collects the old one
through `result.resources`.
Both PostgreSQL recipes get the same treatment, since `app.bicep` is shared by
the AKS and K3s environments.
Changes:
* .gitattributes: replace the per-file bicep allowlist with globs.
* postgres-azure-flex.bicep, postgres-kubernetes.bicep: strip CR from the
schema SQL and the initializer script, and hash the whole pod template into
the Job name.
* validate-postgres-recipes.sh: guard CR stripping, the use of the extracted
script variable, the Job name hash inputs, and the .gitattributes globs.
* configure-recipes.sh, solution-04.md: bump the recipe version to 1.0.6 and
document both constraints, including how to verify the Job actually
completed rather than trusting the deployment result.
Each of the nine new guards was verified to fail when its defect is
reintroduced. All three templates still compile, and they were compiled from a
CRLF working tree so the compiled output exercises the stripping path.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…-endings Fix the schema initializer Job, which has never run on a Windows checkout
Challenge 05 could not finish on AKS. After the schema initializer was fixed the
backend still crash-looped, and `rad deploy` failed before the frontend container was
ever created:
"code": "Internal",
"message": "Container state is 'Waiting' Reason: CrashLoopBackOff, ..."
The backend reached PostgreSQL fine; `/api/accounts` returned data. It died on MQTT.
`MqttOrderListener` called `SubscribeAsync` on a client that never connected, which
threw `MqttClientNotConnectedException`, and .NET's default
`BackgroundServiceExceptionBehavior.StopHost` stopped the host.
The connection was refused because Azure Event Grid accepts a Microsoft Entra token
only through the MQTT v5 enhanced-authentication fields: `Authentication Method` set
to `OAUTH2-JWT` and the bearer token in `Authentication Data`. The published Trading
backend sends the token as an ordinary CONNECT password, which Event Grid never
inspects. This is a defect in the application image, not in the workshop's Azure
setup, so provisioning topic spaces, federated credentials and Event Grid data-plane
roles would all have been correct work and the broker would still have refused every
connection.
Register the in-cluster Mosquitto recipe for `Radius.Resources/mqttBrokers` on AKS,
behind a `mqttRecipeTemplatePath` override so the Event Grid recipe can be restored in
one parameter once the application supports enhanced authentication. This keeps the
change where it belongs. `iac/app.bicep` still requests the portable contract and names
no broker; only the platform team's recipe registration differs.
Swapping the recipe alone was not enough, which exposed a second defect. `app.bicep`
read `MQTT_AUTH_METHOD` and `MQTT_TOKEN_AUDIENCE` from the workload identity, and the
Azure workload-identity recipe hard-codes `authMethod: 'OAUTH2-JWT'`. The application
therefore attempted token authentication regardless of which broker recipe the
environment had registered, so the identity silently overrode the platform team's
choice. Read both from the broker instead, which is what the `mqttBrokers` resource
type documents. The expression is identical in both environments and each recipe
supplies its own answer, so K3s is unaffected; its no-op identity recipe already
reported `none`.
While here, derive `AZURE_TENANT_ID` from the identity resource rather than from the
bare override parameter. The parameter defaults to empty, so the containers were given
an empty tenant even though the recipe returns the correct one.
Guard all of this in `validate-postgres-recipes.sh`: the AKS environment must not
default `mqttBrokers` to the Event Grid recipe, and `app.bicep` must read the MQTT auth
method and audience from the broker and the tenant from the identity.
Correct the documentation that described this as a topic-space and permission-binding
gap, since that was never the cause, and that promised a reachable frontend with only
MQTT features degraded, since the deployment actually failed outright.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
… failure modes (#21) * docs(challenge-05): place orders in step 5 and verify them in Azure PostgreSQL Step 5 stopped at "open localhost:3000", so step 6 had no application data to check and the managed database was never actually queried. Step 5 now covers sign-in and asks the attendee to place several BUY orders (and optionally an uncoverable SELL) so both `processed` and `rejected` rows exist. Orders reach PostgreSQL over the backend's /api/orders endpoint. Step 6 is split per platform, because app.bicep is identical but the database it resolves to is not: - K3s keeps the in-cluster verification pod. - AKS gets an Azure-native check that reads the orders back out of Azure Database for PostgreSQL Flexible Server, which is the actual proof the data left the cluster. Verified end to end against a live Flexible Server. Fixes found while doing so: - `az postgres flexible-server firewall-rule` takes `--server-name` for the server and `--name` for the rule; `--rule-name` does not exist. - `az postgres flexible-server execute` splits `--querytext` on newlines and runs only the first fragment, failing with a misleading `column "..." does not exist`. All queries are now single-line. - Its table renderer drops a bare `id` column and native timestamp columns, so the orders query aliases `id` and casts `created_at::text`. - The recipe allowlists only AKS egress IPs, so the section opens a temporary client firewall rule and closes it again, with an in-cluster psql fallback that needs no firewall change at all. - The PowerShell K3s block piped `kubectl get job` into ConvertFrom-Json without `--output json`, which always threw. Also records that Flexible Server has no portal query editor, so the portal route is Cloud Shell running the same CLI. * docs(challenge-05): document the two failure modes that cost the most time Both were hit repeatedly while validating this challenge and neither was written down anywhere. A deployment can report a container failure that already succeeded. Radius polls the pods that already exist, so a pod left crash-looping by an earlier attempt is still inside its five-minute back-off window and reports a stale status while the replacement ReplicaSet rolls out. The give-away is that the pod hash in the error belongs to the previous attempt. Records how to confirm it from the live pods and that re-running is the fix. The AKS cluster and the PostgreSQL Flexible Server can both be stopped between sessions, and each fails unrecognisably: the cluster stops resolving its API server name, so every rad and kubectl command dies with NXDOMAIN before reaching Radius, while the database surfaces as a bare InternalServerError nested inside RecipeDeploymentFailed. Neither looks like a stopped resource. Adds the state checks, the start commands, and the kubeconfig refresh for a stale context pointing at a deleted cluster. Also links the Mosquitto explanation to the upstream defect it depends on, microsoft/adaptive-apps#44, so readers can tell when Event Grid becomes usable.
* Add Challenge 09: model, review, and deploy with Radius Canvas Replaces the custom-resources scaffold with a full student challenge and coach walkthrough built around the Radius Canvas public preview for the GitHub Copilot app. The challenge contrasts the Canvas developer path (source-derived .radius/app.bicep, recipe packs, ephemeral control plane in GitHub Actions, OIDC trust) with the platform path built in Challenges 03-08, and requires an honest assessment of what the preview does and does not solve for the non-Azure port. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Correct Challenge 09 factual errors found in review - OIDC: the federated credential subject binds repository and environment, not branch. Replace the incorrect branch-isolation claim with a three-way table and point at GitHub Environment deployment-branch rules as the actual control. - Diff analysis: modeled properties such as env vars, image references and probes are reported as modified, not unchanged. Replace the two-way table with the correct three-way split of reported, reported-but-not-judged, and not-compared. - Do not run rad on the host after stating no host rad is required; move the control-plane isolation checks into the devcontainer. - Compare AI against iac/ai.bicep, not iac/app.bicep, which keeps AI disabled. - Expect and teach the unresolved-recipe case: a generated custom type has no entry in the azure-avm pack, so plan and deploy are expected to surface it. - A shared Radius.Resources namespace is not a shared contract; require a schema-compatibility judgement rather than implying interchangeability. - Prove isolation with Azure resource snapshots and a Challenge 05 health check, not namespace separation and resource counts alone. - Scope the secret criterion to newly introduced values and add a scan of the generated model, since upstream docker-compose.yml already carries dev values. - Drop the immutable-tag and functionally-similar-recipe overclaims, add coach pre-staging guidance, and raise the estimate to 90-150 minutes. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…ve Apps readme (#23) * Target platform engineers and app developers in the Adaptive Apps readme Split the challenge sequence into two audience paths, call out that challenges 01-03 can be pre-provisioned via the Hack Console, and add diagrams for the one-application/many-platforms model. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Anchor the readme to microsoft/adaptive-apps and the Simplified Trading App Add a 'What this MicroHack builds on' table mapping every building block to its upstream source, name the Simplified Trading App as the running example, and add an Additional documentation section including the Azure Architecture Center article. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Removed references to resource types, recipe artifacts, and Radius from the Readme.
Author
|
@microsoft-github-policy-service agree |
Jan Egil Ring (janegilring)
approved these changes
Sep 5, 2026
Jan Egil Ring (janegilring)
left a comment
Collaborator
There was a problem hiding this comment.
The overall structure and documentation looks very good
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Issue #428.
This pull request introduces the Adaptive Apps MicroHack content and supporting developer environment for the Azure App Innovation scenario.
Created and validated challenges:
Challenge 00: universal prerequisite
Complete Challenge 00 before starting a challenge in any bucket, regardless of
which audience path you follow.
<- Start here
Bucket 1: Infrastructure setup
Audience: platform engineer. Prepare the target platforms and deploy the
Radius control plane.
Note
Challenges 01 - 03 can be pre-provisioned automatically through the Hack
Console. Work through them manually to learn how the platform is built, or
use the provisioned environment and continue at Challenge 04 or 05.
Bucket 2: Exploring Radius
Audience: platform engineer (Challenge 04 is also the optional entry point
for application developers). Define portable platform capabilities and
implement them with recipes.
each contract, or skip it and consume the registered recipes as-is.
Bucket 3: Portable apps across platforms
Audience: application developer (Challenge 05 also closes out the platform
engineer path). Deploy the application across environments and adapt its
identity, communication, and AI capabilities.
<- Application developer start here
Bucket 4: Advanced challenges
Audience: application developer. Bring the application model into the
developer inner loop and apply the Adaptive Apps approach to an existing
application.
Additional documentation
Adaptive Apps