@blamejs/blamejs-shop 0.4.3 → 0.4.5

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 (28) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/README.md +8 -7
  3. package/lib/admin.js +376 -0
  4. package/lib/asset-manifest.json +5 -5
  5. package/lib/storefront.js +296 -9
  6. package/lib/vendor/MANIFEST.json +23 -23
  7. package/lib/vendor/blamejs/.pinact.yaml +1 -1
  8. package/lib/vendor/blamejs/CHANGELOG.md +2 -0
  9. package/lib/vendor/blamejs/SECURITY.md +1 -1
  10. package/lib/vendor/blamejs/api-snapshot.json +15 -2
  11. package/lib/vendor/blamejs/index.js +5 -1
  12. package/lib/vendor/blamejs/lib/auth/jar.js +190 -28
  13. package/lib/vendor/blamejs/lib/auth/jwt-external.js +213 -0
  14. package/lib/vendor/blamejs/lib/auth/oauth.js +115 -101
  15. package/lib/vendor/blamejs/lib/http-client.js +3 -4
  16. package/lib/vendor/blamejs/lib/lro.js +3 -4
  17. package/lib/vendor/blamejs/lib/middleware/deny-response.js +2 -10
  18. package/lib/vendor/blamejs/lib/middleware/health.js +1 -4
  19. package/lib/vendor/blamejs/lib/middleware/trace-log-correlation.js +3 -6
  20. package/lib/vendor/blamejs/lib/validate-opts.js +34 -0
  21. package/lib/vendor/blamejs/package.json +1 -1
  22. package/lib/vendor/blamejs/release-notes/v0.14.22.json +91 -0
  23. package/lib/vendor/blamejs/test/layer-0-primitives/auth-jar.test.js +226 -6
  24. package/lib/vendor/blamejs/test/layer-0-primitives/codebase-patterns.test.js +122 -14
  25. package/lib/vendor/blamejs/test/layer-0-primitives/jwt-external.test.js +104 -2
  26. package/lib/vendor/blamejs/test/layer-0-primitives/oauth-callback.test.js +127 -0
  27. package/package.json +1 -1
  28. package/lib/vendor/blamejs/memory/specs/node-26-map-getorinsert-migration.md +0 -165
package/lib/storefront.js CHANGED
@@ -288,6 +288,7 @@ var LAYOUT =
288
288
  "<body>\n" +
289
289
  " <a class=\"skip-link\" href=\"#main\">{{skip_to_content}}</a>\n" +
290
290
  "RAW_ANNOUNCEMENT_BAR" +
291
+ "RAW_PROMO_TOP_STRIP" +
291
292
  "\n" +
292
293
  " <div class=\"utility-bar\" role=\"complementary\">\n" +
293
294
  " <div class=\"utility-bar__inner\">\n" +
@@ -329,6 +330,7 @@ var LAYOUT =
329
330
  " </div>\n" +
330
331
  " </section>\n" +
331
332
  "\n" +
333
+ "RAW_PROMO_FOOTER" +
332
334
  " <footer class=\"site-footer\">\n" +
333
335
  " <div class=\"site-footer__inner\">\n" +
334
336
  " <div class=\"site-footer__brand-col\">\n" +
@@ -576,6 +578,160 @@ function _buildAnnouncementBar(row) {
576
578
  "</aside>";
577
579
  }
578
580
 
581
+ // ---- promo banners -----------------------------------------------------
582
+ //
583
+ // Operator-authored marketing banners rendered at six fixed placements
584
+ // across the storefront — `top_strip` + `footer` are sitewide chrome (every
585
+ // page, spliced through the LAYOUT); `homepage_hero`, `pdp_side`,
586
+ // `cart_side`, `search_empty` are page-specific (spliced into the matching
587
+ // render fn). Each placement shows the single highest-priority banner active
588
+ // for the request's viewer at the request instant (priority DESC, then the
589
+ // cache's own order), audience-filtered (all / guest / logged_in; segment is
590
+ // resolved against the customerSegments handle the primitive carries, when
591
+ // one is wired).
592
+ //
593
+ // Resolution mirrors the announcement bar: a short-TTL in-memory cache of
594
+ // the active rows (refreshed out-of-band, fire-and-forget) feeds a
595
+ // SYNCHRONOUS per-request resolver, so the picked banners can ride the same
596
+ // locale ALS the page handler reads (an async middleware's `enterWith` would
597
+ // not reach the handler). No per-request DB read on the hot render path. The
598
+ // edge mirrors the markup byte-for-byte via `worker/render/_lib.js`'s
599
+ // `promoBanner` so an edge-cached page and a container page render the same
600
+ // banner for the same placement + audience.
601
+ //
602
+ // Impression / click counters fire container-side only (fire-and-forget,
603
+ // drop-silent) — the edge has no container handle, and an edge-cached page
604
+ // can't bump a per-view counter regardless. Counters never block or fail a
605
+ // render.
606
+ var _PROMO_PLACEMENTS = ["top_strip", "homepage_hero", "pdp_side", "cart_side", "search_empty", "footer"];
607
+ var _PROMO_TTL_MS = 30000;
608
+ var _promoCache = { rows: [], at: 0, inflight: false };
609
+
610
+ // Refresh the active-banner cache when it's older than the TTL. Async +
611
+ // fire-and-forget — a slow D1 read never stalls a render; the request
612
+ // resolves against whatever the cache last held (empty on a cold first
613
+ // request, populated within the TTL after).
614
+ function _refreshPromoCache(promoBanners) {
615
+ if (!promoBanners) return;
616
+ var now = Date.now();
617
+ if (_promoCache.inflight) return;
618
+ if (now - _promoCache.at < _PROMO_TTL_MS && _promoCache.at !== 0) return;
619
+ _promoCache.inflight = true;
620
+ Promise.resolve()
621
+ .then(function () { return promoBanners.listAll({ active_only: true }); })
622
+ .then(function (rows) { _promoCache.rows = Array.isArray(rows) ? rows : []; _promoCache.at = Date.now(); })
623
+ .catch(function () { /* drop-silent — keep serving the prior cache */ })
624
+ .then(function () { _promoCache.inflight = false; });
625
+ }
626
+
627
+ // Synchronous pick across the cached active rows: for each placement keep the
628
+ // highest-priority row whose audience the viewer matches. `listAll({active_only})`
629
+ // already sorts priority DESC (then created_at ASC, slug ASC), so the first
630
+ // audience match per placement is the winner. Segment audience is skipped at
631
+ // resolve time (the console doesn't offer it; a seeded segment row would need
632
+ // the isMember handle the cache resolver doesn't carry per request) — exactly
633
+ // like the announcement bar's segment skip. Returns a map keyed by placement
634
+ // (value = the row or null).
635
+ function _resolveActivePromoBanners(viewerKind) {
636
+ var out = {};
637
+ for (var k = 0; k < _PROMO_PLACEMENTS.length; k += 1) out[_PROMO_PLACEMENTS[k]] = null;
638
+ var rows = _promoCache.rows;
639
+ if (!rows || !rows.length) return out;
640
+ for (var i = 0; i < rows.length; i += 1) {
641
+ var row = rows[i];
642
+ var pl = row.placement;
643
+ // Skip unknown placements (out has no such key) and ones already filled —
644
+ // the cache is priority-sorted so the first audience match per placement
645
+ // is the winner.
646
+ if (!Object.prototype.hasOwnProperty.call(out, pl) || out[pl] !== null) continue;
647
+ if (row.audience === "logged_in" && viewerKind !== "logged_in") continue;
648
+ if (row.audience === "guest" && viewerKind !== "guest") continue;
649
+ if (row.audience === "segment") continue;
650
+ out[pl] = row;
651
+ }
652
+ return out;
653
+ }
654
+
655
+ // Render the banner markup for a hydrated row — BYTE-IDENTICAL to
656
+ // lib/promo-banners.js's `renderHtml` and to the edge twin
657
+ // `worker/render/_lib.js`'s `promoBanner`. Returns "" for a null row so an
658
+ // empty placement renders nothing. Every operator field is escaped; the
659
+ // cta_url / image_url were https/-rooted-validated at write time, and the
660
+ // href sink is re-checked here so a `javascript:`/`data:` scheme can never
661
+ // reach the rendered link even if a row arrived by another path.
662
+ function _buildPromoBanner(row) {
663
+ if (!row) return "";
664
+ var esc = function (s) { return b.template.escapeHtml(String(s == null ? "" : s)); };
665
+ var theme = esc(row.theme || "info");
666
+ var placement = esc(row.placement);
667
+ var slug = esc(row.slug);
668
+ var parts = [];
669
+ parts.push("<div class=\"promo-banner promo-banner--" + theme + " promo-banner--" + placement +
670
+ "\" data-banner-slug=\"" + slug + "\">");
671
+ if (row.image_url) {
672
+ var img = String(row.image_url);
673
+ if (/^https:\/\//i.test(img) || (img.charAt(0) === "/" && img.charAt(1) !== "/")) {
674
+ parts.push("<img class=\"promo-banner__image\" src=\"" + esc(img) + "\" alt=\"\" />");
675
+ }
676
+ }
677
+ parts.push("<div class=\"promo-banner__body\">");
678
+ parts.push("<h2 class=\"promo-banner__headline\">" + esc(row.headline) + "</h2>");
679
+ if (row.body) {
680
+ var raw = String(row.body).replace(/\r\n/g, "\n").split("\n");
681
+ while (raw.length && raw[raw.length - 1] === "") raw.pop();
682
+ for (var i = 0; i < raw.length; i += 1) {
683
+ parts.push("<p class=\"promo-banner__line\">" + esc(raw[i]) + "</p>");
684
+ }
685
+ }
686
+ // The CTA points at the container click-tracking route (`/promo/:slug/
687
+ // click`), which bumps recordClick then 303-redirects to the banner's own
688
+ // operator-validated cta_url. The href is derived from the slug alone (a
689
+ // narrow [A-Za-z0-9._-] charset), so it carries no operator free text and
690
+ // is byte-identical edge + container — the edge link simply navigates to
691
+ // the container route on click. Rendered only when the stored cta_url is a
692
+ // valid https/-rooted target (the route re-validates before redirecting).
693
+ var href = String(row.cta_url == null ? "" : row.cta_url);
694
+ if (/^https:\/\//i.test(href) || (href.charAt(0) === "/" && href.charAt(1) !== "/")) {
695
+ parts.push("<a class=\"promo-banner__cta\" href=\"/promo/" + slug + "/click\" data-banner-slug=\"" + slug + "\">" +
696
+ esc(row.cta_label) + "</a>");
697
+ }
698
+ parts.push("</div>");
699
+ parts.push("</div>");
700
+ return parts.join("");
701
+ }
702
+
703
+ // Fire-and-forget impression bump for a rendered banner. Drop-silent on the
704
+ // hot render path — the counter is supplementary; an absent handle / failed
705
+ // write never affects the response. The primitive's recordImpression is
706
+ // itself drop-silent, so this only guards the call shape + the promise tail.
707
+ function _firePromoImpression(handle, slug) {
708
+ if (!handle || typeof handle.recordImpression !== "function" || !slug) return;
709
+ try {
710
+ var r = handle.recordImpression(slug);
711
+ if (r && typeof r.then === "function") r.then(function () {}, function () {});
712
+ } catch (_e) { /* drop-silent — impression bump must not affect render */ }
713
+ }
714
+
715
+ // Resolve the pre-rendered HTML for a placement from the request's ALS store
716
+ // (seeded by `promoBannerMiddleware`), or an explicit `opts.promo_banners`
717
+ // map a renderer / unit test threads. Returns "" when no banner is active.
718
+ // When the ALS path renders a banner, fires a best-effort impression for it
719
+ // (the explicit-opts path is the edge twin / unit test, which never counts).
720
+ function _promoBannerHtml(opts, placement) {
721
+ if (opts && opts.promo_banners && typeof opts.promo_banners === "object" &&
722
+ typeof opts.promo_banners[placement] === "string") {
723
+ return opts.promo_banners[placement];
724
+ }
725
+ var storeCtx = _localeAls.getStore();
726
+ var map = (storeCtx && storeCtx.promo_banners) || null;
727
+ if (map && map[placement]) {
728
+ var row = map[placement];
729
+ _firePromoImpression(storeCtx && storeCtx.promo_banners_handle, row.slug);
730
+ return _buildPromoBanner(row);
731
+ }
732
+ return "";
733
+ }
734
+
579
735
  // Multi-currency display switcher — a GET form in the footer listing the
580
736
  // operator's display currencies. Selecting one POSTs to /currency, which
581
737
  // sets the sealed `shop_ccy` cookie and redirects back. The currently
@@ -793,6 +949,13 @@ function _wrap(opts) {
793
949
  var announcementScript = (announcementRow && announcementRow.dismissible)
794
950
  ? _islandScript("announcement.js", { id: "announcement-island" })
795
951
  : "";
952
+ // Sitewide promo banners (top_strip above the chrome, footer above the
953
+ // site footer) — pre-rendered HTML from the ALS-resolved map, or "" when
954
+ // no banner is active for the request's audience. Page-specific placements
955
+ // (homepage_hero / pdp_side / cart_side / search_empty) are spliced by the
956
+ // matching render fn, not the LAYOUT.
957
+ var promoTopStrip = _promoBannerHtml(opts, "top_strip");
958
+ var promoFooter = _promoBannerHtml(opts, "footer");
796
959
  var chrome = localeCtx.chrome;
797
960
  var cartCount = opts.cart_count == null ? 0 : opts.cart_count;
798
961
  // The cart aria-label carries the count: when the resolved string is
@@ -882,6 +1045,11 @@ function _wrap(opts) {
882
1045
  // Matches the edge renderers' `spliceRaw` so the dual-render stays
883
1046
  // byte-consistent under a `$`-bearing announcement. See `_spliceRaw`.
884
1047
  assembled = _spliceRaw(assembled, "RAW_ANNOUNCEMENT_BAR", announcementBarHtml);
1048
+ // Sitewide promo banners carry operator marketing copy (HTML-escaped, but a
1049
+ // `$` can survive), so splice via the replacer-function helper like the
1050
+ // announcement bar — byte-consistent with the edge `spliceRaw`.
1051
+ assembled = _spliceRaw(assembled, "RAW_PROMO_TOP_STRIP", promoTopStrip);
1052
+ assembled = _spliceRaw(assembled, "RAW_PROMO_FOOTER", promoFooter);
885
1053
  // Primary nav — a raw splice (post strict-render) so the chrome strings
886
1054
  // it consumes (nav_shop / nav_collections / nav_categories /
887
1055
  // nav_framework / nav_account / nav_menu / nav_cart_aria) need no LAYOUT
@@ -1273,7 +1441,11 @@ function renderHome(opts) {
1273
1441
  // resolves the absolute base from the shop name (a bare host or a full
1274
1442
  // URL both normalise to `https://<host>`).
1275
1443
  var homeJsonLd = _orgWebsiteJsonLd(shopName);
1276
- var body = hero + catalog + homeJsonLd;
1444
+ // Homepage-hero promo banner operator marketing block above the hero.
1445
+ // Resolved from the request ALS (or threaded `opts.promo_banners`); "" when
1446
+ // none is active. Byte-identical to the edge `home.js` placement.
1447
+ var promoHero = _promoBannerHtml(opts, "homepage_hero");
1448
+ var body = promoHero + hero + catalog + homeJsonLd;
1277
1449
  return _wrap(Object.assign({
1278
1450
  title: title,
1279
1451
  shop_name: shopName,
@@ -1283,6 +1455,7 @@ function renderHome(opts) {
1283
1455
  og_url: opts.og_url,
1284
1456
  og_description: "Shop the blamejs.shop catalog — an open-source, server-rendered storefront with post-quantum crypto and zero npm runtime dependencies.",
1285
1457
  body: body,
1458
+ promo_banners: opts.promo_banners,
1286
1459
  }, _currencyWrapOpts(opts)));
1287
1460
  }
1288
1461
 
@@ -1735,13 +1908,19 @@ function renderSearch(opts) {
1735
1908
  var chipsHtml = (qTrim.length > 0) ? _renderSearchChips(facets, filters, opts.q) : "";
1736
1909
 
1737
1910
  var header = _render(SEARCH_HEADER, { title: title, summary: summary });
1911
+ // search_empty promo banner — operator marketing block shown alongside the
1912
+ // no-results state (the placement is named for "in place of no-results
1913
+ // copy"). Resolved from the request ALS (or threaded `opts.promo_banners`);
1914
+ // "" when none is active. Byte-identical to the edge `search.js` placement.
1915
+ var promoSearchEmpty = _promoBannerHtml(opts, "search_empty");
1738
1916
  var resultsInner;
1739
1917
  if (products.length === 0) {
1740
1918
  var clearLink = hasFilters
1741
1919
  ? _render("<a href=\"{{href}}\" class=\"btn-ghost\">Clear filters</a>", { href: _searchUrl(opts.q, {}) })
1742
1920
  : "";
1743
- resultsInner = _render(SEARCH_EMPTY, { heading: emptyHeading, copy: emptyCopy, clear_link: "RAW_CLEAR" })
1744
- .replace("RAW_CLEAR", clearLink);
1921
+ resultsInner = promoSearchEmpty +
1922
+ _render(SEARCH_EMPTY, { heading: emptyHeading, copy: emptyCopy, clear_link: "RAW_CLEAR" })
1923
+ .replace("RAW_CLEAR", clearLink);
1745
1924
  } else {
1746
1925
  var assetPrefix = opts.asset_prefix || "/assets/";
1747
1926
  var fmt = _priceFormatter(opts);
@@ -1790,6 +1969,7 @@ function renderSearch(opts) {
1790
1969
  // but let crawlers follow the product links the page lists.
1791
1970
  robots: "noindex,follow",
1792
1971
  body: body,
1972
+ promo_banners: opts.promo_banners,
1793
1973
  }, _currencyWrapOpts(opts)));
1794
1974
  }
1795
1975
 
@@ -6386,6 +6566,11 @@ function renderProduct(opts) {
6386
6566
  var qaPageJsonLd = _buildQaPageJsonLd(opts.qa_questions);
6387
6567
  jsonLd = (jsonLd || "") + breadcrumbJsonLd + qaPageJsonLd;
6388
6568
 
6569
+ // pdp_side promo banner — operator marketing block alongside the product.
6570
+ // Resolved from the request ALS (or threaded `opts.promo_banners`); "" when
6571
+ // none is active. Byte-identical to the edge `product.js` placement.
6572
+ var promoPdpSide = _promoBannerHtml(opts, "pdp_side");
6573
+
6389
6574
  return _wrap(Object.assign({
6390
6575
  title: opts.product.title,
6391
6576
  shop_name: shopName,
@@ -6397,7 +6582,8 @@ function renderProduct(opts) {
6397
6582
  og_image: ogImage,
6398
6583
  canonical_url: opts.canonical_url,
6399
6584
  og_url: opts.og_url,
6400
- body: body + jsonLd,
6585
+ body: promoPdpSide + body + jsonLd,
6586
+ promo_banners: opts.promo_banners,
6401
6587
  }, _currencyWrapOpts(opts)));
6402
6588
  }
6403
6589
 
@@ -7924,9 +8110,14 @@ function renderCart(opts) {
7924
8110
  });
7925
8111
  }
7926
8112
  function _escAttr(s) { return b.template.escapeHtml(s); }
8113
+ // cart_side promo banner — operator marketing block adjacent to the cart
8114
+ // summary. Container-only (the cart page is session-bound, never edge-
8115
+ // cached), resolved from the request ALS (or threaded `opts.promo_banners`);
8116
+ // "" when none is active.
8117
+ var promoCartSide = _promoBannerHtml(opts, "cart_side");
7927
8118
  var body;
7928
8119
  if (rendered.length === 0) {
7929
- body = CART_EMPTY_PAGE;
8120
+ body = promoCartSide + CART_EMPTY_PAGE;
7930
8121
  } else {
7931
8122
  var canSave = !!opts.can_save;
7932
8123
  var rows = rendered.map(function (l) {
@@ -8010,7 +8201,7 @@ function renderCart(opts) {
8010
8201
  // empty. Indented to match the aside's two-space block.
8011
8202
  var cartEstimateHtml = _buildDeliveryEstimate(opts.delivery_estimate, b.template.escapeHtml);
8012
8203
  cartEstimateHtml = cartEstimateHtml ? " " + cartEstimateHtml + "\n" : "";
8013
- body = _render(CART_PAGE, {
8204
+ body = promoCartSide + _render(CART_PAGE, {
8014
8205
  line_rows: "RAW_LINES",
8015
8206
  }).replace("RAW_LINES", rows)
8016
8207
  .replace("RAW_TOTALS_ROWS", totalsRows)
@@ -8041,6 +8232,7 @@ function renderCart(opts) {
8041
8232
  // describing rather than relying on robots.txt alone.
8042
8233
  robots: "noindex",
8043
8234
  body: body,
8235
+ promo_banners: opts.promo_banners,
8044
8236
  }, _currencyWrapOpts(opts)));
8045
8237
  }
8046
8238
 
@@ -8937,6 +9129,17 @@ function _setLocaleCookie(res, locale) {
8937
9129
 
8938
9130
  // ---- account-page renderers --------------------------------------------
8939
9131
 
9132
+ // One sign-in screen offering BOTH passwordless paths. The passkey form is
9133
+ // the primary action (a JS island posts the WebAuthn ceremony to
9134
+ // /account/passkey/login-*). The email magic-link is rendered inline as a
9135
+ // SERVER-RENDERED form that POSTs to /account/login/link — it works with
9136
+ // JavaScript disabled and is the always-available backup so a browser
9137
+ // without WebAuthn (or a failed ceremony) is never a dead end. The two
9138
+ // forms are independent <form> elements: the no-JS fallback never depends
9139
+ // on the island. The magic-link block only renders when the operator has
9140
+ // wired the customer-portal primitive AND a transactional mailer (the GET
9141
+ // route sets `magic_link_enabled`); absent it, the page degrades to the
9142
+ // passkey path with no broken affordance.
8940
9143
  var ACCOUNT_LOGIN_PAGE =
8941
9144
  "<section class=\"auth-page\">\n" +
8942
9145
  " <div class=\"auth-card\">\n" +
@@ -8957,6 +9160,23 @@ var ACCOUNT_LOGIN_PAGE =
8957
9160
  " RAW_LOGIN_SCRIPT\n" +
8958
9161
  "</section>\n";
8959
9162
 
9163
+ // The inline email magic-link path on the unified login screen. A distinct
9164
+ // server-rendered <form> (its own email field) that POSTs to
9165
+ // /account/login/link with no JavaScript — the `_injectCsrfFields` wrap
9166
+ // chokepoint stamps the `_csrf` token automatically (the action is not an
9167
+ // EDGE_POST_PATHS prefix), so a no-JS browser submits an accepted token.
9168
+ // `data-passkey-fallback` lets passkey-login.js point a user here in one tap
9169
+ // when WebAuthn is unsupported or the ceremony fails — no dead end.
9170
+ var LOGIN_MAGIC_INLINE =
9171
+ "<div class=\"auth-oauth\" data-passkey-fallback>" +
9172
+ "<div class=\"auth-oauth__divider\"><span>or</span></div>" +
9173
+ "<p class=\"auth-card__lede\">No passkey on this device? We'll email you a single-use sign-in link.</p>" +
9174
+ "<form method=\"post\" action=\"/account/login/link\" class=\"form-stack auth-form auth-form--magic\">" +
9175
+ "<div class=\"form-row\"><label class=\"form-field\"><span class=\"form-field__label\">Email</span><input type=\"email\" name=\"email\" id=\"magic-email\" required autocomplete=\"email\"></label></div>" +
9176
+ "<div class=\"form-actions\"><button type=\"submit\" class=\"btn-secondary auth-form__submit\">Email me a sign-in link</button></div>" +
9177
+ "</form>" +
9178
+ "</div>";
9179
+
8960
9180
  var LOGIN_ERROR_MESSAGES = {
8961
9181
  oauth: "We couldn't complete that sign-in. Please try again.",
8962
9182
  "email-conflict": "That email already has an account — sign in with your passkey instead.",
@@ -8981,9 +9201,9 @@ function renderAccountLogin(opts) {
8981
9201
  var errHtml = (opts.error && LOGIN_ERROR_MESSAGES[opts.error])
8982
9202
  ? "<p class=\"auth-form__message auth-form__message--err\">" + b.template.escapeHtml(LOGIN_ERROR_MESSAGES[opts.error]) + "</p>"
8983
9203
  : "";
8984
- var magicHtml = opts.magic_link_enabled
8985
- ? "<p class=\"auth-card__alt\"><a href=\"/account/login/link\">Email me a sign-in link instead →</a></p>"
8986
- : "";
9204
+ // Render the email-link path INLINE (a working no-JS form), not as a link
9205
+ // to a separate page, so both passwordless paths live on one screen.
9206
+ var magicHtml = opts.magic_link_enabled ? LOGIN_MAGIC_INLINE : "";
8987
9207
  var body = ACCOUNT_LOGIN_PAGE
8988
9208
  .replace("RAW_LOGIN_OAUTH", oauthHtml)
8989
9209
  .replace("RAW_LOGIN_MAGIC", magicHtml)
@@ -10588,6 +10808,60 @@ function mount(router, deps) {
10588
10808
  });
10589
10809
  }
10590
10810
 
10811
+ // Promo-banner resolution — synchronous (so its `enterWith` reaches the
10812
+ // page handler) and audience-bucketed off the auth-cookie presence, exactly
10813
+ // like the announcement bar. Seeds the request ALS with the resolved
10814
+ // per-placement map (`promo_banners`) + the handle (`promo_banners_handle`)
10815
+ // so the render fns can splice each placement's markup and fire a best-
10816
+ // effort impression at the render sink. The active set rides a short-TTL
10817
+ // in-memory cache refreshed out-of-band here (fire-and-forget — never blocks
10818
+ // the render). Best-effort: any failure drops the banners, never the page.
10819
+ if (typeof router.use === "function" && deps.promoBanners) {
10820
+ router.use(function promoBannerMiddleware(req, _res, next) {
10821
+ try {
10822
+ _refreshPromoCache(deps.promoBanners);
10823
+ var viewerKind = _readPrefixedCookie(req, AUTH_COOKIE_NAME_SECURE, AUTH_COOKIE_NAME) ? "logged_in" : "guest";
10824
+ var map = _resolveActivePromoBanners(viewerKind);
10825
+ var cur = _localeAls.getStore() || {};
10826
+ _localeAls.enterWith(Object.assign({}, cur, { promo_banners: map, promo_banners_handle: deps.promoBanners }));
10827
+ } catch (_e) { /* drop-silent — no banners this request */ }
10828
+ next();
10829
+ });
10830
+
10831
+ // Click pass-through — the banner CTA links here so a click is counted
10832
+ // before the shopper reaches the destination. Bumps recordClick (best-
10833
+ // effort, drop-silent) then 303-redirects to the banner's own cta_url.
10834
+ // The destination is taken from the LIVE banner row (not a query param),
10835
+ // so the redirect target is the operator-validated https/-rooted URL the
10836
+ // primitive already vetted — a hostile/stale slug, or one with no live
10837
+ // banner, falls back to "/" and can never 500 or open-redirect.
10838
+ router.get("/promo/:slug/click", async function (req, res) {
10839
+ var slug = (req.params && typeof req.params.slug === "string") ? req.params.slug : "";
10840
+ var to = "/";
10841
+ if (/^[A-Za-z0-9][A-Za-z0-9._-]{0,79}$/.test(slug)) {
10842
+ try {
10843
+ var row = await deps.promoBanners.getBanner(slug);
10844
+ if (row && row.cta_url) {
10845
+ var href = String(row.cta_url);
10846
+ // Re-validate the destination at the redirect sink — https:// or a
10847
+ // /-rooted path (not protocol-relative `//`). The primitive vetted
10848
+ // this at write time; re-checking keeps a stored bad value from
10849
+ // ever becoming an open redirect.
10850
+ if (/^https:\/\//i.test(href) || (href.charAt(0) === "/" && href.charAt(1) !== "/")) {
10851
+ to = href;
10852
+ }
10853
+ }
10854
+ // recordClick is drop-silent on unknown slug; fire-and-forget.
10855
+ var r = deps.promoBanners.recordClick(slug);
10856
+ if (r && typeof r.then === "function") { try { await r; } catch (_e) { /* drop-silent */ } }
10857
+ } catch (_e) { /* unknown slug / read error → fall back to "/" */ }
10858
+ }
10859
+ res.status(303);
10860
+ res.setHeader && res.setHeader("location", to);
10861
+ return res.end ? res.end() : res.send("");
10862
+ });
10863
+ }
10864
+
10591
10865
  // ---- customer survey (token-gated) ----------------------------------
10592
10866
  // The invitation token IS the access — no login. GET renders the survey
10593
10867
  // (or a state notice); POST records the response. Container-only (the
@@ -13442,6 +13716,19 @@ function mount(router, deps) {
13442
13716
  }
13443
13717
 
13444
13718
  router.get("/account/login", async function (req, res) {
13719
+ // An already-signed-in visitor has no business on the sign-in screen —
13720
+ // send them to their account instead of re-rendering a login form
13721
+ // (mirrors the /account guard's auth read + vault-not-configured catch).
13722
+ var signedIn;
13723
+ try { signedIn = _currentCustomer(req); }
13724
+ catch (e) {
13725
+ if (e && e.code === "vault/not-initialized") return _serviceUnavailable(res, "auth not configured");
13726
+ throw e;
13727
+ }
13728
+ if (signedIn) {
13729
+ res.status(303); res.setHeader && res.setHeader("location", "/account");
13730
+ return res.end ? res.end() : res.send("");
13731
+ }
13445
13732
  var cartCount = await _cartCountForReq(req);
13446
13733
  var url = req.url ? new URL(req.url, "http://localhost") : null;
13447
13734
  // Login captcha is opt-in (CAPTCHA_GATE_LOGIN). The widget + the scoped
@@ -3,8 +3,8 @@
3
3
  "_about": "blamejs.shop vendors a single framework — blamejs — which itself bundles every server-side crypto/identity dependency. The transitive packages blamejs ships are surfaced in its own MANIFEST.json at lib/vendor/blamejs/lib/vendor/MANIFEST.json — Trivy / Grype rely on that nested data for CVE attribution.",
4
4
  "packages": {
5
5
  "blamejs": {
6
- "version": "0.14.21",
7
- "tag": "v0.14.21",
6
+ "version": "0.14.22",
7
+ "tag": "v0.14.22",
8
8
  "license": "Apache-2.0",
9
9
  "author": "blamejs contributors",
10
10
  "source": "https://github.com/blamejs/blamejs",
@@ -791,7 +791,6 @@
791
791
  "lib/worm.js": "lib/vendor/blamejs/lib/worm.js",
792
792
  "lib/ws-client.js": "lib/vendor/blamejs/lib/ws-client.js",
793
793
  "lib/xml-c14n.js": "lib/vendor/blamejs/lib/xml-c14n.js",
794
- "memory/specs/node-26-map-getorinsert-migration.md": "lib/vendor/blamejs/memory/specs/node-26-map-getorinsert-migration.md",
795
794
  "oss-fuzz/projects/blamejs/Dockerfile": "lib/vendor/blamejs/oss-fuzz/projects/blamejs/Dockerfile",
796
795
  "oss-fuzz/projects/blamejs/README.md": "lib/vendor/blamejs/oss-fuzz/projects/blamejs/README.md",
797
796
  "oss-fuzz/projects/blamejs/build.sh": "lib/vendor/blamejs/oss-fuzz/projects/blamejs/build.sh",
@@ -817,6 +816,7 @@
817
816
  "release-notes/v0.14.2.json": "lib/vendor/blamejs/release-notes/v0.14.2.json",
818
817
  "release-notes/v0.14.20.json": "lib/vendor/blamejs/release-notes/v0.14.20.json",
819
818
  "release-notes/v0.14.21.json": "lib/vendor/blamejs/release-notes/v0.14.21.json",
819
+ "release-notes/v0.14.22.json": "lib/vendor/blamejs/release-notes/v0.14.22.json",
820
820
  "release-notes/v0.14.3.json": "lib/vendor/blamejs/release-notes/v0.14.3.json",
821
821
  "release-notes/v0.14.4.json": "lib/vendor/blamejs/release-notes/v0.14.4.json",
822
822
  "release-notes/v0.14.5.json": "lib/vendor/blamejs/release-notes/v0.14.5.json",
@@ -1383,9 +1383,9 @@
1383
1383
  ".gitleaks.toml": "sha256:e97869021bc744236bd882a2af070eebbbc95d211336c60bf179f6521d6ed96b",
1384
1384
  ".hadolint.yaml": "sha256:46a4fbc587c8e5998430788339cc3f2be8d1014a0466a0781eb2e51e31c6dd32",
1385
1385
  ".npmrc": "sha256:66f104e7d07c496d2d0409988225e8c0e4ceb8d247dbcac3be75b2128d20ce66",
1386
- ".pinact.yaml": "sha256:5578a0ebd84a3585c0f04c7cc378889ccfb671ddd0a15ecea746e525bc7164b6",
1386
+ ".pinact.yaml": "sha256:0213ffda55961dc49b64c0a5dfa3c0567419633b1499d57eaf7c8d842d7da6c7",
1387
1387
  "ARCHITECTURE.md": "sha256:9b1c8d2b1b7a41838eb348b0a008e4b4369718fd72bfe2974b37155f7536d35b",
1388
- "CHANGELOG.md": "sha256:289bf6370f97fba4d24ef34d519eaea81b15f5f487b63642f4198c3e9adfa0a9",
1388
+ "CHANGELOG.md": "sha256:c0b12053e79aa39d75018f3e5e33af6c543236b515175aa912c589bfb1d556c1",
1389
1389
  "CODE_OF_CONDUCT.md": "sha256:148a281960fff7c2fe6554dab66da572c72245ddeb00b0d14811558397bff386",
1390
1390
  "CONTRIBUTING.md": "sha256:bb4dbdbc8598da31dbce653a8ed322e08ff46560173f2eb67a4d684653948332",
1391
1391
  "GOVERNANCE.md": "sha256:906df6afb1f552b27b9acb50f7f96c47b917a2f1021cd4e987dbf4ee0e0a821b",
@@ -1394,8 +1394,8 @@
1394
1394
  "MIGRATING.md": "sha256:e6a7e89578e0d7759183e5dfdf67dfb4ecd94aefe0fde2921fd91985ad57ef59",
1395
1395
  "NOTICE": "sha256:f487fa47a11aca0f89e2615cdd3c713e9842abf7a30d8d328eeeae1c864aa774",
1396
1396
  "README.md": "sha256:d81e9f6978eb937af294d7d0c33f869abdebdc906e77d7aeadff399a7e5a8824",
1397
- "SECURITY.md": "sha256:8a5ed25f5321d4d437bf20a9ffb7210f525632f9aada8a29033b7b16630fc123",
1398
- "api-snapshot.json": "sha256:0a9c0c1c30ee22e09eb9c510aba5c48d17a26725d615de80b68027877823ea52",
1397
+ "SECURITY.md": "sha256:ca67ec6b0638d0c64a69e7d260d2b4a101db84d240fe450dd54974c085a26ab1",
1398
+ "api-snapshot.json": "sha256:b914510a543b565493f7783c93725d47444cc457ce2388559cf1a36071034e19",
1399
1399
  "assets/BlameJS_Logo.png": "sha256:3c65699753c771b48ef9ac7f45bb40815ec19a23afcdd0cd30ef4601bbbe293e",
1400
1400
  "assets/BlameJS_Logo.svg": "sha256:dda44f3fb1343d5de9db6b1fcdb75fc649c57e7a99a8e8239fcf852e3841e1a8",
1401
1401
  "bench/README.md": "sha256:74202f2507fd840bfc1ac6c681975d9273cf36cca6e0f72655f138337304033c",
@@ -1583,7 +1583,7 @@
1583
1583
  "fuzz/safe-url_seed_corpus/05-ipv6.txt": "sha256:b210575d6e9f91b70d1616c89a38bf81e66e4356dcf204a3d40f1932961d01cc",
1584
1584
  "fuzz/safe-url_seed_corpus/06-idn.txt": "sha256:9f163641afe7046491b09f95684e30aac38b3cbf243afb115c556ae4fc0339f0",
1585
1585
  "fuzz/safe-vcard.fuzz.js": "sha256:20ef167055ea75b6138bc6dc9d8cbdcf108d92be2792571a0162b849671354bd",
1586
- "index.js": "sha256:efab5ff574cf87b1274bdba7fed17cb3bc60dd76e43eb167d7fd725e7efdbdd9",
1586
+ "index.js": "sha256:0715f7b351f0f769b4eb1994baa46f85541e1dc329b7cf0abd9b61c39c5cdcec",
1587
1587
  "keys/release-pqc-pub.json": "sha256:38fb7f580ccc06c5682c5c3f12b43441d4fdd3e79cf57afb8b3dab3a73af290d",
1588
1588
  "lib/_test/crypto-fixtures.js": "sha256:91470fc813e41eeed06dee1e8fbb92d179af77eb01109c1256f7330cb2fc0980",
1589
1589
  "lib/a2a-tasks.js": "sha256:8308a8a00790035090ae2912030c288e0cf4eaa29134f4c73bb38ddef02a4e59",
@@ -1648,11 +1648,11 @@
1648
1648
  "lib/auth/elevation-grant.js": "sha256:c152c403050b08106f687e46ee85b5d80f160f6e346b5c2d278381f039eca143",
1649
1649
  "lib/auth/fal.js": "sha256:aabf6d8095dd41dcda8a2efdb48e00e95bffe70c78991c93fbef827816918692",
1650
1650
  "lib/auth/fido-mds3.js": "sha256:5ddd58557a0331bb39e7606c15cb0f29fbc38fd85f51c99c93d16621db99261e",
1651
- "lib/auth/jar.js": "sha256:7fde8f2a2f4badbf03c9064993d894c0a419f56df6f2fdf389e1b6382d043442",
1652
- "lib/auth/jwt-external.js": "sha256:d6f9d7552e09fb3e5639406b03d1dbeae681718198a8aae325cd938e12a92cfe",
1651
+ "lib/auth/jar.js": "sha256:f333f25a87b8c60f5f19c51d68aefba8ae8ed304e0f54a957560475359e11f7f",
1652
+ "lib/auth/jwt-external.js": "sha256:4fcf0443decf374aed4d0a328ce769df2d321981afe5a9496c57e1ef90938db7",
1653
1653
  "lib/auth/jwt.js": "sha256:032dec9c7a117ef728ded26ac681b846ff4b98112074aa890c499c97902cbec4",
1654
1654
  "lib/auth/lockout.js": "sha256:44afb265e064401fc2bedfb46673f822fb24c1ffae05cdb116949ce0f841a813",
1655
- "lib/auth/oauth.js": "sha256:dfd7b2f82d9f4620a7fbf361277bdc9cc6fec8a3135a3f37fe40112aa3529bb9",
1655
+ "lib/auth/oauth.js": "sha256:4883d3d054933908c636e06c39147e0187a2805fdc0e045300b635aba6101454",
1656
1656
  "lib/auth/oid4vci.js": "sha256:f8595472abd01beb635ce03fc79e305e288ae7802587ec8f75229d2efa2c8294",
1657
1657
  "lib/auth/oid4vp.js": "sha256:ec2098480638e70d8b7b450242b5d158d52ac3d0f2aea59d6dfc345e8451a29f",
1658
1658
  "lib/auth/openid-federation.js": "sha256:e39b72665c6ab95b233f94ed44dc4e30b9e9a5ae79d0e94fe3474f8e23dba9c5",
@@ -1834,7 +1834,7 @@
1834
1834
  "lib/html-balance.js": "sha256:325db4349ac4c968704e295f2c8cbec330c2d64908c89e9192ab443572c14910",
1835
1835
  "lib/http-client-cache.js": "sha256:5bc95d801cffbde654d5216963dec888ff12ff150c2e82129f0f6d1dbb88b6cf",
1836
1836
  "lib/http-client-cookie-jar.js": "sha256:d0e859a9b548a3dc97e3418a1698b27336021f8f7d6c5327b2004dd710fa06dc",
1837
- "lib/http-client.js": "sha256:0b748fc17a215a1525e7525588adcd32e6e4259863cc2f3df88c540962bc2ca4",
1837
+ "lib/http-client.js": "sha256:885e960349c30cca15b2092c37ea275b11f15226b2e0b7c74871020c11a9bda9",
1838
1838
  "lib/http-message-signature.js": "sha256:9aae3f9231c607d4fdc7693aa8b473744af807bff07b7c50c3cf1bd0b07a3283",
1839
1839
  "lib/http2-teardown.js": "sha256:61d291c34e321e18b64d60a4c0253e638550fff7dc32568b980d3aa13bb178e2",
1840
1840
  "lib/i18n-messageformat.js": "sha256:9f7cc5761f9343e87a210b58706eed01fbfb66c563ad479e75a492b1365a25a1",
@@ -1868,7 +1868,7 @@
1868
1868
  "lib/log-stream-webhook.js": "sha256:390d771da48b3b084d1438f05a348ca34390084d5972074f75742110718e4622",
1869
1869
  "lib/log-stream.js": "sha256:9ffda79044835670fba447876b617b1d5cef0592abf08b52167e2ae7b6bcdba7",
1870
1870
  "lib/log.js": "sha256:2c6215248d5db3d7b3b75495d00ffe1f5c72f3fccc1365aa7f5a550ff153d9a6",
1871
- "lib/lro.js": "sha256:23c74b777c04437f76fc573b59ecb6df512a202ff4ec8689ac9f170ead9eba05",
1871
+ "lib/lro.js": "sha256:da9baf47f27c422c32d51495b2896c887ec3ac283875712efcd7528fd396868b",
1872
1872
  "lib/mail-agent.js": "sha256:8d2c17ac5b1039689eed9ee236a806d89ca48ccc546d7f3ad330a4bb4e475e7c",
1873
1873
  "lib/mail-arc-sign.js": "sha256:ab7a36916d78e60664d4509133cb834bf20c5dacb298404b209d4da991b4cfd5",
1874
1874
  "lib/mail-arf.js": "sha256:13163945823f1cdc4bc3b5ae16b40bedeaea5f4a161f64340398523c9f54659c",
@@ -1929,14 +1929,14 @@
1929
1929
  "lib/middleware/csrf-protect.js": "sha256:20f4f529481a31e5e8ca04019ad912b00c25c95eac21cc27ac54d7ba33921271",
1930
1930
  "lib/middleware/daily-byte-quota.js": "sha256:b1d93d7629838a995ebffeca4f2399cad3d66361e0f67c80b81aa2eb64018fdd",
1931
1931
  "lib/middleware/db-role-for.js": "sha256:b9879c17dbf7fa3d299f402043592a133176a74ac1bcb49b0b717253d4ad1d3a",
1932
- "lib/middleware/deny-response.js": "sha256:dfb8e74ea318279cf646bfc054dd229ea04ec3bde47a4647b90f0d5e22071ac5",
1932
+ "lib/middleware/deny-response.js": "sha256:381241477b78614fb4c39f0ee1a23875403f4c398fb5d0a021c521ae36fa06e6",
1933
1933
  "lib/middleware/dpop.js": "sha256:09f8c1ef796d15da18aa8ba081115e24466e292892b4abec5416a788e77b6c14",
1934
1934
  "lib/middleware/error-handler.js": "sha256:7c0baf2e6b37f1c9330775396bc46a87943a253ceba540379db4785d305c396c",
1935
1935
  "lib/middleware/fetch-metadata.js": "sha256:0be11ee9eeeaf069a553732c8ee7172ea1f01311d93af66b9402f5976b16cd3f",
1936
1936
  "lib/middleware/flag-context.js": "sha256:76fefb869244267f3b2bf24c4dd0b40fcba0988552b62d8b48208d116b85ee3f",
1937
1937
  "lib/middleware/gpc.js": "sha256:6bfc817381582e060ecead9829f92d044dbc6902c86e383429827622044e9a8d",
1938
1938
  "lib/middleware/headers.js": "sha256:d53abbe53b1981df5b934aad06dc4588729ce2024f014d2b4b0aa5ff0701cebd",
1939
- "lib/middleware/health.js": "sha256:8b7bfb4ccaa41d06a3c47992ddaf9cf0008945c81f97814cd42d7fab12fe9311",
1939
+ "lib/middleware/health.js": "sha256:d9e95a38cd18a1236b4787901f6145b258e9a63d98f98eb34e6200489673a4e3",
1940
1940
  "lib/middleware/host-allowlist.js": "sha256:c5572e12b460c69dcc98664eb95a4c810f08c95e66ee8f24f860e08f70b8e3fa",
1941
1941
  "lib/middleware/idempotency-key.js": "sha256:9ae08ef63e7f87e34a77e958d52615b57264ad9c9350c27352ba2dcbbd93eb49",
1942
1942
  "lib/middleware/index.js": "sha256:01643697e716ab912fab7f214d5d51eb1d70ca4363ad67fba5b912be2ccc0c16",
@@ -1961,7 +1961,7 @@
1961
1961
  "lib/middleware/span-http-server.js": "sha256:9fb94fa14c41b2969cce5bc9877ce49de3576d3991cfc7cc64ef2129fdec2cb6",
1962
1962
  "lib/middleware/speculation-rules.js": "sha256:565c7a55c0625f300b6ea412709fbe787fe18715bad3d2a9340184eb44007302",
1963
1963
  "lib/middleware/sse.js": "sha256:d0094cb33fced9bc748edd1f6d55fba6029ff3bff6da735d72ad5d10806f02f7",
1964
- "lib/middleware/trace-log-correlation.js": "sha256:ad85bae4cdd477ffb53419225b0a12904e4ea3510195a0f44f26c849deab8d81",
1964
+ "lib/middleware/trace-log-correlation.js": "sha256:f6a36bfcb666cee5434867b1841092793491491bb9da7bc92626dd93cfa7b0d0",
1965
1965
  "lib/middleware/trace-propagate.js": "sha256:876b91a195ae17d6c8916884c10b3fcc54d12ae97d13927e501b58f77e0c306d",
1966
1966
  "lib/middleware/tus-upload.js": "sha256:f764417775b582778285a191136a5d980e8ca618ecb79403763430ca73353d2a",
1967
1967
  "lib/middleware/web-app-manifest.js": "sha256:75c620a8f5354514f2fe8f030eceb0b2540561412c27907d259ee33063cd4bbd",
@@ -2103,7 +2103,7 @@
2103
2103
  "lib/tsa.js": "sha256:88d0f79c4eb9d6c948c360d8d6aff4982f83dd9614e37a3047c4ff247b349ba8",
2104
2104
  "lib/uri-template.js": "sha256:9b7252fce4a8245ee1ad51b3ce4dadd42d08080ddded961dafad2330a1a6cd8d",
2105
2105
  "lib/uuid.js": "sha256:5b4c2b1880a66a52adb529afce679888a033f17dd284c190419f7e496803d182",
2106
- "lib/validate-opts.js": "sha256:d2e6613976cd466613ece76238fe6df058da0eaa6a21bfbd4b32f8c7905f3870",
2106
+ "lib/validate-opts.js": "sha256:cefcb9256f8676f6ce6fa3c796467643ae44692078bb00c4c5b49ec20652aba9",
2107
2107
  "lib/vault-aad.js": "sha256:f2e5e1abecd6b5a748d1bf4c12be372f4e330ca701f03b7e12ab688a1ab98f67",
2108
2108
  "lib/vault/index.js": "sha256:3a8928741a58e98f24ef7c09880b8ef77cda044c5acd56a1640f715427701286",
2109
2109
  "lib/vault/passphrase-ops.js": "sha256:d7936e193f2ef3f52720d5c42e78eeb0a3f1baea985363c6151a8bdf271ffe9d",
@@ -2138,12 +2138,11 @@
2138
2138
  "lib/worm.js": "sha256:bb2557c6de89780e4e845d46b4bf337c9e95aaa38267c7d50f1d91876dd527e3",
2139
2139
  "lib/ws-client.js": "sha256:48982b3fb5fe12d977001d3566093f4257a247331a3e49adfa6b67fe93fb2425",
2140
2140
  "lib/xml-c14n.js": "sha256:01a27d20df99ebd6eaefbff0aad933a2231049f65a54ae994236694d4146ce5d",
2141
- "memory/specs/node-26-map-getorinsert-migration.md": "sha256:c7ec64dbfe06f280e7a0ac35e43f54e40a59efbb03ebc993383b2c2be901b34a",
2142
2141
  "oss-fuzz/projects/blamejs/Dockerfile": "sha256:277c9e93cf2e8746b0a6d09a0678b35cbe700e9c2373bdc1b2177ed2167b7359",
2143
2142
  "oss-fuzz/projects/blamejs/README.md": "sha256:ae13b7bb79ed8d69b1b3276e5562807a0349fb6e6b7d11cf1f683aad1eafdb4b",
2144
2143
  "oss-fuzz/projects/blamejs/build.sh": "sha256:0ced1cf21782c97be7f8d74faf5e27a308b60b2f858836fb5ca3b8c4e939a8f7",
2145
2144
  "oss-fuzz/projects/blamejs/project.yaml": "sha256:59f2cb83aa622325a175b77416fe155be15b70a9c798bd1a78bba05763b1b03d",
2146
- "package.json": "sha256:71c1d10bd3ccfdcd820555290aee578406030fd6079ed16982e482ae44f4a421",
2145
+ "package.json": "sha256:6cb63950bdc9eea2d713ca7972a6f9670189c132fea11b3a0271891e0a667a64",
2147
2146
  "release-notes/v0.0.x.json": "sha256:7a49819f30068ee119000cad7010194882bb8bfaa12acbdab4dfc066efb7982f",
2148
2147
  "release-notes/v0.1.x.json": "sha256:6742a8c17f947c5cb76f69dead7eea86b942d80621d914b774ba5488e09937e5",
2149
2148
  "release-notes/v0.10.x.json": "sha256:fe498045daf88337bd3d987e5964aa42c99a50e1685b6f09e51f698b8687726f",
@@ -2164,6 +2163,7 @@
2164
2163
  "release-notes/v0.14.2.json": "sha256:0823f20cd837fb2ff80b93506bf5417cdfca2d938797d3bfb0d61c89a10dd8c8",
2165
2164
  "release-notes/v0.14.20.json": "sha256:5c6d374883e9c2593c12acd9cc185f65ff76010560e92915c4bea56bd882f569",
2166
2165
  "release-notes/v0.14.21.json": "sha256:02bae943c89c01d93516ce73a87bc915951f40e428652837f41d9011cdb1db48",
2166
+ "release-notes/v0.14.22.json": "sha256:4f8acf37b8093dc53f871ecf029acefae42daba4df60ba1d031493a3034a02cd",
2167
2167
  "release-notes/v0.14.3.json": "sha256:231442b2ce6a0acc19c5e3cfdebd4725250fc5c27c0fd338d71664d8467abb95",
2168
2168
  "release-notes/v0.14.4.json": "sha256:7d3b405f139accf69bc98929ab0417ef5c45a9c9b1390bf63b5efc527dcc1519",
2169
2169
  "release-notes/v0.14.5.json": "sha256:0dadead3a48b40636222badaa2246fae2ffc73e261c57c48d4a9942e27e08dad",
@@ -2302,7 +2302,7 @@
2302
2302
  "test/layer-0-primitives/audit-use-store.test.js": "sha256:201c462f2d6e3d7f2f4850e91e702e6729427ded41ab04d0a3aae1fb3e345450",
2303
2303
  "test/layer-0-primitives/auth-bot-challenge-verifier.test.js": "sha256:082c610cce567f6fb76f030741ace572f770f600d7aa20a35d19b45f8b908ac2",
2304
2304
  "test/layer-0-primitives/auth-bot-challenge.test.js": "sha256:957abf6bce2e615b45e54c705ded318890271e37dd728d943424eca42af71ab2",
2305
- "test/layer-0-primitives/auth-jar.test.js": "sha256:bd560d71fe9bcb9a448771a6088b2a91199a089f916ce6585dadf453a9c0e1fb",
2305
+ "test/layer-0-primitives/auth-jar.test.js": "sha256:f539217f22205918d22b3a8d651c92a9d837df8279b10a89afae3a7863a10d0e",
2306
2306
  "test/layer-0-primitives/auth-jwt-defenses.test.js": "sha256:f7bda4a1cec7e73f1840b76a41fbcfe2bf41933ec5a92dc2d79466dae1ae3f4e",
2307
2307
  "test/layer-0-primitives/auth-lockout.test.js": "sha256:3d588d8c7e9715c3be8501f2391864388080a5d1163d094a3924114f52a772fb",
2308
2308
  "test/layer-0-primitives/auth-password-audit.test.js": "sha256:9f7152a245d1ba88418402aa6a7e54f4c217f7707e2962998edb1e26d34e067d",
@@ -2356,7 +2356,7 @@
2356
2356
  "test/layer-0-primitives/cluster-storage.test.js": "sha256:5627e621dff001e236b668e04336eb39c9fe08a4a7d45a640e6e7fccce37a022",
2357
2357
  "test/layer-0-primitives/cluster-vault-rotation.test.js": "sha256:3514e9e71d6c39e805248f58ad2f41528d091e196c0f3766a032675677161b2d",
2358
2358
  "test/layer-0-primitives/cms-codec.test.js": "sha256:7e46078ed82be5b69d22c48f22dba37ea5015371c2a8cf5f94fb1a792fb7bb78",
2359
- "test/layer-0-primitives/codebase-patterns.test.js": "sha256:be019c34134a4202988d64549945a9e0526ffede4ac6f3877144c1d3f5c722c0",
2359
+ "test/layer-0-primitives/codebase-patterns.test.js": "sha256:4add8aee8fca78447375c6990dc560eb9045ba08fc774fa094db71e83282c156",
2360
2360
  "test/layer-0-primitives/compliance-ai-act.test.js": "sha256:5ee4ad05d12233cb3c5457ef10a727833710bbc1ce1318838f9f9ef5d2cb8d4b",
2361
2361
  "test/layer-0-primitives/compliance-cascade.test.js": "sha256:ee02cf14541a837a9d7977c6ea6bf7f9210bed293925d93c976e31f270aebec4",
2362
2362
  "test/layer-0-primitives/compliance-eaa.test.js": "sha256:8afb3fa66f3f9452592995e77f5e0644d8c82de2321c551c6f5be6002b2c27a4",
@@ -2503,7 +2503,7 @@
2503
2503
  "test/layer-0-primitives/json-schema.test.js": "sha256:bd1e2a09d1b6c916323acfb6a79c9a2dbf9b70a658ce84069530ce2e3c55f919",
2504
2504
  "test/layer-0-primitives/jtd.test.js": "sha256:74385592a7845358bcae0f5bf9c13d2fab17f7eabdd4accd973b3acc6eb23035",
2505
2505
  "test/layer-0-primitives/jwk.test.js": "sha256:ceb4cf2aae65f7a0881143fdf8d22fa0c026bc4003df18180f3671439dd02b21",
2506
- "test/layer-0-primitives/jwt-external.test.js": "sha256:721e8b4ea52ea0fcc8a9da135d9be74e1d18cef747c7b65ba111fa2fed8fe726",
2506
+ "test/layer-0-primitives/jwt-external.test.js": "sha256:f7896e520a04c98b848d5496c3709798267ed85cafdb0e9e91cadb46aa559f67",
2507
2507
  "test/layer-0-primitives/keychain.test.js": "sha256:8d6fa2888cd9e6757101c4d211891c5a6bf3f1a88842871ff188edf2d07d80c1",
2508
2508
  "test/layer-0-primitives/legal-hold.test.js": "sha256:1ba934827ddcd679a6adaca23bdffda2d4310d9fc4f8aa98c5096fead7ee2fb1",
2509
2509
  "test/layer-0-primitives/link-header.test.js": "sha256:c684b000921c6e79d6b9a432e5f2629f36b23fa9a2fc458b9b8d34857304d0fe",
@@ -2574,7 +2574,7 @@
2574
2574
  "test/layer-0-primitives/no-cache.test.js": "sha256:b80e5ae1ad53cbf552423c3b16653c6d011d773f2056fa156e4436fc4f014e9b",
2575
2575
  "test/layer-0-primitives/notify.test.js": "sha256:8a7cf548e567cdcf0e6cc6d731c5e2e6fcc364e8838ef411999c324234da3917",
2576
2576
  "test/layer-0-primitives/numeric-bounds.test.js": "sha256:539a476f1cf968088b7f680e8a87cd734a3625eea4e375d47b1957a8f145f45f",
2577
- "test/layer-0-primitives/oauth-callback.test.js": "sha256:892d91c66417e5771bc179e6e460735e028a3fe5c0a5e807438deda1c1794ffd",
2577
+ "test/layer-0-primitives/oauth-callback.test.js": "sha256:da785fbad16efd7b76b43285f0483c37f0fa70d5505e28c802cbac9c28d8f5f9",
2578
2578
  "test/layer-0-primitives/observability-tracing.test.js": "sha256:0912c59a2b52ca139a61a06a5f0f57bcd952b0da503656efe6c0e0a3135765f4",
2579
2579
  "test/layer-0-primitives/observability.test.js": "sha256:969600b4e53437d0efdb326cd7e4df06f807afd5c5d4f21100091f1c1e764258",
2580
2580
  "test/layer-0-primitives/openapi.test.js": "sha256:2e552cbb27b70ac28688632364defc9d063b3b26ff45788012e656bce8ba31e3",
@@ -18,7 +18,7 @@
18
18
  # re-verification that the upstream org makes impossible from a
19
19
  # runner.
20
20
  #
21
- # See npm-publish.yml comment block + memory/specs/release-v0.10.19-plan.md.
21
+ # See the npm-publish.yml comment block.
22
22
 
23
23
  version: 3
24
24