Repository navigation
ADR 002 Gate 1222 Monaco MathJS Security Parity
APPROVED WITH CONDITIONS — Do not install in repo yet. All evaluation ran in isolated temp sandboxes only.
Gate spike/1222-monacomathjs-security-gate (issue #1222) blocks #1223. It depends on two unmerged prerequisites:
- #1214 — 10 canonical calculation fixtures: INTERIOR DRY/TRANS/WET, COAST IMMATURE/MATURE, HELI, DISPERSED 0.4, RESIDUAL edges (p52–54)
- #1221 — formula engine is missing in repo
No canonical fixtures or engine exist in main yet. This gate pins candidate library versions, runs six evaluation phases in temp sandboxes, and grants a conditional approval for #1223 to proceed under strict constraints. Repo frontend/package.json and package-lock.json remain untouched.
Candidate versions pinned:
-
@monaco-editor/react4.7.0 (2025-02-13) +monaco-editor0.52.2 (peer>=0.25.0 <1, MIT) -
mathjs15.2.0 (2026-04-07, Apache-2.0)
Do not run
npm installfor these candidates in the repo. Use/tmp/gate-1222and/tmp/gate-1222-probeonly until conditions are met.
All evidence was collected outside the repo.
-
Candidate sandbox:
/tmp/gate-1222—package.jsonlists only@monaco-editor/react@4.7.0,monaco-editor@0.52.2,mathjs@15.2.0with 15 prod deps;npm auditandosv-scannerran against this isolated lockfile. -
Vite probe sandbox:
/tmp/gate-1222-probe— same candidates plusvite@8.0.0to test bundling and to surface dev-time transitive vulns without polluting the baseline audit. -
Baseline sandbox:
/tmp/gate-baseline— Vite 8 build without candidates, to measure gzip delta.
No files were written to frontend/node_modules or committed.
| Phase | Verdict | Evidence (read verbatim, do not guess) |
|---|---|---|
| 1. Security — npm audit (candidate-only) | PASS — 0 vulns |
artifacts/audit-candidates.json + artifacts/audit-candidates.pretty.json: auditReportVersion 2, vulnerabilities: {}, prod 15 / total 17, all severities 0 |
| Security — npm audit (probe with vite@8.0.0) | PASS with note — 3 vulns, dev-time only |
artifacts/audit-probe.json: vite high (GHSA-4w7w-66w2-5vf9, GHSA-v2wj-q39q-566r, GHSA-p9ff-h696-f583, plus GHSA-v6wh-96g9-6wx3, GHSA-fx2h-pf6j-xcff) range >=8.0.0 <=8.0.15, fix 8.2.2; dompurify moderate/low via monaco-editor (GHSA-c2j3-45gr-mqc4, GHSA-cmwh-pvxp-8882, GHSA-vxr8-fq34-vvx9, GHSA-55q2-fjhq-7xh7, range <=3.4.12); monaco-editor flagged via dompurify >=0.54.0-dev-20250909 — not our pinned 0.52.2
|
| Security — OSV | PASS — 0 results |
artifacts/osv.json: walked /tmp/gate-1222/package-lock.json, found 17 packages, results: []
|
| Transitive vuln note | Informational |
artifacts/transitive-vuln-note.json: candidate-only 0 vulns; probe 3 vulns; osv 0; action is to pin monaco-editor 0.52.2 with dompurify >=3.4.13 override and upgrade repo vite to 8.2.2
|
| 2. License | PASS — MIT + Apache-2.0 |
artifacts/licenses.json (license-checker production): @monaco-editor/react 4.7.0 MIT, @monaco-editor/loader 1.7.0 MIT, monaco-editor 0.52.2 MIT, mathjs 15.2.0 Apache-2.0, decimal.js 10.6.0 MIT, complex.js 2.4.3 MIT, fraction.js 5.3.4 MIT, typed-function 4.2.2 MIT, etc. No GPL/AGPL |
| 3. Maintenance | PASS with note |
artifacts/maintenance.json: @monaco-editor/react 4.7.0 published 2025-02-13, next RC 4.8.0-rc.3 2025-11-21, repo suren-atoyan/monaco-react 21 open issues / 0 critical-or-bug, 4736 stars, not archived; mathjs 15.2.0 published 2026-04-07, repo josdejong/mathjs 203 open issues / 46 bug / 0 critical, 15070 stars, not archived |
| 4. Bundle | CONDITIONAL PASS — measurable cost, must chunk + lazy-load |
artifacts/bundle-report.json (Vite 8 probe): mathjs 658,048 raw / 180,740 gz (176.5 kB), monaco_wrapper 21,920 raw / 7,520 gz (7.3 kB), react-vendor 186,470 raw / 58,700 gz, app 1,146 raw; total dist 864,000 bytes; delta +185,409 gz (+181.1 kB) vs baseline 59,542 gz (60k); note: @monaco-editor/react loader uses CDN — static bundling of monaco-editor would add ~1.8 MB raw (~400 kB gz) |
| 5. Bundler / SSR | PASS with constraint — ESM only, browser-only Monaco |
artifacts/npm-view-mathjs.json: type: module, module: ./lib/esm, exports . import ./lib/esm/index.js, main ./lib/cjs, sideEffects: false; artifacts/npm-view-monaco.json: @monaco-editor/react 4.7.0 module dist/index.mjs, main dist/index.js, peer monaco-editor >=0.25.0 <1; artifacts/bundle-report.json build status success on Vite 8 ESM; no fs/path in runtime; Monaco must load lazily on client only — never import monaco-editor at SSR/top-level (window-dependent) |
| 6. Parity — rounding + fixtures | CONDITIONAL PASS — BigNumber required, re-run on real fixtures |
artifacts/parity-report.json + artifacts/parity.log + artifacts/parity-harness.mjs + artifacts/parity-harness-v2.mjs: config create(all, {number:'BigNumber', precision:64}), rounding HALF_UP to 3 decimals via math.round(BigNumber, 3) matching Java BigDecimal; 7/7 rounding PASS, 10/10 fixture rounding PASS (synthetic fixtures covering all #1214 categories), Number vs BigNumber divergence proven; full formula parity awaits #1214 + #1221 |
Detailed findings for each phase follow.
- Isolated candidate audit is clean. Probe audit shows vite dev-time highs fixed in
8.2.2and dompurify moderates viamonaco-editor 0.56.0(not our pinned0.52.2). OSV confirms 0. Pinvite 8.2.2in repo and adddompurify >=3.4.13override if bundling monaco statically — seeartifacts/transitive-vuln-note.json.
- All production deps are MIT except
mathjsApache-2.0. Both are permissive and compatible with BC Gov private-cloud delivery. Verifyartifacts/licenses.jsonfor full list; no copyleft.
- Both candidates are actively maintained.
mathjs 15.2.0is 19 days old at evaluation (2026-04-07).@monaco-editor/react 4.7.0is 6 months old (2025-02-13) with a fresh4.8.0-rc.3signal (2025-11-21). No archived repos, no critical issues.
-
mathjsis the dominant cost (~181 kB gz). Acceptable only with code-splitting. Do not bundlemonaco-editorstatically unless the CDN loader is rejected — that path costs another ~400 kB gz.
- Both libraries expose ESM entry points consumed by Vite 8 without plugin workarounds.
mathjsimport islib/esm/index.js;@monaco-editor/reactisdist/index.mjs. Neither pulls Nodefs/pathat runtime. Monaco editors must be dynamic-imported behind a client check (typeof window !== 'undefined') to avoid SSRdocument is not defined/self is not definedfailures.
-
Synthetic harness covers 10 fixtures across required categories:
- INTERIOR DRY_BELT, INTERIOR TRANSITION, INTERIOR WET_BELT, INTERIOR residual, COAST IMMATURE, COAST MATURE, COAST HELI, RESIDUAL EDGE x2, DISPERSED 0.4
-
Rounding rule
HALF_UPverified at 7 edge cases (including1.005 -> 1.01at 2 dp,4967.82315 -> 4967.823at 3 dp). Fixture rounding parity held 10/10 when roundingunroundedTotalM3 -> roundedTotalM3withmath.round(math.bignumber(v), 3). -
Number mode diverges on
0.1+0.2(0.30000000000000004 vs 0.3),100/3*3(100 vs 99.999...), and4967.8231+0.00005(4967.823149999999 vs 4967.82315). BigNumber is mandatory for finance. Full formula-evaluation parity against backendBigDecimalcan only be proven once real #1214 workbook fixtures and #1221 engine are available — see residual note.
-
Vite 8: ESM works. Verified entries:
mathjslib/esm/index.js/dist/index.mjs(seeartifacts/npm-view-mathjs.jsonexports/module);@monaco-editor/reactdist/index.mjs(seeartifacts/npm-view-monaco.json). No extra ViteoptimizeDepsplugin needed beyond default ESM. -
No Node built-ins in runtime: audit of
artifacts/licenses.jsonand bundle report shows nofs/pathin client chunk. -
Monaco browser-only:
monaco-editoraccesseswindow/documentat init. Requirement: lazy-load@monaco-editor/reactwithReact.lazyor dynamicimport()insideuseEffect, gate on client, never at module top-level or in SSR.
From artifacts/bundle-report.json Vite 8 probe:
- Baseline (no candidates): 59,542 gz bytes
- With candidates: +185,409 gz bytes (181.1 kB)
- Breakout:
mathjs 180.7 kB gz,monaco_wrapper 7.3 kB gz,react-vendor 57.3 kB gz
If you switch from CDN loader to static monaco-editor, add ~400 kB gz (~1.8 MB raw) — avoid unless offline bundle is required.
Recommended vite.config.ts chunking (do not copy as exact bytes — adapt to current config):
// vite.config.ts — build.rollupOptions
export default defineConfig({
build: {
rollupOptions: {
output: {
manualChunks: {
"react-vendor": ["react", "react-dom"],
mathjs: ["mathjs"],
monaco: ["@monaco-editor/react"],
},
},
},
},
optimizeDeps: {
include: ["mathjs", "@monaco-editor/react"],
},
});Load Monaco lazily:
const MonacoEditor = React.lazy(() => import("@monaco-editor/react"));
// render only after mount / typeof window !== 'undefined'Do not put monaco or mathjs in the main entry chunk. Preload mathjs only on the formula-edit route if possible.
Constraint: NEVER use mathjs Number mode for finance.
Required creation (from artifacts/parity-harness.mjs):
import { create, all } from "mathjs";
const math = create(all, { number: "BigNumber", precision: 64 });
function canonicalRound(value) {
return Number(math.round(math.bignumber(value), 3).toString());
}Do not use any of these patterns:
// WRONG — introduces float error
import { evaluate, round } from "mathjs";
const mathNumber = create(all, { number: "number" });
mathNumber.evaluate("0.1 + 0.2"); // 0.30000000000000004
mathNumber.round(1.005, 2); // fragile for HALF_UP edge cases without BigNumberCorrect vs incorrect verified in artifacts/parity.log:
-
0.1 + 0.2Number0.30000000000000004vs BigNumber0.3 -
2.675 -> 2dpBigNumber2.68PASS, Number2.67FAIL in JStoFixedparity cases (seeartifacts/parity-harness-v2.mjs) -
4967.82315 -> 3dpBigNumber4967.823matches JavaHALF_UP
When #1214 fixtures land, re-run:
node /tmp/gate-1222/parity-harness.mjs
node /tmp/gate-1222/parity-harness-v2.mjsand diff each fixture math.evaluate(formula) unrounded and canonicalRound(unrounded) against the engine output from #1221. All diffs must be 0 at 3 dp — synthetic harness tolerance 0.0005 does not apply to real fixtures.
This verdict is synthetic. Follow-up is required when prerequisites land:
-
When #1214 fixtures land — replace the 10 synthetic fixtures in
artifacts/parity-harness.mjswith the workbook canonicals (Provincial Calculator v1.1 Interior/Coast sheets, dispersed 0.4 factor, heli multipliers, residual allocation p52–54). Re-run harness; attach report to this ADR. -
When #1221 engine lands — diff harness
math.evaluate+canonicalRoundoutputs against engineBigDecimaloutputs for all 10 fixtures. No tolerance — require exact 3 dp match. - Before #1223 prod unblock — publish the real-fixture parity report and close this residual in the ADR follow-up section.
The rounding capability is proven; the formula-equality proof is pending.
#1223 may start implementation work only if all conditions hold. Each condition cites its gate evidence.
-
No repo install until #1214 + #1221 are available for re-run. Keep candidates in
/tmp/gate-1222only. Evidence:artifacts/parity-report.jsonneeds_rerunandverdict: CONDITIONAL PASS. -
Pin exact versions.
@monaco-editor/react 4.7.0,monaco-editor 0.52.2,mathjs 15.2.0. No ranges. Evidence:artifacts/bundle-report.jsoncandidate_versions,artifacts/npm-view-*.jsonversion/dist,artifacts/maintenance.jsondates. -
Configure mathjs as BigNumber 64.
create(all, {number:'BigNumber', precision:64})andmath.round(math.bignumber(v), 3). Nevernumber:'number'. Evidence:artifacts/parity-report.jsonmathjs_config/rounding_rule,artifacts/parity.logdivergence section. -
Enforce HALF_UP to 3 decimals. All finance totals round via
math.round(BigNumber, 3). Do not useNumber.toFixed,Math.round, or lodash. Evidence:artifacts/parity-report.jsonrounding_cases 7/7 PASS,fixture_rounding_parity 10/10 PASS. -
Bundle discipline.
manualChunksformathjs+monaco+react-vendor, lazy import for Monaco, no staticmonaco-editorunless the team accepts +400 kB gz. Evidence:artifacts/bundle-report.jsondelta_gzip+notes. -
Client-only Monaco. Dynamic import behind mount/client guard; no top-level
import * as monaco from 'monaco-editor'. Evidence: gate bundler/SSR evaluation (Vite 8 ESM, browser-only requirement). -
Patch transitive vulns at install time. Upgrade repo
viteto8.2.2(fixes GHSA-4w7w-66w2-5vf9, GHSA-v2wj-q39q-566r, GHSA-p9ff-h696-f583, GHSA-v6wh-96g9-6wx3, GHSA-fx2h-pf6j-xcff) and adddompurify >=3.4.13override (fixes GHSA moderates<=3.4.12). Evidence:artifacts/audit-probe.json,artifacts/transitive-vuln-note.json. -
Re-run harness on real fixtures before prod. Attach new
parity.log/parity-report.jsonand update this ADR with PASS/FAIL per fixture, including workbook cell refs and Waste Manual p52–54 edges. Until that report is attached, #1223 stays behind a feature flag and does not ship finance calculations to prod.
If any condition is not met, #1223 is blocked.
All files live under /tmp/gate-1222/artifacts/ — read verbatim, no guessing:
-
audit-candidates.json/audit-candidates.pretty.json— isolated candidate-only npm audit, 0 vulns -
audit-probe.json— probe withvite@8.0.0, 3 vulns (vite high + dompurify moderate viamonaco-editor 0.56.0) -
osv.json— osv-scanner onpackage-lock.json, 0 results -
licenses.json— license-checker production (MIT / Apache-2.0) -
npm-view-monaco.json/npm-view-mathjs.json— npm view metadata (versions, dist, exports, peer ranges) -
bundle-report.json— Vite 8 build probe, chunk sizes, gzip delta, static-monaco note -
maintenance.json— publish dates, open issues, stars, archived flags -
parity-report.json/parity.log/parity-harness.mjs/parity-harness-v2.mjs— synthetic 10-fixture harness, rounding HALF_UP, BigNumber vs Number divergence -
transitive-vuln-note.json— dev-only vite fixes in 8.2.2, dompurify override recommendation
No hallucinated APIs. Only artifact-verified data is cited above.
- Artifacts:
/tmp/gate-1222/artifacts/*(see Evidence Index) - Harnesses:
/tmp/gate-1222/parity-harness.mjs,/tmp/gate-1222/parity-harness-v2.mjs - Package:
/tmp/gate-1222/package.json(candidate pin) - Gate issue: #1222 blocks #1223, depends on #1214 and #1221
- Prior gate: ADR 001 — Deferred-Bind ConnectionProvider
This wiki documents NR Waste Plus for users and developers. Verify security and deployment guidance against the current application configuration before applying it elsewhere.
- Report an issue or ask a question in the GitHub repository.
- Follow the Contributing Guidelines to propose an update.
- Read the Code of Conduct.
Collection indexes support human browsing and machine retrieval. Keep both current when adding or moving wiki pages.
NR Waste Plus Wiki | GitHub Repository