Skip to content

test(smoke): sentinel invariants gate (plan A / task #175) - #78

Merged
arcavenai merged 1 commit into
developfrom
feat/smoke-sentinels
Jul 5, 2026
Merged

arcavenai merged 1 commit into
developfrom
feat/smoke-sentinels

Conversation

@arcavenai

Copy link
Copy Markdown
Member

Summary

Adds a fast (<5s) operator-boundary smoke gate that runs on every PR and push to develop. Guards the class of regressions caught by the tutorial-walk smoke on 2026-07-04 (S1/S3/O1/O3 in .factory/STATE.md drift register): --help exiting non-zero to stderr, missing --version flag, hardcoded version banner literals, missing ldflags wiring.

Plan A of 4 from the BMAD party-mode planning session on smoke-test strategy (task #173/#174). Plan A is the sentinel invariants gate; C is the blast-radius declaration process gate; B (stratified harness) and #179 (orchestrator smoke-before-green doc edit) chain after A merges. Plan D (spec-as-test) deferred.

What the gate asserts (12 invariants, all behavioral)

ID Behavior asserted Guards against
INV-1 switchboard --help → exit 0, stdout non-empty, stderr empty BC-2.07.002 EC-003 Ruling A regression
INV-2 switchboard --version → exit 0, stdout starts with switchboard S3-class: hardcoded literal instead of args[0]-derived basename
INV-3 sbctl --help → exit 0, stdout non-empty, stderr empty Same class as INV-1 for sbctl
INV-4 sbctl --version → exit 0, stdout starts with sbctl O3-class: sbctl --version flag was missing entirely pre-#77
INV-5 sbctl (no args) → exit 2, stderr contains available subcommands: interface-definitions.md §174 usage-error contract
INV-6 sbctl <unknown> → exit 2, stderr contains unknown subcommand interface-definitions.md §174 unknown-subcommand contract
INV-7 switchboard {access,router,console,control} --help short-circuits before I/O with exit 0 Subcommand help regressions (daemons that open sockets before parsing --help)
INV-8 Both --version banners contain the CI-injected ${VERSION} substring Missing -ldflags "-X main.version=..." wiring — the task #163 sbctl-a packaging defect at pre-merge time

Contract rules (per Murat's RISK-1 from BMAD 2026-07-04)

  • Behavioral only. Assertions check exit code, stream direction, substring presence. Cosmetic diffs (whitespace, ordering, colour codes) forbidden — reviewers reject cosmetic sentinels in PR review.
  • Paired docs. New invariants require a paired update to docs/architecture.md §Smoke invariants. An invariant without a documented rationale is a phantom.
  • Fail loud. Failure blocks merge. On failure, CI uploads the JSONL report as artifact smoke-report (7-day retention) for post-mortem.
  • Isolated. Runs in a fresh mktemp -d tmpdir with trap cleanup. Never touches ~/.switchboard, ~/.sbctl, or user state.
  • Exit codes. 0 all-pass, 1 regression, 2 harness broken. CI distinguishes "smoke found a bug" from "smoke can't run."

Files

  • test/smoke/invariants.sh — the harness (213 lines, executable), 12 checks including INV-7 parameterised over 4 subcommands and INV-8 over 2 binaries.
  • justfile — just smoke-quick inlines both binary builds with a computed VERSION stamp so INV-8 asserts the injected value flows through both banners. Inlined rather than depending on a build-both recipe because just evaluates VERSION := env(...) at invocation time and shell-computed values don't back-propagate to dependency recipes.
  • .github/workflows/ci.yml — new "Sentinel invariants (smoke gate)" step after Validate build, stamps VERSION=smoke-ci-${GITHUB_SHA::7}. Report upload on failure via actions/upload-artifact@v7 (SHA-pinned). Also fixes latent defect: Validate build only built switchboard — now builds sbctl too.
  • docs/architecture.md — new §Smoke invariants section with contract rules, invariants table, add-a-new-invariant procedure, historical context.
  • .gitignore — .smoke/ (report artifacts).

Regression-proof

Locally verified INV-8 correctly fails when ldflags wiring is removed. All 12 invariants pass in <1s under just smoke-quick.

Sentinels: 12 passed, 0 failed

Test plan

  • Local just smoke-quick passes 12/12
  • Local go vet ./... clean
  • Local golangci-lint run ./... — 0 issues
  • Commit signed (ED25519)
  • CI Quality Gate passes (fmt, vet, lint, tests+race+coverage, validate build, sentinel invariants)
  • Auto-merge on green

What this unblocks

  • #179 — orchestrator smoke-before-green doc edit to per-story-delivery.md
  • #176 — Plan B stratified harness (Tier 2 daemon startup + Tier 3 tutorial-walk)

Refs #175 #163 #146

Adds a fast (<5s) operator-boundary smoke gate that runs on every PR and
push to develop. Guards against the class of regressions caught by the
tutorial-walk smoke on 2026-07-04 (S1/S3/O1/O3 in .factory/STATE.md drift
register): --help exiting non-zero to stderr, missing --version flag,
hardcoded version banner literals, missing ldflags wiring.

Contract (per Murat's RISK-1 from BMAD party 2026-07-04):
- Assertions are behavioral only — exit code, stream direction, substring
  presence. Cosmetic diffs (whitespace, ordering, colour) are forbidden.
  Reviewers reject cosmetic sentinels in PR review.
- New invariants require a paired update to docs/architecture.md §Smoke
  invariants. An invariant without a documented rationale is a phantom.
- Exit codes: 0 all-pass, 1 regression, 2 harness broken. CI distinguishes
  "smoke found a bug" from "smoke can't run."
- Isolated: runs in mktemp -d with trap cleanup; never touches user state.
- Failure-with-a-receipt: JSONL report written under .smoke/<ts>/, uploaded
  by CI on failure as artifact `smoke-report` (7-day retention).

Files:
- test/smoke/invariants.sh — the harness, 12 invariants INV-1..INV-8
  (INV-7 parameterised over access|router|console|control subcommands;
  INV-8 asserts both switchboard and sbctl banners).
- justfile — `just smoke-quick` inlines both binary builds with a
  computed VERSION stamp so INV-8 (ldflags wiring) asserts the injected
  value flows through both banners. Inlined rather than using a
  build-both dependency because just evaluates `VERSION := env(...)` at
  invocation time and the shell-computed STAMP would not flow into a
  dependency recipe.
- .github/workflows/ci.yml — new step runs after Validate build with
  stamp `smoke-ci-${GITHUB_SHA::7}`; smoke report uploaded on failure.
  Also fixes latent defect: Validate build only built switchboard, now
  builds sbctl too.
- docs/architecture.md — new §Smoke invariants section with contract
  rules, invariants table INV-1..INV-8 with defect-class column,
  add-a-new-invariant procedure, and historical context.
- .gitignore — .smoke/ (report artifacts).

Verified locally: 12/12 pass in <1s. INV-8 correctly fails when ldflags
wiring is removed (regression-proven).

Refs #175 #163 #146
@arcavenai
arcavenai requested a review from arcaven as a code owner July 5, 2026 02:43
@github-actions

github-actions Bot commented Jul 5, 2026

Copy link
Copy Markdown

Coverage Report

Total coverage: 81.8%

Package breakdown
github.com/arcavenae/switchboard/cmd/sbctl/admin.go:120:			String				100.0%
github.com/arcavenae/switchboard/cmd/sbctl/admin.go:122:			Set				100.0%
github.com/arcavenae/switchboard/cmd/sbctl/admin.go:130:			IsBoolFlag			100.0%
github.com/arcavenae/switchboard/cmd/sbctl/admin.go:133:			isTrue				100.0%
github.com/arcavenae/switchboard/cmd/sbctl/admin.go:152:			runAdmin			86.7%
github.com/arcavenae/switchboard/cmd/sbctl/admin.go:191:			runAdminSvtn			66.7%
github.com/arcavenae/switchboard/cmd/sbctl/admin.go:217:			runAdminSvtnCreate		87.5%
github.com/arcavenae/switchboard/cmd/sbctl/admin.go:269:			confirmSVTNShortIDValid		90.9%
github.com/arcavenae/switchboard/cmd/sbctl/admin.go:291:			runAdminSvtnDestroy		93.8%
github.com/arcavenae/switchboard/cmd/sbctl/admin.go:362:			runDestroyConfirmGate		94.7%
github.com/arcavenae/switchboard/cmd/sbctl/admin.go:406:			runAdminKey			71.4%
github.com/arcavenae/switchboard/cmd/sbctl/admin.go:435:			runAdminKeyRegister		94.4%
github.com/arcavenae/switchboard/cmd/sbctl/admin.go:483:			runAdminKeyRevoke		88.9%
github.com/arcavenae/switchboard/cmd/sbctl/admin.go:527:			runAdminKeyExpire		84.2%
github.com/arcavenae/switchboard/cmd/sbctl/client.go:104:			newSuccessEnvelope		100.0%
github.com/arcavenae/switchboard/cmd/sbctl/client.go:109:			newErrorEnvelope		100.0%
github.com/arcavenae/switchboard/cmd/sbctl/client.go:128:			loadEd25519Key			88.0%
github.com/arcavenae/switchboard/cmd/sbctl/client.go:198:			Authenticate			93.1%
github.com/arcavenae/switchboard/cmd/sbctl/client.go:278:			dispatch			92.0%
github.com/arcavenae/switchboard/cmd/sbctl/client.go:346:			connectAndRun			100.0%
github.com/arcavenae/switchboard/cmd/sbctl/console.go:58:			runConsole			85.7%
github.com/arcavenae/switchboard/cmd/sbctl/console.go:89:			runConsoleAttach		87.5%
github.com/arcavenae/switchboard/cmd/sbctl/console.go:113:			runConsoleDetach		75.0%
github.com/arcavenae/switchboard/cmd/sbctl/console.go:138:			runConsoleSwitch		87.5%
github.com/arcavenae/switchboard/cmd/sbctl/io.go:22:				defaultIO			100.0%
github.com/arcavenae/switchboard/cmd/sbctl/main.go:37:				Error				100.0%
github.com/arcavenae/switchboard/cmd/sbctl/main.go:38:				Unwrap				0.0%
github.com/arcavenae/switchboard/cmd/sbctl/main.go:41:				usageErrf			100.0%
github.com/arcavenae/switchboard/cmd/sbctl/main.go:45:				main				90.5%
github.com/arcavenae/switchboard/cmd/sbctl/main.go:136:				runSessions			100.0%
github.com/arcavenae/switchboard/cmd/sbctl/main.go:154:				writeSuccess			77.8%
github.com/arcavenae/switchboard/cmd/sbctl/main.go:170:				writeError			77.8%
github.com/arcavenae/switchboard/cmd/sbctl/paths_list.go:40:			runPathsList			100.0%
github.com/arcavenae/switchboard/cmd/sbctl/router_metrics.go:36:		runRouterMetrics		100.0%
github.com/arcavenae/switchboard/cmd/sbctl/router_status.go:57:			qualityFromPathEntry		100.0%
github.com/arcavenae/switchboard/cmd/sbctl/router_status.go:87:			formatP99			100.0%
github.com/arcavenae/switchboard/cmd/sbctl/router_status.go:98:			formatPathsTable		100.0%
github.com/arcavenae/switchboard/cmd/sbctl/router_status.go:119:		runRouterStatus			73.5%
github.com/arcavenae/switchboard/cmd/sbctl/router_status.go:243:		injectJSONField			87.5%
github.com/arcavenae/switchboard/cmd/switchboard/access.go:108:			tickIntervalFor			100.0%
github.com/arcavenae/switchboard/cmd/switchboard/access.go:128:			runAccess			92.0%
github.com/arcavenae/switchboard/cmd/switchboard/access.go:238:			runAccessWithConnector		92.5%
github.com/arcavenae/switchboard/cmd/switchboard/access.go:376:			buildAccessComponents		100.0%
github.com/arcavenae/switchboard/cmd/switchboard/access.go:391:			Log				0.0%
github.com/arcavenae/switchboard/cmd/switchboard/access.go:408:			buildRouter			100.0%
github.com/arcavenae/switchboard/cmd/switchboard/access.go:424:			startFramesBridge		50.0%
github.com/arcavenae/switchboard/cmd/switchboard/access.go:444:			startSweepTicker		100.0%
github.com/arcavenae/switchboard/cmd/switchboard/access.go:484:			startFramesDroppedTicker	100.0%
github.com/arcavenae/switchboard/cmd/switchboard/admin_handlers.go:120:		BuildAdminHandlers		100.0%
github.com/arcavenae/switchboard/cmd/switchboard/admin_handlers.go:141:		decodePublicKey			91.3%
github.com/arcavenae/switchboard/cmd/switchboard/admin_handlers.go:184:		makeRegisterHandler		94.4%
github.com/arcavenae/switchboard/cmd/switchboard/admin_handlers.go:226:		makeRevokeHandler		83.3%
github.com/arcavenae/switchboard/cmd/switchboard/admin_handlers.go:268:		makeExpireHandler		77.8%
github.com/arcavenae/switchboard/cmd/switchboard/admin_handlers.go:349:		makeListKeysHandler		78.9%
github.com/arcavenae/switchboard/cmd/switchboard/admin_handlers.go:412:		mapAdminError			100.0%
github.com/arcavenae/switchboard/cmd/switchboard/admin_handlers.go:465:		roleToString			80.0%
github.com/arcavenae/switchboard/cmd/switchboard/admin_handlers.go:481:		verifyCallerRole		100.0%
github.com/arcavenae/switchboard/cmd/switchboard/admin_handlers.go:491:		keyFingerprintAdmin		100.0%
github.com/arcavenae/switchboard/cmd/switchboard/admin_handlers.go:528:		resolveAndVerifyCallerRole	88.5%
github.com/arcavenae/switchboard/cmd/switchboard/admin_handlers.go:592:		resolveCallerAdmissionAnyRole	93.8%
github.com/arcavenae/switchboard/cmd/switchboard/admin_handlers.go:641:		Error				100.0%
github.com/arcavenae/switchboard/cmd/switchboard/admin_handlers.go:645:		Unwrap				100.0%
github.com/arcavenae/switchboard/cmd/switchboard/admin_handlers.go:658:		Error				100.0%
github.com/arcavenae/switchboard/cmd/switchboard/admin_handlers.go:662:		Unwrap				100.0%
github.com/arcavenae/switchboard/cmd/switchboard/admin_handlers.go:675:		Error				100.0%
github.com/arcavenae/switchboard/cmd/switchboard/admin_handlers.go:679:		Unwrap				100.0%
github.com/arcavenae/switchboard/cmd/switchboard/admin_handlers.go:721:		makeAdminSVTNCreateHandler	96.3%
github.com/arcavenae/switchboard/cmd/switchboard/admin_handlers.go:827:		makeAdminSVTNDestroyHandler	100.0%
github.com/arcavenae/switchboard/cmd/switchboard/admin_handlers.go:884:		validateSVTNName		100.0%
github.com/arcavenae/switchboard/cmd/switchboard/console_handlers.go:41:	BuildConsoleHandlers		60.0%
github.com/arcavenae/switchboard/cmd/switchboard/console_handlers.go:65:	verifyConsoleCallerRole		75.0%
github.com/arcavenae/switchboard/cmd/switchboard/console_handlers.go:86:	makeConsoleAttachHandler	77.8%
github.com/arcavenae/switchboard/cmd/switchboard/console_handlers.go:107:	makeConsoleDetachHandler	100.0%
github.com/arcavenae/switchboard/cmd/switchboard/console_handlers.go:121:	makeConsoleSwitchHandler	77.8%
github.com/arcavenae/switchboard/cmd/switchboard/main.go:28:			run				27.8%
github.com/arcavenae/switchboard/cmd/switchboard/main.go:181:			main				0.0%
github.com/arcavenae/switchboard/cmd/switchboard/metrics_wire.go:59:		newPathTrackerSource		100.0%
github.com/arcavenae/switchboard/cmd/switchboard/metrics_wire.go:69:		AllSnapshots			75.0%
github.com/arcavenae/switchboard/cmd/switchboard/metrics_wire.go:87:		SVTNMetrics			0.0%
github.com/arcavenae/switchboard/cmd/switchboard/metrics_wire.go:101:		Error				0.0%
github.com/arcavenae/switchboard/cmd/switchboard/metrics_wire.go:106:		Is				0.0%
github.com/arcavenae/switchboard/cmd/switchboard/metrics_wire.go:117:		wireMetricsHandlers		66.7%
github.com/arcavenae/switchboard/cmd/switchboard/mgmt_wire.go:67:		listenUnixMgmt			82.1%
github.com/arcavenae/switchboard/cmd/switchboard/mgmt_wire.go:137:		mgmtDefaultSocket		100.0%
github.com/arcavenae/switchboard/cmd/switchboard/mgmt_wire.go:153:		mgmtNetwork			100.0%
github.com/arcavenae/switchboard/cmd/switchboard/mgmt_wire.go:162:		resolveManagementSocket		100.0%
github.com/arcavenae/switchboard/cmd/switchboard/mgmt_wire.go:170:		mgmtListenAddr			100.0%
github.com/arcavenae/switchboard/cmd/switchboard/mgmt_wire.go:179:		buildMgmtListener		86.7%
github.com/arcavenae/switchboard/cmd/switchboard/mgmt_wire.go:208:		isMgmtLoopbackHost		85.7%
github.com/arcavenae/switchboard/cmd/switchboard/mgmt_wire.go:227:		parsePEMOperatorKeys		23.1%
github.com/arcavenae/switchboard/cmd/switchboard/mgmt_wire.go:255:		newMgmtServer			100.0%
github.com/arcavenae/switchboard/cmd/switchboard/mgmt_wire.go:311:		serveMgmtServer			100.0%
github.com/arcavenae/switchboard/cmd/switchboard/mgmt_wire.go:333:		startMgmtServer			100.0%
github.com/arcavenae/switchboard/cmd/switchboard/mgmt_wire.go:357:		runRouter			100.0%
github.com/arcavenae/switchboard/cmd/switchboard/mgmt_wire.go:377:		runConsole			80.8%
github.com/arcavenae/switchboard/cmd/switchboard/mgmt_wire.go:456:		runControl			85.0%
github.com/arcavenae/switchboard/docs/demo-evidence/S-5.02/stub_daemon.go:30:	main				0.0%
github.com/arcavenae/switchboard/docs/demo-evidence/S-5.02/stub_daemon.go:57:	serve				0.0%
github.com/arcavenae/switchboard/docs/demo-evidence/S-5.02/stub_daemon.go:101:	cannedResponse			0.0%
github.com/arcavenae/switchboard/internal/admission/admission.go:74:		Error				0.0%
github.com/arcavenae/switchboard/internal/admission/admission.go:82:		Unwrap				0.0%
github.com/arcavenae/switchboard/internal/admission/admission.go:113:		KeyRoleFromString		100.0%
github.com/arcavenae/switchboard/internal/admission/admission.go:128:		String				0.0%
github.com/arcavenae/switchboard/internal/admission/admission.go:176:		KeyExpiry			0.0%
github.com/arcavenae/switchboard/internal/admission/admission.go:182:		IsRevoked			100.0%
github.com/arcavenae/switchboard/internal/admission/admission.go:224:		NewAdmittedKeySet		100.0%
github.com/arcavenae/switchboard/internal/admission/admission.go:240:		RegisterKey			100.0%
github.com/arcavenae/switchboard/internal/admission/admission.go:272:		RevokeKey			100.0%
github.com/arcavenae/switchboard/internal/admission/admission.go:310:		RevokeKeyIfRoleMatches		0.0%
github.com/arcavenae/switchboard/internal/admission/admission.go:363:		Lookup				100.0%
github.com/arcavenae/switchboard/internal/admission/admission.go:390:		LookupByPubkey			100.0%
github.com/arcavenae/switchboard/internal/admission/admission.go:403:		IsAdmitted			88.9%
github.com/arcavenae/switchboard/internal/admission/admission.go:428:		GenerateChallenge		80.0%
github.com/arcavenae/switchboard/internal/admission/admission.go:457:		AdmitNode			96.2%
github.com/arcavenae/switchboard/internal/admission/admission.go:537:		RemoveSVTN			0.0%
github.com/arcavenae/switchboard/internal/admission/admission.go:547:		ListBySVTN			0.0%
github.com/arcavenae/switchboard/internal/admission/admission.go:567:		recordNonceUnlocked		77.8%
github.com/arcavenae/switchboard/internal/admission/failure_counter.go:28:	WithNow				100.0%
github.com/arcavenae/switchboard/internal/admission/failure_counter.go:73:	NewFailureCounter		100.0%
github.com/arcavenae/switchboard/internal/admission/failure_counter.go:117:	RecordHMACFailure		93.3%
github.com/arcavenae/switchboard/internal/admission/failure_counter.go:199:	evictLRU			84.6%
github.com/arcavenae/switchboard/internal/admission/failure_counter.go:226:	Timestamps			100.0%
github.com/arcavenae/switchboard/internal/admission/failure_counter.go:241:	SourceCount			100.0%
github.com/arcavenae/switchboard/internal/admission/reauth.go:66:		NewReAuthState			100.0%
github.com/arcavenae/switchboard/internal/admission/reauth.go:75:		setSourceAddr			100.0%
github.com/arcavenae/switchboard/internal/admission/reauth.go:94:		SetKeyExpiry			80.0%
github.com/arcavenae/switchboard/internal/admission/reauth.go:120:		SetKeyExpiryIfRoleMatches	0.0%
github.com/arcavenae/switchboard/internal/admission/reauth.go:172:		ReAuthenticate			72.2%
github.com/arcavenae/switchboard/internal/admission/reauth.go:244:		CurrentSourceAddr		83.3%
github.com/arcavenae/switchboard/internal/arq/arq.go:166:			New				85.7%
github.com/arcavenae/switchboard/internal/arq/arq.go:201:			OnAck				100.0%
github.com/arcavenae/switchboard/internal/arq/arq.go:291:			payloadFor			100.0%
github.com/arcavenae/switchboard/internal/arq/arq.go:310:			SACKFromChannelHeader		90.0%
github.com/arcavenae/switchboard/internal/arq/arq.go:339:			EnqueueSend			100.0%
github.com/arcavenae/switchboard/internal/arq/arq.go:375:			TLPKTDROP			100.0%
github.com/arcavenae/switchboard/internal/arq/arq.go:417:			GapsToRetransmit		100.0%
github.com/arcavenae/switchboard/internal/arq/arq.go:455:			SACKPopCount			100.0%
github.com/arcavenae/switchboard/internal/arq/arq.go:462:			SetNextExpected			100.0%
github.com/arcavenae/switchboard/internal/arq/arq.go:469:			NextExpected			100.0%
github.com/arcavenae/switchboard/internal/arq/arq.go:476:			InFlightContains		100.0%
github.com/arcavenae/switchboard/internal/arq/arq.go:482:			bitmapToUint64			100.0%
github.com/arcavenae/switchboard/internal/arq/fec.go:70:			NewEncoder			75.0%
github.com/arcavenae/switchboard/internal/arq/fec.go:93:			AddFrame			100.0%
github.com/arcavenae/switchboard/internal/arq/fec.go:127:			Flush				100.0%
github.com/arcavenae/switchboard/internal/arq/fec.go:145:			GroupSize			0.0%
github.com/arcavenae/switchboard/internal/arq/fec.go:157:			NewDecoder			75.0%
github.com/arcavenae/switchboard/internal/arq/fec.go:185:			Recover				86.4%
github.com/arcavenae/switchboard/internal/config/config.go:51:			Error				66.7%
github.com/arcavenae/switchboard/internal/config/config.go:60:			Is				0.0%
github.com/arcavenae/switchboard/internal/config/config.go:86:			Error				57.1%
github.com/arcavenae/switchboard/internal/config/config.go:166:			Validate			100.0%
github.com/arcavenae/switchboard/internal/config/config.go:271:			stripControlChars		100.0%
github.com/arcavenae/switchboard/internal/config/config.go:292:			sanitizeAddrForError		100.0%
github.com/arcavenae/switchboard/internal/config/config.go:299:			validateHostPort		83.3%
github.com/arcavenae/switchboard/internal/config/config.go:324:			LoadFile			87.5%
github.com/arcavenae/switchboard/internal/config/config.go:402:			validateManagementSocket	100.0%
github.com/arcavenae/switchboard/internal/config/config.go:417:			validateOperatorKey		90.9%
github.com/arcavenae/switchboard/internal/config/config.go:451:			yamlParseDetail			54.2%
github.com/arcavenae/switchboard/internal/discovery/discovery.go:158:		New				100.0%
github.com/arcavenae/switchboard/internal/discovery/discovery.go:166:		heartbeatInterval		66.7%
github.com/arcavenae/switchboard/internal/discovery/discovery.go:177:		Run				100.0%
github.com/arcavenae/switchboard/internal/discovery/discovery.go:211:		HeartbeatCount			100.0%
github.com/arcavenae/switchboard/internal/discovery/discovery.go:229:		Advertise			94.1%
github.com/arcavenae/switchboard/internal/discovery/discovery.go:273:		Enumerate			100.0%
github.com/arcavenae/switchboard/internal/discovery/discovery.go:296:		ReceiveAdvertisement		90.9%
github.com/arcavenae/switchboard/internal/discovery/discovery.go:363:		Encode				100.0%
github.com/arcavenae/switchboard/internal/discovery/discovery.go:382:		Decode				75.0%
github.com/arcavenae/switchboard/internal/discovery/discovery.go:414:		advertisementKey		100.0%
github.com/arcavenae/switchboard/internal/discovery/discovery.go:427:		encodeBody			100.0%
github.com/arcavenae/switchboard/internal/discovery/discovery.go:470:		encodedSessionName		100.0%
github.com/arcavenae/switchboard/internal/discovery/discovery.go:498:		decodeBody			83.3%
github.com/arcavenae/switchboard/internal/frame/address.go:11:			DeriveNodeAddress		100.0%
github.com/arcavenae/switchboard/internal/frame/frame.go:42:			Valid				100.0%
github.com/arcavenae/switchboard/internal/frame/frame.go:86:			EncodeOuterHeader		100.0%
github.com/arcavenae/switchboard/internal/frame/frame.go:101:			ParseOuterHeader		100.0%
github.com/arcavenae/switchboard/internal/halfchannel/halfchannel.go:104:	New				100.0%
github.com/arcavenae/switchboard/internal/halfchannel/halfchannel.go:117:	Tick				100.0%
github.com/arcavenae/switchboard/internal/halfchannel/halfchannel.go:143:	Enqueue				100.0%
github.com/arcavenae/switchboard/internal/halfchannel/halfchannel.go:158:	Seq				100.0%
github.com/arcavenae/switchboard/internal/halfchannel/halfchannel.go:165:	TickInterval			100.0%
github.com/arcavenae/switchboard/internal/halfchannel/halfchannel.go:183:	Direction			100.0%
github.com/arcavenae/switchboard/internal/hmac/hmac.go:37:			ComputeHMAC			100.0%
github.com/arcavenae/switchboard/internal/hmac/hmac.go:61:			VerifyHMAC			100.0%
github.com/arcavenae/switchboard/internal/hmac/hmac.go:84:			hkdfSHA256			93.3%
github.com/arcavenae/switchboard/internal/hmac/hmac.go:121:			DeriveKey			100.0%
github.com/arcavenae/switchboard/internal/metrics/handlers.go:61:		PathsList			100.0%
github.com/arcavenae/switchboard/internal/metrics/handlers.go:79:		RouterMetrics			100.0%
github.com/arcavenae/switchboard/internal/metrics/handlers.go:110:		RouterStatus			80.0%
github.com/arcavenae/switchboard/internal/metrics/handlers.go:138:		PathEntryFromSnapshot		90.0%
github.com/arcavenae/switchboard/internal/metrics/handlers.go:181:		QualityFromEntry		100.0%
github.com/arcavenae/switchboard/internal/metrics/handlers.go:196:		overallQuality			100.0%
github.com/arcavenae/switchboard/internal/metrics/metrics.go:32:		String				100.0%
github.com/arcavenae/switchboard/internal/metrics/metrics.go:86:		NewQualityIndicator		100.0%
github.com/arcavenae/switchboard/internal/metrics/metrics.go:94:		Current				100.0%
github.com/arcavenae/switchboard/internal/metrics/metrics.go:108:		Update				96.3%
github.com/arcavenae/switchboard/internal/metrics/metrics.go:176:		OnMissingFrame			100.0%
github.com/arcavenae/switchboard/internal/metrics/metrics.go:202:		Classify			100.0%
github.com/arcavenae/switchboard/internal/metrics/metrics.go:207:		classify			100.0%
github.com/arcavenae/switchboard/internal/metrics/types.go:62:			MarshalJSON			100.0%
github.com/arcavenae/switchboard/internal/metrics/types.go:127:			UnmarshalJSON			95.5%
github.com/arcavenae/switchboard/internal/metrics/types.go:178:			validateRTTFloat		100.0%
github.com/arcavenae/switchboard/internal/metrics/types.go:200:			NewRTTValueFloat		100.0%
github.com/arcavenae/switchboard/internal/metrics/types.go:209:			NewRTTValuePending		100.0%
github.com/arcavenae/switchboard/internal/mgmt/mgmt.go:56:			WithHandshakeTimeout		100.0%
github.com/arcavenae/switchboard/internal/mgmt/mgmt.go:65:			WithMaxConnections		100.0%
github.com/arcavenae/switchboard/internal/mgmt/mgmt.go:75:			WithRPCIdleTimeout		100.0%
github.com/arcavenae/switchboard/internal/mgmt/mgmt.go:110:			WithCallerPubkey		100.0%
github.com/arcavenae/switchboard/internal/mgmt/mgmt.go:117:			CallerPubkey			100.0%
github.com/arcavenae/switchboard/internal/mgmt/mgmt.go:134:			NewOperatorKeySet		100.0%
github.com/arcavenae/switchboard/internal/mgmt/mgmt.go:142:			IsBootstrap			100.0%
github.com/arcavenae/switchboard/internal/mgmt/mgmt.go:149:			IsAuthorized			100.0%
github.com/arcavenae/switchboard/internal/mgmt/mgmt.go:193:			NewServer			100.0%
github.com/arcavenae/switchboard/internal/mgmt/mgmt.go:233:			trackConn			100.0%
github.com/arcavenae/switchboard/internal/mgmt/mgmt.go:240:			untrackConn			100.0%
github.com/arcavenae/switchboard/internal/mgmt/mgmt.go:249:			closeAllConns			100.0%
github.com/arcavenae/switchboard/internal/mgmt/mgmt.go:271:			Serve				80.0%
github.com/arcavenae/switchboard/internal/mgmt/mgmt.go:394:			Shutdown			100.0%
github.com/arcavenae/switchboard/internal/mgmt/mgmt.go:460:			sendJSON			83.3%
github.com/arcavenae/switchboard/internal/mgmt/mgmt.go:474:			sendAuthFail			100.0%
github.com/arcavenae/switchboard/internal/mgmt/mgmt.go:490:			handleConnection		89.8%
github.com/arcavenae/switchboard/internal/mgmt/register_metrics.go:27:		Register			100.0%
github.com/arcavenae/switchboard/internal/mgmt/register_metrics.go:45:		RegisterMetricsHandlers		0.0%
github.com/arcavenae/switchboard/internal/mgmt/register_metrics.go:57:		pathsListHandler		0.0%
github.com/arcavenae/switchboard/internal/mgmt/register_metrics.go:65:		routerMetricsHandler		0.0%
github.com/arcavenae/switchboard/internal/mgmt/register_metrics.go:73:		routerStatusHandler		0.0%
github.com/arcavenae/switchboard/internal/multipath/multipath.go:72:		NewDropCache			100.0%
github.com/arcavenae/switchboard/internal/multipath/multipath.go:86:		Contains			100.0%
github.com/arcavenae/switchboard/internal/multipath/multipath.go:96:		Add				100.0%
github.com/arcavenae/switchboard/internal/multipath/multipath.go:124:		Len				100.0%
github.com/arcavenae/switchboard/internal/multipath/multipath.go:136:		Hits				100.0%
github.com/arcavenae/switchboard/internal/multipath/multipath.go:150:		AddIfAbsent			73.3%
github.com/arcavenae/switchboard/internal/multipath/multipath.go:215:		NewMultipath			100.0%
github.com/arcavenae/switchboard/internal/multipath/multipath.go:228:		UpdatePaths			100.0%
github.com/arcavenae/switchboard/internal/multipath/multipath.go:244:		Send				100.0%
github.com/arcavenae/switchboard/internal/multipath/multipath.go:291:		frameChecksum			100.0%
github.com/arcavenae/switchboard/internal/multipath/multipath.go:318:		Receive				100.0%
github.com/arcavenae/switchboard/internal/paths/paths.go:50:			PathScore			100.0%
github.com/arcavenae/switchboard/internal/paths/paths.go:106:			NewPathTracker			100.0%
github.com/arcavenae/switchboard/internal/paths/paths.go:128:			NewPathTrackerWithAddr		100.0%
github.com/arcavenae/switchboard/internal/paths/paths.go:138:			resetRTT			100.0%
github.com/arcavenae/switchboard/internal/paths/paths.go:158:			OnProbe				100.0%
github.com/arcavenae/switchboard/internal/paths/paths.go:191:			Score				100.0%
github.com/arcavenae/switchboard/internal/paths/paths.go:199:			IsActive			100.0%
github.com/arcavenae/switchboard/internal/paths/paths.go:206:			RTT				100.0%
github.com/arcavenae/switchboard/internal/paths/paths.go:213:			LossPct				100.0%
github.com/arcavenae/switchboard/internal/paths/paths.go:226:			updateDegraded			100.0%
github.com/arcavenae/switchboard/internal/paths/paths.go:233:			IsDegraded			100.0%
github.com/arcavenae/switchboard/internal/paths/paths.go:263:			bucketFor			75.0%
github.com/arcavenae/switchboard/internal/paths/paths.go:274:			record				100.0%
github.com/arcavenae/switchboard/internal/paths/paths.go:287:			p99				88.9%
github.com/arcavenae/switchboard/internal/paths/paths.go:331:			Snapshot			100.0%
github.com/arcavenae/switchboard/internal/paths/paths.go:364:			Rank				94.1%
github.com/arcavenae/switchboard/internal/replay/replay.go:65:			New				100.0%
github.com/arcavenae/switchboard/internal/replay/replay.go:101:			OnUpstream			100.0%
github.com/arcavenae/switchboard/internal/replay/replay.go:151:			deliverAndDrain			94.1%
github.com/arcavenae/switchboard/internal/replay/replay.go:184:			evictOldSeen			100.0%
github.com/arcavenae/switchboard/internal/replay/replay.go:199:			WindowSize			100.0%
github.com/arcavenae/switchboard/internal/replay/replay.go:205:			NextSeq				100.0%
github.com/arcavenae/switchboard/internal/routing/advertisement_hmac.go:18:	ComputeAdvertisementHMAC	0.0%
github.com/arcavenae/switchboard/internal/routing/advertisement_hmac.go:27:	VerifyAdvertisementHMAC		0.0%
github.com/arcavenae/switchboard/internal/routing/on_frame_arrival.go:107:	NewFrameArrivalHandler		100.0%
github.com/arcavenae/switchboard/internal/routing/on_frame_arrival.go:124:	WithFrameArrivalLogger		100.0%
github.com/arcavenae/switchboard/internal/routing/on_frame_arrival.go:139:	trackedKeyCount			100.0%
github.com/arcavenae/switchboard/internal/routing/on_frame_arrival.go:153:	trackedIndexLen			100.0%
github.com/arcavenae/switchboard/internal/routing/on_frame_arrival.go:188:	OnFrameArrival			100.0%
github.com/arcavenae/switchboard/internal/routing/on_frame_arrival.go:266:	incrementHitCountLocked		100.0%
github.com/arcavenae/switchboard/internal/routing/routing.go:45:		WithLogger			100.0%
github.com/arcavenae/switchboard/internal/routing/routing.go:58:		WithFailureCounter		100.0%
github.com/arcavenae/switchboard/internal/routing/routing.go:69:		Log				0.0%
github.com/arcavenae/switchboard/internal/routing/routing.go:123:		NewRouter			100.0%
github.com/arcavenae/switchboard/internal/routing/routing.go:140:		RegisterForwardingEntry		100.0%
github.com/arcavenae/switchboard/internal/routing/routing.go:170:		RouteFrame			100.0%
github.com/arcavenae/switchboard/internal/routing/routing.go:250:		SVTNRoute			100.0%
github.com/arcavenae/switchboard/internal/routing/routing.go:280:		verifyFrameHMAC			100.0%
github.com/arcavenae/switchboard/internal/routing/split_horizon.go:64:		Forward				92.9%
github.com/arcavenae/switchboard/internal/session/auth.go:77:			NewSessionAuth			100.0%
github.com/arcavenae/switchboard/internal/session/auth.go:88:			RegisterKey			100.0%
github.com/arcavenae/switchboard/internal/session/auth.go:107:			Authorize			100.0%
github.com/arcavenae/switchboard/internal/session/auth.go:140:			Allow				100.0%
github.com/arcavenae/switchboard/internal/session/fanout.go:62:			ConsoleSetWithClock		100.0%
github.com/arcavenae/switchboard/internal/session/fanout.go:69:			NewConsoleSet			100.0%
github.com/arcavenae/switchboard/internal/session/fanout.go:112:		Add				100.0%
github.com/arcavenae/switchboard/internal/session/fanout.go:133:		Session				100.0%
github.com/arcavenae/switchboard/internal/session/fanout.go:154:		Remove				100.0%
github.com/arcavenae/switchboard/internal/session/fanout.go:186:		Deliver				100.0%
github.com/arcavenae/switchboard/internal/session/fanout.go:210:		Heartbeat			87.5%
github.com/arcavenae/switchboard/internal/session/fanout.go:236:		EvictStale			100.0%
github.com/arcavenae/switchboard/internal/session/fanout.go:268:		IsAttached			100.0%
github.com/arcavenae/switchboard/internal/session/fanout.go:276:		Len				100.0%
github.com/arcavenae/switchboard/internal/session/fanout.go:284:		Snapshot			100.0%
github.com/arcavenae/switchboard/internal/session/fanout.go:298:		FramesDropped			100.0%
github.com/arcavenae/switchboard/internal/session/remote.go:90:			NewConsoleState			100.0%
github.com/arcavenae/switchboard/internal/session/remote.go:97:			Current				100.0%
github.com/arcavenae/switchboard/internal/session/remote.go:112:		NewConsoleServer		100.0%
github.com/arcavenae/switchboard/internal/session/remote.go:138:		HandleConsoleAttach		100.0%
github.com/arcavenae/switchboard/internal/session/remote.go:156:		HandleConsoleDetach		100.0%
github.com/arcavenae/switchboard/internal/session/remote.go:181:		HandleConsoleSwitch		100.0%
github.com/arcavenae/switchboard/internal/session/session.go:62:		NewPublisher			100.0%
github.com/arcavenae/switchboard/internal/session/session.go:73:		Publish				100.0%
github.com/arcavenae/switchboard/internal/session/session.go:92:		Unpublish			100.0%
github.com/arcavenae/switchboard/internal/session/session.go:110:		ListSessions			100.0%
github.com/arcavenae/switchboard/internal/session/session.go:128:		Get				100.0%
github.com/arcavenae/switchboard/internal/session/session.go:145:		AdmittedKeySet			0.0%
github.com/arcavenae/switchboard/internal/session/session.go:152:		Exists				0.0%
github.com/arcavenae/switchboard/internal/session/upstream.go:48:		Allow				100.0%
github.com/arcavenae/switchboard/internal/session/upstream.go:86:		SendInput			100.0%
github.com/arcavenae/switchboard/internal/session/upstream.go:96:		SendInput			0.0%
github.com/arcavenae/switchboard/internal/session/upstream.go:104:		WithKeystrokeSink		100.0%
github.com/arcavenae/switchboard/internal/session/upstream.go:117:		WithClock			100.0%
github.com/arcavenae/switchboard/internal/session/upstream.go:156:		NewAccessNode			83.3%
github.com/arcavenae/switchboard/internal/session/upstream.go:203:		Attach				70.0%
github.com/arcavenae/switchboard/internal/session/upstream.go:244:		Detach				40.0%
github.com/arcavenae/switchboard/internal/session/upstream.go:276:		SendKeystroke			100.0%
github.com/arcavenae/switchboard/internal/session/upstream.go:304:		DeliverFrame			100.0%
github.com/arcavenae/switchboard/internal/session/upstream.go:315:		Sweep				100.0%
github.com/arcavenae/switchboard/internal/session/upstream.go:322:		Heartbeat			100.0%
github.com/arcavenae/switchboard/internal/session/upstream.go:328:		FramesDropped			0.0%
github.com/arcavenae/switchboard/internal/svtnmgmt/svtnmgmt.go:139:		NewSVTNManager			100.0%
github.com/arcavenae/switchboard/internal/svtnmgmt/svtnmgmt.go:147:		NewSVTNManagerWithRandSource	0.0%
github.com/arcavenae/switchboard/internal/svtnmgmt/svtnmgmt.go:151:		newSVTNManager			100.0%
github.com/arcavenae/switchboard/internal/svtnmgmt/svtnmgmt.go:165:		keyFingerprint			100.0%
github.com/arcavenae/switchboard/internal/svtnmgmt/svtnmgmt.go:192:		Create				93.8%
github.com/arcavenae/switchboard/internal/svtnmgmt/svtnmgmt.go:248:		RegisterKey			100.0%
github.com/arcavenae/switchboard/internal/svtnmgmt/svtnmgmt.go:284:		RevokeKey			91.7%
github.com/arcavenae/switchboard/internal/svtnmgmt/svtnmgmt.go:351:		ExpireKey			94.4%
github.com/arcavenae/switchboard/internal/svtnmgmt/svtnmgmt.go:416:		IsBootstrapKey			0.0%
github.com/arcavenae/switchboard/internal/svtnmgmt/svtnmgmt.go:425:		HasAnySVTN			100.0%
github.com/arcavenae/switchboard/internal/svtnmgmt/svtnmgmt.go:448:		BootstrapKeyHasControlRole	64.7%
github.com/arcavenae/switchboard/internal/svtnmgmt/svtnmgmt.go:480:		BootstrapFingerprint		0.0%
github.com/arcavenae/switchboard/internal/svtnmgmt/svtnmgmt.go:492:		CallerKeyRole			100.0%
github.com/arcavenae/switchboard/internal/svtnmgmt/svtnmgmt.go:513:		CallerKeyRoleActive		84.6%
github.com/arcavenae/switchboard/internal/svtnmgmt/svtnmgmt.go:540:		HasControlKey			0.0%
github.com/arcavenae/switchboard/internal/svtnmgmt/svtnmgmt.go:576:		HasNonBootstrapControlKey	0.0%
github.com/arcavenae/switchboard/internal/svtnmgmt/svtnmgmt.go:608:		SVTNByName			100.0%
github.com/arcavenae/switchboard/internal/svtnmgmt/svtnmgmt.go:619:		All				83.3%
github.com/arcavenae/switchboard/internal/svtnmgmt/svtnmgmt.go:638:		CallerKeyRoleInAny		0.0%
github.com/arcavenae/switchboard/internal/svtnmgmt/svtnmgmt.go:673:		IsRegisteredAnyState		85.7%
github.com/arcavenae/switchboard/internal/svtnmgmt/svtnmgmt.go:696:		InsertRawSVTN			81.8%
github.com/arcavenae/switchboard/internal/svtnmgmt/svtnmgmt.go:719:		ListKeys			100.0%
github.com/arcavenae/switchboard/internal/svtnmgmt/svtnmgmt.go:774:		Destroy				100.0%
github.com/arcavenae/switchboard/internal/svtnmgmttest/helpers.go:15:		SVTNRecord			0.0%
github.com/arcavenae/switchboard/internal/svtnmgmttest/helpers.go:26:		SeedSVTNWithoutBootstrapKey	0.0%
github.com/arcavenae/switchboard/internal/tmux/connector_frames.go:54:		activeSourceSnapshot		94.4%
github.com/arcavenae/switchboard/internal/tmux/connector_frames.go:97:		Frames				100.0%
github.com/arcavenae/switchboard/internal/tmux/connector_frames.go:108:		RelayDropped			0.0%
github.com/arcavenae/switchboard/internal/tmux/connector_frames.go:116:		startForwardFrames		100.0%
github.com/arcavenae/switchboard/internal/tmux/connector_frames.go:145:		forwardFrames			90.5%
github.com/arcavenae/switchboard/internal/tmux/control.go:144:			WithExecFunc			100.0%
github.com/arcavenae/switchboard/internal/tmux/control.go:159:			defaultExecFn			0.0%
github.com/arcavenae/switchboard/internal/tmux/control.go:241:			New				100.0%
github.com/arcavenae/switchboard/internal/tmux/control.go:274:			Connect				70.0%
github.com/arcavenae/switchboard/internal/tmux/control.go:336:			Sessions			100.0%
github.com/arcavenae/switchboard/internal/tmux/control.go:356:			Err				100.0%
github.com/arcavenae/switchboard/internal/tmux/control.go:369:			Frames				100.0%
github.com/arcavenae/switchboard/internal/tmux/control.go:377:			Close				100.0%
github.com/arcavenae/switchboard/internal/tmux/control.go:443:			dispatchLoop			90.0%
github.com/arcavenae/switchboard/internal/tmux/control.go:579:			handleLine			91.3%
github.com/arcavenae/switchboard/internal/tmux/control.go:647:			SendInput			90.0%
github.com/arcavenae/switchboard/internal/tmux/control.go:672:			unescapeTmuxOutput		70.0%
github.com/arcavenae/switchboard/internal/tmux/pty_alloc_linux.go:28:		defaultPTYAlloc			0.0%
github.com/arcavenae/switchboard/internal/tmux/pty_alloc_unix.go:18:		Read				0.0%
github.com/arcavenae/switchboard/internal/tmux/pty_alloc_unix.go:19:		Write				0.0%
github.com/arcavenae/switchboard/internal/tmux/pty_alloc_unix.go:23:		Close				0.0%
github.com/arcavenae/switchboard/internal/tmux/pty_fallback.go:75:		WithPTYAllocFunc		100.0%
github.com/arcavenae/switchboard/internal/tmux/pty_fallback.go:93:		WithLogger			100.0%
github.com/arcavenae/switchboard/internal/tmux/pty_fallback.go:104:		Log				100.0%
github.com/arcavenae/switchboard/internal/tmux/pty_fallback.go:151:		NewPTYProxy			100.0%
github.com/arcavenae/switchboard/internal/tmux/pty_fallback.go:178:		Connect				65.5%
github.com/arcavenae/switchboard/internal/tmux/pty_fallback.go:236:		ioRelay				94.1%
github.com/arcavenae/switchboard/internal/tmux/pty_fallback.go:282:		Sessions			100.0%
github.com/arcavenae/switchboard/internal/tmux/pty_fallback.go:294:		Frames				100.0%
github.com/arcavenae/switchboard/internal/tmux/pty_fallback.go:300:		Close				88.2%
github.com/arcavenae/switchboard/internal/tmux/pty_fallback.go:357:		SendInput			90.0%
github.com/arcavenae/switchboard/internal/tmux/pty_fallback.go:381:		SendInput			90.9%
github.com/arcavenae/switchboard/internal/tmux/pty_fallback.go:424:		WithControlModeFactory		100.0%
github.com/arcavenae/switchboard/internal/tmux/pty_fallback.go:527:		NewSessionConnector		100.0%
github.com/arcavenae/switchboard/internal/tmux/pty_fallback.go:549:		Connect				100.0%
github.com/arcavenae/switchboard/internal/tmux/pty_fallback.go:612:		controlModeFailureLogMsg	100.0%
github.com/arcavenae/switchboard/internal/tmux/pty_fallback.go:631:		Sessions			83.3%
github.com/arcavenae/switchboard/internal/tmux/pty_fallback.go:644:		InPTYMode			100.0%
github.com/arcavenae/switchboard/internal/tmux/pty_fallback.go:660:		Err				100.0%
github.com/arcavenae/switchboard/internal/tmux/pty_fallback.go:665:		Close				92.0%
github.com/arcavenae/switchboard/internal/tmux/pty_fallback.go:733:		watchAndFallback		76.1%
github.com/arcavenae/switchboard/internal/tmux/stderr.go:29:			ClassifyStderr			100.0%
total:										(statements)			81.8%

@arcavenai
arcavenai merged commit df79191 into develop Jul 5, 2026
9 checks passed
@arcavenai
arcavenai deleted the feat/smoke-sentinels branch July 5, 2026 02:47
@arcaven arcaven added the type.test A request to run, verify, rehearse, or reproduce shipped behavior label Oct 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type.test A request to run, verify, rehearse, or reproduce shipped behavior

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants