diff --git a/marketRFP.ts b/marketRFP.ts index ae9e4db..c1ee5b4 100644 --- a/marketRFP.ts +++ b/marketRFP.ts @@ -69,6 +69,7 @@ type CollectedBid = { cid: string; record: BidRecord; payload?: Record; + config?: Record; }; export interface MarketRFPConfig { @@ -307,30 +308,37 @@ async function collectBidsForRfp( // Resolve bid payloads from PDS // --------------------------------------------------------------------------- +async function resolveAtRef(uri: string): Promise | undefined> { + const parts = uri.replace("at://", "").split("/"); + const did = parts[0]; + const collection = parts[1]; + const rkey = parts[2]; + const pds = await resolvePDS(did); + const url = new URL(`${pds}/xrpc/com.atproto.repo.getRecord`); + url.searchParams.set("repo", did); + url.searchParams.set("collection", collection); + url.searchParams.set("rkey", rkey); + const res = await fetch(url.toString()); + if (!res.ok) return undefined; + const data = await res.json(); + return data.value as Record; +} + async function resolveBidPayloads(bids: CollectedBid[]): Promise { await Promise.all(bids.map(async (bid) => { - const payloadRef = bid.record.payload; - if (!payloadRef?.uri) return; - - // Parse at://did/collection/rkey - const parts = payloadRef.uri.replace("at://", "").split("/"); - const did = parts[0]; - const collection = parts[1]; - const rkey = parts[2]; - - try { - const pds = await resolvePDS(did); - const url = new URL(`${pds}/xrpc/com.atproto.repo.getRecord`); - url.searchParams.set("repo", did); - url.searchParams.set("collection", collection); - url.searchParams.set("rkey", rkey); - const res = await fetch(url.toString()); - if (res.ok) { - const data = await res.json(); - bid.payload = data.value as Record; + if (bid.record.payload?.uri) { + try { + bid.payload = await resolveAtRef(bid.record.payload.uri); + } catch (err) { + log("resolve bid payload failed", { uri: bid.record.payload.uri, err: String(err) }); + } + } + if (bid.record.config?.uri) { + try { + bid.config = await resolveAtRef(bid.record.config.uri); + } catch (err) { + log("resolve bid config failed", { uri: bid.record.config.uri, err: String(err) }); } - } catch (err) { - log("resolve bid payload failed", { uri: payloadRef.uri, err: String(err) }); } })); } @@ -449,7 +457,7 @@ export async function marketRFPSubmitWorkflow( spindleHostname: string, ): Promise { // Step 1: Generate service name before user_data so it knows its name - const serviceName = `policy-engine-${randomHex(8)}`; + const serviceName = `${config.vm.role}-${randomHex(8)}`; log("service name generated", { serviceName }); // Step 2: Login to ATProto @@ -457,6 +465,7 @@ export async function marketRFPSubmitWorkflow( await session.login({ identifier: config.handle, password: config.password }); const agent = new Agent(session); const agentDid = agent.assertDid; + const agentDidPlcKey = agentDid.split(":")[2]; log("atproto authenticated", { did: agentDid, handle: config.handle }); // Step 3: Build user_data with policy-engine bootstrap @@ -475,7 +484,7 @@ export async function marketRFPSubmitWorkflow( mem: config.vm.mem, disk: config.vm.disk, network: config.vm.network, - role: config.vm.role, + role: serviceName, user_data: userData, location: config.vm.location, createdAt: new Date().toISOString(), @@ -506,6 +515,7 @@ export async function marketRFPSubmitWorkflow( const winner = scoreLowestCost(bids); if (!winner) throw new Error("No scoreable bid found"); log("bid winner selected", { bidUri: winner.uri, did: winner.did, cost: winner.payload?.cost }); + log("winner", { winner: winner }); const bidRef: StrongRef = { $type: "com.atproto.repo.strongRef", @@ -534,9 +544,10 @@ export async function marketRFPSubmitWorkflow( definition: { // aud is us aud: `api://ATProto?actx=${agentDid}`, - iss: "https://droplet-oidc.its1337.com", + iss: `${winner.config.issuer_uri}`, // actx is them for this simple example - sub: `actx:${winner.did}:plc:${agentDid}:role:${serviceName}`, + // TODO Thi should be templating winner.config.subject + sub: `actx:${winner.config.actx}:plc:${agentDidPlcKey}:role:${serviceName}`, policies: ["ssh-key-register"], }, }, @@ -586,6 +597,7 @@ export async function marketRFPSubmitWorkflow( }, createdAt: new Date().toISOString(), }; + log("com.fedproxy.rbac creating", { record: rbacRecord }); const rbacRef = await atprotoCreateRecord(agent, RBAC_NSID, rbacRecord); log("com.fedproxy.rbac created", { uri: rbacRef.uri, subject: agentDid, serviceName }); @@ -690,7 +702,7 @@ export function marketRFPConfigFromEnv(): MarketRFPConfig { region: Deno.env.get("VM_LOCATION_REGION") ?? "west", }, }, - bidWindowMs: parseInt(Deno.env.get("BID_WINDOW_MS") ?? "30000", 10), + bidWindowMs: parseInt(Deno.env.get("BID_WINDOW_MS") ?? "5000", 10), vmReadyTimeoutMs: parseInt(Deno.env.get("VM_READY_TIMEOUT_MS") ?? "600000", 10), peReadyTimeoutMs: parseInt(Deno.env.get("PE_READY_TIMEOUT_MS") ?? "120000", 10), }; diff --git a/run.sh b/run.sh index f6ba82c..5e4a475 100644 --- a/run.sh +++ b/run.sh @@ -1 +1 @@ -SPINDLE_OWNER_DID="did:plc:5svqtrhheairglgiiyvutzik" SPINDLE_HOSTNAME="spindle-1780032922896.johnandersen777.bsky.social.fedproxy.com" POLICY_ENGINE_URL="http://$(docker inspect spindle-sshai-policy-engine-1 --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}'):8080" PORT="9090" deno run --allow-net --allow-env --allow-read --allow-write --watch main.ts 2>&1 | tee spindle.logs | grep --line-buffered -E '^\{' | jq --unbuffered -c . | while IFS='' read -r line; do echo '---' && printf '%s\n' "$line" | yq -P; done +SPINDLE_OWNER_DID="did:plc:5svqtrhheairglgiiyvutzik" SPINDLE_HOSTNAME="spindle-1780032922896.johnandersen777.bsky.social.fedproxy.com" COMPUTE_PROVIDER=market.rfp PORT="9090" deno run --allow-net --allow-env --allow-read --allow-write --watch main.ts 2>&1 | tee spindle.logs | grep --line-buffered -E '^\{' | jq --unbuffered -c . | while IFS='' read -r line; do echo '---' && printf '%s\n' "$line" | yq -P; done