@redsift/products 12.5.12-alpha.2 → 12.5.12-alpha.3
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.
|
@@ -1291,6 +1291,7 @@ var enUS = {
|
|
|
1291
1291
|
"investigate.extracted-email-short": "Extracted from your email",
|
|
1292
1292
|
"investigate.dkim-evaluation": "DKIM Evaluation",
|
|
1293
1293
|
"investigate.fcrdns-evaluation": "FCrDNS Evaluation",
|
|
1294
|
+
"investigate.card.bimi.glossary-link": "What does this mean?",
|
|
1294
1295
|
"investigate.card.spf-passed_md": "**[](SPF#:glossary) authentication passed.**",
|
|
1295
1296
|
"investigate.card.spf-failed_md": "**[](SPF#:glossary) authentication failed.**",
|
|
1296
1297
|
"investigate.card.dmarc.error-raw_md": [
|
|
@@ -2008,6 +2009,7 @@ var frFR = {
|
|
|
2008
2009
|
"investigate.summary.dnssec": "DNSSEC",
|
|
2009
2010
|
"investigate.summary.dns-lookups": "{count} Recherches DNS",
|
|
2010
2011
|
"investigate.summary.void-lookups": "{count} Recherches vides",
|
|
2012
|
+
"investigate.card.bimi.glossary-link": "Qu’est-ce que cela signifie ?",
|
|
2011
2013
|
"investigate.card.spf-passed_md": "**[](SPF#:glossary) authentification réussie.**",
|
|
2012
2014
|
"investigate.card.spf-failed_md": "**[](SPF#:glossary) authentification échouée.**",
|
|
2013
2015
|
"investigate.card.dmarc.error-raw_md": [
|
|
@@ -7472,9 +7474,273 @@ function renderDisplayValue(value) {
|
|
|
7472
7474
|
return value;
|
|
7473
7475
|
}
|
|
7474
7476
|
|
|
7477
|
+
/**
|
|
7478
|
+
* BIMI code → glossary page map.
|
|
7479
|
+
*
|
|
7480
|
+
* Each BIMI finding carries a `checker_codes` schema id from `redsift/event-schemas`
|
|
7481
|
+
* (e.g. `DNS-BIMI-0020`). The glossary publishes one explainer page per code. This
|
|
7482
|
+
* module turns a code into the URL of its page so a card row can offer a
|
|
7483
|
+
* "What does this mean?" link.
|
|
7484
|
+
*
|
|
7485
|
+
* ## Why the map lives here
|
|
7486
|
+
*
|
|
7487
|
+
* Decision D4a of the glossary design: the website deliberately publishes no
|
|
7488
|
+
* code → slug manifest and performs no code → URL resolution. Its side of the
|
|
7489
|
+
* contract is that these slugs are permanent; if one ever has to change, the
|
|
7490
|
+
* website owes a redirect and an updated map here.
|
|
7491
|
+
*
|
|
7492
|
+
* ## Keyed on the code, never the slug or the message text
|
|
7493
|
+
*
|
|
7494
|
+
* The code is the only stable identifier. Titles, severities and statuses drift
|
|
7495
|
+
* (0033 changed under WEB-1155; 0021's grammar changed) and slugs are human text,
|
|
7496
|
+
* so they are the most likely thing to be re-worded later. Row message text is
|
|
7497
|
+
* worse still — it is backend prose that `formatMessageWithUrls()` rewrites before
|
|
7498
|
+
* it reaches the card, and it is not in any translation catalogue.
|
|
7499
|
+
*
|
|
7500
|
+
* ## Unmapped codes resolve to nothing, by design
|
|
7501
|
+
*
|
|
7502
|
+
* `resolveBimiGlossaryHref` returns `undefined` rather than guessing a URL. This is
|
|
7503
|
+
* an everyday path, not an edge case:
|
|
7504
|
+
* - `DNS-BIMI-0003` has no glossary page — the authored set jumps 0002 → 0004.
|
|
7505
|
+
* - `msgCode` is a free-form string upstream; real payloads carry non-schema
|
|
7506
|
+
* values such as `multiple-records`, `config-error` and `DNS-BIMI-0000-MISMATCH`.
|
|
7507
|
+
* - IPForensics currently mis-assigns some codes (`bimi-error-invalid-record` is
|
|
7508
|
+
* tagged 0021, which is the "both l and a tags missed" page). A permissive
|
|
7509
|
+
* resolver would send a reader to a page describing a *different* error, which
|
|
7510
|
+
* is worse than offering no link at all.
|
|
7511
|
+
*
|
|
7512
|
+
* @see https://redsift.atlassian.net/browse/DS-113
|
|
7513
|
+
*/
|
|
7514
|
+
|
|
7515
|
+
/** Production origin for glossary pages, used when the card renders outside the website. */
|
|
7516
|
+
const GLOSSARY_ORIGIN = 'https://redsift.com';
|
|
7517
|
+
|
|
7518
|
+
/** Site-relative path prefix for glossary pages. */
|
|
7519
|
+
const GLOSSARY_PATH = '/glossary';
|
|
7520
|
+
|
|
7521
|
+
/**
|
|
7522
|
+
* Code → glossary slug. Read from the published Prismic API on 2026-08-18.
|
|
7523
|
+
*
|
|
7524
|
+
* 36 entries. Regenerate from the content API rather than editing by hand, and
|
|
7525
|
+
* note the two known coverage gaps:
|
|
7526
|
+
* - 0003 is absent (no authored page).
|
|
7527
|
+
* - 0023–0032 are authored and live but the BIMI Checker does not emit them yet,
|
|
7528
|
+
* so they only appear on cards fed directly from the schema feed.
|
|
7529
|
+
*/
|
|
7530
|
+
const BIMI_CODE_TO_GLOSSARY_SLUG = Object.freeze(Object.assign(Object.create(null), {
|
|
7531
|
+
'DNS-BIMI-0001': 'logo-url-scheme-is-not-https-missing-vmc-in-record',
|
|
7532
|
+
'DNS-BIMI-0002': 'no-vmc-in-record',
|
|
7533
|
+
// 0003 intentionally absent — no glossary page authored.
|
|
7534
|
+
'DNS-BIMI-0004': 'missing-l-tag',
|
|
7535
|
+
'DNS-BIMI-0005': 'failed-to-fetch-logo',
|
|
7536
|
+
'DNS-BIMI-0006': 'no-version-in-record',
|
|
7537
|
+
'DNS-BIMI-0007': 'failed-to-fetch-authority-certificate',
|
|
7538
|
+
'DNS-BIMI-0008': 'no-record',
|
|
7539
|
+
'DNS-BIMI-0009': 'invalid-record',
|
|
7540
|
+
'DNS-BIMI-0010': 'valid-record-but-dmarc-isn-t-ready',
|
|
7541
|
+
'DNS-BIMI-0011': 'vmc-validation-errors',
|
|
7542
|
+
'DNS-BIMI-0012': 'certificate-can-t-be-accessed-automatically',
|
|
7543
|
+
'DNS-BIMI-0013': 'declination-to-publish',
|
|
7544
|
+
'DNS-BIMI-0014': 'certificate-not-valid-for-the-domain',
|
|
7545
|
+
'DNS-BIMI-0015': 'bimi-record-validation-errors',
|
|
7546
|
+
'DNS-BIMI-0016': 'authority-url-scheme-is-not-https',
|
|
7547
|
+
'DNS-BIMI-0017': 'logo-validation-errors',
|
|
7548
|
+
'DNS-BIMI-0018': 'bimi-record-found-on-organizational-domain',
|
|
7549
|
+
'DNS-BIMI-0019': 'multiple-bimi-records-found-on-target-domain',
|
|
7550
|
+
'DNS-BIMI-0020': 'duplicate-tag-detected-in-bimi-record',
|
|
7551
|
+
'DNS-BIMI-0021': 'both-l-and-a-tags-missed',
|
|
7552
|
+
'DNS-BIMI-0022': 'no-bimi-record-found-and-dmarc-policy-is-insufficient',
|
|
7553
|
+
'DNS-BIMI-0023': 'svg-logo-dimensions-use-non-pixel-values',
|
|
7554
|
+
'DNS-BIMI-0024': 'svg-logo-dimensions-below-96x96-pixel-minimum',
|
|
7555
|
+
'DNS-BIMI-0025': 'svg-logo-missing-width-or-height-attributes',
|
|
7556
|
+
'DNS-BIMI-0026': 'vmc-pem-starts-with-utf-8-bom',
|
|
7557
|
+
'DNS-BIMI-0027': 'vmc-pem-bundle-contains-only-the-leaf-certificate',
|
|
7558
|
+
'DNS-BIMI-0028': 'vmc-certificate-chain-signature-verification-failed',
|
|
7559
|
+
'DNS-BIMI-0029': 'vmc-certificate-chain-does-not-terminate-at-a-trusted-bimi-root-ca',
|
|
7560
|
+
'DNS-BIMI-0030': 'vmc-certificate-chain-ordering-does-not-match-expected-issuance-order',
|
|
7561
|
+
'DNS-BIMI-0031': 'vmc-pem-bundle-omits-the-root-ca-certificate',
|
|
7562
|
+
'DNS-BIMI-0032': 'non-https-url-encountered-while-fetching-the-vmc-certificate',
|
|
7563
|
+
'DNS-BIMI-0033': 'vmc-leaf-certificate-selection-is-ambiguous',
|
|
7564
|
+
'DNS-BIMI-0034': 'timed-out-fetching-the-bimi-asset',
|
|
7565
|
+
'DNS-BIMI-0035': 'bimi-asset-host-could-not-be-resolved',
|
|
7566
|
+
'DNS-BIMI-0036': 'connection-to-the-bimi-asset-host-failed',
|
|
7567
|
+
'DNS-BIMI-0037': 'tls-handshake-failed-fetching-the-bimi-asset'
|
|
7568
|
+
}));
|
|
7569
|
+
|
|
7570
|
+
/** Options for {@link resolveBimiGlossaryHref}. */
|
|
7571
|
+
|
|
7572
|
+
/**
|
|
7573
|
+
* Resolve a BIMI schema code to its glossary page URL.
|
|
7574
|
+
*
|
|
7575
|
+
* @param code - A `checker_codes` schema id, e.g. `'DNS-BIMI-0020'`. Anything not
|
|
7576
|
+
* in {@link BIMI_CODE_TO_GLOSSARY_SLUG} resolves to `undefined`.
|
|
7577
|
+
* @returns The page URL, or `undefined` when the code has no glossary page.
|
|
7578
|
+
*
|
|
7579
|
+
* @example
|
|
7580
|
+
* resolveBimiGlossaryHref('DNS-BIMI-0008');
|
|
7581
|
+
* // 'https://redsift.com/glossary/no-record'
|
|
7582
|
+
* resolveBimiGlossaryHref('DNS-BIMI-0008', { relative: true });
|
|
7583
|
+
* // '/glossary/no-record'
|
|
7584
|
+
* resolveBimiGlossaryHref('DNS-BIMI-0003'); // undefined — no page authored
|
|
7585
|
+
* resolveBimiGlossaryHref('config-error'); // undefined — not a schema code
|
|
7586
|
+
*/
|
|
7587
|
+
function resolveBimiGlossaryHref(code) {
|
|
7588
|
+
let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
7589
|
+
if (!code) return undefined;
|
|
7590
|
+
|
|
7591
|
+
// Own-property check, not just a truthiness test. `code` originates from `msgCode`,
|
|
7592
|
+
// which is free-form backend data, so a key like `__proto__` or `constructor` would
|
|
7593
|
+
// otherwise resolve to an inherited value — truthy, and stringified into a URL such
|
|
7594
|
+
// as `/glossary/[object Object]`. Both maps are null-prototyped as well; this is the
|
|
7595
|
+
// second layer, so reverting either one alone cannot reintroduce the hole.
|
|
7596
|
+
// Raised in review on PR #1172.
|
|
7597
|
+
if (!Object.prototype.hasOwnProperty.call(BIMI_CODE_TO_GLOSSARY_SLUG, code)) return undefined;
|
|
7598
|
+
const slug = BIMI_CODE_TO_GLOSSARY_SLUG[code];
|
|
7599
|
+
if (typeof slug !== 'string' || !slug) return undefined;
|
|
7600
|
+
const path = `${GLOSSARY_PATH}/${slug}`;
|
|
7601
|
+
return options.relative ? path : `${GLOSSARY_ORIGIN}${path}`;
|
|
7602
|
+
}
|
|
7603
|
+
|
|
7604
|
+
/**
|
|
7605
|
+
* signal-logic translation key → BIMI schema code, for rows that carry no `code`.
|
|
7606
|
+
*
|
|
7607
|
+
* The `bimiRecordStatus` switch in signal-logic builds its rows from a fixed
|
|
7608
|
+
* translation key and never attaches a code, so those rows cannot be linked from
|
|
7609
|
+
* `InfoItem.code` alone (see WEB-1239). The key, however, *is* deterministic: it is
|
|
7610
|
+
* a literal in signal-logic's source, not backend prose, so mapping key → code is
|
|
7611
|
+
* stable in a way that matching the rendered message text is not.
|
|
7612
|
+
*
|
|
7613
|
+
* Covers two families of key:
|
|
7614
|
+
*
|
|
7615
|
+
* 1. The `bimiRecordStatus` switch rows (`record-missing_md` and friends).
|
|
7616
|
+
* 2. The per-error `recordValidationErrors` rows, keyed `card.bimi.errors.<errorCode>`.
|
|
7617
|
+
* These are the individual "Record l value needs to be a valid URL" lines listed
|
|
7618
|
+
* under an invalid record. They matter because they are the rows that actually tell
|
|
7619
|
+
* a reader *what* to fix, and the schema code that IPForensics assigned them is
|
|
7620
|
+
* destroyed before the card sees it: the website's `RecordMessageCodes` rewrites
|
|
7621
|
+
* `DNS-BIMI-00XX` into these `bimi-error-*` names, collapsing 0009/0015/0020/0021
|
|
7622
|
+
* all onto `bimi-error-invalid-record`. Mapping the name back is therefore the only
|
|
7623
|
+
* way to link these rows, and it is safe only where the relation is 1:1.
|
|
7624
|
+
*
|
|
7625
|
+
* Deliberately absent:
|
|
7626
|
+
* - `record-failed_md` — `NOT_VMC`, `WITH_ERRORS` and `CERT_INVALID` all collapse
|
|
7627
|
+
* to this one row, so it is many-to-one and cannot name a single page.
|
|
7628
|
+
* - `record-passed_md` / `record-declination_md` are pass/info states; 0013
|
|
7629
|
+
* (declination) is emitted separately as a coded report row.
|
|
7630
|
+
* - `record-unknown-error_md` has no page.
|
|
7631
|
+
* - `errors.bimi-error-unsupported-values` has no authored glossary page.
|
|
7632
|
+
* - `errors.bimi-error-invalid-record` — the website collapses 0009, 0015, 0020 and
|
|
7633
|
+
* 0021 onto this one name, and those pages describe different errors: 0009 is a
|
|
7634
|
+
* record that cannot be parsed at all, while 0020 (duplicate tag) and 0021 (both
|
|
7635
|
+
* l= and a= missing) are parseable records. Mapping it to 0009 sent a reader
|
|
7636
|
+
* whose actual error was 0020 or 0021 to a page describing a parse failure that
|
|
7637
|
+
* did not happen. Raised in review on PR #1172.
|
|
7638
|
+
* - Most `logoErrors.invalid-svg-*` rows. These describe *which* SVG rule the logo
|
|
7639
|
+
* broke (bad root element, disallowed `script` element, wrong profile, …) and the
|
|
7640
|
+
* glossary has no page per rule — only the umbrella 0017 `logo-validation-errors`.
|
|
7641
|
+
* Pointing 16 distinct failures at one generic page would tell a reader nothing
|
|
7642
|
+
* they cannot already see on the row. `invalid-svg-missing-title` is the exception:
|
|
7643
|
+
* it appears on cards whose only other SVG row is the 0017 summary, so without it
|
|
7644
|
+
* an all-SVG failure card can have no link at all.
|
|
7645
|
+
* - `logoErrors.logo-extract-error` — the website collapses 0034/0035/0036/0037
|
|
7646
|
+
* (timeout, host unresolved, connection failed, TLS handshake failed) all onto it,
|
|
7647
|
+
* and each of those has its own distinct page, so it cannot name one.
|
|
7648
|
+
* - `pemErrors.cert-not-found` — lossy: the website maps 0002+0007 onto it, and
|
|
7649
|
+
* 0034–0037 land there too.
|
|
7650
|
+
*/
|
|
7651
|
+
const BIMI_TRANSLATION_KEY_TO_CODE = Object.freeze(Object.assign(Object.create(null), {
|
|
7652
|
+
// bimiRecordStatus switch rows
|
|
7653
|
+
'card.bimi.record-missing_md': 'DNS-BIMI-0008',
|
|
7654
|
+
// no-record
|
|
7655
|
+
'card.bimi.record-invalid_md': 'DNS-BIMI-0009',
|
|
7656
|
+
// invalid-record
|
|
7657
|
+
'card.bimi.record-no-cert_md': 'DNS-BIMI-0007',
|
|
7658
|
+
// failed-to-fetch-authority-certificate
|
|
7659
|
+
// The row's own "[here]" help link is stripped by the adapter, so the glossary page
|
|
7660
|
+
// is the single destination rather than competing with it.
|
|
7661
|
+
'card.bimi.cert-forbidden_md': 'DNS-BIMI-0012',
|
|
7662
|
+
// certificate-can-t-be-accessed-automatically
|
|
7663
|
+
'card.bimi.could-not-fetch_md': 'DNS-BIMI-0005',
|
|
7664
|
+
// failed-to-fetch-logo
|
|
7665
|
+
// Per-error recordValidationErrors rows
|
|
7666
|
+
'card.bimi.errors.bimi-error-missing-l': 'DNS-BIMI-0004',
|
|
7667
|
+
// missing-l-tag
|
|
7668
|
+
'card.bimi.errors.bimi-error-missing-a': 'DNS-BIMI-0002',
|
|
7669
|
+
// no-vmc-in-record
|
|
7670
|
+
'card.bimi.errors.bimi-error-missing-v': 'DNS-BIMI-0006',
|
|
7671
|
+
// no-version-in-record
|
|
7672
|
+
'card.bimi.errors.bimi-error-invalid-l': 'DNS-BIMI-0001',
|
|
7673
|
+
// logo-url-scheme-is-not-https…
|
|
7674
|
+
'card.bimi.errors.bimi-error-invalid-a': 'DNS-BIMI-0016',
|
|
7675
|
+
// authority-url-scheme-is-not-https
|
|
7676
|
+
'card.bimi.errors.bimi-error-cert-not-valid': 'DNS-BIMI-0014',
|
|
7677
|
+
// certificate-not-valid-for-the-domain
|
|
7678
|
+
'card.bimi.errors.bimi-error-incorrect-v': 'DNS-BIMI-0006',
|
|
7679
|
+
// no-version-in-record — v= present but wrong
|
|
7680
|
+
// No page describes an unspecified error, but the row only ever appears under record
|
|
7681
|
+
// validation, and 0015 is that section's umbrella page — better than nothing for a
|
|
7682
|
+
// reader who cannot otherwise tell what to do next.
|
|
7683
|
+
'card.bimi.errors.bimi-error-unknown': 'DNS-BIMI-0015',
|
|
7684
|
+
// bimi-record-validation-errors
|
|
7685
|
+
// Safe SVG section
|
|
7686
|
+
'card.bimi.svg-missing-title_md': 'DNS-BIMI-0004',
|
|
7687
|
+
// missing-l-tag — no logo published on the record
|
|
7688
|
+
'card.bimi.record-svg-added-from-itag-invalid_md': 'DNS-BIMI-0017',
|
|
7689
|
+
// logo-validation-errors
|
|
7690
|
+
// VMC/CMC certificate rows
|
|
7691
|
+
'card.bimi.vmc-invalid-title_md': 'DNS-BIMI-0011',
|
|
7692
|
+
// vmc-validation-errors
|
|
7693
|
+
'card.bimi.vmc-not-vmc-title_md': 'DNS-BIMI-0011',
|
|
7694
|
+
// vmc-validation-errors — not a VMC/CMC at all
|
|
7695
|
+
'card.bimi.vmc.cert-ambiguous-leaf_md': 'DNS-BIMI-0033',
|
|
7696
|
+
// vmc-leaf-certificate-selection-is-ambiguous
|
|
7697
|
+
'card.bimi.logoErrors.invalid-svg-missing-title_md': 'DNS-BIMI-0017',
|
|
7698
|
+
// logo-validation-errors
|
|
7699
|
+
// signal-logic's catch-all for an svgErrors string it has no translation for. Those
|
|
7700
|
+
// strings are raw fetch/parse failures ("can't fetch SVG from ...: http status not
|
|
7701
|
+
// OK"), which IPForensics tags extractError = DNS-BIMI-0005, so failed-to-fetch-logo
|
|
7702
|
+
// is the page. Without this the row renders with no link at all, since the fallback
|
|
7703
|
+
// branch attaches neither a code nor a per-error key.
|
|
7704
|
+
'card.bimi.svg-error-fallback_md': 'DNS-BIMI-0005',
|
|
7705
|
+
// failed-to-fetch-logo
|
|
7706
|
+
// IPForensics tags the l=/certificate logo mismatch 'DNS-BIMI-0000-MISMATCH', which is
|
|
7707
|
+
// not a schema code and matches no page, so the code on the item cannot resolve.
|
|
7708
|
+
// The umbrella 0017 page covers this class, and the sibling "image with errors" row
|
|
7709
|
+
// on the same card already points there.
|
|
7710
|
+
'card.bimi.logoErrors.logo-and-evidence-mismatch_md': 'DNS-BIMI-0017',
|
|
7711
|
+
// logo-validation-errors
|
|
7712
|
+
// VMC/PEM error rows that map 1:1
|
|
7713
|
+
'card.bimi.pemErrors.cert-not-https_md': 'DNS-BIMI-0016',
|
|
7714
|
+
// authority-url-scheme-is-not-https
|
|
7715
|
+
'card.bimi.pemErrors.cert-forbidden_md': 'DNS-BIMI-0012',
|
|
7716
|
+
// certificate-can-t-be-accessed-automatically
|
|
7717
|
+
// Matches the `bimi-error-cert-not-valid` row, which resolves to the same page.
|
|
7718
|
+
// Leaving this unlinked made identical "Certificate is not valid" text linked under
|
|
7719
|
+
// BIMI record status but not under VMC/CMC Certificate on the same card.
|
|
7720
|
+
'card.bimi.pemErrors.cert-not-valid_md': 'DNS-BIMI-0014',
|
|
7721
|
+
// certificate-not-valid-for-the-domain
|
|
7722
|
+
'card.bimi.pemErrors.cert-bad-signature_md': 'DNS-BIMI-0028' // vmc-certificate-chain-signature-verification-failed
|
|
7723
|
+
}));
|
|
7724
|
+
|
|
7725
|
+
/**
|
|
7726
|
+
* Resolve the BIMI schema code for a signal-logic translation key, for rows that
|
|
7727
|
+
* signal-logic derives from status alone and therefore emits without a `code`.
|
|
7728
|
+
*
|
|
7729
|
+
* @returns The schema code, or `undefined` when the key maps to no single code.
|
|
7730
|
+
*/
|
|
7731
|
+
function resolveBimiCodeForTranslationKey(key) {
|
|
7732
|
+
if (!key) return undefined;
|
|
7733
|
+
// Same own-property guard as resolveBimiGlossaryHref: translation keys are literals
|
|
7734
|
+
// today, but this must not become the weak link if that ever changes.
|
|
7735
|
+
if (!Object.prototype.hasOwnProperty.call(BIMI_TRANSLATION_KEY_TO_CODE, key)) return undefined;
|
|
7736
|
+
const code = BIMI_TRANSLATION_KEY_TO_CODE[key];
|
|
7737
|
+
return typeof code === 'string' && code ? code : undefined;
|
|
7738
|
+
}
|
|
7739
|
+
|
|
7475
7740
|
// =============================================================================
|
|
7476
7741
|
// BIMI TRANSLATION KEY → data-testid MAPPING
|
|
7477
7742
|
// =============================================================================
|
|
7743
|
+
|
|
7478
7744
|
/**
|
|
7479
7745
|
* Maps signal-logic BIMI translation keys to the data-testid values
|
|
7480
7746
|
* that the old DS helpers used to set on checklist items.
|
|
@@ -7558,17 +7824,106 @@ function adaptExtractedItemCategory(cat) {
|
|
|
7558
7824
|
};
|
|
7559
7825
|
}
|
|
7560
7826
|
|
|
7827
|
+
/**
|
|
7828
|
+
* Build the markdown for a row's "What does this mean?" glossary link (DS-113).
|
|
7829
|
+
*
|
|
7830
|
+
* Returns `undefined` when the row carries no code, or a code with no glossary page.
|
|
7831
|
+
* The link is emitted as markdown so the existing `parseGlossaryMarkdown` path in
|
|
7832
|
+
* Checklist renders it — that path already validates the protocol and applies
|
|
7833
|
+
* `target="_blank" rel="noopener noreferrer"`, so this adds no new render surface.
|
|
7834
|
+
*
|
|
7835
|
+
* The reader never sees the raw code: `DNS-BIMI-0024` means nothing to them.
|
|
7836
|
+
*/
|
|
7837
|
+
function buildGlossaryLinkMarkdown(code, t) {
|
|
7838
|
+
const href = resolveBimiGlossaryHref(code);
|
|
7839
|
+
if (!href) return undefined;
|
|
7840
|
+
return `[${t('card.bimi.glossary-link')}](${href})`;
|
|
7841
|
+
}
|
|
7842
|
+
|
|
7843
|
+
/**
|
|
7844
|
+
* Markdown links whose target is a "how to fix it" help article, in either of the two
|
|
7845
|
+
* hosts signal-logic links to.
|
|
7846
|
+
*
|
|
7847
|
+
* Matches the whole markdown link plus any leading space, so removing it leaves no gap.
|
|
7848
|
+
* The label is matched loosely (any text) but each href is pinned:
|
|
7849
|
+
* - a bare `community.redsift.com/s/article/<n>` — no `#fragment` branch, so a
|
|
7850
|
+
* glossary *term* link like `[BIMI Record](…/article/000001019#bimi-record)`, which
|
|
7851
|
+
* defines a term mid-sentence rather than offering a fix, is left intact.
|
|
7852
|
+
* - a `knowledge.ondmarc.redsift.com/en/articles/<slug>` — the "[here]" link on the
|
|
7853
|
+
* anti-bot row.
|
|
7854
|
+
*/
|
|
7855
|
+
const HELP_ARTICLE_LINK = /\s*\[[^\]]*\]\((?:https:\/\/community\.redsift\.com\/s\/article\/\d+|https:\/\/knowledge\.ondmarc\.redsift\.com\/en\/articles\/[^)]+)\)/g;
|
|
7856
|
+
|
|
7857
|
+
/**
|
|
7858
|
+
* A trailing clause left behind once a help-article link is removed.
|
|
7859
|
+
*
|
|
7860
|
+
* Deliberately **locale-agnostic**: it cuts everything after the last sentence-ending
|
|
7861
|
+
* punctuation rather than matching known lead-in wording. Listing English phrases
|
|
7862
|
+
* would leave every other locale broken — `fr-FR` renders the anti-bot row as
|
|
7863
|
+
* "…anti-bot. En savoir plus et comment résoudre le problème [ici](…)", so an
|
|
7864
|
+
* English-only pattern strips the link and leaves the French clause dangling in front
|
|
7865
|
+
* of the glossary link.
|
|
7866
|
+
*
|
|
7867
|
+
* Requires a preceding sentence to keep: a row that is *only* a lead-in has nothing
|
|
7868
|
+
* to fall back to, so the guard in `stripHelpArticleLink` leaves it alone.
|
|
7869
|
+
*/
|
|
7870
|
+
const TRAILING_CLAUSE = /([.!?](?:\*{1,2}|_{1,2}|`)?)[^.!?]*$/;
|
|
7871
|
+
|
|
7872
|
+
/**
|
|
7873
|
+
* Drop a row's help-article link when a glossary link replaces it.
|
|
7874
|
+
*
|
|
7875
|
+
* Both answer the same question, and the glossary page is the canonical explainer —
|
|
7876
|
+
* it covers what the error means *and* how to fix it — so showing both leaves the
|
|
7877
|
+
* reader choosing between two competing links on one line.
|
|
7878
|
+
*
|
|
7879
|
+
* A clause left stranded by the removal goes with it, in any locale. When the row has
|
|
7880
|
+
* no earlier sentence to fall back on, the text is left as-is minus the link rather
|
|
7881
|
+
* than risk emptying the row.
|
|
7882
|
+
*/
|
|
7883
|
+
function stripHelpArticleLink(text) {
|
|
7884
|
+
const withoutLink = text.replace(HELP_ARTICLE_LINK, '');
|
|
7885
|
+
if (withoutLink === text) return text;
|
|
7886
|
+
const trimmedEnd = withoutLink.trimEnd();
|
|
7887
|
+
// Nothing after the final sentence break means nothing dangled — keep as-is.
|
|
7888
|
+
if (/[.!?]\s*$/.test(trimmedEnd)) return trimmedEnd;
|
|
7889
|
+
const cut = trimmedEnd.replace(TRAILING_CLAUSE, '$1');
|
|
7890
|
+
return cut === trimmedEnd ? trimmedEnd : cut;
|
|
7891
|
+
}
|
|
7892
|
+
|
|
7561
7893
|
/**
|
|
7562
7894
|
* Convert an InfoItem (DisplayValue text) → ChecklistItem-compatible shape.
|
|
7895
|
+
*
|
|
7896
|
+
* When the item carries a `code` (a `checker_codes` schema id forwarded by
|
|
7897
|
+
* signal-logic under WEB-1239) and that code has a glossary page, a
|
|
7898
|
+
* "What does this mean?" link is appended to the end of the row's own text, so it
|
|
7899
|
+
* reads inline rather than as a stacked second line — a card listing several
|
|
7900
|
+
* validation errors would otherwise double in height.
|
|
7901
|
+
*
|
|
7902
|
+
* The link can only be appended inline when the row text is a plain string. A
|
|
7903
|
+
* StructuredData row (charts, status lists, code blocks) has no meaningful "end of
|
|
7904
|
+
* the line" to append to, so those fall back to a subtext beneath the row.
|
|
7905
|
+
*
|
|
7906
|
+
* Where the row already carried a help-article link ("Learn how to fix it", or the
|
|
7907
|
+
* anti-bot row's "[here]"), that link is removed: the glossary page supersedes it, and
|
|
7908
|
+
* two links answering the same question on one line makes the reader choose between
|
|
7909
|
+
* them.
|
|
7563
7910
|
*/
|
|
7564
|
-
function adaptInfoItem(item, testId) {
|
|
7911
|
+
function adaptInfoItem(item, testId, t, fallbackCode) {
|
|
7912
|
+
var _item$code;
|
|
7913
|
+
const existing = item.subtexts ? item.subtexts.map(s => ({
|
|
7914
|
+
text: renderDisplayValue(s.text)
|
|
7915
|
+
})) : undefined;
|
|
7916
|
+
// A code carried on the item always wins; the fallback only fills in for rows
|
|
7917
|
+
// signal-logic derives from status alone (DS-113).
|
|
7918
|
+
const link = t ? buildGlossaryLinkMarkdown((_item$code = item.code) !== null && _item$code !== void 0 ? _item$code : fallbackCode, t) : undefined;
|
|
7919
|
+
const inlineable = link !== undefined && typeof item.text === 'string';
|
|
7565
7920
|
return _objectSpread2({
|
|
7566
7921
|
type: item.type,
|
|
7567
7922
|
theme: item.theme,
|
|
7568
|
-
text: renderDisplayValue(item.text),
|
|
7569
|
-
subtexts:
|
|
7570
|
-
text: renderDisplayValue(
|
|
7571
|
-
}
|
|
7923
|
+
text: renderDisplayValue(inlineable ? `${stripHelpArticleLink(item.text)} ${link}` : item.text),
|
|
7924
|
+
subtexts: link !== undefined && !inlineable ? [...(existing !== null && existing !== void 0 ? existing : []), {
|
|
7925
|
+
text: renderDisplayValue(link)
|
|
7926
|
+
}] : existing
|
|
7572
7927
|
}, testId ? {
|
|
7573
7928
|
'data-testid': testId
|
|
7574
7929
|
} : undefined);
|
|
@@ -7738,12 +8093,14 @@ function adaptBimiExtractor() {
|
|
|
7738
8093
|
const [props, t, context] = args;
|
|
7739
8094
|
|
|
7740
8095
|
// Wrap t to track which translation keys map to which returned strings.
|
|
7741
|
-
// This lets us stamp data-testid on items by matching the translated text back to its key
|
|
8096
|
+
// This lets us stamp data-testid on items by matching the translated text back to its key,
|
|
8097
|
+
// and (DS-113) recover the BIMI schema code for status-derived rows that signal-logic
|
|
8098
|
+
// emits without an `InfoItem.code`.
|
|
7742
8099
|
const translatedValueToKey = new Map();
|
|
7743
8100
|
const trackingT = (key, params) => {
|
|
7744
8101
|
const value = t(key, params);
|
|
7745
|
-
//
|
|
7746
|
-
if (BIMI_KEY_TO_TESTID[key]) {
|
|
8102
|
+
// Track keys that carry a testId mapping or a glossary code
|
|
8103
|
+
if (BIMI_KEY_TO_TESTID[key] || resolveBimiCodeForTranslationKey(key)) {
|
|
7747
8104
|
translatedValueToKey.set(value, key);
|
|
7748
8105
|
}
|
|
7749
8106
|
return value;
|
|
@@ -7784,11 +8141,49 @@ function adaptBimiExtractor() {
|
|
|
7784
8141
|
}
|
|
7785
8142
|
return undefined;
|
|
7786
8143
|
};
|
|
8144
|
+
|
|
8145
|
+
// Recover the schema code for a status-derived row (DS-113). signal-logic attaches
|
|
8146
|
+
// `code` only to rows built from a coded error; the `bimiRecordStatus`, validation
|
|
8147
|
+
// error, Safe SVG and VMC-title rows carry none, so fall back to the translation key
|
|
8148
|
+
// that produced the text.
|
|
8149
|
+
//
|
|
8150
|
+
// Mirrors resolveTestId's reverse CMC→VMC pass: signal-logic post-processes several
|
|
8151
|
+
// of these strings with localizeAssertionMarkdown() after t() returns, so the text on
|
|
8152
|
+
// the item no longer matches what the tracking wrapper recorded.
|
|
8153
|
+
const resolveFallbackCode = text => {
|
|
8154
|
+
const direct = resolveBimiCodeForTranslationKey(translatedValueToKey.get(text));
|
|
8155
|
+
if (direct) return direct;
|
|
8156
|
+
const vmcText = text.replace(/CMC/g, 'VMC');
|
|
8157
|
+
if (vmcText !== text) {
|
|
8158
|
+
const viaVmc = resolveBimiCodeForTranslationKey(translatedValueToKey.get(vmcText));
|
|
8159
|
+
if (viaVmc) return viaVmc;
|
|
8160
|
+
}
|
|
8161
|
+
return undefined;
|
|
8162
|
+
};
|
|
8163
|
+
|
|
8164
|
+
// A row whose message contains a URL is not a string: signal-logic runs it through
|
|
8165
|
+
// formatMessageWithUrls(), which returns FormattedText StructuredData with the text
|
|
8166
|
+
// split into segments. Flatten those segments back to a plain string so the key
|
|
8167
|
+
// lookups above can see rows like the svgErrors fallback ("can't fetch SVG from
|
|
8168
|
+
// <url>: http status not OK"), which would otherwise never resolve a code.
|
|
8169
|
+
const plainText = value => {
|
|
8170
|
+
if (typeof value === 'string') return value;
|
|
8171
|
+
if (!value || typeof value !== 'object') return undefined;
|
|
8172
|
+
const segments = value.segments;
|
|
8173
|
+
if (!Array.isArray(segments)) return undefined;
|
|
8174
|
+
const joined = segments.map(s => {
|
|
8175
|
+
var _s$text;
|
|
8176
|
+
return (_s$text = s === null || s === void 0 ? void 0 : s.text) !== null && _s$text !== void 0 ? _s$text : '';
|
|
8177
|
+
}).join('');
|
|
8178
|
+
return joined || undefined;
|
|
8179
|
+
};
|
|
7787
8180
|
const adaptWithTestIds = categories => categories === null || categories === void 0 ? void 0 : categories.map(cat => ({
|
|
7788
8181
|
title: cat.title,
|
|
7789
8182
|
items: cat.items.map(item => {
|
|
7790
|
-
const
|
|
7791
|
-
|
|
8183
|
+
const text = plainText(item.text);
|
|
8184
|
+
const testId = text !== undefined ? resolveTestId(text) : undefined;
|
|
8185
|
+
const fallbackCode = text !== undefined ? resolveFallbackCode(text) : undefined;
|
|
8186
|
+
return adaptInfoItem(item, testId, t, fallbackCode);
|
|
7792
8187
|
})
|
|
7793
8188
|
}));
|
|
7794
8189
|
return {
|
|
@@ -8672,20 +9067,43 @@ const buildPublishedCertificateSections = (cert, pem, t, now, assertionMarkType)
|
|
|
8672
9067
|
|
|
8673
9068
|
// --- Verification issues ---
|
|
8674
9069
|
// Sourced from PEM evidence errors plus an ambiguous leaf-selection signal.
|
|
8675
|
-
|
|
9070
|
+
//
|
|
9071
|
+
// These rows are built here rather than in the extractor adapter, so the adapter's
|
|
9072
|
+
// glossary-link pass (DS-113) does not reach them. Append the link locally, keyed on
|
|
9073
|
+
// the same code the adapter uses, so a verification issue is explained exactly as the
|
|
9074
|
+
// equivalent row in the legacy VMC section is.
|
|
9075
|
+
const withGlossaryLink = (text, code) => {
|
|
9076
|
+
const href = resolveBimiGlossaryHref(code);
|
|
9077
|
+
return href ? `${text} [${t('card.bimi.glossary-link')}](${href})` : text;
|
|
9078
|
+
};
|
|
9079
|
+
|
|
9080
|
+
// pem.errors entries carry a code alongside the message (IPForensics BimiError);
|
|
9081
|
+
// keep it so the row can resolve its page rather than dropping to message-only.
|
|
9082
|
+
const errorEntries = ((_pem$errors = pem === null || pem === void 0 ? void 0 : pem.errors) !== null && _pem$errors !== void 0 ? _pem$errors : []).map(e => typeof e === 'string' ? {
|
|
9083
|
+
message: e,
|
|
9084
|
+
code: undefined
|
|
9085
|
+
} : {
|
|
9086
|
+
message: e === null || e === void 0 ? void 0 : e.message,
|
|
9087
|
+
code: e === null || e === void 0 ? void 0 : e.code
|
|
9088
|
+
}).filter(e => !!e.message);
|
|
8676
9089
|
const isAmbiguous = (pem === null || pem === void 0 ? void 0 : pem.leafSelectionMethod) === 'AMBIGUOUS';
|
|
8677
9090
|
const issueItems = [];
|
|
8678
9091
|
if (isAmbiguous) {
|
|
9092
|
+
const key = 'card.bimi.vmc.cert-ambiguous-leaf_md';
|
|
8679
9093
|
issueItems.push({
|
|
8680
9094
|
type: 'warning',
|
|
8681
|
-
text: t(
|
|
9095
|
+
text: withGlossaryLink(t(key), resolveBimiCodeForTranslationKey(key)),
|
|
8682
9096
|
'data-testid': 'bimi-cert-issue-ambiguous'
|
|
8683
9097
|
});
|
|
8684
9098
|
}
|
|
8685
|
-
|
|
9099
|
+
errorEntries.forEach((_ref2, idx) => {
|
|
9100
|
+
let {
|
|
9101
|
+
message,
|
|
9102
|
+
code
|
|
9103
|
+
} = _ref2;
|
|
8686
9104
|
issueItems.push({
|
|
8687
9105
|
type: 'warning',
|
|
8688
|
-
text: message,
|
|
9106
|
+
text: withGlossaryLink(message, code),
|
|
8689
9107
|
'data-testid': `bimi-cert-issue-${idx}`
|
|
8690
9108
|
});
|
|
8691
9109
|
});
|