Verified: 2026-08-14 · branch rebuild/v3 · against main @ 1d327b0
Method: every claim reproduced empirically — no finding accepted on inspection alone.
Test harness: Node 18.19.1, js-yaml@4.1.0, marked@15.0.6 (npm) and marked@15.0.12 (the file the
production CDN URL actually serves today), jsdom@24.1.3. The live page was reconstructed in a real
DOM — same markup as index.html, same two CDN libraries, same
js/opencodelaw.js, with fetch stubbed to read the repo from disk — and the
resulting document inspected.
| # | Claim | Verdict |
|---|---|---|
| 1 | Art. 11 Units has no content: key → marked.parse(undefined) throws → kills arts. 11–18, amendments table, back-to-top, theme restore, nav |
❌ Refuted |
| 2 | cdn.jsdelivr.net/npm/marked/marked.min.js is unversioned and the file no longer exists |
|
| 3 | navArticleId = navLink.href = '#article1' returns an absolute URL → querySelector SyntaxError → scroll-spy never works |
❌ Refuted |
| 4 | scroll listener registered before #backToTop exists |
✅ Confirmed |
| 5 | <script async> races its own CDN dependencies |
❌ Refuted |
| 6 | .gitignore contains css/style.css |
✅ Confirmed |
| 7 | act: # parses to null |
✅ Confirmed |
| 8 | archive: /archives/v1 is absolute-rooted and extensionless |
✅ Confirmed |
| 9 | archives/v2.html points at the mutable current spec, titled "STM Constitution V1" |
✅ Confirmed |
| 10 | specs/v1.yaml declares info.version: 2.0.0 |
✅ Confirmed |
| 11 | specs/opencon.yaml is a near-duplicate of v1.yaml |
✅ Confirmed |
| 12 | Montserrat format("ttf") invalid; Cuprum .woff declared format("woff2") |
✅ Confirmed |
| 13 | .vertical-nav { display: none } below 768px |
✅ Confirmed |
| 14 | Three-way naming disagreement: doc / YAML / JS | ✅ Confirmed |
10 confirmed · 3 refuted · 1 partially confirmed.
The three refuted claims are all in the audit's Part 1, "Why it is broken right now". The site is not crashing. It renders all 18 articles today. The governance findings (Part 3) — which are the serious ones — are confirmed in full and are unaffected.
Claim: article 11 has no content: key; marked.parse(undefined) throws; articles 11–18, the
amendments table, the back-to-top button, theme restore and all nav interactivity never render.
Refuted on every element.
The key exists. specs/v2.yaml:203-206 is content: | followed by a blank
line, which YAML resolves to an empty string, not undefined:
title : "Units"
has key : true
typeof : string
value : ""
marked throws only on undefined/null. On "" it returns "":
undefined -> THREW: marked(): input parameter is undefined or null
empty string -> OK, returned ""
actual v2 art-11 value -> OK, returned ""
Rendering the real page in jsdom, the whole chain completes with zero errors:
articles rendered (h2.heading-secondary): 18 / 18 expected
sections rendered (h3) : 22
amendments table present : true
amendment rows : 2
back-to-top button present : true
nav links built : 42
theme toggle present : true
first: 1. Name of the Organization last: 18. Suspension/Termination
ERRORS CAPTURED DURING RUN: (none)
The real defect at this location is different, and still serious. Article 11 renders as a heading with an empty body:
heading : 11. Units
paragraph HTML: ""
-> renders as : SILENTLY EMPTY (no text, no error)
A published constitution article with no text, displayed with no warning, for two years. That is a content-integrity failure rather than a crash — and it is exactly what Phase 2 validation must catch.
The latent hazard the audit describes is real, just not triggered here. An absent content: key,
or content: with no value (which YAML resolves to null), would throw and would abort the
chain, because the throw happens inside .then(). The v2 spec sits one keystroke away from the
failure described. Validation must reject empty, null and missing alike.
https://cdn.jsdelivr.net/npm/marked/marked.min.js returns HTTP 200, 39,903 bytes. It is not a
404 and it is not breaking the site.
What it serves is the interesting part:
/**
* marked v15.0.12 - a markdown parser
*/
marked@latest is 18.0.9. marked.min.js was dropped in the 16.x line, so jsDelivr resolves the
unversioned path back to the newest release that still contains that file — 15.0.12. The site works
today by way of undocumented CDN fallback behaviour, silently pinned to an end-of-life major three
versions behind, with no pin in the repo. The dependency risk stands; the outage does not.
Same for js-yaml — cdnjs.../js-yaml/4.0.0/js-yaml.min.js returns HTTP 200, and it is at least
version-pinned.
Two independent reasons, either sufficient.
a. A chained assignment evaluates to the right-hand side, not to a re-read of the property. The getter is never consulted:
navArticleId = navLink.href = '#article1'
value of navArticleId : "#article1"
value of reading .href back : "https://constitution.stmorg.in/#article1"
b. updateActiveMenu uses getAttribute('href'), which returns the literal content attribute
(#article1), never the resolved URL — so even a bad round-trip could not reach querySelector.
Live DOM, every nav link checked:
total nav links: 42 | unresolved: 0 | SyntaxErrors: 0
sample article href : "#article1"
sample section href : "#article6-section1"
Scroll-spy resolves all 42 targets. The audit's stated cause is wrong.
Note the sub-menu link at js/opencodelaw.js:106 does rely on navArticleId
being an undeclared implicit global. It works only because the loop is synchronous — fragile, but
not the reported bug.
Confirmed and reproduced. js/opencodelaw.js:461 registers the listener at
module scope; #backToTop is not created until the fetch resolves at
line 379. Scrolling in that window:
scroll before render threw: Cannot read properties of null (reading 'style')
On a slow connection or a failed fetch this throws on every scroll event for the life of the page.
Lines 477-482 (topFunction, topButton) are unreachable dead code.
The two CDN tags are parser-blocking classic scripts preceding the async tag. The parser cannot
prepare the async script until it has executed them, so marked and jsyaml are always defined
first. The preload scanner may fetch all three in parallel, but fetch order is not execution order.
Bad practice, and it should still go — but it is not a live defect and not non-deterministic.
.gitignore:2:css/style.css css/style.css
Confirmed via git check-ignore --no-index. The file survives only because it was committed before
the rule landed (git ls-files still lists it). Any clean regeneration drops the site's entire
stylesheet. The file also has no trailing newline.
ammendments[0].act = null
# opens a YAML comment; the value is empty. The renderer assigns it straight to href
(line 348), producing a link to a page named null.
ammendments[0].archive = "/archives/v1"
ammendments[1].archive = "/"
Absolute-rooted and extensionless — it depends on the Apache rewrite in .htaccess, which GitHub Pages does not read. The second amendment's archive is the bare site root, which is not an archive at all.
archives/v2.html loads ../specs/v2.yaml — the live, mutable current spec — and
is titled STM Constitution V1. Both confirmed. Editing the current constitution retroactively
rewrites the "archived" copy.
specs/v1.yaml:13 is version: 2.0.0. Both v1 and v2 report 2.0.0.
Full diff against v1.yaml is 14 lines: version (1.0.0 vs 2.0.0 — the dead file has the
correct value) and the absence of the ammendments: block. 17 articles vs v1's 17; v2 has 18.
css/style.scss:5 — format("ttf") is not a valid format token; it must be
truetype. Lines 12-14 declare the same .woff file twice, first as
format("woff2"). Both faces fail to load; all typography is browser-default fallback.
css/style.scss:295-298 — .vertical-nav { display: none } under 768px.
An 18-article legal document with no table of contents on phones. Open issue #1.
| Source | Root key | Amendments key |
|---|---|---|
| schema/opencodelaw.md:5,54 | OpenCodeLaw |
amendments |
| specs/v2.yaml:1,286 | openconstitution |
ammendments |
| js/opencodelaw.js:303 | — | ammendments |
Confirmed: v2 root keys: [ 'openconstitution', 'info', 'preamble', 'articles', 'ammendments' ].
Anyone following the documented schema produces a file the renderer cannot read.
| # | Finding | Evidence |
|---|---|---|
| A | Malformed colour, declaration silently dropped | css/style.scss:499 — rgba(0,0,0.87) has three arguments, not four. Invalid; the browser discards it. Wide-viewport row headers get no colour. |
| B | Deep links confirmed dead on cold load | No hashchange or load-time hash handling anywhere in the JS — scrollIntoView appears only in click handlers (line 438) and dead code (line 478). Content is injected after the browser has already resolved the hash. |
| C | #320063 verified as the logo colour |
Decoded assets/img/OpenCodeLaw.png (500×500, RGBA): #ffffff 237,099 px, then #320063 4,627 px — the dominant brand hue by an order of magnitude. The promotion to primary accent is justified. |
| D | #533566 is not in the stylesheet |
It exists only at js/opencodelaw.js:178 as an inline slider colour. Full SCSS palette is: #007bff ×12, #333 ×7, #fff ×2, #000 ×2, #f5f5f5, #ccc, #c07534, #555, plus rgba(0,0,0,0.18 / .12 / .54). |
| E | Local toolchain is below the target | Node 18.19.1 installed, no nvm/fnm/volta. marked@18 declares engines: node >= 20. See decision note below. |
| F | The three Acts are text-extractable | pdftotext cleanly extracts all three. Each header confirms assent 3rd May 2024 from the Internal Compliance Committee. Phase 3 will not need OCR. |
Nothing is removed from the plan; one thing is re-prioritised and one justification is corrected.
- The urgency is legal, not operational. The site is not down. Phase 3 — publishing a constitution that reflects its own three enacted Amendment Acts — is the real emergency, and Article 11 has been publishing a blank provision for two years.
- Phase 2 validation is vindicated by a near miss, not a hit.
content: ""passes silently today and renders nothing. The validator must reject empty,nulland missing content alike — the requirement stands, but as a defence against silent blanks, not just crashes. - Phase 4's static build stops depending on CDN fallback luck, which is the accurate reason to vendor and pin — not a 404 that never happened.
- Add to Phase 6: fix the malformed
rgba(0,0,0.87)when extracting tokens. - Phases 1 and 6 are unchanged. Every UI, accessibility, SEO and governance finding stands.
The plan specifies Node 20+; this machine has 18.19.1 with no version manager. To keep
npm run validate && npm test && npm run build runnable locally and on CI, Phase 1 pins
dependencies that run on both (marked@15.0.12, js-yaml@4.1.0, ajv@8.17.1,
sanitize-html@2.17.7), declares engines: >=18.18.0, and sets the GitHub Actions runner to Node 20.
Say the word and I will target Node 20 exclusively instead — local verification would then need a
Node upgrade first.