Skip to content
Merged
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
103 changes: 103 additions & 0 deletions .github/workflows/prearm-source-controls.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
name: prearm-source-controls

on:
pull_request:
paths:
- .github/workflows/prearm-source-controls.yml
- Directory.Build.props
- .config/dotnet-tools.json
- eng/**
- src/Broker.Contracts/**
- src/Broker.Core/**
- src/Broker.Protocol/**
- src/Broker.Browser.Contracts/**
- src/Broker.Browser.Live/**
- src/Broker.Browser.Gateway/**
- src/Broker.Browser.Client/live-runtime.js
- tests/Broker.Browser.Live.Tests/**
- tests/Broker.NativeProof/tactical-native-journey.spec.js
- tests/Broker.NativeProof/tactical-native-readiness.test.mjs
workflow_dispatch:

permissions:
contents: read

concurrency:
group: prearm-source-controls-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

jobs:
prearm-source-controls:
runs-on: ubuntu-24.04
timeout-minutes: 10
env:
CI: 'true'
DOTNET_PROCESSOR_COUNT: '1'
steps:
- name: Check out the exact candidate
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
fetch-depth: 2
persist-credentials: false

- name: Bind the checked-out candidate
shell: bash
env:
EXPECTED_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
run: |
set -euo pipefail
test "$(git rev-parse HEAD)" = "$EXPECTED_SHA"
test -z "$(git status --porcelain --untracked-files=all)"

- name: Set up the exact .NET SDK
uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6
with:
dotnet-version: 10.0.401

- name: Set up the exact Node.js runtime
uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6
with:
node-version: 24.8.0

- name: Run the fourteen JavaScript source controls
shell: bash
run: node --test tests/Broker.NativeProof/tactical-native-readiness.test.mjs

- name: Restore and build the live boundary source
shell: bash
env:
# grpc-fsharp 0.2 targets net6.0; retain the LatestMajor policy in
# specs/001-tui-grpc-broker/readiness/feature-baseline.md.
DOTNET_ROLL_FORWARD: LatestMajor
run: |
set -euo pipefail
project=tests/Broker.Browser.Live.Tests/Broker.Browser.Live.Tests.fsproj
dotnet restore "$project" --disable-parallel
dotnet build "$project" --configuration Release --no-restore --maxcpucount:1

- name: Run exactly three synthetic compiled controls
shell: bash
run: |
set -euo pipefail
results=$(mktemp -d)
trap 'rm -rf "$results"' EXIT
dotnet test tests/Broker.Browser.Live.Tests/Broker.Browser.Live.Tests.fsproj \
--configuration Release --no-restore --no-build \
--filter 'FullyQualifiedName~pre-arm pairing|FullyQualifiedName~native report, authenticated WebSocket, authority ACK, and ID0 command share exact identities' \
--results-directory "$results" --logger 'trx;LogFileName=controls.trx'
python3 - "$results/controls.trx" <<'PY'
import sys
import xml.etree.ElementTree as ET
ns = {"t": "http://microsoft.com/schemas/VisualStudio/TeamTest/2010"}
root = ET.parse(sys.argv[1]).getroot()
expected = {
"gateway diagnostic classification.pre-arm pairing reasons are finite and callbacks cannot expose private material",
"production live boundary.pre-arm pairing emits closed reasons and preserves refusal without authority",
"production live boundary.native report, authenticated WebSocket, authority ACK, and ID0 command share exact identities",
}
results = root.findall("t:Results/t:UnitTestResult", ns)
if len(results) != 3 or {r.attrib["testName"] for r in results} != expected or any(r.attrib["outcome"] != "Passed" for r in results):
raise SystemExit("Expected exactly the three passing synthetic controls; zero, missing, skipped or extra tests refuse qualification")
print("PASS: exact three synthetic compiled source controls")
PY
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# BARC-01.5f pairing observability

The retained selected Count1 browser failure reached “Tactical catalogue unavailable”. Its evidence did not retain authentication, bootstrap, or client close classifications, so the causal branch remains **Unknown**. This change adds evidence for a future qualified attempt; it does not repair the catalogue or qualify native play.

The existing Gateway diagnostic callback now reports four closed pairing reasons: `malformed-auth`, `missing-auth`, `auth-refusal`, and `bootstrap-unavailable`. The last reason covers bootstrap provisioning or the first bootstrap send only. Callback exceptions remain contained, and original authentication, frame bounds, closes, and later output behavior remain intact. The additive `LiveDiagnostic` cases are declared in the signature; package publication remains a separate operation.

The existing browser fixture writes a separate `fsbar.barc-pairing-diagnostic/v1` sidecar. It contains only bounded counters, boolean status, the exact client close classification, and `nativeAcceptance: false`. Authentication and bootstrap payloads are discarded. Counters stop at 64; missing observation or overflow is Unknown. The exact `1008` / `invalid live frame` observer forwards the original client close once and contains diagnostic callback failures. That code identifies the client handler's refusal, not a particular invalid field or backend cause.

The sidecar is at most 4096 bytes, owner-private mode 0600 in an existing owner-private 0700 parent. Publication uses an exclusive temporary file, fsync, exclusive atomic linking, and directory fsync; an existing destination, symlink parent, or unsafe parent is refused. Sidecar failure does not replace an existing pairing failure. An absent sidecar therefore remains Unknown. Capture v1 and arm-failure v1 content grammars are unchanged.

Ordinary source qualification passed with private SDK 10.0.401 and signed-package FSharp.Core 10.1.401 (netstandard2.1 SHA256 `39b0b7f06c11bedd93f94b6f101fffd645f1c4437a5a6d3ee79259f855aeb11a`). A full private rebuild passed with no warnings. Three compiled controls passed: finite callback projection, genuine synthetic loopback authentication/provisioning refusals, and the existing positive native-report/WebSocket authority flow with a controlled first-bootstrap send-bound refusal. Fourteen exact JavaScript source-block controls passed, including payload omission, bounded counters, close forwarding, private/exclusive publication, unsafe-parent refusal, and preservation of the original pairing failure. Routine eligibility and operation-boundary fixtures passed.

The first compiler harness inherited a global compiler host and failed; those outputs were superseded by the full private rebuild. Initial CLI filters selected zero tests and are not counted. A missing-auth fixture initially sent an empty frame and correctly hit the existing malformed-frame check; the final control sends a valid non-auth envelope.

The protected source baseline is `9682516a8ebec133d33c1f8069f46f66477ea54a`. Historical scoped production reuse of compiled `3d723bcab5b25efc8d3467dcbc63790936f2add6` remains historical; this source change does not relabel those binaries or change the sealed candidate. Remote delivery, protected activation, a new qualified helper/capsule configuration, and any further Count1 effect remain pending separate root admission. Generated runtime bytes and SC2 authority claims remain Unknown.

PR 31 initially had no native checks because the existing RuntimeEvidence workflow did not select these changed paths. The focused `prearm-source-controls` workflow now covers Gateway, the live tests, their project/tool dependencies, and both JavaScript control inputs. It binds checkout to the exact PR head, runs the fourteen JavaScript controls, restores/builds once, and requires the exact three passing synthetic compiled controls in the test receipt. Missing, empty, skipped, or unexpected results refuse qualification. The existing RuntimeEvidence workflow is byte-identical.

Pipeline preflight is static: this is one linear job with no matrix, shared artifacts, reuse, or publication. A custom model would add maintenance without an interaction to check. The assessment is limited to configuration, trigger coverage, shell syntax, and exact test selection; reuse is expected on later changes to the same inputs, with a ten-minute assessment cap and a ten-minute hosted timeout. Local workflow commands passed: build with zero warnings, fourteen JavaScript tests, and three exact compiled tests. Result-refusal controls and compiled reflection of preserved union tags 0–14 also passed. Native exact-head acceptance remains pending; historical cause remains Unknown and useful native play remains 0/6.

Native run `37145445532` at head `ab81ef61eaf43571b3ddc7aba4619f8c6b778328` passed the JavaScript controls, then failed during protobuf generation: pinned `grpc-fsharp` 0.2.0 targets .NET 6 while the runner has .NET 8/9/10. Compiled controls were skipped and do not qualify that head. The build step now supplies `DOTNET_ROLL_FORWARD=LatestMajor`, matching the existing generator policy in `specs/001-tui-grpc-broker/readiness/feature-baseline.md` and `us1-progress.md`, through the step environment. No package runtimeconfig or dependency pin is changed. An isolated .NET-10-only setup control reproduced exit 150 without the setting and returned exit 0 with it using the unchanged generator and empty protobuf request. YAML, environment scope, preserved steps/pins, and shell syntax passed static checks; the repaired native run remains pending.
30 changes: 24 additions & 6 deletions src/Broker.Browser.Gateway/Gateway.fs
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,14 @@ module Gateway =
| ReceiveTaskFailed | ReceiveTaskCompleted | ReceiveTaskCancelled
| OutputTaskFailed | OutputTaskCompleted | OutputTaskCancelled
| RenewalTaskFailed | RenewalTaskCompleted | RenewalTaskCancelled
| MalformedAuth | MissingAuth | AuthRefusal | BootstrapUnavailable

let diagnosticNames diagnostic =
match diagnostic with
| MalformedAuth -> struct("pairing","malformed-auth")
| MissingAuth -> struct("pairing","missing-auth")
| AuthRefusal -> struct("pairing","auth-refusal")
| BootstrapUnavailable -> struct("pairing","bootstrap-unavailable")
| SubmitAccepted -> struct("submit","accepted")
| SubmitRefused reason ->
let name =
Expand Down Expand Up @@ -418,22 +423,35 @@ module Gateway =
try
let! received=receiveOne socket config.maxFrameBytes authCts.Token
match received with
| Error detail -> do! close socket WebSocketCloseStatus.InvalidPayloadData detail config.closeTimeout
| Error detail ->
diagnostic MalformedAuth
do! close socket WebSocketCloseStatus.InvalidPayloadData detail config.closeTimeout
| Ok bytes ->
let parsed=try Ok(LiveClientEnvelope.Parser.ParseFrom(bytes.ToArray())) with :? InvalidProtocolBufferException -> Error "malformed live authentication"
match parsed with
| Error detail -> do! close socket WebSocketCloseStatus.InvalidPayloadData detail config.closeTimeout
| Ok message when isNull message.Authenticate -> do! close socket WebSocketCloseStatus.PolicyViolation "live authentication required" config.closeTimeout
| Error detail ->
diagnostic MalformedAuth
do! close socket WebSocketCloseStatus.InvalidPayloadData detail config.closeTimeout
| Ok message when isNull message.Authenticate ->
diagnostic MissingAuth
do! close socket WebSocketCloseStatus.PolicyViolation "live authentication required" config.closeTimeout
| Ok message ->
match authenticateLive config origin message.Authenticate hub with
| Error detail -> do! close socket WebSocketCloseStatus.PolicyViolation detail config.closeTimeout
| Error detail ->
diagnostic AuthRefusal
do! close socket WebSocketCloseStatus.PolicyViolation detail config.closeTimeout
| Ok(sessionId,profile) ->
let state=BrokerState.liveControl hub
match LiveBoundary.provisionBootstrapForProfile profile sessionId config.perspectiveId state with
| Error detail -> do! close socket WebSocketCloseStatus.PolicyViolation detail config.closeTimeout
| Error detail ->
diagnostic BootstrapUnavailable
do! close socket WebSocketCloseStatus.PolicyViolation detail config.closeTimeout
| Ok bootstrap ->
use connectionCts=CancellationTokenSource.CreateLinkedTokenSource(context.RequestAborted)
do! send config.maxFrameBytes socket bootstrap connectionCts.Token
try do! send config.maxFrameBytes socket bootstrap connectionCts.Token
with error ->
diagnostic BootstrapUnavailable
return raise error
let mutable provisionalControllerId = bytesGuid bootstrap.Bootstrap.Controller.ControllerId
let outputs=Channel.CreateBounded<LiveServerEnvelope>(int (LiveControl.maxRetainedResults state) + 16)
let outputGate=obj()
Expand Down
1 change: 1 addition & 0 deletions src/Broker.Browser.Gateway/Gateway.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ module Gateway =
| ReceiveTaskFailed | ReceiveTaskCompleted | ReceiveTaskCancelled
| OutputTaskFailed | OutputTaskCompleted | OutputTaskCancelled
| RenewalTaskFailed | RenewalTaskCompleted | RenewalTaskCancelled
| MalformedAuth | MissingAuth | AuthRefusal | BootstrapUnavailable

val diagnosticNames : diagnostic:LiveDiagnostic -> struct(string * string)
val internal emitDiagnostic : diagnostics:(LiveDiagnostic -> unit) -> diagnostic:LiveDiagnostic -> unit
Expand Down
Loading