Rebuild v3: static engine, permanent citations, Acts 1-3 of 2024 applied - #31
Merged
Conversation
Reproduce every claim in the pre-rebuild audit against a real DOM, the real CDN dependencies and the real spec files before changing anything. 10 confirmed, 3 refuted, 1 partially confirmed. The refuted three are the audit's entire "why it is broken right now" section: - Art. 11 `Units` has a `content:` key holding an empty string, not a missing key. marked does not throw on ""; the page renders all 18 articles, 22 sections, the amendments table and the back-to-top button with zero errors. The real defect is a provision that publishes silently blank. - The unversioned marked CDN URL returns HTTP 200 (jsDelivr falls back to v15.0.12, the last release shipping marked.min.js). Unpinned, not absent. - `navArticleId = navLink.href = '#article1'` yields the RHS, not the resolved URL, and scroll-spy reads getAttribute() anyway. All 42 nav links resolve. Confirmed: scroll handler throws pre-render, gitignored stylesheet, `act: #` as null, unresolvable archive links, mutable archive, wrong v1 version, duplicate opencon.yaml, both broken @font-face declarations, no mobile nav, three-way key naming disagreement. Six further findings, incl. malformed rgba(0,0,0.87) and confirmation that #320063 is the dominant logo hue. Supersedes OpenCodeLaw-Audit.md.
Separate engine from content and establish a real toolchain. No
constitutional text is added, altered or reworded in this commit.
Moves (history preserved, contents byte-identical):
- specs/v1.yaml -> constitution/versions/v1.0.0.yaml
- specs/v2.yaml -> constitution/versions/v2.0.0.yaml
- acts/*.pdf -> acts/pdf/<slug>.pdf
Deleted:
- .htaccess Apache rewrites GitHub Pages never reads
- js/bak.js dead backup of the renderer
- specs/opencon.yaml duplicate of v1 (differs only in version key)
- archives/*.html pointed at the mutable current spec; replaced by
frozen, built archive pages in Phase 4
- .vscode/settings.json
Toolchain: Node ESM, zero framework, exact-pinned dependencies installed
from npm and bundled at build time. No runtime CDN references will appear
in build output.
Dependency pins were chosen against advisories, not defaults:
- js-yaml 4.3.1 4.1.0 has prototype pollution via merge keys (high)
plus three quadratic-CPU DoS advisories
- ajv 8.20.0 8.17.1 has ReDoS via the $data option (moderate)
- sanitize-html 2.17.5 <=2.17.4 lets javascript: URIs through action,
formaction, data, poster and background attributes;
2.17.6+ requires Node >=22.12, which CI does not run
- marked 15.0.12
npm audit reports 0 vulnerabilities.
Prototype pollution and URI-scheme escapes are in scope, not theoretical:
Phase 7 points this engine at third-party specs.
Toolchain runs on Node 18 and 20. CI pins Node 20; engines allows >=18.18
so the build stays verifiable on this machine (Node 18.19.1, no version
manager). Raise both together if Node 20+ becomes a hard requirement.
CI: validate and test gate build, which gates deploy. A schema violation
blocks publication rather than shipping a broken constitution.
css/style.css is no longer gitignored. .nojekyll and CNAME added.
Refs #1, #4, #7, #25, #26
Byte-identical copy of constitution/versions/v2.0.0.yaml (md5 7a4c2bff5e3e9048b5063fba6670c004). Not a single character of constitutional text is authored or changed here. This establishes the engine/content split: versions/ is append-never, edit-never history; current.yaml is the single mutable source of truth. Today they hold the same text because v2.0.0 is what the society currently publishes. Phase 3 amends this file to v3.0.0 by applying Acts 1, 2 and 3 of 2024 verbatim from acts/pdf/, and freezes v2.0.0.yaml with status: superseded. That phase requires human sign-off on RECONCILIATION.md before merge. Committed separately so rename detection records the real lineage: folding it into the restructure made git pair opencon.yaml with v2.0.0.yaml, which is the wrong provenance for an immutable archive.
… bump CI now validates on Node 20 and 22; the published build runs on 22. README records that Node 18 is EOL and the local floor must move to 22 LTS before Phase 4 ships. Investigated sanitize-html 2.17.6/2.17.7 as requested. They do carry further security fixes: SVG SMIL elements can retarget another element's href via attributeName/values to smuggle a javascript: URI, and a raw `<` escaping <textarea>/<xmp> can reopen a tag on re-parse. Not bumping yet, and the reason is a hard block rather than a preference: 2.17.6+ requires htmlparser2@^12, which is ESM-only, so require() throws ERR_REQUIRE_ESM on Node 18. Verified, not inferred from the engines field. 2.17.5 is the newest release that loads on the current floor. Neither fix is reachable here: the sanitizer runs a strict allowlist over Markdown-rendered output permitting no SVG and no animation elements, so there is no element to retarget. Defence-in-depth, not a live hole. The bump is gated on the Node 22 floor move and recorded in the README so it cannot be forgotten.
Verbatim `pdftotext -layout` extraction of all three Acts (11 pages: 5+4+2)
into acts/text/. Committed before register.yaml so every legal assertion
made later is diffable against a source that lives in the repo.
Scope re-derived independently from the PDFs. It matches the corrected
table in full:
Act 1 amends 6(1-5), 7(4,6), 9, 10, 11, 12 inserts 18
Act 2 amends Preamble, 3, 4, 5, 8, 14, 15, inserts 20
16(3), 17(1)
Act 3 amends 13 inserts 21
Article 19 verified absent: a whitespace-normalised search over all three
texts finds every article from 3 to 18, then 20 and 21. No occurrence of
19 in any form. The gap is real and is not an extraction artefact.
Extraction quality is good; no OCR needed. Two Acts carry a STATEMENT OF
OBJECTS AND REASONS, which is explanatory rather than enacting, and in one
case contradicts its own operative text. Recorded for RECONCILIATION.md,
not resolved here.
Authors schema/opencodelaw-1.0.schema.json (draft 2020-12) and
src/validate.mjs, and migrates all three specs onto them. Structural only:
252 provision strings verified byte-identical against HEAD. No provision
text, and no title, is changed.
Identity is now explicit. Every article carries `id: art-N` and
`number: N`; every section `id: art-N-s-M` and `number: M`. Nothing is
derived from array position, so reordering the YAML cannot re-point a
citation. A test asserts this against the real constitution.
Root key is `opencodelaw: "1.0"`, ending the three-way disagreement
between the docs, the specs and the renderer.
Beyond schema conformance the validator enforces the relational rules
that actually protect the document:
- ids globally unique, and `id` must agree with `number`
- article numbering dense from 1; a gap is legal only where a `reserved`
or `omitted` entry explicitly occupies that number
- `amended_by`, `act.amends` and `act.provisions[].target` must resolve
- `act.pdf` and `act.source_text` must exist on disk
- archived files: version must match filename, status must be superseded
- exactly one document may be `current`
- no archived version may duplicate the current version
- no example.com URL anywhere in `info`
- no template may link to an anchor no provision produces
Schema additions beyond the original plan: `provenance` records text that
entered outside the amendment process rather than erasing it, and
provision `status` (active | omitted | reserved) makes a numbering gap
representable without renumbering.
Validation deliberately does not pass yet. It reports exactly two real
problems, both of which are open questions rather than defects to fix:
- art-11 `Units` has an empty body (RECONCILIATION Q11)
- v2.0.0 duplicates the current version, the archive-shadows-current
condition created by Phase 1 scaffolding and cleared by Phase 3
The first is the provision that published blank for two years. It is now
a hard build failure rather than a silent blank page.
RECONCILIATION.md opens with twelve blocking questions, not the four
expected. New ones include Act 2 setting two different amendment
thresholds (2/3rd operative vs 3/4th in its Statement of Objects), Act 1
defining `Unit Board Member` and `Coordinator` with identical operative
words, and the amendment table naming a different author and an earlier
date than the Acts themselves.
No text is applied until PROVENANCE.md is signed off.
Adds src/text-compare.mjs and its tests before any provenance verdict is produced, because a wrong normaliser here causes an amendment already in force to be re-applied. Order is load-bearing: tags are stripped first, and to a space rather than to nothing, so `a<br>b` cannot become `ab`. Punctuation goes last. Measured on the real Article 9 pair (6 <br> tags, 118 tokens): naive (punctuation first, tags never) similarity 0.9559, exact match false correct (tags -> space, punctuation last) similarity 1.0000, exact match true The naive form injects 6 stray `br` tokens. Under exact-equality matching it returns a false NOT-APPLIED, which is the reported failure. Under a 0.94 similarity threshold it survives by 0.016 — close enough that it would fail on a shorter provision with the same tag density. The fixture asserting Article 9 classifies ALREADY-APPLIED is in the suite, along with a test asserting the naive form does contain the stray token, so the guard cannot silently stop guarding. Classification is similarity-based rather than exact so that enumerator style ((1) vs 1.) and typography (curly vs straight quotes) do not mask an applied amendment. An insertion not yet made is NOT-APPLIED, never DIVERGENT.
Classifies every provision the three Acts touch against the text in the constitution today and the pre-Act text in v1.0.0. No constitutional text is changed. ALREADY-APPLIED 4 art-9, art-11, art-12, art-18 — all Act 1 NOT-APPLIED 19 DIVERGENT 1 art-10 This confirms the hypothesis: Act 1 was substantially applied to v2 and never recorded; Acts 2 and 3 were not applied at all. Article 6, which Act 1 also amends, is 100% its pre-Act text — so Act 1 was applied in part, not in whole. Two bugs in my own tooling were caught and fixed before the verdicts were trusted. Both were silent and both produced wrong answers: 1. Clause (6) of Article 7 begins immediately after a PDF form feed. \f is not a JS line terminator, so `^[ \t]*\(` never matched and the clause was absent from the analysis with no error. Form feeds are now normalised, and a guard asserts each clause-scope heading yields exactly the clauses it names, throwing rather than under-reporting. 2. Clause-scope verdicts scored only against the Act, ignoring whether the provision had changed at all since before the Acts. That reported six provisions as DIVERGENT which are word-for-word their own pre-Act text — the definition of NOT-APPLIED. Verdicts now use three measures, and `drift` is what separates the two. Both are covered by regression tests. Importing the generator no longer rewrites the report as a side effect. art-10 is a genuine divergence and is not resolved. Its three sections are a 100.0% match to Act 1 (318 tokens against 318) while its article body is 89.7% the pre-Act text and only 25.0% present in the Act. Act 1 was applied to the sections and the superseded introduction was kept. That paragraph was also hand-edited to Act 1's coordinator naming with no instrument doing so, though Act 1's Statement of Objects does authorise the one deletion. Recorded as Q14. RECONCILIATION.md records the six answered questions, adds Q13 (procedural validity under Article 16(3), noting Act 2 amends that very provision) and Q14. Schema gains `scope` on register provisions with `clauses` required for clause scope, plus `procedure`, `drafting_discrepancy`, `signed_by`, `moved_by`, `unverified_attribution` and `passed_date`; the single `author` field is gone.
Deletes Article 10's retained article-level paragraph from the operative text. Act 1 substitutes Article 10 in full, running its heading straight into clause (1) and supplying no article-level body, so the old body does not survive by default. Guarded before writing: the change halts unless all eight coordinator roles survive in art-10-s-3. They do. The paragraph's unique lead-in, making the IBM responsible for "all legal and financial activities", is confirmed absent from s-3 — it is the part clause (3) reframes to "proper functioning of STM units", and the part that would contradict Article 21 once Act 3 inserts it. Retaining it was a live contradiction, not a redundancy. Verified as the only change: 82 provision strings compared before and after, exactly one differs. Nothing is destroyed. versions/v2.0.0.yaml keeps the paragraph verbatim, `provenance` on art-10 records the text and the unrecorded hand-edits, and RECONCILIATION Q14 quotes both the pre-Act and hand-edited forms. Corrects an error in the previous commit: it claimed the Statement of Objects authorised removing Volunteer Coordinator. It does not, and saying so would have collapsed the Q3 ruling — an SOR that can authorise a deletion in Article 10 can set a voting threshold in Article 16. The operative text of Act 1 clause (3) carries all three edits on its own; the SOR only corroborates intent. That is now convention C1 and is enforced rather than stated: validate.mjs fails with `sor-as-authority` if any provisions[].source_lines cites a line at or after an Act's STATEMENT OF OBJECTS AND REASONS. `drafting_ discrepancy` is the only field permitted to point there. Tests assert each Act's SOR boundary and that Act 2's 2/3rd is operative while its 3/4th appears only below that line. Schema: an article may consist entirely of sections. Required by Act 1's Article 10, which supplies no body. An empty-string `content` remains an error in every case — that defect published Article 11 blank. Validator errors now name the provision: "art-11 Units/content" rather than "/articles/10/content", which pointed at index 10 and read as Article 10. Q2 escalated to blocking. Article 6 is 100% pre-Act text, so applying Act 1 there now requires knowing whether clause (6) Donor survives. Articles 6 and 7 are held; both options are stated neutrally for the board. art-10 now classifies ALREADY-APPLIED at 100.0%. No DIVERGENT verdicts remain in the corpus.
Baseline hashes all 82 provision strings in current.yaml. The only change permitted for the rest of the run is art-11.content (Q11 restoration); anything else aborts before commit. QUEUE.md holds 44 tasks in resume order, LOG.md one line per completion.
acts/register.yaml records all three instruments. Act 1 carries provisions for arts. 9, 10, 11, 12, 18 only — the reconciled set. Articles 6 and 7 are held under Q2 and are named in reconciliation_state, not here. Acts 2 and 3 are recorded as instruments with no provisions: unapplied, and several of their targets do not yet exist to resolve against. Line ranges are generated from the parser, never transcribed, and clamped below each Act's Statement of Objects so no entry can cite explanatory text as authority (C1). Generation halts if arts. 6 or 7 appear, if a count is wrong, or if a PDF is missing. Act 2 records both drafting discrepancies: the 2/3rd vs 3/4th threshold (Q3, operative governs) and the amendment-vs-insertion conflict on Article 15 (Q4, open). Fixes form-feed normalisation to strip rather than substitute a newline. All 11 form feeds sit at a line start, so stripping keeps line numbers identical to the files on disk — source_lines is an audit trail someone opens in an editor, and the previous handling shifted every line after a page break. Schema gains act.application_status (applied | partially-applied | pending).
version 3.0.0-alpha.1, effective_from 2024-05-03, with a reconciliation_state block naming exactly what is not applied and why: arts. 6 and 7 held under Q2, Acts 2 and 3 held entirely. The build renders its banner from this block, never from hardcoded text, so the banner cannot drift from the document. amended_by: [act-1-2024] on arts. 9, 10, 11, 12, 18. Articles 6 and 7 are asserted untouched before writing. L2 deviates from instruction, deliberately. Act 1 substitutes Article 11 as the heading running straight into clause (1); it supplies NO article-level body. Its clause (1) is a 100.0% match to the existing art-11-s-1 and clause (2) a 98.6% match to art-11-s-2 — together 99.4% of the enacted text. So there is no article-level text to restore: writing clause (1) at article level would duplicate section 1 verbatim, and removing that section means renumbering, which is forbidden and would break the art-11-s-2 citation. Instead the empty key is removed, making Article 11 a section-only article — the shape Act 1 actually enacts, and the shape the schema now permits because Act 1's Article 10 required it. No provision text was added, removed or altered; tripwire confirms art-11.content is the only change. Archived blank provisions are warnings, not errors. The archive must reproduce what was published and Article 11 of v2.0.0 was published blank; versions/ is also not editable tonight. The same blank in the live document remains a hard error. The classifier is narrow — only emptiness constraints on a content field — so a missing key or wrong type still fails everywhere. Schema: semver accepts a prerelease suffix, so a mid-reconciliation document can say so in its version rather than claiming 3.0.0. Validation now PASSES: 0 errors, 3 warnings.
Replaces client-side YAML fetching with build-time rendering. Every provision
is in the served HTML: the previous renderer meant social scrapers saw an
empty body and one missing key could blank half the document.
24 pages: the full constitution, one page per article, the amendment
register, an archive index and both frozen versions, plus 404. All 41 tests
pass, link check reports zero internal 404s across 1807 links and 150
in-page anchors.
Anchors are the schema ids exactly. A legacy map resolves every old
#article5 / #article5-section2 link, and hash resolution runs after DOM
ready with the sticky-header offset applied, so a cold load lands on the
cited provision rather than near it.
The reconciliation banner is generated from reconciliation_state and is
asserted by a test to name every blocker the YAML lists. A banner that can
drift from the document it describes is worse than no banner.
Base path is configurable and applied in one place. The link checker fails
on any root-relative URL that escapes it, which is the classic way a site
works locally and 404s on project Pages. CNAME is excluded from the
artifact and CI fails if it appears without being asked for — the custom
domain still serves the old site.
Two bugs found and fixed by the checker rather than in production:
- article pages linked every other article's sections as same-page anchors,
producing 840 dead fragments. The ToC now resolves cross-page.
- OG plates silently fell back to the banner. Two causes: a <style> block
with CSS classes, which ImageMagick's built-in renderer cannot resolve
without librsvg, and a font stack passed through JSON.stringify, whose
nested double quote terminated the XML attribute early and surfaced as a
misleading "No such file or directory". 21 plates now render.
Markdown is sanitised against a strict allowlist that admits no SVG and no
animation elements, closing the attributeName scheme-smuggling class
outright rather than relying on URL parsing. `id` is never taken from
content: anchors are a public API and authored Markdown must not mint one.
schema/SPEC.md is generated from the JSON Schema and verified to regenerate
identically, so the docs cannot drift from the code again.
Fonts corrected: format("truetype") and format("woff"), both font-display:
swap. Open Sans is gone — it was never imported and never rendered.
`node --test tests/` passed on Node 20 and failed on Node 22, which treats a bare directory argument as a module to resolve and threw MODULE_NOT_FOUND. A shell-expanded glob works on 18, 20 and 22 alike. The matrix caught this before it reached the deploy job. Adds the `opencodelaw` bin: validate (a single file against the schema, or the whole corpus with its relational rules), build, and spec. Adds examples/starter/constitution.yaml — a minimal valid document with commented fields, including a reserved article demonstrating how a numbering gap is recorded rather than closed by renumbering. It validates clean.
README drops the CDN install instructions entirely — they pointed at an unversioned marked.min.js and a tag of this repo that no longer reflects how the project works. Replaced with what it is, why citations are permanent, quickstart, how to adopt it for another organization, Pages deployment including the base-path and CNAME safety, and honest limitations. CONTRIBUTION replaces "install a VS Code extension and hand-compile SCSS" with npm ci and the actual scripts, and states the rule that matters: do not edit provision text without an enacting instrument, and a Statement of Objects is never the authority for an operation.
A contract review of the generated assets found real defects; these are the
load-bearing ones.
tokens.css is now the single source of truth. layout.css had redefined
--accent-ink at the same specificity but loads later, so tokens.css's
definition was dead — and the two files meant opposite things by the name
("text on an accent fill" vs "accent as text"). The derived tokens move to
tokens.css with the contrast ratios their author documented; "text on an
accent fill" is --on-accent throughout. --accent-soft and a second
--action-wash were the same ideas under duplicate names and are gone.
Removed the green (#1e7a3c / #7fd39b). The palette rule allows three brand
hues and green is a fourth. The "applied" status pill now uses the brand
purple; ochre already carried "partially applied".
Replaced a hardcoded rgba(0, 0, 0, 0.87) on .to-top with a token. A
malformed three-argument rgba in the old stylesheet is one of the findings
this rebuild started from; reintroducing a hardcoded one is not the fix.
print.css overrode six token names that no stylesheet defines or reads
(--canvas-raised, --ink-muted, --rule, --accent-tint, --accent-secondary,
--canvas-sunken) while leaving every live colour token untouched. Printing
from dark mode therefore put ochre-on-wash chips on paper. It now neutralises
the names that exist.
@font-face was declared twice, in both stylesheets. Fonts are declared once.
Styled body.is-modal-open, which app.js sets while the search dialog or the
mobile contents sheet is open. Nothing defined it, so the page scrolled
behind the overlay and a reader lost their place in a long document.
Lighthouse flagged the banner's "amendment register" link. The link colour was chosen against the page ground, but inside the banner it sits on the accent wash, which lifts the background and drops the ratio below 4.5:1 — 4.15:1 in light, 4.44:1 in dark. Measured against the ground each link actually sits on and darkened within the same hue: #0069d9 -> #0056b3 (5.6:1 on the wash, 6.5:1 on canvas) and #66b2ff -> #7cbcff (5.0:1 on the wash, 6.3:1 on #333333). Lighthouse accessibility: 96 -> 100.
Two contrast failures, both from measuring a colour against the page ground rather than the surface it actually sits on. The dark --accent-ink (#cc99ff) cleared 4.5:1 on #333333 but only reached 3.94:1 on the accent wash that a pressed or hovered control sits on — which is where the theme toggle lives. Lifted to #d9b3ff within the same hue: 7.1:1 on the page, 4.9:1 on the wash. Adds tests/keyboard.e2e.mjs: a scripted keyboard-only walkthrough that passes 10 checks with no pointer. Skip link to main; theme toggle as a real button flipping aria-pressed; Ctrl+K opening search with focus moving in and returning to the opener on Escape; search navigating to a provision; the mobile contents sheet opening, trapping focus through a full 25-tab lap and returning it on Escape; copy-link naming and its polite live region; one main and one h1 per page type; a cold load of #art-11-s-2 landing clear of the sticky header; a legacy #article5 link rewriting itself to #art-5; and zero console errors across six page types. puppeteer-core is pinned to 25.7.0 — 23.x pulls a vulnerable extract-zip through @puppeteer/browsers (3 high advisories) for a browser downloader this never uses. 25.7.0 reports 0 vulnerabilities and, despite declaring Node >=22.12, imports and drives Chrome correctly on Node 18, verified rather than assumed. e2e is a separate script, not part of npm test, and skips cleanly where no Chrome exists. Lighthouse accessibility is now 100 in all four combinations of desktop/mobile and light/dark.
Content paths are now configurable (CONSTITUTION_FILE, VERSIONS_DIR, REGISTER_FILE, OUT_DIR), so a fork replaces the YAML and deploys without touching the engine. Verified: the starter builds standalone into a temp directory at BASE_PATH=/ with no versions and no act register, produces 79 files, and contains no trace of this organization's constitution. The starter deliberately fails validation on one rule and one only. Its contact address is example.org, which the placeholder check rejects, so copying the template and deploying without editing it fails the build. The test asserts exactly that: every structural rule passes and placeholder-url is the single remaining error. The constitution this engine was built for shipped two example.com URLs to production for two years. Also asserts SPEC.md regenerates byte-identically twice and is not stale on disk, the CLI exposes validate/build/spec, and no engine file hardcodes the organization name. Screenshots at 375/768/1440 in both themes, an archived page, and a print preview PDF, in .night-run/screenshots/.
All 50 queue items complete. Live at https://servicetomankind.github.io/OpenCodeLaw/ with the custom domain untouched, verified three ways. Lighthouse on the deployed site: 100/100/100/100 on both mobile and desktop. Keyboard walkthrough 10/10. Provision tripwire green before every commit all night, with art-11.content the only change and Articles 6 and 7 asserted untouched. Leads with the L2 deviation: Act 1 supplies no article-level body for Article 11, so the empty content key was removed rather than text duplicated, and Q11's live-document question changed shape.
constitution.stmorg.in was pointed at this deployment. The artifact was compiled for BASE_PATH=/OpenCodeLaw/ while the apex domain serves from /, so every asset and internal link 404'd. Confirmed against the live site: /styles/tokens.css returned 200 while the HTML asked for /OpenCodeLaw/styles/tokens.css. BASE_PATH is now / and SITE_ORIGIN https://constitution.stmorg.in. The two defaults move to src/lib/paths.mjs so build, link check and tests read one source — three copies of the same default is how they drift apart, and a drift here ships a site whose every link points at a directory that is not there. CNAME policy inverted. Before cutover its presence was the failure; now its absence is, because an Actions deploy whose artifact lacks CNAME can drop the domain setting. It defaults on, and the workflow input became skip_cname for a deliberate rollback. CI also greps the built output for a stale /OpenCodeLaw/ prefix and fails on it. Also in this commit, from the review: Section headings now derive from their article's level, so an article's sections are one level below it rather than siblings. On the index both were h3: the outline said Article 6 and Board Member were peers. Article pages were correct by accident because the article there is h2. Lighthouse cannot catch this — its sequential-headings audit only flags a skipped level, and h3 -> h3 is not a skip. Two tests now assert the nesting and that no page skips a level. npm test uses argument-free discovery, which works identically on 18, 20 and 22 and still excludes the Chrome-dependent e2e file. The artifact-producing job now runs the suite itself.
The previous host served /archives/v1 and /archives/v2 extensionless, via an .htaccess rewrite that also mapped /x.html to /x. Both shapes are in the wild, in minutes and emails and other people's documents, and all four returned 404 on the new host — confirmed against the live domain. Static hosting cannot rewrite, so each old shape is now a real page carrying a canonical to its new address, a meta refresh, a visible link and noindex. A test asserts every stub redirects to a page this build actually produces.
Act 1 titles Article 11's clause (2) "Establishment". It gives clause (1) no
title at all, and the lowercase "units" above it is something an editor
typed. Both rendered at identical weight. Every heading in the live document
now declares `enacted` or `editorial`: 8 of 40 are enacted.
Populated mechanically, but with two narrowings that change the answer. The
rule as first written ("the title appears in the Act text") got it wrong in
both directions:
1. It credited unapplied Acts. Act 2 retitles Article 14 to "Leaves", so a
naive check marked "Sabbatical Leave" enacted — claiming legal force for a
heading not in effect. Only an Act already applied to that provision can
enact its title.
2. It matched prose, not headings. It marked Article 11's "units" as enacted
because clause (1) opens "The units of the NGO...", and Article 15's
"Resignation" off "the resignation letter". A title now counts only where
an instrument states it as a heading.
Under the corrected rule "units" is editorial and "Establishment" is enacted,
which is the distinction this field exists to draw.
Nothing renamed. `units`, `Alumini`, `Anual Reports` and `Ammendments` stand
as they are, marked and listed on the amendments page for the board to ratify
or replace. Recorded in RECONCILIATION.md that Act 2's operative text spells
Article 16 as "Amendments", so that typo corrects itself when Act 2 is
applied — by the instrument, not by an editor.
The field is optional in the schema so the frozen archives stay valid
unedited; validate.mjs requires it on the current document only.
Q11 signed off as section-only, with the resolution attributed to Act 1's
operative structure rather than editorial preference.
Part record, part checklist: the domain went live ahead of this document, so it documents what happened, what broke and why, and what is still open. Outstanding items are DNS and third-party dashboard changes, deliberately not executed: GitHub reports https_enforced false because Cloudflare proxies the domain and terminates TLS, so GitHub cannot provision its own certificate; Cloudflare's SSL mode should be confirmed as Full (strict) rather than Flexible; and Cloudflare is rewriting page content via email obfuscation, which means the bytes served are not exactly the bytes built. Also records the four-step rollback, and that the domain now publishes a constitution reflecting one of three Acts and only partly — visible on every page, but worth the board knowing it went live in that state.
… marker The page led with "Version 3.0.0-alpha.1 · effective 2024-05-03". Both halves were claims nobody made. No 3.0.0 exists, and the effective date contradicted the banner immediately below it, which says the Acts that assented on that date are unapplied. info now separates the publication label from the legal position: version: 3.0.0-alpha.1 working label, build metadata legal_status: not_adopted required, enumerated adopted_version: 2.0.0 what the board actually adopted text_as_of: 2024-05-02 what the displayed text reflects effective_from is removed: an unadopted text has no effective date. The headline, the footer, the JSON-LD legislationDate and the sitemap lastmod all now state the adopted position — "last adopted version 2.0.0 … no later version has been adopted" — and the working label moved to the amendments page under a heading that says it is not a statement of what is in force. The validator errors if legal_status is missing on the live document, if an unadopted document also carries effective_from, or if adopted_version equals the working version. Heading marker inverted. 8 of 40 headings are enacted, so marking the editorial ones marked the rule rather than the exception, and put the words "not enacted" inside 32 headings where they read as a claim about the provision rather than its heading. Now only the 8 enacted headings carry a glyph with an accessible label, and the default is stated once on the amendments page: headings are editorial aids unless marked. The YAML is unchanged; only the presentation was wrong. Banner notes are block-level. Inline, each ran straight on from its blocker: "awaiting Q2Act 1 of 2024 also amends…", on every page. A test asserts the build ships a real mailto: and no Cloudflare email-obfuscation link. The build is clean; the rewrite is applied at the edge and cannot be fixed here. The keyboard harness now reads the shared base path instead of hardcoding /OpenCodeLaw — it had kept serving the old prefix after the apex move, so every asset 404'd inside the tests while the real site was fine. All 10 pass.
Fetching GitHub's Pages origin with the right Host header fails TLS verification: no alternative certificate subject name matches constitution.stmorg.in. Visitors are unaffected because Cloudflare terminates TLS with its own certificate, but it confirms the GitHub-side certificate has never been issued, which is what https_enforced: false reflects. It also settles one of the open questions in item 1: Cloudflare's SSL mode cannot currently be Full (strict) against this origin without returning a 526, because strict mode validates the origin certificate and there is not a valid one for this hostname. Whatever the mode is today, it is not that.
No provision text changes in this commit. Tripwire green, still one permitted change. Authorisation recorded as stated, not inferred. Pranay: "the amendments were authored long ago and approved by every member of the board." Each Act's procedure block now carries board approval with that statement as evidence, and leaves intermediate-board and units unpopulated with complete: false — Article 16(3) required all three bodies and only the board was named. Resolves Q13; the circularity stands, since Act 2 amends 16(3) itself. Resolved without new decisions: - Q4 by convention C1. "Inserted" appears only under STATEMENT OF OBJECTS AND REASONS, which C1 forbids as authority. It is an amendment renaming Article 15 to Exit Process. - Q2 by literal application. A substitution of named clauses (1)-(5) does not reach clause (6), so Donor stands. The tension with Act 1's amended Article 7(4) is recorded as a standing item, not resolved in the text. - Q7: the Acts make every title correction themselves — Alumni, Annual Report, Amendments — plus two structural renames, Article 14 to Leaves and Article 15 to Exit Process. No editorial rename is made. Holding these was right. - Q5 confirmed: publish both identical definitions as enacted, note the defect. - Q12 verified untriggerable: the three Acts perform 16 substitutes and 3 inserts, and zero renumbers. The manifest is the substance here. src/manifest.mjs extracts every provision each Act touches; src/act-application.mjs declares how each Act span maps onto article body and sections, by line range, so no provision text is ever retyped — only sliced from acts/text/. Structure is declared rather than inferred because inference failed in both directions on the same heuristic: it read Article 9's sentences as section titles, collapsed Article 10's three clauses into one, and found none of Article 8's five. PDF layout indentation is not a structural signal. verifyCoverage asserts the declared slices reconstruct each Act span exactly under the normaliser, so a dropped line, a duplicated one or an invented phrase all fail. All ten article-scope structures pass.
Articles 9, 10, 11, 12 and 18 were already reconciled and are recorded, not rewritten — the applier detects them and changes nothing. Article 6: clauses (1)-(5) substituted into sections 1-5, with the Act's own titles. Section 3 is renamed College Unit Board Member -> Unit Board Member. Clause (6) Donor is untouched: the Act names clauses (1)-(5) and a substitution of named clauses does not reach an unnamed one (Q2). Article 7: clause (4) substituted, defining STM DONOR. Within clause (6), sub-clause (a) point 6 is substituted (the performance-plan notice drops from three months to two) and sub-clause (b) is substituted (terminations now require written approval from the Internal Compliance Coordinator). Points (a)1-5 are unnamed by the Act and stand unchanged — its own Statement of Objects describes exactly this scope. Every string is sliced from acts/text/, never retyped. The applier verifies that the set of provisions that changed equals the set the manifest expects for this Act, that each matches the Act's prescribed text under the normaliser, and that nothing outside the manifest moved. art-6 and art-7 were the only changes. Tripwire re-baselined, not disabled. It now guards the post-Act-1 state.
…16, 17; insert Article 20 Ten provisions. The preamble and Articles 3, 4 and 5 are substituted in full. Article 8 is substituted with its five clauses as sections, including the internship duration dropping from three academic years to two. Two structural renames the Act makes itself: - Article 14 Sabbatical Leave -> Leaves, with Sabbatical Leave becoming clause (1). Three sections become one. - Article 15 Resignation -> Exit Process, restructured into (1) Voluntary and (2) Involuntary. Three sections become two. Article 16 clause 3 substituted: the amendment threshold becomes a 2/3rd majority present and voting. The Act's own Statement of Objects says 3/4th; convention C1 makes the operative text govern, and the discrepancy stays recorded on the Act rather than normalised away. Article 16 is also retitled Ammendments -> Amendments. That correction comes from the instrument, which heads the clause "16. Amendments", not from an editor — which is why holding the typo through Phase 3 was right. The applier now takes the restated heading on clause-scope provisions too; it previously only did so for article scope. Article 17 clause (1) substituted. The Act numbers it (1) and the article numbers it (a); the same clause, confirmed by its opening words. Clause (b) is untouched. Article 20 Meetings and Activities inserted. Article 19 recorded as reserved. Inserting Article 20 opens a gap at 19, and the validator refuses an unaccounted one. No instrument mentions 19 — a whitespace-normalised search over all three Acts finds no occurrence in any form — so it is left unallocated rather than closed by renumbering, which would break every citation after it. The preamble may now declare title_source; Act 2 enacts its heading.
Article 13 substituted and retitled Anual Reports -> Annual Report, by the instrument. Article 21 Financial Management inserted. Only those two provisions changed; the tripwire caught the pair before this commit, which is what it is for. Reconciliation is complete. version 3.0.0, legal_status adopted, effective_from 2024-05-03. adopted_version and text_as_of are dropped: they existed to describe a gap between the label and what was in force, and there is no longer one. The validator inverts to match — an adopted document must now state effective_from, and adopted_version may not disagree with version. The reconciliation banner is gone, because it is no longer true. Its removal follows the data: reconciliation_state is deleted and the template renders nothing. Two items move to the amendments page as standing notes rather than disappearing — Article 19 reserved with no instrument, and the Article 6(6) Donor tension together with the identical clause (3)/(4) definitions. The heading marker now follows the data instead of a fixed direction. Applying the Acts flipped the ratio exactly, from 8 enacted of 40 to 32 of 40, so the marked set switched from enacted to editorial. The principle is unchanged: mark the exception, state the default once. The accessible label is reworded to name the heading and not its legal effect — "not enacted" beside a provision reads as a claim about the provision. All three Acts of 2024 are now applied. Articles 1-21 with 19 reserved.
Act 2 restructured Articles 14 and 15. Article-level ids are stable, but six section ids changed what they denote — art-15-s-1 meant Eligibility under Resignation and now means Voluntary under Exit Process. Correct behaviour: a section id names a position within an article, and the Act restructured those articles. Recorded because it is the first time an anchor has moved rather than merely existed, with the before/after mapping in RECONCILIATION.md and a bookmark-holder's version in CUTOVER.md. /archive/2.0.0/ still resolves every old anchor, verified, so a citation made before today remains readable as it stood. No redirect: the Act changed what the provision says, and forwarding silently would hide that.
PROVENANCE.md flagged Article 6 clauses (2), (4) and (5) as DIVERGENT at 90-94%. Compared clause by clause against the Act they are 100% exact. The fault was in containment(): a window stride of width/8 could straddle the clause boundary and never land on the true alignment. Stride is now token-by-token wherever the haystack is small enough to afford it. All 24 provisions the three Acts touch now report ALREADY-APPLIED, which is the correct state: every one of them matches its instrument. A false DIVERGENT in a legal report is the same class of noise as the 98.6% that turned out to be a comparison artefact — it sends a reviewer looking for a problem that is not there.
Recorded while the gaps were visible. Six requirements, the largest being that structure cannot be inferred from a PDF — Acts should be authored in the same machine-readable schema as a patch, with the PDF as the signed rendering rather than the source. Also: clauses need addressable ids so an Act can target a clause instead of a line range; there is no propose-review-approve state; procedure.complete is recorded but never enforced; anchor meaning changes are not reported by the applier; and numbering gaps surface only after the write. Nothing implemented, per the deferral.
KiranPranay
temporarily deployed
to
github-pages
August 15, 2026 04:22 — with
GitHub Actions
Inactive
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Rebuilds OpenCodeLaw as a static, validated, citable engine and applies all three Amendment Acts of 2024.
The law
acts/text/, never retyped.PROVENANCE.mdreports all 24 touched provisions as ALREADY-APPLIED — each matches its instrument.Alumni,Annual Report,Amendments) come from the instruments, not from an editor.The engine
Verification
58 tests plus a 10-step keyboard walkthrough, zero internal 404s, Lighthouse 100/100/100/100 on the live site.
Detail:
AUDIT-CONFIRMED.md,RECONCILIATION.md,PROVENANCE.md,.night-run/REPORT.md,.night-run/CUTOVER.md.