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 @@ -Verify Badge Demo | CommandLayer - -

Embedded UI

Verification badges for receipt-aware interfaces.

A verification badge is a UI surface that displays verifier results. It does not sign receipts and does not replace the verifier.

-

What the badge does

  • Receives or fetches a receipt payload.
  • Sends the receipt to the verifier.
  • Displays VALID, INVALID, or TRANSPORT_ERROR.
  • Links users to full verifier views for deeper inspection.

What it checks indirectly via verifier

  • metadata.proof
  • json.sorted_keys.v1 canonicalization
  • SHA-256 hash match
  • Ed25519 signature validity
  • signer_id and kid
  • Supported verb assertions
  • Tamper invalidation

What the badge does not do

  • Does not sign receipts.
  • Does not hold private keys.
  • Does not treat schema-valid as verified.
  • Does not replace webhook sender authentication.
Open VerifierAutomatic Verification DemoProduction ProofAPI Reference
- - + + + + + + Verify Badge Demo | CommandLayer + + + + + + + + + + +
+
+
+

Embedded Verification

+

Show receipt verification inside your product.

+

CommandLayer badges let apps display verifier results where users make decisions — verified, invalid, or unavailable.

+ +
+
+ +
+
+

Badge states

+
+
+ VERIFIED +

Hash and Ed25519 signature checks passed.

+

status: VALID
hash_matches: true
signature_valid: true

+
+
+ INVALID +

Receipt payload changed or proof failed.

+

status: INVALID
hash_matches: false
signature_valid: false

+
+
+ TRANSPORT_ERROR +

Verifier/runtime unavailable or request failed.

+

status: TRANSPORT_ERROR

+
+
+
+
+ +
+
+
+

What the badge does

+
    +
  • receives or fetches a receipt
  • +
  • submits the receipt to a verifier
  • +
  • displays VALID / INVALID / TRANSPORT_ERROR
  • +
  • links to the full verifier view
  • +
  • can be embedded in dashboards, product UIs, marketplaces, agent logs, or audit trails
  • +
+
+
+

What the badge does not do

+
    +
  • does not sign receipts
  • +
  • does not hold private keys
  • +
  • does not replace the verifier
  • +
  • does not make schema-valid equal verified
  • +
  • does not replace webhook sender authentication
  • +
+
+
+
+ +
+
+
+

What gets checked through the verifier

+
    +
  • receipt structure
  • +
  • metadata.proof present
  • +
  • json.sorted_keys.v1 canonicalization
  • +
  • SHA-256 hash match
  • +
  • Ed25519 signature validity
  • +
  • signature.kid (vC4WbcNoq2znSCiQ)
  • +
  • signer_id (runtime.commandlayer.eth)
  • +
  • supported capability verb
  • +
  • tamper invalidation
  • +
+

The canonical proof model is:

+

metadata.proof.canonicalization = json.sorted_keys.v1
metadata.proof.hash.alg = SHA-256
metadata.proof.hash.value
metadata.proof.signature.alg = Ed25519
metadata.proof.signature.kid = vC4WbcNoq2znSCiQ
metadata.proof.signature.value
metadata.proof.signer_id = runtime.commandlayer.eth

+

Runtime endpoint: https://runtime.commandlayer.org.

+
+
+
+ +
+
+
+

Live proof tie-in

+

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.

+
+
+

Embed example

+
<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.

+
+
+
+ +
+
+ + +
+
+
+ + + +