Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
4e9a82f
[WEB.40] Add the .NET 10 solution skeleton and NuGet admission records
deku2026 Oct 8, 2026
61e03c3
[WEB.40] Static C# Site on the first-party HtmlRenderer, with CON.07 …
deku2026 Oct 8, 2026
e01b6d2
[WEB.40] Admit the Blazor WebAssembly profile closure (web-40-admissi…
deku2026 Oct 8, 2026
4727419
[WEB.40] Blazor WebAssembly Account and Chat profiles and the Operati…
deku2026 Oct 8, 2026
be0d916
[WEB.40] Ports and negative tests for the Blazor profiles, and the lo…
deku2026 Oct 8, 2026
dc46b23
[WEB.40] Convert the canonical-host Worker to TypeScript and close th…
deku2026 Oct 8, 2026
1d635df
[WEB.40] Successor browser profile r11 and admission receipt r4 for t…
deku2026 Oct 8, 2026
cb9d86f
[WEB.40] Site build command in the C# tooling, with successor receipt…
deku2026 Oct 8, 2026
22918e3
[WEB.40] CI for the C# Site and Blazor profiles, with successor recei…
deku2026 Oct 8, 2026
a8592c1
[WEB.40] C# candidate seal and verifier, Blazor profile bundle and No…
deku2026 Oct 8, 2026
f95a655
[WEB.40] Deploy the C# candidate in CI, with successor receipt web-40…
deku2026 Oct 8, 2026
80900a1
[WEB.40] Port the GOV.11 policy suite to C# with refusal and accepted…
deku2026 Oct 8, 2026
dfbc8be
[WEB.40] Parity record for the C# public Site, with its stylesheet by…
deku2026 Oct 8, 2026
c5a5f2e
[WEB.40] Blazor profile budgets, the framework cache rule and the too…
deku2026 Oct 8, 2026
2a5e17f
[WEB.40] Retire the npm Contracts packages for NuGet identity and ven…
deku2026 Oct 8, 2026
3209f54
[WEB.40] Blazor migration U5: retire the React and npm test toolchain…
deku2026 Oct 8, 2026
c6c6a12
[WEB.40] U5 validation fixes: C# editorconfig indent, root npm worksp…
deku2026 Oct 9, 2026
e1e3571
[WEB.40] U5 validation fix 2: tooling test visibility moves into sour…
deku2026 Oct 9, 2026
d60a406
[WEB.40] U5 docs: the public Site pages are static and the Blazor App…
deku2026 Oct 9, 2026
c002eea
[WEB.40] Review fixes: the source job builds the C# tool before the c…
deku2026 Oct 9, 2026
1f8f4a7
[WEB.40] Successor admission receipt web-40-admission-r11 for the CI …
deku2026 Oct 9, 2026
d436642
[WEB.40] Record the open focus-placement gap after Send and Sign out …
deku2026 Oct 9, 2026
109ba2f
[WEB.40] Keep the focused Send, Sign out and Try again controls in th…
deku2026 Oct 9, 2026
d25adcd
[WEB.40] Record the byte parity gate result against the React prerend…
deku2026 Oct 9, 2026
33c57df
[WEB.40] Persistent aria-disabled Chat Cancel (option a) with bUnit t…
deku2026 Oct 9, 2026
01246e8
[WEB.40] Refresh the Ui.Tests, App.Tests and Operations.Tests lock fi…
deku2026 Oct 9, 2026
f6c2610
[WEB.40] Fix the parity anchor check and the axe result counts in the…
deku2026 Oct 9, 2026
0781769
[WEB.40] Match the React stylesheet pair in the parity file-set gate …
deku2026 Oct 9, 2026
49f72bf
[WEB.40] Make the error banner Dismiss a named keyboard button, raise…
deku2026 Oct 9, 2026
73173c2
[WEB.40] Put the error banner message and Reload link in a role=alert…
deku2026 Oct 9, 2026
58f44c4
[WEB.40] Record the WEB.40 accessibility re-proof at 73173c2 with per…
deku2026 Oct 9, 2026
6ff8e1e
[WEB.40] Successor admission web-40-admission-r14: one nuspec digest …
deku2026 Oct 9, 2026
3dc2be6
[WEB.40] Read the local candidate identity without CI variables in th…
deku2026 Oct 9, 2026
eb927b9
[WEB.40] Expect the browser-wasm runtime pack from the SDK packs fold…
deku2026 Oct 9, 2026
389b9fd
[WEB.40] Allow the reviewed public schema digest in the Contracts pub…
deku2026 Oct 9, 2026
efe7872
[WEB.40] Successor admission web-40-admission-r15: record the Contrac…
deku2026 Oct 9, 2026
4cca32e
[WEB.40] Seal a deterministic Site archive and keep the shell encodin…
deku2026 Oct 9, 2026
c58e528
[WEB.40] Read the Site archive without ls in the main-push release an…
deku2026 Oct 9, 2026
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
4 changes: 4 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,9 @@ indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true

[*.cs]
indent_size = 4
tab_width = 4

[*.md]
trim_trailing_whitespace = false
36 changes: 11 additions & 25 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,39 +8,25 @@ updates:
open-pull-requests-limit: 5
versioning-strategy: increase
groups:
react-and-router:
patterns:
[
"react",
"react-dom",
"react-router",
"@react-router/*",
"@types/react",
"@types/react-dom",
]
contracts:
patterns: ["@arcforges/*", "@bufbuild/*", "@connectrpc/*"]
patterns: ["@arcforges/*"]
cloudflare:
patterns: ["wrangler", "@cloudflare/*"]
tooling:
patterns: ["*"]
exclude-patterns:
[
"react",
"react-dom",
"react-router",
"@react-router/*",
"@types/react",
"@types/react-dom",
"@arcforges/*",
"@bufbuild/*",
"@connectrpc/*",
"wrangler",
"@cloudflare/*",
]
exclude-patterns: ["@arcforges/*", "wrangler", "@cloudflare/*"]
ignore:
- dependency-name: "@types/node"
versions: [">=25"]
- package-ecosystem: nuget
directory: /
schedule:
interval: weekly
day: monday
open-pull-requests-limit: 5
groups:
nuget:
patterns: ["*"]
- package-ecosystem: github-actions
directory: /
schedule:
Expand Down
211 changes: 200 additions & 11 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,30 @@ concurrency:
env:
CI: "true"
WRANGLER_SEND_METRICS: "false"
DOTNET_CLI_TELEMETRY_OPTOUT: "1"
DOTNET_NOLOGO: "1"
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: "1"
# The C# product projects. The browser test project (tests/browser) is local opt-in tooling and is never restored
# or built in CI (validation policy). win.slnx is not used here: the explicit project list is the CI authority.
CSHARP_PROJECTS: >-
src/ArcForges.Web.Ui/ArcForges.Web.Ui.csproj
src/ArcForges.Web.Site/ArcForges.Web.Site.csproj
src/ArcForges.Web.App/ArcForges.Web.App.csproj
src/ArcForges.Web.Operations/ArcForges.Web.Operations.csproj
tools/ArcForges.Web.Tooling/ArcForges.Web.Tooling.csproj
tests/ArcForges.Web.Tooling.Tests/ArcForges.Web.Tooling.Tests.csproj
tests/ArcForges.Web.Site.Tests/ArcForges.Web.Site.Tests.csproj
tests/ArcForges.Web.Ui.Tests/ArcForges.Web.Ui.Tests.csproj
tests/ArcForges.Web.App.Tests/ArcForges.Web.App.Tests.csproj
tests/ArcForges.Web.Operations.Tests/ArcForges.Web.Operations.Tests.csproj
tests/ArcForges.Web.Policy.Tests/ArcForges.Web.Policy.Tests.csproj
CSHARP_TEST_PROJECTS: >-
tests/ArcForges.Web.Tooling.Tests/ArcForges.Web.Tooling.Tests.csproj
tests/ArcForges.Web.Site.Tests/ArcForges.Web.Site.Tests.csproj
tests/ArcForges.Web.Ui.Tests/ArcForges.Web.Ui.Tests.csproj
tests/ArcForges.Web.App.Tests/ArcForges.Web.App.Tests.csproj
tests/ArcForges.Web.Operations.Tests/ArcForges.Web.Operations.Tests.csproj
tests/ArcForges.Web.Policy.Tests/ArcForges.Web.Policy.Tests.csproj

jobs:
source:
Expand All @@ -39,6 +63,19 @@ jobs:
cache: ${{ matrix.os == 'ubuntu-latest' && 'npm' || '' }}
package-manager-cache: false
- run: npm ci --ignore-scripts
# The candidate tests (tests/provenance/csharp-candidate.test.ts) run the built C# tool. Its Release build is produced
# here from the locked restore, so this job never depends on stale bin/ output from an earlier local run.
- name: Set up the pinned .NET SDK for the candidate tests
if: runner.os == 'Linux'
uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6
with:
dotnet-version: "10.0.401"
- name: Build the C# tool for the candidate tests
if: runner.os == 'Linux'
run: |
set -euo pipefail
dotnet restore tools/ArcForges.Web.Tooling/ArcForges.Web.Tooling.csproj --locked-mode
dotnet build tools/ArcForges.Web.Tooling/ArcForges.Web.Tooling.csproj --no-restore -c Release
- run: npm run check
if: runner.os == 'Linux'
- name: Set up the IDE project evaluation toolchain
Expand All @@ -57,7 +94,80 @@ jobs:
path: |
artifacts/evidence/licence-*.json
artifacts/evidence/source-provenance.json
artifacts/evidence/browser-graph.json
if-no-files-found: error
retention-days: 30

csharp:
name: C# restore, build, test and publish (${{ matrix.os }})
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
timeout-minutes: 45
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
fetch-depth: 0
persist-credentials: false
- name: Set up the pinned .NET SDK
uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6
with:
dotnet-version: "10.0.401"
- name: Restore with the locked NuGet closure
run: |
set -euo pipefail
for project in $CSHARP_PROJECTS; do
dotnet restore "$project" --locked-mode
done
# The formatting gate of the C# sources (WEB.40 U5 replaces the removed Prettier and Biome gates). The text rules
# (final newline, no trailing whitespace) of the audited C#, MSBuild, npm and Worker inputs are enforced by the
# policy suite in the Test step, and the Worker type check runs in the source job (npm run typecheck).
- name: Check the C# formatting gate
run: |
set -euo pipefail
for project in $CSHARP_PROJECTS; do
dotnet format "$project" --verify-no-changes --no-restore
done
- name: Build
run: |
set -euo pipefail
for project in $CSHARP_PROJECTS; do
dotnet build "$project" --no-restore -c Release
done
- name: Test (no browser E2E; the browser project is not built here)
run: |
set -euo pipefail
for project in $CSHARP_TEST_PROJECTS; do
dotnet test "$project" --no-build -c Release
done
- name: Publish the Account and Chat profiles
run: |
set -euo pipefail
dotnet publish src/ArcForges.Web.App/ArcForges.Web.App.csproj --no-restore -c Release -o artifacts/profiles/app
- name: Publish the Operations profile skeleton
run: |
set -euo pipefail
dotnet publish src/ArcForges.Web.Operations/ArcForges.Web.Operations.csproj --no-restore -c Release -o artifacts/profiles/operations
- name: Build the static Site twice and compare the bytes
run: |
set -euo pipefail
tool=tools/ArcForges.Web.Tooling/bin/Release/net10.0/ArcForges.Web.Tooling.dll
dotnet "$tool" site build --out artifacts/site --source-ref "$GITHUB_SHA"
dotnet "$tool" site build --out artifacts/site-repeat --source-ref "$GITHUB_SHA"
diff -r artifacts/site artifacts/site-repeat
rm -rf artifacts/site-repeat
- name: Retain the C# candidate outputs
if: runner.os == 'Linux'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: web-csharp-${{ github.run_id }}-${{ github.run_attempt }}
path: |
artifacts/site
artifacts/profiles
if-no-files-found: error
retention-days: 30

Expand Down Expand Up @@ -109,22 +219,38 @@ jobs:
strategy:
fail-fast: false
matrix:
language: [javascript-typescript, actions]
language: [javascript-typescript, actions, csharp]
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
fetch-depth: 0
persist-credentials: false
- name: Set up the pinned .NET SDK for CodeQL
if: matrix.language == 'csharp'
uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6
with:
dotnet-version: "10.0.401"
- uses: github/codeql-action/init@1c5b675653bb5c22dbe9b12b556ec555138e09fd # v4
with:
languages: ${{ matrix.language }}
build-mode: none
# C# is built explicitly with the locked restore below, so CodeQL observes the real compilation.
build-mode: ${{ matrix.language == 'csharp' && 'manual' || 'none' }}
- name: Restore and build the C# projects for CodeQL
if: matrix.language == 'csharp'
run: |
set -euo pipefail
for project in $CSHARP_PROJECTS; do
dotnet restore "$project" --locked-mode
done
for project in $CSHARP_PROJECTS; do
dotnet build "$project" --no-restore -c Release
done
- uses: github/codeql-action/analyze@1c5b675653bb5c22dbe9b12b556ec555138e09fd # v4
with:
category: /language:${{ matrix.language }}

candidate:
name: Build static candidate
name: Build and verify the C# static candidate and profile bundle
needs: [source, quality]
runs-on: ubuntu-latest
timeout-minutes: 20
Expand All @@ -141,18 +267,58 @@ jobs:
node-version-file: .node-version
cache: npm
- run: npm ci --ignore-scripts
- run: npm run build
- name: Build, measure and gate the Account and Chat production profiles
run: npm run build:profiles
- name: Set up the pinned .NET SDK
uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6
with:
dotnet-version: "10.0.401"
# WEB.40 deploy switch: the candidate is the C# static Site (tools/ArcForges.Web.Tooling, candidate build), sealed and
# verified before upload. The Worker JavaScript and the build identity are emitted by Node from the reviewed sources,
# because only Node can type-strip worker/index.ts and derive the identity from git.
- name: Restore the locked tooling and the Blazor profile application
run: |
set -euo pipefail
dotnet restore tools/ArcForges.Web.Tooling/ArcForges.Web.Tooling.csproj --locked-mode
dotnet build tools/ArcForges.Web.Tooling/ArcForges.Web.Tooling.csproj --no-restore -c Release
dotnet restore src/ArcForges.Web.App/ArcForges.Web.App.csproj --locked-mode
dotnet publish src/ArcForges.Web.App/ArcForges.Web.App.csproj --no-restore -c Release -o artifacts/publish/app
# The Blazor profile budgets (eng/policy/profile-budgets.json): every initial resource of the published application
# stays within its reviewed baseline plus the allowed regression. A breach fails the candidate before any bundle.
- name: Enforce the Blazor profile budgets
run: |
set -euo pipefail
dotnet tools/ArcForges.Web.Tooling/bin/Release/net10.0/ArcForges.Web.Tooling.dll profiles budget --publish artifacts/publish/app --budgets eng/policy/profile-budgets.json
- name: Emit the Worker and the build identity from the reviewed sources
run: |
set -euo pipefail
mkdir -p artifacts/inputs
node tooling/candidate.ts worker artifacts/inputs/index.js
node tooling/candidate.ts identity "0.1.0-ci.${GITHUB_RUN_NUMBER}.${GITHUB_RUN_ATTEMPT}" artifacts/inputs/build-info.json
- name: Build the sealed candidate twice and compare the bytes
run: |
set -euo pipefail
tool=tools/ArcForges.Web.Tooling/bin/Release/net10.0/ArcForges.Web.Tooling.dll
version="0.1.0-ci.${GITHUB_RUN_NUMBER}.${GITHUB_RUN_ATTEMPT}"
for out in artifacts/candidate artifacts/candidate-repeat; do
dotnet "$tool" candidate build --out "$out" --repo "$GITHUB_WORKSPACE" --source-ref "$GITHUB_SHA" --version "$version" --identity artifacts/inputs/build-info.json --worker artifacts/inputs/index.js --dirty false
done
diff -r artifacts/candidate artifacts/candidate-repeat
rm -rf artifacts/candidate-repeat
- name: Verify the sealed candidate before upload
run: node tooling/candidate.ts verify artifacts/candidate
- id: upload
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: web-candidate-${{ github.run_id }}-${{ github.run_attempt }}
path: artifacts/candidate
if-no-files-found: error
retention-days: 30
# CLOUD.71: the profile bundle written by build:profiles above (one deterministic, digest-named archive of the
# two profile builds), carried to the deployment job unchanged. It stays outside the sealed candidate directory.
# CLOUD.71: the profile bundle (one deterministic, digest-named archive of the Blazor application's publish), carried
# to the deployment job unchanged. It stays outside the sealed candidate directory.
- name: Build the Account and Chat profile bundle
run: |
set -euo pipefail
dotnet tools/ArcForges.Web.Tooling/bin/Release/net10.0/ArcForges.Web.Tooling.dll profiles bundle --publish artifacts/publish/app --out artifacts/profiles
dotnet tools/ArcForges.Web.Tooling/bin/Release/net10.0/ArcForges.Web.Tooling.dll profiles verify --bundle "$(ls artifacts/profiles/web-profiles-*.tar)"
- id: upload-profiles
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
Expand All @@ -165,25 +331,29 @@ jobs:
verify:
name: Verify
if: always()
needs: [source, quality, dependency-review, codeql, candidate]
needs: [source, csharp, quality, dependency-review, codeql, candidate]
runs-on: ubuntu-latest
timeout-minutes: 2
steps:
- name: Require every applicable check
env:
SOURCE: ${{ needs.source.result }}
CSHARP: ${{ needs.csharp.result }}
QUALITY: ${{ needs.quality.result }}
REVIEW: ${{ needs.dependency-review.result }}
CODEQL: ${{ needs.codeql.result }}
CANDIDATE: ${{ needs.candidate.result }}
EVENT: ${{ github.event_name }}
run: |
test "$SOURCE" = success
test "$CSHARP" = success
test "$QUALITY" = success
test "$CODEQL" = success
test "$CANDIDATE" = success
if [ "$EVENT" = pull_request ]; then test "$REVIEW" = success; else test "$REVIEW" = skipped; fi

# Deployment step, outside validation (brief section 10). It ships the same sealed C# candidate that the candidate job
# built and verified, after verifying it again here; the Cloudflare upload guards are unchanged (main push only).
deploy:
name: Deploy Cloudflare
if: >-
Expand All @@ -208,6 +378,15 @@ jobs:
node-version-file: .node-version
cache: npm
- run: npm ci --ignore-scripts
- name: Set up the pinned .NET SDK
uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6
with:
dotnet-version: "10.0.401"
- name: Build the candidate verifier
run: |
set -euo pipefail
dotnet restore tools/ArcForges.Web.Tooling/ArcForges.Web.Tooling.csproj --locked-mode
dotnet build tools/ArcForges.Web.Tooling/ArcForges.Web.Tooling.csproj --no-restore -c Release
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
artifact-ids: ${{ needs.candidate.outputs.artifact-id }}
Expand All @@ -227,9 +406,19 @@ jobs:
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
VERSION=$(node --input-type=module -e 'import fs from "node:fs"; console.log(JSON.parse(fs.readFileSync("artifacts/candidate/manifest.json")).version)')
# The Site archive (web-site-<sha256>.tar) is the candidate's one sealed root member, verified by npm run deploy above.
# It is published here beside web-profiles, and only by this main-push release. It is not a deployed asset.
shopt -s nullglob
site_archives=(artifacts/candidate/web-site-*.tar)
shopt -u nullglob
test "${#site_archives[@]}" = 1
site_archive="${site_archives[0]}"
site_digest=$(sha256sum "$site_archive" | cut -d ' ' -f 1)
test "$(basename "$site_archive")" = "web-site-${site_digest}.tar"
tar -czf "artifacts/web-${VERSION}.tar.gz" -C artifacts/candidate .
gh release create "web-${VERSION}" "artifacts/web-${VERSION}.tar.gz" artifacts/deployment.json artifacts/profiles/web-profiles-*.tar --target "$GITHUB_SHA" --title "Web ${VERSION}" --prerelease --generate-notes
gh release create "web-${VERSION}" "artifacts/web-${VERSION}.tar.gz" artifacts/deployment.json artifacts/profiles/web-profiles-*.tar "$site_archive" --target "$GITHUB_SHA" --title "Web ${VERSION}" --prerelease --generate-notes
- name: Preserve deployment evidence
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
Expand Down
12 changes: 11 additions & 1 deletion .gitleaks.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ description = "Reviewed public source hashes in the immutable browser resource p
condition = "AND"
targetRules = ["generic-api-key"]
regexTarget = "line"
paths = ['''^eng/provenance/profiles/browser-resources-r(?:[123456789]|10)\.json$''']
paths = ['''^eng/provenance/profiles/browser-resources-r(?:[123456789]|1[01])\.json$''']
regexes = [
'''^\s*"(?:apps/site/)?node_modules/@bufbuild/protobuf/dist/esm/wkt/gen/google/protobuf/api_pb\.js": "73e489001027c703bc0224ae73f78ecefe028e88284bd06952e8603c3d81472c",?$''',
'''^\s*"node_modules/@connectrpc/connect-web/dist/esm/assert-fetch-api\.js": "bd56033776818aaa82959c12561dd084d3a730180e6e8f1e681f5d3d439b6474",?$''',
Expand All @@ -21,3 +21,13 @@ regexes = [
'''^\s*"node_modules/@arcforges/api-client/dist/shapes/gen/NativeTokenRequest\.js": "79d75ee79a5b6d52433e6425de6eefdaa1e829fd58ee2dcda204cf391e3517df",?$''',
'''^\s*"node_modules/@arcforges/api-client/dist/shapes/gen/NativeTokenResponse\.js": "725cac5a5ceeba78f775804a26b8b5de99a7eeaf250d37335fc59bdd5ec58ce1",?$''',
]

[[allowlists]]
description = "Reviewed public schema digest in the CON.07 Contracts publication record: the key name contains auth, and the value is the SHA-256 of the packaged public schema (verified against the NuGet package bytes)"
condition = "AND"
targetRules = ["generic-api-key"]
regexTarget = "line"
paths = ['''^eng/contracts/ArcForges\.Contracts\.PublicApi/1\.0\.0-ci\.287\.1/source\.json$''']
regexes = [
'''^\s*"public/http/v1/native-auth\.schema\.json": "e7807ae0ab6b4530d817941a922d7df90e4feb525c68d81a245a4c089ad3d296",?$''',
]
Loading
Loading