-
Notifications
You must be signed in to change notification settings - Fork 1
Align managed-namespace tenant signer with generated VerbAgent ENS #387
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -40,10 +40,17 @@ function safeAgent(agent) { | |
| canonicalParent: String(agent.canonicalParent || agent.canonical_parent || '').toLowerCase().trim(), | ||
| skill: String(agent.skill || '').trim(), | ||
| skillFamily: String(agent.skillFamily || agent.skill_family || '').trim(), | ||
| records: agent.records && typeof agent.records === 'object' && !Array.isArray(agent.records) ? JSON.parse(JSON.stringify(agent.records)) : undefined, | ||
| cardJson: sanitizedCard, | ||
| }; | ||
| } | ||
|
|
||
| function recordsFromAgent(agent) { | ||
| const card = agent && agent.cardJson && typeof agent.cardJson === 'object' ? agent.cardJson : {}; | ||
| const records = agent && agent.records && typeof agent.records === 'object' ? agent.records : card.records; | ||
| return records && typeof records === 'object' && !Array.isArray(records) ? records : {}; | ||
| } | ||
|
|
||
| module.exports = async function handler(req, res) { | ||
| res.setHeader('Content-Type', 'application/json; charset=utf-8'); | ||
| res.setHeader('Cache-Control', 'no-store'); | ||
|
|
@@ -76,6 +83,19 @@ module.exports = async function handler(req, res) { | |
| if (tenantSignerCanonicalization !== CANONICALIZATION) return invalid(res, 'invalid_canonicalization', `canonicalization must be ${CANONICALIZATION}.`); | ||
| if (!agents.length || agents.length > 10) return invalid(res, 'invalid_agents', 'At least one and at most ten generated agents are required.'); | ||
| if (agents.some((a) => !ENS_RE.test(a.ens) || !a.capability || !a.canonicalParent || !a.skill || !a.skillFamily)) return invalid(res, 'invalid_agent', 'Every agent requires ens, capability, canonicalParent, skill, and skillFamily.'); | ||
| if (activationMode === 'managed_namespace') { | ||
| const primaryManagedSignerEns = agents[0].ens; | ||
| if (tenantSignerEns !== primaryManagedSignerEns) { | ||
| return invalid(res, 'managed_signer_mismatch', 'Managed namespace tenantSignerEns must match the first generated managed agent ENS.'); | ||
|
Comment on lines
+88
to
+89
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This only checks that Useful? React with 👍 / 👎. |
||
| } | ||
| const mismatchedAgent = agents.find((agent) => { | ||
| const recordSigner = recordsFromAgent(agent)['cl.receipt.signer']; | ||
| return recordSigner && String(recordSigner).toLowerCase().trim() !== agent.ens; | ||
| }); | ||
| if (mismatchedAgent) { | ||
| return invalid(res, 'managed_agent_signer_mismatch', 'Managed namespace agent TXT records must use the agent ENS as cl.receipt.signer.'); | ||
| } | ||
| } | ||
| if (!process.env.DATABASE_URL) return res.status(503).json({ ok: false, status: 'STORAGE_UNAVAILABLE' }); | ||
|
|
||
| const claimId = `clm_${crypto.randomUUID().replace(/-/g, '')}`; | ||
|
|
@@ -91,6 +111,7 @@ module.exports = async function handler(req, res) { | |
| activationMode, | ||
| packId, | ||
| tenantSignerEns, | ||
| primaryTenantSignerEns: activationMode === 'managed_namespace' ? agents[0].ens : tenantSignerEns, | ||
| tenantSignerPublicKey, | ||
| tenantSignerKid, | ||
| tenantSignerCanonicalization, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -672,8 +672,11 @@ <h3>Activation pipeline</h3> | |
| } | ||
|
|
||
| function getTenantSignerEns() { | ||
| const names = agentNames(); | ||
| return (names[0] || state.ens || '').toLowerCase(); | ||
| if (state.activationMode === 'cl') { | ||
| const names = agentNames(); | ||
| return (names[0] || state.ens || '').toLowerCase(); | ||
|
Comment on lines
+676
to
+677
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When a managed namespace is built by selecting/removing canonical parents directly, Useful? React with 👍 / 👎. |
||
| } | ||
| return (state.ens || '').toLowerCase(); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When activation mode is Useful? React with 👍 / 👎. |
||
| } | ||
|
|
||
| function signerTxtRecordsObject() { | ||
|
|
@@ -1422,6 +1425,7 @@ <h3>Activation pipeline</h3> | |
| key_model: 'single_ed25519_key_for_package', | ||
| kid: state.kid, | ||
| tenant_signer_ens: getTenantSignerEns(), | ||
| primary_tenant_signer_ens: getTenantSignerEns(), | ||
| public_key: `ed25519:${state.pubKeyB64}`, | ||
| tenant_signer_public_key: `ed25519:${state.pubKeyB64}`, | ||
| tenant_signer_kid: state.kid, | ||
|
|
@@ -1469,8 +1473,9 @@ <h3>Activation pipeline</h3> | |
| first_action_receipt: 'not_generated', | ||
| agent_live: 'not_live' | ||
| }; | ||
| const claim = data && data.claim; | ||
| const rows = [ | ||
| ['1. Tenant signing identity generated locally', pipeline.tenant_signing_identity], | ||
| [`1. Signer ENS: ${claim && claim.tenant_signer_ens ? claim.tenant_signer_ens : getTenantSignerEns()}`, pipeline.tenant_signing_identity], | ||
| ['2. Claim request created', pipeline.claim_request], | ||
| ['3. Payment confirmed', pipeline.payment], | ||
| ['4. ENS signer records verified', pipeline.ens_records], | ||
|
|
@@ -1485,7 +1490,6 @@ <h3>Activation pipeline</h3> | |
| const icon = cls === 'complete' ? '✅' : (cls === 'active' ? '⏳' : '○'); | ||
| return `<div class="check-row ${cls === 'complete' ? 'done' : 'pending'}"><span class="check-icon">${icon}</span><div class="check-text"><strong>${label}</strong><span>${status || 'pending'}</span></div></div>`; | ||
| }).join(''); | ||
| const claim = data && data.claim; | ||
| const firstAction = claim && (claim.first_action_receipt_status || 'not_generated'); | ||
| const receiptNote = data && data.claim ? `<div class="check-row pending"><span class="check-icon">ℹ️</span><div class="check-text"><strong>First Action Receipt</strong><span>This proves the claimed agent can produce a scoped execution receipt. It does not prove payment settlement. Status: ${firstAction}</span></div></div>` : ''; | ||
| if (receiptNote) el.insertAdjacentHTML('beforeend', receiptNote); | ||
|
|
@@ -1494,7 +1498,7 @@ <h3>Activation pipeline</h3> | |
| intro.textContent = claim.tenant_signer_record_status === 'records_verified' | ||
| ? 'ENS signer identity verified on Ethereum mainnet' | ||
| : (claim.activation_mode === 'managed_namespace' | ||
| ? 'Managed ENS publication automation is pending activation infrastructure.' | ||
| ? 'Managed namespace mode signs receipts from the generated VerbAgent ENS, not the tenant root ENS.' | ||
| : 'Publish the four records with your ENS owner wallet, then verify records.'); | ||
| } | ||
| const box = document.getElementById('verifyRecordsBox'); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When a managed API client includes an
agent.recordsobject that omitscl.receipt.signer, this line stops looking atcardJson.records; the mismatched signer incardJsonis then persisted intoclaim_agentsand can be pinned byapi/admin/pin-agent-cards.js. For example,records: {}pluscardJson.records['cl.receipt.signer'] = 'acme.eth'returns 202 instead ofmanaged_agent_signer_mismatch, so the new validation can be bypassed for the card records that downstream publication uses.Useful? React with 👍 / 👎.