test(gateway): qualify production path artifacts

This commit is contained in:
sechmachine
2026-07-30 11:15:55 +07:00
parent baf4073f68
commit 0c4d87406e
17 changed files with 659 additions and 139 deletions
+7 -1
View File
@@ -1,7 +1,8 @@
.PHONY: format-check module-verify build vet test openspec verify .PHONY: format-check module-verify build vet test openspec gateway-linux verify
GO ?= go GO ?= go
OPENSPEC ?= openspec OPENSPEC ?= openspec
DIST_DIR ?= dist
format-check: format-check:
@test -z "$$(gofmt -l $$(find gateway -type f -name '*.go' -print))" @test -z "$$(gofmt -l $$(find gateway -type f -name '*.go' -print))"
@@ -21,4 +22,9 @@ test:
openspec: openspec:
$(OPENSPEC) validate --all --strict --no-interactive $(OPENSPEC) validate --all --strict --no-interactive
gateway-linux:
mkdir -p "$(DIST_DIR)"
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GOWORK=off $(GO) build -mod=readonly -trimpath -buildvcs=false -ldflags=-buildid= -o "$(DIST_DIR)/verse-gateway-linux-amd64" ./cmd/verse-gateway
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 GOWORK=off $(GO) build -mod=readonly -trimpath -buildvcs=false -ldflags=-buildid= -o "$(DIST_DIR)/verse-gateway-linux-arm64" ./cmd/verse-gateway
verify: format-check module-verify build vet test openspec verify: format-check module-verify build vet test openspec
+21 -8
View File
@@ -9,15 +9,28 @@ The gateway uses the following exact third-party dependency:
The existing Apple Xcode project was generated by the platform tool and remains The existing Apple Xcode project was generated by the platform tool and remains
reserved for a later native-client phase. reserved for a later native-client phase.
Apollo, Moonlight, and related repositories are external research references The gateway's Apollo fixtures and provider-scoped protocol implementation were
only. Before any source is copied, adapted, linked, embedded, or used to create independently implemented after consulting wire behavior in these exact
fixtures, update this file and the Phase 3C provenance record with: external research references. No implementation source from them is copied,
linked, or embedded:
- upstream repository and exact commit; - Apollo `adc5c5a0bd80831ce495434bb16aee2cd4175fb8`, GPLv3:
- source and destination paths; `src/rtsp.cpp`, `src/stream.cpp`, `src/audio.cpp`, `src/audio.h`,
- license and retained notices; `src/nvhttp.cpp`, `LICENSE`, and `NOTICE`.
- whether the work is copied, derived, or independently implemented; and - Moonlight Qt `c0c4d6056569bba40ac4458a3c225c05ff86df6d` with common-c
- modifications made by VerseVDI. pin `2ea47752c3051d72a64bcca190024e8b354fa1ef`, GPLv3:
`src/ControlStream.c`, `src/Video.h`, `src/RtpAudioQueue.h`,
`src/RtpAudioQueue.c`, `src/SdpGenerator.c`, `src/AudioStream.c`, and
`LICENSE.txt`. The consulted files were verified byte-identical to the
recorded local standalone common-c checkout `703a0694`.
- cgutman/enet `aca87840b57f045a1f7f9299e4b1b9b8e2a5e2f1`, MIT:
`protocol.c`, `peer.c`, `include/enet/protocol.h`, and `LICENSE`.
- nanors `17fc7d61afb2fdd9a9aff38fbd7d4f2ff73a4508`, MIT:
`rs.c`, `deps/obl/gf2_8_tables.h`, and `LICENSE`.
Any future copying, adaptation, linking, or embedding requires an updated
source/destination provenance record and retained license notices before the
change is accepted.
The VerseVDI Protocol is maintained in a separate repository and must be The VerseVDI Protocol is maintained in a separate repository and must be
consumed only through an exact immutable release. consumed only through an exact immutable release.
+66
View File
@@ -0,0 +1,66 @@
package gateway
import (
"bytes"
"debug/buildinfo"
"debug/elf"
"os"
"os/exec"
"path/filepath"
"testing"
)
func TestGatewayLinuxArtifactsAreReproduciblePureGoELF(t *testing.T) {
first, second := t.TempDir(), t.TempDir()
for _, output := range []string{first, second} {
command := exec.Command("make", "-C", "..", "gateway-linux", "DIST_DIR="+output)
command.Env = append(os.Environ(), "GOCACHE="+filepath.Join(t.TempDir(), "go-cache"))
if result, err := command.CombinedOutput(); err != nil {
t.Fatalf("gateway-linux: %v\n%s", err, result)
}
}
for _, architecture := range []struct {
name string
machine elf.Machine
}{
{name: "amd64", machine: elf.EM_X86_64},
{name: "arm64", machine: elf.EM_AARCH64},
} {
firstPath := filepath.Join(first, "verse-gateway-linux-"+architecture.name)
secondPath := filepath.Join(second, "verse-gateway-linux-"+architecture.name)
firstBytes, err := os.ReadFile(firstPath)
if err != nil {
t.Fatal(err)
}
secondBytes, err := os.ReadFile(secondPath)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(firstBytes, secondBytes) {
t.Fatalf("linux/%s gateway build is not byte reproducible", architecture.name)
}
executable, err := elf.Open(firstPath)
if err != nil {
t.Fatalf("linux/%s ELF: %v", architecture.name, err)
}
if executable.FileHeader.Machine != architecture.machine {
_ = executable.Close()
t.Fatalf("linux/%s machine = %s", architecture.name, executable.FileHeader.Machine)
}
if err := executable.Close(); err != nil {
t.Fatal(err)
}
info, err := buildinfo.ReadFile(firstPath)
if err != nil {
t.Fatalf("linux/%s Go build info: %v", architecture.name, err)
}
settings := make(map[string]string, len(info.Settings))
for _, setting := range info.Settings {
settings[setting.Key] = setting.Value
}
if settings["GOOS"] != "linux" || settings["GOARCH"] != architecture.name || settings["CGO_ENABLED"] != "0" {
t.Fatalf("linux/%s build settings = %#v", architecture.name, settings)
}
}
}
+161 -2
View File
@@ -5,8 +5,10 @@ import (
"encoding/binary" "encoding/binary"
"io" "io"
"os" "os"
"os/exec"
"path/filepath" "path/filepath"
"reflect" "reflect"
"strconv"
"strings" "strings"
"testing" "testing"
"time" "time"
@@ -41,7 +43,7 @@ func TestQualificationCatalogMatchesSection7(t *testing.T) {
} }
func TestQualificationProtocolVersionIsExplicitAndImmutable(t *testing.T) { func TestQualificationProtocolVersionIsExplicitAndImmutable(t *testing.T) {
const version = "v1.0.0-phase3c-gateway-rc.6" const version = "v1.0.0-phase3c-gateway-rc.8"
t.Setenv("VERSEVDI_QUALIFICATION_PROTOCOL_VERSION", version) t.Setenv("VERSEVDI_QUALIFICATION_PROTOCOL_VERSION", version)
got, err := qualificationProtocolVersion() got, err := qualificationProtocolVersion()
if err != nil || got != version { if err != nil || got != version {
@@ -104,7 +106,8 @@ func TestQualificationShortProcessingWritesRawArtifact(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
if summary.Count < 1 || summary.RawSamplesSHA256 == "" || summary.RawSamplesBytes < 1 || if summary.Count < 1 || summary.RawSamplesSHA256 == "" || summary.RawSamplesBytes < 1 ||
summary.ResourceSamples < 2 || summary.RawResourcesSHA256 == "" || summary.RawResourcesBytes < 1 { summary.ResourceSamples < 2 || summary.RawResourcesSHA256 == "" || summary.RawResourcesBytes < 1 ||
summary.CPUScope != "isolated gateway qualification process (gateway plus bounded fixture/client driver)" {
t.Fatalf("processing summary = %#v", summary) t.Fatalf("processing summary = %#v", summary)
} }
file, err := os.Open(rawPath) file, err := os.Open(rawPath)
@@ -194,6 +197,162 @@ func TestQualificationImpairmentIsDeterministicAndBounded(t *testing.T) {
} }
} }
func TestQualificationRTTIsNotSyntheticDoubleOneWayCompletion(t *testing.T) {
rawPath := filepath.Join(t.TempDir(), "latency.csv.gz")
observation, err := runQualificationImpairment(
t,
qualificationImpairmentProfiles()[1],
qualificationMediaProfiles()[0],
40,
rawPath,
)
if err != nil {
t.Fatal(err)
}
meanLatency := qualificationRawMeanLatency(t, rawPath)
delta := observation.ObservedRTT - 2*meanLatency
if delta < 0 {
delta = -delta
}
if delta < 5*time.Millisecond {
t.Fatalf("RTT %s was synthesized as twice one-way completion %s", observation.ObservedRTT, meanLatency)
}
}
func TestQualificationFixedSeedJitterIsObservableOnTraversedTraffic(t *testing.T) {
profile := qualificationImpairmentProfiles()[2]
observation, err := runQualificationImpairment(
t,
profile,
qualificationMediaProfiles()[0],
200,
filepath.Join(t.TempDir(), "jitter.csv.gz"),
)
if err != nil {
t.Fatal(err)
}
if observation.RTTSource != "apollo_enet_acknowledge" || observation.ObservedRTT <= 0 {
t.Fatalf("RTT observation is not transport-acknowledged: %#v", observation)
}
if observation.ObservedLatency < 5*time.Millisecond || observation.ObservedLatency > 100*time.Millisecond {
t.Fatalf("observed one-way latency %s does not reflect configured traversal", observation.ObservedLatency)
}
if observation.ObservedJitter < 5*time.Millisecond || observation.ObservedJitter > 80*time.Millisecond {
t.Fatalf("observed jitter %s is outside reviewed fixed-seed tolerance", observation.ObservedJitter)
}
if observation.ObservedOutOfOrder == 0 {
t.Fatal("fixed-seed jitter was serialized away before production traversal")
}
}
func TestQualificationCPUTracksConsumedWorkNotIdleCapacity(t *testing.T) {
started := time.Now()
before := qualificationRuntimeSample(started)
time.Sleep(100 * time.Millisecond)
idle := qualificationRuntimeSample(started).CPUSeconds - before.CPUSeconds
if idle > 50*time.Millisecond.Seconds() {
t.Fatalf("idle CPU consumption = %.6fs, want at most 0.05s", idle)
}
workBefore := qualificationRuntimeSample(started).CPUSeconds
deadline := time.Now().Add(75 * time.Millisecond)
var value uint64 = 1
for time.Now().Before(deadline) {
value = value*6364136223846793005 + 1
}
if value == 0 {
t.Fatal("bounded CPU work was optimized away")
}
work := qualificationRuntimeSample(started).CPUSeconds - workBefore
if work <= idle || work < 20*time.Millisecond.Seconds() {
t.Fatalf("bounded work CPU = %.6fs, idle = %.6fs", work, idle)
}
}
func TestQualificationCPUIsolationExcludesParentTestWork(t *testing.T) {
output := filepath.Join(t.TempDir(), "cpu.txt")
command := exec.Command(os.Args[0], "-test.run=^TestQualificationCPUChild$", "-test.count=1")
command.Env = append(os.Environ(), "VERSEVDI_QUALIFICATION_CPU_CHILD="+output)
if err := command.Start(); err != nil {
t.Fatal(err)
}
deadline := time.Now().Add(150 * time.Millisecond)
var value uint64 = 1
for time.Now().Before(deadline) {
value = value*2862933555777941757 + 3037000493
}
if value == 0 {
t.Fatal("parent CPU work was optimized away")
}
if err := command.Wait(); err != nil {
t.Fatalf("CPU child: %v", err)
}
raw, err := os.ReadFile(output)
if err != nil {
t.Fatal(err)
}
consumed, err := strconv.ParseFloat(string(raw), 64)
if err != nil {
t.Fatal(err)
}
if consumed > 50*time.Millisecond.Seconds() {
t.Fatalf("isolated idle qualification process consumed %.6fs while parent test was busy", consumed)
}
}
func TestQualificationCPUChild(t *testing.T) {
output := os.Getenv("VERSEVDI_QUALIFICATION_CPU_CHILD")
if output == "" {
return
}
started := time.Now()
before := qualificationRuntimeSample(started)
time.Sleep(200 * time.Millisecond)
consumed := qualificationRuntimeSample(started).CPUSeconds - before.CPUSeconds
if err := os.WriteFile(output, []byte(strconv.FormatFloat(consumed, 'f', 9, 64)), 0o600); err != nil {
t.Fatal(err)
}
}
func qualificationRawMeanLatency(t *testing.T, path string) time.Duration {
t.Helper()
file, err := os.Open(path)
if err != nil {
t.Fatal(err)
}
defer file.Close()
compressed, err := gzip.NewReader(file)
if err != nil {
t.Fatal(err)
}
raw, err := io.ReadAll(compressed)
if err != nil {
t.Fatal(err)
}
if err := compressed.Close(); err != nil {
t.Fatal(err)
}
var total time.Duration
var count int
for _, line := range strings.Split(string(raw), "\n")[1:] {
fields := strings.Split(line, ",")
if len(fields) < 5 || fields[4] != "delivered" {
continue
}
sent, sentErr := strconv.ParseInt(fields[1], 10, 64)
delivered, deliveredErr := strconv.ParseInt(fields[2], 10, 64)
if sentErr != nil || deliveredErr != nil || delivered < sent {
t.Fatalf("invalid raw latency row %q", line)
}
total += time.Duration(delivered - sent)
count++
}
if count == 0 {
t.Fatal("no delivered raw latency rows")
}
return total / time.Duration(count)
}
func TestQualificationSixImpairmentProfilesTraverseProductionPath(t *testing.T) { func TestQualificationSixImpairmentProfilesTraverseProductionPath(t *testing.T) {
profiles := qualificationImpairmentProfiles() profiles := qualificationImpairmentProfiles()
if len(profiles) != 6 { if len(profiles) != 6 {
+262 -85
View File
@@ -25,12 +25,12 @@ import (
"regexp" "regexp"
"runtime" "runtime"
"runtime/debug" "runtime/debug"
runtimemetrics "runtime/metrics"
"sort" "sort"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
"sync/atomic" "sync/atomic"
"syscall"
"testing" "testing"
"time" "time"
@@ -38,7 +38,7 @@ import (
) )
const ( const (
qualificationToolVersion = "versevdi-gateway-qualification/v3" qualificationToolVersion = "versevdi-gateway-qualification/v4"
qualificationImpairmentQueuePackets = 64 qualificationImpairmentQueuePackets = 64
qualificationImpairmentMaxPackets = 100_000 qualificationImpairmentMaxPackets = 100_000
qualificationImpairmentPacketCount = 10_000 qualificationImpairmentPacketCount = 10_000
@@ -116,6 +116,7 @@ type qualificationProcessingSummary struct {
RawSamplesBytes int64 `json:"raw_samples_bytes"` RawSamplesBytes int64 `json:"raw_samples_bytes"`
ResourceSamples int `json:"resource_samples"` ResourceSamples int `json:"resource_samples"`
CPUSeconds float64 `json:"cpu_seconds"` CPUSeconds float64 `json:"cpu_seconds"`
CPUScope string `json:"cpu_scope"`
PeakHeapBytes uint64 `json:"peak_heap_bytes"` PeakHeapBytes uint64 `json:"peak_heap_bytes"`
PeakGoroutines int `json:"peak_goroutines"` PeakGoroutines int `json:"peak_goroutines"`
Mallocs uint64 `json:"mallocs"` Mallocs uint64 `json:"mallocs"`
@@ -132,9 +133,12 @@ type qualificationImpairmentObservation struct {
Sent int `json:"sent"` Sent int `json:"sent"`
Delivered int `json:"delivered"` Delivered int `json:"delivered"`
Dropped int `json:"dropped"` Dropped int `json:"dropped"`
InjectedDropped int `json:"injected_dropped"`
InjectedReordered int `json:"injected_reordered"` InjectedReordered int `json:"injected_reordered"`
ObservedOutOfOrder int `json:"observed_out_of_order"` ObservedOutOfOrder int `json:"observed_out_of_order"`
ObservedLatency time.Duration `json:"observed_one_way_latency_ns"`
ObservedRTT time.Duration `json:"observed_rtt_ns"` ObservedRTT time.Duration `json:"observed_rtt_ns"`
RTTSource string `json:"rtt_source"`
ObservedJitter time.Duration `json:"observed_jitter_ns"` ObservedJitter time.Duration `json:"observed_jitter_ns"`
ObservedLossPercent float64 `json:"observed_loss_percent"` ObservedLossPercent float64 `json:"observed_loss_percent"`
ObservedReorderPercent float64 `json:"observed_reorder_percent"` ObservedReorderPercent float64 `json:"observed_reorder_percent"`
@@ -304,6 +308,11 @@ type qualificationApolloFixture struct {
closeOnce sync.Once closeOnce sync.Once
sentPackets atomic.Uint64 sentPackets atomic.Uint64
work protocol.ProviderSessionWork work protocol.ProviderSessionWork
controlImpairmentMu sync.Mutex
controlRTT time.Duration
controlJitter time.Duration
controlRandom uint64
} }
func newQualificationApolloFixture(t *testing.T, serverTLS, clientTLS *tls.Config, sessionID string, profile qualificationMediaProfile) *qualificationApolloFixture { func newQualificationApolloFixture(t *testing.T, serverTLS, clientTLS *tls.Config, sessionID string, profile qualificationMediaProfile) *qualificationApolloFixture {
@@ -551,10 +560,7 @@ func (f *qualificationApolloFixture) serveControl() {
switch command { switch command {
case 1: case 1:
case apolloENetSendReliable, apolloENetPing, apolloENetDisconnect: case apolloENetSendReliable, apolloENetPing, apolloENetDisconnect:
if _, err = f.control.WriteToUDP(sourceShapedENetAcknowledgePacket(7, 2, channel, sequence), remote); err != nil { f.sendControlAcknowledge(sourceShapedENetAcknowledgePacket(7, 2, channel, sequence), remote)
f.fail(err)
return
}
if command == apolloENetDisconnect { if command == apolloENetDisconnect {
return return
} }
@@ -566,6 +572,45 @@ func (f *qualificationApolloFixture) serveControl() {
} }
} }
func (f *qualificationApolloFixture) setControlImpairment(profile qualificationImpairmentProfile) {
f.controlImpairmentMu.Lock()
f.controlRTT = profile.RTT
f.controlJitter = profile.Jitter
f.controlRandom = qualificationImpairmentSeed
f.controlImpairmentMu.Unlock()
}
func (f *qualificationApolloFixture) controlResponseDelay() time.Duration {
f.controlImpairmentMu.Lock()
defer f.controlImpairmentMu.Unlock()
delay := f.controlRTT
if f.controlJitter > 0 {
f.controlRandom ^= f.controlRandom << 13
f.controlRandom ^= f.controlRandom >> 7
f.controlRandom ^= f.controlRandom << 17
width := uint64(f.controlJitter*2 + 1)
delay += time.Duration(f.controlRandom%width) - f.controlJitter
}
return max(delay, 0)
}
func (f *qualificationApolloFixture) sendControlAcknowledge(packet []byte, remote *net.UDPAddr) {
delay := f.controlResponseDelay()
copyPacket := append([]byte(nil), packet...)
copyRemote := *remote
go func() {
timer := time.NewTimer(delay)
defer timer.Stop()
<-timer.C
if f.closed.Load() {
return
}
if _, err := f.control.WriteToUDP(copyPacket, &copyRemote); err != nil {
f.fail(err)
}
}()
}
func (f *qualificationApolloFixture) serveMedia(socket *net.UDPConn, video bool) { func (f *qualificationApolloFixture) serveMedia(socket *net.UDPConn, video bool) {
buffer := make([]byte, apolloMediaMaximumPacket) buffer := make([]byte, apolloMediaMaximumPacket)
for { for {
@@ -751,9 +796,20 @@ func (f *qualificationFleet) Close() {
} }
func newQualificationPath(t *testing.T, profile qualificationMediaProfile, pacerKbps int64) *qualificationPath { func newQualificationPath(t *testing.T, profile qualificationMediaProfile, pacerKbps int64) *qualificationPath {
return newQualificationPathWithImpairment(t, profile, pacerKbps, nil)
}
func newQualificationImpairedPath(t *testing.T, profile qualificationMediaProfile, pacerKbps int64, impairment qualificationImpairmentProfile) *qualificationPath {
return newQualificationPathWithImpairment(t, profile, pacerKbps, &impairment)
}
func newQualificationPathWithImpairment(t *testing.T, profile qualificationMediaProfile, pacerKbps int64, impairment *qualificationImpairmentProfile) *qualificationPath {
t.Helper() t.Helper()
serverTLS, clientTLS := testTLS(t) serverTLS, clientTLS := testTLS(t)
fixture := newQualificationApolloFixture(t, serverTLS, clientTLS, "qualification-session", profile) fixture := newQualificationApolloFixture(t, serverTLS, clientTLS, "qualification-session", profile)
if impairment != nil {
fixture.setControlImpairment(*impairment)
}
backend := &qualificationTracingBackend{native: NewNativeApolloBackend()} backend := &qualificationTracingBackend{native: NewNativeApolloBackend()}
provider := NewApolloAdapter(backend, ProviderIdentity{}) provider := NewApolloAdapter(backend, ProviderIdentity{})
authority := protocol.SessionAuthority{ authority := protocol.SessionAuthority{
@@ -1049,6 +1105,23 @@ func runQualificationImpairment(t *testing.T, profile qualificationImpairmentPro
if _, err := buffered.WriteString("source_sequence,sent_ns,delivered_ns,processing_ns,outcome,delivery_order,bytes,queue_packets\n"); err != nil { if _, err := buffered.WriteString("source_sequence,sent_ns,delivered_ns,processing_ns,outcome,delivery_order,bytes,queue_packets\n"); err != nil {
return qualificationImpairmentObservation{}, err return qualificationImpairmentObservation{}, err
} }
type scheduledPacket struct {
index int
target time.Duration
}
type rawSample struct {
sent, delivered time.Duration
processing time.Duration
outcome string
deliveryOrder int
bytes int
queuePackets int
}
type receivedPacket struct {
index, deliveryOrder, queuePackets int
deliveredAt time.Time
processing time.Duration
}
state := qualificationImpairmentSeed state := qualificationImpairmentSeed
random := func() uint64 { random := func() uint64 {
state ^= state << 13 state ^= state << 13
@@ -1065,22 +1138,126 @@ func runQualificationImpairment(t *testing.T, profile qualificationImpairmentPro
Sent: packetCount, ConfiguredRTT: profile.RTT, ConfiguredJitter: profile.Jitter, Sent: packetCount, ConfiguredRTT: profile.RTT, ConfiguredJitter: profile.Jitter,
ConfiguredLossPercent: profile.LossPercent, ConfiguredReorder: profile.Reorder, ConfiguredLossPercent: profile.LossPercent, ConfiguredReorder: profile.Reorder,
ConfiguredCapacitySteps: append([]int(nil), profile.CapacitySteps...), ConfiguredCapacitySteps: append([]int(nil), profile.CapacitySteps...),
RTTSource: "apollo_enet_acknowledge",
} }
path := newQualificationPath(t, media, media.BitrateKbps) path := newQualificationImpairedPath(t, media, media.BitrateKbps, profile)
defer path.Close() defer path.Close()
started := time.Now()
payload := qualificationPayload(media) payload := qualificationPayload(media)
var deliveries []qualificationDeliverySample rawSamples := make([]rawSample, packetCount)
var totalRTT, totalJitter, previousRTT time.Duration jobs := make([]scheduledPacket, 0, packetCount)
previousDelivered := -1 jobByIndex := make([]int, packetCount)
deliveryOrder := 0 for index := range jobByIndex {
type pendingPacket struct { jobByIndex[index] = -1
index int
jitter time.Duration
} }
pending := pendingPacket{index: -1} for index := 0; index < packetCount; index++ {
jitter := time.Duration(0)
if profile.Jitter > 0 {
width := uint64(profile.Jitter*2 + 1)
jitter = time.Duration(random()%width) - profile.Jitter
}
rawSamples[index].sent = time.Duration(index) * spacing
if float64(random()%10_000) < profile.LossPercent*100 {
rawSamples[index].outcome = "injected_dropped"
observation.InjectedDropped++
continue
}
delay := max(profile.RTT/2+jitter, 0)
jobByIndex[index] = len(jobs)
jobs = append(jobs, scheduledPacket{index: index, target: time.Duration(index)*spacing + delay})
rawSamples[index].outcome = "traversal_dropped"
}
if profile.Reorder {
for index := 18; index+1 < packetCount; index += 20 {
first, second := jobByIndex[index], jobByIndex[index+1]
if first < 0 || second < 0 {
continue
}
earlier := min(jobs[first].target, jobs[second].target)
later := max(jobs[first].target, jobs[second].target)
jobs[second].target = earlier
jobs[first].target = later + time.Nanosecond
observation.InjectedReordered++
}
}
sort.SliceStable(jobs, func(first, second int) bool {
if jobs[first].target == jobs[second].target {
return jobs[first].index < jobs[second].index
}
return jobs[first].target < jobs[second].target
})
started := time.Now()
beforeMetrics := path.server.Metrics()
beforeIngress := path.session.mediaIngress.Load()
beforeRecovered := path.session.mediaRecovered.Load()
beforeEnqueued := path.session.mediaEnqueued.Load()
beforePacer := path.server.pacer.reservations.Load()
grace := max(2*profile.RTT+2*profile.Jitter, 2*time.Second)
lastTarget := time.Duration(packetCount) * spacing
if len(jobs) > 0 {
lastTarget = jobs[len(jobs)-1].target
}
receiveCtx, receiveCancel := context.WithDeadline(context.Background(), started.Add(lastTarget+grace))
defer receiveCancel()
receivedDone := make(chan struct {
packets []receivedPacket
err error
}, 1)
go func() {
result := struct {
packets []receivedPacket
err error
}{packets: make([]receivedPacket, 0, len(jobs))}
metrics := beforeMetrics
seen := make([]bool, packetCount)
for len(result.packets) < len(jobs) {
recovered, receiveErr := path.receivePayload(receiveCtx)
if receiveErr != nil {
if errors.Is(receiveErr, context.DeadlineExceeded) || errors.Is(receiveErr, context.Canceled) {
break
}
var timeout net.Error
if errors.As(receiveErr, &timeout) && timeout.Timeout() {
break
}
result.err = receiveErr
break
}
if len(recovered) != len(payload) {
result.err = errors.New("impaired payload length changed")
break
}
index := int(binary.BigEndian.Uint32(recovered[len(recovered)-4:]))
if index < 0 || index >= packetCount || jobByIndex[index] < 0 || seen[index] {
result.err = errors.New("impaired payload sequence invalid")
break
}
expected := append([]byte(nil), payload...)
binary.BigEndian.PutUint32(expected[len(expected)-4:], uint32(index))
if !bytes.Equal(recovered, expected) {
result.err = errors.New("impaired payload bytes changed")
break
}
seen[index] = true
current := path.server.Metrics()
processing := time.Duration(0)
if samples := current.ProcessingSamples - metrics.ProcessingSamples; samples > 0 {
processing = time.Duration((current.ProcessingDelayNanos - metrics.ProcessingDelayNanos) / samples)
}
metrics = current
result.packets = append(result.packets, receivedPacket{
index: index, deliveryOrder: len(result.packets) + 1,
deliveredAt: time.Now(), processing: processing, queuePackets: len(path.session.video),
})
}
receivedDone <- result
}()
stepAt := make(map[int]time.Time, len(profile.CapacitySteps)) stepAt := make(map[int]time.Time, len(profile.CapacitySteps))
deliver := func(packet pendingPacket) error { for _, packet := range jobs {
if delay := time.Until(started.Add(packet.target)); delay > 0 {
time.Sleep(delay)
}
if len(profile.CapacitySteps) == 2 { if len(profile.CapacitySteps) == 2 {
switch { switch {
case packet.index >= packetCount*2/3 && stepAt[profile.CapacitySteps[1]].IsZero(): case packet.index >= packetCount*2/3 && stepAt[profile.CapacitySteps[1]].IsZero():
@@ -1091,79 +1268,67 @@ func runQualificationImpairment(t *testing.T, profile qualificationImpairmentPro
stepAt[profile.CapacitySteps[0]] = time.Now() stepAt[profile.CapacitySteps[0]] = time.Now()
} }
} }
sentAt := started.Add(time.Duration(packet.index) * spacing)
target := sentAt.Add(profile.RTT/2 + packet.jitter)
if delay := time.Until(target); delay > 0 {
time.Sleep(delay)
}
current := append([]byte(nil), payload...) current := append([]byte(nil), payload...)
binary.BigEndian.PutUint32(current[len(current)-4:], uint32(packet.index)) binary.BigEndian.PutUint32(current[len(current)-4:], uint32(packet.index))
trace, processing, err := path.traverse(t, current) if _, err := path.emit(t, current); err != nil {
if err != nil || !trace.PayloadPreserved || !trace.ProductionPacer { receiveCancel()
if err == nil { return qualificationImpairmentObservation{}, err
err = errors.New("impaired packet bypassed production gateway path")
}
return err
} }
deliveredAt := time.Now() }
rtt := 2 * deliveredAt.Sub(sentAt) received := <-receivedDone
totalRTT += rtt receiveCancel()
if previousRTT != 0 { if received.err != nil {
delta := rtt - previousRTT return qualificationImpairmentObservation{}, received.err
}
var deliveries []qualificationDeliverySample
var totalLatency, totalJitter, previousLatency time.Duration
previousDelivered := -1
for _, packet := range received.packets {
sample := &rawSamples[packet.index]
sample.delivered = packet.deliveredAt.Sub(started)
sample.processing = packet.processing
sample.outcome = "delivered"
sample.deliveryOrder = packet.deliveryOrder
sample.bytes = media.PacketBytes
sample.queuePackets = packet.queuePackets
latency := sample.delivered - sample.sent
totalLatency += latency
if previousLatency != 0 {
delta := latency - previousLatency
if delta < 0 { if delta < 0 {
delta = -delta delta = -delta
} }
totalJitter += delta totalJitter += delta
} }
previousRTT = rtt previousLatency = latency
if previousDelivered >= 0 && packet.index < previousDelivered { if previousDelivered >= 0 && packet.index < previousDelivered {
observation.ObservedOutOfOrder++ observation.ObservedOutOfOrder++
} }
previousDelivered = packet.index previousDelivered = packet.index
observation.Delivered++ deliveries = append(deliveries, qualificationDeliverySample{At: packet.deliveredAt, Bytes: int64(media.PacketBytes + frameHeaderSize)})
deliveryOrder++ observation.MaxQueuePackets = max(observation.MaxQueuePackets, packet.queuePackets)
wireBytes := int64(len(current) + frameHeaderSize)
deliveries = append(deliveries, qualificationDeliverySample{At: deliveredAt, Bytes: wireBytes})
queuePackets := int(path.session.mediaQueueMaximum.Load())
if _, err := fmt.Fprintf(buffered, "%d,%d,%d,%d,delivered,%d,%d,%d\n", packet.index,
sentAt.Sub(started).Nanoseconds(), deliveredAt.Sub(started).Nanoseconds(),
processing.Nanoseconds(), deliveryOrder, len(current), queuePackets); err != nil {
return err
}
observation.MaxQueuePackets = max(observation.MaxQueuePackets, queuePackets)
return nil
} }
for index := 0; index < packetCount; index++ { observation.Delivered = len(received.packets)
jitter := time.Duration(0) observation.Dropped = packetCount - observation.Delivered
if profile.Jitter > 0 { completedAt := started
width := uint64(profile.Jitter*2 + 1) if observation.Delivered > 0 {
jitter = time.Duration(random()%width) - profile.Jitter completedAt = received.packets[len(received.packets)-1].deliveredAt
}
if float64(random()%10_000) < profile.LossPercent*100 {
observation.Dropped++
if _, err := fmt.Fprintf(buffered, "%d,%d,0,0,dropped,0,0,%d\n", index, time.Duration(index)*spacing, path.session.mediaQueueMaximum.Load()); err != nil {
return qualificationImpairmentObservation{}, err
}
continue
}
packet := pendingPacket{index: index, jitter: jitter}
if profile.Reorder && index%20 == 18 {
pending = packet
continue
}
if err := deliver(packet); err != nil {
return qualificationImpairmentObservation{}, err
}
if pending.index >= 0 {
if err := deliver(pending); err != nil {
return qualificationImpairmentObservation{}, err
}
pending.index = -1
observation.InjectedReordered++
}
} }
if pending.index >= 0 { afterMetrics := path.server.Metrics()
if err := deliver(pending); err != nil { if observation.Delivered > 0 && (path.session.mediaIngress.Load() <= beforeIngress ||
path.session.mediaRecovered.Load() <= beforeRecovered ||
path.session.mediaEnqueued.Load() <= beforeEnqueued ||
afterMetrics.ProcessingSamples <= beforeMetrics.ProcessingSamples ||
path.server.pacer.reservations.Load() <= beforePacer ||
afterMetrics.MediaPackets <= beforeMetrics.MediaPackets) {
return qualificationImpairmentObservation{}, errors.New("impaired traffic bypassed a production gateway stage")
}
observation.ObservedRTT = path.session.Telemetry().ControlRTT
for index, sample := range rawSamples {
if _, err := fmt.Fprintf(buffered, "%d,%d,%d,%d,%s,%d,%d,%d\n", index,
sample.sent.Nanoseconds(), sample.delivered.Nanoseconds(), sample.processing.Nanoseconds(),
sample.outcome, sample.deliveryOrder, sample.bytes, sample.queuePackets); err != nil {
return qualificationImpairmentObservation{}, err return qualificationImpairmentObservation{}, err
} }
} }
@@ -1178,28 +1343,32 @@ func runQualificationImpairment(t *testing.T, profile qualificationImpairmentPro
} }
closed = true closed = true
if observation.Delivered > 0 { if observation.Delivered > 0 {
observation.ObservedRTT = totalRTT / time.Duration(observation.Delivered) observation.ObservedLatency = totalLatency / time.Duration(observation.Delivered)
if observation.Delivered > 1 { if observation.Delivered > 1 {
observation.ObservedJitter = totalJitter / time.Duration(observation.Delivered-1) observation.ObservedJitter = totalJitter / time.Duration(observation.Delivered-1)
} }
observation.ObservedThroughputKbps = float64(observation.Delivered*media.PacketBytes*8) / time.Since(started).Seconds() / 1000 observation.ObservedThroughputKbps = float64(observation.Delivered*media.PacketBytes*8) / completedAt.Sub(started).Seconds() / 1000
} }
observation.ObservedLossPercent = float64(observation.Dropped) * 100 / float64(packetCount) observation.ObservedLossPercent = float64(observation.Dropped) * 100 / float64(packetCount)
observation.ObservedReorderPercent = float64(observation.ObservedOutOfOrder) * 100 / float64(packetCount) observation.ObservedReorderPercent = float64(observation.ObservedOutOfOrder) * 100 / float64(packetCount)
if packetCount >= qualificationImpairmentPacketCount { if packetCount >= qualificationImpairmentPacketCount {
capacityFactor := 1.0 capacityFactor := 1.0
if len(profile.CapacitySteps) > 0 { if len(profile.CapacitySteps) > 0 {
inverseRates := 1.0 rates := 1.0
for _, reduction := range profile.CapacitySteps { for _, reduction := range profile.CapacitySteps {
inverseRates += 100 / float64(100-reduction) rates += float64(100-reduction) / 100
} }
capacityFactor = float64(len(profile.CapacitySteps)+1) / inverseRates capacityFactor = rates / float64(len(profile.CapacitySteps)+1)
} }
expectedThroughput := float64(media.BitrateKbps) * (1 - profile.LossPercent/100) * capacityFactor expectedThroughput := float64(media.BitrateKbps) * (1 - profile.LossPercent/100) * capacityFactor
lowerThroughput := expectedThroughput * 0.90 lowerThroughput := expectedThroughput * 0.90
upperThroughput := expectedThroughput * 1.05 upperThroughput := expectedThroughput * 1.05
if observation.ObservedThroughputKbps < lowerThroughput || observation.ObservedThroughputKbps > upperThroughput { if observation.ObservedThroughputKbps < lowerThroughput || observation.ObservedThroughputKbps > upperThroughput {
return qualificationImpairmentObservation{}, fmt.Errorf("observed throughput %.2f outside [%.2f,%.2f]", observation.ObservedThroughputKbps, lowerThroughput, upperThroughput) return qualificationImpairmentObservation{}, fmt.Errorf(
"observed throughput %.2f outside [%.2f,%.2f] over %s with %d delivered/%d injected drops and steps %v",
observation.ObservedThroughputKbps, lowerThroughput, upperThroughput,
completedAt.Sub(started), observation.Delivered, observation.InjectedDropped, stepAt,
)
} }
} }
observation.RawSamples = filepath.Base(rawPath) observation.RawSamples = filepath.Base(rawPath)
@@ -1401,7 +1570,11 @@ func runQualificationProcessing(t *testing.T, profile qualificationMediaProfile,
summary.RawResourcesSHA256 = resourceSum summary.RawResourcesSHA256 = resourceSum
summary.RawResourcesBytes = resourceSize summary.RawResourcesBytes = resourceSize
summary.ResourceSamples = len(resources) summary.ResourceSamples = len(resources)
summary.CPUScope = "isolated gateway qualification process (gateway plus bounded fixture/client driver)"
firstResource, lastResource := resources[0], resources[len(resources)-1] firstResource, lastResource := resources[0], resources[len(resources)-1]
if firstResource.CPUSeconds < 0 || lastResource.CPUSeconds < 0 {
return qualificationProcessingSummary{}, errors.New("process CPU usage unavailable")
}
summary.CPUSeconds = math.Max(0, lastResource.CPUSeconds-firstResource.CPUSeconds) summary.CPUSeconds = math.Max(0, lastResource.CPUSeconds-firstResource.CPUSeconds)
summary.Mallocs = lastResource.Mallocs - firstResource.Mallocs summary.Mallocs = lastResource.Mallocs - firstResource.Mallocs
summary.AllocatedBytes = lastResource.Allocated - firstResource.Allocated summary.AllocatedBytes = lastResource.Allocated - firstResource.Allocated
@@ -1441,12 +1614,16 @@ func runQualificationWarmup(t *testing.T, path *qualificationPath, profile quali
} }
func qualificationRuntimeSample(started time.Time) qualificationResourceSample { func qualificationRuntimeSample(started time.Time) qualificationResourceSample {
cpu := []runtimemetrics.Sample{{Name: "/cpu/classes/total:cpu-seconds"}} var usage syscall.Rusage
runtimemetrics.Read(cpu) cpuSeconds := -1.0
if syscall.Getrusage(syscall.RUSAGE_SELF, &usage) == nil {
cpuSeconds = float64(usage.Utime.Sec+usage.Stime.Sec) +
float64(usage.Utime.Usec+usage.Stime.Usec)/1_000_000
}
var memory runtime.MemStats var memory runtime.MemStats
runtime.ReadMemStats(&memory) runtime.ReadMemStats(&memory)
return qualificationResourceSample{ return qualificationResourceSample{
Elapsed: time.Since(started), CPUSeconds: cpu[0].Value.Float64(), Elapsed: time.Since(started), CPUSeconds: cpuSeconds,
HeapBytes: memory.HeapAlloc, Goroutines: runtime.NumGoroutine(), HeapBytes: memory.HeapAlloc, Goroutines: runtime.NumGoroutine(),
Mallocs: memory.Mallocs, Allocated: memory.TotalAlloc, Mallocs: memory.Mallocs, Allocated: memory.TotalAlloc,
} }
-34
View File
@@ -113,11 +113,6 @@ func (m *Metrics) observeProviderState(state string) {
} }
} }
type Pacer struct {
bytesPerSecond int64
last time.Time
}
// fairPacer is the gateway's one shared, equal-tier media scheduler. Each // fairPacer is the gateway's one shared, equal-tier media scheduler. Each
// session can hold only its existing bounded provider media channel while it // session can hold only its existing bounded provider media channel while it
// waits for the next reservation, so a slow client cannot grow a global queue. // waits for the next reservation, so a slow client cannot grow a global queue.
@@ -207,32 +202,3 @@ func (p *fairPacer) wait(ctx context.Context, flow string, bytes int) error {
} }
return nil return nil
} }
func NewPacer(kbps int64) *Pacer {
if kbps < 1 {
return &Pacer{}
}
return &Pacer{bytesPerSecond: kbps * 1000 / 8}
}
func (p *Pacer) Wait(ctx context.Context, bytes int) error {
if p.bytesPerSecond < 1 || bytes < 1 {
return nil
}
now := time.Now()
if p.last.IsZero() || now.After(p.last) {
p.last = now
}
delay := time.Duration(float64(bytes) / float64(p.bytesPerSecond) * float64(time.Second))
p.last = p.last.Add(delay)
if wait := time.Until(p.last); wait > 0 {
timer := time.NewTimer(wait)
defer timer.Stop()
select {
case <-ctx.Done():
return ctx.Err()
case <-timer.C:
}
}
return nil
}
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-07-30
@@ -0,0 +1,31 @@
## Context
The native production path and fair pacer already exist. The defect was evidence collection: provider sends waited synchronously for client delivery, RTT was arithmetic, CPU was available capacity, and the repository had no canonical Linux build target.
## Goals / Non-Goals
**Goals:**
- Observe concurrent source-shaped traversal without a duplicate transport.
- Distinguish actual one-way delivery, acknowledged RTT, queue/processing/pacing, and consumed process CPU.
- Produce reproducible inspectable Linux artifacts for the deployment architectures.
**Non-Goals:**
- A new transport, scheduler, dependency, codec operation, scanner, signer, or container framework.
- Live provider/client/firewall evidence or Connection Server image remediation.
## Decisions
- Keep the existing provider fixture and production path; overlap its UDP sender with the public QUIC receiver.
- Apply fixed-seed impairment before provider UDP injection and derive delivery statistics from decoded payload sequence and timestamps.
- Use native ENet acknowledgement timing for RTT instead of doubling one-way completion.
- Use OS process user plus system CPU for the isolated qualification command; retain memory, goroutine, and allocation series separately.
- Build both Linux architectures with `CGO_ENABLED=0`, `GOWORK=off`, `-trimpath`, no VCS stamping, and an empty build ID, then inspect ELF and embedded Go settings.
- Delete the dead exported pacer rather than consolidate it with the sole production `fairPacer`.
## Risks / Trade-offs
- [Short RTT smoke runs contain ENet smoothing history] → Classify the metric as acknowledged transport RTT and enforce profile tolerances on the full frozen run.
- [Process CPU includes the bounded fixture/client harness] → Run only the named qualification test in an isolated process and label the scope exactly; never call it host-wide or binary-only CPU.
- [No qualifying vulnerability scanner is installed] → Record unscanned status and deterministic dependency/artifact evidence without zero-finding claims.
@@ -0,0 +1,25 @@
## Why
The prior Phase 3C-G artifacts measured serialized simulator timing, runtime CPU capacity, and a Darwin test binary rather than the actual deployable gateway candidate. VER-009, VER-010, VER-015, and OPS-009 require observed production traversal, bounded resource evidence, and exact artifact provenance before engineering exit.
## What Changes
- Drive impairment concurrently through the source-shaped provider UDP, native recovery, bounded queue, production pacer, QUIC, and public decoder.
- Measure RTT from actual Apollo ENet acknowledgements, one-way latency and jitter from delivery observations, and CPU from isolated process user/system consumption.
- Remove the unused legacy pacer so qualification and production share one scheduler.
- Build and inspect reproducible pure-Go Linux amd64 and arm64 gateway artifacts.
- Report dependency, scanner, architecture, and security evidence only when actually generated.
## Capabilities
### New Capabilities
- `gateway-deployment-artifact`: Reproducible, inspectable Linux gateway build and evidence requirements.
### Modified Capabilities
- `gateway-qualification`: Replace serialized/synthetic timing and CPU-capacity evidence with actual bounded traversal and process-consumption observations.
## Impact
This affects only the GPLv3 Data Plane qualification harness, resource evidence, production scheduler inventory, and gateway packaging target. It adds no dependency, cgo, sidecar, codec operation, direct provider route, Server dependency, or proprietary source. Live Apollo, macOS-client, physical-firewall, promotion scanning/signing, and Connection Server Phase 3C-C images remain outside this deterministic gate.
@@ -0,0 +1,15 @@
## ADDED Requirements
### Requirement: Reproducible pure-Go Linux gateway artifacts
The candidate SHALL build the gateway with the normal immutable Protocol module boundary for Linux amd64 and arm64 using `CGO_ENABLED=0`, deterministic path/VCS/build-ID settings, and no sidecar. Two independent builds of each architecture MUST be byte-identical.
#### Scenario: Both Linux architectures are built
- **WHEN** the canonical gateway Linux target runs twice from the same frozen source and dependency inputs
- **THEN** both amd64 and arm64 outputs are byte-identical pure-Go ELF executables with matching embedded GOOS, GOARCH, and cgo settings
### Requirement: Artifact evidence is inspected and truthful
Candidate evidence SHALL record exact source and Protocol revisions, artifact hashes, architecture, embedded dependency inventory, container configuration when built, and the actual scanner/signing status. It MUST NOT claim an SBOM, vulnerability result, signature, image architecture, or deployment that was not produced and inspected.
#### Scenario: Supplemental scanner is unavailable
- **WHEN** no qualifying vulnerability scanner is available in the frozen environment
- **THEN** the artifact remains explicitly unscanned, deterministic compiler/dependency/boundary evidence is retained, and no zero-finding security claim is emitted
@@ -0,0 +1,23 @@
## MODIFIED Requirements
### Requirement: Fixed media processing qualification
The qualification harness SHALL drive pinned-mTLS Apollo management, encrypted RTSP, ENet, and provider UDP through native source validation, `readUDPMedia`, recovery/FEC, bounded production queues, the production fair pacer, Verse framing/QUIC, and a public or independent client decoder for 1080p60 H.264 at 20 Mbps, 1440p120 HEVC at 50 Mbps, and 4K60 HEVC at 80 Mbps. After a recorded warm-up, the frozen candidate SHALL run each profile for ten wall-clock minutes, preserve encoded payload bytes, retain every monotonic processing sample plus bounded process CPU, memory, goroutine, allocation, and provider-queue observations, and report count, min, median, p90, p95, p99, max, mean, standard deviation, timing overhead, and observed bitrate. Processing begins at complete provider-unit receipt and ends at QUIC handoff, excluding client transit and pacing. CPU SHALL be actual OS user plus system consumption of the isolated gateway qualification process and MUST NOT be GOMAXPROCS-times-wall capacity or unrelated parent test work. Any bypass, payload mutation, wall-duration violation, bitrate outside both lower and upper bounds, or p95 above 5 ms SHALL fail.
#### Scenario: Healthy fixed profile
- **WHEN** a frozen candidate runs one fixed profile for the normative duration in the isolated qualification command
- **THEN** the harness emits compressed raw path and resource samples plus a summary tied to the exact command, CPU scope, topology, source commit, immutable Protocol version, environment, and payload hash
#### Scenario: Processing gate failure
- **WHEN** any production path stage lacks a per-traversal observation, payload integrity fails, duration or bitrate bounds fail, measured p95 exceeds 5 ms, or idle capacity is reported as consumed CPU
- **THEN** the qualification command exits unsuccessfully without recording a passing candidate
### Requirement: Bounded impairment qualification
The harness SHALL run exactly the baseline, latency, jitter, loss, reorder, and constrained Section 7.2 profiles once by applying fixed-seed impairment at the source-shaped provider network boundary while traffic concurrently traverses the production gateway path. Baseline SHALL cover all three media profiles and the other profiles SHALL cover 1080p60. The harness MUST NOT serialize a complete provider-to-client traversal per source unit. Each artifact SHALL retain raw impairment and queue observations and record tool version, exact command/configuration, environment, candidate commit, immutable Protocol version, direction, queue discipline, topology, fixed seed, observed one-way latency, acknowledged Apollo ENet RTT, jitter, loss, reorder, throughput, drops, and capacity-step statistics.
#### Scenario: Complete six-profile run
- **WHEN** the frozen candidate runs impairment qualification
- **THEN** one result exists for each named profile, configured jitter remains observable within reviewed fixed-seed tolerances, RTT comes from real request/response acknowledgement timing, and raw statistics come from actual traversal
#### Scenario: Unsupported or unbounded configuration
- **WHEN** a profile name, packet count, queue bound, loss, reorder, or bandwidth step falls outside the fixed catalog
- **THEN** the harness rejects it before allocating or running traffic
@@ -0,0 +1,18 @@
## 1. Qualification observations
- [x] 1.1 Reproduce synthetic doubled one-way RTT and serialized traversal
- [x] 1.2 Overlap source-shaped provider sends with public QUIC receive and retain actual delivery observations
- [x] 1.3 Measure RTT from Apollo ENet acknowledgements and verify fixed-seed jitter
- [x] 1.4 Replace CPU capacity with isolated OS process consumption and prove idle/work/parent isolation
## 2. Production and packaging
- [x] 2.1 Prove the legacy pacer has no production caller and delete it
- [x] 2.2 Build and inspect byte-reproducible pure-Go Linux amd64 and arm64 gateway artifacts
- [ ] 2.3 Generate final dependency/artifact evidence and record scanner/signing status truthfully
## 3. Frozen verification
- [ ] 3.1 Pass focused race/resource/impairment tests and complete Data Plane verification
- [ ] 3.2 Freeze immutable Protocol consumer inputs and run corrected Section 7 qualification once
- [ ] 3.3 Retain raw artifacts and explicit live Apollo/macOS/firewall deferral
+1 -1
View File
@@ -1,7 +1,7 @@
# apollo-stream-policy Specification # apollo-stream-policy Specification
## Purpose ## Purpose
TBD - created by archiving change phase3c-gateway-audit-remediation. Update Purpose after archive. Define fail-closed translation of authenticated immutable stream policy into source-backed Apollo launch and ANNOUNCE behavior.
## Requirements ## Requirements
### Requirement: Apollo launch consumes the effective policy ### Requirement: Apollo launch consumes the effective policy
The native Apollo backend SHALL derive ANNOUNCE resolution, frame rate, supported codec, selected bitrate, and audio profile from authenticated `ProviderSessionWork`, and MUST NOT substitute local defaults. The native Apollo backend SHALL derive ANNOUNCE resolution, frame rate, supported codec, selected bitrate, and audio profile from authenticated `ProviderSessionWork`, and MUST NOT substitute local defaults.
+1 -1
View File
@@ -1,7 +1,7 @@
# audio-fec-resilience Specification # audio-fec-resilience Specification
## Purpose ## Purpose
TBD - created by archiving change phase3c-gateway-audit-remediation. Update Purpose after archive. Define bounded Apollo audio FEC state advancement and recovery after sustained incomplete blocks.
## Requirements ## Requirements
### Requirement: Bounded audio FEC state advances after loss ### Requirement: Bounded audio FEC state advances after loss
The Apollo audio recovery window SHALL remain bounded and SHALL evict the oldest incomplete block when accepting a newer block would otherwise be rejected. The Apollo audio recovery window SHALL remain bounded and SHALL evict the oldest incomplete block when accepting a newer block would otherwise be rejected.
@@ -0,0 +1,19 @@
# gateway-deployment-artifact Specification
## Purpose
Define the reproducible Linux gateway artifacts and truthful inspection evidence required for a deterministic Phase 3C engineering candidate.
## Requirements
### Requirement: Reproducible pure-Go Linux gateway artifacts
The candidate SHALL build the gateway with the normal immutable Protocol module boundary for Linux amd64 and arm64 using `CGO_ENABLED=0`, deterministic path/VCS/build-ID settings, and no sidecar. Two independent builds of each architecture MUST be byte-identical.
#### Scenario: Both Linux architectures are built
- **WHEN** the canonical gateway Linux target runs twice from the same frozen source and dependency inputs
- **THEN** both amd64 and arm64 outputs are byte-identical pure-Go ELF executables with matching embedded GOOS, GOARCH, and cgo settings
### Requirement: Artifact evidence is inspected and truthful
Candidate evidence SHALL record exact source and Protocol revisions, artifact hashes, architecture, embedded dependency inventory, container configuration when built, and the actual scanner/signing status. It MUST NOT claim an SBOM, vulnerability result, signature, image architecture, or deployment that was not produced and inspected.
#### Scenario: Supplemental scanner is unavailable
- **WHEN** no qualifying vulnerability scanner is available in the frozen environment
- **THEN** the artifact remains explicitly unscanned, deterministic compiler/dependency/boundary evidence is retained, and no zero-finding security claim is emitted
@@ -1,7 +1,7 @@
# gateway-heartbeat-telemetry Specification # gateway-heartbeat-telemetry Specification
## Purpose ## Purpose
TBD - created by archiving change phase3c-gateway-audit-remediation. Update Purpose after archive. Define truthful measured gateway egress, disjoint delay semantics, and bounded authenticated heartbeat telemetry.
## Requirements ## Requirements
### Requirement: Heartbeat egress is observed ### Requirement: Heartbeat egress is observed
Authenticated gateway heartbeat telemetry SHALL calculate egress from monotonic transmitted-byte deltas over monotonic elapsed time and MUST NOT report configured capacity as measured traffic. Authenticated gateway heartbeat telemetry SHALL calculate egress from monotonic transmitted-byte deltas over monotonic elapsed time and MUST NOT report configured capacity as measured traffic.
+6 -6
View File
@@ -5,22 +5,22 @@ Define the deterministic processing, impairment, pacing, and evidence boundaries
for qualifying a frozen Phase 3C gateway candidate. for qualifying a frozen Phase 3C gateway candidate.
## Requirements ## Requirements
### Requirement: Fixed media processing qualification ### Requirement: Fixed media processing qualification
The qualification harness SHALL drive pinned-mTLS Apollo management, encrypted RTSP, ENet, and provider UDP through native source validation, `readUDPMedia`, recovery/FEC, bounded production queues, the production fair pacer, Verse framing/QUIC, and a public or independent client decoder for 1080p60 H.264 at 20 Mbps, 1440p120 HEVC at 50 Mbps, and 4K60 HEVC at 80 Mbps. After a recorded warm-up, the frozen candidate SHALL run each profile for ten wall-clock minutes, preserve encoded payload bytes, retain every monotonic processing sample plus bounded CPU, memory, goroutine, allocation, and provider-queue observations, and report count, min, median, p90, p95, p99, max, mean, standard deviation, timing overhead, and observed bitrate. Processing begins at complete provider-unit receipt and ends at QUIC handoff, excluding client transit. Any bypass, payload mutation, wall-duration violation, bitrate outside both lower and upper bounds, or p95 above 5 ms SHALL fail. The qualification harness SHALL drive pinned-mTLS Apollo management, encrypted RTSP, ENet, and provider UDP through native source validation, `readUDPMedia`, recovery/FEC, bounded production queues, the production fair pacer, Verse framing/QUIC, and a public or independent client decoder for 1080p60 H.264 at 20 Mbps, 1440p120 HEVC at 50 Mbps, and 4K60 HEVC at 80 Mbps. After a recorded warm-up, the frozen candidate SHALL run each profile for ten wall-clock minutes, preserve encoded payload bytes, retain every monotonic processing sample plus bounded process CPU, memory, goroutine, allocation, and provider-queue observations, and report count, min, median, p90, p95, p99, max, mean, standard deviation, timing overhead, and observed bitrate. Processing begins at complete provider-unit receipt and ends at QUIC handoff, excluding client transit and pacing. CPU SHALL be actual OS user plus system consumption of the isolated gateway qualification process and MUST NOT be GOMAXPROCS-times-wall capacity or unrelated parent test work. Any bypass, payload mutation, wall-duration violation, bitrate outside both lower and upper bounds, or p95 above 5 ms SHALL fail.
#### Scenario: Healthy fixed profile #### Scenario: Healthy fixed profile
- **WHEN** a frozen candidate runs one fixed profile for the normative duration - **WHEN** a frozen candidate runs one fixed profile for the normative duration in the isolated qualification command
- **THEN** the harness emits compressed raw path and resource samples plus a summary tied to the exact command, topology, source commit, immutable Protocol version, environment, and payload hash - **THEN** the harness emits compressed raw path and resource samples plus a summary tied to the exact command, CPU scope, topology, source commit, immutable Protocol version, environment, and payload hash
#### Scenario: Processing gate failure #### Scenario: Processing gate failure
- **WHEN** any production path stage lacks a per-traversal observation, payload integrity fails, duration or bitrate bounds fail, or measured p95 exceeds 5 ms - **WHEN** any production path stage lacks a per-traversal observation, payload integrity fails, duration or bitrate bounds fail, measured p95 exceeds 5 ms, or idle capacity is reported as consumed CPU
- **THEN** the qualification command exits unsuccessfully without recording a passing candidate - **THEN** the qualification command exits unsuccessfully without recording a passing candidate
### Requirement: Bounded impairment qualification ### Requirement: Bounded impairment qualification
The harness SHALL run exactly the baseline, latency, jitter, loss, reorder, and constrained Section 7.2 profiles once by applying impairment at the source-shaped provider UDP boundary while traffic traverses the production gateway path. Baseline SHALL cover all three media profiles and the other profiles SHALL cover 1080p60. Each artifact SHALL retain raw impairment and queue observations and record tool version, exact command/configuration, environment, candidate commit, immutable Protocol version, direction, queue discipline, topology, fixed seed, and observed RTT, jitter, loss, reorder, throughput, drops, and capacity-step statistics. The harness SHALL run exactly the baseline, latency, jitter, loss, reorder, and constrained Section 7.2 profiles once by applying fixed-seed impairment at the source-shaped provider network boundary while traffic concurrently traverses the production gateway path. Baseline SHALL cover all three media profiles and the other profiles SHALL cover 1080p60. The harness MUST NOT serialize a complete provider-to-client traversal per source unit. Each artifact SHALL retain raw impairment and queue observations and record tool version, exact command/configuration, environment, candidate commit, immutable Protocol version, direction, queue discipline, topology, fixed seed, observed one-way latency, acknowledged Apollo ENet RTT, jitter, loss, reorder, throughput, drops, and capacity-step statistics.
#### Scenario: Complete six-profile run #### Scenario: Complete six-profile run
- **WHEN** the frozen candidate runs impairment qualification - **WHEN** the frozen candidate runs impairment qualification
- **THEN** one result exists for each named profile, with no Cartesian expansion and with raw observed rather than configured statistics from the real traversal - **THEN** one result exists for each named profile, configured jitter remains observable within reviewed fixed-seed tolerances, RTT comes from real request/response acknowledgement timing, and raw statistics come from actual traversal
#### Scenario: Unsupported or unbounded configuration #### Scenario: Unsupported or unbounded configuration
- **WHEN** a profile name, packet count, queue bound, loss, reorder, or bandwidth step falls outside the fixed catalog - **WHEN** a profile name, packet count, queue bound, loss, reorder, or bandwidth step falls outside the fixed catalog