What the badge does
- Receives or fetches a receipt payload.
- Sends the receipt to the verifier.
- Displays
VALID,INVALID, orTRANSPORT_ERROR. - Links users to full verifier views for deeper inspection.
diff --git a/public/js/verify-badge.js b/public/js/verify-badge.js index d01e726..4a76448 100644 --- a/public/js/verify-badge.js +++ b/public/js/verify-badge.js @@ -1,7 +1,7 @@ 'use strict'; (function initVerifyBadge() { - const BADGE_CLASS = 'cl-verify-badge'; + const BADGE_CLASSES = ['commandlayer-verify-badge', 'cl-verify-badge']; const STYLE_ID = 'cl-verify-badge-style'; function injectStyles() { @@ -9,19 +9,19 @@ const style = document.createElement('style'); style.id = STYLE_ID; style.textContent = ` -.${BADGE_CLASS}{font-family:Inter,ui-sans-serif,system-ui,-apple-system,Segoe UI,Roboto,Arial,sans-serif;color:#24324a;background:#fff;border:1px solid #d6dee9;border-radius:10px;max-width:320px;padding:10px 12px;font-size:12px;line-height:1.35;box-shadow:0 1px 2px rgba(0,0,0,.05)} -.${BADGE_CLASS} *{box-sizing:border-box} -.${BADGE_CLASS} .clvb-head{display:flex;justify-content:space-between;align-items:center;gap:8px;margin-bottom:8px} -.${BADGE_CLASS} .clvb-title{font-size:11px;letter-spacing:.04em;text-transform:uppercase;color:#5a6a83;font-weight:600} -.${BADGE_CLASS} .clvb-status{font-weight:700;font-size:11px;padding:2px 7px;border-radius:999px;border:1px solid #d6dee9;color:#1b2940;background:#f7f9fc} -.${BADGE_CLASS}[data-cl-state="VERIFIED"] .clvb-status{background:#edf9f1;color:#17673e;border-color:#bbe3ca} -.${BADGE_CLASS}[data-cl-state="INVALID"] .clvb-status,.${BADGE_CLASS}[data-cl-state="ERROR"] .clvb-status{background:#fff4f4;color:#8b1f1f;border-color:#f0c8c8} -.${BADGE_CLASS} .clvb-row{display:flex;justify-content:space-between;gap:8px;margin:4px 0} -.${BADGE_CLASS} .clvb-k{color:#6d7b92} -.${BADGE_CLASS} .clvb-v{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;text-align:right;word-break:break-all} -.${BADGE_CLASS} .clvb-foot{margin-top:8px;padding-top:8px;border-top:1px solid #e7ecf3} -.${BADGE_CLASS} .clvb-link{font-size:12px;color:#2752d8;text-decoration:underline;text-underline-offset:2px} -.${BADGE_CLASS} .clvb-note{display:block;margin-top:6px;color:#6b7890;font-size:10px;line-height:1.4} +.commandlayer-verify-badge,.cl-verify-badge{font-family:Inter,ui-sans-serif,system-ui,-apple-system,Segoe UI,Roboto,Arial,sans-serif;color:#24324a;background:#fff;border:1px solid #d6dee9;border-radius:10px;max-width:320px;padding:10px 12px;font-size:12px;line-height:1.35;box-shadow:0 1px 2px rgba(0,0,0,.05)} +.commandlayer-verify-badge *,.cl-verify-badge *{box-sizing:border-box} +.commandlayer-verify-badge .clvb-head,.cl-verify-badge .clvb-head{display:flex;justify-content:space-between;align-items:center;gap:8px;margin-bottom:8px} +.commandlayer-verify-badge .clvb-title,.cl-verify-badge .clvb-title{font-size:11px;letter-spacing:.04em;text-transform:uppercase;color:#5a6a83;font-weight:600} +.commandlayer-verify-badge .clvb-status,.cl-verify-badge .clvb-status{font-weight:700;font-size:11px;padding:2px 7px;border-radius:999px;border:1px solid #d6dee9;color:#1b2940;background:#f7f9fc} +.commandlayer-verify-badge[data-cl-state="VERIFIED"] .clvb-status,.cl-verify-badge[data-cl-state="VERIFIED"] .clvb-status{background:#edf9f1;color:#17673e;border-color:#bbe3ca} +.commandlayer-verify-badge[data-cl-state="INVALID"] .clvb-status,.cl-verify-badge[data-cl-state="INVALID"] .clvb-status,.commandlayer-verify-badge[data-cl-state="TRANSPORT_ERROR"] .clvb-status,.cl-verify-badge[data-cl-state="TRANSPORT_ERROR"] .clvb-status{background:#fff4f4;color:#8b1f1f;border-color:#f0c8c8} +.commandlayer-verify-badge .clvb-row,.cl-verify-badge .clvb-row{display:flex;justify-content:space-between;gap:8px;margin:4px 0} +.commandlayer-verify-badge .clvb-k,.cl-verify-badge .clvb-k{color:#6d7b92} +.commandlayer-verify-badge .clvb-v,.cl-verify-badge .clvb-v{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;text-align:right;word-break:break-all} +.commandlayer-verify-badge .clvb-foot,.cl-verify-badge .clvb-foot{margin-top:8px;padding-top:8px;border-top:1px solid #e7ecf3} +.commandlayer-verify-badge .clvb-link,.cl-verify-badge .clvb-link{font-size:12px;color:#2752d8;text-decoration:underline;text-underline-offset:2px} +.commandlayer-verify-badge .clvb-note,.cl-verify-badge .clvb-note{display:block;margin-top:6px;color:#6b7890;font-size:10px;line-height:1.4} `; document.head.appendChild(style); } @@ -72,7 +72,7 @@ async function verifyBadgeElement(el) { const receiptUrl = el.getAttribute('data-cl-receipt-url'); if (!receiptUrl) { - setState(el, 'ERROR', [['error', 'Missing data-cl-receipt-url']], null); + setState(el, 'TRANSPORT_ERROR', [['error', 'Missing data-cl-receipt-url']], null); return; } @@ -95,23 +95,27 @@ const raw = await verifyResp.json(); const result = raw && typeof raw === 'object' && raw.receipt && raw.status == null ? raw.receipt : raw; - const status = verifyResp.ok ? (result.status || 'ERROR') : 'ERROR'; + const normalizedStatus = String(result.status || '').toUpperCase(); + const isValid = verifyResp.ok && (normalizedStatus === 'VALID' || normalizedStatus === 'VERIFIED' || result.ok === true); + const isInvalid = verifyResp.ok && !isValid; + const status = isValid ? 'VERIFIED' : (isInvalid ? 'INVALID' : 'TRANSPORT_ERROR'); const rows = [ ['signer', result.signer], ['verb', result.trust_verb || result.verb], ['schema_valid', result.schema_valid], - ['hash_matched', result.hash_matched ?? result.hash_matches], + ['hash_matches', result.hash_matches ?? result.hash_matched], ['signature_valid', result.signature_valid], ]; setState(el, status, rows, viewLink); } catch (error) { - setState(el, 'ERROR', [['error', error && error.message ? error.message : 'Unexpected error']], viewLink); + setState(el, 'TRANSPORT_ERROR', [['error', error && error.message ? error.message : 'Unexpected error']], viewLink); } } function boot() { injectStyles(); - const badges = document.querySelectorAll(`.${BADGE_CLASS}`); + const selector = BADGE_CLASSES.map((className) => `.${className}`).join(','); + const badges = document.querySelectorAll(selector); badges.forEach((el) => { verifyBadgeElement(el); }); diff --git a/public/verify-badge-demo.html b/public/verify-badge-demo.html index 903c272..ec85b4a 100644 --- a/public/verify-badge-demo.html +++ b/public/verify-badge-demo.html @@ -1,6 +1,225 @@ -
Embedded UI
A verification badge is a UI surface that displays verifier results. It does not sign receipts and does not replace the verifier.
VALID, INVALID, or TRANSPORT_ERROR.metadata.proofjson.sorted_keys.v1 canonicalizationsigner_id and kidVALID for demo-valid-receipt.json
INVALID for demo-tampered-receipt.json
TRANSPORT_ERROR when verifier transport fails.
Embedded Verification
+CommandLayer badges let apps display verifier results where users make decisions — verified, invalid, or unavailable.
+ +Hash and Ed25519 signature checks passed.
+status: VALIDhash_matches: truesignature_valid: true
Receipt payload changed or proof failed.
+status: INVALIDhash_matches: falsesignature_valid: false
Verifier/runtime unavailable or request failed.
+status: TRANSPORT_ERROR
metadata.proof presentjson.sorted_keys.v1 canonicalizationsignature.kid (vC4WbcNoq2znSCiQ)signer_id (runtime.commandlayer.eth)The canonical proof model is:
+metadata.proof.canonicalization = json.sorted_keys.v1metadata.proof.hash.alg = SHA-256metadata.proof.hash.valuemetadata.proof.signature.alg = Ed25519metadata.proof.signature.kid = vC4WbcNoq2znSCiQmetadata.proof.signature.valuemetadata.proof.signer_id = runtime.commandlayer.eth
Runtime endpoint: https://runtime.commandlayer.org.
Manual verification: Paste and inspect a receipt at /verify.html.
+Production proof: See runtime sign, verify, and tamper invalidation at /stack-proof-demo.html.
+Automatic verification: Run the webhook auto-verify demo at /webhook-auto-verify.html.
+Embedded verification: Use this badge pattern to display the result wherever the receipt is used.
+<div
+ class="commandlayer-verify-badge"
+ data-receipt-url="/receipts/demo-valid-receipt.json">
+</div>
+<script src="/js/verify-badge.js"></script>
+ The script fetches the receipt, calls the verifier, and renders the badge state.
+Valid sample: /receipts/demo-valid-receipt.json
+Tampered sample: /receipts/demo-tampered-receipt.json
+