using System.Security.Cryptography; using System.Text; using System.Text.Json; namespace XFEEDToolBox.Core.Execution; public sealed record OriginalCommunityGoalRead(ulong Generation,string Proof,string Body,IReadOnlyList Goals); /// Official SDK membership; uncertain signup intents are never replayed. public sealed class NativeCommunityGoalClient(IOriginalCommerceChannel channel,long marketId=CommunityGoalCatalogue.HomerMarket,ulong? goalId=null) { private CommunityGoalInfo Target(IReadOnlyList goals,bool subscribed=false) =>CommunityGoalCatalogue.RequireDestinationTrade(goals,marketId,goalId,DateTimeOffset.UtcNow,subscribed); private async Task WaitAsync(ulong generation,CancellationToken token) { using var timeout=CancellationTokenSource.CreateLinkedTokenSource(token);timeout.CancelAfter(TimeSpan.FromSeconds(35)); while(true) { JsonElement result; try{result=await channel.CallAsync(new{op="cg.status",generation},timeout.Token);} catch(Exception error)when(!timeout.IsCancellationRequested&&error is IOException or OperationCanceledException) {await Task.Delay(80,timeout.Token);continue;} if(result.GetProperty("state").GetInt32()>=3)return result; await Task.Delay(80,timeout.Token); } } public async Task ReadAsync(bool mine,CancellationToken token=default) { // Journal Docked can precede the original healthy docking snapshot. // Retry only this pre-queue refusal; an accepted generation is polled, // and signup intents retain their existing no-replay contract. var until=DateTimeOffset.UtcNow.AddSeconds(10);JsonElement queued; while(true) { token.ThrowIfCancellationRequested(); try{queued=await channel.CallAsync(new{op=mine?"cg.read.mine":"cg.read",marketId},token);break;} catch(InvalidOperationException error)when(error.Message=="Fresh healthy docking at the requested CG market required"&&DateTimeOffset.UtcNow EnsureTradeJoinedAsync(string directory,Func beforeInput,CancellationToken token=default) { Directory.CreateDirectory(directory);await beforeInput(token); var mine=await ReadAsync(true,token); await File.WriteAllTextAsync(Path.Combine(directory,"membership-latest.json"),JsonSerializer.Serialize(mine),token); var joined=mine.Goals.SingleOrDefault(goal=>goal.MarketId==marketId&&(!goalId.HasValue||goal.Id==goalId)&&goal.Active&&goal.Expires>DateTimeOffset.UtcNow); if(joined is not null) { joined=Target(mine.Goals,true); // A fresh original membership response also reconciles a prior // process loss after opt-in; no signup needs to be sent again. foreach(var pending in Directory.EnumerateFiles(directory,"*.pending.json")) { var confirmed=pending.Replace(".pending.json",".confirmed.json",StringComparison.Ordinal); if(File.Exists(confirmed))continue; using var intent=JsonDocument.Parse(await File.ReadAllTextAsync(pending,token)); var original=intent.RootElement.GetProperty("target"); if(original.GetProperty("Id").GetUInt64()!=joined.Id||original.GetProperty("MarketId").GetInt64()!=joined.MarketId) throw new InvalidDataException("A prior signup belongs to another CG and cannot be reconciled as this membership."); await File.WriteAllTextAsync(confirmed,JsonSerializer.Serialize(new{joined,reconciledFromOriginalMembership=true}),token); } return joined; } foreach(var pending in Directory.EnumerateFiles(directory,"*.pending.json")) if(!File.Exists(pending.Replace(".pending.json",".confirmed.json",StringComparison.Ordinal))) throw new InvalidOperationException("A prior CG signup is unresolved; no signup will be replayed."); var all=await ReadAsync(false,token);var target=Target(all.Goals); await beforeInput(token);var operationKey=Guid.NewGuid().ToString("N"); var pendingPath=Path.Combine(directory,operationKey+".pending.json"); await using(var file=new FileStream(pendingPath,FileMode.CreateNew,FileAccess.Write,FileShare.Read)) await JsonSerializer.SerializeAsync(file,new{operationKey,target,channel=channel.Identity,all.Generation,all.Proof,at=DateTimeOffset.UtcNow},cancellationToken:token); // Once the original request is accepted, finish observing it even if // the caller cancels. Never substitute an unverified local flag. var queued=await channel.CallAsync(new{op="cg.join",marketId,goalId=target.Id, operationKey,generation=all.Generation,proof=all.Proof},token); using var reconcile=new CancellationTokenSource(TimeSpan.FromSeconds(50)); var result=await WaitAsync(queued.GetProperty("generation").GetUInt64(),reconcile.Token); await File.WriteAllTextAsync(Path.Combine(directory,operationKey+".response.json"),JsonSerializer.Serialize(result),reconcile.Token); if(result.GetProperty("state").GetInt32()!=3)throw new InvalidOperationException("Original CG signup has not confirmed success; no replay is permitted."); mine=await ReadAsync(true,reconcile.Token);joined=Target(mine.Goals,true); if(joined.Id!=target.Id)throw new InvalidDataException("CG membership differs from the exact signup target."); await File.WriteAllTextAsync(Path.Combine(directory,"membership-latest.json"),JsonSerializer.Serialize(mine),reconcile.Token); await File.WriteAllTextAsync(Path.Combine(directory,operationKey+".confirmed.json"),JsonSerializer.Serialize(new{operationKey,joined,confirmedAt=DateTimeOffset.UtcNow}),reconcile.Token); return joined; } }