diff --git a/api/admin/_auth.js b/api/admin/_auth.js deleted file mode 100644 index d279848..0000000 --- a/api/admin/_auth.js +++ /dev/null @@ -1,25 +0,0 @@ -'use strict'; - -function requireAdminAuth(req, res) { - const configured = process.env.ADMIN_API_KEY; - if (!configured) { - res.status(503).json({ ok: false, status: 'ADMIN_NOT_CONFIGURED' }); - return false; - } - - const authorization = req.headers && (req.headers.authorization || req.headers.Authorization); - const token = typeof authorization === 'string' && authorization.startsWith('Bearer ') - ? authorization.slice('Bearer '.length) - : ''; - - if (!token || token !== configured) { - res.status(401).json({ ok: false, status: 'UNAUTHORIZED' }); - return false; - } - - return true; -} - -module.exports = { - requireAdminAuth -}; diff --git a/api/admin/claim-action.js b/api/admin/claim-action.js deleted file mode 100644 index dc94edc..0000000 --- a/api/admin/claim-action.js +++ /dev/null @@ -1,140 +0,0 @@ -'use strict'; - -const db = require('../../lib/db'); -const { requireAdminAuth } = require('./_auth'); - -const CLAIM_STATUSES = { - CREATED: 'created', - APPROVED: 'approved', - REJECTED: 'rejected', - CARDS_PUBLISHED: 'cards_published', - PAYMENT_PENDING: 'payment_pending', - PAID: 'paid', - ERC8004_REGISTERED: 'erc8004_registered', - ENS_RECORDS_GENERATED: 'ens_records_generated', - ENS_PROVISIONED: 'ens_provisioned', - LIVE_TEST_PASSED: 'live_test_passed', - LIVE: 'live', - FAILED: 'failed' -}; - -const SUPPORTED_ACTIONS = new Set(['approve', 'reject', 'mark_failed', 'add_note']); - -module.exports = async function handler(req, res) { - res.setHeader('Content-Type', 'application/json; charset=utf-8'); - res.setHeader('Cache-Control', 'no-store'); - - if (req.method !== 'POST') { - res.setHeader('Allow', 'POST'); - return res.status(405).json({ ok: false, status: 'METHOD_NOT_ALLOWED' }); - } - if (!requireAdminAuth(req, res)) return; - - const body = req.body || {}; - const claimId = typeof body.claimId === 'string' ? body.claimId.trim() : ''; - const action = typeof body.action === 'string' ? body.action.trim() : ''; - const actor = typeof body.actor === 'string' && body.actor.trim() ? body.actor.trim() : 'admin'; - const reason = typeof body.reason === 'string' ? body.reason.trim() : ''; - const notes = typeof body.notes === 'string' ? body.notes.trim() : ''; - const override = body.override === true; - - if (!claimId) return res.status(400).json({ ok: false, status: 'INVALID_CLAIM_ID' }); - if (!SUPPORTED_ACTIONS.has(action)) return res.status(400).json({ ok: false, status: 'INVALID_ACTION' }); - if ((action === 'reject' || action === 'mark_failed') && !reason) { - return res.status(400).json({ ok: false, status: 'REASON_REQUIRED' }); - } - if (action === 'add_note' && !notes) { - return res.status(400).json({ ok: false, status: 'NOTES_REQUIRED' }); - } - - let fromStatus = null; - - try { - const claimRows = db.normalizeRows(await db.query('select * from claim_requests where claim_id = $1 limit 1', [claimId])); - if (!claimRows.length) return res.status(404).json({ ok: false, status: 'CLAIM_NOT_FOUND' }); - const claim = claimRows[0]; - fromStatus = claim.status; - - if (action === 'approve') { - const allow = fromStatus === CLAIM_STATUSES.CREATED || (fromStatus === CLAIM_STATUSES.REJECTED && override); - if (!allow) { - return res.status(409).json({ ok: false, status: 'INVALID_STATUS_TRANSITION', error: `Cannot approve claim from ${fromStatus} status.`, fromStatus, action }); - } - await db.query( - `update claim_requests - set status = $2, - approved_at = now(), - reviewed_at = now(), - reviewed_by = $3, - admin_notes = case when $4 <> '' then $4 else admin_notes end - where claim_id = $1`, - [claimId, CLAIM_STATUSES.APPROVED, actor, notes] - ); - await insertEventAndTransition({ claimId, fromStatus, toStatus: CLAIM_STATUSES.APPROVED, action, actor, reason, notes, eventType: 'claim.approved', metadata: { override } }); - return res.status(200).json({ ok: true, status: 'CLAIM_ACTION_APPLIED', claimId, action, claimStatus: CLAIM_STATUSES.APPROVED }); - } - - if (action === 'reject') { - if (fromStatus !== CLAIM_STATUSES.CREATED) { - return res.status(409).json({ ok: false, status: 'INVALID_STATUS_TRANSITION', error: `Cannot reject claim from ${fromStatus} status.`, fromStatus, action }); - } - await db.query( - `update claim_requests - set status = $2, - rejected_at = now(), - reviewed_at = now(), - reviewed_by = $3, - rejection_reason = $4 - where claim_id = $1`, - [claimId, CLAIM_STATUSES.REJECTED, actor, reason] - ); - await insertEventAndTransition({ claimId, fromStatus, toStatus: CLAIM_STATUSES.REJECTED, action, actor, reason, notes, eventType: 'claim.rejected' }); - return res.status(200).json({ ok: true, status: 'CLAIM_ACTION_APPLIED', claimId, action, claimStatus: CLAIM_STATUSES.REJECTED }); - } - - if (action === 'mark_failed') { - if (fromStatus === CLAIM_STATUSES.LIVE) { - return res.status(409).json({ ok: false, status: 'INVALID_STATUS_TRANSITION', error: `Cannot mark_failed claim from ${fromStatus} status.`, fromStatus, action }); - } - await db.query( - `update claim_requests - set status = $2, - last_error = $3, - last_error_at = now() - where claim_id = $1`, - [claimId, CLAIM_STATUSES.FAILED, reason] - ); - await insertEventAndTransition({ claimId, fromStatus, toStatus: CLAIM_STATUSES.FAILED, action, actor, reason, notes, eventType: 'claim.failed', metadata: { previousStatus: fromStatus, actor } }); - return res.status(200).json({ ok: true, status: 'CLAIM_ACTION_APPLIED', claimId, action, claimStatus: CLAIM_STATUSES.FAILED }); - } - - const mergedNotes = [claim.admin_notes, notes].filter(Boolean).join('\n').trim(); - await db.query('update claim_requests set admin_notes = $2 where claim_id = $1', [claimId, mergedNotes || notes]); - await db.query( - `insert into claim_events (claim_id, event_type, actor, message, event_json) - values ($1, 'claim.note_added', $2, $3, $4::jsonb)`, - [claimId, actor, notes, JSON.stringify({ action, notes, actor })] - ); - return res.status(200).json({ ok: true, status: 'CLAIM_ACTION_APPLIED', claimId, action, claimStatus: fromStatus }); - } catch (error) { - const debug = { message: error.message, code: error.code }; - console.error('ADMIN_CLAIM_ACTION_FAILED', { ...debug, action, claimId, currentStatus: typeof fromStatus === 'string' ? fromStatus : null }); - const payload = { ok: false, status: 'ADMIN_CLAIM_ACTION_FAILED', error: 'Failed to apply claim action.' }; - if (process.env.NODE_ENV !== 'production') payload.debug = debug; - return res.status(500).json(payload); - } -}; - -async function insertEventAndTransition({ claimId, fromStatus, toStatus, action, actor, reason, notes, eventType, metadata }) { - const message = eventType === 'claim.failed' ? reason : notes || reason || null; - await db.query( - `insert into claim_events (claim_id, event_type, actor, message, event_json) - values ($1, $2, $3, $4, $5::jsonb)`, - [claimId, eventType, actor, message, JSON.stringify({ action, reason, notes, ...(metadata || {}) })] - ); - await db.query( - `insert into claim_status_transitions (claim_id, from_status, to_status, action, actor, reason, metadata_json) - values ($1, $2, $3, $4, $5, $6, $7::jsonb)`, - [claimId, fromStatus, toStatus, action, actor, reason || null, JSON.stringify({ notes, ...(metadata || {}) })] - ); -} diff --git a/api/admin/claim.js b/api/admin/claim.js deleted file mode 100644 index 2f682a3..0000000 --- a/api/admin/claim.js +++ /dev/null @@ -1,64 +0,0 @@ -'use strict'; - -const db = require('../../lib/db'); -const { requireAdminAuth } = require('./_auth'); - -module.exports = async function handler(req, res) { - res.setHeader('Content-Type', 'application/json; charset=utf-8'); - res.setHeader('Cache-Control', 'no-store'); - - if (req.method !== 'GET') { - res.setHeader('Allow', 'GET'); - return res.status(405).json({ ok: false, status: 'METHOD_NOT_ALLOWED' }); - } - - if (!requireAdminAuth(req, res)) { - return; - } - - const claimId = req.query && req.query.claimId; - if (!claimId || typeof claimId !== 'string') { - return res.status(400).json({ ok: false, status: 'INVALID_CLAIM_ID' }); - } - - try { - const claimRows = db.normalizeRows( - await db.query(`select cr.*, (select cp.metadata_json->>'checkoutUrl' from claim_payments cp where cp.claim_id = cr.claim_id order by cp.created_at desc limit 1) as stripe_checkout_url from claim_requests cr where cr.claim_id = $1 limit 1`, [claimId]) - ); - if (!claimRows.length) { - return res.status(404).json({ ok: false, status: 'CLAIM_NOT_FOUND' }); - } - - const agentRows = db.normalizeRows( - await db.query('select * from claim_agents where claim_id = $1 order by capability asc', [claimId]) - ); - const eventRows = db.normalizeRows( - await db.query('select * from claim_events where claim_id = $1 order by created_at asc', [claimId]) - ); - const transitionRows = db.normalizeRows( - await db.query('select * from claim_status_transitions where claim_id = $1 order by created_at asc', [claimId]) - ); - const cardRows = db.normalizeRows( - await db.query('select * from agent_cards where claim_id = $1 order by ens asc', [claimId]) - ); - - return res.status(200).json({ - ok: true, - claim: claimRows[0], - agents: agentRows, - events: eventRows, - transitions: transitionRows, - cards: cardRows - }); - } catch (error) { - console.error('ADMIN_CLAIM_QUERY_FAILED', { message: error.message, code: error.code }); - const payload = { ok: false, status: 'ADMIN_CLAIM_QUERY_FAILED', error: 'Failed to load claim.' }; - if (process.env.NODE_ENV !== 'production') { - payload.debug = { - message: error.message, - code: error.code - }; - } - return res.status(500).json(payload); - } -}; diff --git a/api/admin/claims.js b/api/admin/claims.js deleted file mode 100644 index b4a2b46..0000000 --- a/api/admin/claims.js +++ /dev/null @@ -1,74 +0,0 @@ -'use strict'; - -const db = require('../../lib/db'); -const { requireAdminAuth } = require('./_auth'); - -module.exports = async function handler(req, res) { - res.setHeader('Content-Type', 'application/json; charset=utf-8'); - res.setHeader('Cache-Control', 'no-store'); - - if (req.method !== 'GET') { - res.setHeader('Allow', 'GET'); - return res.status(405).json({ ok: false, status: 'METHOD_NOT_ALLOWED' }); - } - - if (!requireAdminAuth(req, res)) { - return; - } - - const requestedLimit = Number.parseInt(req.query && req.query.limit, 10); - const limit = Number.isFinite(requestedLimit) && requestedLimit > 0 - ? Math.min(requestedLimit, 200) - : 50; - - try { - const result = await db.query( - `select - cr.claim_id, - cr.tenant, - cr.authenticated_address, - cr.activation_mode, - cr.pack_id, - cr.status, - cr.created_at, - count(ca.id)::int as agent_count - from claim_requests cr - left join claim_agents ca on ca.claim_id = cr.claim_id - group by - cr.claim_id, - cr.tenant, - cr.authenticated_address, - cr.activation_mode, - cr.pack_id, - cr.status, - cr.created_at - order by cr.created_at desc - limit $1`, - [limit] - ); - - const rows = db.normalizeRows(result); - const claims = rows.map((row) => ({ - claimId: row.claim_id, - tenant: row.tenant, - authenticatedAddress: row.authenticated_address, - activationMode: row.activation_mode, - packId: row.pack_id, - status: row.status, - agentCount: Number(row.agent_count || 0), - createdAt: row.created_at - })); - - return res.status(200).json({ ok: true, claims }); - } catch (error) { - console.error('ADMIN_CLAIMS_QUERY_FAILED', { message: error.message, code: error.code }); - const payload = { ok: false, status: 'ADMIN_CLAIMS_QUERY_FAILED', error: 'Failed to load claims.' }; - if (process.env.NODE_ENV !== 'production') { - payload.debug = { - message: error.message, - code: error.code - }; - } - return res.status(500).json(payload); - } -}; diff --git a/api/admin/create-checkout-session.js b/api/admin/create-checkout-session.js deleted file mode 100644 index f4276b8..0000000 --- a/api/admin/create-checkout-session.js +++ /dev/null @@ -1,204 +0,0 @@ -'use strict'; - -const createStripeClient = require('../../lib/stripe-client'); -const db = require('../../lib/db'); -const { requireAdminAuth } = require('./_auth'); - -function asServiceUnavailable(res, status, error) { - return res.status(503).json({ ok: false, status, error }); -} - -function asConflict(res, status, error) { - return res.status(409).json({ ok: false, status, error }); -} - -function getSanitizedSiteUrl() { - const rawSiteUrl = process.env.COMMANDLAYER_SITE_URL; - const siteUrl = typeof rawSiteUrl === 'string' && rawSiteUrl.trim() - ? rawSiteUrl.trim() - : 'https://www.commandlayer.org'; - - if (siteUrl.includes(',')) { - const error = new Error('COMMANDLAYER_SITE_URL must be a valid https://www.commandlayer.org URL.'); - error.code = 'SITE_URL_INVALID'; - throw error; - } - - let parsed; - try { - parsed = new URL(siteUrl); - } catch (_error) { - const error = new Error('COMMANDLAYER_SITE_URL must be a valid https://www.commandlayer.org URL.'); - error.code = 'SITE_URL_INVALID'; - throw error; - } - - if (parsed.protocol !== 'https:' || !['commandlayer.org', 'www.commandlayer.org'].includes(parsed.hostname)) { - const error = new Error('COMMANDLAYER_SITE_URL must be a valid https://www.commandlayer.org URL.'); - error.code = 'SITE_URL_INVALID'; - throw error; - } - - return `${parsed.origin}${parsed.pathname}`.replace(/\/$/, ''); -} - -module.exports = async function handler(req, res) { - res.setHeader('Content-Type', 'application/json; charset=utf-8'); - res.setHeader('Cache-Control', 'no-store'); - - if (req.method !== 'POST') { - res.setHeader('Allow', 'POST'); - return res.status(405).json({ ok: false, status: 'METHOD_NOT_ALLOWED' }); - } - if (!requireAdminAuth(req, res)) return; - - const body = req.body || {}; - const claimId = typeof body.claimId === 'string' ? body.claimId.trim() : ''; - const forceNew = body.forceNew === true; - if (!claimId) return res.status(400).json({ ok: false, status: 'INVALID_CLAIM_ID' }); - - let stripe; - try { - stripe = createStripeClient(process.env.STRIPE_SECRET_KEY); - } catch (error) { - if (error?.code === 'STRIPE_NOT_CONFIGURED') { - return asServiceUnavailable(res, 'STRIPE_NOT_CONFIGURED', 'Stripe secret key is not configured.'); - } - if (error?.code === 'STRIPE_SECRET_KEY_INVALID') { - return asServiceUnavailable(res, 'STRIPE_SECRET_KEY_INVALID', error.message); - } - console.error('ADMIN_CREATE_CHECKOUT_STRIPE_INIT_FAILED', { message: error?.message, code: error?.code, claimId }); - return asServiceUnavailable(res, 'STRIPE_NOT_CONFIGURED', 'Stripe secret key is not configured.'); - } - - try { - const claims = db.normalizeRows(await db.query('select * from claim_requests where claim_id = $1 limit 1', [claimId])); - if (!claims.length) return res.status(404).json({ ok: false, status: 'CLAIM_NOT_FOUND', error: 'Claim not found.' }); - - const claim = claims[0]; - if (claim.status === 'paid' || claim.payment_status === 'paid') { - return asConflict(res, 'PAYMENT_ALREADY_COMPLETED', 'Payment is already completed for this claim.'); - } - - if (!['cards_published', 'payment_pending'].includes(claim.status)) { - return asConflict(res, 'CLAIM_NOT_READY_FOR_PAYMENT', 'Claim must be cards_published before creating checkout.'); - } - - if (claim.status === 'payment_pending' && claim.stripe_checkout_session_id && !forceNew) { - return res.status(200).json({ - ok: true, - status: 'CHECKOUT_SESSION_CREATED', - claimId, - checkoutUrl: claim.stripe_checkout_url || null, - sessionId: claim.stripe_checkout_session_id - }); - } - - let siteUrl; - try { - siteUrl = getSanitizedSiteUrl(); - } catch (error) { - if (error?.code === 'SITE_URL_INVALID') { - return asServiceUnavailable(res, 'SITE_URL_INVALID', 'COMMANDLAYER_SITE_URL must be a valid https://www.commandlayer.org URL.'); - } - throw error; - } - - const successUrl = `${siteUrl}/claim/status.html?claimId=${encodeURIComponent(claimId)}&payment=success`; - const cancelUrl = `${siteUrl}/claim/status.html?claimId=${encodeURIComponent(claimId)}&payment=cancelled`; - - let session; - try { - session = await stripe.checkout.sessions.create({ - mode: 'payment', - success_url: successUrl, - cancel_url: cancelUrl, - line_items: [{ - quantity: 1, - price_data: { - currency: 'usd', - unit_amount: 2000, - product_data: { - name: 'CommandLayer Founding Activation', - description: '10 Trust Verification agent namespaces' - } - } - }], - metadata: { - claimId, - tenant: claim.tenant || '', - packId: claim.pack_id || '', - product: 'founding_activation' - } - }); - } catch (error) { - console.error('ADMIN_CREATE_CHECKOUT_SESSION_FAILED', { message: error?.message, code: error?.code, claimId }); - const payload = { - ok: false, - status: 'CHECKOUT_SESSION_CREATE_FAILED', - error: 'Unable to create Stripe checkout session.' - }; - if (process.env.NODE_ENV !== 'production') { - payload.debug = { message: error?.message || 'Unknown Stripe error', code: error?.code || null }; - } - return res.status(502).json(payload); - } - - await db.query( - `insert into claim_payments (claim_id, provider, stripe_checkout_session_id, amount_cents, currency, status, metadata_json) - values ($1, 'stripe', $2, $3, 'usd', 'pending', $4::jsonb) - on conflict (claim_id, provider) - do update set stripe_checkout_session_id = excluded.stripe_checkout_session_id, - amount_cents = excluded.amount_cents, - currency = excluded.currency, - status = excluded.status, - metadata_json = excluded.metadata_json, - updated_at = now()`, - [claimId, session.id, 2000, JSON.stringify({ checkoutUrl: session.url || null })] - ); - - const fromStatus = claim.status; - await db.query( - `update claim_requests - set status = 'payment_pending', - payment_status = 'pending', - payment_amount_cents = $2, - payment_currency = 'usd', - stripe_checkout_session_id = $3 - where claim_id = $1`, - [claimId, 2000, session.id] - ); - - const eventType = forceNew && fromStatus === 'payment_pending' - ? 'payment.checkout_regenerated' - : 'payment.checkout_created'; - const eventMessage = forceNew && fromStatus === 'payment_pending' - ? 'Stripe checkout regenerated.' - : 'Stripe checkout created.'; - - await db.query( - `insert into claim_events (claim_id, event_type, actor, message, event_json) - values ($1, $2, 'system', $3, $4::jsonb)`, - [claimId, eventType, eventMessage, JSON.stringify({ sessionId: session.id, checkoutUrl: session.url || null })] - ); - - if (fromStatus === 'cards_published') { - await db.query( - `insert into claim_status_transitions (claim_id, from_status, to_status, action, actor, metadata_json) - values ($1, 'cards_published', 'payment_pending', 'create_checkout', 'system', $2::jsonb)`, - [claimId, JSON.stringify({ sessionId: session.id })] - ); - } - - return res.status(200).json({ - ok: true, - status: forceNew && fromStatus === 'payment_pending' ? 'CHECKOUT_SESSION_REGENERATED' : 'CHECKOUT_SESSION_CREATED', - claimId, - checkoutUrl: session.url || null, - sessionId: session.id - }); - } catch (error) { - console.error('ADMIN_CREATE_CHECKOUT_SESSION_UNEXPECTED', { message: error?.message, code: error?.code, claimId }); - return res.status(500).json({ ok: false, status: 'ADMIN_CREATE_CHECKOUT_SESSION_FAILED', error: 'Failed to create checkout session.' }); - } -}; diff --git a/api/admin/publish-agent-cards.js b/api/admin/publish-agent-cards.js deleted file mode 100644 index af69505..0000000 --- a/api/admin/publish-agent-cards.js +++ /dev/null @@ -1,174 +0,0 @@ -'use strict'; - -const db = require('../../lib/db'); -const { requireAdminAuth } = require('./_auth'); - -const BASE_URL = 'https://www.commandlayer.org'; -const CARD_VERSION = '1.1.0'; - -module.exports = async function handler(req, res) { - res.setHeader('Content-Type', 'application/json; charset=utf-8'); - res.setHeader('Cache-Control', 'no-store'); - - if (req.method !== 'POST') { - res.setHeader('Allow', 'POST'); - return res.status(405).json({ ok: false, status: 'METHOD_NOT_ALLOWED' }); - } - if (!requireAdminAuth(req, res)) return; - - const claimId = typeof req.body?.claimId === 'string' ? req.body.claimId.trim() : ''; - if (!claimId) return res.status(400).json({ ok: false, status: 'INVALID_CLAIM_ID' }); - - let transactionOpen = false; - try { - const claimRows = db.normalizeRows(await db.query('select * from claim_requests where claim_id = $1 limit 1', [claimId])); - if (!claimRows.length) return res.status(404).json({ ok: false, status: 'CLAIM_NOT_FOUND' }); - - const claim = claimRows[0]; - if (claim.status !== 'approved' && claim.status !== 'cards_published') { - return res.status(409).json({ ok: false, status: 'CLAIM_NOT_APPROVED', error: 'Claim must be approved before publishing cards.' }); - } - - const agents = db.normalizeRows(await db.query('select * from claim_agents where claim_id = $1 order by capability asc', [claimId])); - if (!agents.length) return res.status(409).json({ ok: false, status: 'AGENTS_NOT_FOUND' }); - - const existing = db.normalizeRows(await db.query('select * from agent_cards where claim_id = $1 order by ens asc', [claimId])); - const existingByEns = new Map(existing.map((row) => [String(row.ens || '').trim(), row])); - - if (claim.status === 'cards_published' && isComplete(agents, existingByEns)) { - return res.status(200).json({ ok: true, claimId, status: 'CARDS_ALREADY_PUBLISHED', cards: formatCards(existing) }); - } - - await db.query('begin'); - transactionOpen = true; - - const cards = []; - for (const agent of agents) { - const ens = String(agent.ens || '').trim(); - const capability = String(agent.capability || '').trim(); - const cardPath = `/agent-cards/agents/v${CARD_VERSION}/trust/${ens}.json`; - const cardUrl = `${BASE_URL}${cardPath}`; - const cardJson = buildCardJson(agent, ens, capability); - - await db.query( - `insert into agent_cards (claim_id, ens, card_url, card_json, version, status) - values ($1, $2, $3, $4::jsonb, $5, 'published') - on conflict (card_url) - do update set - claim_id = excluded.claim_id, - ens = excluded.ens, - card_json = excluded.card_json, - version = excluded.version, - status = 'published', - updated_at = now()`, - [claimId, ens, cardUrl, JSON.stringify(cardJson), CARD_VERSION] - ); - - await db.query( - `update claim_agents - set card_url = $3, - card_status = 'published', - card_published_at = coalesce(card_published_at, now()) - where claim_id = $1 and id = $2`, - [claimId, agent.id, cardUrl] - ); - cards.push({ ens, cardUrl }); - } - - const alreadyPublishedEvent = db.normalizeRows( - await db.query("select id from claim_events where claim_id = $1 and event_type = 'agent_cards.published' limit 1", [claimId]) - ); - if (!alreadyPublishedEvent.length) { - await db.query( - `insert into claim_events (claim_id, event_type, actor, message, event_json) - values ($1, 'agent_cards.published', 'admin', 'Agent cards published', $2::jsonb)`, - [claimId, JSON.stringify({ count: cards.length, cards })] - ); - } - - const publishedTransition = db.normalizeRows( - await db.query( - `select id from claim_status_transitions - where claim_id = $1 and from_status = 'approved' and to_status = 'cards_published' and action = 'publish_agent_cards' - limit 1`, - [claimId] - ) - ); - if (!publishedTransition.length) { - await db.query( - `insert into claim_status_transitions (claim_id, from_status, to_status, action, actor, reason, metadata_json) - values ($1, 'approved', 'cards_published', 'publish_agent_cards', 'admin', null, $2::jsonb)`, - [claimId, JSON.stringify({ cardCount: cards.length })] - ); - } - - if (claim.status === 'approved') { - await db.query("update claim_requests set status = 'cards_published' where claim_id = $1", [claimId]); - } - - await db.query('commit'); - transactionOpen = false; - - return res.status(200).json({ - ok: true, - claimId, - status: claim.status === 'approved' ? 'CARDS_PUBLISHED' : 'AGENT_CARDS_REPAIRED', - cards - }); - } catch (error) { - if (transactionOpen) { - try { await db.query('rollback'); } catch (_) {} - } - - const statusCode = error && error.statusCode ? error.statusCode : 500; - const status = error && error.status ? error.status : (statusCode === 500 ? 'ADMIN_PUBLISH_AGENT_CARDS_FAILED' : 'AGENT_CARD_PUBLISH_FAILED'); - const message = error && error.error ? error.error : 'Failed to publish agent cards.'; - - console.error('ADMIN_PUBLISH_AGENT_CARDS_FAILED', { message: error.message, code: error.code, claimId }); - - const payload = { ok: false, status, error: message }; - if (process.env.NODE_ENV !== 'production') payload.debug = { message: error.message, code: error.code }; - return res.status(statusCode).json(payload); - } -}; - -function isComplete(agents, existingByEns) { - for (const agent of agents) { - const ens = String(agent.ens || '').trim(); - const row = existingByEns.get(ens); - if (!row) return false; - if (!agent.card_url || !agent.card_status) return false; - } - return true; -} - -function formatCards(rows) { - return rows.map((row) => ({ ens: row.ens, cardUrl: row.card_url })); -} - -function buildCardJson(agent, ens, capability) { - return { - type: 'erc8004/registration/v1', - name: ens, - description: `CommandLayer Trust Verification agent for ${capability}.`, - image: 'https://www.commandlayer.org/favicon.ico', - services: [ - { type: 'ens', endpoint: ens }, - { type: 'commandlayer_runtime', endpoint: 'https://runtime.commandlayer.org' }, - { type: 'commandlayer_verifier', endpoint: 'https://runtime.commandlayer.org/verify' } - ], - commandlayer: { - version: CARD_VERSION, - tenant: agent.tenant, - capability, - canonicalParent: agent.canonical_parent, - skill: agent.skill, - skillFamily: agent.skill_family, - kid: agent.kid, - publicKey: agent.public_key, - runtime: 'https://runtime.commandlayer.org', - verifier: 'https://runtime.commandlayer.org/verify' - }, - registrations: [] - }; -} diff --git a/api/claim/commandlayer-namespace.js b/api/claim/commandlayer-namespace.js index d5b8fef..4cffe1d 100644 --- a/api/claim/commandlayer-namespace.js +++ b/api/claim/commandlayer-namespace.js @@ -3,21 +3,6 @@ const crypto = require('node:crypto'); const db = require('../../lib/db'); -const TRUST_VERIFICATION_MAP = { - sign: 'signagent.eth', - attest: 'attestagent.eth', - authorize: 'authorizeagent.eth', - approve: 'approveagent.eth', - reject: 'rejectagent.eth', - permit: 'permitagent.eth', - grant: 'grantagent.eth', - authenticate: 'authenticateagent.eth', - endorse: 'endorseagent.eth', - verify: 'verifyagent.eth' -}; - -const RUNTIME_URL = 'https://runtime.commandlayer.org'; -const VERIFIER_URL = 'https://runtime.commandlayer.org/verify'; const ADDRESS_RE = /^0x[a-fA-F0-9]{40}$/; const TENANT_RE = /^[a-z0-9-]{3,32}$/; @@ -25,123 +10,40 @@ function invalid(res, error, reason) { return res.status(400).json({ ok: false, status: 'CLAIM_REQUEST_INVALID', error, reason }); } -function storageUnavailable(res) { - return res.status(503).json({ - ok: false, - status: 'STORAGE_UNAVAILABLE', - error: 'DATABASE_URL is not configured' - }); -} - module.exports = async function handler(req, res) { res.setHeader('Content-Type', 'application/json; charset=utf-8'); res.setHeader('Cache-Control', 'no-store'); if (req.method !== 'POST') { res.setHeader('Allow', 'POST'); - return res.status(405).json({ ok: false, status: 'CLAIM_REQUEST_INVALID', error: 'method_not_allowed', reason: 'Method not allowed. Use POST.' }); + return res.status(405).json({ ok: false, status: 'METHOD_NOT_ALLOWED' }); } - const body = req.body; - if (!body || typeof body !== 'object' || Array.isArray(body)) { - return invalid(res, 'invalid_body', 'Missing or invalid JSON body.'); - } - - const { authenticatedAddress, tenant, activationMode, packId, capabilities, agents, publicKey, kid, verifier, runtime, schemaVersion } = body; + // TODO: add request rate limiting middleware for public intake. + const { authenticatedAddress, tenant, activationMode, packId } = req.body || {}; if (!authenticatedAddress || !ADDRESS_RE.test(authenticatedAddress)) return invalid(res, 'invalid_authenticated_address', 'authenticatedAddress must be a valid 0x Ethereum address.'); if (activationMode !== 'cl') return invalid(res, 'invalid_activation_mode', 'activationMode must be "cl".'); - if (typeof tenant !== 'string' || !tenant.trim()) return invalid(res, 'invalid_tenant', 'tenant is required.'); - if (tenant.includes('.eth')) return invalid(res, 'invalid_tenant', 'tenant must not include ".eth".'); - if (!TENANT_RE.test(tenant) || tenant.startsWith('-') || tenant.endsWith('-')) { - return invalid(res, 'invalid_tenant', 'tenant must be 3-32 chars of lowercase letters, numbers, hyphen, and cannot start or end with hyphen.'); - } - - if (packId !== 'trust') { - return invalid(res, 'unsupported_pack', 'Only Trust Verification activation requests are supported in this first backend flow.'); - } - if (!Array.isArray(capabilities) || capabilities.length === 0 || capabilities.length > 10) return invalid(res, 'invalid_capabilities', 'capabilities must contain between 1 and 10 items.'); - if (typeof publicKey !== 'string' || !publicKey.startsWith('ed25519:')) return invalid(res, 'invalid_public_key', 'publicKey must start with "ed25519:".'); - if (typeof kid !== 'string' || !kid.trim()) return invalid(res, 'invalid_kid', 'kid is required.'); - if (!Array.isArray(agents) || agents.length === 0) return invalid(res, 'invalid_agents', 'agents is required.'); - if (runtime !== RUNTIME_URL) return invalid(res, 'invalid_runtime', `runtime must be ${RUNTIME_URL}.`); - if (verifier !== VERIFIER_URL) return invalid(res, 'invalid_verifier', `verifier must be ${VERIFIER_URL}.`); - - for (const capability of capabilities) { - const canonicalParent = TRUST_VERIFICATION_MAP[capability]; - if (!canonicalParent) return invalid(res, 'invalid_capability', `Unsupported capability "${capability}" for Trust Verification pack.`); - } - - const capabilitySet = new Set(capabilities); - for (const agent of agents) { - if (!agent || typeof agent !== 'object') return invalid(res, 'invalid_agents', 'Each agent must be an object.'); - const { ens, capability, canonicalParent, skill, skillFamily } = agent; - if (!TRUST_VERIFICATION_MAP[capability]) return invalid(res, 'invalid_agent_capability', `Unsupported agent capability "${capability}".`); - if (!capabilitySet.has(capability)) return invalid(res, 'invalid_agent_capability', `Agent capability "${capability}" must be present in capabilities.`); - if (TRUST_VERIFICATION_MAP[capability] !== canonicalParent) return invalid(res, 'invalid_agent_mapping', `Capability "${capability}" must map to canonical parent "${TRUST_VERIFICATION_MAP[capability]}".`); - if (!Object.values(TRUST_VERIFICATION_MAP).includes(canonicalParent)) return invalid(res, 'invalid_canonical_parent', `Unsupported canonical parent "${canonicalParent}".`); - if (ens !== `${tenant}.${canonicalParent}`) return invalid(res, 'invalid_agent_ens', `Agent ENS must equal "${tenant}.${canonicalParent}".`); - if (skill !== `trust-verification.${capability}`) return invalid(res, 'invalid_skill', `skill must equal "trust-verification.${capability}".`); - if (skillFamily !== 'trust-verification') return invalid(res, 'invalid_skill_family', 'skillFamily must equal "trust-verification".'); - } - - if (!process.env.DATABASE_URL) { - return storageUnavailable(res); - } + if (typeof tenant !== 'string' || !TENANT_RE.test(tenant) || tenant.startsWith('-') || tenant.endsWith('-') || tenant.includes('.eth')) return invalid(res, 'invalid_tenant', 'tenant must be 3-32 lowercase alphanumeric/hyphen chars.'); + if (packId !== 'trust') return invalid(res, 'unsupported_pack', 'Only trust pack intake is currently accepted.'); + if (!process.env.DATABASE_URL) return res.status(503).json({ ok: false, status: 'STORAGE_UNAVAILABLE' }); const claimId = `clm_${crypto.randomUUID().replace(/-/g, '')}`; try { - await db.query('BEGIN'); await db.query( `insert into claim_requests (claim_id, authenticated_address, tenant, activation_mode, pack_id, public_key, kid, runtime, verifier, schema_version, request_json) - values ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11::jsonb)`, - [claimId, authenticatedAddress, tenant, activationMode, packId, publicKey, kid, runtime, verifier, schemaVersion || '1.1.0', JSON.stringify(body)] + values ($1,$2,$3,$4,$5,'','', '', '', '1.1.0', $6::jsonb)`, + [claimId, authenticatedAddress, tenant, activationMode, packId, JSON.stringify({ authenticatedAddress, tenant, activationMode, packId })] ); - - for (const agent of agents) { - await db.query( - `insert into claim_agents - (claim_id, ens, capability, canonical_parent, skill, skill_family) - values ($1,$2,$3,$4,$5,$6)`, - [claimId, agent.ens, agent.capability, agent.canonicalParent, agent.skill || '', agent.skillFamily || ''] - ); - } - await db.query( `insert into claim_events (claim_id, event_type, message, metadata_json) values ($1,$2,$3,$4::jsonb)`, - [ - claimId, - 'claim.created', - 'CommandLayer namespace claim request created.', - JSON.stringify({ agentCount: agents.length, packId, activationMode }) - ] + [claimId, 'claim.created', 'Public claim request received.', JSON.stringify({ publicIntake: true })] ); - await db.query('COMMIT'); - } catch (error) { - await db.query('ROLLBACK'); - if (error && error.code === 'DATABASE_URL_MISSING') { - return storageUnavailable(res); - } - return res.status(500).json({ ok: false, status: 'CLAIM_REQUEST_PERSISTENCE_ERROR', error: 'Failed to persist claim request.' }); + } catch (_error) { + return res.status(500).json({ ok: false, status: 'CLAIM_REQUEST_PERSISTENCE_ERROR' }); } - return res.status(200).json({ - ok: true, - status: 'CLAIM_REQUEST_CREATED', - claimId, - activationMode: 'cl', - tenant, - authenticatedAddress, - agents, - lifecycle: { - claim: 'created', - operatorReview: 'not_started', - ensProvisioning: 'not_started', - agentCards: 'not_started', - erc8004: 'not_started', - liveReceiptTest: 'not_started' - } - }); + return res.status(202).json({ ok: true, status: 'CLAIM_REQUEST_RECEIVED', claimId, message: 'Claim request received.' }); }; diff --git a/api/stripe/webhook.js b/api/stripe/webhook.js deleted file mode 100644 index 5a414b7..0000000 --- a/api/stripe/webhook.js +++ /dev/null @@ -1,66 +0,0 @@ -'use strict'; - -const Stripe = require('../../lib/stripe-client'); -const db = require('../../lib/db'); - -module.exports = async function handler(req, res) { - res.setHeader('Content-Type', 'application/json; charset=utf-8'); - res.setHeader('Cache-Control', 'no-store'); - - if (req.method !== 'POST') { - res.setHeader('Allow', 'POST'); - return res.status(405).json({ ok: false, status: 'METHOD_NOT_ALLOWED' }); - } - - if (!process.env.STRIPE_SECRET_KEY || !process.env.STRIPE_WEBHOOK_SECRET) { - return res.status(503).json({ ok: false, status: 'STRIPE_NOT_CONFIGURED' }); - } - - const stripe = new Stripe(process.env.STRIPE_SECRET_KEY); - const sig = req.headers['stripe-signature'] || req.headers['Stripe-Signature']; - const rawBody = req.rawBody || req.body; - - let event; - try { - event = stripe.webhooks.constructEvent(rawBody, sig, process.env.STRIPE_WEBHOOK_SECRET); - } catch (error) { - return res.status(400).json({ ok: false, status: 'WEBHOOK_SIGNATURE_INVALID' }); - } - - if (event.type !== 'checkout.session.completed') { - return res.status(200).json({ ok: true, status: 'WEBHOOK_EVENT_UNHANDLED' }); - } - - const session = event.data && event.data.object ? event.data.object : {}; - const claimId = session.metadata && session.metadata.claimId; - if (!claimId) return res.status(404).json({ ok: false, status: 'CLAIM_NOT_FOUND' }); - - const claims = db.normalizeRows(await db.query('select * from claim_requests where claim_id = $1 limit 1', [claimId])); - if (!claims.length) return res.status(404).json({ ok: false, status: 'CLAIM_NOT_FOUND' }); - if (claims[0].status === 'paid' || claims[0].payment_status === 'paid') return res.status(200).json({ ok: true }); - - await db.query( - `update claim_payments - set status = 'paid', stripe_payment_intent_id = $2, updated_at = now() - where stripe_checkout_session_id = $1 or claim_id = $3`, - [session.id || null, session.payment_intent || null, claimId] - ); - await db.query( - `update claim_requests - set status = 'paid', payment_status = 'paid', stripe_payment_intent_id = $2, paid_at = now() - where claim_id = $1`, - [claimId, session.payment_intent || null] - ); - await db.query( - `insert into claim_events (claim_id, event_type, actor, message, event_json) - values ($1, 'payment.completed', 'system', 'Stripe payment completed.', $2::jsonb)`, - [claimId, JSON.stringify({ sessionId: session.id || null, paymentIntentId: session.payment_intent || null })] - ); - await db.query( - `insert into claim_status_transitions (claim_id, from_status, to_status, action, actor, metadata_json) - values ($1, 'payment_pending', 'paid', 'payment_webhook', 'system', $2::jsonb)`, - [claimId, JSON.stringify({ sessionId: session.id || null })] - ); - - return res.status(200).json({ ok: true }); -}; diff --git a/public/admin/claims.html b/public/admin/claims.html deleted file mode 100644 index 5721298..0000000 --- a/public/admin/claims.html +++ /dev/null @@ -1,82 +0,0 @@ - - -
- - -Internal operator dashboard for claim review and activation pipeline.
-Select a claim to review.