Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .github/aspire-cache/AspireSdkCache.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Aspire.Hosting" Version="13.4.6" />
<PackageReference Include="Aspire.Hosting.PostgreSQL" Version="13.4.6" />
<PackageReference Include="Aspire.Hosting.Redis" Version="13.4.6" />
<PackageReference Include="Aspire.Hosting.Browsers" Version="13.4.6-preview.1.26319.6" />
<PackageReference Include="Aspire.Hosting.CodeGeneration.TypeScript" Version="13.4.6" />
</ItemGroup>
</Project>
2 changes: 2 additions & 0 deletions .github/aspire-cache/apphost.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Cache-hydration fixture: Aspire only needs the AppHost identity during restore.
export {};
14 changes: 14 additions & 0 deletions .github/aspire-cache/aspire.config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"appHost": {
"path": "apphost.mts",
"language": "typescript/nodejs"
},
"sdk": {
"version": "13.4.6"
},
"packages": {
"Aspire.Hosting.PostgreSQL": "13.4.6",
"Aspire.Hosting.Redis": "13.4.6",
"Aspire.Hosting.Browsers": "13.4.6-preview.1.26319.6"
}
}
46 changes: 40 additions & 6 deletions .github/scripts/aspire-nuget-cache-policy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,60 @@ import { assertEquals, assertStringIncludes } from '@std/assert';
const POLICY = {
action: 'uses: actions/cache@v4',
path: 'path: ~/.nuget/packages',
key: 'key: nuget-aspire-${{ runner.os }}-13.4.6-v1',
} as const;

Deno.test('every Aspire runtime workflow persists the exact pinned NuGet package train', async () => {
for (
const [path, expectedCaches] of [
['.github/workflows/e2e-cli.yml', 2],
['.github/workflows/e2e-cli-prod.yml', 1],
['.github/workflows/e2e-cli-prod-local.yml', 1],
const [path, expectedCaches, key] of [
['.github/workflows/e2e-cli.yml', 2, 'key: nuget-aspire-${{ runner.os }}-13.4.6-v1'],
['.github/workflows/e2e-cli-prod.yml', 2, 'key: nuget-aspire-${{ runner.os }}-13.4.6-v2'],
[
'.github/workflows/e2e-cli-prod-local.yml',
1,
'key: nuget-aspire-${{ runner.os }}-13.4.6-v1',
],
] as const
) {
const source = await Deno.readTextFile(path);
assertEquals(count(source, POLICY.action), expectedCaches, path);
assertEquals(count(source, POLICY.path), expectedCaches, path);
assertEquals(count(source, POLICY.key), expectedCaches, path);
assertEquals(count(source, key), expectedCaches, path);
assertStringIncludes(source, 'ASPIRE_CLI_VERSION:');
assertStringIncludes(source, '13.4.6');
}
});

Deno.test('production E2E seeds and verifies every exact Aspire integration package before runtime', async () => {
const workflow = await Deno.readTextFile('.github/workflows/e2e-cli-prod.yml');
const project = await Deno.readTextFile('.github/aspire-cache/AspireSdkCache.csproj');
for (
const [name, version] of [
['Aspire.Hosting', '13.4.6'],
['Aspire.Hosting.PostgreSQL', '13.4.6'],
['Aspire.Hosting.Redis', '13.4.6'],
['Aspire.Hosting.Browsers', '13.4.6-preview.1.26319.6'],
['Aspire.Hosting.CodeGeneration.TypeScript', '13.4.6'],
] as const
) {
assertStringIncludes(project, `Include="${name}" Version="${version}"`);
assertStringIncludes(workflow, name.toLowerCase());
}
assertStringIncludes(workflow, 'needs: prepare-aspire-sdk-cache');
assertStringIncludes(workflow, "steps.aspire-sdk-cache.outputs.cache-hit != 'true'");
assertStringIncludes(workflow, 'Materialize pinned Aspire local package source');
assertStringIncludes(workflow, 'timeout 5s aspire restore');
assertStringIncludes(workflow, "-name '*.nupkg'");
assertStringIncludes(workflow, '-name aspire-managed');
assertStringIncludes(workflow, 'find "$HOME" -type f -name aspire-managed');
assertStringIncludes(workflow, 'quickstart-only:');
assertStringIncludes(workflow, 'inputs.quickstart-only && github.run_id || github.ref');
});

Deno.test('published E2E artifacts retain Aspire CLI diagnostics', async () => {
const workflow = await Deno.readTextFile('.github/workflows/e2e-cli-prod.yml');
assertStringIncludes(workflow, '~/.aspire/logs/cli_*.log');
});

function count(source: string, needle: string): number {
return source.split(needle).length - 1;
}
64 changes: 62 additions & 2 deletions .github/workflows/e2e-cli-prod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,61 @@ on:
description: Published @netscript/cli version to validate
required: true
type: string
quickstart-only:
description: Run only the published Quickstart walk (manual repeated-proof lane)
required: false
default: false
type: boolean

permissions:
actions: read
contents: read

concurrency:
group: e2e-cli-prod-${{ github.workflow }}-${{ github.ref }}
group: e2e-cli-prod-${{ github.workflow }}-${{ inputs.quickstart-only && github.run_id || github.ref }}
cancel-in-progress: false

jobs:
prepare-aspire-sdk-cache:
name: prepare pinned Aspire SDK cache
if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }}
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout
uses: actions/checkout@v5

- name: Setup .NET
uses: actions/setup-dotnet@v5
with:
dotnet-version: '10.0.x'

- name: Restore pinned Aspire SDK cache
id: aspire-sdk-cache
uses: actions/cache@v4
with:
path: ~/.nuget/packages
key: nuget-aspire-${{ runner.os }}-13.4.6-v2

- name: Populate exact Aspire SDK packages
if: steps.aspire-sdk-cache.outputs.cache-hit != 'true'
run: >-
dotnet restore .github/aspire-cache/AspireSdkCache.csproj
--packages "$HOME/.nuget/packages"
--source https://api.nuget.org/v3/index.json
--source https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet9/nuget/v3/index.json

- name: Verify exact Aspire SDK packages
run: |
test -f "$HOME/.nuget/packages/aspire.hosting/13.4.6/aspire.hosting.13.4.6.nupkg"
test -f "$HOME/.nuget/packages/aspire.hosting.postgresql/13.4.6/aspire.hosting.postgresql.13.4.6.nupkg"
test -f "$HOME/.nuget/packages/aspire.hosting.redis/13.4.6/aspire.hosting.redis.13.4.6.nupkg"
test -f "$HOME/.nuget/packages/aspire.hosting.browsers/13.4.6-preview.1.26319.6/aspire.hosting.browsers.13.4.6-preview.1.26319.6.nupkg"
test -f "$HOME/.nuget/packages/aspire.hosting.codegeneration.typescript/13.4.6/aspire.hosting.codegeneration.typescript.13.4.6.nupkg"

scaffold-runtime-jsr:
name: scaffold-runtime (published JSR CLI)
needs: prepare-aspire-sdk-cache
if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }}
runs-on: ubuntu-latest
timeout-minutes: 75
Expand All @@ -43,7 +86,7 @@ jobs:
uses: actions/cache@v4
with:
path: ~/.nuget/packages
key: nuget-aspire-${{ runner.os }}-13.4.6-v1
key: nuget-aspire-${{ runner.os }}-13.4.6-v2

- name: Install Aspire CLI
env:
Expand All @@ -52,6 +95,20 @@ jobs:
dotnet tool install Aspire.Cli --tool-path "$HOME/.aspire/bin" --version "$ASPIRE_CLI_VERSION"
echo "$HOME/.aspire/bin" >> "$GITHUB_PATH"

- name: Materialize pinned Aspire local package source
run: |
warmup=0
timeout 5s aspire restore \
--apphost .github/aspire-cache/apphost.mts --non-interactive --nologo || warmup=$?
test "$warmup" -eq 0 -o "$warmup" -eq 6 -o "$warmup" -eq 124
mkdir -p .llm/tmp/aspire-nuget-source
find "$HOME/.nuget/packages" -name '*.nupkg' \
-exec cp --update=none '{}' .llm/tmp/aspire-nuget-source/ \;
find "$HOME" -type f -name aspire-managed -print -quit \
> .llm/tmp/aspire-managed-path.txt
test -s .llm/tmp/aspire-managed-path.txt
sed 's|^|aspire-managed: |' .llm/tmp/aspire-managed-path.txt

- name: Download published version
if: github.event_name == 'workflow_run'
uses: actions/download-artifact@v5
Expand All @@ -78,6 +135,7 @@ jobs:
echo "version=$version" >> "$GITHUB_OUTPUT"

- name: Wait for JSR propagation
if: ${{ github.event_name != 'workflow_dispatch' || inputs.quickstart-only != true }}
run: |
echo "sleeping 120s so just-published @netscript/cli@${{ steps.version.outputs.version }} and deps become resolvable on JSR before install."
sleep 120
Expand Down Expand Up @@ -106,6 +164,7 @@ jobs:
test -f .llm/tmp/cli-prod-init-smoke/deno.json

- name: Full scaffold runtime E2E (published CLI, one pass)
if: ${{ github.event_name != 'workflow_dispatch' || inputs.quickstart-only != true }}
run: |
deno task e2e:cli run scaffold.runtime \
--source jsr \
Expand Down Expand Up @@ -153,4 +212,5 @@ jobs:
.llm/tmp/cli-e2e-prod.ndjson
.llm/tmp/quickstart-walk-prod-report.json
.llm/tmp/quickstart-walk-prod.ndjson
~/.aspire/logs/cli_*.log
if-no-files-found: ignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Context pack — #1227 reopened restore stability

PR #1297 fixed the 30-minute failure budget but not the intermittent restore cancellation.
Run 30961102523 failed canary.10 quickstart restore after 180.1s, named an Aspire CLI log, and then
discarded it. Current slice captures that log first. Do not implement broad retry/cache guesses;
read the cloud log, then lock the exact predicate, package paths, and consecutive-run count.

Identical retained local logs prove the operation is Aspire's bundled NuGet restore of five exact
integration packages. The v1 cache missed on both cloud runs and could not save from a red job. S2
uses a prerequisite seed/save job under v2, verifies every package, and retries only the exact exit-6
two-marker signature. Completion requires three consecutive green branch runs.
14 changes: 14 additions & 0 deletions .llm/runs/fix-aspire-restore-stability-1227--1227/drift.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Drift — #1227 reopened restore stability

- Owner D6 ruling replaces the default local PLAN-EVAL with composed draft→ready augmentation,
OpenHands label, and orchestrator pre-merge gate.
- Issue body retains the original three checked boxes; the owner reopening comment supplies four
new unchecked acceptance rows and is authoritative for this continuation.
- The cloud diagnostic run was still in progress when identical retained local logs closed the
root-cause decision. Implementation proceeded from those logs; the branch artifact remains a
required corroboration before ready state.
- Proof run 30962998528 cleared restore in 22.58s from a confirmed v2 cache hit, then exposed that
Quickstart step 7 reused the runtime suite's database-aware health assertion. The generated users
service correctly returned healthy with no DB check, so the walk could not become green. The
suite-only call now requests service health while the runtime gate continues passing an explicit
database and retains its stronger assertion.
51 changes: 51 additions & 0 deletions .llm/runs/fix-aspire-restore-stability-1227--1227/plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Plan — #1227 reopened restore stability

## Profile and gates

- Surface: Archetype 6 CLI/tooling E2E and release workflow.
- Required: focused unit/policy tests, scoped check/lint/fmt, workflow YAML parse, live branch
diagnostic run, then N consecutive published-canary `quickstart.walk` runs.
- Release class: required because the published CLI quickstart gate changes.
- JSR audit: N/A; no package public export is planned.

## Locked decisions

1. Preserve PR #1297's bounded timeout and infrastructure classification.
2. Land diagnostic artifact capture before changing retry/cache behavior; diagnosis controls scope.
3. Retry only the exact observed restore failure class, with a finite total budget.
4. Cache exact pinned inputs and assert cache population, not merely cache-step syntax.
5. A single green run is not completion evidence.

## Commit slices

1. Diagnostic capture — upload Aspire CLI logs on every production E2E outcome; policy test; branch
workflow run supplies the log.
2. Root-cause mitigation — signature-specific retry and exact cache correction chosen from slice 1
evidence; focused negative/positive tests.
3. Repeated proof — N consecutive published-canary walks green; final evidence and ready handoff.

## Risk register

- Artifact paths may not expand `~`: workflow policy and live artifact inspection prove them.
- Broad retries could mask product failures: retry predicate is exact and unit-tested negative-first.
- Cache could be present but irrelevant: diagnostic logs and explicit package-presence assertions
bind it to the restore operation.
- Cloud flake may not reproduce immediately: keep diagnostic upload always-on and do not claim
completion without the consecutive-run bar.

## Open-decision sweep

- Consecutive-run count: three; enough to reject the observed alternating/lucky-single-pass pattern
while keeping the p0 proof bounded.
- Log retention duration: safe to defer to Actions defaults.

## Deferred scope

- Aspire CLI upstream repair; this lane makes the NetScript quickstart resilient and diagnosable.

## Slice 2 decision

- Cache preparation is a separate prerequisite job so its post-save completes before product E2E
begins. It restores and verifies all five exact packages under a new v2 key.
- Quickstart restore retries once only when exit code is 6 and stderr contains both observed Aspire
preparation-cancellation markers. Timeouts, product exit codes, and partial matches do not retry.
39 changes: 39 additions & 0 deletions .llm/runs/fix-aspire-restore-stability-1227--1227/research.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Research — #1227 reopened restore stability

## Re-baseline

PR #1297 merged the bounded-failure half: `runtime.aspire-restore` now has three 180-second
attempts, infrastructure classification, and an exact Aspire 13.4.6 NuGet cache key. Preserve it.

## Findings

1. Issue owner evidence has two consecutive `quickstart.walk` runs against canary.10: one passed
Aspire restore/start in 22.3s; one failed restore after 180.1s with exit 6 and `A task was
canceled`.
2. Failed run 30961102523 names `/home/runner/.aspire/logs/cli_20260804T235623_1b62993a.log`, but
artifact 8913213616 contains only four suite report/log files. Root-cause evidence was discarded.
3. The failed report says `retried: false`: quickstart step 4 invokes `runBoundedAspireWalk` once,
and that helper invokes restore once.
4. The workflow cache step covers `~/.nuget/packages`, but its instant success does not prove which
Aspire SDK packages were present. The CLI log must identify the actual restore path before the
cache policy changes.
5. The same workflow's preceding `scaffold.runtime` run also reported a prebuilt AppHost preparation
failure, strengthening the hypothesis that the failure is below NetScript product code.

## Open questions

- Which operation is canceled in the Aspire CLI log: NuGet/feed, prebuilt AppHost preparation,
certificate setup, or another CLI operation?
- Which exact package/cache paths must be pinned to make the successful path independent of a cold
feed?
- What consecutive-run count is feasible and sufficient? Lock after observing the diagnostic run.

## Root-cause evidence

The two retained local Aspire logs for the byte-identical signature
(`cli_20260804T092654_7dc37fe7.log` and `cli_20260804T094300_8a06ef75.log`) both stop inside
`BundleNuGetService.RestorePackagesAsync`. The command is the bundled `aspire-managed nuget restore`
for five exact packages: Hosting, PostgreSQL, Redis, Browsers preview, and TypeScript codegen. No
container operation has begun. Runs 30959430176 and 30961102523 both report a cache miss for the v1
key; because each overall job failed, the cache post-save never established that key. Classification:
NuGet/feed latency amplified by a cache lifecycle defect.
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Supervisor — #1227 reopened restore stability

- Route: openai · gpt-5.6-sol · medium
- Worktree: `/home/codex/repos/ns005-quickwins`
- Branch: `fix/aspire-restore-stability-1227`
- Baseline: `00f96af76e5825422e8bc716a9c27d4c13e16f7f`
- Harness ruling: D6 composed draft→ready evaluation; no local PLAN-EVAL
- Evaluator/merge authority: orchestrator pre-merge gate

Loading
Loading