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<CommunityGoalInfo> Goals);
/// <summary>Official SDK membership; uncertain signup intents are never replayed.</summary>
public sealed class NativeCommunityGoalClient(IOriginalCommerceChannel channel,long marketId=CommunityGoalCatalogue.HomerMarket,ulong? goalId=null)
{
private CommunityGoalInfo Target(IReadOnlyList<CommunityGoalInfo> goals,bool subscribed=false)
=>CommunityGoalCatalogue.RequireDestinationTrade(goals,marketId,goalId,DateTimeOffset.UtcNow,subscribed);
private async Task<JsonElement> 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<OriginalCommunityGoalRead> 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<until)
{await Task.Delay(80,token);}
}
var generation=queued.GetProperty("generation").GetUInt64();var result=await WaitAsync(generation,token);
if(result.GetProperty("state").GetInt32()!=3||result.GetProperty("kind").GetInt32()!=(mine?2:0)
||result.GetProperty("mutationsSent").GetInt32()!=0||!result.GetProperty("fresh").GetBoolean())
throw new InvalidDataException("Original CG read did not return a fresh, read-only response.");
var body=result.GetProperty("responseBody").GetString()!;var proof=result.GetProperty("proof").GetString()!;
if(Convert.ToHexStringLower(SHA256.HashData(Encoding.UTF8.GetBytes(body)))!=proof)throw new InvalidDataException("CG response proof does not match.");
return new(generation,proof,body,CommunityGoalCatalogue.Parse(body));
}
public async Task<CommunityGoalInfo> EnsureTradeJoinedAsync(string directory,Func<CancellationToken,Task> 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;
}
}
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<CommunityGoalInfo> Goals);
/// <summary>Official SDK membership; uncertain signup intents are never replayed.</summary>
public sealed class NativeCommunityGoalClient(IOriginalCommerceChannel channel,long marketId=CommunityGoalCatalogue.HomerMarket,ulong? goalId=null)
{
private CommunityGoalInfo Target(IReadOnlyList<CommunityGoalInfo> goals,bool subscribed=false)
=>CommunityGoalCatalogue.RequireDestinationTrade(goals,marketId,goalId,DateTimeOffset.UtcNow,subscribed);
private async Task<JsonElement> 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<OriginalCommunityGoalRead> 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<until)
{await Task.Delay(80,token);}
}
var generation=queued.GetProperty("generation").GetUInt64();var result=await WaitAsync(generation,token);
if(result.GetProperty("state").GetInt32()!=3||result.GetProperty("kind").GetInt32()!=(mine?2:0)
||result.GetProperty("mutationsSent").GetInt32()!=0||!result.GetProperty("fresh").GetBoolean())
throw new InvalidDataException("Original CG read did not return a fresh, read-only response.");
var body=result.GetProperty("responseBody").GetString()!;var proof=result.GetProperty("proof").GetString()!;
if(Convert.ToHexStringLower(SHA256.HashData(Encoding.UTF8.GetBytes(body)))!=proof)throw new InvalidDataException("CG response proof does not match.");
return new(generation,proof,body,CommunityGoalCatalogue.Parse(body));
}
public async Task<CommunityGoalInfo> EnsureTradeJoinedAsync(string directory,Func<CancellationToken,Task> 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;
}
}