diff --git a/api/admin/generate-first-action-receipt.js b/api/admin/generate-first-action-receipt.js index de9aac6..a4f09ed 100644 --- a/api/admin/generate-first-action-receipt.js +++ b/api/admin/generate-first-action-receipt.js @@ -11,7 +11,7 @@ function paidOrApproved(claim) { function preflight(claim) { if (!paidOrApproved(claim)) return 'FIRST_ACTION_REQUIRES_PAID_CLAIM'; if (!claim.tenant_signer_ens || !claim.tenant_signer_kid) return 'TENANT_SIGNER_ENS_REQUIRED'; - if (claim.tenant_signer_record_status !== 'records_verified') return 'TENANT_SIGNER_RECORDS_NOT_VERIFIED'; + if (claim.tenant_signer_record_status !== 'records_verified' && claim.tenant_signer_record_status !== 'verified') return 'TENANT_SIGNER_RECORDS_NOT_VERIFIED'; if (!claim.genesis_receipt_id) return 'GENESIS_RECEIPT_REQUIRED'; if ('tenant_proof_status' in claim && claim.tenant_proof_status && claim.tenant_proof_status !== 'verified') return 'TENANT_PROOF_NOT_VERIFIED'; return null; diff --git a/api/admin/run-activation-pipeline.js b/api/admin/run-activation-pipeline.js index a3baf89..9c3b094 100644 --- a/api/admin/run-activation-pipeline.js +++ b/api/admin/run-activation-pipeline.js @@ -4,6 +4,7 @@ const db = require('../../lib/db'); const { requireAdminAuth } = require('./_auth'); const pinAgentCards = require('./pin-agent-cards'); const generateGenesisReceipt = require('./generate-genesis-receipt'); +const generateFirstActionReceipt = require('./generate-first-action-receipt'); function makeInternalRes() { return { @@ -26,6 +27,17 @@ function paymentStep(claim) { return claim.status === 'paid' || claim.status === 'cards_pinned' || claim.payment_status === 'paid' || claim.paid_at ? 'already_paid' : 'payment_required'; } +function recordsVerified(claim) { + return claim.tenant_signer_record_status === 'records_verified' || claim.tenant_signer_record_status === 'verified'; +} + +function canAttemptFirstAction(claim) { + if (!claim.genesis_receipt_id) return false; + if (!recordsVerified(claim)) return false; + if ('tenant_proof_status' in claim && claim.tenant_proof_status && claim.tenant_proof_status !== 'verified') return false; + return true; +} + module.exports = async function handler(req, res) { res.setHeader('Content-Type', 'application/json; charset=utf-8'); res.setHeader('Cache-Control', 'no-store'); @@ -52,6 +64,8 @@ module.exports = async function handler(req, res) { agent_cards: 'not_started', genesis_receipt: claim.genesis_receipt_id ? 'already_generated' : 'not_started', tenant_action_proof: claim.tenant_proof_status || 'not_submitted', + first_action_receipt: claim.first_action_receipt_status || 'not_generated', + errors: {}, }; if (steps.payment !== 'already_paid') { @@ -71,6 +85,7 @@ module.exports = async function handler(req, res) { steps.agent_cards = pinRes.body && pinRes.body.status === 'ALREADY_PINNED' ? 'cards_pinned' : 'cards_pinned'; } else { steps.agent_cards = pinRes.body && pinRes.body.status ? pinRes.body.status : 'pinning_not_completed'; + steps.errors.agent_cards = steps.agent_cards; } } @@ -86,6 +101,23 @@ module.exports = async function handler(req, res) { steps.genesis_receipt = 'already_generated'; } else { steps.genesis_receipt = genesisRes.body && genesisRes.body.status ? genesisRes.body.status : 'not_generated'; + steps.errors.genesis_receipt = steps.genesis_receipt; + } + } + + claimResult = await db.query('select * from claim_requests where claim_id = $1 limit 1', [claimId]); + claim = claimResult.rows[0] || claim; + if (claim.first_action_receipt_status === 'verified') { + steps.first_action_receipt = 'verified'; + } else if (claim.first_action_receipt_status === 'challenge_ready') { + steps.first_action_receipt = 'challenge_ready'; + } else if (canAttemptFirstAction(claim)) { + const firstActionRes = await runHandler(generateFirstActionReceipt, { method: 'POST', headers: { 'x-admin-api-key': req.headers['x-admin-api-key'] || process.env.ADMIN_API_KEY }, body: { claimId } }); + if (firstActionRes.statusCode >= 200 && firstActionRes.statusCode < 300) { + steps.first_action_receipt = firstActionRes.body && firstActionRes.body.status ? firstActionRes.body.status : 'challenge_ready'; + } else { + steps.first_action_receipt = firstActionRes.body && firstActionRes.body.status ? firstActionRes.body.status : 'not_generated'; + steps.errors.first_action_receipt = steps.first_action_receipt; } } diff --git a/api/admin/verify-managed-ens-publication.js b/api/admin/verify-managed-ens-publication.js index 2b7271a..ac44ae8 100644 --- a/api/admin/verify-managed-ens-publication.js +++ b/api/admin/verify-managed-ens-publication.js @@ -3,6 +3,25 @@ const db = require('../../lib/db'); const { requireAdminAuth } = require('./_auth'); const { verifyManagedEnsPublication } = require('../../lib/claims/managed-ens-publication'); +const runActivationPipeline = require('./run-activation-pipeline'); + +function makeInternalRes() { + return { + statusCode: 200, + body: null, + headers: {}, + setHeader(name, value) { this.headers[String(name).toLowerCase()] = value; }, + status(code) { this.statusCode = code; return this; }, + json(payload) { this.body = payload; return this; }, + }; +} + +async function advanceActivation(req, claimId) { + const pipelineRes = makeInternalRes(); + await runActivationPipeline({ method: 'POST', headers: { 'x-admin-api-key': req.headers['x-admin-api-key'] || process.env.ADMIN_API_KEY }, body: { claimId } }, pipelineRes); + if (pipelineRes.statusCode >= 200 && pipelineRes.statusCode < 300 && pipelineRes.body && pipelineRes.body.ok) return pipelineRes.body.steps || {}; + return { errors: { activation_pipeline: pipelineRes.body && pipelineRes.body.status ? pipelineRes.body.status : 'ACTIVATION_PIPELINE_FAILED' } }; +} module.exports = async function handler(req, res) { res.setHeader('Content-Type', 'application/json; charset=utf-8'); @@ -19,12 +38,14 @@ module.exports = async function handler(req, res) { if (verification.ok) { await db.query( `update claim_requests - set managed_ens_publication_status = 'verified', tenant_signer_record_status = 'records_verified', + set managed_ens_publication_status = 'verified', tenant_signer_record_status = 'verified', tenant_signer_records_verified_at = now(), managed_ens_verified_at = now(), managed_ens_publication_error = null, tenant_signer_verification_error = null, updated_at = now() where claim_id = $1`, [claimId] ); + const advanced = await advanceActivation(req, claimId); + return res.status(200).json({ ok: true, claim_id: claimId, managed_ens_publication_status: 'verified', tenant_signer_record_status: 'verified', verification, advanced }); } else { await db.query( `update claim_requests diff --git a/api/claims/status.js b/api/claims/status.js index d9414fd..a399f58 100644 --- a/api/claims/status.js +++ b/api/claims/status.js @@ -44,6 +44,7 @@ module.exports = async function handler(req, res) { } catch (_error) { cards = []; } + const recordsVerified = claim.tenant_signer_record_status === 'records_verified' || claim.tenant_signer_record_status === 'verified'; const paymentConfirmed = claim.status === 'paid' || claim.status === 'cards_pinned' || claim.status === 'active' || claim.payment_status === 'paid' || Boolean(claim.paid_at); const pipeline = { tenant_signing_identity: claim.tenant_signer_ens ? 'generated' : 'missing', @@ -55,7 +56,7 @@ module.exports = async function handler(req, res) { genesis_receipt: claim.genesis_receipt_id ? 'generated' : 'not_generated', tenant_action_proof: claim.tenant_proof_status || 'not_submitted', first_action_receipt: claim.first_action_receipt_status || 'not_generated', - agent_live: paymentConfirmed && claim.tenant_signer_record_status === 'records_verified' && cardsStatus(cards) === 'cards_pinned' && claim.genesis_receipt_id && claim.tenant_proof_status === 'verified' && (claim.first_action_receipt_status === 'verified' || typeof claim.first_action_receipt_status === 'undefined') ? 'live' : 'not_live', + agent_live: paymentConfirmed && recordsVerified && cardsStatus(cards) === 'cards_pinned' && claim.genesis_receipt_id && claim.tenant_proof_status === 'verified' && (claim.first_action_receipt_status === 'verified' || typeof claim.first_action_receipt_status === 'undefined') ? 'live' : 'not_live', }; return res.status(200).json({ ok: true, read_only: true, claim: { ...stripClaimSecrets(claim), cardsStatus: cardsStatus(cards), managed_ens_publication: { status: claim.managed_ens_publication_status || 'not_started', signer_ens: claim.tenant_signer_ens || null, parent_namespace: claim.managed_ens_parent_namespace || null, record_names: Object.keys(claim.managed_ens_required_txt_records || claim.tenant_signer_txt_records || {}), helper_copy: 'The operator must publish the generated TXT records to ENS before signer verification can pass.', verified_at: claim.managed_ens_verified_at || null, error: claim.managed_ens_publication_error || null } }, pipeline, cards }); } catch (_error) { diff --git a/public/admin/claims.html b/public/admin/claims.html index 189472b..a3f05b5 100644 --- a/public/admin/claims.html +++ b/public/admin/claims.html @@ -51,7 +51,7 @@

CommandLayer Claims Admin

Internal operator dashboard f