@certrev/cert-block 1.2.1-canary.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/CHANGELOG.md +72 -0
  2. package/README.md +12 -1
  3. package/dist/builder/cert-review-card.d.ts.map +1 -1
  4. package/dist/builder/cert-review-card.js +68 -3
  5. package/dist/builder/cert-review-card.js.map +1 -1
  6. package/dist/builder/delivered-face.d.ts +74 -0
  7. package/dist/builder/delivered-face.d.ts.map +1 -0
  8. package/dist/builder/delivered-face.js +122 -0
  9. package/dist/builder/delivered-face.js.map +1 -0
  10. package/dist/builder/delivery-runtime.d.ts +1 -1
  11. package/dist/builder/delivery-runtime.d.ts.map +1 -1
  12. package/dist/builder/delivery-runtime.js +3 -1
  13. package/dist/builder/delivery-runtime.js.map +1 -1
  14. package/dist/builder/index.d.ts +4 -1
  15. package/dist/builder/index.d.ts.map +1 -1
  16. package/dist/builder/index.js +4 -1
  17. package/dist/builder/index.js.map +1 -1
  18. package/dist/builder/ssr-seed.d.ts +13 -1
  19. package/dist/builder/ssr-seed.d.ts.map +1 -1
  20. package/dist/builder/ssr-seed.js +14 -2
  21. package/dist/builder/ssr-seed.js.map +1 -1
  22. package/dist/index.d.ts +2 -2
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +1 -1
  25. package/dist/index.js.map +1 -1
  26. package/dist/verify/get-verified-delivery.d.ts +28 -0
  27. package/dist/verify/get-verified-delivery.d.ts.map +1 -1
  28. package/dist/verify/get-verified-delivery.js +29 -3
  29. package/dist/verify/get-verified-delivery.js.map +1 -1
  30. package/package.json +112 -112
  31. package/src/builder/cert-review-card.tsx +96 -4
  32. package/src/builder/delivered-face.ts +142 -0
  33. package/src/builder/delivery-runtime.ts +3 -1
  34. package/src/builder/index.tsx +4 -1
  35. package/src/builder/ssr-seed.ts +25 -2
  36. package/src/index.ts +3 -0
  37. package/src/verify/get-verified-delivery.ts +45 -3
package/src/index.ts CHANGED
@@ -106,6 +106,7 @@ export {
106
106
  type CertCredential,
107
107
  type CertDeliveryEnvelope,
108
108
  type CertDisplayConfig,
109
+ type CertFaceDigest,
109
110
  type CertLifecycle,
110
111
  type CertPayload,
111
112
  type CertSignature,
@@ -135,6 +136,7 @@ export {
135
136
  export { TtlCache, type TtlCacheOptions } from './verify/cache.js'
136
137
  export {
137
138
  type CachedDelivery,
139
+ type DeliveredFace,
138
140
  type DesignPin,
139
141
  deliveryCacheKey,
140
142
  deliveryUrl,
@@ -142,6 +144,7 @@ export {
142
144
  type GetVerifiedDeliveryOptions,
143
145
  getVerifiedDelivery,
144
146
  invalidateDelivery,
147
+ parseDeliveredFace,
145
148
  parseDesignPin,
146
149
  parseRenderDef,
147
150
  peekVerifiedDelivery,
@@ -36,6 +36,12 @@
36
36
  * card paints its options face instead of nothing. A definitive answer (404/410, a key or signature that does not
37
37
  * verify, a revoked, expired or mismatched artifact) is never `unreachable`. `readVerifiedDelivery` is the
38
38
  * synchronous read that also serves a stale entry while refreshing it.
39
+ *
40
+ * 1.3.0: the response may also carry a server-rendered face as a second UNSIGNED sibling, `face: { html, css }`. It is
41
+ * read the way `renderDef` is (shape-checked, malformed is dropped, never read unless the artifact verified) and cached
42
+ * beside it, and `settleDelivery` hands it out only on a `render` verdict. NOTHING HERE VOUCHES FOR IT: the signature
43
+ * does not cover it. What binds it is the signed `payload.content.faceDigest` (cert-contract 0.6.0), and a caller
44
+ * paints it only after hashing it against that digest, as the Builder card does (`src/builder/delivered-face.ts`).
39
45
  */
40
46
 
41
47
  import type { CertChromeRenderDef } from '../builder/cert-chrome-data.js'
@@ -52,6 +58,16 @@ import { renderVerdict, verifyArtifact } from '../contract/kernel.js'
52
58
  import { TtlCache } from './cache.js'
53
59
  import { DEFAULT_FETCH_TIMEOUT_MS, fetchWithDeadline } from './fetch-with-deadline.js'
54
60
 
61
+ /**
62
+ * 1.3.0: the server-rendered face the Delivery API may serve beside the artifact. UNSIGNED and UNVERIFIED as carried
63
+ * here: paint it only when sha256 of each half's UTF-8 bytes equals the signed `payload.content.faceDigest`, and the
64
+ * digest's `defVersion` is the def served with it.
65
+ */
66
+ export interface DeliveredFace {
67
+ readonly html: string
68
+ readonly css: string
69
+ }
70
+
55
71
  /**
56
72
  * What one Delivery read settles to, as cached: a verified payload + its def (and, from 1.2.0, the signature it
57
73
  * verified with, so a server render can hand the signed envelope to the browser), or the reason not.
@@ -63,6 +79,8 @@ export type CachedDelivery =
63
79
  readonly renderDef: CertChromeRenderDef | null
64
80
  /** Absent only in a cache filled by hand (a test, a custom cache); a read always keeps it. */
65
81
  readonly signature?: CertSignature
82
+ /** 1.3.0: the unsigned face sibling, when the response carried a well-formed one; else absent. */
83
+ readonly face?: DeliveredFace
66
84
  }
67
85
  | {
68
86
  readonly kind: 'suppressed'
@@ -80,6 +98,11 @@ export interface VerifiedDelivery {
80
98
  readonly signature?: CertSignature
81
99
  /** 1.2.0: on a suppress verdict, true when CertREV was not reached; else undefined. */
82
100
  readonly unreachable?: true
101
+ /**
102
+ * 1.3.0: on a `render` verdict, the server-rendered face served beside the artifact, when the response carried a
103
+ * well-formed one; else undefined (never on a suppress verdict). UNVERIFIED: see `DeliveredFace`.
104
+ */
105
+ readonly face?: DeliveredFace
83
106
  }
84
107
 
85
108
  export interface GetVerifiedDeliveryOptions {
@@ -230,6 +253,18 @@ export function parseRenderDef(value: unknown): CertChromeRenderDef | null {
230
253
  }
231
254
  }
232
255
 
256
+ /**
257
+ * 1.3.0: the face sibling, shape-checked: an object whose `html` and `css` are both strings, copied to exactly those
258
+ * two keys. Anything else is null, and the card paints exactly what it paints with no face. The markup is NOT judged
259
+ * here; the signed digest judges it at paint time.
260
+ */
261
+ export function parseDeliveredFace(value: unknown): DeliveredFace | null {
262
+ if (!isRecord(value)) return null
263
+ const { html, css } = value
264
+ if (!isString(html) || !isString(css)) return null
265
+ return { html, css }
266
+ }
267
+
233
268
  /** Fetch, parse and cryptographically verify one Delivery response. Never throws. */
234
269
  async function loadDelivery(opts: GetVerifiedDeliveryOptions): Promise<CachedDelivery> {
235
270
  const fetchImpl = opts.fetchImpl ?? globalThis.fetch
@@ -259,8 +294,8 @@ async function loadDelivery(opts: GetVerifiedDeliveryOptions): Promise<CachedDel
259
294
  let verdict: CertVerdict
260
295
  try {
261
296
  // `verifyArtifact`, so a tombstone verifies as itself and reports 'revoked'. The kernel reads
262
- // `payload` + `signature` (or the tombstone's own fields); the `renderDef` sibling is not
263
- // part of what is verified, and is never read unless the artifact verified.
297
+ // `payload` + `signature` (or the tombstone's own fields); the `renderDef` and (1.3.0) `face`
298
+ // siblings are not part of what is verified, and are never read unless the artifact verified.
264
299
  verdict = await verifyArtifact(body as unknown as CertDeliveryArtifact, opts.resolveKid, { ...opts.context })
265
300
  } catch {
266
301
  return suppressed('unknown_key')
@@ -268,11 +303,13 @@ async function loadDelivery(opts: GetVerifiedDeliveryOptions): Promise<CachedDel
268
303
  if (verdict.decision !== 'render') return suppressed(verdict.reason)
269
304
  // A `render` verdict came from an envelope (a tombstone never renders), so `signature` is the one just verified.
270
305
  const signature = isRecord(body.signature) ? (body.signature as unknown as CertSignature) : undefined
306
+ const face = parseDeliveredFace(body.face)
271
307
  return {
272
308
  kind: 'verified',
273
309
  payload: verdict.payload,
274
310
  renderDef: parseRenderDef(body.renderDef),
275
311
  ...(signature ? { signature } : {}),
312
+ ...(face ? { face } : {}),
276
313
  }
277
314
  }
278
315
 
@@ -287,7 +324,12 @@ export function settleDelivery(cached: CachedDelivery, context: RenderContext):
287
324
  }
288
325
  const verdict = renderVerdict(cached.payload, context)
289
326
  if (verdict.decision !== 'render') return { verdict, renderDef: null }
290
- return { verdict, renderDef: cached.renderDef, ...(cached.signature ? { signature: cached.signature } : {}) }
327
+ return {
328
+ verdict,
329
+ renderDef: cached.renderDef,
330
+ ...(cached.signature ? { signature: cached.signature } : {}),
331
+ ...(cached.face ? { face: cached.face } : {}),
332
+ }
291
333
  }
292
334
 
293
335
  /**