Dogfood proof — Aspire Deno runtime (Layers A + B) against the full NetScript framework
This issue is a dogfood-evidence record for the .NET Aspire team. It proves that both of the
Deno-runtime fork branches (rickylabs/aspire) work end-to-end against the heavy, complete NetScript
framework — by actually swapping NetScript's scaffold/host wiring onto them locally and booting real
scaffold output under the fork Aspire CLI. Evidence only — the dogfood branch is never merged.
Fork branches under test (rickylabs/aspire)
Dogfood branch (NetScript, evidence-only — NOT merged)
dogfood/aspire-deno-runtime — commits:
b704d839 feat(cli/scaffold): run TS AppHost under the Aspire fork Deno toolchain resolver [Layer A]
75e4dea7 feat(cli/aspire): host Deno services and plugins via the fork's addDenoApp [Layer B]
cb2c3e98 test(dogfood): self-contained re-runnable targeted proof harness [crux proof]
What was swapped
Layer A (packages/cli/src/kernel/application/scaffold/render-ts-apphost.ts): the scaffolder now
emits a single aspire/deno.json (with "unstable":["sloppy-imports"] + JSR/subpath import map) and
no package.json / tsconfig.apphost.json. Removing package.json is what makes the fork's
resolver select the Deno toolchain path for apphost.mts.
Layer B (generate-register-services.ts, generate-register-plugins.ts + templates,
generate-aspire-config.ts, scaffold-aspire.ts, packages/aspire/.../resolve-env-vars.ts):
NetScript services and plugins are now hosted via builder.addDenoApp(name, workdir, entrypoint)
instead of addExecutable. Aspire.Hosting.JavaScript is registered in the packages map (empty
version → fork dev-mode source resolution). OTel switched to buildOtelEnvVars(name, ver, 'denoApp')
(3 env vars: OTEL_DENO=true + service name + service.version) and the explicit withOtlpExporter()
was dropped — the fork's WithDenoDefaults() wires OTLP natively.
Live boot proof (fork Aspire CLI, DB-less scaffold)
Booted real scaffold output under the fork CLI (dev mode via ASPIRE_REPO_ROOT, fork build at
13.5.0-dev): aspire restore exit 0 → deno check apphost.mts exit 0 → aspire start --isolated.
- Layer A verified:
aspire ps shows apphost.mts running under sdk 13.5.0-dev; aspire/deno.json present, no aspire/package.json.
- Layer B verified: service resource
source: "deno", appArgs: ["run","-A","src/main.ts"], state Running, health Healthy; live GET /health → HTTP 200 {"status":"healthy","version":"1.0.0"}.
- OTel verified (zero app-side SDK):
OTEL_DENO=true present (denoApp 3-var mode); the scaffolded service imports no OpenTelemetry SDK — telemetry is native via the fork's env-only bridge.
Validation (raw exit codes)
Docker-independent gates — all green:
deno check (packages/cli/src/kernel, 268 files): 0 failures; (packages/aspire/src, 31 files): 0 failures.
- generator unit tests (aspire register generators): 13 passed / 122 steps / 0 failed.
- packages/aspire unit tests (denoApp OTel mode): 18 passed / 51 steps / 0 failed.
- targeted proof harness (
.llm/tools/dogfood/prove-aspire-deno-runtime.ts, fresh run): OVERALL PASS — 12/12 assertions (8/8 Layer A/B), raw exit 0.
The targeted harness scaffolds a fresh DB-less NetScript project (--db none, a users
addDenoApp HTTP service), boots it under the fork CLI in dev mode, and asserts (parsing resource
JSON, not prose): Layer A (aspire/deno.json present + package.json absent, deno check exit 0,
sdkVersion=13.5.0-dev, apphost running); Layer B (codegen emits addDenoApp +
buildOtelEnvVars(...,'denoApp') with no withOtlpExporter; live resource source=deno,
appArgs=["run","-A","src/main.ts"], Running/Healthy); service discovery (URL resolved from
urls[].url, not hardcoded — --isolated randomizes the target PORT) → GET /health = 200; OTel
(the 3 denoApp vars OTEL_DENO=true / OTEL_SERVICE_NAME=users /
OTEL_RESOURCE_ATTRIBUTES=service.version=1.0.0 plus fork-injected OTEL_EXPORTER_OTLP_ENDPOINT
proving WithDenoDefaults() wires OTLP natively, and the service source imports zero
@opentelemetry SDK). Live-trace assertion via the dashboard telemetry API was intentionally skipped
(headless dashboard unavailable) — the env + zero-SDK proof is the accepted fallback.
Full scaffold.runtime merge-readiness E2E (warm Docker — Docker note corrected)
The earlier "Docker unavailable" caveat is superseded: Docker Desktop was updated (4.80.0, server
29.6.1, docker ps exit 0) and the full one-pass suite
deno task e2e:cli run scaffold.runtime --cleanup --format pretty was run twice.
Verdict (both runs): 11 passed / 1 failed.
- All 11 swap-relevant steps green — scaffold init (incl.
apphost.mts generation +
deno check type-check), the 4 first-party plugin installs (workers/sagas/triggers/streams),
plugin list, aspire restore, aspire start, aspire stop. This exercises both Layer A (TS
AppHost under the Deno toolchain resolver) and Layer B (addDenoApp services/plugins) in the
real merge-readiness harness, not just the targeted crux.
- The lone failure is
database.init, timing out at ~900030ms — the NetScript
DbOperationRunner's own DEFAULT_TIMEOUT_MS = 15*60_000 (900s) cap.
Triage — swap-orthogonal, pre-existing flake (not a Layer A/B defect): netscript db init does
not boot the swapped app graph. It routes through the generated .helpers/db-cli-mode.mts
AppHost short-circuit, whose target set is built exclusively from
infrastructure.databases.get(...) (see generate-db-cli-mode.ts) — it starts only the database
resource, and the DB resources were kept on addExecutable (never swapped to addDenoApp). So
the swapped addDenoApp services/plugins are never instantiated during db init; the timeout is in
the untouched Prisma/DB path. The consistent 900s hang across both warm-Docker runs matches the
documented pre-existing Prisma schema-engine db-init flake (#98/#145, bounded-retry in
migrate.ts), not the crash-and-clear signature and not the swap. Fork/swap surface is therefore
fully validated by the 11 green steps + the 12/12 crux boot above.
Node-free (ask #5): the boot path is Deno-only by construction — fork publishes denoland/deno:2
for both build+runtime stages, NetScript codegen emits only deno (zero addNodeApp/addBunApp/
node/npm), and the live crux boot shows every app resource as a deno run -A process
(source=deno). docker ps during the run carried only infra containers, no Node app container.
(Host ambient Node processes are the dev tooling — Claude Code itself runs on Node — not NetScript's
Aspire graph.) Full write-up: node-free-proof.md.
Dogfood feedback for the Aspire team (addDenoApp surface gaps)
addDenoApp's fixed deno run -A <script> cannot express several flags NetScript relies on, so those
resources stayed on addExecutable:
- background (workers/sagas/triggers): need
--unstable-worker-options before the script path.
- apps (
deno task dev), tools, db-cli: task-form and/or extra flags.
- Swapped services/plugins additionally lose
--node-modules-dir=none, --minimum-dependency-age=0,
--config, and granular permissions (addDenoApp uses -A and auto-discovers deno.json).
Recommendation: let AddDenoApp accept caller-supplied Deno flags / permission scoping (or a
WithArgs-style escape hatch before the script path) so worker-options and granular perms are
expressible without falling back to addExecutable.
Proof for the Aspire team that Layers A + B hold up against a real, heavy polyglot framework.
Dogfood proof — Aspire Deno runtime (Layers A + B) against the full NetScript framework
This issue is a dogfood-evidence record for the .NET Aspire team. It proves that both of the
Deno-runtime fork branches (rickylabs/aspire) work end-to-end against the heavy, complete NetScript
framework — by actually swapping NetScript's scaffold/host wiring onto them locally and booting real
scaffold output under the fork Aspire CLI. Evidence only — the dogfood branch is never merged.
Fork branches under test (rickylabs/aspire)
Layer A — TypeScript-AppHost Deno toolchain resolver
compare: microsoft/aspire@main...rickylabs:aspire:feat/deno-typescript-apphost-toolchain
The Aspire CLI runs
apphost.mtsunder Deno (not Node). Detection keys ondeno.lock/deno.json(c), not thepackageManagerfield. Vectors:deno install,deno check {f},deno run -A {f},deno run -A --check --watch {f},deno task --cwd <dir> <script>.Layer B —
AddDenoApp/DenoAppResource(Aspire.Hosting.JavaScript)compare: microsoft/aspire@main...rickylabs:aspire:feat/deno-hosting-adddenoapp
deno run -Adeny-by-default;OTEL_DENO=truenative OTel (zero app-side SDK);denoland/deno:2single-stage publish;type:"deno"debug config.Dogfood branch (NetScript, evidence-only — NOT merged)
dogfood/aspire-deno-runtime— commits:b704d839feat(cli/scaffold): run TS AppHost under the Aspire fork Deno toolchain resolver [Layer A]75e4dea7feat(cli/aspire): host Deno services and plugins via the fork's addDenoApp [Layer B]cb2c3e98test(dogfood): self-contained re-runnable targeted proof harness [crux proof]What was swapped
Layer A (
packages/cli/src/kernel/application/scaffold/render-ts-apphost.ts): the scaffolder nowemits a single
aspire/deno.json(with"unstable":["sloppy-imports"]+ JSR/subpath import map) andno
package.json/tsconfig.apphost.json. Removingpackage.jsonis what makes the fork'sresolver select the Deno toolchain path for
apphost.mts.Layer B (
generate-register-services.ts,generate-register-plugins.ts+ templates,generate-aspire-config.ts,scaffold-aspire.ts,packages/aspire/.../resolve-env-vars.ts):NetScript services and plugins are now hosted via
builder.addDenoApp(name, workdir, entrypoint)instead of
addExecutable.Aspire.Hosting.JavaScriptis registered in the packages map (emptyversion → fork dev-mode source resolution). OTel switched to
buildOtelEnvVars(name, ver, 'denoApp')(3 env vars:
OTEL_DENO=true+ service name + service.version) and the explicitwithOtlpExporter()was dropped — the fork's
WithDenoDefaults()wires OTLP natively.Live boot proof (fork Aspire CLI, DB-less scaffold)
Booted real scaffold output under the fork CLI (dev mode via
ASPIRE_REPO_ROOT, fork build at13.5.0-dev):aspire restoreexit 0 →deno check apphost.mtsexit 0 →aspire start --isolated.aspire psshowsapphost.mtsrunning under sdk 13.5.0-dev;aspire/deno.jsonpresent, noaspire/package.json.source: "deno",appArgs: ["run","-A","src/main.ts"], state Running, health Healthy; liveGET /health→ HTTP 200{"status":"healthy","version":"1.0.0"}.OTEL_DENO=truepresent (denoApp 3-var mode); the scaffolded service imports no OpenTelemetry SDK — telemetry is native via the fork's env-only bridge.Validation (raw exit codes)
Docker-independent gates — all green:
deno check(packages/cli/src/kernel, 268 files): 0 failures; (packages/aspire/src, 31 files): 0 failures..llm/tools/dogfood/prove-aspire-deno-runtime.ts, fresh run): OVERALL PASS — 12/12 assertions (8/8 Layer A/B), raw exit 0.The targeted harness scaffolds a fresh DB-less NetScript project (
--db none, ausersaddDenoAppHTTP service), boots it under the fork CLI in dev mode, and asserts (parsing resourceJSON, not prose): Layer A (
aspire/deno.jsonpresent +package.jsonabsent,deno checkexit 0,sdkVersion=13.5.0-dev, apphostrunning); Layer B (codegen emitsaddDenoApp+buildOtelEnvVars(...,'denoApp')with nowithOtlpExporter; live resourcesource=deno,appArgs=["run","-A","src/main.ts"], Running/Healthy); service discovery (URL resolved fromurls[].url, not hardcoded —--isolatedrandomizes the targetPORT) →GET /health= 200; OTel(the 3 denoApp vars
OTEL_DENO=true/OTEL_SERVICE_NAME=users/OTEL_RESOURCE_ATTRIBUTES=service.version=1.0.0plus fork-injectedOTEL_EXPORTER_OTLP_ENDPOINTproving
WithDenoDefaults()wires OTLP natively, and the service source imports zero@opentelemetrySDK). Live-trace assertion via the dashboard telemetry API was intentionally skipped(headless dashboard unavailable) — the env + zero-SDK proof is the accepted fallback.
Full
scaffold.runtimemerge-readiness E2E (warm Docker — Docker note corrected)The earlier "Docker unavailable" caveat is superseded: Docker Desktop was updated (4.80.0, server
29.6.1,
docker psexit 0) and the full one-pass suitedeno task e2e:cli run scaffold.runtime --cleanup --format prettywas run twice.Verdict (both runs): 11 passed / 1 failed.
apphost.mtsgeneration +deno checktype-check), the 4 first-party plugin installs (workers/sagas/triggers/streams),plugin list,
aspire restore,aspire start,aspire stop. This exercises both Layer A (TSAppHost under the Deno toolchain resolver) and Layer B (
addDenoAppservices/plugins) in thereal merge-readiness harness, not just the targeted crux.
database.init, timing out at ~900030ms — the NetScriptDbOperationRunner's ownDEFAULT_TIMEOUT_MS = 15*60_000(900s) cap.Triage — swap-orthogonal, pre-existing flake (not a Layer A/B defect):
netscript db initdoesnot boot the swapped app graph. It routes through the generated
.helpers/db-cli-mode.mtsAppHost short-circuit, whose target set is built exclusively from
infrastructure.databases.get(...)(seegenerate-db-cli-mode.ts) — it starts only the databaseresource, and the DB resources were kept on
addExecutable(never swapped toaddDenoApp). Sothe swapped
addDenoAppservices/plugins are never instantiated duringdb init; the timeout is inthe untouched Prisma/DB path. The consistent 900s hang across both warm-Docker runs matches the
documented pre-existing Prisma schema-engine db-init flake (#98/#145, bounded-retry in
migrate.ts), not the crash-and-clear signature and not the swap. Fork/swap surface is thereforefully validated by the 11 green steps + the 12/12 crux boot above.
Node-free (ask #5): the boot path is Deno-only by construction — fork publishes
denoland/deno:2for both build+runtime stages, NetScript codegen emits only
deno(zeroaddNodeApp/addBunApp/node/npm), and the live crux boot shows every app resource as adeno run -Aprocess(
source=deno).docker psduring the run carried only infra containers, no Node app container.(Host ambient Node processes are the dev tooling — Claude Code itself runs on Node — not NetScript's
Aspire graph.) Full write-up:
node-free-proof.md.Dogfood feedback for the Aspire team (addDenoApp surface gaps)
addDenoApp's fixeddeno run -A <script>cannot express several flags NetScript relies on, so thoseresources stayed on
addExecutable:--unstable-worker-optionsbefore the script path.deno task dev), tools, db-cli: task-form and/or extra flags.--node-modules-dir=none,--minimum-dependency-age=0,--config, and granular permissions (addDenoApp uses-Aand auto-discoversdeno.json).Recommendation: let
AddDenoAppaccept caller-supplied Deno flags / permission scoping (or aWithArgs-style escape hatch before the script path) so worker-options and granular perms areexpressible without falling back to
addExecutable.Proof for the Aspire team that Layers A + B hold up against a real, heavy polyglot framework.