Skip to content

Make the SonarCloud reliability gate reproducible, and clear its live findings - #233

Merged
hyperpolymath merged 4 commits into
mainfrom
arena/030f31ad-metamanifold-webui
Oct 23, 2026
Merged

hyperpolymath merged 4 commits into
mainfrom
arena/030f31ad-metamanifold-webui

Conversation

@hyperpolymath

@hyperpolymath hyperpolymath commented Oct 23, 2026 •

Copy link
Copy Markdown
Owner

What this fixes

PR #224's SonarCloud Code Analysis check fails with D Reliability Rating on New Code. This branch fixes the two comparison-less sort() calls live on main under the same rule, adds a sub-second local reproduction of the rule that failed the gate, and documents why the rule's own suggested fix is wrong here.

Root cause

The check is the SonarQube Cloud GitHub App running Automatic Analysis. There is no scanner workflow, no sonar-project.properties, and no .sonarcloud.properties — so nothing here configures it. Automatic Analysis ignores sonar-project.properties in any case, and .sonarcloud.properties accepts only scope/exclusion settings — not rule selection, suppression, or the new-code definition.

The project's gate is the built-in Sonar way with the coverage condition absent. Its conditions, measured from /api/qualitygates/project_status:

Condition Threshold What drives it
new_reliability_rating <= A worst bug on new code
new_security_rating <= A worst vulnerability on new code
new_maintainability_rating <= A technical-debt ratio (< 5% is A)
new_duplicated_lines_density <= 3% duplicate lines
new_security_hotspots_reviewed = 100% hotspots reviewed

Reliability is worst-severity, not a count. MINOR -> B, MAJOR -> C, CRITICAL -> D, BLOCKER -> E, and anything above A fails. So one bug fails the gate at any severity, while a handful of code smells is usually free — maintainability is a ratio. Both directions were measured, on this PR and on #224:

PR #224 has exactly three findings, all in its new frontend/src/api/ijsonJcs.ts: typescript:S2871 (CRITICAL bug) at line 53, and typescript:S7758 (MINOR, but a code smell) at lines 8 and 10. The single sort() at line 53 is the entire failure. The two S7758s were never what failed it.

Why the obvious fix is wrong

S2871's suggestion is (a, b) => a.localeCompare(b). For ijsonJcs.ts that is a correctness bug: RFC 8785 section 3.2.3 orders object members by UTF-16 code unit, and localeCompare applies locale collation. Measured:

['A','Z','a','e','z','a-umlaut','e-acute']
  default sort()      -> A, Z, a, e, z, a-umlaut, e-acute
  byCodeUnit          -> A, Z, a, e, z, a-umlaut, e-acute   (identical)
  localeCompare('en') -> a, A, a-umlaut, e, e-acute, z, Z   (different)

So the "fix" turns the check green while breaking canonical JSON. byCodeUnit() is added to frontend/src/utils/text.ts so the correct fix is also the easy one.

The same trap sits under typescript:S7758: frontend/src/tree/model.ts's FNV-1a tip hash is defined over UTF-16 code units, and codePointAt would merge surrogate pairs and change every tip key. That file is allowlisted with its reason in the guard, rather than suppressed with // NOSONAR — which would still leave the finding counted and add a typescript:S1291 maintainability finding of its own.

What changed

  • frontend/src/utils/text.ts — byCodeUnit, the default string ordering written out, with the gate and the localeCompare trap documented.
  • frontend/src/utils/text.test.ts — three tests: order pinned literally (not recomputed with another sort), localeCompare shown to differ, comparator total and case-sensitive.
  • frontend/src/views/CompositionsView.tsx — the two typescript:S2871 findings open on main since July 2026, at :23 and :28.
  • frontend/src/tree/model.test.ts — stateJson's six normalising sorts, so the new guard does not fire on them.
  • scripts/lint-sonar-reliability.sh — reproduces S2871 and S7758 over frontend/src in ~0.1 s with no toolchain, emits ::error annotations in Actions, and has no waiver for S2871 because a comparison-less sort is never what is wanted.
  • .github/workflows/ci.yml — runs the guard first in the Julia tests job, before anything is installed. Job name and shape untouched (test/unit/test_install_pins.jl still passes: no matrix, no interpolation).
  • docs/sonar-quality-gate.md, CONTRIBUTING.md — the measured gate, the new-code definition, both rules, and the fix.

Verification

  • The guard, run against PR Resolve #221 against main's validated-statistics fixes #224's actual file, reports exactly lines 53, 8 and 10 — matching the SonarCloud API issue list — and against main's CompositionsView.tsx reports lines 23 and 28. Each was checked by restoring the pre-fix file, then re-checking the fixed tree is clean; it also works from a subdirectory and exits 2 outside a repository.
  • The corrected ijsonJcs.ts passes the genuine eslint-plugin-sonarjs rule sonarjs/no-alphabetical-sort under typed linting (failure reproduced at 53:38 before, clean after). That file is not in this branch — it belongs to Resolve #221 against main's validated-statistics fixes #224, and the patch is posted there.
  • The codePointAt rewrite of validString was compared against the original over 2,628,290 inputs — every BMP unit alone and sandwiched, every high x low surrogate pair, every noncharacter in every plane, 400,000 random hostile strings — zero disagreements.
  • SonarCloud on this branch: 0 new issues, gate passed.
  • bun test src -> 69 pass, 0 fail; tsc --noEmit clean; bun run build succeeds.

Left out

The gate itself. The built-in condition is new_reliability_rating > 1; raising the threshold to 2 would tolerate a rating B (one MINOR bug) instead of blocking on it. That is a custom quality gate in the SonarCloud UI, and no file in this repository can create it.

…epro

The required "SonarCloud Code Analysis" check is the SonarQube Cloud GitHub
App running Automatic Analysis. Nothing in this repository configures it: no
scanner workflow, no sonar-project.properties, and Automatic Analysis ignores
that file anyway. Its gate is Sonar's built-in "Sonar way" (coverage condition
absent), and it requires reliability rating A on new code, which means ZERO
findings of any severity -- a single MINOR is rating B and fails the merge.

PR #224 fails it with three findings in a new frontend/src/api/ijsonJcs.ts:
CRITICAL typescript:S2871 on a comparison-less Object.keys().sort(), and two
MINOR typescript:S7758 on charCodeAt. The rule's own suggested fix for S2871,
localeCompare, is wrong for that file: RFC 8785 orders members by UTF-16 code
unit, and localeCompare applies collation, so the "fix" would break canonical
JSON while turning the check green.

This change:

* adds byCodeUnit() to frontend/src/utils/text.ts -- the default string
  ordering, written out, so the correct fix is also the easy one -- with tests
  that pin the order literally and show localeCompare differs;
* uses it at the two comparison-less sorts that are live on main,
  frontend/src/views/CompositionsView.tsx:23 and :28 (open since July), and in
  model.test.ts's state normalisation;
* adds scripts/lint-sonar-reliability.sh, which reproduces S2871 and S7758
  over frontend/src in ~0.1s with no toolchain, and runs first in the test job
  before anything is installed. Verified against the real files: it reports
  exactly lines 53, 8 and 10 of PR #224's ijsonJcs.ts and lines 23 and 28 of
  CompositionsView.tsx, matching the SonarCloud API;
* documents the whole thing in docs/sonar-quality-gate.md and points at it
  from CONTRIBUTING.

charCodeAt is allowlisted for frontend/src/tree/model.ts, whose FNV-1a hash is
defined over UTF-16 code units, with the reason in the script. Suppressing it
with // NOSONAR would leave the finding counted and add a typescript:S1291
maintainability finding of its own.

Checks: bash -n and the script itself; bun test src (69 pass); tsc --noEmit;
bun run build.

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
@coderabbitai

coderabbitai Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 59 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: 18ec6bd2-5019-46e2-8d77-62208ccc369d
📥 Commits

Reviewing files that changed from the base of the PR and between b6e5ecb and c455d14.

📒 Files selected for processing: 8
  • .github/workflows/ci.yml
  • CONTRIBUTING.md
  • docs/sonar-quality-gate.md
  • frontend/src/tree/model.test.ts
  • frontend/src/utils/text.test.ts
  • frontend/src/utils/text.ts
  • frontend/src/views/CompositionsView.tsx
  • scripts/lint-sonar-reliability.sh
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-23T13:54:20.526458Z c455d14 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@github-actions

github-actions Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

Fork governance

Lane Status Run
DEED syntax (repo deed) 🟢 conformant run 39964425681
Hypatia verdict grid purple (job: success) same run

Hypatia grid purple

🟣 critical · commit c455d14f2802 · run 39964425681

Reporter Status Critical High Medium Minor Since previous revision
Hypatia scan 🟣 critical 45 61 34 0 ↓ regressed (4 more blocking)
panic-attack assail 🔴 high 0 6 13 0 ↔ unchanged
pons asinorum 🟡 medium 0 0 10 0 ↔ unchanged
rhodibot (RSR compliance) 🟠 indeterminate 0 0 0 0 — indeterminate
panicbot (panic recovery, error handling) 🟠 indeterminate 0 0 0 0 — indeterminate
echidnabot (formal verification: proofs/) 🟠 indeterminate 0 0 0 0 — indeterminate
seambot (integration health: API routes) 🟠 indeterminate 0 0 0 0 — indeterminate
finishingbot (release readiness) 🟠 indeterminate 0 0 0 0 — indeterminate
accessibilitybot (WCAG on the React UI) 🟠 indeterminate 0 0 0 0 — indeterminate
glambot (presentation quality) 🟠 indeterminate 0 0 0 0 — indeterminate
cipherbot ⚪ not applicable 0 0 0 0 ↔ unchanged
sustainabot ⚪ not applicable 0 0 0 0 ↔ unchanged
Hypatia scan: 140 finding(s)
  • critical cicd_rules/banned_language_file — frontend/src/views/run/parseFilterYaml.ts — TypeScript file detected -- banned language
  • critical cicd_rules/banned_language_file — frontend/src/figure/encode.ts — TypeScript file detected -- banned language
  • critical cicd_rules/banned_language_file — frontend/src/figure/charts.ts — TypeScript file detected -- banned language
  • critical cicd_rules/banned_language_file — frontend/src/figure/export.ts — TypeScript file detected -- banned language
  • critical cicd_rules/banned_language_file — frontend/src/figure/figure.test.ts — TypeScript file detected -- banned language
  • critical cicd_rules/banned_language_file — frontend/src/figure/legend.ts — TypeScript file detected -- banned language
  • critical cicd_rules/banned_language_file — frontend/src/figure/types.ts — TypeScript file detected -- banned language
  • critical cicd_rules/banned_language_file — frontend/src/utils/download.ts — TypeScript file detected -- banned language
  • critical cicd_rules/banned_language_file — frontend/src/utils/text.test.ts — TypeScript file detected -- banned language
  • critical cicd_rules/banned_language_file — frontend/src/utils/timeago.ts — TypeScript file detected -- banned language
  • critical cicd_rules/banned_language_file — frontend/src/utils/text.ts — TypeScript file detected -- banned language
  • critical cicd_rules/banned_language_file — frontend/src/hooks/useUnsavedGuard.ts — TypeScript file detected -- banned language
  • critical cicd_rules/banned_language_file — frontend/src/hooks/useApi.test.ts — TypeScript file detected -- banned language
  • critical cicd_rules/banned_language_file — frontend/src/hooks/useApi.ts — TypeScript file detected -- banned language
  • critical cicd_rules/banned_language_file — frontend/src/hooks/useAnalysis.ts — TypeScript file detected -- banned language
  • critical cicd_rules/banned_language_file — frontend/src/hooks/useSSE.ts — TypeScript file detected -- banned language
  • critical cicd_rules/banned_language_file — frontend/src/hooks/useTabParam.ts — TypeScript file detected -- banned language
  • critical cicd_rules/banned_language_file — frontend/src/hooks/useNavRefresh.ts — TypeScript file detected -- banned language
  • critical cicd_rules/banned_language_file — frontend/src/hooks/useJobEvents.ts — TypeScript file detected -- banned language
  • critical cicd_rules/banned_language_file — frontend/src/about.ts — TypeScript file detected -- banned language
  • 120 more; see the structured report in the artifact.
panic-attack assail: 19 finding(s)
  • high DynamicCodeExecution — src/pipeline/dada2/context.jl — eval/Meta.parse in src/pipeline/dada2/context.jl
  • high DynamicCodeExecution — src/analysis/analysis.jl — eval/Meta.parse in src/analysis/analysis.jl
  • high DynamicCodeExecution — src/analysis/differential.jl — eval/Meta.parse in src/analysis/differential.jl
  • high DynamicCodeExecution — src/core/provenance.jl — eval/Meta.parse in src/core/provenance.jl
  • high DynamicCodeExecution — scripts/check-julia-syntax.jl — eval/Meta.parse in scripts/check-julia-syntax.jl
  • high DynamicCodeExecution — test/unit/test_permanova.jl — eval/Meta.parse in test/unit/test_permanova.jl
  • medium UnsafeFFI — src/core/memory_probe.jl — 3 ccall/FFI calls in src/core/memory_probe.jl
  • medium InputBoundary — src/server/routes/runs.jl — 2 JSON3.read/JSON.parse call(s) with 0 try block(s) in src/server/routes/runs.jl — these throw on malformed input; wrap in try/catch
  • medium InputBoundary — src/server/routes/composition.jl — 6 JSON3.read/JSON.parse call(s) with 2 try block(s) in src/server/routes/composition.jl — these throw on malformed input; wrap in try/catch
  • medium InputBoundary — src/server/routes/studies.jl — 4 JSON3.read/JSON.parse call(s) with 2 try block(s) in src/server/routes/studies.jl — these throw on malformed input; wrap in try/catch
  • medium InputBoundary — src/server/routes/analysis.jl — 9 JSON3.read/JSON.parse call(s) with 4 try block(s) in src/server/routes/analysis.jl — these throw on malformed input; wrap in try/catch
  • medium UnsafeFFI — install.jl — 1 ccall/FFI calls in install.jl
  • medium ProofDrift — test/unit/test_occupancy_grade.jl — 2 `@test x isa Y` assertion(s) with no value check — type-only tests may substitute for an unwritten formal theorem in test/unit/test_occupancy_grade.jl
  • medium ProofDrift — test/unit/test_evidence.jl — 1 `@test x isa Y` assertion(s) with no value check — type-only tests may substitute for an unwritten formal theorem in test/unit/test_evidence.jl
  • medium InputBoundary — bench/advanced_zero_handling/benchmark.jl — 1 JSON3.read/JSON.parse call(s) with 0 try block(s) in bench/advanced_zero_handling/benchmark.jl — these throw on malformed input; wrap in try/catch
  • medium InputBoundary — frontend/src/figure/charts.ts — 1 JSON.parse call(s) with 0 try block(s) in frontend/src/figure/charts.ts — JSON.parse throws SyntaxError on malformed input; wrap in try-catch
  • medium InputBoundary — frontend/src/tree/model.ts — 1 JSON.parse call(s) with 0 try block(s) in frontend/src/tree/model.ts — JSON.parse throws SyntaxError on malformed input; wrap in try-catch
  • medium InputBoundary — frontend/src/api/events.ts — 2 JSON.parse call(s) with 0 try block(s) in frontend/src/api/events.ts — JSON.parse throws SyntaxError on malformed input; wrap in try-catch
  • medium InputBoundary — frontend/src/components/alphaMetrics.tsx — 1 JSON.parse call(s) with 0 try block(s) in frontend/src/components/alphaMetrics.tsx — JSON.parse throws SyntaxError on malformed input; wrap in try-catch
pons asinorum: 10 finding(s)
  • medium string-concat-in-loop — ./frontend/src/tree/model.ts:122 — string built by repeated concatenation in a loop (T0, HEURISTIC)
  • medium string-concat-in-loop — ./frontend/src/tree/model.ts:118 — string built by repeated concatenation in a loop (T0, HEURISTIC)
  • medium string-concat-in-loop — ./frontend/src/tree/TreeShapes.tsx:69 — string built by repeated concatenation in a loop (T0, HEURISTIC)
  • medium string-concat-in-loop — ./frontend/src/tree/TreeShapes.tsx:66 — string built by repeated concatenation in a loop (T0, HEURISTIC)
  • medium string-concat-in-loop — ./frontend/src/tree/TreeShapes.tsx:61 — string built by repeated concatenation in a loop (T0, HEURISTIC)
  • medium string-concat-in-loop — ./frontend/src/tree/TreeShapes.tsx:53 — string built by repeated concatenation in a loop (T0, HEURISTIC)
  • medium string-concat-in-loop — ./frontend/src/tree/TreeShapes.tsx:43 — string built by repeated concatenation in a loop (T0, HEURISTIC)
  • medium string-concat-in-loop — ./frontend/src/tree/TreeShapes.tsx:42 — string built by repeated concatenation in a loop (T0, HEURISTIC)
  • medium unreachable-after-jump — ./frontend/src/figure/PaneEditor.tsx:45 — statement is unreachable after an unconditional jump (T0, HEURISTIC)
  • medium unreachable-after-jump — ./frontend/src/components/PublicationTablesPanel.tsx:208 — statement is unreachable after an unconditional jump (T0, HEURISTIC)
rhodibot (RSR compliance): not_run — enrolled, but no report-back was received
  • 🟠 indeterminate: enrolled, but no report-back was received
panicbot (panic recovery, error handling): not_run — enrolled, but no report-back was received
  • 🟠 indeterminate: enrolled, but no report-back was received
echidnabot (formal verification: proofs/): not_run — enrolled, but no report-back was received
  • 🟠 indeterminate: enrolled, but no report-back was received
seambot (integration health: API routes): not_run — enrolled, but no report-back was received
  • 🟠 indeterminate: enrolled, but no report-back was received
finishingbot (release readiness): not_run — enrolled, but no report-back was received
  • 🟠 indeterminate: enrolled, but no report-back was received
accessibilitybot (WCAG on the React UI): not_run — enrolled, but no report-back was received
  • 🟠 indeterminate: enrolled, but no report-back was received
glambot (presentation quality): not_run — enrolled, but no report-back was received
  • 🟠 indeterminate: enrolled, but no report-back was received
cipherbot: not_applicable — no cryptography is implemented in this repository
  • ⚪ not applicable: no cryptography is implemented in this repository
sustainabot: not_applicable — fleet adapter slot reserved; no adapter exists yet (gitbot-fleet README)
  • ⚪ not applicable: fleet adapter slot reserved; no adapter exists yet (gitbot-fleet README)

Scope: static findings reported by each row's tool at the named commit. Purple, red, yellow and blue grade the worst finding present; orange is evidence missing, malformed or a scanner that did not run; grey is an explicit scope exclusion with its reason. No row is a security certification. report.json, badge.json and the raw findings are in the verdict-grid artifact on the linked run.

The first commit described rating A as "zero findings of any severity". The
analysis of this very PR disproves it: two MAJOR code smells (typescript:S3358
in text.ts, shelldre:S7679 in the guard) and 10 minutes of technical debt left
new_maintainability_rating at 1.0 (A), and the gate passed.

Measured from /api/measures and /api/qualitygates/project_status:

  PR #233  new_bugs=0, new_code_smells=2, new_technical_debt=10min
           new_reliability_rating=1.0, new_maintainability_rating=1.0

So reliability and security are rated on the WORST bug/vulnerability on new
code (MINOR->B, MAJOR->C, CRITICAL->D, BLOCKER->E) -- one bug fails it at any
severity -- while maintainability is a debt ratio, where a handful of smells
stays A. PR #224 fails on one CRITICAL bug (typescript:S2871); its two
typescript:S7758 findings are code smells and were never what failed it.

This makes the diagnosis sharper, not softer: a single comparison-less sort()
is the entire cause. Corrected in docs/sonar-quality-gate.md, the guard's
header, the byCodeUnit doc comment and CONTRIBUTING, and the two new smells
this PR introduced are fixed rather than left standing.

Checks: bash -n and the guard; bun test src (69 pass); tsc --noEmit.

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d8d5b4dd5c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

echo " behaviour) or an explicit comparator. Do not reach for localeCompare." >&2
annotate "$file" "$line" "SonarCloud typescript:S2871" "Sort without a compare function fails the reliability gate on new code; pass byCodeUnit() from frontend/src/utils/text.ts"
failures=$((failures + 1))
done < <( (( ${#sources[@]} )) && grep -HnE '\.(sort|toSorted)\([[:space:]]*\)' "${sources[@]}" || true )

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Match empty sort calls across line breaks

When an empty call is formatted across lines, such as values.sort(\n), grep processes each line independently, so [[:space:]]* cannot bridge the newline and this check reports success even though Sonar's syntax-based S2871 rule still reports the call. This lets the new CI guard pass while the required cloud gate fails; use a multiline-aware or syntax-aware matcher for comparator-less calls.

Useful? React with 👍 / 👎.

while IFS= read -r hit; do
[[ -z "$hit" ]] && continue
file=${hit%%:*}; rest=${hit#*:}; line=${rest%%:*}
char_code_allowed "$file" && continue

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Narrow the charCodeAt exception to the known call

Because char_code_allowed is checked only by filename, every future charCodeAt added anywhere in frontend/src/tree/model.ts is skipped, not just the documented FNV-1a use. Sonar will still inspect a newly added or changed call in that file and fail the required gate, while this guard reports success; scope the exception to the known hashing occurrence rather than the entire file.

Useful? React with 👍 / 👎.

echo " an encoder), add the file to CHARCODE_ALLOWED with its reason." >&2
annotate "$file" "$line" "SonarCloud typescript:S7758" "charCodeAt on a changed line fails the reliability gate on new code; prefer codePointAt, or allowlist the file with a reason"
failures=$((failures + 1))
done < <( (( ${#sources[@]} )) && grep -HnE '\.charCodeAt\(' "${sources[@]}" || true )

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Ignore non-code charCodeAt text

Because this searches raw source text rather than parsed calls, any comment, documentation example, regular expression, or string literal containing .charCodeAt( is counted as a finding even though Sonar ignores it. For example, adding a comment that explains why another API is preferable would make this CI step fail indefinitely; use a syntax-aware check or otherwise exclude comments and literals before matching.

Useful? React with 👍 / 👎.

… with it

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@hyperpolymath
hyperpolymath merged commit 688d16a into main Oct 23, 2026
9 of 10 checks passed
@hyperpolymath
hyperpolymath deleted the arena/030f31ad-metamanifold-webui branch October 23, 2026 13:49
@sonarqubecloud

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c455d14f28

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

# there the script would otherwise report OK having looked at nothing.
sources=()
while IFS= read -r -d '' tracked; do sources+=("$tracked"); done \
< <(git ls-files -z -- 'frontend/src/*.ts' 'frontend/src/*.tsx')

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Scan all TypeScript files analyzed by Sonar

When a comparison-less sort() or charCodeAt() is added to the tracked frontend/vite.config.ts, this pathspec omits it, so the local guard reports success while Sonar can still report the changed line. The repository has no source-scope configuration, and Sonar's default source scope is the project base directory, so the guard should scan all tracked TypeScript/JavaScript files in the analysis scope rather than only frontend/src (SonarQube Cloud analysis parameters).

Useful? React with 👍 / 👎.

Comment on lines +100 to +101
while IFS= read -r -d '' tracked; do sources+=("$tracked"); done \
< <(git ls-files -z -- 'frontend/src/*.ts' 'frontend/src/*.tsx')

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Fail closed when Git cannot enumerate sources

When this script is run from an exported source tree without .git, or when git ls-files otherwise fails, the process-substitution failure is never checked: Git prints a fatal error, sources remains empty, and the script exits 0 with an OK message. This contradicts the documented exit code 2 outside a repository and can make the guard silently inspect nothing, so source enumeration must be performed in a way whose exit status is checked before reporting success.

Useful? React with 👍 / 👎.

echo " Walk the string with codePointAt, which reads a surrogate pair as one" >&2
echo " code point. If the code really is defined over UTF-16 code units (a hash," >&2
echo " an encoder), add the file to CHARCODE_ALLOWED with its reason." >&2
annotate "$file" "$line" "SonarCloud typescript:S7758" "charCodeAt on a changed line fails the reliability gate on new code; prefer codePointAt, or allowlist the file with a reason"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Stop calling S7758 a reliability-gate failure

When S7758 is the only hit, this annotation tells contributors that charCodeAt fails the reliability gate, but the script's own header and the new documentation establish that S7758 is a code smell which does not affect the reliability rating and did not fail the measured cloud gate. The local job intentionally blocks it to keep the smell count at zero, so the diagnostic should describe that stricter repository policy rather than falsely attributing the failure to Sonar's reliability gate.

Useful? React with 👍 / 👎.

hyperpolymath added a commit that referenced this pull request Oct 23, 2026
…ings (#234)

This is #227's contribution, re-landed on current `main` with its review
findings fixed.

## Why not merge #227 directly

#227's branch point is `8b76b71`, which predates #222, #229 and #233 on
`main`, so its diff *also* reverted:

- NHX `[&&NHX:B=…]` capture threading in `parse_newick` (#222)
- `PLACEMENT_SINGLE_MIN_LWR` handling for single-candidate placements
(#222/#233)
- the `edge_number` graft walk fix (#229)
- `unplaced_reads` in the clade-cumulus diversity payload and its test
(#233)
- Canonical I-JSON writers (`jcs_json`/`ijson_read`) and the
`AbstractString` request boundaries in `routes/trees.jl`,
`routes/clade_cumulus.jl`, `src/server/server.jl`

Taking the union naively would have regressed all of that, so this
branch takes **only** what `main` is genuinely missing.

## What `main` is actually missing

`src/server/routes/cladistics.jl` calls `CC.asv_tip_name` (line 123) and
`CC.analyse_phylogeny` (line 211), and `bench/cladistics.jl` calls the
latter, but neither was defined after the #207/#206 merges — every
cladistics build and every compute job died with
`UndefVarError`/`MethodError`. Restored here as an indexed study-tree
layer alongside the existing `PhyloNode` API:

| Symbol | Purpose |
| --- | --- |
| `IndexedPhyloNode`, `PhylogeneticTree` | parents-precede-children node
vector + `tips` index |
| `parse_phylogenetic_tree` | strict Newick → `PhylogeneticTree`
(quoted/Unicode labels, comments, support labels, non-binary nodes) |
| `asv_tip_name` | content-addressed, tool-safe ASV tip names |
| `clade_counts` | nodes × samples cumulative roll-up |
| `unifrac` | raw samples × ASVs **or** an existing roll-up,
unweighted/weighted |
| `analyse_phylogeny` | the single call whose NamedTuple the cladistics
route writes to `results.json` |

The payload matches `CladisticsResult` in `frontend/src/api/types.ts`
exactly.

## Codex findings from #227

1. **P1 — `SubString` from the byte buffer.** `_ip_label!` and
`_ip_length!` called `SubString(p.bytes, first:last)` on a
`Vector{UInt8}`, which has no such method, so every ordinary unquoted
label and branch length raised `MethodError`. Labels/lengths are now
sliced straight into `String`s; the label return is an explicit `String`
instead of a `strip` SubString.
2. **P1 — graft walk read `u.edge_num`.** Already fixed on `main` by
#229 and not reintroduced: `edge_num` now only ever names
`QueryCandidate`'s field, and the new code touches no `PhyloNode` field.
3. **P2 — Faith PD accepted invalid cumulative counts.**
`_faith_pd_indexed` treated negative/`NaN` entries as absent (`> 0` is
false) and returned a plausible but underestimated PD. It now validates
finite, non-negative counts like `clade_counts` and `unifrac`.
4. **P2 — raw-count UniFrac overload missing.** `unifrac(tree, counts,
tip_names; weighted=…)` now exists and delegates through `clade_counts`,
matching the adjacent `faith_pd(tree, counts, tip_names)`.

## Also

- `bench/cladistics.jl` builds its tree with `parse_phylogenetic_tree`
(it calls `analyse_phylogeny`, which needs the indexed type).
- Dropped the duplicated `import ..Tools` in
`src/pipeline/phylogeny.jl`.
- New testset covers the parser (byte-buffer labels, multi-byte/quoted
labels, trailing comments, duplicate tips, negative lengths,
missing/extra `;`), the roll-up, Faith's PD including the new
rejections, both UniFrac variants and the raw-count overload, and the
JSON payload with the same row-major conversion the route performs.
- `docs/cladistics/clade-cumulus.md` documents the new API.

## Verification

- tree-sitter-julia parse of every touched file: clean (the two files
over the JS binding's 32 KiB limit were parsed in top-level chunks; the
inserted block parses standalone).
- No Julia runtime is reachable from the authoring sandbox (julialang-s3
and GitHub release assets are blocked), so the Julia suite is CI on this
PR.

#227 can be closed once this lands.

---------

Signed-off-by: Jonathan D.A. Jewell <6759885+hyperpolymath@users.noreply.github.com>
Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
hyperpolymath added a commit that referenced this pull request Oct 23, 2026
…N with 400 (#244)

## Change and reason

Three things, in the order the linked review asked for them.

### 1. PR #228's merge conflicts, resolved (and shown to be a no-op)

PR #228 (`arena/7805092e`) branched from `8df1041`; all six files it
touched
have since been rewritten on `main` by #231, #233, #234, #236, #237 and
#239.
Resolving the six conflicted files in favour of `main` produces a tree
that is
**byte-identical** to `main` (`git diff --stat origin/main` is empty),
so the
merge commit records the resolution and changes no content. Concretely:

* `ci.yml` - main publishes a per-assertion digest of every failing
validated-statistics test plus a log tail; #228's awk windows are
superseded.
* `src/analysis/clade_cumulus.jl` - main already reads
`PhyloNode.edge_number`
in `_graft_jplace_queries`, already sums `rk.subtree_pd` plus the node's
own
branch, and additionally recomputes an emptied clade from
`direct_count`.
* `src/server/routes/clade_cumulus.jl` - main already uses
`with_results_db`
and `String(strip(...))`, and deliberately does **not** renormalise
placement
like-weights by the retained total: `parse_jplace` already rejects
ratios
outside [0, 1], a multi-candidate placement already partitions its mass,
and
dividing a lone 0.6 candidate by its own total would move the 0.4 the
tool
  left unplaced onto the reported edge (#236/#237 report it as
  `unplaced_reads` instead).
* `src/server/routes/validated_statistics.jl` - main validates raw
tokens for
the whole body after `ijson_read` proves it I-JSON, and streams bounded
rows
  instead of materialising a DataFrame. Both are strict supersets.
* `test/unit/*` - main's suites are far larger; the one test #228 had
that main
lacked ("Nested-clade drag-and-drop counts each active branch once") is
  restored here.

### 2. Codex P2 on PR #228: leaf subtree PD

`_reaggregate_after_move` credits an active leaf with its own incoming
branch,
so a parent can sum `subtree_pd` alone and count each active edge once.
The
ordinary builder (`build_node`) hard-coded `0.0` for leaves, so
validating a
drag-and-drop changed the reported PD of leaves whose topology had not
changed
and made the proposed tree's metrics incomparable with the original.

Both builders now take a leaf's PD from `clade_faith_pd` - the function
the
internal-node branch already used - so the conventions cannot drift
again.
Internal nodes and inactive tips are unchanged.

### 3. panic-attack `InputBoundary`: malformed bodies are 400s

35 unguarded `JSON3.read(String(req.body))` call sites across eight
route files
raised a JSON3 parse error out of the handler, so a truncated or hostile
body
produced a 500. Every route now parses through `Server._request_json`,
which
throws the existing `BadRequest`. The helper copies `req.body`, because
`String(::Vector{UInt8})` can consume the buffer.

Rescuing that turned out to need one more step, which CI caught: Oxygen
builds
its chain as `custom(serializer(router))` and its serializer catches
every
handler exception and answers 500 before our middleware runs, so
`_bad_request_middleware` never saw the throw. Oxygen's error mapper is
the
hook inside the serializer, and it is already a dispatch point
(`handle_error(::ValidationError)` answers 400). Extending it for
`BadRequest`
keeps this server's `{"error", "message"}` shape and makes the test's
`status == 400` true.

### 4. pons asinorum: tree path building is linear

The six `string-concat-in-loop` hits in `TreeShapes.tsx` were real:
every node
appended to four shared strings inside a `useMemo` over the whole
layout, which
is quadratic in the node count on a view that renders every node. The
fragments
are collected per node and joined once; the emitted order is unchanged.

### 5. panic-attack `ProofDrift` and a guarded benchmark baseline

The `@test x isa Y` assertions the rule flagged were real tautologies:
they
passed for any string. The occupancy pair now asserts the text the code
emits
(`measured 201 KiB above the predicted 101 KiB`) and the evidence round
trip
asserts the ISO-8601 UTC stamp `save_document` writes. The advanced
zero-handling benchmark parsed its baseline outside any guard, so a
truncated
baseline surfaced as a JSON3 failure from inside the timing loop; it now
names
the file it could not read.

## Validation

- `cd frontend && npx tsc --noEmit -p .` - clean.
- `cd frontend && npx vite build` - clean (chunk-size warning only,
pre-existing).
- `cd frontend && bun test src` - **72 pass, 0 fail** (11 files).
- `bash scripts/check-governance-contract.sh` - **all 20 checks
passed**.
- `bash scripts/lint-sonar-reliability.sh` - clean.
- `bash -n` on every `scripts/*.sh` - clean.
- All four workflows parse as YAML; the DEED lints clean with the pinned
  `deed_lint.js` from `hyperpolymath/standards@75bd5994` (see below).
- **Not run locally:** the Julia suite (no Julia runtime in this
environment;
  the two changed Julia files are covered by CI's `Julia tests` job).

## Hypatia and K9/DEED conformance

* **DEED** - `MetaManifold-WebUI_chora.deed` lints clean with the exact
linter
the reusable lane runs (`bun 1-formats/deed/tools/deed_lint.js`). Note
the
stdlib Python port in the same directory is older and rejects `#u8`; the
JS
linter the workflow uses accepts it, so the deed is correct as written.
* **K9** - this repository has no `.machine_readable/contractiles/`
registry or
Mustfile, and `governance.yml` does not call `k9-contractile.yml`, so
the K9
contractile lane is not in scope here. What *was* wrong is that the deed
declared `:report-backs ".gitbot-fleet/report-backs/"` while the
directory
did not exist; it now does, with the envelope contract documented, and
the
grid keeps rendering those members orange until a real report-back
lands.
* **Hypatia** - the 43 critical findings are all
`cicd_rules/banned_language_file` for `frontend/src/**/*.ts`. That rule
is a
repo-side-unfixable policy finding: TypeScript's only carve-outs are
narrow
path prefixes (`bindings/typescript/`, `.d.ts`, `vite.config.ts`, ...),
none
of which cover an upstream-authored React frontend, and the rule cannot
be
  suppressed from the repository. Migrating upstream's frontend to
  AffineScript is an estate project, not a change this fork can make.
* **panic-attack / pons asinorum** - most remaining findings are stale
or
false positives at `main`: `api/events.ts` and `tree/model.ts` already
wrap
`JSON.parse` in try/catch (hardened in #239), the `DynamicCodeExecution`
  hits are `RCall.reval` (R source strings, not Julia `eval`), and the
`unreachable-after-jump` hits point at plain arrow-function bodies. The
two
classes that were real - unguarded request-body parsing and quadratic
path
building - are fixed above. The grid's residual after this PR: Hypatia
45
critical (all `banned_language_file` on upstream TypeScript), 61 high,
35
medium; panic-attack 6 high (all `RCall.reval` false positives), 7
medium;
pons asinorum 2 medium (`unreachable-after-jump` on a `useMemo` return
and a `useEffect` cleanup, both false positives).

## Scope

- [x] Focused on the linked review plus conformance evidence
- [x] Does not alter dependencies, lockfiles, generated assets, or
vendored files
- [x] Secrets, local paths, and run outputs are not committed

## Contribution checks

- [x] I followed CONTRIBUTING.md
- [x] My commits include `Signed-off-by:` (DCO) and the licence is
unchanged

---------

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: CodeRabbit <noreply@coderabbit.ai>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant