Skip to content

[WEB.40] Blazor WebAssembly migration of Web: C# Site, Account and Chat profiles, TypeScript thin Worker, React retired - #35

Merged
deku2026 merged 38 commits into
mainfrom
task/web-40
Oct 9, 2026
Merged

deku2026 merged 38 commits into
mainfrom
task/web-40

Conversation

@deku2026

@deku2026 deku2026 commented Oct 9, 2026

Copy link
Copy Markdown
Contributor

Claim: WEB.40 epoch 1 (w-deku-20261008-web-40)

Task record: web lane, task-web-40. Authority: P2-021 (C#-first: Web moves to Blazor WebAssembly; TypeScript only for the thin Cloudflare Worker adapter), plus the coordinator adjudications recorded for WEB.40 (sequencing, deploy switch, Site interactivity, parity waiver, focus option (a), accessibility re-proof).

What this changes

  • Public Site: a static C# Site, generated by the first-party Razor HtmlRenderer generator in tools/ArcForges.Web.Tooling. Its output is deterministic, as a repeated build shows. It needs no Blazor boot for public pages.

  • Account and Chat: Blazor WebAssembly profiles (src/ArcForges.Web.App) on generated C# gRPC-Web clients, with session and CSRF handling, CSP, exact int64/uint64/decimal, and typed failure states. The Operations profile skeleton is in place. The owned design system is ArcForges.Web.Ui.

  • Worker: the canonical-host Worker is converted to TypeScript as a thin adapter, with no business decision and no authoritative state.

  • Deploy switch: the candidate seal and verifier, CI and the main-push Cloudflare deploy now operate on the C# Site and Blazor outputs. The guards are unchanged: sealed candidate, verification before upload, main-push only, no workers.dev and no previews.

  • React retirement: the React apps, the npm test toolchain, the npm Contracts packages and the Prettier and Biome gates are retired. The record is in docs/web-40-react-retirement.md.

  • Parity: normalised-DOM parity against the recorded React prerender (docs/web-40-site-parity.md) shows 0 differences on /, /hello/, /cloud-hello/ and /404.html, with byte-identical static assets and stylesheet pair.

  • Accessibility re-proof (P2-021 item 8): docs/web-40-accessibility.md covers 14 screen states with 0 axe violations (axe-core 4.13.0 in Chrome 156.0.8078.12, local opt-in), and a WCAG 2.2 A and AA checklist per state. Fixed on the way:

    • 1.4.11: App input boundaries raised to 4.0 to 4.5:1;
    • 2.1.1 and 4.1.2: the error banner Dismiss is now a named keyboard button;
    • the axe region rule: the banner message sits in role=alert.

    The judgement items and the human assistive-technology session remain open and are not claimed.

  • Policy: the GOV.11 policy suite is ported to C#. NuGet admission successor receipts run up to web-40-admission-r14, all naming the reviewer.

Scope notes

  • ADP-07 supporting-file binding. These files are outside the recorded writes and are changed only as the migration requires: global.json (SDK 10.0.401), Directory.Build.props, .editorconfig, the vendored eng/naming tool (digest-pinned by eng/policy/naming-candidate.json), the eng/contracts PublicApi copy, eng/version-sources.json, and the root README.md, AGENTS.md, CONTRIBUTING.md and THIRD_PARTY_NOTICES.md. The retired React, Prettier, VS Code and Playwright configurations are deleted.
  • Node build tooling (coordinator adjudication of 2026-10-09, applied by the pending planning repair P2-026): Web's Node TypeScript build, policy and provenance tooling and its node:test provenance tests stay as repository build tooling, with no business decision or authoritative state. Their C# port is recorded as out of scope, not completed.
  • Receipt r14 rationale. Its convention sentence is inaccurate. The rows are correct: each row's contentHash equals the packages.lock.json and .nupkg.metadata contentHash, not the .nupkg file SHA-512, and nuspecSha256 is the SHA-256 of the restored .nuspec bytes including the BOM. The next Web successor receipt corrects the sentence; committed receipts are not rewritten.
  • Streaming: WEB.40 has no streaming surface, because the retired React app was unary only. The streaming proof belongs to PRF.11.

Validation actually performed

  • Coordinator, through the workstation build slot at 58f44c4:
    • full-local passed: locked restore, dotnet format, build, candidate publish, all test suites, profile publishes, Site build with determinism repeat and diff, browser build;
    • a11y-parity passed: the parity and axe suites.
  • Reviewer at 6ff8e1e:
    • dependency check (114 dependencies, 39 inputs), typecheck, test:dependencies 15/15, npm tests 88/88, and licence-evaluated;
    • the policy wrapper, with the Node 24.20 versus 24.21 pin gap stubbed;
    • every admission row verified against the restored packages.
  • Not run locally: gitleaks (not installed; hosted secret-scan is authoritative), and the Node 24.21 toolchain gate (local Node 24.20; hosted CI is authoritative).

🤖 Generated with Claude Code

deku2026 and others added 30 commits October 8, 2026 15:57
Step 1 of the Blazor migration. Adds the central .NET 10 solution layout
with one exact SDK pin (10.0.401), central package management and the
AGPL licence metadata on every project:

- src/ArcForges.Web.Site (C# static Site generator skeleton)
- src/ArcForges.Web.Ui (Razor class library skeleton)
- src/ArcForges.Web.App (Blazor WebAssembly standalone Account/Chat profile
  skeleton, RunAOTCompilation false, transitive framework references off)
- src/ArcForges.Web.Operations (separate-origin Blazor WebAssembly profile
  skeleton; no operator features)
- tools/ArcForges.Web.Tooling (refuses every command until the port lands)
- tests/ArcForges.Web.{Site,App,Ui,Operations,Policy}.Tests (xUnit v3 on
  Microsoft.Testing.Platform, bUnit for Ui; the Policy suite checks the
  restored NuGet closure against the admitted records, with negative cases)

NuGet admission: eng/policy/dependency-policy.json gains nugetClosure (55
exact packages from the project lock files, with content hash, licence,
nuspec SHA-256 and source) and the successor receipt
eng/policy/dependency-reviews/web-40-admission-r1.json. Every new csproj,
props, targets, global.json, NuGet.config and packages.lock.json input is
hashed in the npm input map. The npm closure is unchanged.

Architecture policy: the GOV.11 refusal of every Blazor target is reversed
to the P2-021 rule: Blazor WebAssembly standalone is allowed, and Blazor
Server render modes and circuits are refused. The fixture follows.

win.slnx keeps the ArcForges.Web.esproj entry until the React parity step.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
…and browser-wasm admission

Step 2 of the Blazor migration. Section 10 items first:

- CON.07 identity: verified from Plan ledger con-07.md (Contracts PR 74,
  merge ca45f36, candidate 1.0.0-ci.287.1, publication run 36966686225) and
  from the nuget.org flat-container index and nuspec of all twelve packable
  Contracts projects (each lists 1.0.0-ci.287.1, carries repository commit
  ca45f36 and is Apache-2.0). ArcForges.Contracts.PublicApi 1.0.0-ci.287.1 is
  pinned centrally and referenced by ArcForges.Web.Ui; its transitive
  Foundation, Google.Protobuf 3.36.1 and Grpc.Core.Api 2.84.0 are admitted.
- browser-wasm admission: Microsoft.NETCore.App.Runtime.Mono.browser-wasm
  10.0.12 (implicit for the BlazorWebAssembly profile) and
  Microsoft.NET.Runtime.WebAssembly.Sdk 10.0.12 (PackageDownload in
  ArcForges.Web.App.csproj), recorded in nugetPackDownloads because NuGet
  does not record PackageDownload items in packages.lock.json.
  Microsoft.AspNetCore.App.Runtime.browser-wasm is not admitted: no such
  package exists on nuget.org.
- Receipt web-40-admission-r2.json supersedes r1 (r1 is unchanged), with the
  con07Identity table, browserWasmPacks and the input digests of every
  changed csproj, props and packages.lock.json.

Static Site (src/ArcForges.Web.Site, src/ArcForges.Web.Ui):

- Public pages (/, /hello, /cloud-hello) are Razor components rendered with
  Microsoft.AspNetCore.Components.Web.HtmlRenderer. The markup reproduces the
  React prerender content: titles, descriptions, shell, copy, disabled initial
  controls, the default greeting and the noscript notices. No script element,
  no event-handler attribute and no framework reference is emitted (TB-01).
  Blazor internal event markers are removed from the static markup.
- The stylesheet is one content-hashed asset (Tailwind preflight equivalent
  plus the packages/ui styles). The 404 page, robots.txt, favicon and 404.css
  are the React public files, byte for byte.
- The Content-Security-Policy is derived from the emitted pages with the React
  rule (script-src 'self' plus the SHA-256 of every inline script body; no
  unsafe-inline, unsafe-eval or wasm-unsafe-eval; an external script is
  refused). _headers carries the exact React securityHeaders text.
- Greeting and connection logic are ports of apps/site/app/hello.ts and
  cloud-hello.ts: the protobuf SayHello round trip, the JavaScript trim set,
  the 80-code-point limit, the control-character refusal, the exact failure
  text, the same-origin gRPC-Web unary call with no credentials, no redirect,
  a ten-second deadline and a reply size limit.
- The build is deterministic: two builds of the same inputs produce the same
  bytes (tested).

Tests: xUnit ports of the site unit cases (GreetingTests, ServerConnectionTests,
bUnit HelloExampleTests and CloudHelloExampleTests), plus determinism, no-script,
header, CSP, inventory and framing failure tests. The Policy suite gains the
pack-download admission check. Results: Site 65 passed, Ui 7 passed, Policy 9
passed (local dotnet 10.0.401 build and run).

Not in this step (recorded as remaining): apps/site and the React tests are
kept until byte and content parity is shown; the interactive hello and
connection checks need the Blazor App profile (no runtime JavaScript on public
pages); Node/vitest and the Playwright suites were not run locally (no
node_modules, hosted CI authoritative).

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
…on-r3)

Step 3, unit U1, first commit: the dependency admission for the Account and
Chat profiles and the Operations skeleton, and the project files that carry it.

- eng/policy/dependency-reviews/web-40-admission-r3.json supersedes r2 (r2 is
  unchanged). Its review reviewer field is the pre-assigned independent reviewer
  w-deku-20261008-rev-web-40, not PENDING.
- eng/policy/dependency-policy.json binds 39 input digests (every csproj, props,
  targets and packages.lock.json, plus the earlier named inputs; CRLF is
  normalised to LF as the policy digests them) and 65 nugetClosure rows, each
  read from the restored lock files and checked against its nuspec.
- Directory.Packages.props, win.slnx, the App and Operations project files, and
  the App, Operations and test packages.lock.json files are the write-scope
  project files for these packages.
- Restored offline-checked with dotnet 10.0.401 (global.json, no adapter needed).

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
…ons skeleton

Step 3, unit U1, second commit: the standalone Blazor WebAssembly source of the
Account and Chat deployment profiles (ArcForges.Web.App, replacing apps/app) and
the Operations profile skeleton (ArcForges.Web.Operations), plus the shared
profile CSP helper in ArcForges.Web.Ui.

- Probe/HelloProbe.cs: the anonymous binary gRPC-Web greeting through the
  generated Contracts client. Fixed in this unit: the frame watcher read the
  frame length from the flag byte plus three length bytes, so a trailers frame
  never reported its grpc-status (trailers-only answers became malformed); the
  watcher now reads the four length bytes after the flag. The watched content
  also copies bytes through the serialize path the library uses to buffer an
  answer, so the status is seen on every path. Bytes are never changed.
- Pages/Chat.razor and Pages/Account.razor, Probe/*.cs, App.razor, layouts and
  wwwroot: the same semantics as apps/app (routes/chat.tsx, probe/*.ts): one
  message at a time, the Sending state, Cancel, cancellation on leaving the page,
  the last twenty transcript entries, fixed failure text, and cookie-session,
  CSRF and Origin rules unchanged.
- Operations: the project, shell route and separate-origin configuration only;
  no operator feature code (OPS.05 and OPS.11 build those).
- Ui/WasmContentSecurityPolicy.cs: the exact script-src token set for the App and
  Operations profiles ('self' 'wasm-unsafe-eval' plus required hashes; never
  unsafe-eval or unsafe-inline).

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
…cal browser opt-in

Step 3, unit U1, third commit: the xUnit and bUnit successors of the Account and
Chat probe cases (tests/unit/app-*.test.tsx and app-hello.test.ts), the profile
CSP token-set assertions, and the Operations skeleton tests.

- ArcForges.Web.App.Tests: Account, Chat, hello, session, exact unsigned and
  profile policy cases with a test-only same-origin double (TestDoubles). Fixes in
  this unit, without weakening any assertion:
  - LeavingTheChatPageCancelsTheMessageThatIsStillPending waits for the request
    to start through a completion source and disposes the rendered components
    with DisposeComponentsAsync, the bUnit API that disposes components (the
    context dispose only releases services).
  - The cancellation token of the wait uses the xUnit test context token.
- ArcForges.Web.Operations.Tests: skeleton only (the profile, its origin and the
  exact CSP token set).
- ArcForges.Web.Ui.Tests: WasmContentSecurityPolicyTests with a passing and a
  failing example for each token rule.
- tests/browser/ArcForges.Web.Browser.Tests: Microsoft.Playwright for .NET, local
  opt-in test-only tooling (LocalOptIn gate; not run in CI). The local browser
  test is skipped unless the opt-in is set, as recorded by the test.

Results on this worktree (dotnet 10.0.401, through the build slot, after the
admission r3 restore): Ui 14 passed; App 49 passed; Operations 5 passed; Browser
6 passed and 1 local opt-in skipped; Policy 9 passed; Site 65 passed.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
…e provenance and licence-boundary gaps

worker/index.js becomes worker/index.ts with identical behaviour: www-to-apex
308 keeping path and query, everything else served by env.ASSETS.fetch. The
candidate ships the JavaScript form emitted by Node type stripping (LF
normalized) into the private worker/index.js; no bundling, no import.

- tooling/project.ts: workerScript() emits the candidate Worker and
  verifyWorkerScript() compares the candidate bytes with that emission.
- tests/unit/canonical-host.test.ts: the emitted module is importless, carries
  no type syntax, and answers like the TypeScript source on the redirect,
  asset-passthrough and loopback cases.
- tests/unit/licence-boundary.test.ts: an unknown first-party package in a
  packages.lock.json fails the audit.
- tooling/licence-boundary.ts: arcforges.contracts.foundation is an allowed
  first-party owner (CON.07 candidate 1.0.0-ci.287.1, admitted by
  web-40-admission-r2 from Contracts ca45f36, an ancestor of Contracts main).
- eng/policy/licence-boundary.json: the browser-test csproj is a licence-
  boundary MSBuild input.
- eng/provenance/files.json, biome.json, tsconfig.json: worker/index.ts replaces
  worker/index.js in the inventory, the lint and typecheck includes.
- docs/deploying.md, docs/provenance.md: factual update for the TypeScript source.

The browser-resources profile still binds worker/index.js and the previous
tooling digests. The successor chain (browser-resources-r11 and admission
receipt r4) is committed separately, so this commit is not the final gate.

Validation: unit suite (vitest run tests/unit) 15 files, 139 tests passed;
tsc -p tsconfig.json passed; biome lint passed. Candidate build not yet
passing at this commit (browser-input gate, resolved by the successor).

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
…he TypeScript Worker

browser-resources-r11 supersedes browser-resources-r10. It binds the
canonical-host Worker source worker/index.ts (replacing worker/index.js), the
emitter in tooling/project.ts and the active-profile selector in
tooling/browser-provenance.ts. The browser graph, the normalized templates,
package identities, legal inputs, Contracts metadata and both SBOM expectations
are unchanged. Its sourceCommit is dc46b23, the commit that carries the reviewed
Worker and emitter bytes. The reviewer field names w-deku-20261008-rev-web-40.

web-40-admission-r4 chains from web-40-admission-r3 and rebinds the policy
inputs to r11. It changes no package, lock, manifest or class: the NuGet and npm
closures and package-lock are the r3 values. The active review pointer and the
artifactRecords move to r4 and r11. The r3 receipt and the r10 profile and
record stay immutable history. files.json lists the new members and its
artifacts point at r11. NOTICE.txt is regenerated from the active records.

Validation run locally through the workstation build slot:
- node tooling/project.ts build passed and verified candidate 0.1.0-local at
  dc46b23 (local changes flag cleared at this commit).
- node --test tests/provenance (43) and tooling/dependency-policy.test.ts (15)
  passed.
- vitest run tests/unit: 15 files, 139 tests passed.
- tsc -p tsconfig.json and biome lint passed; prettier passes on every file this
  commit changes.
- The policy gate passed its licence, provenance and browser-input checks. It
  stops at the pinned Node 24.21.0 assertion, because the local runtime is
  24.20.0. Hosted CI remains authoritative (brief section 10). The remaining
  policy steps passed locally when run without that assertion.
- Not run locally: the npm run format:check repo-wide gate. It already fails
  on committed files outside this change (see the unit report), and the
  dependency-policy.json change keeps the plain JSON style that file uses.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
… web-40-admission-r5

Step 4 of the Blazor migration, unit U3, first commit: the ported 'site build'
command that CI needs to publish the C# static Site.

- tools/ArcForges.Web.Tooling/Program.cs: 'site build --out <new-directory>
  [--source-ref <40-hex>]' writes every SiteBuilder file to a directory that must
  not exist yet (fail closed on a stale directory, refuse paths that escape it,
  refuse a malformed source ref with exit 2). Every other command is refused with
  exit 2 and the list of ported commands.
- tools/ArcForges.Web.Tooling/ArcForges.Web.Tooling.csproj: ProjectReference to
  src/ArcForges.Web.Site. packages.lock.json records that reference and the
  Contracts and gRPC packages it needs, all already admitted (CON.07 identity).
- eng/policy/dependency-reviews/web-40-admission-r5.json supersedes r4 (r4 is
  unchanged). Its reviewer field is w-deku-20261008-rev-web-40. The active
  review and reviewRecord in eng/policy/dependency-policy.json move to r5; the
  two tooling input digests are rebound and every other input is unchanged.

Validation (build slot, Windows, dotnet 10.0.401): Tooling build succeeded with
0 warnings; 'site build' twice into fresh directories gives identical trees
(9 files, diff -r empty); negative cases exit 2 (existing directory, bad ref,
unknown option, missing --out, unknown command). node tooling/dependency-policy.ts
passed (351 dependencies, 39 inputs); node --test tooling/dependency-policy.test.ts
15/15; Policy test suite 9/9.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
…pt web-40-admission-r6

Step 4 of the Blazor migration, unit U3, second commit: the workflow runs the C#
product on Windows and Linux and keeps the existing security jobs.

.github/workflows/ci.yml:
- Workflow env CSHARP_PROJECTS and CSHARP_TEST_PROJECTS list the ten C# projects
  explicitly. win.slnx is not used because it carries the retiring esproj entry,
  and tests/browser (Microsoft.Playwright, local opt-in) is never restored or
  built in CI.
- New job csharp (ubuntu-latest, windows-latest, SDK 10.0.401 via setup-dotnet):
  dotnet restore --locked-mode, dotnet build --no-restore, dotnet test --no-build
  for the Site, Ui, App, Operations and Policy suites, dotnet publish of the Account
  and Chat profile and of the Operations skeleton, and a static Site build by the
  ported 'site build' command that is compared byte for byte with a repeat build.
  The Linux leg retains the Site and profile outputs for 30 days.
- CodeQL matrix gains csharp with build-mode manual and an explicit locked
  restore and build, so CodeQL observes the real compilation. The javascript-
  typescript and actions analyses are unchanged (build-mode none).
- Secret scan (gitleaks by digest), actionlint, npm audit, dependency review and
  the verify required-check gate are kept; verify now also requires csharp.
- The main-push deploy job is unchanged and still ships the sealed React candidate.
  It carries a comment that the C# outputs are not yet promoted by it. Deploying
  the C# candidate needs the candidate and deploy port (a decision for the
  coordinator; not made here).
- No browser E2E, live-service, macOS or device job is added.

eng/policy/dependency-reviews/web-40-admission-r6.json supersedes r5 (r5 is
unchanged). Its reviewer field is w-deku-20261008-rev-web-40. The active review
and reviewRecord in eng/policy/dependency-policy.json move to r6, and the
.github/workflows/ci.yml input digest is rebound (the workflow file is a gated
dependency input). Every other input digest, package and class is unchanged.

Validation (Windows, build slot, dotnet 10.0.401, the csharp job steps replayed
from this file): restore --locked-mode for all ten projects; build of all ten;
tests Site 65, Ui 14, App 49, Operations 5, Policy 9, all passed; both profile
publishes succeeded; the Site built twice with identical trees (9 files).
node tooling/dependency-policy.ts passed (351 dependencies, 39 inputs); the
dependency test suite 15/15. prettier --check passes on ci.yml. actionlint is not
installed on this workstation, so the workflow was checked with a YAML parse and
the job graph read back (hosted CI remains authoritative). The Linux leg is
validated separately in WSL2 Debian (see the unit report).

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
…de glue (U3b, part 1)

- tools/ArcForges.Web.Tooling: candidate build and candidate verify (C# Site
  output, identity and legal files, pinned Worker digest, wrangler candidate
  config, build SBOM, runtime SBOM, provenance receipt, sealed manifest), and
  profiles bundle and profiles verify (web-profiles-<sha256>.tar, same name
  pattern and manifest-first layout; static web assets manifest integrity check).
- tooling/candidate.ts: Node emits the Worker JavaScript and the build identity,
  and re-verifies the identity before deploy.
- tooling/cloudflare.ts: deploy and smoke verify through the C# tool; new
  dry-run command (wrangler deploy --dry-run, no upload).
- tests/provenance/csharp-candidate.test.ts: determinism, seal, resealed
  security and bundle tamper tests. Not yet run to completion locally when this
  commit was made (slot queued); see the unit report.
- eng/provenance/files.json: inventory entries for the new files, and the
  receipts r5 and r6 that HEAD had omitted.
- docs/deploying.md: C# candidate sequence and local dry run.

Local proof (build slot): two candidate builds identical; candidate verify
passes; Blazor publish; bundle builds, verifies and is byte-deterministic;
wrangler dry run read 17 assets and uploaded nothing.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
…-admission-r7 (U3b, part 2)

- .github/workflows/ci.yml: the candidate job publishes ArcForges.Web.App,
  emits the Worker and identity (tooling/candidate.ts), builds the sealed C#
  candidate twice with a byte comparison, verifies it, and builds and verifies
  the profile bundle. The React candidate and profile builds leave the deploy
  path. The deploy job restores and builds the C# verifier and ships the same
  sealed candidate under the unchanged main-push guards.
- eng/policy/dependency-reviews/web-40-admission-r7.json supersedes r6. Its
  reviewer field is w-deku-20261008-rev-web-40. Only the ci.yml input digest
  changed; no package, lock, class or project file changed.
- eng/policy/dependency-policy.json binds r7 and the rebound ci.yml digest.

Validation: tooling/dependency-policy.ts passed (351 dependencies, 39 inputs);
dependency-policy tests 15/15; provenance source tests 35/35; architecture audit
0 findings; naming check pass; prettier and biome clean on changed TypeScript.
Not run to completion: the C# candidate negative tests and the workflow itself
(hosted CI is authoritative).

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
… fixtures (U4)

Re-implements the GOV.11 Node/TypeScript policy as the C# successor
tests/ArcForges.Web.Policy.Tests, with one xUnit refusal per rule family
and sub-rule and accepted forms that must stay admitted.

- Architecture: workspace (one solution, lock per project, one npm lock,
  manifest inventory), exact pins (SDK, Node, npm, central versions,
  PackageDownload), obsolete Blazor targets (Blazor Server forbidden),
  portable references, production and install commands, SDK-to-UI
  licence separation, wire-source rules, private and server imports,
  desktop DOM prohibition, computed and unresolved imports and the release
  route graph (Architecture/*.cs, ArchitecturePolicy.cs).
- Licence boundary over eng/policy/licence-boundary.json (Licence/).
- Forbidden-term scanner: NamingScannerTests verifies the published naming
  authority identity and runs the published scanner under python -I on a
  Git fixture for the current terms and every forbidden term.
- Fixtures: Fixtures/PolicyBaseline.cs (passing repository) and
  Fixtures/PolicyCases.cs (refusals and accepted forms).
- RepositoryPolicyTests runs the audit over the real tracked and
  nonignored inputs; it must report zero findings.
- ci.yml csharp job: setup-node from .node-version and npm ci --ignore-scripts,
  so the naming authority is in node_modules for the test.
- docs/development.md: factual C# policy suite paragraph.

Admission: successor receipt eng/policy/dependency-reviews/web-40-admission-r8.json
supersedes r7 (reviewer w-deku-20261008-rev-web-40); only the ci.yml digest
changed. eng/policy/dependency-policy.json binds r8 and the rebound digest.

ADP-07 supporting files (write scope, recorded per commit):
  .github/workflows/ci.yml
  docs/development.md
  eng/policy/dependency-policy.json
  eng/policy/dependency-reviews/web-40-admission-r8.json
  eng/provenance/files.json (12 first-party paths added)
  tests/ArcForges.Web.Policy.Tests/Architecture/*.cs
  tests/ArcForges.Web.Policy.Tests/Fixtures/*.cs
  tests/ArcForges.Web.Policy.Tests/Licence/LicencePolicy.cs
  tests/ArcForges.Web.Policy.Tests/ArchitecturePolicyTests.cs
  tests/ArcForges.Web.Policy.Tests/NamingScannerTests.cs
  tests/ArcForges.Web.Policy.Tests/RepositoryPolicyTests.cs

Validation (local, Windows, .NET 10.0.401 locked):
  dotnet restore --locked-mode, build and test of the Policy project:
  97/97 passed (includes RepositoryPolicyTests with zero findings).
Validation (local, WSL2 Debian copy, SDK 10.0.400 with the pin rewritten
in the copy, locked restore): restore, build and test 97/97 passed.
node --test tooling/dependency-policy.test.ts: 43/43 (and dependency
policy: 351 dependencies, 39 inputs).
Provenance tests 43/43, vitest unit 139/139, biome lint and tsc clean.
Not run locally: tooling/project.ts policy after its Node-version assertion.
The local toolchain is Node 24.20.0 against the 24.21.0 pin, so that
assertion fails locally and the later steps did not run; hosted CI is
authoritative.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
…tes (U5, step 1)

Before the React sources are removed, the C# static Site is compared with
the React prerender of 80900a1 (docs/web-40-site-parity.md):

- index, hello and cloud-hello have equal element skeletons (99, 112 and 94
  elements), equal visible text, titles, metadata, anchors, form controls,
  landmarks and noscript text. Only the React hydration scripts and
  modulepreload links differ (TB-01).
- 404.html, 404.css, robots.txt and favicon.svg are byte identical.
- The public stylesheet is now the React build's Tailwind v4.3.3 output,
  byte identical (site.d4912dbd6dc5e22b.css). The previous hand-written
  approximation differed in the system font stack, the placeholder colour
  and 15 selectors, which would have changed rendering.
- The 17 security and cache header lines are identical to the React
  securityHeaders template; the CSP differs only by the seven React hydration
  script hashes, because the public pages carry no script.

SiteOutputTests expects the Tailwind range-syntax breakpoint of the reviewed
stylesheet. The Site suite passes 65/65.

docs/web-40-site-parity.md records the open decision on the interactive
/hello and /cloud-hello checks, which the static Site cannot run.
eng/provenance/files.json lists the new record.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
…ling tests, with successor receipt web-40-admission-r9 (U5, step 2)

Replaces the React profile budgets (apps/app/budgets.json and measure.ts)
with a measured, reviewed Blazor baseline enforced by the C# tool.

- tools/ArcForges.Web.Tooling/Profiles/ProfileBudget.cs: the initial set of
  the published application (every file except precompressed siblings),
  measured in-process with GZip SmallestSize. ProfileBudgets.Parse refuses a
  missing, extra or non-positive field and a profile set other than the
  bundle's. A metric may grow by at most regressionPercent, rounded down.
- Program.cs: profiles budget --publish <dir> --budgets <file> (exit 1 on a
  breach).
- eng/policy/profile-budgets.json: the baseline measured on the SDK 10.0.401
  publish at this source (57 requests, 719 HTML bytes, 2245 CSS, 125944 JS,
  3227235 WASM, 848189 data and 238 other gzip bytes; total 4203851). The
  React interaction ceilings are re-baseline-pending (owner PRF.11, AL-06).
- .github/workflows/ci.yml: the candidate job enforces the budgets after the
  App publish; the csharp job restores, builds and tests the new tooling test
  project.
- ProfileBundle: every _framework file must be fingerprinted (ten lower-case
  characters before the extension, .br and .gz siblings included) or be one of
  the two unfingerprinted loaders; the headers add an immutable rule for
  /_framework/* and no-cache overrides for blazor.webassembly.js and dotnet.js.
- tests/ArcForges.Web.Tooling.Tests: 21 xUnit cases with a passing and a
  failing example per rule (budget parse, the ten percent limit, the measure,
  the fingerprint rule, the headers). Policy suite 97/97; tooling 21/21; both
  restored with --locked-mode.

ADP-07 supporting files (write scope):
  .github/workflows/ci.yml
  eng/policy/dependency-policy.json (rebound to r9; inputs recomputed)
  eng/policy/dependency-reviews/web-40-admission-r9.json (supersedes r8;
    reviewer w-deku-20261008-rev-web-40; npm coordinates unchanged)
  eng/policy/licence-boundary.json (tooling test project row)
  eng/policy/profile-budgets.json
  eng/provenance/files.json
  tests/ArcForges.Web.Tooling.Tests/** (project, lock, root helper, tests)
  tools/ArcForges.Web.Tooling/Profiles/ProfileBudget.cs, ProfileBundle.cs,
    Program.cs

Validation (Windows, dotnet 10.0.401, through the build slot): the App
publish, the budget measure, the bundle build and verify, the dependency
policy, the provenance and licence audits (run from the scratchpad) all
passed. Node 24.20 against the 24.21 pin is a local gap; the policy script's
Node assertion is not run locally.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
…dored naming, add the formatting and whitespace gates, with successor receipts (U5, checkpoint)

Checkpoint of unit U5. The C# validation run that gates this commit is not complete; see
the remaining list below.

- Identity: tooling/build-identity.ts reads the Contracts axes from the NuGet CON.07
  publication record eng/contracts/ArcForges.Contracts.PublicApi/1.0.0-ci.287.1
  (byte copies of build-identity.json and source.json, sourceCommit ca45f36) and pins
  it against the central Directory.Packages.props entry. Bound by contracts-publication-r1.
- Naming: eng/naming holds byte copies of tools/naming from the NuGet package
  ArcForges.Contracts.Validation 1.0.0-ci.287.1; digests pinned in
  eng/policy/naming-candidate.json. NamingScannerTests and tooling/project.ts read it.
- package.json and package-lock.json: @arcforges/api-client and @arcforges/proto removed
  (lock: 115 entries, removals only). Dependency policy and receipt web-40-admission-r10
  derived from the lock by the repository digest functions (114 entries).
- Formatting: CI csharp job runs dotnet format --verify-no-changes per project; Prettier
  and Biome are gone; the policy suite enforces final newline and no trailing whitespace
  (ArchitecturePolicy.Whitespace.cs, with refusal and accepted fixtures).
- Provenance: cloud-dependency-policy-r3 supersedes r2 and cloud-build-identity-r5 is
  refreshed; NOTICE regenerated with writeNotice.
- Wrangler root template repointed to ./artifacts/site (already staged).

Local validation in this checkpoint:
- typecheck, check:dependencies, test:dependencies, test (88 + 15 passing), provenance
  audit, licence and naming steps of policy: passed.
- npm run policy stops at its Node 24.21 pin assertion (local Node 24.20 gap).

Remaining before U5 can be marked complete:
- C# restore --locked-mode, build, dotnet format and the Policy and Tooling test runs.
- Full CI-equivalent run: npm ci, publish, Site build twice, budgets, candidate build and
  verify, profile bundle, wrangler dry-run.
- Final doc pass and the U5 commit message.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
…, the npm Contracts packages and the Prettier and Biome gates (U5 final)

The C# static Site reproduces the React prerender (docs/web-40-site-parity.md): element skeletons,
visible texts, metadata, the stylesheet bytes and the header lines are equal. Only the hydration
scripts and the CSP script hashes differ (TB-01). The public pages carry no JavaScript; the live
/hello and /cloud-hello controls are retired by the coordinator adjudication of 2026-10-09 (brief
section 10), and ArcForges.Web.App remains the only interactive browser application.

Removed: apps/site, apps/app, packages/ui, the Vite, Vitest, React Router, Tailwind and Playwright
sources, the Node tests and TypeScript tooling that served them, the Prettier, Biome and
ArcForges.Web.esproj configuration. docs/web-40-react-retirement.md maps every retired test to its
C# or node:test successor.

- package.json reduced to wrangler, TypeScript and @types/node (the Worker build and Node tooling).
  @arcforges/api-client and @arcforges/proto are retired (package-lock.json regenerated with the
  pinned npm 11.19.0). The sharp override is kept.
- tooling/build-identity.ts reads the Contracts axes from the NuGet CON.07 publication record
  eng/contracts/ArcForges.Contracts.PublicApi/1.0.0-ci.287.1 (sourceCommit ca45f36, verified against
  the Plan ledger con-07.md) and pins it to the central Directory.Packages.props entry.
- eng/naming holds byte copies of the NuGet ArcForges.Contracts.Validation 1.0.0-ci.287.1 naming
  tools, pinned in eng/policy/naming-candidate.json.
- Formatting gate: CI runs dotnet format --verify-no-changes for every C# project. The policy suite
  enforces the final-newline and no-trailing-whitespace rules (ArchitecturePolicy.Whitespace.cs).
  The thin TypeScript Worker is type checked by npm run typecheck.
- wrangler.json: the assets directory is the C# Site output (./artifacts/site); main stays
  ./worker/index.js.
- Blazor profile size budgets: eng/policy/profile-budgets.json holds the recorded baseline, enforced
  in CI before the bundle. The immutable cache rule covers fingerprinted /_framework files (the two
  unfingerprinted loaders are no-cache), in the Site and in the profile bundle _headers.

Receipts (each names reviewer w-deku-20261008-rev-web-40): web-40-admission-r10 (dependency policy
rebound from the new lock), cloud-dependency-policy-r3, cloud-build-identity-r5 (the tooling
digest changed by the pin move), browser-resources-r12 and contracts-publication-r1. Earlier
receipts stay as immutable history.

Docs: README, AGENTS.md, CONTRIBUTING, development, provenance, licence-boundary, validation,
THIRD_PARTY_NOTICES and the profile bundle layout. Historical docs are marked superseded.

Local validation is recorded in the WEB.40 U5 unit report. Node 24.20 is local against the 24.21
pin; the pin assertion is recorded as a local environment gap, and hosted CI is authoritative.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
…ace rule, tooling test visibility and the formatting gate (U5, fix 1)

Found by the CI-equivalent run of U5 (dotnet format, restore, build, test and policy):

- .editorconfig: the repository-wide indent_size = 2 (the React-era setting) applied to the C#
  sources, which use the standard 4-space indent. A [*.cs] section sets indent_size and tab_width
  to 4. The CRLF line endings the formatter reported are normalised (end_of_line = lf) and only
  indentation changed in the formatter's output (PolicyBaseline.cs, NuGetClosureAdmissionTests.cs).
- ArchitecturePolicy: the root package.json may omit the workspaces field. The React workspaces are
  retired, so the root is the only npm project. A non-array workspaces value is still refused, and
  the workspace inventory stays exact (negative fixture workspace-root-workspaces-not-array).
- ArcForges.Web.Tooling.csproj: InternalsVisibleTo ArcForges.Web.Tooling.Tests, so the archive tests
  reach the internal TarArchive writer.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
…ce, so the reviewed tooling project file is unchanged (U5, fix 2)

The CI-equivalent run found that the InternalsVisibleTo item in ArcForges.Web.Tooling.csproj changed a
reviewed dependency input (tools/ArcForges.Web.Tooling/ArcForges.Web.Tooling.csproj) of the dependency policy,
and the change is not a dependency change. The project file is restored to its reviewed content, and the same
visibility is declared by one assembly attribute in Profiles/TarArchive.cs, the file that defines the internal
writer and reader. No receipt changes.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
… carries the connection check; the retirement is recorded (U5, docs)

- docs/cloud-hello.md: the current behaviour states that /hello and /cloud-hello are static C# pages since U5,
  that the Blazor App is the only interactive browser application and supplies the connection check through
  the NuGet client ArcForges.Contracts.PublicApi 1.0.0-ci.287.1, and that the 2026-09-18 browser observation
  predates the retirement. The npm Browser SDK row is replaced.
- docs/web-40-site-parity.md: the interactive-control paragraph records the retirement (brief section 10,
  2026-10-09) instead of an open decision.
- docs/web-40-react-retirement.md: the coordinator adjudication of 2026-10-09 confirms the retirement.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
…andidate tests, and the Account and Chat suites assert the keyboard focus order (U6, review fixes)

- ci.yml: the Linux source job restores and builds tools/ArcForges.Web.Tooling (locked, Release) with the pinned SDK before
  npm run check, so tests/provenance/csharp-candidate.test.ts never depends on stale bin/ output.
- csharp-candidate.test.ts: a missing tool build fails at load with the build command.
- FocusOrder: bUnit tab-sequence, reading-order, positive-tabindex and default-button helpers.
- Account: anonymous, signed-in and failed-read states have their exact tab sequence; Sign out is a native type=button.
- Chat: the tab sequence is Name then Send, Cancel only while pending; Enter in the name field submits through the form's
  default Send button; the transcript is the polite live region. This is the successor of the retired keyboard greeting case.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
…source-job change, and the classified focus-order helper (U6, review fixes)

- eng/policy/dependency-reviews/web-40-admission-r11.json: the successor of web-40-admission-r10 (its bytes are unchanged).
  The only input change is .github/workflows/ci.yml, whose digest is computed by the repository's tooling/dependency-policy.ts
  digest(). The reviewer field names w-deku-20261008-rev-web-40, the pre-assigned independent reviewer.
- eng/policy/dependency-policy.json: reviewRecord, the ci.yml input digest and the review object follow the successor.
- eng/provenance/files.json: classifies the successor receipt and tests/ArcForges.Web.App.Tests/FocusOrder.cs.
- docs/web-40-react-retirement.md: the Account and Chat row records the focus-order successor of the retired keyboard case.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
…in the retirement decisions (U6, review fixes)

bUnit asserts the keyboard sequence but does not move browser focus. The retirement record now states the two focus losses that remain (a focused Send button disabled while pending, and a removed Sign out button after the session ends) as a recorded decision for the coordinator.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
…e document, with an AX-02 focus record and browser check (U6, review fix)

Send, Sign out and Try again could lose keyboard focus: a focused control that became disabled or was removed dropped focus to the body (AX-02). Send was disabled while a message was pending, Sign out was disabled while signing out and removed once the session ended, and Try again was removed when its read began.

- Chat: Send is aria-disabled while a message is pending, not disabled. The existing send guard refuses a second send, so no message is sent twice.
- Account: Sign out and Try again are one action element for the visit. Its label and aria-disabled state change; the element is never removed, so focus stays on it through the sign-out, a failed sign-out and a retry. A click on an aria-disabled control is refused by the handler.
- Both pages refresh their state when an operation starts, so the disabled state is rendered while the operation runs.
- app.css: aria-disabled buttons look disabled, so the visual state matches the refused action.
- bUnit (CI): the controls stay present and reachable at each step, and refused actions do nothing. bUnit re-parses markup on every render, so it cannot prove element identity; that is asserted by the local opt-in LocalFocusBrowserTests (Chromium, keyboard focus identity, same-origin answers fixed by routes).
- ArcForges.Web.Browser.Tests: ARCFORGES_CHROMIUM_PATH selects an installed Chromium for the focus check only. Local-only; never built in CI.
- docs/web-40-ax-02-focus.md records each control state, the checks and the run result. docs/web-40-react-retirement.md item 8 is updated; the Cancel focus drop in Chat stays open for the coordinator (a persistent aria-disabled Cancel or a combined Send/Cancel control).
- eng/provenance/files.json classifies the new record and browser test.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
…er: byte parity is not shown after removing the hydration nodes, and a coordinator waiver is needed (review fix, not complete)

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
…ests, the normalised-DOM site parity gate, the local axe accessibility suite (Deque.AxeCore.Playwright) and successor admission web-40-admission-r12 with policy and provenance updates (U6, captured as found; full local validation and the accessibility/parity runs not yet passed)

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
…les for the centrally pinned AngleSharp (CentralTransitive, same version 1.7.0, no new admission; locked restore fails without it) (U6, fix)

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
… browser tests (U6, fix)

- SiteParityTests: the anchor href check read Lines[0], which is the html element because the parser wraps the anchor in html, head and body. It now checks the anchor's own line for the verbatim href and that no stylesheet placeholder applies to a non-stylesheet href. The parity rule is unchanged.
- LocalAccessibilityBrowserTests: Deque.AxeCore.Commons 4.13.0 exposes AxeResult.Violations, Passes, Incomplete, Inapplicable and AxeResultNode.Nodes as arrays, so .Count bound to the LINQ method group (CS1503, CS8978, CS0019). These now use .Length.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
…and repair the r12 NuGet contentHash rows (successor admission web-40-admission-r13)

- SiteParityTests: the React stylesheet named by the prerender and the candidate's content-hashed stylesheet are excused
  on their own side only, through CompareFileSets. Every other file-set rule stays strict: the reference-only files must be
  exactly the React runtime files and the candidate may have no other file. An unmatched pair fails, and the pair bytes are
  still compared. Four unit tests cover the pair handling.
- web-40-admission-r13 (successor of r12, reviewer w-deku-20261008-rev-web-40): the contentHash of Deque.AxeCore.Commons,
  Deque.AxeCore.Playwright, Newtonsoft.Json and System.IO.Abstractions is the restored lock value (confirmed against
  .nupkg.metadata), not the .nupkg.sha512 value r12 recorded. Three stale bound lock digests (App.Tests, Operations.Tests,
  Ui.Tests) are re-bound. dependency-policy.json points at r13.
- eng/provenance/files.json: r13 is classified; the entry for docs/web-40-accessibility.md is removed because the file was
  never committed (the WCAG 2.2 AA per-screen record is still to be written and must be re-listed with it).

Checks run: SiteParityTests (10 passed, parity reference set, 0 skipped); NuGetClosureAdmissionTests.RestoredClosureMatchesTheAdmittedRecords
(passed); node tooling/dependency-policy.ts (passed); provenance audit (passed); node --test tooling/dependency-policy.test.ts (15 passed).

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
… App input boundaries to 3:1, and name the axe rules per screen (U-fix)

- app.css: .field input and .input-row input borders move from #b9c1b4 (1.85:1 on white, 1.66:1 on the page) and #a3b1a4 (2.20:1 on the field fill) to #6b7a72 (4.51:1 on white, 4.44:1 on #fffdf7, 4.06:1 on the page). The disabled button fill #6b7a72 (4.06:1 on the page) and the focus outline #235844 (7.41:1 on the page, 7.43:1 on the error banner) already met 3:1 and are unchanged.
- index.html: the unhandled-error Dismiss is <button type="button" class="dismiss" aria-label="Dismiss">. The Blazor selector #blazor-error-ui .dismiss is unchanged (blazor.webassembly.js binds its onclick to that selector). app.css resets the button so it looks as the anchor did. No other anchor without an href exists in the App, Ui or Site sources.
- LocalAccessibilityBrowserTests: each axe result writes a second line with the sorted rule IDs that passed, were incomplete, inapplicable and violated. New axe states: Chat failed send, and the error banner (shown through window.Blazor._internal.dotNetCriticalError, then Dismiss is focused and Enter hides the banner). The zero-violation assertion is kept. These local opt-in tests are built but not run in this commit.
- HostPageAccessibilityTests: Dismiss markup, no href-less anchors in the host page, and 3:1 checks of the boundaries, fills and focus rings parsed from app.css.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
… region so axe's region rule passes

- index.html: the unhandled-error text sat directly in #blazor-error-ui, outside every landmark, region, live region or alert, and the Reload anchor (href="") was flagged by the axe 4.13.0 region rule once the banner text moved. The message and the Reload link are now inside <span role="alert">, an inline element, so the banner's markup, its #blazor-error-ui selector, the .dismiss button, its fixed position and its appearance are unchanged.
- HostPageAccessibilityTests: TheErrorBannerMessageIsAnAlertAndNoTextSitsDirectlyInTheBanner asserts the alert holds the message and the Reload link, that the banner has no text node of its own, and that every link in the banner sits inside the alert.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
deku2026 and others added 2 commits October 9, 2026 07:37
…-state axe rule names, and list the record in provenance

- docs/web-40-accessibility.md: 14 screen states (Account, Chat including the failed send, the App error banner, Site), each with the axe counts and the rule names that passed, were inapplicable, were incomplete and were violated (zero violations). pass (axe) cells are derived from the state's passed list; target-size and audio-caption were not evaluated by the run and are not claimed.
- Adds pass (browser test) for the banner Dismiss keyboard assertion, the one check that is neither axe nor inspection.
- Records the tool versions, the local opt-in status (P2-017), and that the Chrome version of the run is not recorded.
- Open judgement items and the human AT session stay as not verified. Adds an Operations follow-up: its banner Dismiss (index.html:18) has the pre-fix markup and is outside WEB.40 scope.
- eng/provenance/files.json: lists docs/web-40-accessibility.md after Directory.Packages.props (as 33c57df did) and tests/ArcForges.Web.App.Tests/HostPageAccessibilityTests.cs, which 49f72bf added without an inventory entry, so the provenance audit's inventory matches git.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
…convention for all admitted rows, a restored-nuspec comparison in the policy suite, and the browser-resources-r11 gitleaks path exception (review fixes)

- nuspecSha256 for all 71 admitted closure and pack-download rows is the SHA-256 of the restored .nuspec bytes with the UTF-8 BOM kept. Sixteen rows held the BOM-stripped digest in r13; they are corrected. The CON.07 rows restored locally follow the same convention; the six not restored locally are kept and marked unverified.
- NuGetClosureAdmissionTests compares each admitted nuspec digest and each restored CON.07 digest with the NuGet packages folder, and requires the policy rows to equal the active receipt. Verified by a mutation run: a planted wrong digest fails two tests, and the restored file passes.
- .gitleaks.toml: the reviewed public source-hash path exception covers browser-resources-r11 as well as r1 to r10, with the same eleven exact-line regexes. Needs the named independent exact-head review (w-deku-20261008-rev-web-40); gitleaks is not installed locally.
- r13 stays as immutable history; dependency-policy.json and files.json point at r14.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
@deku2026

deku2026 commented Oct 9, 2026

Copy link
Copy Markdown
Contributor Author

Reviewed 6ff8e1e for [WEB.40] epoch 1: approved

Reviewer: independent session w-deku-20261008-rev-web-40, which authored none of this.

Rounds: round 1 at 58f44c4 had a material finding in the admission records (nuspec digest convention). It was fixed in 6ff8e1e by successor receipt web-40-admission-r14, the restored-nuspec comparison in the policy test, and the .gitleaks.toml path widening. Round 2 approved.

Verified at the exact head:

  • the delta since the coordinator's full-local and a11y-parity runs at 58f44c4: 5 files, with no UI or browser source change since 73173c2;
  • through the build slot:
    • check:dependencies (114 dependencies, 39 inputs);
    • the policy wrapper, with only the Node 24.20 vs 24.21 assertion stubbed, covering the licence, provenance, naming, lock-provenance and wrangler-routing audits;
    • typecheck;
    • test:dependencies 15/15 and npm tests 88/88;
    • licence-evaluated;
    • C# policy tests 107/107 and dotnet format;
  • admission:
    • 71/71 nuspecSha256 rows equal the restored .nuspec bytes;
    • contentHash equals the lock and .nupkg.metadata values (69 lock rows);
    • every receipt on the active path names this reviewer;
  • the accessibility record:
    • 14/14 states internally consistent;
    • all 113 "pass (axe)" cells backed by a default-enabled axe-core 4.13.0 rule that passed on that state;
    • no confirmed WCAG 2.2 AA failure on a required screen;
  • deploy readiness: the main-push-only, sealed-candidate and verify-before-upload guards are intact.

Non-blocking (recorded; see the PR body):

  • the r14 rationale sentence;
  • gitleaks is hosted-only;
  • accessibility record refinements (2.5.8 spacing exception; the skip-link criterion; commit or hash the axe report);
  • ADP-07 out-of-writes paths;
  • the retained Node build tooling (coordinator adjudication, P2-026);
  • a test that passes silently for unrestored CON.07 rows;
  • a comment naming the wrong emitter;
  • streaming has no WEB.40 surface (PRF.11).

Posted by the coordinator under the 2026-10-08 publication protocol.

@github-advanced-security

Copy link
Copy Markdown

You are seeing this message because GitHub Code Scanning has recently been set up for this repository, or this pull request contains the workflow file for the Code Scanning tool.

What Enabling Code Scanning Means:

  • The 'Security' tab will display more code scanning analysis results (e.g., for the default branch).
  • Depending on your configuration and choice of analysis tool, future pull requests will be annotated with code scanning analysis results.
  • You will be able to see the analysis results for the pull request's branch on this overview once the scans have completed and the checks have passed.

For more information about GitHub Code Scanning, check out the documentation.

deku2026 and others added 4 commits October 9, 2026 10:03
…e candidate suite

The csharp-candidate suite calls expectedIdentity("0.1.0-local") at module load. Under GITHUB_ACTIONS=true
the producer check requires a CI-shaped version, so the whole suite failed in the hosted Source job. The
suite builds a local candidate, so it now reads the identity with the GITHUB_* variables removed and
restores them before returning. Production identity code is unchanged.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
…er on Windows, with a recorded reason

The hosted windows-latest job failed EveryAdmittedNuspecDigestIsTheRestoredNuspecDigest: the admitted
microsoft.netcore.app.runtime.mono.browser-wasm/10.0.12 pack was not in a clean NuGet packages folder after the
locked restore. The SDK adds that implicit runtime pack as a NuGet download only when its packs folder lacks the
bundled version. The hosted Windows image preinstalls the wasm.tools workload into C:\Program Files\dotnet, which
holds the 10.0.12 browser-wasm pack, so Windows restores no NuGet copy. Ubuntu has no workload and restores it.

Reproduced with a clean NUGET_PACKAGES folder and the ci.yml C# step order: a restore with the 10.0.12 pack
visible to the SDK leaves the NuGet copy absent (the hosted message), and a restore without it downloads and digests it.

The test now carries an explicit PacksFolderRows table with one reason for Windows. It skips the NuGet copy only
for an explicit row on the current platform whose packs copy exists at the admitted version, and reads the packs
folder the way the SDK does (NetCoreTargetingPackRoot when set, else <dotnet root>/packs). Every other admitted row
still needs its NuGet copy and digest. A drift test requires each table row to be an admitted pack-download row.
The admitted policy rows, the receipts and ci.yml are unchanged.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
…lication record for the history scan

The hosted "Dependency audit and repository checks" job failed with gitleaks reporting one leak and no detail.
Offline emulation of gitleaks v8 git scanning (upstream default rules, the repository .gitleaks.toml, added lines of
origin/main..HEAD) found exactly one finding: rule generic-api-key at commit 2a5e17f in
eng/contracts/ArcForges.Contracts.PublicApi/1.0.0-ci.287.1/source.json, line 9. The key
"public/http/v1/native-auth.schema.json" contains the keyword auth, and its 64-hex value passes the entropy threshold.

It is a false positive. The value is the SHA-256 of the public native-auth schema, and it equals the SHA-256 of the
schema entry in the restored arcforges.contracts.publicapi 1.0.0-ci.287.1 package. All 21 recorded schema sources of
that publication match their packaged bytes.

The new allowlist is the narrowest form: its own [[allowlists]] block, targeting generic-api-key only, with AND over
the exact source.json path and the exact line with the exact digest. The emulator calibrates against the reviewed
count: without this file's allowlists, browser-resources-r11 gives 14 lines, as the r14 review recorded.
This is a governed scanner-exception change. The independent exact-head review is required before merge.
.gitleaks.toml is not bound by a provenance record or the dependency-policy inputs, so no successor receipt is written.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
…ts scanner exception and the packs-folder rows, require the pack content marker, and correct the r14 contentHash convention

Reviewed base 389b9fd (reviewer w-deku-20261008-rev-web-40). r15 supersedes r14 and leaves r14 unedited. No package, version, source, licence, contentHash, nuspecSha256, closure coordinate or bound input changed.

- .gitleaks.toml scanner exception (commit 389b9fd) is recorded in r15: rule generic-api-key, AND over the exact source.json path and one exact line regex; the value is the SHA-256 of the packaged native-auth schema (verified false positive).
- NuGetClosureAdmissionTests: PacksFolderRows gains an explicit Linux row beside the Windows row, each with a recorded reason. A packs copy counts only with its content marker runtimes/browser-wasm/native/dotnet.native.wasm, not the folder alone. The redundant final Assert.All is removed.
- r14 rationale correction: each row contentHash equals the .nupkg.metadata and lock contentHash, not the SHA-512 of the .nupkg file; nuspecSha256 is over the restored .nuspec bytes including the BOM.
- dependency-policy.json points reviewRecord and review at r15; eng/provenance/files.json lists r15.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
@deku2026

deku2026 commented Oct 9, 2026

Copy link
Copy Markdown
Contributor Author

Reviewed efe7872 for [WEB.40] epoch 1: approved (delta review over the approved 6ff8e1e, including the named exact-head exception review of the .gitleaks.toml change).

Reviewer: independent session w-deku-20261008-rev-web-40.

Hosted failures at 6ff8e1e, each root-caused and reproduced under CI conditions:

  • Node: csharp-candidate.test.ts built a local identity while GITHUB_ACTIONS=true. The test now reads it with the GITHUB_* variables cleared; the production assertion is unchanged.
  • Windows C# job: the hosted windows image preinstalls the wasm-tools workload, so no NuGet copy of the browser-wasm runtime pack is restored. An explicit per-OS PacksFolderRows table now covers it: a Windows row requiring the content marker runtimes/browser-wasm/native/dotnet.native.wasm, and a Linux row. The empty-folder and partial-folder negative controls fail closed.
  • gitleaks: one generic-api-key false positive at eng/contracts/ArcForges.Contracts.PublicApi/1.0.0-ci.287.1/source.json:9. The value is the SHA-256 of the packaged native-auth schema, verified against the cached nupkg. A path-, rule- and line-bound allowlist covers it, and the regex matches exactly one tracked line.
  • Receipt web-40-admission-r15 (chained from r14, which is unedited) records the scanner exception and the packs table, and corrects the r14 contentHash sentence.

Verified at efe7872, through the build slot:

  • dotnet format on all 11 projects, plus build;
  • the Policy suite 108/108, in both the default and the Windows-like packs layouts;
  • npm tests 88/88, with and without CI variables;
  • typecheck, check:dependencies and test:dependencies 15/15;
  • the policy() steps, with only the Node 24.21 pin assertion stubbed.

Non-blocking:

  • a zero-byte marker would still satisfy the Windows row;
  • gitleaks itself runs hosted.

Merge waits for the planning repair P2-026, because WEB.40's retained Node build tooling is recorded there (coordinator adjudication S17).

Posted by the coordinator under the 2026-10-08 publication protocol.

deku2026 and others added 2 commits October 9, 2026 13:58
…gs off the profile root

S20(a) of the migration brief (CLOUD.85 decisions D2 option A and D3):

- Add web-site-<sha256>.tar, a deterministic ustar archive of the C# static Site output written by the
  same TarArchive writer as web-profiles. Entries are the Site files in ordinal path order with fixed
  metadata. The candidate build seals it as a root member beside the assets directory, so the seal
  records its digest. The verifier regenerates the Site and refuses a second archive, a name that is not
  the digest, or any bytes other than the regenerated archive. The Cloudflare deploy still serves only
  the assets directory, so nothing served changes.
- Publish web-site-<sha256>.tar in the same main-push release as web-profiles, and only there (ci.yml).
- Exclude the root index.html.br and index.html.gz from the web-profiles bundle. The shell is not served
  at the root, and its encodings are not either. Every other publish entry is kept. Verify refuses an
  archive that serves either encoding at the root.
- Tests: Site archive determinism, name equal to digest, exact read-back; seal and verify coverage
  (changed, resealed, renamed, duplicated archive refused); bundle exclusion and its refusal.
- Successor receipt web-40-admission-r16 (chained from r15, r15 unchanged) for the changed ci.yml
  input, reviewer w-deku-20261008-rev-web-40, and the provenance inventory update. Docs updated.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
…d admit web-40-admission-r17

The release step located web-site-<sha256>.tar with ls, which actionlint flags as SC2012 in the hosted quality job. It now collects the archive with a nullglob bash array, requires exactly one archive, and requires the archive name to equal web-site-<sha256 of its bytes>.tar, all fail-closed. Only that script changes in ci.yml.

The successor receipt web-40-admission-r17 chains from r16 and changes only the ci.yml input hash, the review metadata that names its predecessor, and the dependency-policy and provenance inventory entries that point at it. r16 is not edited.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
@deku2026

deku2026 commented Oct 9, 2026

Copy link
Copy Markdown
Contributor Author

Reviewed 4cca32e for [WEB.40] epoch 1: approved (delta review over the approved efe7872; coordinator decision S20(a)).

Reviewer: independent session w-deku-20261008-rev-web-40.

The delta:

  • a deterministic web-site-<sha256>.tar built with the existing ustar writer, sealed as a candidate root member, and verified (exactly one archive, name equal to digest, bytes equal to the regenerated Site archive);
  • main-push release upload only;
  • the root index.html.br and index.html.gz excluded from web-profiles;
  • the Cloudflare deploy serving is unchanged (assets ./assets, no tar);
  • receipt web-40-admission-r16 is chained from r15 and changes only the ci.yml input.

Verified:

  • a full CI-shaped run in clean clones: restore, format, build, all C# tests, candidate build twice and compare, verify, bundle and repeat bytes, release simulation, deploy dry-run, typecheck, npm tests 94/94, and the dependency checks;
  • the Site archive is byte-identical across two separate checkouts;
  • the naming failure in clones came from the local-path git remote: with the GitHub origin it passes (0 findings).

Non-blocking:

  • The profile bundle differs across separate checkouts, though it is identical within one build. Razor-generated sources embed the absolute checkout path (#line, #pragma checksum), which changes the PDB, the MVID and the wasm fingerprint. This predates the delta, and no WEB.40 claim depends on cross-checkout identity. A follow-up PathMap or ContinuousIntegrationBuild setting would make it reproducible.
  • docs/profile-bundle.md "deterministic" should read "within one build".
  • The dry-run's .wrangler/tmp can reach the release candidate archive.

Posted by the coordinator under the 2026-10-08 publication protocol.

@deku2026

deku2026 commented Oct 9, 2026

Copy link
Copy Markdown
Contributor Author

Reviewed c58e528 for [WEB.40] epoch 1: approved (delta review over the approved 4cca32e).

Reviewer: independent session w-deku-20261008-rev-web-40.

Hosted actionlint reported SC2012 (ls) at ci.yml:408, the main-push Site archive step. The step now uses a nullglob array, requires exactly one web-site-*.tar, and requires its name to equal web-site-<sha256 of bytes>.tar.

The extracted step was run under bash -euo pipefail:

  • zero archives, a mismatched name, two archives, an unrelated file and a .tar.gz all fail;
  • one correctly named archive passes.

Also verified:

  • actionlint is clean on all workflows;
  • dependency-policy.ts passes (114 dependencies, 39 inputs);
  • auditProvenance passes (304 files, dirty false);
  • r17 differs from r16 only in the ci.yml input hash, which equals the committed file's sha256 (45457067...), and in its links and rationale. It names this reviewer.

Merge waits for planning repair P2-026 (coordinator adjudication S17).

Posted by the coordinator under the 2026-10-08 publication protocol.

@deku2026
deku2026 merged commit c858868 into main Oct 9, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants