January 28, 2026
The generated application compiles. The checkout button still fails because the preview deployment has no payment credential, the identity provider rejects its callback origin, the webhook points at last week's URL, and the DNS record still targets an obsolete deployment.
None of those failures are in the TypeScript repository. All of them are in the program.
Part 2 described a semantic language with domain extensions and concrete adapters. That program cannot stop at the repository boundary. A useful definition of the Storefront includes application behavior, database state, provider instances, webhook registrations, secrets, deployment settings, DNS, and the evidence that the public system actually works.
Dashboard state is an untyped, mutable side channel in the program. It is written through forms and buttons, remembered by whoever clicked them, and discovered later through failures. The browser is not the problem because it has pixels. It is the problem because it exposes state the program failed to model.
The Storefront checkout crosses at least these systems:
These are not independent setup chores. The application exports a webhook endpoint; the payment provider imports it. The deployment exports an origin; the identity provider authorizes it. DNS points to a hostname exported by the deployment. The application requires payment authority; the environment binds that capability to credentials that must never appear in model-visible state.
The repository is a convenient ownership boundary for Git. It is not a semantic boundary for the system.
Terraform, Pulumi, Kubernetes, provider APIs, secret managers, deployment platforms, and configuration systems already model external state. A new application language should not reimplement them.
Correct. It should use them as backends.
The novel claim is not that create DNS record needs a new implementation. It is that the DNS target, application origin, identity allowlist, webhook endpoint, credential scope, and health assertion belong to one typed graph. Existing infrastructure tools remain responsible for provider-specific lifecycle and planning where they are good at it.
Terraform already distinguishes configuration, state, plan, and apply. Its state documentation explains that it refreshes managed objects from real infrastructure, and its import workflow brings existing resources under management rather than recreating them. Those are exactly the semantics a model-facing planner should preserve.
The proposal adds application-aware types and end-to-end postconditions above those mechanisms. It should lower to Terraform or a provider API when possible, not compete on how many cloud resources it supports.
A model that emits a resource declaration has not created a resource.
External systems need a reconciliation loop:
This distinction prevents a coding agent from behaving like a shell script that creates everything on every run. Resource identity must survive repairs. Destructive replacement must be explicit. Importing an existing payment product is different from creating a duplicate one. A plan should expose that difference before an authorized mutation occurs.
A structured drift report is much better than a failed click:
{
"code": "WEBHOOK_TARGET_DRIFT",
"resource": "stripe.webhook.storefront-payments",
"desired": "https://store.example.com/events/payment",
"observed": "https://old-preview.example.dev/events/payment",
"owner": "environment.production",
"repair": {
"operation": "update_webhook_target",
"requires": [
"payment_account.write",
"production_approval"
]
}
}
The diagnostic says what differs, who owns the declaration, how to repair it, and what authority the repair requires. It does not silently click Save.
An environment declaration might look like this:
environment production {
bind Storefront.customers <- clerk.instance(
"storefront-production"
) {
authorized_origins = [Storefront.web.production.origin]
sign_in_route = Storefront.web.route("/sign-in")
synchronize [UserCreated, UserUpdated, UserDeleted]
-> Storefront.web.endpoint("/events/identity")
}
bind Storefront.billing <- stripe.account("storefront-live") {
product AnnualPlan {
amount = money(9900, "EUR")
recurrence = yearly
}
deliver [
CheckoutCompleted,
PaymentFailed,
SubscriptionUpdated,
SubscriptionDeleted
] -> Storefront.web.endpoint("/events/payment")
}
deploy Storefront.web as web_production {
target = vercel.project("storefront")
regions = ["fra1"]
}
domain "store.example.com" {
target = web_production.hostname
tls = required
}
}
Clerk, Stripe, and Vercel are adapters in this example, not language keywords. The compiler sees relationships that individual provider tools do not:
authorized_origins consumes the deployment's production origin.AnnualPlan connects application code to an environment-specific provider resource.If the application renames /events/payment, the planner can identify the webhook binding that becomes invalid. If production binds a test payment adapter, the type and policy layers can reject the environment before deployment.
A secret value should not be source code, model context, a plan field, a trace attribute, or an observation returned after a tool call.
secret payment_credentials = secrets.reference(
"production/storefront/stripe"
)
bind web_production.secret("PAYMENT_CREDENTIALS")
<- payment_credentials
export = write_only
The semantic program knows the identity, scope, destination, rotation policy, and export policy of the binding. It does not know the credential bytes. Preview and production use separate references. Diagnostics can report binding missing or reference denied without printing a value.
This is not only a redaction problem. Once a secret enters model-visible context, a later tool call, log, or generated patch may reproduce it. The safer design prevents the value from entering that plane at all.
Reading provider state is different from changing it. Changing a preview webhook is different from transferring a domain. A planner should classify proposed operations before asking for approval:
The agent can prepare a complete plan without receiving authority to apply every item. Approval should attach to specific semantic operations and resource identities, not to a vague instruction such as "fix deployment."
PLAN production/storefront
CREATE deployment.storefront.production
CREATE dns.record.store-example-com
CREATE payment.product.annual-plan
UPDATE payment.webhook.storefront-payments
BIND secret.production-storefront-stripe
→ deployment.storefront.PAYMENT_CREDENTIALS
VERIFY DNS resolution
VERIFY TLS certificate
VERIFY application health
VERIFY signed payment event delivery
APPROVAL REQUIRED
- Public DNS mutation
- Production secret binding
- Live payment-account mutation
The system should prefer automation in this order:
typed provider API
→ official CLI
→ browser automation with state readback
→ explicit human step
The browser remains necessary because some control planes expose no complete API, or because the API available to an organization differs from the public one. But browser automation is a fallback adapter. A successful click proves only that a click occurred. The runtime must read the resulting state back and compare it with the desired resource.
When no safe adapter exists, the compiler should produce an unresolved step. It must not invent a provider capability or declare success based on a toast message.
The plan applied without an error. That still does not mean the Storefront works.
Operational postconditions should verify that DNS resolves, TLS is valid, the identity provider accepts the origin, the webhook endpoint is reachable, a signed test event is accepted, the health endpoint returns the expected result, and preview credentials cannot reach live payment resources.
The payment details are not theoretical. Stripe's API uses idempotency keys to make retries safe, and its webhook documentation requires signature verification over the original request body. Those provider constraints should appear in adapter contracts and end-to-end assertions rather than remain facts the model has to recall on every generation.
Different checks happen at different times:
A complete program can now be compiled and deployed. It can still be wrong. The missing component is the developer's loop: run it, interact with it, observe what happened, and construct an experiment that distinguishes one hypothesis from another.
Previous: A Language Designed to Be Written by Models. Next: The Compiler Is Only Half the Programmer.