{
 "profile": "alltio-text-3",
 "note": "Conformance suite. An implementation is conformant when every vector's canonical text and digest match exactly, and every error vector throws. Digests are SHA-256, lowercase hex, over the canonical text encoded UTF-8 without BOM.",
 "vectors": [
  {
   "name": "minimal",
   "why": "Baseline: one paragraph, nothing to strip.",
   "html": "<article data-alltio-body><p>Hello.</p></article>",
   "canonical": "Hello.",
   "sha256": "2d8bd7d9bb5f85ba643f0110d50cb506a1fe439e769a22503193ea6046bb87f7"
  },
  {
   "name": "scope-is-the-body-only",
   "why": "Head, nav and footer are outside the marked subtree, so they are excluded by scope rather than by any per-element rule. This is why the JSON-LD mark, analytics and the build stamp never touch the digest.",
   "html": "<html><head><title>T</title><script>var a=1</script></head><body><nav>Home Blog</nav><article data-alltio-body><p>Body.</p></article><footer>Contact</footer></body></html>",
   "canonical": "Body.",
   "sha256": "521b25cc458684eb504a7c885adccd748cf2f97429ff80a14886d0804d62c862"
  },
  {
   "name": "comments-dropped",
   "why": "A comment is not an element, so the element list in step 3 does not reach it. Dropping it is required: a commented-out block that is later uncommented must not move the digest.",
   "html": "<article data-alltio-body><p>Kept.</p><!-- <p>Not the work.</p> RECORD_ID --></article>",
   "canonical": "Kept.",
   "sha256": "38bbfc82be4e4d46e051c1d1b1c8d451616afaa56b78f110bea167be559e1c77"
  },
  {
   "name": "uncommenting-the-mark-is-a-no-op",
   "why": "The circularity case. The same work, once with the mark commented out and once with it live, must produce one digest — otherwise stamping a page invalidates the record just filed for it.",
   "html": "<article data-alltio-body><p>Work.</p><p class=\"alltio-mark\" data-alltio-mark>Registered with Alltio · ALT-XXXX-0000 · <a href=\"https://alltio.com/r/ALT-XXXX-0000\">Verify</a></p></article>",
   "canonical": "Work.",
   "sha256": "bc92a1db5e32de1f2d019ac13ccf77af7b3eda6b7508988dc489dec7f8a37a39"
  },
  {
   "name": "nested-exclusion",
   "why": "An excluded subtree is removed whole, including kept-looking descendants.",
   "html": "<article data-alltio-body><p>A</p><div data-alltio-mark><p>B</p><div data-alltio-mark><p>C</p></div><p>D</p></div><p>E</p></article>",
   "canonical": "A\nE",
   "sha256": "53ef2ed874462cb51ae803023c8ea49ca6141fc5a5c914af19bc311d6ed7f413"
  },
  {
   "name": "inline-exclusion-leaves-no-boundary",
   "why": "Removing an inline subtree joins the surrounding text with nothing between. Pinned because it is surprising and a reasonable implementer might insert a space.",
   "html": "<article data-alltio-body><p>foo<span data-alltio-mark>bar</span>baz</p></article>",
   "canonical": "foobaz",
   "sha256": "798f012674b5b8dcab4b00114bdf6738a69a4cdcf7ca0db1149260c9f81b73f7"
  },
  {
   "name": "script-style-template-noscript",
   "why": "Removed with their contents even though their text would otherwise be extracted.",
   "html": "<article data-alltio-body><p>Real.</p><script>var x='fake'</script><style>p{color:red}</style><template><p>tpl</p></template><noscript>ns</noscript></article>",
   "canonical": "Real.",
   "sha256": "80156831ad113514e96c14cd91933e16aebefa4d4892af07edfacbfaf662efd2"
  },
  {
   "name": "br-inside-paragraph",
   "why": "<br> emits a line break; the paragraph still emits its own on close.",
   "html": "<article data-alltio-body><p>One<br>Two</p><p>Three</p></article>",
   "canonical": "One\nTwo\nThree",
   "sha256": "0e103bf76de9c9b9dc7f796a282c6f7453620e2d5480aad93051de60c3b01b6e"
  },
  {
   "name": "inline-elements-contribute-nothing",
   "why": "Emphasis and links carry text but add no structure, so a rewrap into different inline markup must not change the digest.",
   "html": "<article data-alltio-body><p>A <strong>bold</strong> and <a href=\"/x\">linked</a> line.</p></article>",
   "canonical": "A bold and linked line.",
   "sha256": "623cc24ce91b92f14d32ffad29e21e9d15c03115e02d2101932ce1eb2cae5384"
  },
  {
   "name": "entity-heavy",
   "why": "Character references are decoded by the parser before extraction, then folded, so the source encoding of a character is invisible to the digest.",
   "html": "<article data-alltio-body><p>Sweden&rsquo;s BankID &mdash; &ldquo;quoted&rdquo; &amp; &lt;escaped&gt; &#8212; &#x2014;</p></article>",
   "canonical": "Sweden's BankID - \"quoted\" & <escaped> - -",
   "sha256": "72246fc898fb35129bc4956b37d382cab77e129defd0984a03b34f21101289bd"
  },
  {
   "name": "typographic-folding",
   "why": "Step 7. Platforms rewrite straight quotation marks into curly ones at publish, so the curly and straight renderings of one work must hash identically. Both paragraphs here fold to the same characters and land on one line each.",
   "html": "<article data-alltio-body><p>‘a’ “b” — c</p><p>'a' \"b\" - c</p></article>",
   "canonical": "'a' \"b\" - c\n'a' \"b\" - c",
   "sha256": "d929b9b5f9facaf64c8e7aa3ecabb7238e55f6bdd3bbd80488b1ea98343ce47b"
  },
  {
   "name": "folding-primes-ellipsis-dashes",
   "why": "Step 7's full inventory: primes fold to typewriter quotes, en and em dashes and the minus sign fold to a hyphen, and an ellipsis character becomes three periods.",
   "html": "<article data-alltio-body><p>5′10″ tall… range −3 – 4 — done</p></article>",
   "canonical": "5'10\" tall... range -3 - 4 - done",
   "sha256": "60522137db7c04c871aa8bb27ae8bf47fe65317cd8f398aa5f2a2aa96a764f46"
  },
  {
   "name": "folding-removes-invisibles",
   "why": "Step 7. Soft hyphens and zero-width characters are typographic dress a platform inserts; none of them are the work.",
   "html": "<article data-alltio-body><p>hy­phen zero​width join‍er word⁠joiner</p></article>",
   "canonical": "hyphen zerowidth joiner wordjoiner",
   "sha256": "7e47c539b0c3c08f1d0e53a4879d2b7a8d94ca0195c5efd32bbf854a392c69f9"
  },
  {
   "name": "nfc-not-nfkc",
   "why": "Decomposed and composed forms of the same character must agree (NFC), but ligatures and superscripts must survive (not NFKC).",
   "html": "<article data-alltio-body><p>café ﬁn 2²</p></article>",
   "canonical": "café ﬁn 2²",
   "sha256": "3f49d3e0650c62a290a5e9f5a3cf4228b4d0db48b96dac9c17e65fefd821970f"
  },
  {
   "name": "mixed-whitespace",
   "why": "Runs of Unicode whitespace collapse to one space within a line, lines are trimmed, blank lines vanish, and there is no trailing newline.",
   "html": "<article data-alltio-body>\n  <p>  spaced\t\tout   here  </p>\n\n  <p>\n     second\n  </p>\n</article>",
   "canonical": "spaced out here\nsecond",
   "sha256": "2406d3e3a37a3f2ee1fc33af7d7667ff146eee4107b7d3d5ef7af83bf136c44f"
  },
  {
   "name": "list-and-nesting",
   "why": "Every list item is its own line; nesting does not add blank lines because empty lines are dropped.",
   "html": "<article data-alltio-body><p>Q:</p><ul><li>One</li><li>Two<ul><li>Inner</li></ul></li></ul></article>",
   "canonical": "Q:\nOne\nTwo\nInner",
   "sha256": "d93952baa249c8e0a6f533ae7b7b6841978f4d7d167c2b88c306694b9ae1a660"
  },
  {
   "name": "figcaption-is-stripped",
   "why": "A caption is entered at publish time into the platform's caption field; it is never in the text the author registered, so retaining it would fail against nearly every published article. The image itself was never covered by a text hash. Changed from alltio-text-2, where this same input retained the caption.",
   "html": "<article data-alltio-body><figure><img src=\"/a.png\" alt=\"Alt text never hashed\"><figcaption>The caption is not either.</figcaption></figure><p>The prose is the work.</p></article>",
   "canonical": "The prose is the work.",
   "sha256": "b33c0be9b855de3f39f7ec0e428ee3a8421aa66c18070585ec8dc32c7b5f56a8"
  },
  {
   "name": "whitespace-only-body",
   "why": "A body with no text is empty, not an error. Registering it is a caller decision.",
   "html": "<article data-alltio-body>   \n  <script>x</script>  </article>",
   "canonical": "",
   "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
  },
  {
   "name": "error-no-body",
   "why": "Fail closed rather than falling back to <article>.",
   "html": "<article><p>No marker.</p></article>",
   "throws": "expected exactly one [data-alltio-body], found 0"
  },
  {
   "name": "error-two-bodies",
   "why": "Fail closed rather than concatenating or picking the first.",
   "html": "<article data-alltio-body><p>A</p></article><article data-alltio-body><p>B</p></article>",
   "throws": "expected exactly one [data-alltio-body], found 2"
  },
  {
   "name": "standfirst-inside-body-is-the-work",
   "why": "Ruling of 23 September 2026: the canonical text is the standfirst plus the body prose, as one block. A title is separable on every platform; a standfirst is not, and on most templates it is simply the first paragraph, so excluding it would produce a mismatch everywhere a dek renders as body text.",
   "html": "<article data-alltio-body><h3 class=\"dek\">For three thousand years, an idea came with a source.</h3><p>Now the answer arrives without the source.</p></article>",
   "canonical": "For three thousand years, an idea came with a source.\nNow the answer arrives without the source.",
   "sha256": "b6f03a7a9f5f8a3c858c467d18bb7d61e44abc07d9caef5ecca4b3203fef909d"
  },
  {
   "name": "standfirst-element-kind-is-irrelevant",
   "why": "Same words as the previous vector with the standfirst as a div instead of an h3: identical canonical text and digest. What decides inclusion is whether the element sits inside the marked body, not what kind of element it is.",
   "html": "<article data-alltio-body><div class=\"dek\">For three thousand years, an idea came with a source.</div><p>Now the answer arrives without the source.</p></article>",
   "canonical": "For three thousand years, an idea came with a source.\nNow the answer arrives without the source.",
   "sha256": "b6f03a7a9f5f8a3c858c467d18bb7d61e44abc07d9caef5ecca4b3203fef909d"
  },
  {
   "name": "unmarked-page-is-never-guessed",
   "why": "The two body-detection modes: on a page carrying data-alltio-body the declared subtree is the work, exactly one or error; on a page with no marker this module refuses rather than guessing. The article-body heuristic for unmarked pages belongs to the capture widget, runs only there, and its result must be reported as heuristic evidence, never silently mixed with declared evidence.",
   "html": "<article><p>No declaration anywhere on this page.</p></article>",
   "throws": "expected exactly one [data-alltio-body], found 0"
  }
 ],
 "textVectors": [
  {
   "name": "text-prose-blank-line-paragraphs",
   "why": "A prose file separates paragraphs with blank lines; the HTML pipeline emits one line per block element. Both must land on the same canonical form or a file registration never matches its published page.",
   "text": "First paragraph.\n\nSecond paragraph.\n",
   "canonical": "First paragraph.\nSecond paragraph.",
   "sha256": "31cf545a878cf251517e18064d04daa77a847d9f40e2af899deeb419168d9c30"
  },
  {
   "name": "text-matches-html-pipeline",
   "why": "The standing acceptance test at unit level: this text digest must equal the 'minimal' HTML vector's digest for the same prose.",
   "text": "Hello.",
   "canonical": "Hello.",
   "sha256": "2d8bd7d9bb5f85ba643f0110d50cb506a1fe439e769a22503193ea6046bb87f7"
  },
  {
   "name": "text-messy-editor-whitespace",
   "why": "Editor buffers carry trailing spaces, tabs, NBSP, and CRLF. All of it is incidental; none of it is the work.",
   "text": "Line one. \t\r\nLine two.\r\n\r\n   Line three.   ",
   "canonical": "Line one.\nLine two.\nLine three.",
   "sha256": "cced344019bb360eb0b7a5471e7174f1aa94de6a074748b674c7956a92a08955"
  },
  {
   "name": "text-nfc",
   "why": "Step 6 applies in text mode too: decomposed e + combining acute normalizes to the precomposed form.",
   "text": "café",
   "canonical": "café",
   "sha256": "850f7dc43910ff890f8879c0ed26fe697c93a067ad93a7d50f466a7028a9bf4e"
  },
  {
   "name": "text-curly-equals-straight",
   "why": "The acceptance test's folding case at unit level: the file holds straight quotes, the platform published curly ones, and both must produce one digest.",
   "text": "She said “don’t” — twice…",
   "canonical": "She said \"don't\" - twice...",
   "sha256": "39e86e32379eb382d23a287890dcaf32dac2e14aa255d7bcd4dee2368f580fe0"
  },
  {
   "name": "text-empty",
   "why": "Empty text is empty, not an error, mirroring whitespace-only-body.",
   "text": "   \n\t\n",
   "canonical": "",
   "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
  }
 ]
}