Skip to content

Infra Improvements#51

Merged
xzyaoi merged 52 commits intomainfrom
fix/security
Mar 25, 2026
Merged

Infra Improvements#51
xzyaoi merged 52 commits intomainfrom
fix/security

Conversation

@xzyaoi
Copy link
Copy Markdown
Collaborator

@xzyaoi xzyaoi commented Mar 22, 2026

No description provided.

xzyaoi and others added 16 commits March 19, 2026 14:39
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Implements deploy/test/deploy.yml with plays for building a signed
binary locally, deploying to all hosts, discovering bootstrap multiaddrs,
deploying final configs with peer bootstraps, and verifying mesh formation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds R2 bucket, Worker custom domain, and DNS record for docs site
to OpenTofu config. Worker code deployment stays with wrangler.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Cloudflare provider v5 requires ttl explicitly. Value 1 = auto (managed
by Cloudflare) for proxied records.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove docs DNS record (auto-created by Worker custom domain)
- Add ttl=1 for proxied DNS records (provider v5 requirement)
- Add dependency lock file

All 6 resources applied:
- bootstraps.opentela.ai: 2x A records + origin rule (port 8092) + SSL rule
- docs.opentela.ai: R2 bucket + Worker custom domain

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 22, 2026 16:18
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Mar 22, 2026

Test Coverage Report 📊

Click to view detailed coverage
opentela/entry/cmd/init.go:103:					writeDefaultConfig		0.0%
opentela/entry/cmd/root.go:34:					init				100.0%
opentela/entry/cmd/root.go:70:					configFilePath			85.7%
opentela/entry/cmd/root.go:85:					initConfig			88.4%
opentela/entry/cmd/root.go:215:					Execute				0.0%
opentela/entry/cmd/start.go:40:					init				100.0%
opentela/entry/cmd/update.go:13:				doUpdate			0.0%
opentela/entry/cmd/wallet.go:388:				init				100.0%
opentela/entry/main.go:18:					main				0.0%
opentela/internal/attestation/attestation.go:43:		loadPubKey			53.8%
opentela/internal/attestation/attestation.go:65:		attestationMessage		100.0%
opentela/internal/attestation/attestation.go:72:		Verify				75.0%
opentela/internal/attestation/attestation.go:96:		Sign				100.0%
opentela/internal/attestation/cmd/buildsign/main.go:22:		main				0.0%
opentela/internal/attestation/cmd/buildsign/main.go:39:		keygen				0.0%
opentela/internal/attestation/cmd/buildsign/main.go:51:		sign				0.0%
opentela/internal/common/constants.go:10:			GetHomePath			63.6%
opentela/internal/common/constants.go:27:			GetDBPath			100.0%
opentela/internal/common/filesystem.go:7:			RemoveDir			100.0%
opentela/internal/common/logger.go:11:				init				100.0%
opentela/internal/common/logger.go:15:				InitLogger			50.0%
opentela/internal/common/logger.go:55:				ReportError			50.0%
opentela/internal/common/process/manager.go:13:			NewProcessManager		100.0%
opentela/internal/common/process/manager.go:20:			StartProcess			0.0%
opentela/internal/common/process/manager.go:27:			StopAllProcesses		0.0%
opentela/internal/common/process/manager.go:33:			StartCriticalProcess		57.1%
opentela/internal/common/process/manager.go:44:			HealthCheck			50.0%
opentela/internal/common/process/process.go:36:			NewProcess			100.0%
opentela/internal/common/process/process.go:63:			Start				94.4%
opentela/internal/common/process/process.go:93:			SetTimeout			66.7%
opentela/internal/common/process/process.go:101:		Wait				100.0%
opentela/internal/common/process/process.go:105:		awaitOutput			100.0%
opentela/internal/common/process/process.go:111:		Kill				100.0%
opentela/internal/common/process/process.go:120:		OpenInputStream			71.4%
opentela/internal/common/process/process.go:133:		StreamOutput			77.8%
opentela/internal/common/process/process.go:151:		finishTimeOutOrDie		87.5%
opentela/internal/common/process/process.go:169:		cleanup				100.0%
opentela/internal/common/process/process.go:181:		isRunning			0.0%
opentela/internal/common/requests.go:12:			RemoteGET			52.6%
opentela/internal/common/serialization.go:5:			DictionaryToBytes		100.0%
opentela/internal/common/utils.go:3:				DeduplicateStrings		100.0%
opentela/internal/ingest/server.go:40:				Run				0.0%
opentela/internal/ingest/server.go:70:				collectStats			0.0%
opentela/internal/metrics/collector.go:20:			NewAggregatedCollector		100.0%
opentela/internal/metrics/collector.go:38:			Describe			0.0%
opentela/internal/metrics/collector.go:42:			Collect				88.9%
opentela/internal/metrics/collector.go:58:			SetNetworkStats			100.0%
opentela/internal/metrics/collector.go:63:			SetScraperTargets		100.0%
opentela/internal/metrics/collector.go:67:			metricFromDTO			91.7%
opentela/internal/metrics/peer_provider.go:15:			GetScrapablePeers		0.0%
opentela/internal/metrics/peer_provider.go:42:			extractServices			100.0%
opentela/internal/metrics/peer_provider.go:57:			buildPeerLabels			100.0%
opentela/internal/metrics/relabeler.go:12:			Relabel				100.0%
opentela/internal/metrics/relabeler.go:53:			sanitizeMetricName		75.0%
opentela/internal/metrics/scraper.go:54:			NewMetricsScraper		100.0%
opentela/internal/metrics/scraper.go:83:			Start				0.0%
opentela/internal/metrics/scraper.go:99:			Stop				0.0%
opentela/internal/metrics/scraper.go:103:			GetCachedMetrics		100.0%
opentela/internal/metrics/scraper.go:114:			GetSelfMetrics			0.0%
opentela/internal/metrics/scraper.go:118:			scrapeAll			100.0%
opentela/internal/metrics/scraper.go:145:			scrapePeer			72.7%
opentela/internal/metrics/scraper.go:166:			scrapeTarget			84.6%
opentela/internal/metrics/scraper.go:189:			parseMetrics			84.6%
opentela/internal/metrics/scraper.go:210:			evictStale			85.7%
opentela/internal/platform/gpu.go:10:				GetGPUInfo			31.2%
opentela/internal/platform/slurm/env.go:10:			IsSlurm				100.0%
opentela/internal/platform/slurm/env.go:14:			getJobId			100.0%
opentela/internal/platform/slurm/env.go:18:			getNodeId			100.0%
opentela/internal/platform/slurm/env.go:22:			getRemainingTimeInSeconds	44.4%
opentela/internal/platform/slurm/env.go:43:			GetJobInfo			100.0%
opentela/internal/protocol/bootstrap.go:17:			getDefaultBootstrapPeers	77.8%
opentela/internal/protocol/bootstrap.go:49:			collectBootstrapSources		92.3%
opentela/internal/protocol/bootstrap.go:71:			resolveBootstrapSources		90.0%
opentela/internal/protocol/bootstrap.go:88:			resolveBootstrapSource		85.7%
opentela/internal/protocol/bootstrap.go:104:			fetchHTTPBootstraps		80.0%
opentela/internal/protocol/bootstrap.go:120:			fetchDNSAddrBootstraps		77.8%
opentela/internal/protocol/bootstrap.go:147:			expandBootstrapValue		54.5%
opentela/internal/protocol/bootstrap.go:167:			splitBootstrapValue		100.0%
opentela/internal/protocol/bootstrap.go:174:			parseBootstrapMultiaddrs	75.0%
opentela/internal/protocol/clock.go:20:				StartTicker			0.0%
opentela/internal/protocol/crdt.go:33:				GetCRDTStore			0.0%
opentela/internal/protocol/crdt.go:183:				Reconnect			0.0%
opentela/internal/protocol/crdt.go:199:				ClearCRDTStore			0.0%
opentela/internal/protocol/go-ds-crdt/compaction.go:16:		decodeTombstoneTimestamp	0.0%
opentela/internal/protocol/go-ds-crdt/compaction.go:32:		CompactTombstones		0.0%
opentela/internal/protocol/go-ds-crdt/crdt.go:114:		verify				0.0%
opentela/internal/protocol/go-ds-crdt/crdt.go:147:		DefaultOptions			0.0%
opentela/internal/protocol/go-ds-crdt/crdt.go:231:		New				0.0%
opentela/internal/protocol/go-ds-crdt/crdt.go:370:		handleNext			0.0%
opentela/internal/protocol/go-ds-crdt/crdt.go:461:		decodeBroadcast			0.0%
opentela/internal/protocol/go-ds-crdt/crdt.go:492:		encodeBroadcast			0.0%
opentela/internal/protocol/go-ds-crdt/crdt.go:501:		randomizeInterval		0.0%
opentela/internal/protocol/go-ds-crdt/crdt.go:510:		rebroadcast			0.0%
opentela/internal/protocol/go-ds-crdt/crdt.go:527:		repair				0.0%
opentela/internal/protocol/go-ds-crdt/crdt.go:555:		rebroadcastHeads		0.0%
opentela/internal/protocol/go-ds-crdt/crdt.go:588:		logStats			0.0%
opentela/internal/protocol/go-ds-crdt/crdt.go:614:		handleBlock			0.0%
opentela/internal/protocol/go-ds-crdt/crdt.go:632:		handleBranch			0.0%
opentela/internal/protocol/go-ds-crdt/crdt.go:650:		dagWorker			0.0%
opentela/internal/protocol/go-ds-crdt/crdt.go:690:		sendNewJobs			0.0%
opentela/internal/protocol/go-ds-crdt/crdt.go:775:		sendJobWorker			0.0%
opentela/internal/protocol/go-ds-crdt/crdt.go:791:		processedBlockKey		0.0%
opentela/internal/protocol/go-ds-crdt/crdt.go:795:		isProcessed			0.0%
opentela/internal/protocol/go-ds-crdt/crdt.go:799:		markProcessed			0.0%
opentela/internal/protocol/go-ds-crdt/crdt.go:803:		dirtyKey			0.0%
opentela/internal/protocol/go-ds-crdt/crdt.go:808:		MarkDirty			0.0%
opentela/internal/protocol/go-ds-crdt/crdt.go:817:		IsDirty				0.0%
opentela/internal/protocol/go-ds-crdt/crdt.go:826:		MarkClean			0.0%
opentela/internal/protocol/go-ds-crdt/crdt.go:836:		processNode			0.0%
opentela/internal/protocol/go-ds-crdt/crdt.go:959:		repairDAG			0.0%
opentela/internal/protocol/go-ds-crdt/crdt.go:1072:		Repair				0.0%
opentela/internal/protocol/go-ds-crdt/crdt.go:1078:		Get				0.0%
opentela/internal/protocol/go-ds-crdt/crdt.go:1086:		Has				0.0%
opentela/internal/protocol/go-ds-crdt/crdt.go:1093:		GetSize				0.0%
opentela/internal/protocol/go-ds-crdt/crdt.go:1108:		Query				0.0%
opentela/internal/protocol/go-ds-crdt/crdt.go:1117:		Put				0.0%
opentela/internal/protocol/go-ds-crdt/crdt.go:1123:		Delete				0.0%
opentela/internal/protocol/go-ds-crdt/crdt.go:1137:		Sync				0.0%
opentela/internal/protocol/go-ds-crdt/crdt.go:1181:		Context				0.0%
opentela/internal/protocol/go-ds-crdt/crdt.go:1186:		Close				0.0%
opentela/internal/protocol/go-ds-crdt/crdt.go:1198:		Batch				0.0%
opentela/internal/protocol/go-ds-crdt/crdt.go:1202:		deltaMerge			0.0%
opentela/internal/protocol/go-ds-crdt/crdt.go:1215:		addToDelta			0.0%
opentela/internal/protocol/go-ds-crdt/crdt.go:1221:		rmvToDelta			0.0%
opentela/internal/protocol/go-ds-crdt/crdt.go:1232:		updateDeltaWithRemove		0.0%
opentela/internal/protocol/go-ds-crdt/crdt.go:1254:		updateDelta			0.0%
opentela/internal/protocol/go-ds-crdt/crdt.go:1265:		publishDelta			0.0%
opentela/internal/protocol/go-ds-crdt/crdt.go:1276:		putBlock			0.0%
opentela/internal/protocol/go-ds-crdt/crdt.go:1295:		publish				0.0%
opentela/internal/protocol/go-ds-crdt/crdt.go:1307:		addDAGNode			0.0%
opentela/internal/protocol/go-ds-crdt/crdt.go:1349:		broadcast			0.0%
opentela/internal/protocol/go-ds-crdt/crdt.go:1383:		Put				0.0%
opentela/internal/protocol/go-ds-crdt/crdt.go:1395:		Delete				0.0%
opentela/internal/protocol/go-ds-crdt/crdt.go:1409:		Commit				0.0%
opentela/internal/protocol/go-ds-crdt/crdt.go:1415:		PrintDAG			0.0%
opentela/internal/protocol/go-ds-crdt/crdt.go:1434:		printDAGRec			0.0%
opentela/internal/protocol/go-ds-crdt/crdt.go:1495:		DotDAG				0.0%
opentela/internal/protocol/go-ds-crdt/crdt.go:1523:		dotDAGRec			0.0%
opentela/internal/protocol/go-ds-crdt/crdt.go:1576:		InternalStats			0.0%
opentela/internal/protocol/go-ds-crdt/crdt.go:1591:		newCidSafeSet			0.0%
opentela/internal/protocol/go-ds-crdt/crdt.go:1597:		Visit				0.0%
opentela/internal/protocol/go-ds-crdt/crdt.go:1610:		Remove				0.0%
opentela/internal/protocol/go-ds-crdt/crdt.go:1618:		Has				0.0%
opentela/internal/protocol/go-ds-crdt/heads.go:29:		newHeads			0.0%
opentela/internal/protocol/go-ds-crdt/heads.go:42:		key				0.0%
opentela/internal/protocol/go-ds-crdt/heads.go:47:		write				0.0%
opentela/internal/protocol/go-ds-crdt/heads.go:56:		delete				0.0%
opentela/internal/protocol/go-ds-crdt/heads.go:68:		IsHead				0.0%
opentela/internal/protocol/go-ds-crdt/heads.go:79:		Len				0.0%
opentela/internal/protocol/go-ds-crdt/heads.go:90:		Replace				0.0%
opentela/internal/protocol/go-ds-crdt/heads.go:134:		Add				0.0%
opentela/internal/protocol/go-ds-crdt/heads.go:149:		List				0.0%
opentela/internal/protocol/go-ds-crdt/heads.go:176:		primeCache			0.0%
opentela/internal/protocol/go-ds-crdt/ipld.go:23:		GetDelta			0.0%
opentela/internal/protocol/go-ds-crdt/ipld.go:33:		GetPriority			0.0%
opentela/internal/protocol/go-ds-crdt/ipld.go:48:		GetDeltas			0.0%
opentela/internal/protocol/go-ds-crdt/ipld.go:72:		extractDelta			0.0%
opentela/internal/protocol/go-ds-crdt/ipld.go:82:		makeNode			0.0%
opentela/internal/protocol/go-ds-crdt/migrations.go:18:		versionKey			0.0%
opentela/internal/protocol/go-ds-crdt/migrations.go:22:		getVersion			0.0%
opentela/internal/protocol/go-ds-crdt/migrations.go:39:		setVersion			0.0%
opentela/internal/protocol/go-ds-crdt/migrations.go:50:		applyMigrations			0.0%
opentela/internal/protocol/go-ds-crdt/migrations.go:81:		migrate0to1			0.0%
opentela/internal/protocol/go-ds-crdt/pubsub_broadcaster.go:28:	NewPubSubBroadcaster		0.0%
opentela/internal/protocol/go-ds-crdt/pubsub_broadcaster.go:60:	Broadcast			0.0%
opentela/internal/protocol/go-ds-crdt/pubsub_broadcaster.go:65:	Next				0.0%
opentela/internal/protocol/go-ds-crdt/set.go:50:		newCRDTSet			0.0%
opentela/internal/protocol/go-ds-crdt/set.go:73:		Add				0.0%
opentela/internal/protocol/go-ds-crdt/set.go:86:		Rmv				0.0%
opentela/internal/protocol/go-ds-crdt/set.go:133:		Element				0.0%
opentela/internal/protocol/go-ds-crdt/set.go:150:		Elements			0.0%
opentela/internal/protocol/go-ds-crdt/set.go:251:		InSet				0.0%
opentela/internal/protocol/go-ds-crdt/set.go:259:		keyPrefix			0.0%
opentela/internal/protocol/go-ds-crdt/set.go:264:		elemsPrefix			0.0%
opentela/internal/protocol/go-ds-crdt/set.go:269:		tombsPrefix			0.0%
opentela/internal/protocol/go-ds-crdt/set.go:274:		valueKey			0.0%
opentela/internal/protocol/go-ds-crdt/set.go:279:		priorityKey			0.0%
opentela/internal/protocol/go-ds-crdt/set.go:283:		getPriority			0.0%
opentela/internal/protocol/go-ds-crdt/set.go:300:		setPriority			0.0%
opentela/internal/protocol/go-ds-crdt/set.go:313:		setValue			0.0%
opentela/internal/protocol/go-ds-crdt/set.go:359:		findBestValue			0.0%
opentela/internal/protocol/go-ds-crdt/set.go:464:		putElems			0.0%
opentela/internal/protocol/go-ds-crdt/set.go:510:		putTombs			0.0%
opentela/internal/protocol/go-ds-crdt/set.go:584:		Merge				0.0%
opentela/internal/protocol/go-ds-crdt/set.go:599:		inTombsKeyID			0.0%
opentela/internal/protocol/go-ds-crdt/set.go:620:		datastoreSync			0.0%
opentela/internal/protocol/host.go:48:				GetP2PNode			0.0%
opentela/internal/protocol/host.go:70:				newHost				0.0%
opentela/internal/protocol/host.go:267:				StartAutoReconnect		0.0%
opentela/internal/protocol/host.go:275:				startAutoReconnect		0.0%
opentela/internal/protocol/host.go:328:				tryReconnectToBootstraps	0.0%
opentela/internal/protocol/host.go:384:				waitFor				100.0%
opentela/internal/protocol/host.go:400:				backoffDelay			77.8%
opentela/internal/protocol/host.go:416:				backoffBaseDelay		90.0%
opentela/internal/protocol/host.go:436:				isTransientNetworkError		87.5%
opentela/internal/protocol/host.go:453:				newResourceManager		0.0%
opentela/internal/protocol/host.go:463:				newDHT				0.0%
opentela/internal/protocol/host.go:477:				ConnectedPeers			0.0%
opentela/internal/protocol/host.go:492:				AllPeers			0.0%
opentela/internal/protocol/host.go:506:				BuildBootstrapAddr		100.0%
opentela/internal/protocol/host.go:518:				isRecentRelayPeer		100.0%
opentela/internal/protocol/host.go:527:				ConnectedBootstraps		0.0%
opentela/internal/protocol/host.go:567:				MakeRelayReservations		0.0%
opentela/internal/protocol/host.go:605:				IsDirectlyConnected		0.0%
opentela/internal/protocol/host.go:621:				FindRelayFor			0.0%
opentela/internal/protocol/host.go:658:				GetResourceManagerStats		0.0%
opentela/internal/protocol/key.go:11:				writeKeyToFile			0.0%
opentela/internal/protocol/key.go:34:				loadKeyFromFile			0.0%
opentela/internal/protocol/node_table.go:34:			InitScalableNodeTable		0.0%
opentela/internal/protocol/node_table.go:42:			GetScalableSnapshot		0.0%
opentela/internal/protocol/node_table.go:49:			GetNodeTableWriter		0.0%
opentela/internal/protocol/node_table.go:55:			StartSWIM			0.0%
opentela/internal/protocol/node_table.go:211:			getNodeTable			100.0%
opentela/internal/protocol/node_table.go:218:			UpdateNodeTable			0.0%
opentela/internal/protocol/node_table.go:253:			MarkSelfAsBootstrap		0.0%
opentela/internal/protocol/node_table.go:279:			AnnounceLeave			0.0%
opentela/internal/protocol/node_table.go:304:			UpdateNodeTableHook		70.5%
opentela/internal/protocol/node_table.go:381:			DeleteNodeTableHook		100.0%
opentela/internal/protocol/node_table.go:388:			GetPeerFromTable		100.0%
opentela/internal/protocol/node_table.go:399:			GetConnectedPeers		0.0%
opentela/internal/protocol/node_table.go:411:			GetAllPeers			100.0%
opentela/internal/protocol/node_table.go:421:			GetService			0.0%
opentela/internal/protocol/node_table.go:438:			GetAllProviders			0.0%
opentela/internal/protocol/node_table.go:466:			InitializeMyself		0.0%
opentela/internal/protocol/node_table.go:555:			GetSelf				100.0%
opentela/internal/protocol/node_table.go:562:			SetMyselfRelayPeer		0.0%
opentela/internal/protocol/node_table.go:569:			SetMyselfForTest		100.0%
opentela/internal/protocol/node_table.go:577:			RegisterRemotePeer		0.0%
opentela/internal/protocol/nodetable/snapshot.go:55:		NewNodeTable			100.0%
opentela/internal/protocol/nodetable/snapshot.go:62:		Snapshot			100.0%
opentela/internal/protocol/nodetable/snapshot.go:67:		Store				100.0%
opentela/internal/protocol/nodetable/snapshot.go:72:		NewSnapshot			100.0%
opentela/internal/protocol/nodetable/snapshot.go:84:		Clone				100.0%
opentela/internal/protocol/nodetable/snapshot.go:100:		ApplyEvent			71.7%
opentela/internal/protocol/nodetable/snapshot.go:187:		RebuildIndexes			91.7%
opentela/internal/protocol/nodetable/writer.go:33:		init				100.0%
opentela/internal/protocol/nodetable/writer.go:52:		NewWriter			100.0%
opentela/internal/protocol/nodetable/writer.go:60:		Start				100.0%
opentela/internal/protocol/nodetable/writer.go:65:		Stop				100.0%
opentela/internal/protocol/nodetable/writer.go:71:		Send				100.0%
opentela/internal/protocol/nodetable/writer.go:79:		run				100.0%
opentela/internal/protocol/nodetable/writer.go:109:		drainAndApply			66.7%
opentela/internal/protocol/nodetable/writer.go:123:		applyBatch			100.0%
opentela/internal/protocol/registrar.go:25:			addLocalService			100.0%
opentela/internal/protocol/registrar.go:54:			snapshotLocalServices		100.0%
opentela/internal/protocol/registrar.go:62:			RegisterLocalServices		0.0%
opentela/internal/protocol/registrar.go:96:			healthCheckRemote		0.0%
opentela/internal/protocol/registrar.go:120:			registerLLMService		0.0%
opentela/internal/protocol/registrar.go:147:			provideService			0.0%
opentela/internal/protocol/registrar.go:173:			ReannounceLocalServices		0.0%
opentela/internal/protocol/swim/dissemination.go:23:		NewDisseminator			66.7%
opentela/internal/protocol/swim/dissemination.go:35:		UpdateN				100.0%
opentela/internal/protocol/swim/dissemination.go:44:		retransmitLimit			100.0%
opentela/internal/protocol/swim/dissemination.go:50:		Enqueue				88.9%
opentela/internal/protocol/swim/dissemination.go:73:		GetPiggyback			93.3%
opentela/internal/protocol/swim/dissemination.go:100:		statusPriority			50.0%
opentela/internal/protocol/swim/messages.go:58:			MarshalJSON			100.0%
opentela/internal/protocol/swim/messages.go:67:			UnmarshalJSON			87.5%
opentela/internal/protocol/swim/messages.go:87:			Marshal				28.6%
opentela/internal/protocol/swim/messages.go:109:		Unmarshal			100.0%
opentela/internal/protocol/swim/messages.go:129:		Marshal				80.0%
opentela/internal/protocol/swim/messages.go:149:		Unmarshal			76.9%
opentela/internal/protocol/swim/metrics.go:30:			init				100.0%
opentela/internal/protocol/swim/swim.go:72:			NewSWIM				100.0%
opentela/internal/protocol/swim/swim.go:86:			AddMember			100.0%
opentela/internal/protocol/swim/swim.go:97:			RemoveMember			100.0%
opentela/internal/protocol/swim/swim.go:107:			GetStatus			100.0%
opentela/internal/protocol/swim/swim.go:117:			GetIncarnation			100.0%
opentela/internal/protocol/swim/swim.go:124:			Members				100.0%
opentela/internal/protocol/swim/swim.go:136:			probeOnce			86.7%
opentela/internal/protocol/swim/swim.go:168:			processPendingProbes		94.2%
opentela/internal/protocol/swim/swim.go:273:			processSuspects			100.0%
opentela/internal/protocol/swim/swim.go:314:			HandleMessage			100.0%
opentela/internal/protocol/swim/swim.go:366:			processEvents			27.8%
opentela/internal/protocol/swim/swim.go:437:			Run				100.0%
opentela/internal/protocol/swim/swim.go:460:			updateMemberGauge		100.0%
opentela/internal/protocol/swim/swim.go:475:			Close				75.0%
opentela/internal/protocol/swim/transport.go:27:		NewLibP2PTransport		0.0%
opentela/internal/protocol/swim/transport.go:31:		send				0.0%
opentela/internal/protocol/swim/transport.go:49:		SendPing			0.0%
opentela/internal/protocol/swim/transport.go:53:		SendAck				0.0%
opentela/internal/protocol/swim/transport.go:57:		SendPingReq			0.0%
opentela/internal/protocol/swim/transport.go:62:		RegisterHandler			0.0%
opentela/internal/protocol/tombstone_compactor.go:21:		startTombstoneCompactor		0.0%
opentela/internal/protocol/tombstone_compactor.go:91:		readDurationSetting		100.0%
opentela/internal/protocol/tombstone_manager.go:24:		GetTombstoneManager		0.0%
opentela/internal/protocol/tombstone_manager.go:37:		CleanupLeftNodes		87.5%
opentela/internal/protocol/tombstone_manager.go:58:		collectCandidates		100.0%
opentela/internal/server/access_control.go:46:			resolveCallerWallet		36.4%
opentela/internal/server/access_control.go:74:			accessControlMiddleware		73.5%
opentela/internal/server/access_control.go:142:			containsWallet			100.0%
opentela/internal/server/auth_client.go:41:			get				100.0%
opentela/internal/server/auth_client.go:51:			set				100.0%
opentela/internal/server/auth_client.go:64:			verifyBearerToken		83.3%
opentela/internal/server/auth_client.go:107:			resolveClientWallet		75.0%
opentela/internal/server/cors.go:10:				corsHeader			100.0%
opentela/internal/server/cors.go:26:				rewriteHeader			100.0%
opentela/internal/server/crdt_handler.go:12:			listPeers			100.0%
opentela/internal/server/crdt_handler.go:17:			listPeersWithStatus		100.0%
opentela/internal/server/crdt_handler.go:23:			listBootstraps			0.0%
opentela/internal/server/crdt_handler.go:28:			getResourceStats		100.0%
opentela/internal/server/crdt_handler.go:45:			updateLocal			0.0%
opentela/internal/server/crdt_handler.go:55:			deleteLocal			0.0%
opentela/internal/server/crdt_handler.go:64:			getDNT				0.0%
opentela/internal/server/health.go:9:				healthStatusCheck		100.0%
opentela/internal/server/ingest.go:38:				getIngestStats			88.9%
opentela/internal/server/p2p_listener.go:11:			P2PListener			0.0%
opentela/internal/server/proxy_handler.go:58:			init				100.0%
opentela/internal/server/proxy_handler.go:62:			getGlobalTransport		0.0%
opentela/internal/server/proxy_handler.go:77:			ErrorHandler			0.0%
opentela/internal/server/proxy_handler.go:89:			WriteHeader			0.0%
opentela/internal/server/proxy_handler.go:99:			Flush				0.0%
opentela/internal/server/proxy_handler.go:106:			P2PForwardHandler		0.0%
opentela/internal/server/proxy_handler.go:144:			ServiceForwardHandler		0.0%
opentela/internal/server/proxy_handler.go:180:			parseFallbackLevel		100.0%
opentela/internal/server/proxy_handler.go:199:			selectCandidates		100.0%
opentela/internal/server/proxy_handler.go:274:			weightedRandomSelect		81.2%
opentela/internal/server/proxy_handler.go:304:			scoreCandidates			0.0%
opentela/internal/server/proxy_handler.go:316:			excludePeers			100.0%
opentela/internal/server/proxy_handler.go:330:			shouldShedLoad			100.0%
opentela/internal/server/proxy_handler.go:339:			filterByTrust			0.0%
opentela/internal/server/proxy_handler.go:354:			GlobalServiceForwardHandler	0.0%
opentela/internal/server/ratelimit.go:27:			newRateLimiterStore		100.0%
opentela/internal/server/ratelimit.go:37:			getLimiter			100.0%
opentela/internal/server/ratelimit.go:51:			cleanup				57.1%
opentela/internal/server/ratelimit.go:66:			rateLimitMiddleware		88.9%
opentela/internal/server/registration.go:52:			challengePeer			81.8%
opentela/internal/server/registration.go:77:			registerPeer			37.9%
opentela/internal/server/registration.go:227:			StartChallengeCleanup		0.0%
opentela/internal/server/registration.go:236:			cleanExpiredChallenges		0.0%
opentela/internal/server/self_handler.go:14:			isLoopback			80.0%
opentela/internal/server/self_handler.go:24:			getSelf				100.0%
opentela/internal/server/self_handler.go:34:			signData			10.7%
opentela/internal/server/server.go:24:				StartServer			0.0%
opentela/internal/server/tracer.go:20:				initTracer			0.0%
opentela/internal/server/tracer.go:44:				IngestEvents			0.0%
opentela/internal/solana/client.go:27:				NewClient			100.0%
opentela/internal/solana/client.go:55:				call				88.2%
opentela/internal/solana/client.go:93:				HasSPLToken			100.0%
opentela/internal/solana/client.go:141:				GetBalance			88.9%
opentela/internal/solana/client.go:159:				GetBalanceSOL			75.0%
opentela/internal/solana/client.go:173:				GetTokenBalance			73.3%
opentela/internal/solana/client.go:215:				RequestAirdrop			77.8%
opentela/internal/solana/client.go:253:				SendSOL				0.0%
opentela/internal/solana/client.go:309:				getRecentBlockhash		0.0%
opentela/internal/solana/client.go:336:				buildTransferMessage		100.0%
opentela/internal/solana/client.go:387:				serializeTransaction		100.0%
opentela/internal/solana/processor.go:45:			NewPaymentProcessor		83.3%
opentela/internal/solana/processor.go:72:			ProcessUsageRecords		0.0%
opentela/internal/solana/processor.go:134:			submitPayment			0.0%
opentela/internal/solana/processor.go:223:			confirmTransaction		0.0%
opentela/internal/solana/processor.go:247:			VerifyBalance			0.0%
opentela/internal/solana/rates.go:30:				NewRateManager			100.0%
opentela/internal/solana/rates.go:38:				rateKey				100.0%
opentela/internal/solana/rates.go:44:				GetRate				100.0%
opentela/internal/solana/rates.go:72:				SetRate				100.0%
opentela/internal/solana/rates.go:79:				LoadFromConfig			87.5%
opentela/internal/solana/settlement.go:22:			SubmitSettlement		7.3%
opentela/internal/solana/spl.go:26:				FindATA				85.7%
opentela/internal/solana/spl.go:42:				findProgramAddress		57.1%
opentela/internal/solana/spl.go:57:				createProgramAddress		88.9%
opentela/internal/solana/spl.go:88:				isOnCurve			100.0%
opentela/internal/solana/spl.go:98:				BuildSPLTransferChecked		100.0%
opentela/internal/solana/spl.go:155:				BuildCreateATAInstruction	100.0%
opentela/internal/solana/spl.go:208:				SendSPLTransfer			0.0%
opentela/internal/solana/spl.go:249:				CreateATA			0.0%
opentela/internal/solana/spl.go:292:				GetSignatureStatus		0.0%
opentela/internal/solana/spl.go:331:				mustDecodeBase58		75.0%
opentela/internal/usage/aggregator.go:31:			NewAggregator			100.0%
opentela/internal/usage/aggregator.go:39:			AddRecord			100.0%
opentela/internal/usage/aggregator.go:59:			ShouldFlush			92.3%
opentela/internal/usage/aggregator.go:86:			BuildAggregate			88.9%
opentela/internal/usage/aggregator.go:114:			GetValue			83.3%
opentela/internal/usage/aggregator.go:127:			SetWindowStart			83.3%
opentela/internal/usage/crdt.go:13:				PublishAggregate		0.0%
opentela/internal/usage/crdt.go:27:				GetPeerAggregate		0.0%
opentela/internal/usage/crdt.go:46:				getAggregateKey			100.0%
opentela/internal/usage/extractor.go:15:			ExtractUsageMetrics		93.8%
opentela/internal/usage/reconciler.go:11:			ReconcileRecords		92.6%
opentela/internal/usage/store.go:16:				NewUsageStore			83.3%
opentela/internal/usage/store.go:29:				Close				100.0%
opentela/internal/usage/store.go:34:				SaveRecord			83.3%
opentela/internal/usage/store.go:48:				GetRecord			100.0%
opentela/internal/usage/store.go:72:				GetPendingRecords		88.9%
opentela/internal/usage/store.go:111:				MarkAggregated			83.3%
opentela/internal/usage/store.go:124:				SaveAggregate			83.3%
opentela/internal/usage/tracker.go:28:				InitTracker			0.0%
opentela/internal/usage/tracker.go:54:				CloseTracker			0.0%
opentela/internal/usage/tracker.go:62:				Track				0.0%
opentela/internal/usage/tracker.go:89:				GenerateRequestID		100.0%
opentela/internal/wallet/identity.go:35:			SignIdentity			75.0%
opentela/internal/wallet/identity.go:67:			VerifyIdentity			77.8%
opentela/internal/wallet/wallet.go:67:				NewWalletManager		66.7%
opentela/internal/wallet/wallet.go:98:				NewWalletManagerWithDir		66.7%
opentela/internal/wallet/wallet.go:116:				loadAccounts			94.4%
opentela/internal/wallet/wallet.go:151:				migrateLegacyWallet		87.5%
opentela/internal/wallet/wallet.go:186:				migrateLegacyDir		82.9%
opentela/internal/wallet/wallet.go:254:				saveAccounts			71.4%
opentela/internal/wallet/wallet.go:275:				Accounts			100.0%
opentela/internal/wallet/wallet.go:282:				DefaultAccount			100.0%
opentela/internal/wallet/wallet.go:291:				AddSolanaAccount		73.3%
opentela/internal/wallet/wallet.go:325:				ImportSolanaKeypair		81.8%
opentela/internal/wallet/wallet.go:375:				ExportKeypair			77.8%
opentela/internal/wallet/wallet.go:394:				ExportBase58PrivateKey		85.7%
opentela/internal/wallet/wallet.go:413:				FindByFile			100.0%
opentela/internal/wallet/wallet.go:423:				FindByPublicKey			100.0%
opentela/internal/wallet/wallet.go:433:				FindByProviderID		100.0%
opentela/internal/wallet/wallet.go:443:				WalletExists			100.0%
opentela/internal/wallet/wallet.go:451:				GetPublicKey			100.0%
opentela/internal/wallet/wallet.go:458:				GetPrivateKey			100.0%
opentela/internal/wallet/wallet.go:465:				GetWalletPath			100.0%
opentela/internal/wallet/wallet.go:472:				GetWalletType			100.0%
opentela/internal/wallet/wallet.go:481:				GetProviderID			100.0%
opentela/internal/wallet/wallet.go:490:				GetPrivateKeyBytes		77.8%
opentela/internal/wallet/wallet.go:510:				InitializeWallet		83.3%
opentela/internal/wallet/wallet.go:527:				writeSolanaKeypair		77.8%
opentela/internal/wallet/wallet.go:544:				parseSolanaKeypairJSON		100.0%
opentela/internal/wallet/wallet.go:565:				deriveProviderID		100.0%
opentela/internal/wallet/wallet.go:574:				copyDirRecursive		76.9%
opentela/plugins/webui/embed.go:12:				Static				0.0%
total:								(statements)			36.2%

Summary:

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR focuses on infrastructure/test improvements for OpenTela: adding Cloudflare OpenTofu IaC, new deployment automation (Ansible + Clariden scripts), expanding integration coverage for billing, and tightening protocol behaviors around peer trust/liveness.

Changes:

  • Added Cloudflare OpenTofu configuration (DNS/origin rules/Worker custom domain + R2) plus accompanying docs/spec/plan.
  • Added/expanded Go integration tests for billing (pipeline + Docker mesh) and improved peer discovery test robustness.
  • Updated protocol code to (a) always trust the local node when signed-binary enforcement is enabled and (b) switch peer liveness checks to libp2p ping; removed the legacy tokens/ Anchor project scaffold.

Reviewed changes

Copilot reviewed 36 out of 39 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tokens/yarn.lock Removed legacy tokens JS dependency lockfile.
tokens/tsconfig.json Removed legacy tokens TS config.
tokens/tests/tokens.ts Removed legacy Anchor TS test scaffold.
tokens/tests/anchor.test.rs Removed legacy Anchor/Playground test file.
tokens/programs/tokens/src/lib.rs Removed Anchor program implementation.
tokens/programs/tokens/Xargo.toml Removed Anchor/Xargo config.
tokens/programs/tokens/Cargo.toml Removed Anchor program manifest.
tokens/package.json Removed legacy tokens JS package manifest.
tokens/migrations/deploy.ts Removed legacy Anchor migration script.
tokens/Cargo.toml Removed legacy tokens Rust workspace.
tokens/Cargo.lock Removed legacy tokens Rust lockfile.
tokens/Anchor.toml Removed legacy Anchor workspace config.
tokens/.prettierignore Removed legacy formatter ignore list.
tokens/.gitignore Removed legacy tokens gitignore.
src/tests/integration/peer_discovery_test.go Makes integration builds more hermetic and speeds peer ID discovery for bootstrapping.
src/tests/integration/billing_pipeline_test.go Adds end-to-end (non-network) integration tests for billing pipeline components.
src/tests/integration/billing_mesh_test.go Adds a multi-node Docker mesh integration test validating usage tracking through the proxy path.
src/internal/protocol/node_table_test.go Adds coverage ensuring self is accepted even when signed-binary enforcement is enabled.
src/internal/protocol/node_table.go Adjusts signed-binary enforcement to never reject the local node’s own CRDT entry.
src/internal/protocol/clock.go Switches peer liveness verification from dial attempts to libp2p ping-based checks.
docs/superpowers/specs/2026-03-22-cloudflare-dns-opentofu-design.md Design spec for managing Cloudflare infra with OpenTofu.
docs/superpowers/plans/2026-03-22-cloudflare-opentofu.md Step-by-step implementation plan for the Cloudflare OpenTofu setup.
deploy/test/templates/otela.service.j2 New systemd unit template for test node deployment.
deploy/test/templates/cfg.yaml.j2 New templated node config for test deployments.
deploy/test/inventory.yml New Ansible inventory for test nodes.
deploy/test/deploy.yml New Ansible playbook for building, deploying, discovering bootstraps, and verifying mesh.
deploy/test/ansible.cfg Ansible config for the test deployment workflow.
deploy/cloudflare/variables.tf Declares Cloudflare OpenTofu input variables.
deploy/cloudflare/outputs.tf Exposes useful URLs as OpenTofu outputs.
deploy/cloudflare/main.tf Implements DNS + rulesets + R2 + Worker custom domain resources.
deploy/cloudflare/README.md Operator docs for initializing/importing/applying the Cloudflare OpenTofu config.
deploy/cloudflare/.terraform.lock.hcl Locks Cloudflare provider version/hashes for reproducible applies.
deploy/cloudflare/.gitignore Ensures tfstate/tfvars don’t get committed.
deploy/clariden/worker.cfg.yaml Clariden worker node config example.
deploy/clariden/start-relay.sh Script to start a relay node with data dir in /tmp.
deploy/clariden/sglang.toml EDF config for running sglang containerized workload.
deploy/clariden/setup.sh Local helper to cross-build and transfer artifacts to Clariden.
deploy/clariden/relay.cfg.yaml Clariden relay node config example.
deploy/clariden/job.sh SLURM job script to run sglang + OpenTela worker together.
Files not reviewed (1)
  • deploy/cloudflare/.terraform.lock.hcl: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/tests/integration/billing_mesh_test.go
Comment thread src/internal/protocol/clock.go Outdated
Comment thread deploy/cloudflare/README.md Outdated
xzyaoi and others added 10 commits March 22, 2026 17:59
Peers need to carry their own TCP port so bootstrap address construction
uses the correct port per-peer (relays use 18905, heads use 43905).
PublicPort is propagated in UpdateNodeTable alongside PublicAddress.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Introduces 'role' config (worker/head/relay) so nodes can self-identify.
Adds RoleRelay to SWIM metadata for relay membership propagation.
Default role is 'worker' for backwards compatibility.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Relays with public-addr automatically advertise as bootstraps.
InitializeMyself sets role and PublicPort from config.
MarkSelfAsBootstrap triggers for role=relay in addition to public-addr.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ConnectedBootstraps() was using the local node's tcpport for all peers,
producing wrong multiaddrs for relays on different ports (e.g., 18905).
Extracts BuildBootstrapAddr() as a testable pure function.
Falls back to local tcpport for un-upgraded peers without PublicPort.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The ping handler and ConnectedF callback were ignoring peers not yet in
the node table (added in 2c069a2 to avoid creating entries without build
attestation). This created a chicken-and-egg problem: fresh nodes could
never learn about peers because the fast path (gossip messages) ignored
them, and the slow path (CRDT DAG walk) could take minutes for deep
history.

Now, when we receive a gossip message from a peer that is actually
connected at the libp2p level, or when a new libp2p connection is
established, we create a minimal entry in the node table. The full
record (with build attestation) will overwrite this when the CRDT
PutHook fires.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…otstraps

peer.ID(stringValue) does not produce a valid peer.ID from a string
representation — it just casts the raw bytes. Use peer.Decode() which
properly parses the base58/CID-encoded peer ID string. This was causing
ConnectedBootstraps() to always see NotConnected for remote peers,
making the endpoint only return the local node's own address.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
xzyaoi and others added 26 commits March 22, 2026 20:57
The 30-second verification ticker was writing Connected/LastSeen for
every peer to the CRDT store, creating ~12,000 DAG entries per day.
Fresh nodes joining the network had to walk this entire history via
bitswap before they could discover any peers, taking 20+ minutes.

Liveness updates (Connected, LastSeen, stale peer marking) now go to
the in-memory node table only via UpdateNodeTableHook. Structural
changes (join, leave, service registration, bootstrap) still go through
CRDT store.Put for replication.

Also removes the periodic ReannounceLocalServices() from the 2-minute
maintenance ticker — it was writing to CRDT every 2 minutes even when
nothing changed. Reannouncement still happens on actual reconnects.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
All deploy configs now use only https://bootstraps.opentela.ai/v1/dnt/bootstraps
as their bootstrap source. No hardcoded relay multiaddrs or peer IDs.

Nodes discover each other dynamically:
- Head nodes register themselves via public-addr
- Relay nodes register via role=relay + public-addr
- Workers and relays fetch the aggregated list from the domain endpoint

This eliminates the need to update configs when relay IPs or peer IDs change.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… reachable

When the head node forwards a request to a worker it's not directly
connected to (e.g., worker behind HPC firewall), it now tries relay
circuit addresses through any connected peer. This enables the flow:

  client → head node → relay (circuit) → worker

EnsureConnected() first tries the peerstore's known addresses, then
constructs /p2p/<relay>/p2p-circuit/p2p/<target> multiaddrs through
each connected peer until one succeeds.

The proxy handler calls EnsureConnected before forwarding, returning
502 Bad Gateway with a clear error if the worker is truly unreachable.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ForceReachabilityPublic() tells libp2p the node is publicly reachable,
which prevents it from making relay reservations. Workers behind HPC
firewalls need libp2p to detect they're private so it automatically
reserves slots on relay nodes, enabling circuit relay connections.

Now only nodes with public-addr set (head nodes, relays) force public
reachability. Workers without public-addr let AutoNAT detect their
reachability, enabling automatic relay reservation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Workers without public-addr are forced to ForceReachabilityPrivate and
use EnableAutoRelayWithPeerSource to automatically find relay servers
among connected peers and maintain active reservations.

This is required for relay circuit routing: the relay returns
NO_RESERVATION (204) when a head node tries to connect to a worker
through it, unless the worker has previously reserved a slot.

The peer source callback offers all connected peers as relay candidates.
Peers running EnableRelayService() (all nodes) will accept reservations.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
AutoRelay wasn't making reservations due to timing issues (peer source
callback fires before host has connections). Instead, workers now
explicitly call client.Reserve() on every connected peer after bootstrap
and on reconnects. This ensures the relay has an active reservation so
head nodes can connect to workers via circuit relay.

MakeRelayReservations() runs 10s after bootstrap (to let connections
establish) and 5s after each reconnect. Only runs for workers (nodes
without public-addr).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace relay circuit stream approach (which times out due to p2phttp
over circuit limitations) with HTTP relay-hop routing:

When the head node can't directly reach a worker, it forwards the
request through a connected relay node's /v1/p2p/<worker>/ handler:

  client → head (HTTP) → relay (libp2p) → worker (libp2p)

The relay receives the request via its P2P listener and forwards it
to the worker using its own direct libp2p connection. No relay circuit
or reservation needed for this path — just standard p2phttp between
directly connected peers at each hop.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
homedir.Dir() from github.com/mitchellh/go-homedir reads /etc/passwd
and ignores the HOME env var. This caused relay nodes (which override
HOME=/tmp/opentela-relay in start-relay.sh) to still read/write keys
from the real home directory on Lustre, producing inconsistent peer IDs
across sessions.

os.UserHomeDir() (Go stdlib) respects the HOME environment variable,
so the relay's key file is now correctly stored under the overridden
home directory.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two fixes for workers behind relays:

1. GetAllProviders: Include all peers with matching services regardless
   of connectivity status. Workers behind relays appear as disconnected
   from the head's perspective but are reachable via relay-hop routing.

2. Stale peer cleanup: Skip peers with registered services when marking
   as disconnected or removing from the table. They're actively providing
   workloads and reachable through relays even if we can't ping directly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The hardcoded GossipSub parameters (D=128, Dlo=16, Dhi=256) prevented
mesh formation in networks with fewer than 16 peers. With only 3-5
nodes (2 heads + 1 relay + workers), the Dlo=16 threshold was never
met, causing GossipSub to never form a proper mesh for the CRDT topic.
This meant CRDT data from workers never propagated through the relay
to cloud head nodes.

Reverts to Go-libp2p default params (D=6, Dlo=4, Dhi=12) which work
correctly for any network size. The scalability.crdt_tuned override
is preserved for large deployments.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The close-all-peers-and-reconnect pattern during CRDT initialization
was disrupting GossipSub mesh topology. After closing all connections,
the ping topic (ocf-crdt-net) would reform because it actively publishes
every 20s, but the CRDT topic (ocf-crdt) mesh never reformed because
it only publishes on data changes. This caused CRDT data from workers
to never propagate through the relay to cloud head nodes.

Removing the ClosePeer loop preserves the GossipSub mesh that was
already established during host creation. The IPFS-lite bootstrap
still runs to ensure bitswap connectivity.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…formation

GossipSub exchanges topic subscriptions with already-connected peers.
When topics were joined before bootstrap (peers not yet connected),
the relay never saw cloud nodes as CRDT topic subscribers, creating
a unidirectional mesh where CRDT data never propagated from relay
to cloud.

Fix: bootstrap and wait 3s for connections to establish BEFORE joining
GossipSub topics. This ensures peers are connected when topic
subscriptions are exchanged, forming a proper bidirectional mesh.

Also adds diagnostic logging to PubSubBroadcaster (topic peer count
on broadcast, received message source on receive).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
FindRelayFor now prefers peers with role=relay in the node table,
since relay nodes bridge network segments and are most likely
connected to unreachable workers. Previously it picked any connected
peer, which could route through a head node that also can't reach
the worker.

Also includes GossipSub mesh fix (bootstrap before topic join) and
diagnostic logging in PubSubBroadcaster.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Workers now store the relay peer ID in their CRDT entry (RelayPeer
field) after successfully making a relay reservation. Head nodes use
this to route requests through the correct relay:

  head → libp2p://<worker's relay>/v1/p2p/<worker>/<path>

This solves the multi-relay problem: with Euler, Clariden, etc. relays,
the head node needs to know which specific relay can reach each worker.
The worker tells it by advertising RelayPeer in the CRDT.

FindRelayFor checks the worker's RelayPeer first, falling back to any
connected relay-role peer.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds sections covering:
- Direct vs relay-hop routing flow diagrams
- How the head node detects unreachable workers and routes through relays
- The relay_peer CRDT field and automatic relay registration
- Zero-config worker bootstrap via domain endpoint

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Expose the node's own Peer record via GetSelf(), provide a test helper
SetMyselfForTest() for other packages, and add RegisterRemotePeer() to
write a remote peer's entry into the CRDT store using the peer's own ID
as the key (with Connected=false and a fresh LastSeen timestamp).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ConnectedBootstraps() now includes relay peers seen within the last 10
minutes (maxBootstrapAge), even when not currently P2P-connected. This
ensures newly-registered relay nodes propagated via CRDT are usable as
bootstrap addresses before a direct connection is established.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add challengePeer (GET /v1/dnt/challenge) and registerPeer (POST /v1/dnt/register)
handlers with nonce-based challenge-response, RSA public key verification,
build/identity attestation validation, and CRDT peer registration.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds getSelf (GET /v1/self) returning this node's Peer struct and
signData (POST /v1/sign) signing hex-encoded data with the node's
libp2p private key, both restricted to loopback clients.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Register /v1/dnt/challenge and /v1/dnt/register under the crdtGroup
gated by role=="head", and add /v1/self and /v1/sign to the v1 group.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a register_relay function and background registration loop to
start-relay.sh. After the relay becomes healthy, it fetches a challenge
nonce from the bootstrap service, signs it with the relay's libp2p key,
and POSTs the full self-info plus challenge_response to register. Uses
exponential backoff (5s → 120s cap) on failure and re-registers every 5
minutes on success.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… Clariden

All three deploy configs (ocf-1, ocf-2, euler worker) already had no
hardcoded relay multiaddrs — only the bootstrap HTTP URL. Updated
deploy/clariden/start-relay.sh to match deploy/euler/start-relay.sh:
added IP detection, public-addr config patching, health-wait loop,
register_relay() challenge-response function, and registration loop
with exponential backoff. Adapted for Clariden's binary name (otela)
and ports (18092/18905).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…metadata, and invalid ports

- Replace c.ClientIP() with c.Request.RemoteAddr in isLoopback() to prevent X-Forwarded-For spoofing on /v1/self and /v1/sign
- Add nil check for P2P host in signData() to return 503 when node is not ready
- Sanitize peer metadata before CRDT write: clear Owner/ProviderID when no identity attestation, restrict Role to exactly ["relay"], clear Service/Load/Hardware
- Validate public_port as a numeric TCP port (1-65535) instead of just non-empty
- Add TestGetSelf_SpoofedXForwardedFor to verify spoofed XFF is rejected

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…tricted nodes

- Add dedicated `wsport` config for a separate WebSocket listener port (43906),
  avoiding conflict with raw TCP multistream on the main libp2p port
- Add `ws_domain` config (e.g., "p2p.opentela.ai") so ConnectedBootstraps()
  advertises WSS multiaddrs for all peers with a PublicAddress
- Add Cloudflare DNS + origin rules for p2p.opentela.ai proxying 443 → 43906
- Nodes behind restrictive firewalls (e.g., JSC/JUWELS) can now connect via
  wss://p2p.opentela.ai:443, eliminating the need for cloudflared tunnels

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…oadcaster logging

- Add nil checks for GetP2PNode in MakeRelayReservations, IsDirectlyConnected,
  FindRelayFor to prevent panics when host is not ready
- Defer nonce consumption in registration until after signature verification
  so attackers cannot burn legitimate nonces
- Add myselfMu RWMutex to protect the global `myself` Peer from concurrent
  read/write across goroutines (ticker, reannounce, relay reservations)
- Remove per-call debug logging from CRDT PubSubBroadcaster (ListPeers on
  every Broadcast was unnecessary overhead at scale)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@xzyaoi xzyaoi merged commit a8c1055 into main Mar 25, 2026
2 checks passed
@xzyaoi xzyaoi deleted the fix/security branch March 25, 2026 16:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants