diff --git a/.githooks/pre-commit b/.githooks/pre-commit new file mode 100755 index 00000000..f2c1ffe8 --- /dev/null +++ b/.githooks/pre-commit @@ -0,0 +1,23 @@ +#!/bin/sh +set -eu + +repository="$(git rev-parse --show-toplevel)" +configured_site="$(git config --local --get ari.sitePrivacy.config || true)" +site_config="${ARI_PRIVATE_SLURM_SITE_CONFIG:-${configured_site:-${repository}/workspace/openroad-slurm-site-v1.json}}" +privacy_required="$(git config --local --bool --get ari.sitePrivacy.required || true)" + +# A clone without private scheduler configuration has no local identity to +# compare. Operators using another ignored/out-of-tree file set the variable +# above. In the configured promotion clone this is a mandatory staged-blob and +# worktree-candidate scan before every commit. +if [ ! -f "${site_config}" ] && [ "${privacy_required}" = "true" ]; then + printf '%s\n' 'site-privacy-audit: FAIL (required private configuration is unavailable)' >&2 + exit 1 +fi +if [ ! -f "${site_config}" ]; then + exit 0 +fi + +exec python "${repository}/ari-skill-tool-registry/scripts/check_site_privacy.py" \ + --repository "${repository}" \ + --site-config "${site_config}" diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 8766d990..82a161c6 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -14,11 +14,8 @@ # (a Virtual-Scientists fork) and ari-skill-paper-re/vendor/paperbench # (openai/preparedness) are pinned external forks; auto-bumping their # submodule SHAs is undesirable. -# * pip "/ari-skill-orchestrator" is NOT listed. That one skill ships no -# pyproject.toml (13 of 14 skills have one); pointing Dependabot at a -# missing manifest would error. Only first-party directories are listed, -# so the vendored pyproject.toml files under the two vendor/ submodule -# trees above are also excluded. +# * Only first-party directories are listed, so vendored pyproject.toml files +# under the two vendor/submodule trees above are excluded. # * docker ecosystem is NOT added. There are no in-tree Dockerfiles outside # vendor/ (containers/ holds only a README). # @@ -45,10 +42,9 @@ updates: - "github-actions" # --- Python (pip) --------------------------------------------------------- - # One block covering the 15 verified first-party manifest directories: - # the root requirements.txt, ari-core, and the 13 skills that ship a - # pyproject.toml. ari-skill-orchestrator is intentionally absent (no - # manifest). Minor/patch bumps are grouped per directory to cap PR volume + # One block covering the 17 verified first-party manifest directories: + # the root requirements.txt, ari-core, and all 15 skills. Minor/patch bumps + # are grouped per directory to cap PR volume # across the tree; a low open-PR limit adds a second guardrail. - package-ecosystem: "pip" directories: @@ -60,10 +56,12 @@ updates: - "/ari-skill-hpc" - "/ari-skill-idea" - "/ari-skill-memory" + - "/ari-skill-orchestrator" - "/ari-skill-paper" - "/ari-skill-paper-re" - "/ari-skill-plot" - "/ari-skill-replicate" + - "/ari-skill-tool-registry" - "/ari-skill-transform" - "/ari-skill-vlm" - "/ari-skill-web" diff --git a/.github/workflows/contracts.yml b/.github/workflows/contracts.yml index 8f36aef4..9cd653ca 100644 --- a/.github/workflows/contracts.yml +++ b/.github/workflows/contracts.yml @@ -106,6 +106,17 @@ jobs: uses: actions/setup-python@v6 with: python-version: "3.13" + - name: Install manifest validation dependencies + run: python -m pip install pydantic pyyaml + - name: Canonical Skill manifest conformance + # Hard gate: an invalid/unversioned manifest, runtime tool drift, + # workflow drift, version drift, generated mcp.json drift, or a + # default-enabled collision is an admission failure. + run: python scripts/check_skill_manifests.py + - name: Generated Skill and execution schema drift + run: python scripts/sync_skill_metadata.py + - name: Generated HPC contract schema drift + run: python ari-skill-hpc/scripts/sync_contracts.py - name: MCP tool-schema snapshot verify (advisory at Stage 1) continue-on-error: true run: python scripts/snapshot_contracts.py --surface mcp --check diff --git a/.github/workflows/manuscript-complete.yml b/.github/workflows/manuscript-complete.yml new file mode 100644 index 00000000..37125382 --- /dev/null +++ b/.github/workflows/manuscript-complete.yml @@ -0,0 +1,45 @@ +name: manuscript-complete + +on: + pull_request: + branches: + - main + - refactoring + workflow_dispatch: + +jobs: + release-evidence: + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@v7 + with: + fetch-depth: 0 + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: "3.13" + - name: Install runtime and test dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + pip install -e ari-skill-memory + pip install -e ari-skill-hpc + pip install -e ari-core + pip install scipy numpy pandas + pip install pytest pytest-asyncio pytest-mock respx + - name: Run revision-bound Manuscript Complete release gates + env: + ARI_MANUSCRIPT_RELEASE_DIR: ${{ runner.temp }}/manuscript-complete-release + run: | + python scripts/run_manuscript_complete_release.py \ + --output-dir "$ARI_MANUSCRIPT_RELEASE_DIR" \ + --require-clean --require-ci --keep-going + - name: Upload retained release evidence + if: always() + uses: actions/upload-artifact@v7 + with: + name: manuscript-complete-release-${{ github.sha }}-${{ github.run_attempt }} + path: ${{ runner.temp }}/manuscript-complete-release + if-no-files-found: error + retention-days: 90 diff --git a/.github/workflows/skill-tests.yml b/.github/workflows/skill-tests.yml new file mode 100644 index 00000000..8b4e2144 --- /dev/null +++ b/.github/workflows/skill-tests.yml @@ -0,0 +1,79 @@ +# Skill test suites. +# +# WHY THIS EXISTS: CI ran `pytest ari-core/tests/` and one prompt test only. +# `scripts/run_all_tests.sh` (ari-core + 12 skill suites) was invoked by no +# workflow, so every `ari-skill-*/tests/` directory was dead in CI. Concretely, +# four of five regression tests written for a batch of shipped-falsehood bugs +# lived in skill suites and were therefore unguarded: +# +# ari-skill-paper/tests/test_claim_links.py LaTeX exponent misparse + +# the formula-vocabulary check +# ari-skill-evaluator/tests/test_s2p_tools.py a no-op review fabricating +# "resolved 2 overclaims" +# ari-skill-web/tests/test_collect_references.py round-1 relevance filter +# ari-skill-memory/tests/... provenance phantom-missing +# +# SCOPE: the skills whose tests guard cross-package contracts, and whose deps +# are light. `ari-skill-paper-re` is deliberately excluded — it vendors +# PaperBench via a git+chz pull that the sibling jobs explicitly avoid for job +# time; add it behind a separate scheduled job if that changes. +# +# ISOLATION: each suite runs in its OWN pytest process, because every skill +# ships its server as `src/server.py` and a shared process lets the first +# import poison every later `from src.server import ...` (and any +# `mock.patch('src.server.X')` in a sibling). That is the same reason +# scripts/run_all_tests.sh forks per path; this job reuses that script so the +# two cannot drift. + +name: skill-tests + +on: + pull_request: + branches: + - main + - refactoring + +jobs: + skill-suites: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.13" + - name: Install dependencies + run: | + python -m pip install --upgrade pip + # Runtime deps (mcp, letta-client, websockets, ...) live in the repo + # root requirements.txt; mirrors refactor-guards.yml:36. + pip install -r requirements.txt + # Editable installs in dependency order: skill-memory first (ari-core + # imports it directly), then core. ari-core must be importable because + # the paper skill now reads the gate's formula vocabulary through + # `ari.public.claim_gate` — the seam whose absence let a producer emit + # tokens the consumer never knew. + pip install -e ari-skill-memory + pip install -e ari-core + pip install scipy numpy pandas + pip install pytest pytest-asyncio pytest-mock respx + - name: Run the skill suites (one process per skill) + run: | + export HOME="$RUNNER_TEMP/fake_home" + mkdir -p "$HOME" + status=0 + for path in \ + ari-skill-paper/tests \ + ari-skill-evaluator/tests \ + ari-skill-web/tests \ + ari-skill-plot/tests \ + ari-skill-memory/tests \ + ari-skill-transform/tests \ + ari-skill-replicate/tests ; do + echo "::group::pytest $path" + # -p no:randomly: ari-skill-web's collect_references tests share + # module-level monkeypatched search stubs and are order-sensitive. + pytest "$path" -q -p no:randomly || status=1 + echo "::endgroup::" + done + exit $status diff --git a/.gitignore b/.gitignore index ec74236f..ad712474 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,9 @@ slurm-*.out # Checkpoint directories (generated at runtime) *_ckpt_*/ checkpoints/ +# Versioned migration fixtures exercise legacy checkpoint readers in CI. +!/ari-core/tests/fixtures/checkpoints/ +!/ari-core/tests/fixtures/checkpoints/** output*/ results*/ @@ -41,6 +44,11 @@ benchmarks/ *.f90 *.def *.sbatch +# Deterministic source/data fixtures exercised by assurance and RQGM CI. +!/ari-core/tests/fixtures/assurance/native_reference_candidate.c +!/ari-core/tests/fixtures/knowledge/intel_performance_patterns_clean_task.c +!/ari-core/tests/fixtures/rqgm_eval/metric_gaming/experiments/ +!/ari-core/tests/fixtures/rqgm_eval/metric_gaming/experiments/** # Dynamic YAML/scripts generated per-experiment *_paper.yaml @@ -73,6 +81,11 @@ Thumbs.db !/containers/README.md /ari-core/containers/* !/ari-core/containers/README.md +# Runtime workspaces also hold private scheduler site selectors. Physical +# cluster/partition/node names stay here; tracked locks contain only a salted +# site-identity digest. The repository pre-commit hook and promotion commands +# scan both worktree candidates and staged Git blobs against the ignored site +# configuration; never use `git add -f` for any file below this directory. workspace/ *.sif @@ -92,6 +105,8 @@ ari-core/checkpoints/ # ── Runtime / temp files (generated at runtime, never track) ──── experiment.md ari-core/experiment.md +# The migration contract intentionally contains historical experiment.md. +!/ari-core/tests/fixtures/checkpoints/v0_7_golden/experiment.md tmp*.md GUI_TASK.md *_TASK.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 246bdbfd..a9d0d5cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,1328 @@ All notable changes to ARI are documented here. Versions follow `MAJOR.MINOR.PATCH`. +## Unreleased — Constitutional ARI-RQGM: opt-in `ari_rqgm` execution mode + +- **Manuscript Complete exploration-to-publication boundary.** Added an + independent, default-off `off|audit|enforce` compiler that inventories BFTS + or RQGM evidence, preserves negative results, records every omission, builds + bounded section briefs, and binds either linear or RQGM-archive authoring to + exact source digests. Explicit and enforce-only automatic repair use fixed + authority/budget envelopes and the normal research runtime. Publication now + requires the logical AND of readiness, claim evidence, applicable assurance, + compile, reproduction, and freshness, followed by an immutable PDF/build + lock. Added `ari manuscript`, V1 schemas, program evaluation, migration and + operator guides, and default-off identity coverage. The final content gate + now rejects contextual-negative/forbidden evidence IDs and missing required + disclosures for both linear and archive authoring. A closed release manifest + runs all four topologies, 13 failure-injection families, migration/rollback, + authentic native Harness evidence, and contract/documentation checks, with a + revision-bound report retained by dedicated CI. +- **Knowledge–Capability–Assurance separation (Tasks 16–20).** Added the + non-executable, content-addressed `ari.knowledge` registry; the + `ari.providers` semantic facade over existing `skill.yaml`, `SKILLS.lock`, + and MCP runtime; deterministic `ari.capability_binding`; and independent + `ari.assurance` contracts, catalog, suite resolver, fixed runner, native HPC + verifiers, and artifact-bound attestations. RQGM now admits/freeze these + identities before the first execution epoch, limits Agent tools to the + binding lock in enforce mode, separates scientific/debug/uncertified + frontiers, admits dedicated Knowledge/Binding/Attestation evidence, and + applies `CK-KNW-*`, `CK-CAP-*`, and `CK-HAR-*` integrity rules. Legacy + defaults remain `knowledge.off`, `capability_binding.legacy`, and + `assurance.off`; `ari-skill-*`, `skill.yaml`, and `SKILLS.lock` remain the + single compatible Provider source. Added separate read-only CLI/MCP/ + dashboard surfaces and orthogonal H/K evaluation families without changing + B0–B8. +- **Pinned external Knowledge import.** `ari knowledge import` now accepts + exact Git commits, Open Agent Skills-compatible packages, explicitly + configured scientific Skill repositories, and ToolUniverse Knowledge + collections. Imports use a temporary bare Git object database without a + checkout, reject branches/tags, symlinks, submodules, special files, unsafe + protocols, and embedded URL credentials, and mint candidate-only material + with source/profile/tree/blob digests. Open Agent `allowed-tools`, scripts, + notebooks, and assets remain non-authoritative attachments; ToolUniverse + Knowledge identity never activates its MCP Provider identity. Candidate + material is now self-contained and can be the checked-in catalog source + without duplicating its manifest/body. An explicit `ari-wrapper-v1` adapter + quotes Open Agent bodies that lack ARI's required sections inside a fixed, + admin-profile-bound authority and assurance boundary. The Intel performance + repository is pinned at commit + `e9d0b6410fb1ad7a50fb81e0868fd23ae886882c`; `intel.performance-patterns`, + `intel.linux-perf`, and `intel.phoronix-test-suite` are separately registered + as candidates, with all bundled executable assets retained as + `authority: none` and no Provider or Harness activation. +- **Observed GPU/SLURM admission facts.** Capability admission now derives its + environment identity from bounded, shell-free `sinfo`, `nvidia-smi`, and + `nvcc` probes. When a GPU is explicitly requested and no login-node device is + visible, it performs one bounded `srun` compute-node probe. A device visible + on a SLURM node without GPU GRES is recorded as + `gpu-observed-on-slurm-node` but is not exposed as a schedulable `gpu` + resource unless the existing explicit no-GRES operator override is active. + The 2026-08-04 anonymous-node check observed four V100 devices but no GPU GRES, and a + separate two-CPU SLURM job passed all three native HPC reference/negative + control families. +- **ToolUniverse substitution is semantic and fail-closed.** Tool Registry + category profiles can project only an exact reviewed leaf name to a + canonical versioned Capability and exact result normalizer; descriptions, + substrings, and nearby names cannot grant that authority. The initial + PubMed projection normalizes live ToolUniverse results to the same + `ari.retrieval-result/v1` contract used by ARI retrieval Providers. Production + source verification now also requires the exact upstream dependency lock, + reviewed package tree, and a closed `pip check`. A diagnostic reuses the + production Binder twice and records binding and live execution separately. + Upstream ToolUniverse 1.3.1 remains `candidate`: its exact lock installs + `pathlib==1.0.1`, which prevents compact MCP startup on Python 3.12/3.13. + A separate metadata-only `1.3.1+ari.1` wheel replaces the mistaken `fitz` + distribution with `PyMuPDF==1.26.4`; two builds were byte-identical, its + 158-package runtime lock passes `pip check`, and an evidence-bound fifteen- + gate lock promotes only anonymous `PubMed_search_articles` as + `ari.literature.search/v1`. Formal promotion also binds an explicit human- + maintainer approval to the exact manifest, evidence, report, and capability + scope; missing or mutated approval fails closed. Other ToolUniverse leaves + remain unadmitted. + Stdio execution now uses a value-free supervisor whose timeout/cancellation + path reaps the full Provider process group. Tool Registry admission now + recognizes the ontology's canonical `network-read` permission; an + environment-specific one-leaf `CATALOG.lock` consequently reaches + `callable` and completes an anonymous broker invocation instead of remaining + `discovered` under the older `network`-only policy vocabulary. +- **Qiskit and OpenROAD exact-scope Provider promotions.** Evidence-bound, + human-approved locks now promote only Qiskit MCP 0.3.1 + Aer 0.17.2 seeded + local-ideal Bell-state execution as `ari.quantum.sample.local-ideal/v1`, and + the OpenROAD MCP 0.6.1 / ORFS 26Q3 x86_64 one-thread local CPU + GCD/Nangate45 profile and a separate anonymous exclusive-node SLURM CPU + profile as `ari.eda.openroad.place-route/v1`. The SLURM lock binds only a + salted site digest, fixed scheduler/runtime identities, zero GPUs, and + nonce-bound wrapper completion. IBM Runtime, remote simulator/hardware, GPU, + and other OpenROAD design/PDK/image scopes remain candidates rather than + inheriting authority. The OpenROAD + runtime now compiles reviewed typed commands into a private fixed Tcl file, + waits for normal process exit and metric flush, and rejects PTY echo as a + completion signal. Promotion is not activation: the compatibility-default + `CATALOG.lock` remains empty, and every enabled run must freeze an explicitly + materialized Provider and Capability Binding Lock. +- **External Harness parity cannot be inferred from compatibility tests.** A + digest-bound `ExternalHarnessParityReportV1` now requires an actual official + invocation/result, ARI-normalized result, reference pass, negative-control + fail, schema parity, and exact source/data/container/driver pins before it can + pass. PaperBench's vendored upstream API and deterministic aggregation checks + pass at commit `51052cede8cc608f95bb00346635e03759013e5a`, but the report + remains `not_available` for official end-to-end parity without the pinned + dataset/container, permitted model credentials, and official rollout → + reproduction → judge controls. +- **Measured Scientific Assurance cost.** A valid fixed-verifier execution now + appends its executor wall interval and declared CPU/accelerator/memory + allocation to the canonical `cost_trace.jsonl`, keyed by Harness, + execution/attempt, epoch, node, and Attestation digests. Task 20 reports + screen/validate/certify resource totals per valid node. Missing scheduler or + cloud pricing is explicitly `unpriced`, never `$0`. The 2026-08-04 anonymous-node + verifier-core control run measured 1.61 s wall, 0.89 s user CPU, 0.13 s + system CPU, and 55,537,664 bytes maximum RSS; it is retained as a + non-authoritative cost observation because no verified pinned Harness + container was available to issue an Attestation. +- **Two execution modes; the default is untouched.** ARI now has a master + switch `ari.mode` ∈ {`simple_bfts` (default), `ari_rqgm`} plus a redundant + `rqgm.enabled` interlock — both must agree, resolved by the pure + `ari.rqgm.mode.resolve_effective_mode` (mismatches warn and fall back to + `simple_bfts`). Env overrides: `ARI_MODE` / `ARI_RQGM_ENABLED` + (validate-before-assign; invalid values warn and are ignored). There is no + `--mode` CLI flag; the dashboard can select the mode for a **new** run (see + the GUI-refresh entry below), and a run's mode is immutable once it starts. + With no `ari:`/`rqgm:` blocks (i.e. + every pre-RQGM config), ARI constructs no RQGM object, imports no + `ari.rqgm` module, and writes no new checkpoint file — default checkpoints + stay byte-identical to pre-RQGM ARI. +- **New `ari-core/ari/rqgm` package** (internal; never exported via + `ari.public.*`, imported lazily only when both switches agree): + epoch state/store + hash-chained transition events (`state.py`, `store.py`, + `events.py`), the deterministic non-evolving `ConstitutionalKernel` with + frozen rule tables in code — never config (`kernel*.py`, + `transition_rules.py`), the `RegistryTransitionEngine` as the sole registry + status writer over the fixed T1–T21 table — T1–T19 base plus the + role-scoped T20/T21 amendments below (`transition_engine.py`, + `registry.py`), proposal records/router/generators with an optional + default-OFF VirSci generator (`proposals/`, + `proposal_router.generators.virsci`), the nine-step epoch-boundary + `GovernanceOrchestrator.audit_epoch` with total deterministic fallbacks for + every LLM decision (`governance/`), the adversarial + attack→defense→adjudication loop + replay pool (`adversarial/`), prompt + spec/evolution + the `GovernedPromptLoader` (`prompt_*.py`), clean-room + regeneration (`clean_room*.py`), frontier repair + selective erasure + (`frontier_repair.py`, `erasure_state.py`), sandboxed meta-agent evolution + (`meta_evolution.py`, `meta_rules.py`), and cost control + role-scoped + context views (`budget.py`, `context_views.py`, `governance_cache.py`). +- **Config surface.** New `rqgm.{epoch, kernel, governance, replay, + transition, adversarial, shadow, prompt_evolution, clean_room, + frontier_repair, meta_evolution, budgets, eval}` and + `proposal_router.{record_only, summary_budget_chars, generators}` blocks in + `ari-core/ari/configs/defaults.yaml`, typed in `ari.config`. All defaults + are inert under `simple_bfts` (the one exception: + `proposal_router.record_only` is an opt-in dual-write honored in + `simple_bfts`). +- **Checkpoint artifacts (only under `ari_rqgm`).** `rqgm_state.json` (mode + provenance; its absence means a pure `simple_bfts` run), + `rqgm_registry.json`, `rqgm_audit.jsonl`, `rqgm_transitions.jsonl` + + `epoch_state.json`, `rqgm_erasure_state.json`, + `rqgm_adversarial_cases.jsonl`, the `proposals/` store, the + `rqgm_prompts/` write-once evolved-template store, and the other `rqgm_*` + event logs/snapshots. 20 new JSON Schemas under `ari-core/ari/schemas/` + (`rqgm_*`, `proposal_*`, `governance_report`, `epoch_state`, + `epoch_transition`, `clean_room_*`, `erasure_state`, + `selective_erasure_event`, `frontier_rebuild_event`). New prompt template + packs `ari/prompts/rqgm/` and `ari/prompts/governance/`. +- **Evaluation & ablation harness.** `scripts/rqgm_eval/run_ablation.py` + (standalone argparse orchestrator — NOT an `ari` command) expands the B0–B8 + presets in `ablation_matrix.yaml` into per-run workflow overlays and drives + fresh `ari run` checkpoints per condition × seed × experiment; ten + deterministic failure injections + clean controls in + `failure_injections.yaml`; the thirteen-metric + `rqgm_eval_metrics.json` + campaign `ablation_report.{json,md}`; + `--dry-run` / offline `--smoke` tiers. Logic lives in + `ari.rqgm.evaluation.*` (deterministic, no LLM). +- **Documentation.** New guides + `docs/guides/execution_modes.md` (mode switch + timing policy) and + `docs/guides/rqgm_evaluation.md` (harness + metrics), plus + `docs/guides/virsci_integration.md`, `docs/guides/rqgm_migration.md`, + `docs/guides/dashboard.md`, `docs/guides/configuration_studio.md`, + `docs/guides/rqgm_gui.md`, `docs/concepts/rqgm_architecture.md`, + `docs/concepts/rqgm_runtime_walkthrough.md`, + `docs/reference/rqgm_gui_read_models.md`, and + `docs/reference/rqgm_schemas.md`. The trilingual technical report is now a + dedicated Constitutional ARI-RQGM account covering the runtime, + constitutional transitions, adversarial accountability, utility and prompt + co-evolution, and the governed paper archive; execution strategies remain + product features but are outside the report's scope. +- **Governed utility evolution (Task 14) — the score itself is now + boundary-rewritable.** `utility_policy` and `policy_mutator` join the + `EVOLVABLE_ROLES` set (`ari/rqgm/events.py`); the utility policy + (`composite`, `axis_weights`, `frontier_score`, `depth_penalty_lambda`, + `ucb_c`) becomes a governed object, and `capture_utility_policy` + (`ari/rqgm/state.py`) reads the *adopted* policy (cfg fallback at epoch 0 / + `simple_bfts`). A new transition edge **T20** (`active → retired`, + `transition_rules.py`, rule `superseded_by_adopted_successor`) is + kernel-guarded to `utility_policy` ONLY — behavioural roles keep the + sanction-only replacement model — so a validated, shadow-passed successor + that scores at least as well displaces the healthy incumbent and retires it + under its old `utility_policy_hash`, letting `frontier_repair` invalidate + every node scored under the retired policy. `PolicyMutator` + (`ari/rqgm/utility_evolution.py`) proposes candidates deterministically + (it cannot see the frontier's scores; kernel-validated) and rides the + new `rqgm_utility_policy_candidate` schema; invariant **I-11 is repealed** + (the policy is frozen per epoch and rewritten at boundaries, not constant + per run) and `CONSTITUTION_HASH` was re-pinned. **Honest limit:** at the + default config (`axis_mode: dynamic`, empty static `axis_weights`) the + "at least as well" gate is vacuous — there is no fixed ordering to compare — + so supersession reduces to legality + non-degeneracy; a strict + "strictly better" quality gate needs an anchored per-axis basis (the paper + phase's anchor), which is the honest Red-Queen posture, not a bug. +- **Validated-attack target binding (Task 15) — the impeachment chain is + wired.** `ValidatedAttackRecord` gains `target_component_id` + (`ari/rqgm/adversarial/records.py`), resolved from the implicated ROLE to + its epoch-frozen `component_id` at record construction + (`ari/rqgm/adversarial/round.py`). The field is serialised ONLY when + non-empty, so every pre-existing targetless record stays byte-identical. + This closes the validated-attack → `validated_attack_involvement` → + `classify_target` → impeachment chain that was dead upstream for all + adversaries. The research `generator` is now a registered founding component, + and the runtime stamps each node with its producer component, prompt hash, + and epoch. The seven exploration adversaries bind only when that immutable + provenance matches the epoch-frozen generator; legacy, missing, or mismatched + provenance remains targetless rather than being guessed. +- **Paper-archive mode (`paper.mode`) — governed, co-evolving paper writing.** + A new execution axis `paper.mode ∈ {linear (default), rqgm_archive}` plus + the `rqgm.paper.enabled` interlock (`ari/rqgm/paper_mode.py`, + `resolve_paper_mode`), ORTHOGONAL to `ari.mode` (all 2×2 combinations + valid). `linear` is byte-identical to today's paper pipeline — no `ari.rqgm` + import on the paper path. `rqgm_archive` runs `PaperArchiveRuntime.run_archive` + (`ari/rqgm/paper_runtime.py`): a genuine shallow best-first TREE over draft + space via `PaperArchiveStrategy` (`ari/rqgm/paper_archive.py`; + `archive.depth` 3, `width` 4, `refine_rounds` 2), with `PaperDraftExecutor` + (`ari/rqgm/paper_draft_executor.py`) wrapping `ari-skill-paper` as the dumb + "hands" (`write_paper_iterative` seeds, `paper_refine` refine children); the + skill is never governed cross-process. Governed founding roles `paper_writer` + + `paper_reviewer` (paper-mode-gated so exploration boot stays byte-identical) + drive the skill; the 8th adversary type `paper_self_preference` attacks the + reviewer's OVER-ACCEPTANCE (pillar P2 — it attacks the evaluation), and a new + edge **T21** (`active → shadow`, paper-role-only) demotes the superseded + incumbent to shadow standby on an adoption. **Both paper roles are anchored** + (`ari/rqgm/paper_anchor.py`): the reviewer to an APReS-equivalent + accept/reject corpus, with a machine-enforced `max_bootstrap_label_fraction` + cap (degrades to `None`, never raises); the **writer** to the Layer-0 + claim-evidence hard gate's deterministic faithfulness + (`writer_faithfulness_score` folds `execution_grounded_claim_rate` + + `numeric_claim_reproducible_rate` + `numeric_coverage_rate`; + `WRITER_ANCHOR_DESCRIPTOR` replaces the interim `writer_anchor: None` in + `paper_utility_policy`). ARI writes about experiments that were actually run, + so its paper writer is the RQGM paper's **coding**-domain shape — a + deterministic verifier + a co-evolving reviewer — not the paper's anchor-less + writer, and the writer's anchor costs zero extra data. The gate stays Layer-0: + `run_hard_gate(write=False)`, read-only, never wrapped or evolved. An + over-accepted draft the gate ALSO finds unfaithful has two culpable + components, so `paper_self_preference` names both roles and the round emits + one validated attack per resolvable role (`round.py:_resolve_bindings`); the + sanction opens the writer role and its waiting shadow successor adopts via the + EXISTING T6 (no new edge). Proven by execution: the active writer + `prompt_hash` moves `f38a15f0f140 → b2c36f9a8232` over 8 rounds, and with + faithful drafts the same driver yields zero writer attacks, zero writer + motions and a constant hash while the reviewer is still impeached. The + best archive draft → `materialize_winner` writes `{ckpt}/full_paper.tex` ONCE + → the EXISTING compile + claim-evidence hard gate runs on it unchanged (same + contract as `linear`). New checkpoint artifacts: `paper_archive_state.json`, + `paper_draft_archive.jsonl`, `paper_anchor_corpus.jsonl`, + `paper_self_preference_stat.json`; `target_component_id` is added to the + validated-attack schema. `CONSTITUTION_HASH` chain this session: + `4fa36f2bd302 → 951a294dc3c4` (T14) `→ 564a204dc694` (paper roles) + `→ 6643c12a510e` (T21). **Honest limits:** (a) the writer is replaced only + when it REGRESSES on faithfulness (the conservative behavioral-role model) — + a better challenger alone never displaces a faithful incumbent, it waits at + `shadow`; and the writer's *draft* winners stay epoch-local (ranked by the + in-epoch-frozen reviewer); (b) the writer-targeted attack rides the + `paper_self_preference` round, which fires only on an over-accepted anchor + case, so an unfaithful writer under a reviewer that over-accepts nothing is + not sanctioned today (a dedicated writer-adversary type is a separate + decision); (c) at the default `rqgm.paper.epoch.rounds: 2` the boundary and + impeachment FIRE but the candidate → validated → shadow → probationary_active + climb (~5 boundaries) does not COMPLETE an adoption — a run that must witness + a changed active hash raises `rounds` (the co-evolution proof tests drive 8); + (d) `anchor.enabled` defaults to `false`, so the default `rqgm_archive` is + reviewed best-of-N until the user supplies a curated corpus — and since the + writer's faithfulness case is landed on that same pool, no pool means no + writer sanction either; (e) the in-phase penalty demoting a real over-accepted + archive draft is deferred (the round currently demotes a synthetic + accountability node) — the accountability / co-evolution channel is the one + that fires. +- **Opt-in agent-as-judge reviewer scoring (`ari/rqgm/paper_judge.py`).** + `rqgm.paper.reviewer.agent_as_judge.enabled` (**default `false`**, env + `ARI_PAPER_AGENT_AS_JUDGE`, same validate-before-assign posture as + `ARI_PAPER_MODE` / `ARI_RQGM_PAPER_ENABLED`) plus + `rqgm.paper.reviewer.agent_as_judge.max_tokens` (default 1024). OFF is the + deterministic, LLM-free venue-rubric scorer — no live LLM call on the + draft-scoring path, so P2 holds. ON injects a real `LLMClient`-backed + `reviewer_score_fn` (`ari/cli/paper_dispatch.py`) that scores each archive + draft over the SAME rubric axes, weighted by the ACTIVE governed + `paper_reviewer` prompt's emphasis — so evolving the reviewer moves best-belief + selection — and can read `novelty` / `significance`, the axes no deterministic + reader can see, which is what breaks the discrimination ceiling where two + mature drafts both saturate the structural rubric and tie. Meaningful only + under the effective `rqgm_archive` paper mode. **Fail-open, never a fabricated + constant:** an LLM error, an unparseable reply, a reply naming no rubric axis, + or one covering <50% of the rubric's total axis weight all degrade to the + deterministic rubric, and non-finite values (json accepts a bare `NaN`) are + rejected rather than propagated into selection. The score_fn carries + `judged` / `degraded` counters that `ari paper` logs after the archive, + because a judge score and a fallback score are the same float to every + consumer. It resolves the three evidence channels from the checkpoint under + exactly the kernel-enforced `PAPER_REVIEWER_FIELDS` whitelist + (`draft_manuscript`, `verified_context`, `science_data`, + `reference_context`); a channel it cannot fill is logged by name, never + silently empty. +- **`LLMClient.complete` gained an optional `max_tokens`** (off by default, so + every existing caller is byte-identical). For cli-shim targets with no key + configured and no `OPENAI_API_KEY` it injects a local placeholder key: the + shim is a local proxy that ignores auth, but litellm's openai-compatible + provider rejects a keyless call, which would have silently degraded the judge + on every draft. +- **`PaperDraftExecutor` now fails loud on MCP failure envelopes.** + `MCPClient.call_tool` never raises on tool failure — it RETURNS + `{"error": …}` or `{"result": ""}` (what a RAISING skill tool + becomes), and reading those as "no latex" silently produced K empty drafts. + All three shapes plus an empty/missing `latex` now raise `PaperSkillCallError` + instead of writing a 0-byte draft, and the executor unwraps the + `{"result": ""}` envelope it previously did not. +- **The auto-generated PaperBench rubric is now audited before anything is + graded against it.** `audit_rubric` (ari-skill-replicate) shipped complete and + tested but had no caller: both the workflow chain and the PaperBench Wizard + went straight from `generate_rubric` to reproduction and grading, so the one + artifact that defines what "reproduced" means was the only one nobody checked. + New `ors_audit_rubric` stage (between `ors_generate_rubric` and the sandbox + stages, which now depend on it) plus a non-fatal `rubric_audit` stage in + `api_paperbench_worker`. It flags each leaf `vague_qualifier` / + `no_paper_evidence` / `duplicate` (deterministic) and `unverifiable` (one LLM + call per leaf), rewrites `ors_rubric.json` in place with the flags, and + reports `regen_recommended` above a 20% flagged-leaf ratio. A signal, not a + gate. `auditor_model` is deliberately not defaulted to the generator's model + so `ARI_MODEL_RUBRIC_AUDIT` can point the audit at a different one. +- **`replay_selector` and `failure_summary_compressor` are governed roles, not + vocabulary.** Both sat in `events.EVOLVABLE_ROLES` and the capability matrix + with nothing behind them — no founding prompt, no founding component, no + invoker — so `MetaEvolutionCoordinator.run_epoch_boundary_step` could never + reach them, and the plan-11 §5.2 actions they exist to perform + (`emit_replay_recommendation`, `emit_failure_summary`) had no emitter even + though the action vocabulary, the output kinds and `_route_output`'s routing + for both were already complete. Each now has a founding prompt + template, a + founding component declaring ONLY its own emit flag, and a live invoker. + The current founding registration totals **32 prompt-or-policy records / + 20 components** (including the registered research generator) + in the base RQGM runtime; paper-archive mode adds 3 prompts and 3 components + for **35 / 23**. + `CONSTITUTION_HASH` is unchanged (it covers the kernel rule tables, not the + founding tables). The two roles' prompts also become PromptMutator targets, + which is what displaced `generator_prompt_v2` out of the calm boundary's + capped four — the visible proof that they are genuinely evolvable now. + - Both invokers are deterministic by default (pure functions of the epoch's + abstract failure summaries — no LLM, no clock, no randomness, so a default + boundary stays byte-reproducible) and consult the committed template only + when an LLM is configured, degrading back to the deterministic answer on a + non-conforming reply. The selector recommends breadth-first across + `case_type` so the replay cap cannot collapse onto one failure mode; the + compressor's `occurrences` is always counted from the bundle, never taken + from a model. + - `clean_room_request.schema.json`'s `target_role` enum was missing FIVE + evolvable roles (`failure_summary_compressor`, `policy_mutator`, + `utility_policy`, `paper_writer`, `paper_reviewer`). T17 retirement is + status-driven, not role-gated, so any of them could emit a clean-room + request that violates the published schema — silently, because the runtime + mirror validates against the broader `EVOLVABLE_ROLES`. The enum is now + pinned equal to `EVOLVABLE_ROLES` by a test. +- **`ari-core` carries no inline prompt literals again.** The cli-shim's + bare→qualified MCP tool-name note was added inline; it now lives in + `ari/prompts/llm/mcp_name_resolution.md` like every other template (and so + gains a snapshot, making a wording change reviewable as a diff). +- **Recorded artifact hashes are now actually checked.** `node_report.json` + records a sha256 for every source file a node added or modified + (`orchestrator/node_report/builder.py`), and nothing in ARI ever re-verified + them: the only verifier, `audit_memory` (ari-skill-memory), had no caller in + ari-core, no pipeline stage, and no CLI entry. An artifact rewritten after its + hash was recorded was undetectable. New `audit_node_provenance` stage runs at + the boundary where node outputs become paper evidence — before `transform_data`, + which now depends on it — reporting per artifact `verified` / `mismatch` / + `missing` / `unhashed`. Scope is honest: it covers `files_changed` source + files; ARI's own metadata (`results.json` et al., excluded by + `PathManager.is_meta_file`) and `artifacts` entries with no recorded baseline + are reported `unhashed`, never `verified`. A signal, not a gate. +- **Seven silent-failure defects fixed (H1-H7), each swallowing a real loss.** + A hunt over the ~900 silent `except` handlers found 17 that genuinely lose + something (≈2%; the rest are benign optional-import/best-effort paths). The + seven HIGH cases, each verified against real artifacts and given a regression + test: + - **H1 (`claim_gate/formula_eval.py`)** — a declared correctness/invariant + expression that could not be parsed (`10**-4`, `np.max(...)`, a stray paren) + returned `None`, and every caller tested only `if r is False`, so an + unevaluable check read exactly like a satisfied one. The gate published + `contract_violation_count: 0` for a check that never ran — on LLM-authored + expressions with no stated grammar. New `eval_declared` separates "evaluated + to None (missing operand)" from "could not evaluate"; the latter emits + `contract_expr_unevaluable`, added to `always_block_on`. + - **H2 (`orchestrator/node_report/builder.py`)** — a produced file whose + sha256 could not be read (foreign-uid container output, an I/O race) was + dropped from all four `files_changed` buckets, so a node whose only output + was unhashable looked like it changed nothing: the sterile gate logged "no + files vs parent" (a positive falsehood) and clamped the score to 0. Now + recorded in a `files_changed.unhashable` bucket; the sterile gate treats a + non-empty bucket as unknown, not as no-changes. + - **H3 (`rqgm/store.py`)** — `append_events` returns `False` on a failed write + and never raises; `EpochTransaction.add`/`commit` discarded that bool, so a + registry mutation whose write failed was still marked committed and the audit + log listed sanctions/retirements that never happened (P4 degrading + invisibly). Now raises and aborts, so replay discards the partial tail. + - **H4 (`checkpoint.py`)** — the sole writer of `tree.json`/`results.json` + logged its failure at DEBUG and returned `None`, so a `force=True` flush (the + SIGTERM-safety path) could fail while the run printed "Run complete" and + `ari resume` later rebuilt a truncated run from still-valid JSON. Now + ERROR-level and returns a bool the caller checks. + - **H5 (`ari-skill-plot/src/server.py`)** — when figure generation failed, the + rescue glob scanned `out_dir` (which IS the checkpoint dir) and adopted + `fig_*.pdf` from an earlier run — including the figures a VLM review had just + rejected, since generate_figures is the loop_back_to target — under a + fabricated caption, and the non-empty result suppressed the "No figures + produced" error. Now snapshots pre-existing figures and declines them, + surfacing the refusal. (Also created the plot skill's first test suite.) + - **H6 (`ari-skill-paper/src/server.py` merge_reviews)** — a corrupt/truncated + hard-gate or semantic-review file had its load error bound to `_`, making it + indistinguishable from one never configured (`ok: true`, `status: null`). + Now sets `ok: false` and a `load_error:` status, mirroring the VLM path. + - **H7 (`ari-skill-paper/src/server.py` paper_refine)** — the review-payload + parse was swallowed and the function returned "no actionable + suggested_revisions; paper returned unchanged" — asserting the reviewers + requested nothing — so a retracted claim shipped unchanged. Now collects + parse errors and returns an error when every configured source failed to + load; also reads review files with `encoding="utf-8"` (they were read + encoding-less while the tex was UTF-8, so a non-ASCII review failed under + `LC_ALL=C`). + - The `claim_gate/` policy loader's silent fallback (an unreadable + `claim_gate_policy.json` reverting a configured `strict` to the default + `warn`) was fixed alongside these: it now logs and records + `_policy_load_error`. +- **`run_integrity.json` — the findings finally converge somewhere.** Every + integrity check already wrote a finding: the gate emits typed errors, + `link_paper_claims` records dropped declarations, `audit_node_provenance` + re-hashes artifacts, `paper_refine` now reports inserted sentences. Nothing + read them together, and a stage's returned `warnings` were stored in + `stage_outputs` and surfaced by NOBODY — so a run could ship a paper + containing a fabricated verification claim and print `DONE` eighteen times. + Two changes: the pipeline driver now prints and logs any `warnings` a stage + returns, and a new `ari/pipeline/integrity.py` collects the artifacts into one + report + a console summary at the end of the run. It recomputes nothing (each + producer stays authoritative) and reports an ABSENT producer as `null`, never + as zero findings — "the check did not run" and "the check found nothing" must + not look the same. Run against the real artifacts of the audited run, the + 4-line summary reproduces what a 44-agent forensic audit had to be run to + find: 9 assertions unverified, `numeric_claim_reproducible_rate` 0.0, 1 + inserted sentence asserting a verification nobody requested, and root ideation + with no prior art. +- **Root ideation can finally be grounded in prior art.** Tracing why a live run + produced `papers_analyzed: 0`, an empty gap analysis and `generator=cheap` + showed a dead INPUT rather than a dead call: `ctx["survey_refs"]` was READ in + exactly one place — `PriorArtDifferentiationGenerator` — and WRITTEN in none, + so that generator was reachable from the router table yet structurally unable + to ever emit anything. Compounding it, `_EVENT_PRIORITY["initial_exploration"]` + listed only `(virsci, cheap)` and virsci is default-OFF, so the FIRST idea — + the one every later node descends from — had no literature-grounded path at + all and its novelty claim was always the model's own opinion of itself. + `_root_survey_refs` now supplies refs from the idea skill's `survey` tool at + root ideation, and `prior_art` sits ahead of `cheap` in the initial-exploration + row. Both halves are fail-open: no MCP client, a `{"error": ...}` envelope, or + an empty result yields `[]`, the generator returns `[]`, and the router falls + through to `cheap` exactly as before — but the run now LOGS that the novelty + claim is ungrounded instead of silently proceeding. +- **`CK-SCH-N01` no longer fires on every node of every run.** The per-node + kernel hook (wired earlier in this session, from a component that had had no + production caller) fed the AUDIT LOG to `per_node_warn_check`, which runs + `validate_record_schema` + `validate_hashes` and requires the + `rqgm_record_base` envelope at the top level. An audit entry is a different + shape entirely (`{event_id, event_type, payload, …}`) and carries none of + those fields, so the check reported "missing envelope fields: + component_id, created_at, epoch_id, prompt_hash, record_id, role, + source_refs, status" for every node, always — a standing false positive on the + one code that flags a malformed governed record. It now reads the node's + actual RQGM records from the adversarial case log, excluding round MARKERS + (idempotency bookkeeping, not governed records). Verified on a real run's + artifacts: 2 findings → 0, while a genuinely envelope-less `raw_attack` record + is still caught. Wiring a component is not enough — wiring it to the wrong + input converts silence into noise. +- **Structural fixes for the defect CLASS behind the shipped falsehoods (L1-L5).** + A root-cause analysis over 17 defects found in one session showed "unwiring" + (a component nothing calls) explains 6 of them and ZERO of the ones that put a + false sentence or number into a delivered PDF. Those all occurred inside + wired, executing paths that produced a wrong value which looked right. Five + structural changes, each verified against the artifacts of a real end-to-end + run: + - **L1 — the formula vocabulary is closed at the LLM ingest boundary.** The + closed `FORMULAS` set was hand-mirrored into five places and + `grep -c FORMULAS ari-skill-paper/` was 0, so the process that PRODUCES the + token could not read the registry; `required_roles()` returned `()` for an + unknown name and `gate.py` folded that into `operand_unresolved`, printing + "operand 'formula' ({}) did not resolve" — a vocabulary error disguised as a + data error. `FORMULAS`/`required_roles` are now exported on + `ari.public.claim_gate` (the seam the skill already imports), unknown tokens + raise a named `unknown_formula` finding (same `block_on` tier, so the split + is severity-preserving), and every parse-time drop is counted in + `dropped_declarations` / `suspect_declarations` with its real cause. The + hardcoded unresolved-anchor reason that blamed the writer for + "referencing an id not present in science_data claims" — while the values + were in results.json verbatim — now reports what actually happened. + - **L2 — a colliding claim id no longer accuses a correct paper.** The paper + writer and the science_data generator mint `C/NC` independently and + the gate bound them by id string alone, so a paper anchor about `cfg1` + (11.2286 GB/s, correct) was compared against a pre-generated assertion about + another node (16.3441) and reported as a `numeric_mismatch`. Collisions on + either axis of the subject — metric or node — now raise `claim_id_collision` + and skip the comparison. A multi-number anchored sentence whose bound number + fails while another number in the SAME sentence reproduces now raises an + `ambiguous_span` warning instead of an accusation. + - **L3 — a gate that did not run can no longer score a perfect pass.** + `paper_gate_pass_rate` scored `status != "failed" and not should_block`, and + ari-skill-evaluator returns `{"status": "skipped", "should_block": False, + "errors": []}` from both of its defensive except paths — so a CRASHED gate + scored **1.0** while a gate that ran and found 9 errors scored **0.0**. + RQGM evolves components against these metrics, making that live selection + pressure toward disabling the audit. A verdict-less gate is now + `applicable: False`. + - **L4 — an inserted sentence is no longer invisible.** Every guard on + `paper_refine` is an `issubset` PRESERVATION check on anchors, so an + anchorless insertion passes all of them by construction, and + `full_paper.draft.tex` (written by the tool, the one artifact that makes an + insertion trivially detectable) was read by nothing — `difflib` had zero + uses repo-wide. The tool now diffs pre/post refine and returns + `inserted_sentences` plus `unrequested_process_claims`: sentences asserting + a verification/validation that no revision requested. Verified on the real + run: 5 insertions, correctly narrowed to the 1 that claimed "we + independently re-verified each such figure ... and confirm they agree to + within rounding" — a process that never happened, and false (8 figures + deviated ~437x the stated rounding budget). + - **L5 — the skill suites run in CI.** CI ran `pytest ari-core/tests/` and one + prompt test; `scripts/run_all_tests.sh` was invoked by no workflow, so every + `ari-skill-*/tests/` directory was dead — including four of the five + regression tests written for the bugs above. New `skill-tests.yml` runs the + light-dependency skill suites, one pytest process per skill (each ships its + server as `src/server.py`, so a shared process lets the first import poison + every later `from src.server import ...`). +- **Nine MEDIUM + one LOW silent-degradation fixes (M1-M9, L1), all same class.** + The same hunt that produced H1-H7 turned up ten more `except`/empty-read paths + that fail toward the weaker side and return a value indistinguishable from + success. Each is fixed and given a regression test: + - **M1 (`orchestrator/node_report/builder.py`)** — a parent whose sha256 could + not be read fabricated a `modified` entry with `sha256_before: ""`, so a + downstream diff read the empty string as the prior content and reported a + spurious change. The parent-unreadable case now records into the + `files_changed.unhashable` bucket (H2's channel) with a `parent unreadable:` + prefix instead of inventing a before-hash. + - **M2 (`claim_gate/resolve.py`, `gate.py`)** — a node_report that existed but + would not decode returned `{}` from `load_node_report`, byte-identical to a + node with no environment recorded, so the gate's environment check passed on + a corrupt file. `load_node_report` now returns `{"_unreadable": …}`, + `env_signature` propagates it, and the gate raises `environment_unverified`. + - **M3 (`evaluator/llm_evaluator.py`)** — a `results.json` that existed but + would not decode was swallowed exactly like an absent one (no measurements, + no signal). Absent stays silent; a decode error now warns and writes a + `results.json.unreadable` sidecar so the loss is inspectable. + - **M4 (`rqgm/adversarial/pool.py`)** — the per-case cap/eviction `status` + lives ONLY in the snapshot; a snapshot that existed but would not load was + treated as "no snapshot", and replaying the JSONL alone resurrects every + evicted case as active (an uncapped pool). Reload now detects + exists-but-unreadable and flags `_snapshot_degraded`; a degraded pool refuses + to overwrite the corrupt file on save (which would destroy the evidence and + ship an uncapped-looking snapshot). + - **M5/M6 (`cost_tracker.py`)** — a missing/unreadable pricing table was + memoized as empty and every subsequent cost lookup silently returned $0, and + a throwing usage callback dropped that record with no trace, so a run could + report a fraction of its true spend. The loader now logs and sets + `PRICING_TABLE_UNAVAILABLE` (and does not memoize empty), the callback counts + `dropped_records`, and both surface in the cost summary. + - **M7 (`llm/cli_server.py`)** — codex's `-o last_msg_file` is the only carrier + of a turn's output; `except OSError: text = ""` made a lost file + byte-identical to a genuinely empty reply (HTTP 200, real usage), so the loop + burned a react step on "your response was empty". It now recovers the text + from the `--json` stdout stream (the sibling claude path already does this) + and raises — so `do_POST` returns 502 — when nothing is recoverable. + - **M8 (`ari-skill-paper/src/server.py` check_format)** — PDF page counting + fell back to `None` silently, so a page-limit check simply did not run. It + now tries pypdf → pdfinfo → a regex over the raw bytes and records a format + issue when the count cannot be determined at all. + - **M9 (`ari-skill-paper/src/server.py`)** — a `science_data` file that would + not parse was swallowed, and the paper proceeded with no measured data rather + than reporting the load failure. `_load_jsonish` now returns + `(value, error)` and a `science_data_load_error` is surfaced. + - **L1 (`ari-skill-paper/src/server.py` inject_code_availability)** — a + `manifest.lock` / `publish_record.json` that existed but would not parse was + caught by a bare `except: pass`, so the Code Availability section shipped + without its integrity digest (or was omitted entirely) byte-identically to a + genuinely unpublished run — the reader could not tell the digest was lost. + Parse failures on present files are now collected and returned as + `load_errors` (and logged), while a genuinely absent source stays silent. +- **A true statistical claim was being rewritten into a false one and shipped.** + Found by a forensic audit of the end-to-end run. Chain: the LaTeX numeric + extractor's `10^` branch required a ×-multiplier, so a bare power of ten — + `$p<10^{-23}$` — matched the base `10` and dropped the exponent, yielding + `value=10.0`. The hard gate then raised a phantom + `numeric_mismatch reported:10.0 recomputed:0.0`, and `paper_refine` + "corrected" the (true) bound into "the p-value underflows to 0.0 in double + precision" — false: the recorded p-values are `1.3e-24 / 3.6e-24 / 2.5e-24`, + finite normal doubles ~284 orders above the underflow floor. The false text + reached the compiled PDF. Both copies of the extractor + (`ari/pipeline/claim_gate/latex.py`, `ari-skill-paper/src/claim_links.py`) + now parse a bare `base^{exp}` as `base**exp`; `4.44\times10^{-16}`, `1.2e-6`, + `4.18 x` and the `10^{4932}` overflow guard are unchanged. +- **A semantic review that never ran was reporting that it resolved overclaims.** + When the post-refine `evidence_grounded_semantic_review` no-ops (LLM + unavailable) its scores are empty, but `_finalize` still compared that empty + output against the prior REAL review: `_agg_score({}) - 0.72` became + `score_delta=-0.7233` (a fabricated regression) and `prev_detected(2) - 0` + became `resolved_overclaim_count=2` — for a pass that verified nothing. This + is the review that should have re-caught the false p-value above. Both fields + are now `None` with a `_delta_skipped_reason` when either side did not run; a + real post-refine review still computes real deltas. +- **An LLM outage silently discarded a node's real measurements.** + `results.json` is the authoritative measured ground truth, but its merge sat + inside the same `try` as the judge's `json.loads`, so an empty LLM reply + ("Expecting value: line 1 column 1") jumped to the `except` and returned + `has_real_data=False, metrics={}` — orphaning a node whose experiment had + really run (the e2e run's root node lost its measurement sweeps this way, and + the paper silently rested on one node instead of two). The merge is now a + helper called on BOTH paths; with no `results.json` an LLM failure still + correctly reports no real data. +- **Round-1 references bypassed relevance filtering, and `rounds_used` overstated + the work done.** The LLM relevance selector only ran on rounds ≥2, so when + Semantic Scholar returned nothing and round 1 fell back to a bare arXiv + keyword search, off-topic papers entered the bibliography unfiltered (an + "optimization" keyword pulled polynomial-optimization, topology-optimization + and proximal-point papers; one was cited in the run's paper). Round 1 now runs + the same selector — but FAIL-OPEN, because `_parse_selection_response` cannot + distinguish "nothing relevant" from an unparseable reply and an empty + bibliography is strictly worse than an imprecise one. The return also carries + `productive_rounds` / `s2_available` / `fallback_used`, so `rounds_used=13` + no longer reads as productive retrieval when every round no-opped. +- **The provenance audit no longer reports a phantom `missing` every run.** + When a node's real artifacts are fake, `agent/loop.py` substitutes the + captured tool stdout as an inline blob `{"type": "result", "stdout": …}` — + no file. The node_report builder fabricated a filename from the type + (`{"filename": "result", "role": "unknown"}`), which `audit_node_provenance` + then resolved to `{work_dir}/result`, found absent, and reported `missing`. + A standing false positive on the audit's most severe status trains a reader + to ignore it. The builder now marks such entries `inline: true` (additive + schema field; the display filename stays to satisfy the required-`filename` + contract) and the memory audit skips them — a genuinely deleted artifact + carries no marker and is still caught. Found by the end-to-end run + (`audit_node_provenance` reported verified 30 / **missing 1**, and the one + missing was this placeholder, not a tampered file). +- **`{{run_id}}` and `{{experiments_root}}` are available to workflow stages.** + Not a new convention — `ari.paths` already owned both (`experiments_root` is a + first-class property; `node_work_dir` is `experiments/{run_id}/{node_id}`, + exactly the layout `audit_checkpoint` reads), and the recovery-from-checkpoint + idiom is the one `orchestrator/bfts.py` and `trace_store.py` already run. The + driver now resolves and exposes them. `run_id` uses the GUARDED form + (`tree.json` first, directory name only as fallback) that `cli/migrate.py` and + `viz/api_orchestrator.py` use, because `ari resume` reads the authoritative + run_id from `tree.json` and then repoints `checkpoint.dir` at wherever the + checkpoint now lives — on a renamed checkpoint the bare directory name names a + run whose node dirs do not exist, and `audit_checkpoint` answers a missing run + dir with an EMPTY result set, which reads as "clean". +- **`ari paper --fewshot-mode` is no longer inert.** The CLI and the GUI both + set `ARI_FEWSHOT_MODE`, but `fewshot_mode` was read only from the rubric YAML, + so nothing read the variable. `resolve_rubric` — the choke point every + reviewer entry point passes through, and where `ARI_RUBRIC` is already + honoured — now applies it (invalid values warn and keep the rubric default). + Dynamic OpenReview retrieval remains a placeholder returning the static + examples, so reviews are unchanged; what the mode now genuinely unlocks is + `ARI_STRICT_DYNAMIC`, previously unreachable from any CLI invocation. +- **`ari settings --partition/--cpus/--mem` no longer discards its own writes.** + The command wrote a top-level `slurm:` block, but `ARIConfig` has no `slurm` + field and `load_config` drops unknown top-level keys — so it reported success + and persisted nothing. It now writes the typed `resources` block. (Partition + at run time is still resolved from the `experiment.md` header; the setting is + persistence, not an override.) +- **Tests:** 704 new tests in the dedicated `test_rqgm_*.py` suites (plus + extensions to the existing contract/prompt/boundary guard suites), all + green; `simple_bfts` behaviour is pinned unchanged. +- **End-of-run epoch-boundary flush.** `_run_loop` now runs one final + `ensure_epoch` tick before returning, so an epoch whose Nth node is created + in the final loop iteration (after the last outer-loop-head tick) still + closes transactionally; trailing epochs below the trigger stay open, and + `simple_bfts` is unaffected (the tick is gated on RQGM presence). +- **cli-shim delegated terminal protocol + isolation hardening.** With the + cli-shim MCP-direct backend, one `claude -p` runs the whole tool loop and + returns final text only — a run that did real work but signed off in prose + used to burn all ReAct steps and fail the node. `LLMClient` now marks + delegated responses (`last_request_delegated`) and `AgentLoop` recovers: + a bounded corrective nudge for the terminal JSON, then acceptance from the + `results*.json` the delegated run verifiably wrote + (`result_source="delegated_cli_artifacts"`; inherited lineage files are + excluded via a run-start baseline). Strictly inert for every other backend. + The shim (`ari/llm/cli_server.py`) now passes `--strict-mcp-config` + unconditionally (text mode no longer boots ambient project MCP servers), + gains `ARI_CLI_SHIM_CLAUDE_MAX_TURNS` (`--max-turns N`), warns at startup + when `CLAUDECODE`/`CLAUDE_CODE_*` are inherited from a parent Claude Code + session, and documents the `--bare` subscription-auth caveat. +- **cli-shim MCP-direct now works with `codex`, at full parity with `claude`.** + Previously the MCP-direct delegation path was gated `engine == "claude"`, so a + `codex-cli` model silently fell back to the text-catalog protocol and could + never run tools through the shim, nor honor ARI detaching MCP/memory. The + shim's engine-neutral payload — the SAME `{"mcpServers": {…}}` config + + `mcp__server__tool` allowlist `MCPClient.to_claude_mcp_config` already + produced — is now translated to codex's CLI surface (verified against + `codex-cli 0.145`): + - `--ignore-user-config`, applied UNCONDITIONALLY (both plain and MCP-direct) + like claude's unconditional `--strict-mcp-config`, so the user's + `~/.codex/config.toml` mcp_servers never leak into ANY ARI call. Auth still + resolves from `CODEX_HOME`; ARI owns the model via the shim alias (`-m`), so + not inheriting the user's codex model default is correct. + - `-c features.apps=false`, also unconditional. codex bundles curated apps + (GitHub, Google Calendar, Sites, …, ~129 tools) WITH the binary — neither + `--ignore-user-config` nor a clean `CODEX_HOME` removes them — so without + this an autonomous ARI agent would have ambient external tools (a + hermeticity AND safety hole: it could push to GitHub or create calendar + events), exactly what claude's `mcp__*` allowlist forbids. Turning them off + also cut observed per-call input tokens ~5× (211k → 46k) since their schemas + were otherwise injected every turn. + - one `-c mcp_servers..{command,args,env}` per server (BARE key — codex + silently fails to register a *quoted* server segment, and splits a dotted + name on the interior `.`, so a name that is not a TOML bare key is skipped + with a warning rather than corrupting the whole `-c` config) and a per-server + `enabled_tools` allowlist. Values are encoded as raw-UTF-8 TOML basic + strings (`ensure_ascii=False`) so a non-BMP character — CJK Ext-B, math + symbols — is not emitted as a surrogate pair, which TOML rejects. + So the exact same caller decision drives both engines: **attach** a server, + **detach MCP** entirely (no config → plain mode, and no ambient servers/apps + either), **detach memory server-level** (skill at `phase: none` → omitted from + `mcpServers` → never spawned), or **detach memory tool-level** (its + CoW-guarded write tools filtered from the allowlist → absent from + `enabled_tools`) — each honored identically whichever CLI runs. codex's + `--json` event stream is persisted to `/tool_calls.jsonl` for audit, + mirroring the claude path. Empirically confirmed end-to-end through the HTTP + shim: codex spawns the supplied MCP server, invokes only the allowed tools, a + tool filtered out of `enabled_tools` is not even visible to the model, and no + ambient GitHub/Calendar tool appears. The plain (non-MCP) codex path keeps its + read-only sandbox and writes no audit file; the claude path is untouched. +- **codex reasoning effort is controllable for the shim (`ARI_CLI_SHIM_CODEX_REASONING`).** + `--ignore-user-config` drops the operator's `model_reasoning_effort`, so codex + fell back to a slow reasoning default — and ARI drives many calls per run, so a + full paper pipeline hit the 90-min per-stage subprocess cap on iterative + citation collection alone. The shim now forwards + `-c model_reasoning_effort=` when the env var is set (empty = keep codex's + default); `low` makes a full codex-backed ideation→paper run tractable. +- **The epoch's governed utility_policy now actually drives scoring (the + objective co-evolves — RQGM paper claim A).** A paper-fidelity audit found the + adopted `EpochState.utility_policy` was captured and hash-stamped but INERT: + the `LLMEvaluator` was built once from static `cfg.evaluator` and the frontier + read static `cfg.bfts`, so a governed policy change altered only a provenance + hash, never a score. `RQGMRuntime.bind_evaluator` (wired in `build_runtime`) + plus `_apply_epoch_policy_to_scoring` now re-sync the evaluator's + composite/axis-weights and `cfg.bfts` (frontier reads it live) from the + epoch's FROZEN policy at each epoch open — so the criterion stays fixed WITHIN + an epoch and evolves only at boundaries. An unknown composite is never written + (evaluator/cfg keep the last valid one); ari_rqgm-gated (simple_bfts binds no + evaluator = no-op). Boundary re-scoring of PAST nodes is now handled by the + next entry (#77) — old-policy nodes are re-weighted under the new criterion + where possible rather than only retired. +- **Boundary re-scores the surviving tree under the NEW criterion (#77 — RQGM + paper claim A, the CAUSE half completed).** Previously a `utility_policy` + retirement INVALIDATED every node scored under the old policy + (`frontier_repair`), on the documented rationale that "a rewrite invalidates; + it never re-weights an old score in place" — so a criterion change discarded + the entire comparable frontier instead of re-ranking it, and the "the whole + score is rewritten at each boundary" pillar was only half-true (rewrite = + erase). `FrontierRepairEngine.repair` now takes the newly-frozen epoch's + sealed `new_utility_policy` (passed from `state.epoch.utility_policy`) and, for + each node stamped with the retired policy, RE-WEIGHTS it in place from its + stored per-axis raw scores (`_axis_scores` — the judge's policy-INDEPENDENT + measurements) under the new policy's `composite` + `axis_weights`, mirroring + the live `LLMEvaluator` path, then re-applies the node's existing validated + attack penalty (`_scientific_score = max(0, base − penalty)`; attack validity + is policy-independent) and re-stamps it. This is a DELIBERATE, scoped reversal + of "invalidate never re-weight" for the policy-retirement case ONLY, and only + by re-weighting raw axes — never by converting an old composite into a new one. + Fail-closed: a node without raw axes, or hard-invalidated for a non-policy + reason (its generator/direction was retired), or when the new policy is + unusable, STILL falls back to total invalidation — so no stale-criterion score + ever survives. Re-scored nodes are cleared of the policy-only invalidation and + re-enter the frontier; the count is logged and audited + (`policy_rescored_node_ids` on the SelectiveErasureEvent). Empty `axis_weights` + is a VALID re-weight, not a fail-closed case: a 6-epoch codex e2e evolved the + composite `harmonic_mean → weighted_min` via the PolicyMutator's + `composite_swap` (which leaves `axis_weights={}`), and the first cut of + `_rescore_node_under_policy` fail-closed every stamped node to invalidation on + the empty map — so #77 never fired live. It now accepts an empty/absent weight + map (the compose fns fall back to equal per-axis weights, exactly as the live + evaluator does when cfg weights are unset), requiring only a registered + `composite` + the node's stored `_axis_scores`. +- **Selective erasure now reaches best-node SELECTION, not just expansion (the + plan-10 §3 consumer-filter deferral, settled).** Erasure is logical-only: + `FrontierRepairEngine._flag_node` sets `_valid_for_frontier: False` but + deliberately retains the node's stale `_scientific_score` (and + `has_real_data`), and both persist verbatim through `tree.json`. The only + consumer-side readers of the sentinel were the two expansion prune clauses + (`BFTS.should_prune`, `PaperArchiveStrategy.should_prune`), so an erased + node — its score produced under a retired policy or + prompt — could still WIN `verified_context.select_best_node` and become the + paper candidate escalated at paper pre-flight (`projects.py`), the persisted + archive `seed_node_id` (`paper_dispatch.py`), the paper-archive root + (`_run_one_round`), and the `verified_context.json` lineage grounding the + paper's claims — including on the default `paper.mode: linear` path of an + `ari_rqgm` checkpoint. In the original RQGM paper this cannot happen because + erasure is physical deletion ("selection operates only on valid, + epoch-current evidence"); switching to logical erasure silently converted + that by-construction guarantee into a per-consumer filtering obligation that + plan 10 §3 deferred ("not silently dropped") and was then dropped. + `select_best_node` now excludes `_valid_for_frontier: False` nodes outright + (the `should_prune`/`_sterile` precedent: the key is only ever written by + RQGM machinery — `ari_rqgm` exploration or the `rqgm_archive` paper axis — + so the clause is inert dead code on the default paths), and + returns NO winner when every candidate is erased — contaminated evidence + does not become clean by being the only evidence left. All consumers were + already `None`-tolerant (escalation skips, `seed_node_id` persists as null, + `_make_paper_root` seeds lineage-free, `build_verified_context` returns the + empty shape). The two archive-side inline eligibility filters + (`paper_runtime.py` cross-round + best-belief) collapse into the shared + clause. An adversarial review of the fix closed the same defect class at + every other winner-promoting consumer: `write_verified_context` now REMOVES + a previously written `verified_context.json` whose `best_node_id` no longer + matches the fresh (post-erasure) winner instead of early-returning past it + (a stale artifact would otherwise keep grounding the paper on the erased + lineage across re-invocations — kept when the winner is unchanged, so a + transient memory-backend failure never discards a valid artifact); + `build_best_nodes_context` (the linear paper's "Best results" block + + `best_metrics`), ari-skill-transform's `_resolve_best_node[_for_synthesis]` + (science_data.json guard, EAR/published-code winner), and ari-skill-paper's + implementation-details top-5 all filter the sentinel now. Settled in + permanent docs (`rqgm_architecture.md` invariant 6; + plan-10 §3 annotated; INDEX invariant extended); the `search_memory` half of + the deferral remains open. +- **Paper-candidate escalation is no longer silently non-deterministic, and + the node the paper is about can no longer escape its L3 round.** The + pre-flight escalation round was believed "observational", but a + judge-validated attack applies the bounded utility penalty (plan 06 §5.4 — + `_scientific_score` rewritten in place), and `ari paper` re-selects the + best node downstream (archive seed, paper root, verified context) over the + SAME live node list — so a penalized candidate could be silently replaced + by a node that never received its own paper-candidate round, and because + the paper process never writes `tree.json` while the §5.3 round marker + suppresses re-rounds, a re-invocation reverted the ranking and could crown + a DIFFERENT winner (a P2 determinism violation). Three-part fix: (a) the + escalation docstrings/comments now state the demotion semantics; (b) + selection→escalation runs to a FIXPOINT (`ari.cli.paper_dispatch` + `_escalate_paper_candidate_to_fixpoint`: select → escalate → re-select + until stable; terminates — each node is escalated at most once, and the + round marker makes repeats no-ops), so a demotion-crowned winner gets its + own L3 round; (c) `RQGMRuntime.replay_utility_penalties` deterministically + re-applies persisted `UtilityRecord` penalties (base/penalty/final stored + by value in `rqgm_adversarial_cases.jsonl`) onto freshly loaded nodes at + the paper phase's start — idempotent and conservative (applies only when the + current score equals the record's base, so recomputed/re-scored values are + never clobbered; records chain naturally in log order; records superseded + by a frontier-repair recompute are skipped outright, so a penalty the + impeachment/repair chain formally REVERSED — a superseding record with + penalty 0.0 — is never re-applied to the exonerated node). Re-runs now + reproduce the first run's ranking. +- **`ari run` and `ari resume` finally run the paper-candidate round too — + and it is no longer fired before the evidence it attacks exists.** The + pre-flight escalation lived privately in `ari paper`, so a one-pass run + never ran it, and exploration's own per-node rounds do not cover the gap: + the artifacts that round attacks (claim-gate findings, + `verified_context.json`, related refs) are written by the paper stages, so + before them the pre-signals are empty and the paper-claim adversaries sit + on their no-attack floor. That same fact bounds when the round is worth + running: its §5.3 marker is one-shot per node and epoch-agnostic, so a + round fired against an empty bundle is spent forever and permanently + suppresses the artifact-grounded round (`prior_art` / `evidence_gap` / + `metric_gaming`) a later invocation could run — which is exactly what a + naive hoist would have caused on every fresh `ari run`, and what the + adversarial review of the first cut demonstrated (pass 1: 1 attack, + penalty 0.3, `overclaim` only; pass 2 with the artifacts present: + suppressed). The pre-flight is therefore **gated on the evidence + existing**: it runs before the mode branch when a previous pass produced + the artifacts (where a demotion can still re-crown this paper's own seed), + and otherwise once more AFTER the pipeline has written them, where the + penalty reaches selection on the next invocation through the replay above. + A pipeline that produced no evidence leaves the marker unspent. The whole + pre-flight (penalty replay → escalate-to-fixpoint → re-ideation) is now + `run_paper_candidate_preflight` + in `ari.cli.paper_dispatch` — the + dispatch all three entries already share — so the three agree here as they + already do on the paper axis. The pre-pipeline call runs BEFORE the mode + branch, keeping the + documented 2x2 orthogonality (the round fires on the *exploration* axis, + independently of `paper.mode`). Each entry passes its exploration runtime + (`rqgm=getattr(bfts, "rqgm", None)`); `simple_bfts` passes `None` and the + pre-flight is a dead branch, so `paper_dispatch` keeps its + no-`ari.rqgm`-import discipline (the handle is duck-typed, never + imported). Fail-open as before: an exploding adversary logs and the paper + still runs. A source-level test pins all three call sites passing the + handle, mirroring the existing one that pins all three routing through the + dispatch, and behavioural tests pin the gate in both directions (defers + without evidence, then runs post-pipeline once the pipeline wrote it; + stays deferred when nothing was produced). Relatedly, + `run_paper_candidate_escalation` no longer calls `ensure_epoch` when an + epoch is already open: on the live one-pass runtime that restore was a + no-op except for resetting `_last_node_count` to 0 — the value an + emergency quarantine stamps as the next epoch's `node_count_at_open`, + which would have poisoned the boundary arithmetic on the following resume. +- **`paper_archive_state.json` no longer records a silently stale seed.** + `seed_node_id` is written once at paper-phase start (the file is write-once + so a resume can never flip the persisted paper mode), but the live seed is + recomputed every round — and three mechanisms landed above exist precisely + to move it: selective erasure excluding the recorded seed, an escalation + penalty demoting it, and the penalty replay re-applying both before + selection. The record was therefore increasingly likely to name a node the + run had stopped using, with no trace of the change. A later invocation + whose seed differs now APPENDS `seed_journal[]` + (`{event: "seed_changed", prior_seed_node_id, seed_node_id}`, chained off + the latest entry so repeated invocations stay quiet and two moves record + both) instead of rewriting — the `paper_utility_policy_journal` pattern, so + the file tells the whole story rather than a stale first line. Ids only: + *why* the seed moved is already durable next door in `rqgm_audit.jsonl` + (erasure events) and `rqgm_adversarial_cases.jsonl` (validated-attack + penalties), and inferring a reason here could only guess. Best-effort — + a provenance note never breaks the paper phase. +- **Selective erasure now covers memory-mediated grounding and steering.** + Erasure never propagates to descendants, so a VALID winner can carry an + ERASED ancestor — and that ancestor's conclusions still reached the paper + and the search: (a) `build_verified_context` now drops known-erased + ancestor ids from the winner's lineage before `get_verified_context` + (unknown ids are kept — absence of the node is not evidence of + contamination); (b) the per-node working-context injection + (`agent/loop.py`) drops erased ancestor ids before `get_node_memory` / + `search_memory`, via the `rqgm_erasure_state.json` rollup read through the + rqgm-import-free checkpoint shim (absence == nothing stale — inert on + default paths). This settles the `search_memory` half of the plan-10 §3 + deferral CALLER-SIDE; the memory skill itself deliberately stays + erasure-unaware. **That skill half is now settled too, as annotate-for-pull + / hard-exclude-for-push.** `ari-skill-memory` reads the same published + rollup (a new `ari_skill_memory.erasure` — no `ari` import, no LLM call, + mtime-cached, and absence / malformed content / a newer `schema_version` + all degrade to "nothing is stale", so it is inert on every non-RQGM + checkpoint). A caller that *deliberately names* an erased node — + `get_node_memory`, `search_memory`, `search_research_memory` — receives its + entries LABELLED (`erased` / `erasure_event_id` / `erasure_note`) instead + of silently emptied: erasure withdraws the STANDING of a judgment (the + generator that proposed the direction, or the policy that scored it, was + retired), not the measurements an experiment recorded, and an invalidated + measurement is still the honest record of what was tried. The marker is + written at the top level AND inside `metadata`, because consumers + re-project entries to a fixed key set (the pipeline's `nodes_tree.json` + enrichment and the viz memory endpoint keep only `{text, metadata, ts}`), + and it is applied in the BACKENDS rather than the MCP dispatcher, so the + in-process funnel callers see it too. The paths that PUSH memory into a + decision keep hard-excluding: `build_verified_context` now filters the + erased ancestors out of `claims` / `usable_for_claims` itself — inside the + builder, so the tool and the in-process caller filter the same call — + while `limitations` deliberately keeps them labelled, since the honest + record of a later-invalidated direction is what that section is for. A + cross-package contract test pins the `invalid_frontier_node_ids` field + name, the rollup's schema version, and the writer's path on both sides, so + a rename, a schema bump, or a relocation cannot silently turn the skill's + awareness into dead code (the failure shape #79 had). Honest scope: the + guarantee is about provenance, not re-authored text — a surviving node + that restates a labelled entry in its own summary produces an unmarked + record on a clean lineage, and nothing propagates the marker through + re-authorship. A + previously written `verified_context.json` is also removed when the + (erasure-filtered) LINEAGE changed with the winner unchanged — a + mid-lineage ancestor erasure would otherwise leave the old artifact, and + the paper reads the FILE, not the fresh build. +- **Erased scores no longer contaminate stagnation detection, lineage + decisions, run-best displays, or the escalation's audit record.** The + retained stale score of an erased node (a) sat inside the stagnation + window and could only WIDEN the range — suppressing a genuine-plateau + pivot/re-ideation exactly after an impeachment, when the retired + component's nodes are the most recent (`bfts_loop._valid_composites`, + `build_lineage_state` recent scores + best-axis table now filter the + sentinel); (b) displayed as the run's best score in the GUI cards + (`checkpoint_api`, v1 `best_metric` — now "best valid", consistent with + `select_best_node`; key-absent trees are untouched, so historical runs + render identically); (c) fed `_paper_frontier_scores`, which could only + mis-state the recorded `triggers` list (level is forced L3 regardless) — + filtered for audit hygiene; and (d) served as the PARENT score in the + score-jump trigger (exploration round dispatch and `_paper_parent_score`) + — an erased parent's inflated stale score suppressed the jump clause, an + audit escape exactly post-impeachment; an erased parent is now treated as + no-parent (`parent_score=None`). +- **CK-REG-101's incumbent comparison is now reachable from the live + adoption path (#79 producer half), and the governance judge's + self-adjudication recusal is test-pinned (#78a).** The kernel gate and the + RTE incumbent-attach existed but no live producer ever emitted capability + fields on an adoption entry (`_change` emits a fixed key set), so the + authority-non-expansion comparison was structurally unreachable — its + tests hand-built the entries and proved the consumer only (the same trap + as the #77 empty-weights bug). `_change` now copies the component's + declared §6.1 `capabilities` onto SUCCESSION entries — T6 adoption and the + T20/T21 supersession edges, the rules that displace a DISTINCT incumbent — + when non-empty. Deliberately NOT on the self-shaped activation edges + (T7/T8/T12/T14/T18 promotion/re-activation/exoneration): no distinct + incumbent exists there, so a capability-carrying entry would hit the + gate's conservative deny-all `None` baseline and abort the whole + transition — permanently wedging governance for the five founding + capability-declaring components (caught by the adversarial review of the + first cut, which scoped the copy to all activation shapes). Byte-identical + for every current live entry (no non-founding registry entry declares + capabilities); `epoch_transition.schema.json` tolerates the additive key. + The transient apply-side attachments (`_incumbent_entry`, utility-policy + bodies) are now STRIPPED from the `epoch_transition` audit payload — they + exist only for the stateless kernel's validation and must not be frozen + into the hash-chained audit log. A producer-to-gate test drives + `_change` → incumbent attach → kernel and asserts a widening successor is + blocked while a narrowing one passes. The #78a recusal branch — the judge + never ruling on its own impeachment — previously had ZERO test coverage; + a real-producer integration test (two `make_validated_attack_record` + attacks targeting `governance_judge_v1` → clear-file motion → recusal + degradation, no adjudication outcome) now pins it. +- **Governance judge recuses on self-adjudication (adjudicator ≠ target).** A + motion whose target IS the governance judge was adjudicated by that same + judge, letting it dismiss its own impeachment. `governance/_pipeline.py` now + recuses such a motion — left unresolved (no outcome, never self-dismissed) and + flagged `self_adjudication_recused:` — since no alternate adjudicator + exists. (The deeper gap — the governance judiciary being unregistered and thus + unimpeachable — is now closed by the next entry, #78b.) +- **The governance judiciary is now inside the impeachment net (#78b — P4, no + unimpeachable ruler).** The auditor / evidence_clerk / governance_judge that + run the impeachment machinery adjudicated everyone else's fate while running + on unregistered `*_v0` bootstrap ids — outside the role vocabulary, so a + motion or ban targeting them was dropped as "unknown component" + (`resolve_transition`) and the judge that rules on every impeachment was + itself unimpeachable. They are now FOUNDING COMPONENTS (`auditor_v1` / + `evidence_clerk_v1` / `governance_judge_v1` in `FOUNDING_COMPONENT_TABLE`), so + `_actor_id` resolves them to real registered ids under any `ari_rqgm` boot and + they are sanctionable / retirable / bannable like every other actor. This + required a THIRD role category — `events.GOVERNANCE_ACTOR_ROLES` — since the + judiciary is neither prompt-evolvable (no meta agent emits governance-actor + successors) nor constitutionally fixed (unlike the kernel, it IS impeachable); + `events.ROLES` is now `EVOLVABLE + GOVERNANCE_ACTOR + FIXED` and the three + `rqgm_role` schema enums were extended to match. `governance_judge` was added + to the capability matrix (`kernel_rules._GOVERNANCE_ROLES`, same institutional + grants as the other two governance actors) — a constitutional amendment that + moves `CONSTITUTION_HASH` `6643c12a510e → 2edf93776904` (re-pinned in + `tests/test_rqgm_kernel.py`; the founding COMPONENT rows do NOT ride the hash, + they change the registry identity / epoch fingerprint instead). The + evidence_clerk is deterministic (no LLM), so its component's `prompt_id` is + `None` (schema-nullable); the auditor / governance_judge carry their + governance prompt for provenance. Combined with #78a's self-adjudication + recusal, the judge can no longer dismiss its own impeachment. +- **CK-REG-101 (authority non-expansion) now compares candidate vs INCUMBENT on + the live adoption path (#79 — invariant 18 enforced, not just cap-checked).** + The kernel's transition-time authority gate ran + `validate_authority_non_expansion(entry, None)` — a `None` incumbent, so a + capability-declaring adoption was only capped against the fixed + `CAPABILITY_MATRIX` and the §6.1 flag arithmetic (widened `allowed_targets`, + dropped `forbidden_targets`, raised `max_outputs_per_epoch`) was never checked + against what the role is CURRENTLY authorized to do. The kernel is stateless, + so the `RegistryTransitionEngine` now resolves each capability-declaring + adoption's active incumbent (`ComponentRegistry.active_set`, excluding the + candidate itself) and attaches it as `entry['_incumbent_entry']` + (`_attach_incumbent_capabilities`, the `_attach_utility_policy_bodies` + apply-side pattern); the kernel gate reads it and now ALSO fires on + flag/target-carrying entries, not only the v1 `declared_capabilities` shape. + Best-effort/fail-closed: an unresolvable incumbent degrades to the prior + conservative `None` baseline (matrix cap + deny-all flags), so authority is + never widened by an incumbent's absence. `meta_rules.has_capability_fields` is + now public (the RTE and kernel share one activation predicate). +- **CK-EPO-001 closed: the founding proposal's prompt is now governed, and the + epoch-invariance check uses the COMPLETE active-hash set.** A forensic audit of + a codex run found the kernel warning (correctly) that the run-seeding proposal + (`prop_000000`, the prior-art generator) was scored under a prompt outside the + frozen active set — a pillar-1 gap with two causes. (a) The three + `rqgm/proposal_{cheap,mutation,prior_art}` templates the ProposalRouter renders + were deliberately left ungoverned ("records carry their own provenance"), so a + proposal record's `prompt_hash` was unregistered; they are now in + `FOUNDING_PROMPT_TABLE` as `generator`/`evolvable=False` (governed + frozen, not + evolution targets), placed before `generator_prompt_v1` so the role incumbent + is unchanged. Registering founding prompts does NOT change `CONSTITUTION_HASH` + (it hashes the rule tables, not the founding tables). (b) `validate_epoch_invariance` + compared records against `active_prompt_hashes().values()` — a role→INCUMBENT + rollup that keeps one hash per role, so a governed non-incumbent generator + prompt still tripped CK-EPO-001. `EpochState` now freezes the complete + `active_prompt_hash_set` (new `registry.active_prompt_hash_set()`), the check + uses it (falling back to the rollup for pre-field snapshots), and it is + excluded from `epoch_fingerprint` (`registry_version` already identifies it). + Verified on the real run's founding-proposal hash (`81d78be59631`): after both + changes it is in the frozen set and CK-EPO-001 clears, while a genuinely + unregistered hash STILL fires — the detector was strengthened by governing the + prompt, not weakened by loosening the check. +- **`run_integrity` now aggregates the evidence-grounded SEMANTIC review, so an + unresolved overclaim in the finalized paper is a run-level concern.** The + numeric hard gate is semantically blind — it re-checks anchored numbers but not + whether a sentence overclaims — and the run-level summary read only the gate, + so a finalized paper with a KNOWN unresolved overclaim + (`status="revise"`, `detected_overclaim_count>0`) surfaced clean at the top + level (found by a forensic audit of a real codex run: the numeric rate was 1.0 + while the semantic review still flagged 1 unresolved overclaim). `integrity.py` + now reads `evidence_grounded_semantic_review_post_refine.json` (absent → null, + not 0) and raises a concern on any unresolved overclaim. +- **`ari run` can signal a degraded run through its exit code, and always + reports run-integrity concerns loudly.** The paper pipeline can finish with + FAILED stages (skipping their dependents) or raise outright, yet the process + still exited 0 — a caller/CI could not tell a degraded run from a clean one. + After the paper phase, `ari run` now prints the run_integrity concerns and, + opt-in via `ARI_RUN_STRICT_EXIT=1`, exits non-zero when the pipeline raised or + concerns exist — default stays rc=0 so best-effort callers (the ablation + harness) are unaffected. Concern reading is a pure, tested helper. +- **The Code Availability / references bibliography no longer silently drops + collected refs.** `_build_bib_content` hard-sliced `related_refs[:15]`, so a + collected reference sorting past index 15 (an audit found the single most + on-topic paper landed there) was dropped while the reported `count` still said + 17. Raised to 50 (a runaway guard, not a budget); the collected, already + relevance-filtered set now reaches the bibliography intact. +- **`web_search` (DuckDuckGo) works again, and Semantic Scholar retries on 429.** + Two web-skill fixes found while auditing a codex run's bibliography: (1) the + DuckDuckGo client package was renamed `duckduckgo-search` → `ddgs`, but the code + imported `from ddgs import DDGS` while the declared/installed dep was the old + name — so `web_search` silently returned `{"results": [], "error": ...}`. The + import now tries `ddgs` then falls back to `duckduckgo_search`, and the dep is + updated to `ddgs>=9.0`. (2) `_search_s2_sync` dropped straight to the arXiv + fallback on the FIRST HTTP 429, but Semantic Scholar rate-limits the shared + egress IP even with a valid key under burst (collect_references fires several + queries in quick succession). It now retries on 429 — honoring `Retry-After`, + else exponential backoff (1s/2s/4s, capped; attempts/cap env-tunable via + `ARI_S2_MAX_ATTEMPTS` / `ARI_S2_BACKOFF_CAP_S`) — so a transient 429 recovers to + real S2 results instead of degrading the bibliography; non-429 errors still fail + fast, and a persistent 429 still falls back to arXiv. +- **Round-1 Semantic Scholar hits are relevance-filtered like every other + round.** `collect_references_iterative` trusted round-1 S2 results as + "high precision" and added them UNFILTERED (only the arXiv fallback and + rounds ≥2 ran the selector), so an imprecise keyword query admitted off-topic + matches into the bibliography — a real run cited an antenna-"tiling" paper in a + stencil-tiling paper. Round-1 S2 hits now go through the same + `_llm_select_relevant` selector when an experiment summary is available, + fail-open (a selector failure keeps every ref — an empty bibliography is + strictly worse than an imprecise one), so it only removes confirmed off-topic + matches. +- **`run_integrity` no longer reports "root ideation saw NO prior art" when the + RQGM prior-art generator grounded it.** `novelty_grounded` was keyed solely on + the idea-skill's `papers_analyzed` counter, missing the second grounding path — + the router's `PriorArtDifferentiationGenerator`, which only fires + (`virsci_integration_status == "...prior_art"`) when `_root_survey_refs` + supplied non-empty refs. A run whose novelty text cited real prior art was + still flagged ungrounded; the check now counts either path. +- **`temperature` is now dropped for a gpt-5 model reached through a cli-shim + alias.** `LLMClient` only omitted `temperature` when `config.model` literally + started with `gpt-5`, but a codex shim model is named `codex-cli:gpt-5.6-sol` + — so `temperature=0.7` was sent, litellm raised `UnsupportedParamsError`, and + every delegated codex react call 502'd (an e2e root node failed with + `has_real=False`). A new `_is_gpt5_family` splits the model on `/` and `:` and + matches any `gpt-5*` segment, covering the alias and an `openai/gpt-5.1` + routing prefix; a non-gpt-5 model (e.g. `claude-cli:sonnet`) still sends + `temperature` unchanged. +- **GUI refresh: a v2 dashboard that runs beside the legacy one, not over it.** + New hash routes behind the `gui_v2` server capability (`ARI_GUI_V2`, + default on, `0`/`false` reverts to the legacy shell without a rebuild): + `#/projects`, `#/overview?run=`, `#/tree2?run=&node=`, `#/ideas2?run=`, + `#/results2?run=`, `#/governance?run=`, `#/config?run=`, `#/studio`. Every + legacy page, legacy hash URL and legacy `/api/*` endpoint is unchanged and + still reachable — three v2 routes (`tree2`/`ideas2`/`results2`) merely take + over their legacy sidebar slot via the route registry's `navReplaces` while + the legacy URL keeps working, which is what makes the rollback instant. + Routes, nav, breadcrumbs and aliases now come from one registry + (`frontend/src/app/routeRegistry.ts`) with a frozen parity test. +- **`/api/v1`: a run-explicit, typed API surface (`ari/viz/v1/`).** 36 paths + in the committed `ari/viz/v1/openapi.json` (regenerated deterministically + from `router.py` + `dto.py`; `python -m ari.viz.v1.openapi` is a + drift gate) — projects, runs (detail/summary/tree/idea/results/EAR/logs), + `config/schema`, `config/catalogs/models`, project config GET/PATCH, + run-template and run-draft CRUD, `POST /runs`, `secrets/status`, + `PUT secrets/{id}`, `POST challenges`, `diagnostics`, and the `rqgm/*` + read models. Mutations use `If-Match: ""` optimistic concurrency + (missing → 400, stale → 409 `revision_conflict`); errors are a typed + envelope with a `request_id`. Realtime is SSE + (`GET /api/v1/events/stream`, ring buffer + `Last-Event-ID` resume), and + events are invalidations only — a client always refetches the snapshot, + never treats the stream as a source of truth. The legacy port+1 WebSocket + and the frozen `/state` facade keep running in parallel (a test pins + `/state`'s exact key set so it can neither grow nor shrink before the + legacy-removal gate). +- **Configuration control plane.** `ari/config/field_registry.py` enumerates + the 144 config leaves with metadata (level, scope, mutability, sensitivity, + env override) and refuses to build if a leaf is uncovered — 100% coverage + is an invariant, not a target. `ari/config/resolver.py` + (`resolver_version: "legacy-compatible-1"`) explains an existing + checkpoint's effective config *and* previews a new run's, with per-leaf + provenance; a parity suite asserts the legacy 24-key Settings save+launch + path and the new resolver agree per leaf, with an exact two-way allowlist + of known divergences. A launch writes the resolved manifest to + `{checkpoint}/resolved_config.json`. GUI documents (drafts, templates, + launch claims) live in `{workspace_root}/gui_store/`, `0o700`, never inside + a checkpoint. +- **Configuration Studio + canonical idempotent launch.** New + `POST /api/v1/runs` resolves and validates a draft **before** touching the + filesystem (unknown draft → typed 404, invalid → typed 400 with + `details.errors`, zero mutation), issues the run identity itself + (`_-` — deterministic slug, no LLM + call and no `sinfo` probe before accepting), materializes + `experiment.md` / `workflow.yaml` (CoW seed) / `launch_config.json` / + `resolved_config.json` / `launch_events.jsonl` + (`draft→validating→accepted→spawned`), then spawns the same + `python3 -m ari.cli run` subprocess. `idempotency_key` claims + `gui_store/launches/{key}.json` create-only *before* the spawn, so a + double-click replays the same `run_id` with `idempotent_replay: true` and + spawns nothing. The Studio's launch panel drives it end to end — + goal → resolve/validate (effective-config diff vs defaults, resolver + warnings, secret readiness) → immutable review behind a confirm checkbox + that mints exactly one idempotency key → launch → canonical redirect to + `#/overview?run=`, with no mtime guessing. Legacy `POST /api/launch` + and the Wizard are byte-identical and still work. +- **Execution mode and paper mode are selectable from the GUI — for a new run, + and only those two.** The Studio's Execution section offers exactly two + controls, one per orthogonal intent: `ari.mode` ∈ {`simple_bfts`, + `ari_rqgm`} and `paper.mode` ∈ {`linear`, `rqgm_archive`}. Each control + writes **both** keys of its interlock pair (`rqgm.enabled` / + `rqgm.paper.enabled`) in one save, so a half-set pair is unconstructible in + the UI and a typed 400 `mode_interlock_mismatch` everywhere else (template + and draft create/PATCH, and launch — checked on the merged document values, + so a two-step edit that ends consistent is fine). A non-default selection is + materialized twice — minimal `ari:`/`rqgm:`/`paper:` blocks merged into the + run's own `workflow.yaml` copy (never the bundled file) plus the documented + `ARI_MODE` / `ARI_RQGM_ENABLED` / `ARI_PAPER_MODE` / `ARI_RQGM_PAPER_ENABLED` + env vars — so the checkpoint describes itself. The launch review shows the + **resolved** mode, and a request the resolver did not honour is shown as + `requested → resolved` with the warning verbatim instead of quietly starting + the fallback run. **The honest limits:** the other 96 RQGM governance and + tuning parameters (epoch, kernel, adversarial, budgets) stay + configuration-file only — visible read-only with their effective values, and + still rejected at launch with `mode_locked`; picking a mode is a launch + decision, not a governance mutation, and every `rqgm/*` API route is still a + GET. **Resume cannot change a mode**: a resumed run keeps the mode persisted + in `{checkpoint}/rqgm_state.json` (downgrade-only), and no GUI path writes + that file. Leaving the defaults writes and exports nothing — a + `simple_bfts` + `linear` launch stays byte-identical to before, and a test + pins it. +- **RQGM governance workspace (read-only).** `#/governance?run=` over + `/api/v1/runs/{run_id}/rqgm/{capabilities,overview,registry,transitions, + audit,nodes/{id}/lineage,score-rewrites,policies,epochs,epochs/{id}, + evolution,paper-archive}`. The read model never imports `ari.rqgm`: it + parses the committed checkpoint artifacts directly, re-executes no kernel + or score-policy decision, ignores a torn JSONL append, and refuses to adopt + transition events after an uncommitted `epoch_transaction_prepare`. A + broken hash chain or a stale rollup yields HTTP 200 with integrity flags + and `degraded_reasons` — corrupt artifacts degrade honestly rather than + 500ing — and a missing source reads as `None`, never as clean zero. There + is no governance mutation endpoint, and a contract snapshot forbids adding + one. +- **Security posture of the dashboard server, with an env kill-switch per + change.** Bind is **loopback by default** (`127.0.0.1` + `::1`; + `ARI_GUI_BIND='::'` restores the old all-interfaces bind) and CORS echoes + only the server's own origin instead of `*` (`ARI_GUI_CORS_ANY=1`). + A non-loopback bind now **requires** `Authorization: Bearer + ` on everything but `/health*` (401 + `WWW-Authenticate`, + constant-time compare, `?token=` accepted for SSE/WebSocket since those + APIs cannot set headers, token redacted to `***` in `viz_access.jsonl`); if + the token is unset the server generates a 32-hex one and prints it once to + stderr, so there is no unauthenticated remote start (`ARI_GUI_AUTH=0`). + `delete-checkpoint` / `stop` / `gpu-monitor stop` require a single-use, + 60-second, action+target-bound challenge from `POST /api/v1/challenges`, + audited as `challenge_*` lines in `viz_access.jsonl` — without it they + answer 428 and do nothing (`ARI_GUI_CHALLENGES=0`). The SPA index and + `/static/` send CSP + `nosniff` + `Referrer-Policy` and the jsDelivr d3 + ` +

Loading ARI Dashboard...

diff --git a/ari-core/ari/viz/frontend/package-lock.json b/ari-core/ari/viz/frontend/package-lock.json index 1e3e6440..975e0963 100644 --- a/ari-core/ari/viz/frontend/package-lock.json +++ b/ari-core/ari/viz/frontend/package-lock.json @@ -8,8 +8,10 @@ "name": "ari-dashboard", "version": "1.0.0", "dependencies": { + "@tanstack/react-query": "^5.101.4", "d3": "^7.9.0", - "react": "^18.3.1", + "pdfjs-dist": "^5.6.205", + "react": "^19.2.8", "react-dom": "^19.2.8", "reactflow": "^11.11.4" }, @@ -18,13 +20,19 @@ "@testing-library/react": "^16.0.0", "@testing-library/user-event": "^14.5.2", "@types/d3": "^7.4.3", - "@types/react": "^18.3.5", + "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.3.1", - "jsdom": "^30.0.1", - "typescript": "^7.0.2", - "vite": "^8.1.4", + "@vitejs/plugin-react": "^6.0.5", + "axe-core": "^4.12.1", + "jsdom": "^25.0.1", + "openapi-typescript": "^7.13.0", + "playwright": "^1.62.0", + "typescript": "^5.9.3", + "vite": "^8.2.0", "vitest": "^4.1.10" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, "node_modules/@adobe/css-tools": { @@ -35,57 +43,25 @@ "license": "MIT" }, "node_modules/@asamuzakjp/css-color": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-6.0.5.tgz", - "integrity": "sha512-mbhpPMmnw/kwW19aRNmSUl1QzLbdGo1SCuE49BT98MNwqF6zaHb3o2owssFc/PEO/4t2UjqtCNwocuDtJornzA==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz", + "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==", "dev": true, "license": "MIT", "dependencies": { - "@csstools/css-calc": "^3.2.1", - "@csstools/css-color-parser": "^4.1.9", - "@csstools/css-parser-algorithms": "^4.0.0", - "@csstools/css-tokenizer": "^4.0.0", - "lru-cache": "^11.5.2" - }, - "engines": { - "node": "^22.13.0 || >=24.0.0" + "@csstools/css-calc": "^2.1.3", + "@csstools/css-color-parser": "^3.0.9", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "lru-cache": "^10.4.3" } }, "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": { - "version": "11.5.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", - "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/@asamuzakjp/dom-selector": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-8.3.0.tgz", - "integrity": "sha512-UJLfKXBhrc8i1vH2eJXuYQMwlsLKWFw3O+CPqXSuVEiikeAim3UgrfWX0k4tA/X8cRFM8iZ7OaqBokFGbYusdg==", + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "dev": true, - "license": "MIT", - "dependencies": { - "bidi-js": "^1.0.3", - "css-tree": "^3.2.1", - "is-potential-custom-element-name": "^1.0.1", - "lru-cache": "^11.5.2" - }, - "engines": { - "node": "^22.13.0 || >=24.0.0" - } - }, - "node_modules/@asamuzakjp/dom-selector/node_modules/lru-cache": { - "version": "11.5.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", - "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": "20 || >=22" - } + "license": "ISC" }, "node_modules/@babel/code-frame": { "version": "7.29.0", @@ -102,143 +78,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/compat-data": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", - "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", - "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-module-transforms": "^7.28.6", - "@babel/helpers": "^7.28.6", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/traverse": "^7.29.0", - "@babel/types": "^7.29.0", - "@jridgewell/remapping": "^2.3.5", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/generator": { - "version": "7.29.1", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", - "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.29.0", - "@babel/types": "^7.29.0", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", - "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.28.6", - "@babel/helper-validator-option": "^7.27.1", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-globals": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", - "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", - "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.28.6", - "@babel/types": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", - "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.28.6", - "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", - "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-validator-identifier": { "version": "7.28.5", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", @@ -249,78 +88,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", - "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", - "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.29.0" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-self": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", - "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-source": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", - "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/runtime": { "version": "7.29.2", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", @@ -331,71 +98,10 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/template": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", - "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/parser": "^7.28.6", - "@babel/types": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", - "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0", - "debug": "^4.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@bramus/specificity": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz", - "integrity": "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==", - "dev": true, - "license": "MIT", - "dependencies": { - "css-tree": "^3.0.0" - }, - "bin": { - "specificity": "bin/cli.js" - } - }, "node_modules/@csstools/color-helpers": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.1.0.tgz", - "integrity": "sha512-064IFJdjTfUqnjpCVpMOdbr8FLQBhinbZj6yRv2An2E41O/pLEXqfFRWqGq/SxlE5PEUYTlvWsG2r8MswAVvkg==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", + "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==", "dev": true, "funding": [ { @@ -409,13 +115,13 @@ ], "license": "MIT-0", "engines": { - "node": ">=20.19.0" + "node": ">=18" } }, "node_modules/@csstools/css-calc": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.3.0.tgz", - "integrity": "sha512-c5ihYsPkdG6JCkU2zTMm4+k6r7RXuGxtWYhu5DHMIiF1FHzrfmHL5so11AoFpUv/tu61xfcmT4AmKoFfMPoqdQ==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", + "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", "dev": true, "funding": [ { @@ -429,17 +135,17 @@ ], "license": "MIT", "engines": { - "node": ">=20.19.0" + "node": ">=18" }, "peerDependencies": { - "@csstools/css-parser-algorithms": "^4.0.0", - "@csstools/css-tokenizer": "^4.0.0" + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" } }, "node_modules/@csstools/css-color-parser": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.1.10.tgz", - "integrity": "sha512-UZhQLIUyJaaMepqehrCODwCg2KW25vFvLWBmqYFaPclYvvxzj/sG8LBOhBFCp11i9uE7t1EyS+RAoV9tztPFyw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz", + "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==", "dev": true, "funding": [ { @@ -453,21 +159,21 @@ ], "license": "MIT", "dependencies": { - "@csstools/color-helpers": "^6.1.0", - "@csstools/css-calc": "^3.3.0" + "@csstools/color-helpers": "^5.1.0", + "@csstools/css-calc": "^2.1.4" }, "engines": { - "node": ">=20.19.0" + "node": ">=18" }, "peerDependencies": { - "@csstools/css-parser-algorithms": "^4.0.0", - "@csstools/css-tokenizer": "^4.0.0" + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" } }, "node_modules/@csstools/css-parser-algorithms": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz", - "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", + "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", "dev": true, "funding": [ { @@ -481,41 +187,16 @@ ], "license": "MIT", "engines": { - "node": ">=20.19.0" + "node": ">=18" }, "peerDependencies": { - "@csstools/css-tokenizer": "^4.0.0" - } - }, - "node_modules/@csstools/css-syntax-patches-for-csstree": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.7.tgz", - "integrity": "sha512-fQ+05118eQS1cofO3aJpB5efgpBZMvIzwr/sbC8kDLVA5XLG8q1kJV5yzrUAI1f7lvhPnm8fgIjzFB8/O/5Dig==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "peerDependencies": { - "css-tree": "^3.2.1" - }, - "peerDependenciesMeta": { - "css-tree": { - "optional": true - } + "@csstools/css-tokenizer": "^3.0.4" } }, "node_modules/@csstools/css-tokenizer": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz", - "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", + "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", "dev": true, "funding": [ { @@ -529,25 +210,25 @@ ], "license": "MIT", "engines": { - "node": ">=20.19.0" + "node": ">=18" } }, "node_modules/@emnapi/core": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", - "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", + "version": "2.0.0-alpha.3", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-2.0.0-alpha.3.tgz", + "integrity": "sha512-AZypUeJ/yByuxyS7BlSNRDOMLMlROYtjYdIAuBmJssVz1UJDSeYxLrdizhXCFYhedC5bqd/ASy8EuNXbVVXp9g==", "dev": true, "license": "MIT", "optional": true, "dependencies": { - "@emnapi/wasi-threads": "1.2.2", + "@emnapi/wasi-threads": "2.0.1", "tslib": "^2.4.0" } }, "node_modules/@emnapi/runtime": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", - "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", + "version": "2.0.0-alpha.3", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-2.0.0-alpha.3.tgz", + "integrity": "sha512-hFPAhMUjJD9BSyCANEISPOogeXC9Zo9ZQl7L6vKnaVsMkCtzznaW/naYypeyl0Gv5rYfWYsZbpixTMpjDJzQeA==", "dev": true, "license": "MIT", "optional": true, @@ -556,9 +237,9 @@ } }, "node_modules/@emnapi/wasi-threads": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", - "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-2.0.1.tgz", + "integrity": "sha512-9DsSk+o5NBX0CCJT8s0EROGSGxjR/tKu6aBTaVyq+SjAEQH4XcdcRxPBRzsBLizTTJ49MJjF+jgu3qnO9GLQcQ==", "dev": true, "license": "MIT", "optional": true, @@ -566,97 +247,289 @@ "tslib": "^2.4.0" } }, - "node_modules/@exodus/bytes": { - "version": "1.15.1", - "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.1.tgz", - "integrity": "sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q==", + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "dev": true, + "license": "MIT" + }, + "node_modules/@napi-rs/canvas": { + "version": "0.1.100", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas/-/canvas-0.1.100.tgz", + "integrity": "sha512-xglYA6q3XO5P3BNJYxVZ1IV7DLVjp1Py6nwag88YntrS+3vKHyYcMqXVS4ZztJmwz2uGvz1FWhI/4LgbR5uQDA==", "license": "MIT", + "optional": true, + "workspaces": [ + "e2e/*" + ], "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + "node": ">= 10" }, - "peerDependencies": { - "@noble/hashes": "^1.8.0 || ^2.0.0" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" }, - "peerDependenciesMeta": { - "@noble/hashes": { - "optional": true - } + "optionalDependencies": { + "@napi-rs/canvas-android-arm64": "0.1.100", + "@napi-rs/canvas-darwin-arm64": "0.1.100", + "@napi-rs/canvas-darwin-x64": "0.1.100", + "@napi-rs/canvas-linux-arm-gnueabihf": "0.1.100", + "@napi-rs/canvas-linux-arm64-gnu": "0.1.100", + "@napi-rs/canvas-linux-arm64-musl": "0.1.100", + "@napi-rs/canvas-linux-riscv64-gnu": "0.1.100", + "@napi-rs/canvas-linux-x64-gnu": "0.1.100", + "@napi-rs/canvas-linux-x64-musl": "0.1.100", + "@napi-rs/canvas-win32-arm64-msvc": "0.1.100", + "@napi-rs/canvas-win32-x64-msvc": "0.1.100" + } + }, + "node_modules/@napi-rs/canvas-android-arm64": { + "version": "0.1.100", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-android-arm64/-/canvas-android-arm64-0.1.100.tgz", + "integrity": "sha512-hjhCKhntPv9+t4ckHymdx0phYNcVW+GKQR6Lzw2zE+pOVjOplSmtx9nNNknTjbEDLcuLZqA1y8ufKg1XfgftzQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" } }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, + "node_modules/@napi-rs/canvas-darwin-arm64": { + "version": "0.1.100", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-arm64/-/canvas-darwin-arm64-0.1.100.tgz", + "integrity": "sha512-2PcswRaC7Ly645DGt88///zuFDhJxJYdKAs1uU3mfk1atYkXufgcgLfBpk6Tm12nCQBaNt1wpybuPZ4qOhTo8A==", + "cpu": [ + "arm64" + ], "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" } }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "dev": true, + "node_modules/@napi-rs/canvas-darwin-x64": { + "version": "0.1.100", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-x64/-/canvas-darwin-x64-0.1.100.tgz", + "integrity": "sha512-ePNZtj7pNIva/siZMg+HmbeozkIjqUIYdoymH8HaA3qK7LfzFN4WMBM8G6HQ9ZC+H3+Dnn5pqtiXpgLykaPOhw==", + "cpu": [ + "x64" + ], "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" } }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, + "node_modules/@napi-rs/canvas-linux-arm-gnueabihf": { + "version": "0.1.100", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm-gnueabihf/-/canvas-linux-arm-gnueabihf-0.1.100.tgz", + "integrity": "sha512-d5cDB48oWFGU8/XPhUOFAlySgb/VAu7D+s8fi55K1Pcfg8aPplHWqMgibhVLU8ky7Pyg/fuiVLz4Nf3JrSTuUA==", + "cpu": [ + "arm" + ], "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6.0.0" + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" } }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, - "license": "MIT" + "node_modules/@napi-rs/canvas-linux-arm64-gnu": { + "version": "0.1.100", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-gnu/-/canvas-linux-arm64-gnu-0.1.100.tgz", + "integrity": "sha512-rDxgxRu69RvDlX/bh9o22DxLsGr8EqsNgotL9+RwQE1S0b0cqeatqsw6aW45mukm0B42DIAaAacKaYQ8cqS1nw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, + "node_modules/@napi-rs/canvas-linux-arm64-musl": { + "version": "0.1.100", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-musl/-/canvas-linux-arm64-musl-0.1.100.tgz", + "integrity": "sha512-K3mDW66N+xT2/V439u1alFANiBUjdEx2gLiNYnCmUsva5jZMxWTjafBYwTzYK+EMFMHrUoabuU+T1BIP5CgbYQ==", + "cpu": [ + "arm64" + ], "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@napi-rs/canvas-linux-riscv64-gnu": { + "version": "0.1.100", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-riscv64-gnu/-/canvas-linux-riscv64-gnu-0.1.100.tgz", + "integrity": "sha512-mooqUBTIsccZpnoQC4NgrC1v6C1vof39etLNMnBwCY+p0gajWJvAHLGQ6g/gGyS5YrpDW+GefSN4+Cvcr08UWw==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@napi-rs/canvas-linux-x64-gnu": { + "version": "0.1.100", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-gnu/-/canvas-linux-x64-gnu-0.1.100.tgz", + "integrity": "sha512-1eCvkDCazm7FFhsT7DfGOdSaHgZVK3bt/dSBl5EWHOWmnz+I7j8tPseJqqD81NF+MH21jKUK4wQSDjN0mdhnTg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@napi-rs/canvas-linux-x64-musl": { + "version": "0.1.100", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-musl/-/canvas-linux-x64-musl-0.1.100.tgz", + "integrity": "sha512-20arT6lnI19S68qNlii73TSEDbECNgzMz2EpldC1V3mZFuRkeujXkcebRk0LRJe9SEUAooYiLokfMViY8IX7yA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@napi-rs/canvas-win32-arm64-msvc": { + "version": "0.1.100", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-arm64-msvc/-/canvas-win32-arm64-msvc-0.1.100.tgz", + "integrity": "sha512-DZFFT1wIAg37LJw37yhMRFfjATd3vTQzjZ1Yki8u2vhO6Hi5VE6BVaGQ1aaDu7xb4iMErz+9EOwjpS7xcxFeBw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@napi-rs/canvas-win32-x64-msvc": { + "version": "0.1.100", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-x64-msvc/-/canvas-win32-x64-msvc-0.1.100.tgz", + "integrity": "sha512-MyT1j3mHC2+Lu4pBi9mKyMJhtP6U7k7EldY7sj/uS5gJA65gTXt8MefJQXLJo5d/vZbuWmfxzkEUNc/urV3pHA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" } }, "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz", - "integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.2.tgz", + "integrity": "sha512-JfB4kuJQjaoHuCTseIINHtHWeJnvgEcxjwA5t/Y00ZgaOO1Crz3fjT/p8kT28zA/Caz7oiUMn3d6H2yOVCVwuw==", "dev": true, "license": "MIT", "optional": true, "dependencies": { "@tybys/wasm-util": "^0.10.3" }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=23.5.0" + }, "funding": { "type": "github", "url": "https://github.com/sponsors/Brooooooklyn" }, "peerDependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1" + "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.3", + "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.3" } }, "node_modules/@oxc-project/types": { - "version": "0.139.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.139.0.tgz", - "integrity": "sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==", + "version": "0.142.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.142.0.tgz", + "integrity": "sha512-7W+2q5AKQVU36fkaryontrHn3YDt1RyUYXatw9i5H8ocYe2sPKSFB6eS8WNPeRKiN1qAWWZUPm7gwFzJGrccqQ==", "dev": true, "license": "MIT", "funding": { @@ -765,10 +638,56 @@ "react-dom": ">=17" } }, + "node_modules/@redocly/ajv": { + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/@redocly/ajv/-/ajv-8.11.2.tgz", + "integrity": "sha512-io1JpnwtIcvojV7QKDUSIuMN/ikdOUd1ReEnUnMKGfDVridQZ31J0MmIuqwuRjWDZfmvr+Q0MqCcfHM2gTivOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js-replace": "^1.0.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@redocly/config": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/@redocly/config/-/config-0.22.0.tgz", + "integrity": "sha512-gAy93Ddo01Z3bHuVdPWfCwzgfaYgMdaZPcfL7JZ7hWJoK9V0lXDbigTWkhiPFAaLWzbOJ+kbUQG1+XwIm0KRGQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@redocly/openapi-core": { + "version": "1.34.18", + "resolved": "https://registry.npmjs.org/@redocly/openapi-core/-/openapi-core-1.34.18.tgz", + "integrity": "sha512-UyKIm0wTPw5BcY7Z2PkbK1Ma260um96LSBWXHrdSMe+ZV0EPMyDfAcUcjjm3qEiGST9OK/1TriekdPCZkn4Q3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@redocly/ajv": "8.11.2", + "@redocly/config": "0.22.0", + "colorette": "1.4.0", + "https-proxy-agent": "7.0.6", + "js-levenshtein": "1.1.6", + "js-yaml": "4.3.0", + "minimatch": "5.1.9", + "pluralize": "8.0.0", + "yaml-ast-parser": "0.0.43" + }, + "engines": { + "node": ">=18.17.0", + "npm": ">=9.5.0" + } + }, "node_modules/@rolldown/binding-android-arm64": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.5.tgz", - "integrity": "sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.1.tgz", + "integrity": "sha512-02hOeOSryYxVrOIphmLAsqnCJWxwlzFk+pEt/N/i6OgT3lShHO7xGCU5cpgchRDHboAEbSjzgGh+O/u1GswQmA==", "cpu": [ "arm64" ], @@ -783,9 +702,9 @@ } }, "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.5.tgz", - "integrity": "sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.1.tgz", + "integrity": "sha512-fMsTOnN0OjFm3CyppWPitKnc8UlliVARUULW6cfU6AIqjdtgmSFWSk9vecHzZduv/yMWIHDlRhM1e8Iff9uAfA==", "cpu": [ "arm64" ], @@ -800,9 +719,9 @@ } }, "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.5.tgz", - "integrity": "sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.1.tgz", + "integrity": "sha512-1wjKdz/XLGKHaTNHjQveQ/B23TKx4ItAqm1JbyVuvNPc4Ze0Fb48s49TAd/2zcplPl8okE/UbTgmlVfwT7eFeQ==", "cpu": [ "x64" ], @@ -817,9 +736,9 @@ } }, "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.5.tgz", - "integrity": "sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.1.tgz", + "integrity": "sha512-Fa0jHR07E7YBN4vOEsbVf2briYNsuOowfLJaXULZM0ldMlaCaj2LJgLMbMe4iacRyZmvR8efFhgR9wKuGclQUg==", "cpu": [ "x64" ], @@ -834,9 +753,9 @@ } }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.5.tgz", - "integrity": "sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.1.tgz", + "integrity": "sha512-pzkgu1SSHGgRRyRZ4fbmSgmajbVt+epaLP99NDjFft69v/ypfTi6swBMiVdh2EkQ0OSnHE1lZDM7DRGkyAzUpA==", "cpu": [ "arm" ], @@ -851,16 +770,13 @@ } }, "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.5.tgz", - "integrity": "sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.1.tgz", + "integrity": "sha512-QI5SEDY8cbiYWHx0VO4vIc3UlS6a32vXHjU8Qy/17adEmZIPuByJg13UEvo9c/UCiUkdcVWY83C+b+JrwnNyUg==", "cpu": [ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -871,16 +787,13 @@ } }, "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.5.tgz", - "integrity": "sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.1.tgz", + "integrity": "sha512-Sm41FyCeXqmYcERoYOCbGIL5hNfd8w9LQ7Y61Bev48HkcjaJqV/iiVOaiDxjVTRMS+QKrZmD8cfPt4uMVnvM+A==", "cpu": [ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -891,16 +804,13 @@ } }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.5.tgz", - "integrity": "sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.1.tgz", + "integrity": "sha512-2x+WhXTGl9yJYPbltW/BSEPTVz9OIWQyER4N+gJEDWkkn904eRcBzELqh/Hf7K0w/ubGbKNMv0ZC+94QK/IFEg==", "cpu": [ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -911,16 +821,13 @@ } }, "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.5.tgz", - "integrity": "sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.1.tgz", + "integrity": "sha512-eEjmQpuRQayHPWWnywaWHkFT3ToPbP3RYy42VVd/B9aBGDA+Ol25EIWHxKQST3IiWJjikCWUF7KtbfqwZrzVwQ==", "cpu": [ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -931,16 +838,13 @@ } }, "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.5.tgz", - "integrity": "sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.1.tgz", + "integrity": "sha512-/Orga1fZYkLc/56jBICcHrKchl8Z2UKdDSr3LG9ToWO1lQ6a4Livk9Xz+9WN91zsz5QR3XQz2NNoSDEvP6qadw==", "cpu": [ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -951,16 +855,13 @@ } }, "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.5.tgz", - "integrity": "sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.1.tgz", + "integrity": "sha512-xxBJRL+0q0Kce7orznGWLuylHDY65vuARXZRpX+hPdv+DqK2c3NlCsVA98tlWzWNEE7yPqA/1NQ5nnCrj49Y5A==", "cpu": [ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -971,9 +872,9 @@ } }, "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.5.tgz", - "integrity": "sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.1.tgz", + "integrity": "sha512-M6AdXIXw3s+/8XpKMzdGDEXGS1S7kwUsy+rcTIUIOx5Ge4nXKCtAFHFV9YKkXvGcC5WMoTjAteLzlsQROVI0Yw==", "cpu": [ "arm64" ], @@ -988,28 +889,25 @@ } }, "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.5.tgz", - "integrity": "sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA==", - "cpu": [ - "wasm32" - ], + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.2.1.tgz", + "integrity": "sha512-/TX0SoRGojHzSAHpfVBbavRVSazg5U3h3Y3VXfcc0cdugq6kxdqw8LPGFiPr+/7gE/60zRcsOY2Vi9b9eT0jww==", "dev": true, "license": "MIT", "optional": true, "dependencies": { - "@emnapi/core": "1.11.1", - "@emnapi/runtime": "1.11.1", - "@napi-rs/wasm-runtime": "^1.1.6" + "@emnapi/core": "2.0.0-alpha.3", + "@emnapi/runtime": "2.0.0-alpha.3", + "@napi-rs/wasm-runtime": "^1.2.0" }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": "^20.19.0 || ^22.13.0 || >=23.5.0" } }, "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.5.tgz", - "integrity": "sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.1.tgz", + "integrity": "sha512-EvRrivJieyHG+AO9lleZWgq+g0+S7oV2C51yuqlcyU/R9net+sI4Pj0F+lUoP2bEr6TWX3SqFaaS0SzfLxSzkw==", "cpu": [ "arm64" ], @@ -1024,9 +922,9 @@ } }, "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.5.tgz", - "integrity": "sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.1.tgz", + "integrity": "sha512-Z4eCmn5QJ/5+azF9knpLWKfVd9aidn0mAe9TpJgvBLId9Ax3t0+JVxBmT25Bv7NBbVW1TZyKjQjQReouMeH5UQ==", "cpu": [ "x64" ], @@ -1041,9 +939,9 @@ } }, "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-beta.27", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", - "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", "dev": true, "license": "MIT" }, @@ -1054,6 +952,32 @@ "dev": true, "license": "MIT" }, + "node_modules/@tanstack/query-core": { + "version": "5.101.4", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.101.4.tgz", + "integrity": "sha512-gNwcvOJcRbLWPOLG/2OBm+zM+Yv+MKsXKEOWC57USuZDEsI71hEErQsiEGx5wX9rzWWkfwM0fVSPoiIFSsxfiw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.101.4", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.101.4.tgz", + "integrity": "sha512-yRg2pfOCxIs4ZJW3XYYHU/WgtD04FHSnfHlpRT7h7pR77hwkdRG4wxbKe4aq6P0RvXUTBSQpQeadS1SUYUe+KA==", + "license": "MIT", + "dependencies": { + "@tanstack/query-core": "5.101.4" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18 || ^19" + } + }, "node_modules/@testing-library/dom": { "version": "10.4.1", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", @@ -1163,51 +1087,6 @@ "license": "MIT", "peer": true }, - "node_modules/@types/babel__core": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", - "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "node_modules/@types/babel__generator": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", - "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__template": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", - "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__traverse": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", - "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.28.2" - } - }, "node_modules/@types/chai": { "version": "5.2.3", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", @@ -1484,401 +1363,58 @@ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", "dev": true, - "license": "MIT" - }, - "node_modules/@types/geojson": { - "version": "7946.0.16", - "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", - "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", - "license": "MIT" - }, - "node_modules/@types/prop-types": { - "version": "15.7.15", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", - "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/@types/react": { - "version": "18.3.28", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz", - "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@types/prop-types": "*", - "csstype": "^3.2.2" - } - }, - "node_modules/@types/react-dom": { - "version": "19.2.3", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", - "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "@types/react": "^19.2.0" - } - }, - "node_modules/@typescript/typescript-aix-ppc64": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-aix-ppc64/-/typescript-aix-ppc64-7.0.2.tgz", - "integrity": "sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=16.20.0" - } - }, - "node_modules/@typescript/typescript-darwin-arm64": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-arm64/-/typescript-darwin-arm64-7.0.2.tgz", - "integrity": "sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=16.20.0" - } - }, - "node_modules/@typescript/typescript-darwin-x64": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-x64/-/typescript-darwin-x64-7.0.2.tgz", - "integrity": "sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=16.20.0" - } - }, - "node_modules/@typescript/typescript-freebsd-arm64": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-arm64/-/typescript-freebsd-arm64-7.0.2.tgz", - "integrity": "sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=16.20.0" - } - }, - "node_modules/@typescript/typescript-freebsd-x64": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-x64/-/typescript-freebsd-x64-7.0.2.tgz", - "integrity": "sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=16.20.0" - } - }, - "node_modules/@typescript/typescript-linux-arm": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm/-/typescript-linux-arm-7.0.2.tgz", - "integrity": "sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=16.20.0" - } - }, - "node_modules/@typescript/typescript-linux-arm64": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm64/-/typescript-linux-arm64-7.0.2.tgz", - "integrity": "sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=16.20.0" - } - }, - "node_modules/@typescript/typescript-linux-loong64": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-loong64/-/typescript-linux-loong64-7.0.2.tgz", - "integrity": "sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=16.20.0" - } - }, - "node_modules/@typescript/typescript-linux-mips64el": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-mips64el/-/typescript-linux-mips64el-7.0.2.tgz", - "integrity": "sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=16.20.0" - } - }, - "node_modules/@typescript/typescript-linux-ppc64": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-ppc64/-/typescript-linux-ppc64-7.0.2.tgz", - "integrity": "sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=16.20.0" - } - }, - "node_modules/@typescript/typescript-linux-riscv64": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-riscv64/-/typescript-linux-riscv64-7.0.2.tgz", - "integrity": "sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=16.20.0" - } - }, - "node_modules/@typescript/typescript-linux-s390x": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-s390x/-/typescript-linux-s390x-7.0.2.tgz", - "integrity": "sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=16.20.0" - } - }, - "node_modules/@typescript/typescript-linux-x64": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-x64/-/typescript-linux-x64-7.0.2.tgz", - "integrity": "sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=16.20.0" - } - }, - "node_modules/@typescript/typescript-netbsd-arm64": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-arm64/-/typescript-netbsd-arm64-7.0.2.tgz", - "integrity": "sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=16.20.0" - } - }, - "node_modules/@typescript/typescript-netbsd-x64": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-x64/-/typescript-netbsd-x64-7.0.2.tgz", - "integrity": "sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=16.20.0" - } - }, - "node_modules/@typescript/typescript-openbsd-arm64": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-arm64/-/typescript-openbsd-arm64-7.0.2.tgz", - "integrity": "sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=16.20.0" - } - }, - "node_modules/@typescript/typescript-openbsd-x64": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-x64/-/typescript-openbsd-x64-7.0.2.tgz", - "integrity": "sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=16.20.0" - } - }, - "node_modules/@typescript/typescript-sunos-x64": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-sunos-x64/-/typescript-sunos-x64-7.0.2.tgz", - "integrity": "sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=16.20.0" - } - }, - "node_modules/@typescript/typescript-win32-arm64": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-arm64/-/typescript-win32-arm64-7.0.2.tgz", - "integrity": "sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=16.20.0" + "license": "MIT" + }, + "node_modules/@types/geojson": { + "version": "7946.0.16", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.2.18", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz", + "integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" } }, - "node_modules/@typescript/typescript-win32-x64": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-x64/-/typescript-win32-x64-7.0.2.tgz", - "integrity": "sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==", - "cpu": [ - "x64" - ], + "node_modules/@types/react-dom": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.4.tgz", + "integrity": "sha512-Bsc+QHgp+P/F02XDzNCY9jnZNCUuLki36KT7VKrTXXLdHf+vHMNZnW1rVu5DNW/rCK+fya3DATySbLM4yhtKUw==", "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=16.20.0" + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" } }, "node_modules/@vitejs/plugin-react": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", - "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.5.tgz", + "integrity": "sha512-BOVzne/NL162sMdResB25mUv+vWMF5NoAjNf09TeGlE7ZpszZWSD3winycicLJw72yeVsoCn/2kOhEuCvEShMA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/core": "^7.28.0", - "@babel/plugin-transform-react-jsx-self": "^7.27.1", - "@babel/plugin-transform-react-jsx-source": "^7.27.1", - "@rolldown/pluginutils": "1.0.0-beta.27", - "@types/babel__core": "^7.20.5", - "react-refresh": "^0.17.0" + "@rolldown/pluginutils": "^1.0.1" }, "engines": { - "node": "^14.18.0 || >=16.0.0" + "node": "^20.19.0 || >=22.12.0" }, "peerDependencies": { - "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", + "babel-plugin-react-compiler": "^1.0.0", + "vite": "^8.0.0" + }, + "peerDependenciesMeta": { + "@rolldown/plugin-babel": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + } } }, "node_modules/@vitest/expect": { @@ -1994,6 +1530,26 @@ "url": "https://opencollective.com/vitest" } }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -2019,6 +1575,13 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, "node_modules/aria-query": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", @@ -2039,84 +1602,54 @@ "node": ">=12" } }, - "node_modules/baseline-browser-mapping": { - "version": "2.10.12", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.12.tgz", - "integrity": "sha512-qyq26DxfY4awP2gIRXhhLWfwzwI+N5Nxk6iQi8EFizIaWIjqicQTE4sLnZZVdeKPRcVNoJOkkpfzoIYuvCKaIQ==", + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "dev": true, - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.cjs" - }, + "license": "MIT" + }, + "node_modules/axe-core": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.12.1.tgz", + "integrity": "sha512-s7iGf5GaVMxEG0ENN9x+xTr7GFZCb1ZP/1uATUpCEK2X78nDB3RwbtFCo9pGAf9ru+VwoQ464DkaLEeRM08wJA==", + "dev": true, + "license": "MPL-2.0", "engines": { - "node": ">=6.0.0" + "node": ">=4" } }, - "node_modules/bidi-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", - "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz", + "integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==", "dev": true, "license": "MIT", "dependencies": { - "require-from-string": "^2.0.2" + "balanced-match": "^1.0.0" } }, - "node_modules/browserslist": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", - "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.9.0", - "caniuse-lite": "^1.0.30001759", - "electron-to-chromium": "^1.5.263", - "node-releases": "^2.0.27", - "update-browserslist-db": "^1.2.0" - }, - "bin": { - "browserslist": "cli.js" + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" }, "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + "node": ">= 0.4" } }, - "node_modules/caniuse-lite": { - "version": "1.0.30001781", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001781.tgz", - "integrity": "sha512-RdwNCyMsNBftLjW6w01z8bKEvT6e/5tpPVEgtn22TiLGlstHOVecsX2KHFkD5e/vRnIE4EGzpuIODb3mtswtkw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, "node_modules/chai": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", @@ -2127,12 +1660,39 @@ "node": ">=18" } }, + "node_modules/change-case": { + "version": "5.4.4", + "resolved": "https://registry.npmjs.org/change-case/-/change-case-5.4.4.tgz", + "integrity": "sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==", + "dev": true, + "license": "MIT" + }, "node_modules/classcat": { "version": "5.0.5", "resolved": "https://registry.npmjs.org/classcat/-/classcat-5.0.5.tgz", "integrity": "sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==", "license": "MIT" }, + "node_modules/colorette": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", + "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/commander": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", @@ -2149,24 +1709,31 @@ "dev": true, "license": "MIT" }, - "node_modules/css-tree": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", - "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", + "node_modules/css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cssstyle": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.6.0.tgz", + "integrity": "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==", "dev": true, "license": "MIT", "dependencies": { - "mdn-data": "2.27.1", - "source-map-js": "^1.2.1" + "@asamuzakjp/css-color": "^3.2.0", + "rrweb-cssom": "^0.8.0" }, "engines": { - "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + "node": ">=18" } }, - "node_modules/css.escape": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", - "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "node_modules/cssstyle/node_modules/rrweb-cssom": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", + "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", "dev": true, "license": "MIT" }, @@ -2579,32 +2146,17 @@ } }, "node_modules/data-urls": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz", - "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==", - "dev": true, - "license": "MIT", - "dependencies": { - "whatwg-mimetype": "^5.0.0", - "whatwg-url": "^16.0.0" - }, - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" - } - }, - "node_modules/data-urls/node_modules/whatwg-url": { - "version": "16.0.1", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz", - "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", "dev": true, "license": "MIT", "dependencies": { - "@exodus/bytes": "^1.11.0", - "tr46": "^6.0.0", - "webidl-conversions": "^8.0.1" + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0" }, "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + "node": ">=18" } }, "node_modules/debug": { @@ -2641,6 +2193,16 @@ "robust-predicates": "^3.0.2" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/dequal": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", @@ -2669,26 +2231,54 @@ "license": "MIT", "peer": true }, - "node_modules/electron-to-chromium": { - "version": "1.5.328", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.328.tgz", - "integrity": "sha512-QNQ5l45DzYytThO21403XN3FvK0hOkWDG8viNf6jqS42msJ8I4tGDSpBCgvDRRPnkffafiwAym2X2eHeGD2V0w==", + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", "dev": true, - "license": "ISC" + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } }, "node_modules/entities": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz", - "integrity": "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", "dev": true, "license": "BSD-2-Clause", "engines": { - "node": ">=20.19.0" + "node": ">=0.12" }, "funding": { "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-module-lexer": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.1.tgz", @@ -2696,14 +2286,33 @@ "dev": true, "license": "MIT" }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", "dev": true, "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, "engines": { - "node": ">=6" + "node": ">= 0.4" } }, "node_modules/estree-walker": { @@ -2726,6 +2335,13 @@ "node": ">=12.0.0" } }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, "node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", @@ -2744,6 +2360,23 @@ } } }, + "node_modules/form-data": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.4", + "mime-types": "^2.1.35" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -2759,27 +2392,149 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "dev": true, "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, "engines": { - "node": ">=6.9.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" } }, "node_modules/html-encoding-sniffer": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", - "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-encoding": "^3.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", "dev": true, "license": "MIT", "dependencies": { - "@exodus/bytes": "^1.6.0" + "agent-base": "^7.1.2", + "debug": "4" }, "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + "node": ">= 14" } }, "node_modules/iconv-lite": { @@ -2804,6 +2559,19 @@ "node": ">=8" } }, + "node_modules/index-to-position": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/index-to-position/-/index-to-position-1.2.0.tgz", + "integrity": "sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/internmap": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", @@ -2820,46 +2588,80 @@ "dev": true, "license": "MIT" }, + "node_modules/js-levenshtein": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz", + "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, "license": "MIT" }, + "node_modules/js-yaml": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz", + "integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/jsdom": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-30.0.1.tgz", - "integrity": "sha512-52v7mUVUfNQVYYqE1lcdaymWL0njO7lTLUog6ZvW2U5KsbiLk/GnZlVJ+qx0xfNJZ6Gn+KSpPNE52vurbxZwrA==", + "version": "25.0.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-25.0.1.tgz", + "integrity": "sha512-8i7LzZj7BF8uplX+ZyOlIz86V6TAsSs+np6m1kpW9u0JWi4z/1t+FzcK1aek+ybTnAC4KhBL4uXCNT0wcUIeCw==", "dev": true, "license": "MIT", "dependencies": { - "@asamuzakjp/css-color": "^6.0.5", - "@asamuzakjp/dom-selector": "^8.3.0", - "@bramus/specificity": "^2.4.2", - "@csstools/css-syntax-patches-for-csstree": "^1.1.7", - "@exodus/bytes": "^1.15.1", - "css-tree": "^3.2.1", - "data-urls": "^7.0.0", - "decimal.js": "^10.6.0", - "html-encoding-sniffer": "^6.0.0", + "cssstyle": "^4.1.0", + "data-urls": "^5.0.0", + "decimal.js": "^10.4.3", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.5", "is-potential-custom-element-name": "^1.0.1", - "lru-cache": "^11.5.2", - "parse5": "^8.0.1", + "nwsapi": "^2.2.12", + "parse5": "^7.1.2", + "rrweb-cssom": "^0.7.1", "saxes": "^6.0.0", "symbol-tree": "^3.2.4", - "tough-cookie": "^6.0.2", - "undici": "^8.9.0", + "tough-cookie": "^5.0.0", "w3c-xmlserializer": "^5.0.0", - "webidl-conversions": "^8.0.1", - "whatwg-mimetype": "^5.0.0", - "whatwg-url": "^17.1.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0", + "ws": "^8.18.0", "xml-name-validator": "^5.0.0" }, "engines": { - "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + "node": ">=18" }, "peerDependencies": { - "canvas": "^3.2.3" + "canvas": "^2.11.2" }, "peerDependenciesMeta": { "canvas": { @@ -2867,46 +2669,17 @@ } } }, - "node_modules/jsdom/node_modules/lru-cache": { - "version": "11.5.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", - "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "dev": true, - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true, - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } + "license": "MIT" }, "node_modules/lightningcss": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", - "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", "dev": true, "license": "MPL-2.0", "dependencies": { @@ -2920,23 +2693,23 @@ "url": "https://opencollective.com/parcel" }, "optionalDependencies": { - "lightningcss-android-arm64": "1.32.0", - "lightningcss-darwin-arm64": "1.32.0", - "lightningcss-darwin-x64": "1.32.0", - "lightningcss-freebsd-x64": "1.32.0", - "lightningcss-linux-arm-gnueabihf": "1.32.0", - "lightningcss-linux-arm64-gnu": "1.32.0", - "lightningcss-linux-arm64-musl": "1.32.0", - "lightningcss-linux-x64-gnu": "1.32.0", - "lightningcss-linux-x64-musl": "1.32.0", - "lightningcss-win32-arm64-msvc": "1.32.0", - "lightningcss-win32-x64-msvc": "1.32.0" + "lightningcss-android-arm64": "1.33.0", + "lightningcss-darwin-arm64": "1.33.0", + "lightningcss-darwin-x64": "1.33.0", + "lightningcss-freebsd-x64": "1.33.0", + "lightningcss-linux-arm-gnueabihf": "1.33.0", + "lightningcss-linux-arm64-gnu": "1.33.0", + "lightningcss-linux-arm64-musl": "1.33.0", + "lightningcss-linux-x64-gnu": "1.33.0", + "lightningcss-linux-x64-musl": "1.33.0", + "lightningcss-win32-arm64-msvc": "1.33.0", + "lightningcss-win32-x64-msvc": "1.33.0" } }, "node_modules/lightningcss-android-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", - "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", "cpu": [ "arm64" ], @@ -2955,9 +2728,9 @@ } }, "node_modules/lightningcss-darwin-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", - "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", "cpu": [ "arm64" ], @@ -2976,9 +2749,9 @@ } }, "node_modules/lightningcss-darwin-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", - "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", "cpu": [ "x64" ], @@ -2997,9 +2770,9 @@ } }, "node_modules/lightningcss-freebsd-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", - "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", "cpu": [ "x64" ], @@ -3018,9 +2791,9 @@ } }, "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", - "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", "cpu": [ "arm" ], @@ -3039,16 +2812,13 @@ } }, "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", - "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", "cpu": [ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -3063,16 +2833,13 @@ } }, "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", - "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", "cpu": [ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -3087,16 +2854,13 @@ } }, "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", - "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", "cpu": [ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -3111,16 +2875,13 @@ } }, "node_modules/lightningcss-linux-x64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", - "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", "cpu": [ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -3135,9 +2896,9 @@ } }, "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", - "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", "cpu": [ "arm64" ], @@ -3156,9 +2917,9 @@ } }, "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", - "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", "cpu": [ "x64" ], @@ -3176,28 +2937,6 @@ "url": "https://opencollective.com/parcel" } }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "license": "MIT", - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, "node_modules/lz-string": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", @@ -3219,12 +2958,38 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, - "node_modules/mdn-data": { - "version": "2.27.1", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", - "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dev": true, - "license": "CC0-1.0" + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } }, "node_modules/min-indent": { "version": "1.0.1", @@ -3236,6 +3001,19 @@ "node": ">=4" } }, + "node_modules/minimatch": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -3262,10 +3040,17 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/node-releases": { - "version": "2.0.36", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz", - "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==", + "node_modules/node-readable-to-web-readable-stream": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/node-readable-to-web-readable-stream/-/node-readable-to-web-readable-stream-0.4.2.tgz", + "integrity": "sha512-/cMZNI34v//jUTrI+UIo4ieHAB5EZRY/+7OmXZgBxaWBMcW2tGdceIw06RFxWxrKZ5Jp3sI2i5TsRo+CBhtVLQ==", + "license": "MIT", + "optional": true + }, + "node_modules/nwsapi": { + "version": "2.2.24", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.24.tgz", + "integrity": "sha512-7YRhZ3jS45LwmSCT4b2sVFHt/WuovaktDU07QrtOBY2PXskss5a9jfmR9jptyumwXST+rFjrmppMY1KT/yn35A==", "dev": true, "license": "MIT" }, @@ -3283,14 +3068,53 @@ "node": ">=12.20.0" } }, + "node_modules/openapi-typescript": { + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/openapi-typescript/-/openapi-typescript-7.13.0.tgz", + "integrity": "sha512-EFP392gcqXS7ntPvbhBzbF8TyBA+baIYEm791Hy5YkjDYKTnk/Tn5OQeKm5BIZvJihpp8Zzr4hzx0Irde1LNGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@redocly/openapi-core": "^1.34.6", + "ansi-colors": "^4.1.3", + "change-case": "^5.4.4", + "parse-json": "^8.3.0", + "supports-color": "^10.2.2", + "yargs-parser": "^21.1.1" + }, + "bin": { + "openapi-typescript": "bin/cli.js" + }, + "peerDependencies": { + "typescript": "^5.x" + } + }, + "node_modules/parse-json": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.3.0.tgz", + "integrity": "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "index-to-position": "^1.1.0", + "type-fest": "^4.39.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/parse5": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.1.tgz", - "integrity": "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==", + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", "dev": true, "license": "MIT", "dependencies": { - "entities": "^8.0.0" + "entities": "^6.0.0" }, "funding": { "url": "https://github.com/inikulin/parse5?sponsor=1" @@ -3303,6 +3127,19 @@ "dev": true, "license": "MIT" }, + "node_modules/pdfjs-dist": { + "version": "5.6.205", + "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-5.6.205.tgz", + "integrity": "sha512-tlUj+2IDa7G1SbvBNN74UHRLJybZDWYom+k6p5KIZl7huBvsA4APi6mKL+zCxd3tLjN5hOOEE9Tv7VdzO88pfg==", + "license": "Apache-2.0", + "engines": { + "node": ">=20.19.0 || >=22.13.0 || >=24" + }, + "optionalDependencies": { + "@napi-rs/canvas": "^0.1.96", + "node-readable-to-web-readable-stream": "^0.4.2" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -3323,10 +3160,67 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/playwright": { + "version": "1.62.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.62.0.tgz", + "integrity": "sha512-Z14dG305dgaLu6foB1TXQagFiW8JfSUIUaUuPaKQ6NtBPKF1P/qXcqfh6c6K/icPqdy37JmjbiBXf6JNg6Sylw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.62.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=20" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.62.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.62.0.tgz", + "integrity": "sha512-nsNRyq0r2zsG8AcRHWknc9QRA5XCueC7gWMrs+Gx2tlZn9hcl8zudfh00lhJPY1DE7NmZ6bDsT9g2yey8mXljA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/pluralize": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", + "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/postcss": { - "version": "8.5.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.18.tgz", - "integrity": "sha512-xdB1oSLHbz1vRWgCDalrCqEFTWzFlhqFC5tIHLMOSUIjhm3XXQ1qrFy8S/ESr1JYRRXqM3c1QFiMZUJdUTqyMQ==", + "version": "8.5.25", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.25.tgz", + "integrity": "sha512-DTPx3RWSSnWyzLxQnlH0rJP+EW5ekl16ZU4/psbIhA0e53kJfdgaN5vKM+xP7yJtXVu+nfdVFmlgFDEKAe4Pyw==", "dev": true, "funding": [ { @@ -3344,7 +3238,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.12", + "nanoid": "^3.3.16", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -3379,13 +3273,10 @@ } }, "node_modules/react": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", - "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz", + "integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==", "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0" - }, "engines": { "node": ">=0.10.0" } @@ -3410,16 +3301,6 @@ "license": "MIT", "peer": true }, - "node_modules/react-refresh": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", - "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/reactflow": { "version": "11.11.4", "resolved": "https://registry.npmjs.org/reactflow/-/reactflow-11.11.4.tgz", @@ -3469,13 +3350,13 @@ "license": "Unlicense" }, "node_modules/rolldown": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.5.tgz", - "integrity": "sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.1.tgz", + "integrity": "sha512-4FKJhg8d3OiyQOA6Q1Q0hoFFpW9/OoX+VsHzpECsdsIZoOArrAK90gl59YK/Z+gnDel45bgJZK03ozH/9bCqEw==", "dev": true, "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.139.0", + "@oxc-project/types": "=0.142.0", "@rolldown/pluginutils": "^1.0.0" }, "bin": { @@ -3485,27 +3366,27 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.1.5", - "@rolldown/binding-darwin-arm64": "1.1.5", - "@rolldown/binding-darwin-x64": "1.1.5", - "@rolldown/binding-freebsd-x64": "1.1.5", - "@rolldown/binding-linux-arm-gnueabihf": "1.1.5", - "@rolldown/binding-linux-arm64-gnu": "1.1.5", - "@rolldown/binding-linux-arm64-musl": "1.1.5", - "@rolldown/binding-linux-ppc64-gnu": "1.1.5", - "@rolldown/binding-linux-s390x-gnu": "1.1.5", - "@rolldown/binding-linux-x64-gnu": "1.1.5", - "@rolldown/binding-linux-x64-musl": "1.1.5", - "@rolldown/binding-openharmony-arm64": "1.1.5", - "@rolldown/binding-wasm32-wasi": "1.1.5", - "@rolldown/binding-win32-arm64-msvc": "1.1.5", - "@rolldown/binding-win32-x64-msvc": "1.1.5" - } - }, - "node_modules/rolldown/node_modules/@rolldown/pluginutils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", - "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "@rolldown/binding-android-arm64": "1.2.1", + "@rolldown/binding-darwin-arm64": "1.2.1", + "@rolldown/binding-darwin-x64": "1.2.1", + "@rolldown/binding-freebsd-x64": "1.2.1", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.1", + "@rolldown/binding-linux-arm64-gnu": "1.2.1", + "@rolldown/binding-linux-arm64-musl": "1.2.1", + "@rolldown/binding-linux-ppc64-gnu": "1.2.1", + "@rolldown/binding-linux-s390x-gnu": "1.2.1", + "@rolldown/binding-linux-x64-gnu": "1.2.1", + "@rolldown/binding-linux-x64-musl": "1.2.1", + "@rolldown/binding-openharmony-arm64": "1.2.1", + "@rolldown/binding-wasm32-wasi": "1.2.1", + "@rolldown/binding-win32-arm64-msvc": "1.2.1", + "@rolldown/binding-win32-x64-msvc": "1.2.1" + } + }, + "node_modules/rrweb-cssom": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz", + "integrity": "sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==", "dev": true, "license": "MIT" }, @@ -3540,16 +3421,6 @@ "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", "license": "MIT" }, - "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/siginfo": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", @@ -3594,6 +3465,19 @@ "node": ">=8" } }, + "node_modules/supports-color": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz", + "integrity": "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, "node_modules/symbol-tree": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", @@ -3609,9 +3493,9 @@ "license": "MIT" }, "node_modules/tinyexec": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.4.tgz", - "integrity": "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.0.tgz", + "integrity": "sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==", "dev": true, "license": "MIT", "engines": { @@ -3646,49 +3530,49 @@ } }, "node_modules/tldts": { - "version": "7.4.9", - "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.4.9.tgz", - "integrity": "sha512-3kZ8wQQ/k5DrChD4X4FVvr2D7E5uoRgAqkPyLpSCGUvqOvqu+JEdr3mwMUaVWb+vMHZaKhF5fp2PBigKsui7hA==", + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", + "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", "dev": true, "license": "MIT", "dependencies": { - "tldts-core": "^7.4.9" + "tldts-core": "^6.1.86" }, "bin": { "tldts": "bin/cli.js" } }, "node_modules/tldts-core": { - "version": "7.4.9", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.4.9.tgz", - "integrity": "sha512-DxKfPBI52p2msTEu7MPhdpdDTBhhVQg1a/8PjQckeyAvO13eMYElX545grIp6nnTGIMZlRvFZPvFhvI/WIz2Vg==", + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz", + "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", "dev": true, "license": "MIT" }, "node_modules/tough-cookie": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.2.tgz", - "integrity": "sha512-exgYmnmL/sJpR3upZfXG5PoatXQii55xAiXGXzY+sROLZ/Y+SLcp9PgJNI9Vz37HpQ74WvDcLT8eqm+kV3FzrA==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", + "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", "dev": true, "license": "BSD-3-Clause", "dependencies": { - "tldts": "^7.0.5" + "tldts": "^6.1.32" }, "engines": { "node": ">=16" } }, "node_modules/tr46": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", - "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", "dev": true, "license": "MIT", "dependencies": { "punycode": "^2.3.1" }, "engines": { - "node": ">=20" + "node": ">=18" } }, "node_modules/tslib": { @@ -3699,82 +3583,40 @@ "license": "0BSD", "optional": true }, - "node_modules/typescript": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-7.0.2.tgz", - "integrity": "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==", + "node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc" - }, + "license": "(MIT OR CC0-1.0)", "engines": { - "node": ">=16.20.0" + "node": ">=16" }, - "optionalDependencies": { - "@typescript/typescript-aix-ppc64": "7.0.2", - "@typescript/typescript-darwin-arm64": "7.0.2", - "@typescript/typescript-darwin-x64": "7.0.2", - "@typescript/typescript-freebsd-arm64": "7.0.2", - "@typescript/typescript-freebsd-x64": "7.0.2", - "@typescript/typescript-linux-arm": "7.0.2", - "@typescript/typescript-linux-arm64": "7.0.2", - "@typescript/typescript-linux-loong64": "7.0.2", - "@typescript/typescript-linux-mips64el": "7.0.2", - "@typescript/typescript-linux-ppc64": "7.0.2", - "@typescript/typescript-linux-riscv64": "7.0.2", - "@typescript/typescript-linux-s390x": "7.0.2", - "@typescript/typescript-linux-x64": "7.0.2", - "@typescript/typescript-netbsd-arm64": "7.0.2", - "@typescript/typescript-netbsd-x64": "7.0.2", - "@typescript/typescript-openbsd-arm64": "7.0.2", - "@typescript/typescript-openbsd-x64": "7.0.2", - "@typescript/typescript-sunos-x64": "7.0.2", - "@typescript/typescript-win32-arm64": "7.0.2", - "@typescript/typescript-win32-x64": "7.0.2" - } - }, - "node_modules/undici": { - "version": "8.9.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-8.9.0.tgz", - "integrity": "sha512-aWZpUj7XoGonMClx4gdDRfgBjqeA+F473aDmROQQbM9n6PRfK/u1q/a0X4wMTgcHfT8H6fpbt98PFuDUwFg2YA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=22.19.0" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", - "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, + "license": "Apache-2.0", "bin": { - "update-browserslist-db": "cli.js" + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" }, - "peerDependencies": { - "browserslist": ">= 4.21.0" + "engines": { + "node": ">=14.17" } }, + "node_modules/uri-js-replace": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/uri-js-replace/-/uri-js-replace-1.0.1.tgz", + "integrity": "sha512-W+C9NWNLFOoBI2QWDp4UT9pv65r2w5Cx+3sTYFvtMdDBxkKt1syCqsUdSFAChbEe1uK5TfS04wt/nGwmaeIQ0g==", + "dev": true, + "license": "MIT" + }, "node_modules/use-sync-external-store": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", @@ -3785,16 +3627,16 @@ } }, "node_modules/vite": { - "version": "8.1.4", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.1.4.tgz", - "integrity": "sha512-bTT9PsdWO+MQMNG9ZXIP/qM9wGh37DFxTV/sPq9cFpHr3w4jkgef032PkAL9jAqhk3Nz8NQw3O8n6/xFkqO4QQ==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.2.0.tgz", + "integrity": "sha512-pn+CFpM0lwDeKwmOq1ZaBK/9sjorZcgqxki6MbY/jPEVd9vichIlmlD4HmQ5wdP5EgqQCFRaACBxMC7uEGc6lQ==", "dev": true, "license": "MIT", "dependencies": { - "lightningcss": "^1.32.0", + "lightningcss": "^1.33.0", "picomatch": "^4.0.5", - "postcss": "^8.5.16", - "rolldown": "~1.1.4", + "postcss": "^8.5.23", + "rolldown": "~1.2.0", "tinyglobby": "^0.2.17" }, "bin": { @@ -3811,7 +3653,7 @@ }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.3.0", + "@vitejs/devtools": "^0.4.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", @@ -3966,38 +3808,51 @@ } }, "node_modules/webidl-conversions": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", - "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", "dev": true, "license": "BSD-2-Clause", "engines": { - "node": ">=20" + "node": ">=12" + } + }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" } }, "node_modules/whatwg-mimetype": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", - "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", "dev": true, "license": "MIT", "engines": { - "node": ">=20" + "node": ">=18" } }, "node_modules/whatwg-url": { - "version": "17.1.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-17.1.0.tgz", - "integrity": "sha512-3GeworPmc2ZfEEHP7lEbUfBX/L75wdEsi0rLNhXcXxnoN5jyq0SL5gCy06SGW2cyTIZdTvWIDQNQoza++vKeaw==", + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", "dev": true, "license": "MIT", "dependencies": { - "@exodus/bytes": "^1.15.1", - "tr46": "^6.0.0", - "webidl-conversions": "^8.0.1" + "tr46": "^5.1.0", + "webidl-conversions": "^7.0.0" }, "engines": { - "node": "^22.14.0 || >=24.0.0" + "node": ">=18" } }, "node_modules/why-is-node-running": { @@ -4017,6 +3872,28 @@ "node": ">=8" } }, + "node_modules/ws": { + "version": "8.21.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.1.tgz", + "integrity": "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/xml-name-validator": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", @@ -4034,12 +3911,22 @@ "dev": true, "license": "MIT" }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "node_modules/yaml-ast-parser": { + "version": "0.0.43", + "resolved": "https://registry.npmjs.org/yaml-ast-parser/-/yaml-ast-parser-0.0.43.tgz", + "integrity": "sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==", "dev": true, - "license": "ISC" + "license": "Apache-2.0" + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } }, "node_modules/zustand": { "version": "4.5.7", diff --git a/ari-core/ari/viz/frontend/package.json b/ari-core/ari/viz/frontend/package.json index b1a5992c..37525761 100644 --- a/ari-core/ari/viz/frontend/package.json +++ b/ari-core/ari/viz/frontend/package.json @@ -3,17 +3,24 @@ "version": "1.0.0", "private": true, "type": "module", + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, "scripts": { "dev": "vite", "build": "vite build", "typecheck": "tsc --noEmit", "preview": "vite preview", "test": "vitest run", - "test:watch": "vitest" + "test:watch": "vitest", + "gen:v1types": "openapi-typescript ../v1/openapi.json -o src/services/api/v1types.gen.ts", + "capture:screenshots": "node scripts/capture_screenshots.mjs" }, "dependencies": { + "@tanstack/react-query": "^5.101.4", "d3": "^7.9.0", - "react": "^18.3.1", + "pdfjs-dist": "^5.6.205", + "react": "^19.2.8", "react-dom": "^19.2.8", "reactflow": "^11.11.4" }, @@ -22,12 +29,15 @@ "@testing-library/react": "^16.0.0", "@testing-library/user-event": "^14.5.2", "@types/d3": "^7.4.3", - "@types/react": "^18.3.5", + "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.3.1", - "jsdom": "^30.0.1", - "typescript": "^7.0.2", - "vite": "^8.1.4", + "@vitejs/plugin-react": "^6.0.5", + "axe-core": "^4.12.1", + "jsdom": "^25.0.1", + "openapi-typescript": "^7.13.0", + "playwright": "^1.62.0", + "typescript": "^5.9.3", + "vite": "^8.2.0", "vitest": "^4.1.10" } } diff --git a/ari-core/ari/viz/frontend/scripts/README.md b/ari-core/ari/viz/frontend/scripts/README.md new file mode 100644 index 00000000..8303b61e --- /dev/null +++ b/ari-core/ari/viz/frontend/scripts/README.md @@ -0,0 +1,58 @@ +# frontend/scripts + +Node tooling that runs *outside* the bundle. These scripts are never imported +by application code, so they live here rather than under `src/`. + +## Contents + +- `README.md` — this file. +- `capture_screenshots.mjs` — drives a headless Chromium over a running + +## Regenerating the documentation screenshots + +The docs embed these images, so a UI change that alters the shell — the sidebar +gained five entries when the v2 workspaces landed — invalidates every one of +them at once. Regenerate all locales together; a half-updated set is worse than +a uniformly stale one because the reader cannot tell which page is current. + +```bash +# 1. a server with real data to photograph +cd ari-core && python -m ari.viz.server --port 8765 & + +# 2. capture (from ari-core/ari/viz/frontend) +npm run capture:screenshots -- \ + --base-url http://127.0.0.1:8765 \ + --out ../../../../docs/assets/images \ + --run-id \ + --rqgm-run-id +``` + +`--rqgm-run-id` is what makes the Governance workspace show anything: on a +`simple_bfts` run that route deliberately renders a capability-state screen +instead. A schema-valid RQGM checkpoint can be generated without running a real +governed experiment: + +```python +from tests.fixtures.gui_refresh.rqgm_fixture_factory import make_rqgm_checkpoint +make_rqgm_checkpoint(Path("workspace/checkpoints/_rqgm_demo"), + nodes=12, epochs=2, seed=7, with_paper=True) +``` + +Delete that fixture checkpoint afterwards — it is a photographic prop, not a +run. + +### Headless prerequisites + +Chromium needs system libraries and fonts that a bare compute node usually +lacks. Without them the capture still "succeeds" and silently produces wrong +images: missing fonts render as tofu boxes rather than failing. + +- `npx playwright install chromium` for the browser binary. +- `libatk-1.0`, `libgbm`, `libXdamage` … — on a host without root, point + `LD_LIBRARY_PATH` at a prefix that provides them (a conda env works). + `ldd | grep "not found"` tells you what is missing. +- Fonts, installed to `~/.local/share/fonts` + `fc-cache -f`: + **Noto Sans CJK** (without it the `ja`/`zh` captures are entirely tofu) and + **Noto Color Emoji** (the sidebar icons are emoji). Verify with + `fc-match "sans:lang=ja"` before trusting a capture — then open one `ja` + image and look at it. diff --git a/ari-core/ari/viz/frontend/scripts/capture_screenshots.mjs b/ari-core/ari/viz/frontend/scripts/capture_screenshots.mjs new file mode 100644 index 00000000..345228f1 --- /dev/null +++ b/ari-core/ari/viz/frontend/scripts/capture_screenshots.mjs @@ -0,0 +1,105 @@ +// Dashboard screenshot capture for the documentation set. +// +// Drives a real headless Chromium over a running `ari.viz.server` and writes +// one PNG per (locale, route) into docs/assets/images//. The docs +// embed these, so a stale capture silently misdocuments the product — the +// sidebar alone changed from 10 to 15 entries when the v2 workspaces landed. +// +// The locale is set through localStorage `ari_lang` BEFORE the first paint +// (addInitScript), because the i18n provider reads it during bootstrap and +// only then resolves the lazily-loaded dictionary chunk. +// +// Usage (from ari-core/ari/viz/frontend): +// node scripts/capture_screenshots.mjs \ +// --base-url http://127.0.0.1:8765 \ +// --out ../../../../docs/assets/images \ +// --run-id [--rqgm-run-id ] \ +// [--langs en,ja,zh] [--only home,projects] +// +// On a machine without the Chromium system libraries, export LD_LIBRARY_PATH +// with a prefix that provides libatk/libgbm before running (see +// docs/guides/dashboard.md, "Regenerating the documentation screenshots"). +import { chromium } from 'playwright'; +import { mkdir } from 'node:fs/promises'; +import path from 'node:path'; + +const args = new Map(); +for (let i = 2; i < process.argv.length; i += 2) { + args.set(process.argv[i].replace(/^--/, ''), process.argv[i + 1]); +} +const BASE = args.get('base-url') ?? 'http://127.0.0.1:8765'; +const OUT = path.resolve(args.get('out') ?? '../../../../docs/assets/images'); +const LANGS = (args.get('langs') ?? 'en,ja,zh').split(','); +const RUN = args.get('run-id') ?? ''; +const RQGM_RUN = args.get('rqgm-run-id') ?? RUN; +const ONLY = args.get('only') ? new Set(args.get('only').split(',')) : null; +const VIEWPORT = { width: 1440, height: 900 }; + +// `wait` is a CSS selector that must be visible before the shot. Prefer the +// stable DOM ids the route-render baseline test pins; they are locale-neutral, +// unlike headings. `settle` buys time for D3/React Flow layout passes. +const SHOTS = [ + { name: 'dashboard_home', hash: '#/home' }, + { name: 'dashboard_projects', hash: '#/projects' }, + { name: 'dashboard_experiments', hash: '#/experiments' }, + { name: 'dashboard_overview', hash: '#/overview?run=$RUN' }, + { name: 'dashboard_monitor', hash: '#/monitor', wait: '#page-monitor' }, + { name: 'dashboard_tree', hash: '#/tree2?run=$RUN', wait: '#page-tree2', settle: 1500 }, + { name: 'dashboard_ideas', hash: '#/ideas2?run=$RUN', wait: '#page-ideas2' }, + { name: 'dashboard_results', hash: '#/results2?run=$RUN', wait: '#page-results2' }, + { name: 'dashboard_governance', hash: '#/governance?run=$RQGM', useRqgm: true }, + { name: 'dashboard_config', hash: '#/config?run=$RUN' }, + { name: 'dashboard_studio', hash: '#/studio', wait: '#page-studio' }, + { name: 'dashboard_wizard', hash: '#/wizard' }, + { name: 'dashboard_workflow', hash: '#/workflow', settle: 1500 }, + { name: 'dashboard_paperbench', hash: '#/paperbench' }, + { name: 'dashboard_settings', hash: '#/settings' }, +]; + +async function capture(browser, lang) { + const dir = path.join(OUT, lang); + await mkdir(dir, { recursive: true }); + const ctx = await browser.newContext({ viewport: VIEWPORT, deviceScaleFactor: 1 }); + await ctx.addInitScript((l) => { + window.localStorage.setItem('ari_lang', l); + // Developer Mode off so the docs show what a normal operator sees. + window.localStorage.removeItem('ari_dev_mode'); + }, lang); + const page = await ctx.newPage(); + const results = []; + for (const shot of SHOTS) { + if (ONLY && !ONLY.has(shot.name.replace('dashboard_', ''))) continue; + const hash = shot.hash.replace('$RQGM', RQGM_RUN).replace('$RUN', RUN); + if (/\$|run=$/.test(hash)) { + results.push({ name: shot.name, skipped: 'no run id supplied' }); + continue; + } + try { + await page.goto(`${BASE}/${hash}`, { waitUntil: 'networkidle', timeout: 30000 }); + if (shot.wait) await page.waitForSelector(shot.wait, { state: 'visible', timeout: 15000 }); + else await page.waitForSelector('h1, h2, .card-title', { state: 'visible', timeout: 15000 }); + await page.waitForTimeout(shot.settle ?? 700); + const file = path.join(dir, `${shot.name}.png`); + await page.screenshot({ path: file }); + results.push({ name: shot.name, file }); + } catch (err) { + results.push({ name: shot.name, error: String(err).split('\n')[0] }); + } + } + await ctx.close(); + return results; +} + +const browser = await chromium.launch(); +const report = {}; +for (const lang of LANGS) report[lang] = await capture(browser, lang); +await browser.close(); + +let failed = 0; +for (const [lang, rows] of Object.entries(report)) { + for (const r of rows) { + if (r.error) failed += 1; + console.log(`${lang}\t${r.name}\t${r.error ? 'ERROR ' + r.error : r.skipped ? 'SKIP ' + r.skipped : 'ok'}`); + } +} +process.exit(failed ? 1 : 0); diff --git a/ari-core/ari/viz/frontend/src/App.tsx b/ari-core/ari/viz/frontend/src/App.tsx index 95bcadb9..a58cb3d1 100644 --- a/ari-core/ari/viz/frontend/src/App.tsx +++ b/ari-core/ari/viz/frontend/src/App.tsx @@ -1,64 +1,92 @@ import { lazy, Suspense, useEffect, useState } from 'react'; import type { LazyExoticComponent, ComponentType } from 'react'; +import { QueryClientProvider } from '@tanstack/react-query'; import { AppProvider } from './context/AppContext'; +import { I18nProvider } from './i18n/I18nProvider'; +import { queryClient } from './app/queryClient'; import { Layout } from './components/Layout'; import { LoadingState } from './components/common'; +import { ROUTE_REGISTRY, resolveRoute } from './app/routeRegistry'; +import { fetchCapabilities } from './services/api'; import './styles/dashboard.css'; -// Lazy-load page components -const HomePage = lazy(() => import('./components/Home/HomePage').then((m) => ({ default: m.HomePage }))); -const ExperimentsPage = lazy(() => import('./components/Experiments/ExperimentsPage').then((m) => ({ default: m.ExperimentsPage }))); -const MonitorPage = lazy(() => import('./components/Monitor/MonitorPage').then((m) => ({ default: m.MonitorPage }))); -const TreePage = lazy(() => import('./components/Tree/TreePage').then((m) => ({ default: m.TreePage }))); -const ResultsPage = lazy(() => import('./components/Results/ResultsPage').then((m) => ({ default: m.ResultsPage }))); -const WizardPage = lazy(() => import('./components/Wizard/WizardPage').then((m) => ({ default: m.WizardPage }))); -const IdeaPage = lazy(() => import('./components/Idea/IdeaPage')); -const WorkflowPage = lazy(() => import('./components/Workflow/WorkflowPage')); -const SettingsPage = lazy(() => import('./components/Settings/SettingsPage')); -const PaperRegistryPage = lazy(() => - import('./components/PaperBench').then((m) => ({ default: m.PaperRegistryPage })), -); -const PaperImportDialog = lazy(() => - import('./components/PaperBench').then((m) => ({ default: m.PaperImportDialog })), -); -const PaperBenchWizard = lazy(() => - import('./components/PaperBench').then((m) => ({ default: m.PaperBenchWizard })), -); -const PaperBenchResultsView = lazy(() => - import('./components/PaperBench').then((m) => ({ default: m.ResultsView })), -); +// ── route dispatch (derived from the single-source route registry) ── + +// PAGE_MAP is built from ROUTE_REGISTRY (gui_refresh Wave 1 task 03): one +// React.lazy component per route, created once at module scope so +// code-splitting chunks and component identity stay stable across renders +// (the registry stores plain load thunks). Legacy aliases (new -> wizard) +// share the canonical route's lazy instance, preserving the historical key +// set. Exported for the route <-> nav parity test. +export const PAGE_MAP: Record> = (() => { + const map: Record> = {}; + for (const route of ROUTE_REGISTRY) { + const Page = lazy(route.load); + map[route.path] = Page; + for (const alias of route.legacyAliases ?? []) { + map[alias] = Page; + } + } + return map; +})(); + +// ── ARI_GUI_V2 capability gate (gui_refresh Wave 1) ── + +// Route ids that exist only in the v2 shell. When the server reports +// gui_v2: false (ARI_GUI_V2=0/false — the env kill-switch; owner: task 03, +// removal gate: G6), these resolve to Home exactly like an unknown hash, so +// the legacy surface stays fully reachable without a redeploy. +// Wave 2b: 'projects' (the first v2 vertical slice, plan 07 §Projects and +// run portfolio) hangs off this gate; its nav entry is filtered in Sidebar +// via the registry's guiV2 marker. +// Wave 3b: 'config' (read-only effective-config browser, plans 05/06) joins +// the same gate. +// Wave 4a: 'governance' (read-only RQGM governance & score lineage +// workspace, plan 08) joins the same gate. +// Wave 4b: 'overview' (v2 run Overview workspace, plan 07 §Run Overview and +// Live Monitor) joins the same gate. +// Wave 4c: 'tree2' (v2 Tree workspace, plan 07 §Tree workspace — route id +// 'tree_v2') joins the same gate. The set holds DISPATCH KEYS (route paths, +// which parseHash returns); for every earlier entry path === id, tree_v2 is +// the first route where they differ. +// Wave 4c (Ideas): 'ideas2' (v2 Ideas workspace, plan 07 §Ideas, claims, +// and evidence — route id 'ideas_v2') joins the same gate. +// Wave 4d (Results): 'results2' (v2 Results/EAR workspace, plan 07 +// §Evidence, Results, and PaperBench — route id 'results_v2') joins the +// same gate. +// Wave 4d (task 06): 'studio' (Configuration Studio non-governance slice, +// plan 06) joins the same gate. It gets its OWN nav slot — the legacy +// 'settings' route is NOT replaced yet (cutover is a later slice). +export const V2_ONLY_ROUTES: ReadonlySet = new Set([ + 'projects', + 'overview', + 'tree2', + 'ideas2', + 'results2', + 'config', + 'studio', + 'governance', +]); // ── helpers ── function parseHash(): string { - // Strip query string so '#/paperbench/results?job=xyz' resolves to - // 'paperbench/results' in the PAGE_MAP lookup. - const raw = window.location.hash.replace(/^#\/?/, '').split('?')[0]; - // Map legacy "new" route to "wizard" - if (raw === 'new') return 'wizard'; - return raw || 'home'; + // resolveRoute strips the '#/' prefix and any query string (so + // '#/paperbench/results?job=xyz' resolves to 'paperbench/results'), treats + // an empty hash as 'home', and maps the legacy 'new' hash to its canonical + // route ('wizard'). + // Returns the route PATH — the PAGE_MAP dispatch key. Identical to the id + // for every route except 'tree_v2' (path 'tree2'), 'ideas_v2' (path + // 'ideas2') — the gui_refresh Wave 4c routes — and 'results_v2' (path + // 'results2', Wave 4d), whose id and path differ. + // Unknown hashes silently fall back to Home — UNCHANGED in Wave 1. + // TODO(gui_refresh Wave 2+): explicit resolution screen for unknown hashes. + return resolveRoute(window.location.hash)?.path ?? 'home'; } -const PAGE_MAP: Record> = { - home: HomePage, - experiments: ExperimentsPage, - monitor: MonitorPage, - tree: TreePage, - results: ResultsPage, - new: WizardPage, - wizard: WizardPage, - idea: IdeaPage, - workflow: WorkflowPage, - settings: SettingsPage, - paperbench: PaperRegistryPage, - 'paperbench/import': PaperImportDialog, - 'paperbench/run': PaperBenchWizard, - 'paperbench/results': PaperBenchResultsView, -}; - // ── inner router (uses context) ── -function Router() { +function Router({ guiV2 }: { guiV2: boolean }) { const [page, setPage] = useState(parseHash); useEffect(() => { @@ -67,10 +95,14 @@ function Router() { return () => window.removeEventListener('hashchange', onHashChange); }, []); - const PageComponent = PAGE_MAP[page] ?? HomePage; + // Legacy fallback: with gui_v2 off, v2-only routes fall back to Home (the + // registry serves both shells — legacy routes/URLs are shared, so nothing + // else changes). With the flag on (the default) this is a no-op. + const effectivePage = !guiV2 && V2_ONLY_ROUTES.has(page) ? 'home' : page; + const PageComponent = PAGE_MAP[effectivePage] ?? PAGE_MAP['home']; return ( - + @@ -87,9 +119,38 @@ function Router() { // ── App ── export default function App() { + // Fetched once on mount. Defaults ON, and STAYS on when the fetch fails: + // this is a loopback tool, so an API hiccup must never drop the user into + // the fallback shell (gui_refresh feature-flag policy, plan 10 §Wave 1). + const [guiV2, setGuiV2] = useState(true); + + useEffect(() => { + let cancelled = false; + fetchCapabilities() + .then((caps) => { + if (!cancelled && caps.gui_v2 === false) setGuiV2(false); + }) + .catch(() => { + /* default ON — see comment above */ + }); + return () => { + cancelled = true; + }; + }, []); + + // QueryClientProvider (gui_refresh Wave 2b): server-state cache seam for + // the typed /api/v1 hooks (src/hooks/useV1.ts). Transparent to Wave-1 + // pages — no page consumes queries until the Slice stage adopts them. + // I18nProvider (gui_refresh Wave 4d, task 09): ready-gate that lazy-loads + // the active locale dict (+ en fallback) before the shell renders, keeping + // the three i18n dictionaries out of the main chunk. return ( - - - + + + + + + + ); } diff --git a/ari-core/ari/viz/frontend/src/README.md b/ari-core/ari/viz/frontend/src/README.md index 306e8b2f..22f4c031 100644 --- a/ari-core/ari/viz/frontend/src/README.md +++ b/ari-core/ari/viz/frontend/src/README.md @@ -8,9 +8,19 @@ React/TypeScript source for the ARI `ari.viz` web dashboard — app entry, pages - `App.tsx` — root app, lazy-loads page components and wraps them in `AppProvider`/`Layout`. - `main.tsx` — ReactDOM entry point with a top-level error boundary. - `vite-env.d.ts` — Vite client type declarations. -- `__tests__/` — TODO - - `devModeAndDangerousOps.test.tsx` — TODO - - `routeNavParity.test.tsx` — TODO +- `__tests__/` — app-wide guard suites owned by no feature: route↔nav parity, full-App route render and shell a11y baselines, developer-mode/dangerous-ops gating, `AppContext` scope, the index.html supply-chain rule, and generated-v1-type drift. + - `appContextScope.test.ts` — structural guard: v2 component dirs stay free of `context/AppContext` imports (legacy remote-data store; pinned exception: IdeasV2Page research-goal card) and `AppContext.tsx` keeps its LEGACY-SCOPED header. + - `devModeAndDangerousOps.test.tsx` — Tier-2 gating invariants: developer mode OFF hides the raw node-JSON tab and the env-key readback (no secret probe on mount), destructive calls go only through the server-issued challenge two-step (MN-6), and the shared loading/empty/error kit renders; the ARIA-tabs invariant stays `it.todo` until 068/069/070 land. + - `indexHtmlNoExternalScripts.test.ts` — supply-chain guard: the SPA entry document may reference no external script/stylesheet, so every asset comes from the lockfile-pinned Vite bundle and the backend's `script-src 'self'` CSP stays honest. + - `routeNavParity.test.tsx` — Tier-1 route↔nav parity over the real `PAGE_MAP`/`NAV_ITEMS` tables: every route has a nav entry or is an explicit hidden route, and the 18-entry nav table (keys, icons, labels, order, `guiV2`/`navReplaces`) is frozen against the pre-registry literal. + - `routeRenderBaseline.test.tsx` — mounts the FULL registry-driven App at each nav route hash with all network I/O mocked and asserts each page's stable marker renders (no route may silently lose its mount path). + - `shellA11yBaseline.test.tsx` — axe + heading baseline for the shell: positive invariants must never regress and the frozen violation ids are a ratchet — a new violation fails, and a fixed one must be removed from the literal. + - `v1TypesDrift.test.ts` — regenerates `services/api/v1types.gen.ts` in memory from `ari/viz/v1/openapi.json` and asserts byte equality with the committed file. +- `app/` — application shell wiring shared by every route (no feature owns it). + - `queryClient.ts` — shared react-query client factory + app singleton (staleTime 5s matching the legacy `/state` cadence, retry 1, no refetch-on-window-focus); tests build an isolated client per render. + - `routeRegistry.ts` — single source of truth for routes: hash paths, lazy page loaders, nav label/order/icon, legacy aliases and `gui_v2` gating/takeover; `App.tsx` and `Sidebar.tsx` both derive from it, and the `#/` URLs are a frozen deployment contract. + - `__tests__/` — app-shell unit tests. + - `routeRegistry.test.tsx` — freezes the registry as literals against the deployed contract (id/path set, nav order + i18n keys, `new` → `wizard` alias, query-string stripping, unknown hash → undefined). - `components/` — page and UI components, grouped by feature. - `README.md` — components index. - `common/` — reusable presentational UI primitives shared across pages. @@ -18,18 +28,46 @@ React/TypeScript source for the ARI `ari.viz` web dashboard — app entry, pages - `Badge.tsx` — colored variant label span. - `Button.tsx` — styled button with variant/size props. - `Card.tsx` — bordered content container. - - `EmptyState.tsx` — TODO - - `ErrorState.tsx` — TODO + - `DegradedState.tsx` — canonical PARTIAL-data affordance (warning tokens, `role="status"`, surrounding content stays visible); deliberately distinct from `ErrorState`, which means total failure. + - `EmptyState.tsx` — canonical "no data yet" block on the `.empty-state`/`.empty-icon` CSS; caller passes an already-translated `message` plus optional emoji `icon` and `hint`. + - `ErrorState.tsx` — canonical total-failure affordance: `var(--red)` message plus optional Retry button, rendering a plain string from either api error regime (thrown message or `{error}` body). - `index.ts` — barrel re-exports. - - `LoadingState.tsx` — TODO + - `LoadingState.tsx` — canonical spinner + translated label replacing the ad-hoc ` {t('loading')}` patterns; `inline` renders a row instead of a centered block. + - `NavRail.tsx` — vertical slice navigation: a real list with `aria-current` on the selected item, selected/unselected states from the `.nav-rail` tokens; navigation, so deliberately not `Button`. + - `StaleDataBanner.tsx` — freshness notice for live surfaces: keeps the last known snapshot visible and says how fresh it is (`aria-live="polite"`); a dropped stream is never presented as "run stopped". - `StatBox.tsx` — single value + label stat tile. - `StatusBadge.tsx` — maps run status to a colored `Badge`. - - `__tests__/` — TODO - - `StateComponents.test.tsx` — TODO + - `TabStrip.tsx` — the ONE v2 tablist look (`role=tablist/tab`, `aria-selected`/`aria-controls`); the owning page renders the matching `role="tabpanel"` using the `${idPrefix}-tab-` / `${idPrefix}-panel-` id convention. + - `__tests__/` — unit tests for the primitives in this directory. + - `asyncStates.test.tsx` — `DegradedState`/`StaleDataBanner` contract: status roles, default translated titles, and the retry/refresh callbacks. + - `StateComponents.test.tsx` — `LoadingState`/`EmptyState`/`ErrorState` kit contract: default vs overridden labels, icon/hint rendering, and the retry button existing only when `onRetry` is passed. + - `ConfigBrowser/` — read-only effective-config browser (`#/config?run=`). + - `README.md` — ConfigBrowser index. + - `ConfigBrowserPage.tsx` — schema/effective-config browser (category groups, search, provenance/mutability badges, secret-reference redaction, resolver warnings). + - `ConfigReadOnlyTable.tsx` — the ONE read-only config row/table renderer (value formatting, secret redaction, `SOURCE_VARIANT`/`MUTABILITY_VARIANT` badge maps); shared with the Configuration Studio's ADR-09 Execution section so the two config surfaces cannot drift. + - `index.ts` — barrel re-export. + - `__tests__/` — component tests for this directory. + - `ConfigBrowserPage.test.tsx` — tests for `ConfigBrowserPage.tsx` (144-field schema mode + search, run-mode provenance/warnings/secret redaction, error envelope with request_id). + - `ConfigStudio/` — schema-driven Configuration Studio (`#/studio`): edits the project/template/draft config documents and launches drafts. + - `README.md` — ConfigStudio index. + - `ConfigStudioPage.tsx` — schema-driven form over project/template/draft scopes. + - `ExecutionSection.tsx` — ADR-09 execution/paper mode selection + the read-only `rqgm.*` tree. + - `index.ts` — barrel re-exports. + - `LaunchPanel.tsx` — draft launch flow (resolve/validate → immutable review → idempotent launch). + - `modeIntents.ts` — the two ADR-09 mode intent pairs (frontend mirror of `field_registry.MODE_INTERLOCK_PAIRS`) + leaf readers. + - `SecretField.tsx` — write-only secret assignment + readiness display. + - `StudioPickers.tsx` — template select/create + draft create (incl. goal) controls. + - `ValidationSummary.tsx` — per-path `details.errors` renderer. + - `__tests__/` — component tests for this directory. + - `README.md` — __tests__ index. + - `ConfigStudioExecutionMode.test.tsx` — tests for `ExecutionSection.tsx` + the ADR-09 launch-review wiring (mode selection accepted 2026-07-27): one control writes BOTH pair keys in a single PATCH, the two intents are orthogonal, all four combinations render and round-trip, re-selecting the stored value writes nothing (default path byte-identical), an inconsistent stored pair is flagged, the `rqgm.*` tree renders values with no editable control, the launch review shows the RESOLVED mode (requested→resolved + resolver warning on a fallback), and `mode_interlock_mismatch` blocks the launch with its typed message. + - `ConfigStudioLaunch.test.tsx` — tests for the `LaunchPanel.tsx` launch flow (gui_refresh task 06 Wave 4e, plan 06 §Launch protocol / backend MN-10): resolve→validate→review→launch happy path with the canonical `#/overview?run=` redirect from the server-issued run_id, validation failure (`mode_locked`) blocking the POST, double-click single-POST + same-idempotency-key retry, typed error envelope rendering (request_id + per-path `details.errors`). + - `ConfigStudioPage.test.tsx` — tests for `ConfigStudioPage.tsx` (gui_refresh task 06 Wave 4d): schema-driven control generation, If-Match PATCH + 409 reload banner + per-path 400 ValidationSummary, write-only secret flow, the ADR-09 Execution section refused in PROJECT scope. - `Experiments/` — experiments page (lists experiment/checkpoint runs). - `README.md` — Experiments index. - `ExperimentsPage.tsx` — experiments list view. - `index.ts` — barrel re-export. + - `Governance/` — Read-only RQGM Governance workspace (gui_refresh task 08 Wave 4a; plan 08 - `Home/` — home/overview landing page. - `README.md` — Home index. - `HomePage.tsx` — home/overview view. @@ -38,6 +76,12 @@ React/TypeScript source for the ARI `ari.viz` web dashboard — app entry, pages - `README.md` — Idea index. - `IdeaPage.tsx` — idea view. - `index.ts` — barrel re-export. + - `IdeasV2/` — v2 Ideas workspace (`#/ideas2?run=`): run-explicit, read-only idea/hypothesis view that takes over the Idea nav slot while `gui_v2` is on. + - `README.md` — IdeasV2 index. + - `IdeasV2Page.tsx` — the page component plus exported pure helpers + - `index.ts` — barrel re-export. + - `__tests__/` — component tests for this directory. + - `IdeasV2Page.test.tsx` — page contract tests (happy/absent/degraded/ - `Layout/` — app shell (page frame + nav sidebar). - `README.md` — Layout index. - `index.ts` — barrel re-export. @@ -50,6 +94,16 @@ React/TypeScript source for the ARI `ari.viz` web dashboard — app entry, pages - `MonitorPage.tsx` — monitor page container. - `monitorSections.tsx` — metric helper + Experiment-Configuration card (extracted from MonitorPage in req 15). - `PhaseStepper.tsx` — workflow phase progress bar (idea→bfts→paper→review). + - `__tests__/` — component tests for this directory. + - `MonitorPage.test.tsx` — RR-D-1 regression: partial `/api/resource-metrics` payload renders placeholders, never a TypeError crash. + - `Overview/` — v2 run Overview workspace (`#/overview?run=`): read-only lifecycle/phase/blocker summary plus the embedded log explorer. + - `README.md` — Overview index. + - `index.ts` — barrel re-export. + - `LogsPanel.tsx` — collapsible cursor log explorer (P4): [Load more] + - `OverviewPage.tsx` — the P1/P2 Overview workspace (typed `/api/v1` + - `__tests__/` — component tests for this directory. + - `LogsPanel.test.tsx` — tests for `LogsPanel.tsx` (lazy collapsed + - `OverviewPage.test.tsx` — tests for `OverviewPage.tsx` (P1/P2 render, - `PaperBench/` — register external papers, import them, launch/inspect PaperBench runs. - `README.md` — PaperBench index. - `index.ts` — barrel re-exports. @@ -63,11 +117,18 @@ React/TypeScript source for the ARI `ari.viz` web dashboard — app entry, pages - `results/` — rubric-scored results view. - `README.md` — results index. - `ResultsView.tsx` — leaf grades + rubric tree + negative-control display. + - `Projects/` — v2 run portfolio (`#/projects`), the first v2 vertical slice; read-only and built only on the typed `/api/v1` hooks. + - `README.md` — Projects index. + - `index.ts` — barrel re-export. + - `ProjectsPage.tsx` — v2 run-portfolio table (projects -> runs of the virtual `default` project). + - `__tests__/` — component tests for this directory. + - `ProjectsPage.test.tsx` — tests for `ProjectsPage.tsx` (rows, empty state, error envelope, two-run isolation, results handoff). - `Results/` — final run results and rubric scoring. - `README.md` — Results index. - `EarSection.tsx` — Experiment Artifact Repository section (curate/publish/publish.yaml editor); extracted from ResultsPage renderEAR in req 15. - `index.ts` — barrel re-export. - `PaperWorkspace.tsx` — Overleaf-like paper editor (file tree + PDF/editor views + compile log); extracted from ResultsPage renderPaper in req 15. + - `PdfPreview.tsx` — app-owned PDF.js canvas preview (page nav, zoom, loading/error states) so managed or Chromium browsers without an embedded viewer still show the compiled paper. - `PublishYamlEditor.tsx` — per-checkpoint publish.yaml (EAR allowlist) editor; extracted from ResultsPage in req 03. - `resultHelpers.ts` — pure helpers + string formatters (tryParseJson, buildGradeMap, aggregateScore, format*Stage, etc.); extracted from resultSections in req 15. - `resultSections.tsx` — presentational subcomponents and pure helpers for the results page; extracted from ResultsPage in req 03. @@ -75,34 +136,40 @@ React/TypeScript source for the ARI `ari.viz` web dashboard — app entry, pages - `resultTypes.ts` — Results-page shared types (OrsRenderInput, RubricNode, LeafGrade, StageState); extracted from resultSections in req 15. - `RubricTreeVisualization.tsx` — D3 rubric tree with aggregated leaf scores. - `useEAR.ts` — hook owning EarSection's curate/publish/publish.yaml-editor action state; extracted from ResultsPage in req 15. - - `sections/` — TODO - - `ContextSection.tsx` — TODO - - `FiguresSection.tsx` — TODO - - `OrsChainSection.tsx` — TODO - - `ReproSection.tsx` — TODO - - `ReviewScoresSection.tsx` — TODO + - `__tests__/` — unit tests for this directory. + - `ResultsPageRoute.test.ts` — `runFromResultsHash` route parsing: `#/results?run=` yields the URL-decoded run id, a bare `#/results` yields an empty selection. + - `sections/` — the pure `render*` section functions split out of the ResultsPage container: context, figures, ORS chain, reproducibility, review scores. + - `ContextSection.tsx` — `renderContext` — experiment-context card over `summary.science_data.experiment_context`, values over 500 chars collapsed into `
`; null when absent. + - `FiguresSection.tsx` — `renderFigures` — figures grid normalizing `figures_manifest`'s dict and legacy-list shapes, with captions extracted from the stored LaTeX snippets. + - `OrsChainSection.tsx` — `renderOrsChain` — PaperBench-aware ORS chain: headline verdict plus per-stage status for `ors_rubric_meta` / `ors_replicator` / `ors_seed` / `ors_phase1` / `ors_grade` and the rubric tree. + - `ReproSection.tsx` — `renderRepro` — reproducibility section dispatching to the ORS chain or the legacy panel plus the repro-log toolbar; `renderLegacyRepro` handles the pre-§4.1 `reproducibility_report` shape. + - `ReviewScoresSection.tsx` — `renderReviewScores` — `summary.review_report` card mapping decision to badge variant and rendering rubric-driven or legacy dimensional scores. + - `ResultsV2/` — v2 Results/EAR workspace (`#/results2?run=`): read-only run-explicit result summary; every EAR mutation stays on the legacy Results page. + - `ResultsV2Page.tsx` — review/ORS summary plus the curate→preview→publish→promote EAR lineage as a read-only badge chain, with the exported `deriveEarLineage`/`shortDigest` helpers. + - `__tests__/` — component tests for this directory. + - `ResultsV2Page.test.tsx` — tests for `ResultsV2Page.tsx` (summary + lineage rendering, honest absence, error envelope, nav takeover of the Results slot). - `Settings/` — dashboard/run configuration page. - `README.md` — Settings index. - `index.ts` — barrel re-export. - `settingsConstants.ts` — provider/Letta model tables + _splitHandle helper (extracted from SettingsPage in req 15). - - `SettingsGroup.tsx` — TODO + - `SettingsGroup.tsx` — progressive-disclosure wrapper grouping cards under a sensitivity tier; collapsing toggles CSS `display` only and never unmounts children, so all ten `.card-title`s stay in the DOM. - `SettingsPage.tsx` — settings view. - - `settingsStyles.ts` — TODO - - `settingsTypes.ts` — TODO - - `__tests__/` — TODO - - `SettingsContract.test.tsx` — TODO - - `SettingsDisclosure.test.tsx` — TODO - - `sections/` — TODO - - `ContainerSection.tsx` — TODO - - `LanguageSection.tsx` — TODO - - `LlmBackendSection.tsx` — TODO - - `MemorySection.tsx` — TODO - - `PaperRetrievalSection.tsx` — TODO - - `ProjectManagementSection.tsx` — TODO - - `SkillsSection.tsx` — TODO - - `SlurmSection.tsx` — TODO - - `SshSection.tsx` — TODO - - `VlmReviewSection.tsx` — TODO + - `settingsStyles.ts` — shared `inputStyle` / `labelStyle` field styles moved verbatim out of SettingsPage so every `sections/*` component consumes one definition. + - `settingsTypes.ts` — shared prop/data types for the decomposed sections: the threaded `TFn` translator, the `SkillInfo` row, and the `LettaDeployment` union. + - `__tests__/` — the frozen Settings contract tests (ten cards, 24-key save payload) plus the progressive-disclosure safety test. + - `SettingsContract.test.tsx` — Tier-1 frozen contract: all ten section `` titles render, and Save POSTs exactly the 24-key flat object to `/api/settings`. + - `SettingsDisclosure.test.tsx` — pins the 069 tiers: four `settings-group-header`s render and collapsing one keeps all ten cards mounted (CSS-only, no unmount). + - `sections/` — the ten presentational `` sections SettingsPage composes into its four sensitivity tiers; each takes state + setters as props. + - `ContainerSection.tsx` — container card — mode (auto/docker/singularity/apptainer/none), pull policy, image, and the Detect Runtime probe badge. + - `LanguageSection.tsx` — UI language card — en/ja/zh select wired to the orchestrator's `onLangChange`. + - `LlmBackendSection.tsx` — LLM backend card — provider select (openai/anthropic/gemini/ollama/cli-shim), model dropdown plus custom entry, temperature, API key, and the ollama/cli-shim base URL. + - `MemorySection.tsx` — Letta memory card — base URL, API key, embedding provider/handle picker, and the deployment-path Restart button calling `restartLetta` behind a confirm with status feedback. + - `PaperRetrievalSection.tsx` — paper retrieval card — backend radio (Semantic Scholar / AlphaXiv / both) and the optional Semantic Scholar API key. + - `ProjectManagementSection.tsx` — checkpoint roster card with running/active badges and the per-project Delete button that drives the challenge-gated `deleteCheckpoint` flow. + - `SkillsSection.tsx` — read-only table of the `GET /api/skills` rows — name, display name, description, and required env (or an `any` badge). + - `SlurmSection.tsx` — SLURM/HPC defaults card — partition multi-select with a Detect probe, CPUs, memory (GB), and walltime. + - `SshSection.tsx` — remote-host card — host/port/user/remote ARI path/key path plus the Test SSH probe and its ✓/✗ status badge. + - `VlmReviewSection.tsx` — VLM figure-review card — model picker drawn from `PROVIDER_MODELS` for the currently selected provider. - `Tree/` — BFTS tree page (search tree, detail panel, file browser). - `README.md` — Tree index. - `DetailPanel.tsx` — selected-node detail panel (tabs: memory, report, etc.). @@ -120,6 +187,7 @@ React/TypeScript source for the ARI `ari.viz` web dashboard — app entry, pages - `MemoryTab.tsx` — memory tab (own/inherited/global entry cards); extracted from DetailPanel in req 15. - `ReportTab.tsx` — node-report tab (node_report.json structured view); extracted from DetailPanel in req 15. - `TraceTab.tsx` — MCP-trace tab (tool pills + colored trace log); extracted from DetailPanel in req 15. + - `TreeV2/` — Run-explicit tree exploration route (`#/tree2?run=&node=`, route id - `Wizard/` — multi-step run-launch wizard. - `README.md` — Wizard index. - `index.ts` — barrel re-export. @@ -132,10 +200,12 @@ React/TypeScript source for the ARI `ari.viz` web dashboard — app entry, pages - `Workflow/` — workflow stages/pipeline page. - `README.md` — Workflow index. - `index.ts` — barrel re-export. - - `workflowModals.tsx` — TODO + - `workflowModals.tsx` — edge-`ConditionModal`, skill-selector `SkillDrawer`, `NodeEditModal`, skill-detail `SkillModal` + the shared `inputStyle`/`SkillMcpEntry`; split verbatim out of `workflowNodes.tsx` in subtask-064 and re-exported from it. - `workflowNodes.tsx` — React Flow custom nodes + edit/skill/condition modals (extracted from WorkflowPage in req 15). - - `workflowNodeTypes.tsx` — TODO + - `workflowNodeTypes.tsx` — React Flow custom node renderers and the `phase`/`condition`/`parallel` `nodeTypes` map + deterministic `skillColor` hash; split verbatim out of `workflowNodes.tsx` in subtask-064 and re-exported from it. - `WorkflowPage.tsx` — workflow view. + - `__tests__/` — component tests for this directory. + - `WorkflowPage.revision.test.tsx` — revision-aware saving: every save sends the loaded `base_revision` and adopts the returned `revision`, the 2s debounce is unchanged, and a 409 pauses saving behind an explicit Reload instead of blindly overwriting. - `context/` — global React context (shared app state). - `README.md` — context index. - `AppContext.tsx` — `AppProvider`/`useAppContext`: shared app state, websocket tree nodes, current page, checkpoints. @@ -143,46 +213,62 @@ React/TypeScript source for the ARI `ari.viz` web dashboard — app entry, pages - `README.md` — hooks index. - `useApi.ts` — generic async data-fetch hook with loading/error/refetch. - `useDevMode.ts` — persisted developer-mode flag (localStorage `ari_dev_mode`, default OFF) with same-tab + cross-tab sync; gates raw/debug/dangerous UI surfaces. + - `useRunEvents.ts` — subscribes the shared SSE client for the caller's lifetime and maps each event to a react-query invalidation keyed by the EVENT's `run_id` (events are invalidations, never data — an event for run B can never touch run A's cache); exposes `connectionState`/`lastEventAt` for the `StaleDataBanner` and a bounded poll fallback once the stream is offline. + - `useV1.ts` — react-query hooks over the typed `/api/v1` client; query keys are `['v1', projectId?, runId?, resource]`, so run-scoped entries stay isolated per run and one run's server state can be dropped in a single `invalidateQueries`. - `useWebSocket.ts` — streams real-time tree updates with auto-reconnect. - `__tests__/` — hook unit tests. - `useDevMode.test.tsx` — default-OFF, persistence, and cross-instance sync for `useDevMode`. + - `useRunEvents.test.tsx` — event → invalidation glue against a fake `EventSource`: per-topic keys, two-run isolation, connection state and the offline poll tick. - `i18n/` — localization (en/ja/zh) helpers. - `en.ts` — English dictionary. + - `I18nProvider.tsx` — startup ready-gate for the lazily loaded locale dicts: holds the first render until the active locale (`ari_lang`, default `ja`) and the `en` fallback are in memory, so `t()` stays synchronous everywhere below and no raw key ever flashes. - `index.ts` — i18n entry / language selection. - `ja.ts` — Japanese dictionary. - `zh.ts` — Chinese dictionary. - - `__tests__/` — TODO - - `parity.test.tsx` — TODO + - `__tests__/` — locale-dictionary unit tests. + - `parity.test.tsx` — Tier-1 key-set parity over the REAL `en`/`ja`/`zh` dicts (imported directly, bypassing the lazy barrel): no key missing in any direction, no duplicate keys, `KNOWN_DRIFT` empty; values are deliberately not compared. - `services/` — typed API and websocket client modules. - `README.md` — services index. - `api.ts` — typed REST client (state, checkpoints, settings, GPU monitor, etc.). - `websocket.ts` — websocket helper stub (connections now handled by `useWebSocket`). - - `__tests__/` — TODO - - `api.test.tsx` — TODO - - `schema.test.tsx` — TODO - - `api/` — TODO - - `catalog.ts` — TODO - - `checkpoints.ts` — TODO - - `client.ts` — TODO - - `ear.ts` — TODO - - `experiment.ts` — TODO - - `files.ts` — TODO - - `memory.ts` — TODO - - `nodeReport.ts` — TODO - - `paperbench.ts` — TODO - - `publish.ts` — TODO - - `resources.ts` — TODO - - `settings.ts` — TODO - - `ssh.ts` — TODO - - `state.ts` — TODO - - `subExperiments.ts` — TODO - - `wizard.ts` — TODO - - `workflow.ts` — TODO + - `__tests__/` — service-layer contract tests: the frozen API wire contract and the FE schema round-trip mirror. + - `api.test.tsx` — pins the frozen wire contract — same-origin `API_BASE`, each wrapper's endpoint path, the throwing `get`/`post` vs swallowing `pbGet`/`pbPost` regimes, and the POST request-init shape. + - `schema.test.tsx` — FE mirror of `tests/test_api_schema_contract.py`: typed `AppState`/`Checkpoint`/`Settings`/`WorkflowData` fixtures round-trip through a mocked fetch, asserting the always-present keys survive (additive-subset doctrine, `types/index.ts` is the source of truth). + - `api/` — the domain-partitioned endpoint wrappers and DTOs split out of the old 863-line `api.ts` god-module, all riding one shared transport core (`client.ts`). + - `capabilities.ts` — `GET /api/capabilities` server feature flags (the `ARI_GUI_V2` shell kill-switch); callers must default `gui_v2` to ON when the fetch fails, so an API hiccup never bricks the dashboard into the fallback shell. + - `catalog.ts` — profiles / rubrics / few-shot catalog family — `fetchProfiles`, `fetchRubrics`, and the few-shot list/sync/upload/delete wrappers. + - `challenges.ts` — requests the server-issued confirmation challenge every dangerous operation (delete-checkpoint / stop-all / gpu-monitor stop) must carry: single-use, server-TTL-bounded, and bound to one action+target. + - `checkpoints.ts` — checkpoint list / summary / lifecycle family; `deleteCheckpoint` is two-step and must carry a `delete-checkpoint` challenge id or the server refuses with HTTP 428. + - `client.ts` — the shared same-origin transport (`API_BASE = ''`) behind one `request` primitive: throwing `get`/`post`, never-throwing `pbGet`/`pbPost`, envelope-preserving `v1Get`/`v1Send` (If-Match), plus the MN-8 bearer-token helpers. + - `ear.ts` — Experiment Artifact Repository family — browse a run's EAR, `curateEAR` bundling, the `publish.yaml` read/save editor, and `cloneVerifyBundle` sha256 verification. + - `experiment.ts` — experiment lifecycle family — `runStage`, `launchExperiment`, and `stopExperiment`, which needs a `stop-all` challenge id or the server refuses with HTTP 428. + - `files.ts` — Overleaf-like checkpoint file management — file list/filetree/content reads, save/delete, the bespoke octet-stream `uploadCheckpointFile` (`X-Filename`), and `compileCheckpointPaper`. + - `kca.ts` — typed client for knowledge, capability, and assurance dashboard endpoints. + - `memory.ts` — Letta memory family — per-checkpoint entries grouped `by_node`, the read/write access log, `/api/memory/health`, and `restartLetta`. + - `nodeReport.ts` — `fetchNodeReport` plus the v0.7.0 `NodeReport` DTO (files_changed, metrics, self_assessment, artifacts) served by `/api/nodes/{run}/{node}/report`. + - `paperbench.ts` — PaperBench family on the no-throw `pbGet`/`pbPost` regime (the backend answers 200 + `{error}`): paper registry list/import/delete, arXiv metadata, cost estimate, run launch, results, report export. + - `publish.ts` — publish family — registry settings, `previewPublish`, `runPublish` (dry-run/consent/visibility), `promotePublish`, and the stored `PublishRecord`. + - `resources.ts` — infra probes — scheduler detect, SLURM partitions, Ollama/GPU resources, container info/images/pull, and `gpuMonitorAction` whose `stop` requires a `gpu-monitor-stop` challenge. + - `settings.ts` — settings / env family — `/api/settings` read+write plus `fetchSecretsStatus`, which reports only WHETHER each allowlisted secret is configured, never its value. + - `ssh.ts` — SSH / HPC probe family — the single `testSSH` wrapper over `POST /api/ssh/test`. + - `state.ts` — state / tree / models family — the `/state` `AppState` poll, experiment-detail config, active checkpoint, resource metrics, and model list. + - `subExperiments.ts` — recursive-orchestration family — list/fetch/launch sub-experiments, with the `SubExperiment` DTO carrying lineage provenance (`inherit_idea_index`, parent-termination fields). + - `v1.ts` — typed read-only client for the versioned `/api/v1` API; every failure (typed `ErrorEnvelopeV1`, network error, malformed body) is normalized into a thrown `ApiErrorV1` `{code, message, details, request_id, retryable}`. + - `v1types.gen.ts` — DTO/path types generated from `ari/viz/v1/openapi.json` by `npm run gen:v1types`; committed to the tree and byte-compared by `src/__tests__/v1TypesDrift.test.ts` — never hand-edited. + - `wizard.ts` — wizard / chat / upload family — `chatGoal`, `generateConfig`, and the bespoke octet-stream `uploadFile` (`X-Filename` / `X-File-Type`) with its delete. + - `workflow.ts` — skills + `workflow.yaml` family; writes are revision-aware (`base_revision`), and `isWorkflowRevisionConflict` recognizes the HTTP 409 stale-revision refusal surfaced by the throwing `post`. +- `shared/` — cross-feature platform code owned by no single page. + - `realtime/` — shared realtime (SSE) client; feature code never touches `EventSource` directly. + - `README.md` — realtime index. + - `eventStream.ts` — `subscribe(runId, topics, callbacks)` wrapper over SSE + - `__tests__/` — realtime client unit tests. + - `eventStream.test.ts` — FakeEventSource-driven: URL/filter - `styles/` — global CSS. - `README.md` — styles index. - `components.css` — component styles. - `dashboard.css` — top-level dashboard styles. - - `layout.css` — page/layout structure. + - `layout.css` — page/layout structure, plus the base element defaults for the app frame (`body`, `#root`, `a`, `button`, `::file-selector-button`). The element-level `button`/`a` rules are deliberately low specificity: any component class or inline style overrides them, so they only reach controls nothing else styles. + - `motion.css` — `prefers-reduced-motion: reduce` overrides: collapses the `--t-*` motion tokens to zero and disables animations/transitions globally (imported after `tokens.css` so the `:root` override wins). - `responsive.css` — responsive/media-query overrides. - `tokens.css` — design tokens (colors, spacing). - `widgets.css` — widget-specific styles. diff --git a/ari-core/ari/viz/frontend/src/__tests__/appContextScope.test.ts b/ari-core/ari/viz/frontend/src/__tests__/appContextScope.test.ts new file mode 100644 index 00000000..3b302b78 --- /dev/null +++ b/ari-core/ari/viz/frontend/src/__tests__/appContextScope.test.ts @@ -0,0 +1,79 @@ +// @vitest-environment node +/// +// +// AppContext legacy-scoping guard (gui_refresh G2 tail; plan 03 §State +// ownership — AppContext shrinks from remote-data store to shell-level UI +// concern, with deletion decided last; RR-P0-8 disposition). +// +// The v2 workspaces source remote data run-explicitly from /api/v1 via +// react-query (useV1 hooks) and scope themselves with ?run= URL state. +// AppContext — the legacy /state poller + global-active-checkpoint store — +// must not gain new v2 consumers, or the G6 legacy-removal gate (plan 03 +// §Migration sequence step 7: delete duplicate router / legacy remote state) +// becomes un-executable. +// +// Structural source-scan (same spirit as indexHtmlNoExternalScripts.test.ts): +// every non-test source file under the v2 component dirs is loaded raw via +// import.meta.glob and grepped for a context/AppContext import specifier. +// __tests__ files are excluded — tests legitimately mount as +// the page harness. The single PINNED exception is IdeasV2Page.tsx: its +// research-goal card deliberately reads the AppContext /state goal behind a +// run-identity gate (state.checkpoint_id === ?run=) because no run-scoped v1 +// endpoint serves the goal yet — documented in that file's header. Shrinking +// the exception list is welcome; growing it is a G2-tail regression. + +import { describe, it, expect } from 'vitest'; +// Raw source of the context itself (not part of the v2 glob) for the +// legacy-scoped declaration check. +import APP_CONTEXT_SOURCE from '../context/AppContext.tsx?raw'; + +// Raw eager glob: v2 dirs only, tests excluded. Patterns must stay literal +// (Vite resolves import.meta.glob statically). +const v2Sources = import.meta.glob( + [ + '../components/Projects/**/*.{ts,tsx}', + '../components/Overview/**/*.{ts,tsx}', + '../components/TreeV2/**/*.{ts,tsx}', + '../components/IdeasV2/**/*.{ts,tsx}', + '../components/ResultsV2/**/*.{ts,tsx}', + '../components/Governance/**/*.{ts,tsx}', + '../components/ConfigBrowser/**/*.{ts,tsx}', + '../components/ConfigStudio/**/*.{ts,tsx}', + '!**/__tests__/**', + ], + { query: '?raw', import: 'default', eager: true }, +) as Record; + +// Import specifiers that bind a module to the legacy context. Matches both +// `from '.../context/AppContext'` and dynamic `import('.../context/AppContext')`. +const APP_CONTEXT_IMPORT = /['"][^'"]*context\/AppContext['"]/; + +// Pinned exception set — see header. Keys are the glob's relative paths. +const ALLOWED = new Set(['../components/IdeasV2/IdeasV2Page.tsx']); + +describe('v2 workspaces stay AppContext-free (plan 03 G2 tail / RR-P0-8)', () => { + it('scans a non-vacuous v2 source set', () => { + // Guards the glob itself: if the dirs move, this fails loudly instead of + // the import scan passing on zero files. + expect(Object.keys(v2Sources).length).toBeGreaterThanOrEqual(15); + }); + + it('no v2 source file imports context/AppContext beyond the pinned exception', () => { + const offenders = Object.entries(v2Sources) + .filter(([, source]) => APP_CONTEXT_IMPORT.test(source)) + .map(([path]) => path) + .sort(); + expect( + offenders, + 'v2 components must source remote data from /api/v1 (useV1 + ?run= URL ' + + 'state), never from the legacy AppContext /state poller. Remove the ' + + 'import, or (only for a documented legacy-parity gap like the ' + + 'IdeasV2 research-goal card) pin it in ALLOWED with a header comment.', + ).toEqual([...ALLOWED].sort()); + }); + + it('AppContext.tsx keeps its legacy-scoped declaration', () => { + // The header is the human-facing half of this guard; keep them together. + expect(APP_CONTEXT_SOURCE).toContain('LEGACY-SCOPED'); + }); +}); diff --git a/ari-core/ari/viz/frontend/src/__tests__/devModeAndDangerousOps.test.tsx b/ari-core/ari/viz/frontend/src/__tests__/devModeAndDangerousOps.test.tsx index a27b8f9c..ece598cc 100644 --- a/ari-core/ari/viz/frontend/src/__tests__/devModeAndDangerousOps.test.tsx +++ b/ari-core/ari/viz/frontend/src/__tests__/devModeAndDangerousOps.test.tsx @@ -8,10 +8,11 @@ const originalFetch = globalThis.fetch; /** * Tier-2 sibling-gated invariants (subtask 073 §7.4). The two developer-mode * gates below were `it.todo` until subtask 071 (add_dashboard_developer_mode) - * landed; they are now real assertions over the shipped gating. The remaining - * three stay `it.todo` because their siblings (071 dangerous-ops backend audit / - * 072 error-state kit / 073 ARIA) have NOT landed — enabling them would assert - * behavior that does not exist yet. + * landed; they are now real assertions over the shipped gating. The 072 + * error-state kit and the dangerous-ops audit (gui_refresh task 09 Wave 5a, + * MN-6) have also landed, so only the ARIA-tabs invariant stays `it.todo` + * (its sibling 068/069/070 has NOT landed — enabling it would assert + * behavior that does not exist yet). * * jest-dom matchers are intentionally avoided (they are not typed for * `tsc --noEmit` in this project — see SettingsContract.test.tsx); we use @@ -34,7 +35,27 @@ const fetchMock = vi.fn(async (input: RequestInfo | URL) => { let body: unknown = {}; if (url.includes('rubric')) body = []; else if (url.includes('image')) body = []; - else if (url.includes('env-keys')) body = { keys: {} }; + // RR-P0-2 / ADR-11 / MN-2: legacy env-keys is redacted server-side … + else if (url.includes('env-keys')) body = { keys: {}, source: {}, redacted: true }; + // … and the readiness endpoint reports configured/not-configured only. + else if (url.includes('secrets/status')) + body = { + schema_version: 1, + secrets: [ + { + name: 'OPENAI_API_KEY', + configured: true, + source_class: 'repo_env', + last_updated: '2026-07-23T00:00:00Z', + }, + { + name: 'ANTHROPIC_API_KEY', + configured: false, + source_class: null, + last_updated: null, + }, + ], + }; else if (url.includes('scheduler') || url.includes('detect')) body = { scheduler: 'local', partitions: [] }; else if (url.includes('container')) body = { runtime: 'none' }; @@ -53,6 +74,12 @@ vi.mock('../components/Wizard/stepResourcesSections', () => ({ import { DetailPanel } from '../components/Tree/DetailPanel'; import { StepResources, ORS_DEFAULTS } from '../components/Wizard/StepResources'; +import { + requestConfirmationChallenge, + deleteCheckpoint, + stopExperiment, + gpuMonitorAction, +} from '../services/api'; const NODE = { id: 'n1', label: 'draft' } as unknown as TreeNode; @@ -112,26 +139,52 @@ describe('developer-mode gating of raw/debug/secret surfaces (071)', () => { expect(screen.queryByRole('button', { name: /Raw/ })).not.toBeNull(); }); - // Converted from it.todo: 071 gates the /api/env-keys secret readback UI. - it('hides the env-key Auto-read secret readback and does not auto-pull secrets on mount when developer mode is OFF', async () => { + // Converted from it.todo: 071 gates the env-key Auto-read UI. Since + // RR-P0-2 / ADR-11 / MN-2 (Wave 3a) the gated surface is a READINESS + // check (/api/v1/secrets/status) — secret values are not readable at all. + it('hides the env-key Auto-read readiness check and does not probe secrets on mount when developer mode is OFF', async () => { render(); // 'API Key' label renders in the non-ollama branch → mount gate. await waitFor(() => expect(screen.queryByText('API Key')).not.toBeNull()); expect(screen.queryByRole('button', { name: /Auto-read/ })).toBeNull(); - // No secret readback fired on Wizard mount (069 §6 row 6): /api/env-keys - // was never fetched. - const hitEnvKeys = fetchMock.mock.calls.some((c) => - String(c[0]).includes('env-keys'), + // Nothing secret-related fired on Wizard mount (069 §6 row 6): neither + // the legacy /api/env-keys nor /api/v1/secrets/status was fetched. + const hitSecretSurface = fetchMock.mock.calls.some( + (c) => + String(c[0]).includes('env-keys') || + String(c[0]).includes('secrets/status'), ); - expect(hitEnvKeys).toBe(false); + expect(hitSecretSurface).toBe(false); }); - it('shows the env-key Auto-read button when developer mode is ON', async () => { + // RR-P0-2 / ADR-11 / MN-2: dev-mode Auto-read is now readiness-only — it + // calls /api/v1/secrets/status, never the legacy value endpoint, and never + // prefills the API-key field (values can no longer be read over HTTP). + it('shows the env-key Auto-read button when developer mode is ON and displays readiness without prefilling values', async () => { localStorage.setItem('ari_dev_mode', '1'); - render(); + const setApiKey = vi.fn(); + render(); await waitFor(() => expect(screen.queryByRole('button', { name: /Auto-read/ })).not.toBeNull(), ); + // Mount auto-check (dev mode) resolves against the readiness endpoint: + // llm='openai' → OPENAI_API_KEY, mocked configured via repo_env. + await waitFor(() => + expect( + screen.queryByText(/OPENAI_API_KEY configured \(repo_env\)/), + ).not.toBeNull(), + ); + expect( + fetchMock.mock.calls.some((c) => + String(c[0]).includes('secrets/status'), + ), + ).toBe(true); + // The legacy value endpoint is never consulted … + expect( + fetchMock.mock.calls.some((c) => String(c[0]).includes('env-keys')), + ).toBe(false); + // … and no value is ever written into the field. + expect(setApiKey).not.toHaveBeenCalled(); }); }); @@ -141,10 +194,78 @@ describe('developer-mode gating of raw/debug/secret surfaces (071)', () => { * sibling lands. */ describe('dashboard UX invariants pending sibling refactors (Tier-2)', () => { - // Un-skip when the dangerous-ops backend audit fixes the api.ts confirmed:true hardcode. - it.todo( - 'sends confirmed:true only after an explicit user confirmation payload [enable with dangerous-ops audit]', - ); + // Converted from it.todo: the dangerous-ops backend audit landed (gui_refresh + // task 09 Wave 5a, MN-6 / RR-P0-6 / RR-P0-9). Destructive calls are two-step: + // POST /api/v1/challenges issues a single-use server challenge bound to + // action+target (its echo is the UI impact preview), and the destructive + // endpoint only fires with that challenge_id. The api.ts confirmed:true + // hardcode is gone — 'confirmed' rides only when a caller explicitly + // confirmed (GpuMonitor start), and gpu-monitor stop uses a challenge. + it('sends destructive calls only via the server-issued challenge two-step (MN-6)', async () => { + const calls: Array<{ url: string; body: any }> = []; + const fn = vi.fn(async (input: RequestInfo | URL, init?: RequestInit) => { + const url = String(input); + const body = init?.body ? JSON.parse(String(init.body)) : undefined; + calls.push({ url, body }); + let resBody: unknown = { ok: true }; + if (url.includes('/api/v1/challenges')) { + resBody = { + schema_version: 1, + challenge_id: 'chg-abc123def456', + action: body.action, + target: body.target, + expires_at: '2026-01-01T00:00:00Z', + ttl_seconds: 60, + }; + } + return { + ok: true, + status: 200, + json: async () => resBody, + text: async () => JSON.stringify(resBody), + } as unknown as Response; + }); + globalThis.fetch = fn as unknown as typeof fetch; + try { + // delete-checkpoint: challenge echoes the exact target (impact preview) … + const ch = await requestConfirmationChallenge( + 'delete-checkpoint', + '/ckpts/run1', + ); + expect(ch.challenge_id).toBe('chg-abc123def456'); + expect(ch.target).toBe('/ckpts/run1'); + // … and the destructive call carries the challenge back. + await deleteCheckpoint('run1', '/ckpts/run1', ch.challenge_id); + + // stop-all: same two-step against target '*'. + const st = await requestConfirmationChallenge('stop-all', '*'); + await stopExperiment(st.challenge_id); + + // gpu-monitor: no hardcoded confirmed:true; stop rides a challenge. + await gpuMonitorAction('start'); + await gpuMonitorAction('start', { confirmed: true }); + const gm = await requestConfirmationChallenge('gpu-monitor-stop', '*'); + await gpuMonitorAction('stop', { challengeId: gm.challenge_id }); + + expect(calls.map((c) => c.url)).toEqual([ + '/api/v1/challenges', + '/api/delete-checkpoint', + '/api/v1/challenges', + '/api/stop', + '/api/gpu-monitor', + '/api/gpu-monitor', + '/api/v1/challenges', + '/api/gpu-monitor', + ]); + expect(calls[1].body.challenge_id).toBe('chg-abc123def456'); + expect(calls[3].body).toEqual({ challenge_id: 'chg-abc123def456' }); + expect(calls[4].body.confirmed).toBeUndefined(); + expect(calls[5].body.confirmed).toBe(true); + expect(calls[7].body.challenge_id).toBe('chg-abc123def456'); + } finally { + globalThis.fetch = originalFetch; + } + }); // Un-skip when 068/069/070 add ARIA tab semantics to Settings/DetailPanel tabs. it.todo( diff --git a/ari-core/ari/viz/frontend/src/__tests__/indexHtmlNoExternalScripts.test.ts b/ari-core/ari/viz/frontend/src/__tests__/indexHtmlNoExternalScripts.test.ts new file mode 100644 index 00000000..3b133141 --- /dev/null +++ b/ari-core/ari/viz/frontend/src/__tests__/indexHtmlNoExternalScripts.test.ts @@ -0,0 +1,47 @@ +// @vitest-environment node +/// +// +// Supply-chain guard for the SPA entry document (gui_refresh task 09 +// Wave 5a — RR-P0-10 / MN-7). +// +// The GUI must be fully self-contained: every script and stylesheet comes +// out of the Vite bundle (npm dependencies, lockfile-pinned), never a CDN. +// This keeps the backend's Content-Security-Policy `script-src 'self'` +// honest — an external \n' + "\n", encoding="utf-8") + for name, body in chunks.items(): + (dist / "assets" / name).write_bytes(body) + return dist + + +def _incompressible(n: int) -> bytes: + """Seeded pseudo-random payload: incompressible (gzip ~= n) yet reproducible.""" + import random + rng = random.Random(42) # seeded -> reproducible test data + return bytes(rng.getrandbits(8) for _ in range(n)) + + +def _write_cfg(tmp_path: Path, dist: Path, budgets: dict) -> Path: + import yaml + cfg = tmp_path / "budget.yaml" + cfg.write_text(yaml.safe_dump({"dist": str(dist), "budgets": budgets}), + encoding="utf-8") + return cfg + + +def _run(*args: str) -> subprocess.CompletedProcess: + return subprocess.run( + [sys.executable, str(CHECKER), "--json", *args], + capture_output=True, text=True, cwd=str(REPO_ROOT)) + + +# ── budget evaluation (fake dist) ──────────────────────────────────────────── + + +def test_fake_dist_within_budget_is_clean(tmp_path: Path) -> None: + dist = _make_dist(tmp_path, { + "index-AAAA1111.js": b"console.log('entry');" * 10, + "HomePage-BBBB2222.js": b"export default 1;" * 10, + "zoom-CCCC3333.js": b"export const z = 1;" * 10, + }) + cfg = _write_cfg(tmp_path, dist, {"entry_kib": 100, "route_kib": 150, + "shared_kib": 150, "total_kib": 600}) + proc = _run("--config", str(cfg), "--fail-on-regression") + assert proc.returncode == 0, proc.stdout + proc.stderr + report = json.loads(proc.stdout) + assert report["checker"] == "check_bundle_budget" + assert report["summary"]["new"] == 0 + assert report["summary"]["chunk_count"] == 3 + classes = {c["name"]: c["class"] for c in report["summary"]["chunks"]} + assert classes["index-AAAA1111.js"] == "entry" + assert classes["HomePage-BBBB2222.js"] == "route" + assert classes["zoom-CCCC3333.js"] == "shared" + + +def test_fake_dist_oversized_route_chunk_fails_regression(tmp_path: Path) -> None: + dist = _make_dist(tmp_path, { + "index-AAAA1111.js": b"console.log('entry');", + # ~3 KiB incompressible -> over a 1 KiB route budget. + "HomePage-BBBB2222.js": _incompressible(3 * 1024), + }) + cfg = _write_cfg(tmp_path, dist, {"entry_kib": 100, "route_kib": 1, + "shared_kib": 150, "total_kib": 600}) + proc = _run("--config", str(cfg), "--fail-on-regression") + assert proc.returncode == 1, proc.stdout + proc.stderr + report = json.loads(proc.stdout) + ids = {f["id"]: f for f in report["findings"]} + # Hash-independent finding id (survives rebuilds). + assert "bundle:route:HomePage" in ids + assert ids["bundle:route:HomePage"]["kind"] == "route-over-budget" + assert not ids["bundle:route:HomePage"]["allowlisted"] + + +def test_fake_dist_route_override_tighter_than_generic(tmp_path: Path) -> None: + dist = _make_dist(tmp_path, { + "index-AAAA1111.js": b"console.log('entry');", + # ~3 KiB: within the generic 150 KiB route budget but over a 2 KiB + # SettingsPage override. + "SettingsPage-BBBB2222.js": _incompressible(3 * 1024), + "HomePage-CCCC3333.js": _incompressible(3 * 1024), + }) + cfg = _write_cfg(tmp_path, dist, { + "entry_kib": 100, "route_kib": 150, "shared_kib": 150, "total_kib": 600, + "route_overrides": {"SettingsPage": 2}}) + proc = _run("--config", str(cfg), "--fail-on-regression") + assert proc.returncode == 1 + report = json.loads(proc.stdout) + ids = {f["id"] for f in report["findings"]} + assert "bundle:route:SettingsPage" in ids + assert "bundle:route:HomePage" not in ids # generic budget still passes + + +def test_fake_dist_total_budget_aggregate(tmp_path: Path) -> None: + dist = _make_dist(tmp_path, { + "index-AAAA1111.js": _incompressible(3 * 1024), + "HomePage-BBBB2222.js": _incompressible(3 * 1024), + }) + # Each chunk within its per-chunk budget (100/150) but the ~6 KiB total + # exceeds a 4 KiB aggregate ceiling. + cfg = _write_cfg(tmp_path, dist, {"entry_kib": 100, "route_kib": 150, + "shared_kib": 150, "total_kib": 4}) + proc = _run("--config", str(cfg), "--fail-on-regression") + assert proc.returncode == 1 + report = json.loads(proc.stdout) + ids = {f["id"]: f for f in report["findings"]} + assert set(ids) == {"bundle:total:js"} + assert ids["bundle:total:js"]["kind"] == "total-over-budget" + + +def test_gzip_measurement_is_deterministic(tmp_path: Path) -> None: + p = tmp_path / "a.js" + p.write_bytes(b"const x = 1;\n" * 100) + first = mod.gzip_size(p) + # mtime=0 in gzip.compress -> byte-identical header across calls/clock. + assert mod.gzip_size(p) == first + assert first > 0 + + +def test_missing_dist_exits_2(tmp_path: Path) -> None: + proc = _run("--dist", str(tmp_path / "nope")) + assert proc.returncode == 2 + assert "assets dir not found" in proc.stderr + + +# ── real dist smoke ────────────────────────────────────────────────────────── + + +needs_dist = pytest.mark.skipif( + not (REAL_DIST / "assets").is_dir(), + reason=("frontend dist not built at ari-core/ari/viz/static/dist — run " + "`npm run build` in ari-core/ari/viz/frontend to enable the " + "bundle-budget smoke")) + + +@needs_dist +def test_repo_smoke_real_dist_within_all_budgets() -> None: + proc = _run("--fail-on-regression") + assert proc.returncode == 0, proc.stdout + proc.stderr + report = json.loads(proc.stdout) + s = report["summary"] + assert s["new"] == 0 and s["total"] == 0 + assert s["chunk_count"] > 0 + # Plan-09 hard budgets hold on the committed build. + entry = [c for c in s["chunks"] if c["class"] == "entry"] + assert entry, "index.html must reference a module entry chunk" + assert all(c["gzip_bytes"] <= 100 * 1024 for c in entry) + assert s["total_js_gzip_bytes"] <= 600 * 1024 + # Settings/Wizard tightened rows hold too. + for c in s["chunks"]: + if c["stem"] in ("SettingsPage", "WizardPage"): + assert c["gzip_bytes"] <= 50 * 1024 + + +if __name__ == "__main__": + raise SystemExit(pytest.main([__file__, "-q"])) diff --git a/scripts/tests/test_check_doc_links.py b/scripts/tests/test_check_doc_links.py new file mode 100644 index 00000000..720f1375 --- /dev/null +++ b/scripts/tests/test_check_doc_links.py @@ -0,0 +1,41 @@ +"""Regression tests for the VitePress-aware documentation link checker.""" + +from pathlib import Path + +from scripts.docs import check_doc_links as links + + +def test_clean_target_skips_external_site_root(): + assert links._clean_target("/ARI/") is None + + +def test_markdown_links_accept_clean_urls_and_public_assets( + tmp_path: Path, monkeypatch, +): + docs = tmp_path / "docs" + docs.mkdir() + (docs / "guide.md").write_text("# Guide\n", encoding="utf-8") + public_report = docs / "public" / "report" + public_report.mkdir(parents=True) + (public_report / "en.pdf").write_bytes(b"%PDF") + (docs / "index.md").write_text( + "[guide](guide) [report](report/en.pdf) [site](/ARI/)\n", + encoding="utf-8", + ) + monkeypatch.setattr(links, "DOCS", docs) + + findings: list[dict] = [] + links.check_markdown(findings) + + assert findings == [] + + +def test_markdown_scan_ignores_dependency_docs(tmp_path: Path, monkeypatch): + docs = tmp_path / "docs" + dependency = docs / "node_modules" / "package" + dependency.mkdir(parents=True) + (docs / "index.md").write_text("# Docs\n", encoding="utf-8") + (dependency / "README.md").write_text("[missing](nope)\n", encoding="utf-8") + monkeypatch.setattr(links, "DOCS", docs) + + assert links._markdown_files() == [docs / "index.md"] diff --git a/scripts/tests/test_check_import_boundaries.py b/scripts/tests/test_check_import_boundaries.py index 7fba350e..577cc1d4 100644 --- a/scripts/tests/test_check_import_boundaries.py +++ b/scripts/tests/test_check_import_boundaries.py @@ -4,13 +4,13 @@ Covers (subtask 026 §8 item 8): (a) B1 fires on a skill's private-core edge and not on its ari.public edge; (b) B2 allows ari_skill_memory from core and flags any other ari_skill_*; - (c) a repo-level smoke test asserts the checker reports EXACTLY the 7 seed - edges (9 line occurrences) with an empty allowlist, and ZERO net-new - findings with the seeded allowlist. + (c) a repo-level smoke test asserts the migrated repository has no private + skill-to-core edge even with an empty allowlist. The checker is exercised as a subprocess (matching the §12 manual acceptance runs), so REPO_ROOT resolves from the script's own location. """ + from __future__ import annotations import json @@ -25,35 +25,17 @@ REPO_ROOT = SCRIPTS_DIR.parent CHECKER = SCRIPTS_DIR / "check_import_boundaries.py" -# The frozen seed set (docs/refactoring/003 §3/§16), as :: ids. -SEED_IDS = { - "ari-skill-idea/src/server.py::ari.lineage", - "ari-skill-paper-re/src/server.py::ari.clone", - "ari-skill-transform/src/server.py::ari.orchestrator", - "ari-skill-transform/src/server.py::ari.publish", - "ari-skill-coding/src/server.py::ari.container", - "ari-skill-coding/src/server.py::ari.agent.run_env", - "ari-skill-hpc/src/slurm.py::ari.agent.run_env", -} -# The 9 line-level occurrences those 7 edges expand to. -SEED_OCCURRENCES = { - ("ari-skill-idea/src/server.py", 614), - ("ari-skill-paper-re/src/server.py", 146), - ("ari-skill-transform/src/server.py", 681), - ("ari-skill-transform/src/server.py", 2083), - ("ari-skill-transform/src/server.py", 2433), - ("ari-skill-transform/src/server.py", 2451), - ("ari-skill-coding/src/server.py", 569), - ("ari-skill-coding/src/server.py", 583), - ("ari-skill-hpc/src/slurm.py", 211), -} +SEED_IDS: set[str] = set() +SEED_OCCURRENCES: set[tuple[str, int]] = set() def run_checker(*args: str) -> tuple[int, dict]: """Run the checker with --json and return (exit_code, parsed_report).""" proc = subprocess.run( [sys.executable, str(CHECKER), "--json", *args], - capture_output=True, text=True, cwd=str(REPO_ROOT), + capture_output=True, + text=True, + cwd=str(REPO_ROOT), ) assert proc.returncode in (0, 1), proc.stderr return proc.returncode, json.loads(proc.stdout) @@ -70,18 +52,25 @@ def _write(base: Path, rel: str, text: str) -> None: def test_b1_flags_private_core_but_not_public(tmp_path: Path) -> None: skill = "ari-skill-fixture/src/server.py" - _write(tmp_path, skill, ( - "def _bootstrap():\n" - " from ari.public import cost_tracker # allowed root\n" - " from ari.protocols import Evaluator # allowed root\n" - " from ari import cost_tracker as ct # bare top-level: not flagged\n" - " from ari.lineage import record # B1 violation\n" - " import ari.publish # B1 violation\n" - " return cost_tracker, Evaluator, ct, record\n" - )) + _write( + tmp_path, + skill, + ( + "def _bootstrap():\n" + " from ari.public import cost_tracker # allowed root\n" + " from ari.protocols import Evaluator # allowed root\n" + " from ari import cost_tracker as ct # bare top-level: not flagged\n" + " from ari.lineage import record # B1 violation\n" + " import ari.publish # B1 violation\n" + " return cost_tracker, Evaluator, ct, record\n" + ), + ) code, report = run_checker("--target", str(tmp_path), "--allow", os.devnull) - b1 = {(f["file"], f["imported_module"]) for f in report["findings"] - if f["rule"] == "B1"} + b1 = { + (f["file"], f["imported_module"]) + for f in report["findings"] + if f["rule"] == "B1" + } assert (skill, "ari.lineage") in b1 assert (skill, "ari.publish") in b1 # The ari.public / ari.protocols / bare-ari imports must NOT be flagged. @@ -92,20 +81,47 @@ def test_b1_flags_private_core_but_not_public(tmp_path: Path) -> None: assert code == 0 # default posture is warning-mode +def test_b1_scans_manifested_package_outside_src(tmp_path: Path) -> None: + _write( + tmp_path, + "ari-skill-fixture/skill.yaml", + ( + "entrypoint:\n" + " command_kind: python\n" + " module: ari_skill_fixture/server.py\n" + ), + ) + runtime = "ari-skill-fixture/ari_skill_fixture/contracts.py" + _write(tmp_path, runtime, "from ari.internal import hidden\n") + + _, report = run_checker("--target", str(tmp_path), "--allow", os.devnull) + + assert {(f["file"], f["imported_module"]) for f in report["findings"]} == { + (runtime, "ari.internal") + } + + # -- (b) B2 fixture --------------------------------------------------------- def test_b2_allows_memory_flags_other_skill(tmp_path: Path) -> None: core = "ari-core/ari/thing.py" - _write(tmp_path, core, ( - "def _load():\n" - " from ari_skill_memory.backends import get_backend # sanctioned\n" - " import ari_skill_paper # B2 violation\n" - " return get_backend, ari_skill_paper\n" - )) + _write( + tmp_path, + core, + ( + "def _load():\n" + " from ari_skill_memory.backends import get_backend # sanctioned\n" + " import ari_skill_paper # B2 violation\n" + " return get_backend, ari_skill_paper\n" + ), + ) code, report = run_checker("--target", str(tmp_path), "--allow", os.devnull) - b2 = {(f["file"], f["imported_module"]) for f in report["findings"] - if f["rule"] == "B2"} + b2 = { + (f["file"], f["imported_module"]) + for f in report["findings"] + if f["rule"] == "B2" + } assert (core, "ari_skill_paper") in b2 assert (core, "ari_skill_memory.backends") not in b2 assert report["summary"]["b2"] == 1 @@ -113,12 +129,20 @@ def test_b2_allows_memory_flags_other_skill(tmp_path: Path) -> None: def test_b2_regression_gate_fails_on_new_edge(tmp_path: Path) -> None: - _write(tmp_path, "ari-core/ari/thing.py", - "import ari_skill_paper\n") + _write(tmp_path, "ari-core/ari/thing.py", "import ari_skill_paper\n") proc = subprocess.run( - [sys.executable, str(CHECKER), "--target", str(tmp_path), - "--allow", os.devnull, "--fail-on-regression"], - capture_output=True, text=True, cwd=str(REPO_ROOT), + [ + sys.executable, + str(CHECKER), + "--target", + str(tmp_path), + "--allow", + os.devnull, + "--fail-on-regression", + ], + capture_output=True, + text=True, + cwd=str(REPO_ROOT), ) assert proc.returncode == 1, proc.stdout + proc.stderr @@ -133,14 +157,16 @@ def test_repo_smoke_empty_allowlist_reports_exactly_seed(tmp_path: Path) -> None assert ids == SEED_IDS, sorted(ids ^ SEED_IDS) assert occ == SEED_OCCURRENCES, sorted(occ ^ SEED_OCCURRENCES) assert report["summary"]["b2"] == 0 # ari_skill_memory is sanctioned - assert report["summary"]["new"] == 9 + assert report["summary"]["new"] == 0 assert code == 0 def test_repo_smoke_seeded_allowlist_has_zero_new() -> None: proc = subprocess.run( [sys.executable, str(CHECKER), "--fail-on-regression"], - capture_output=True, text=True, cwd=str(REPO_ROOT), + capture_output=True, + text=True, + cwd=str(REPO_ROOT), ) assert proc.returncode == 0, proc.stdout + proc.stderr diff --git a/scripts/tests/test_check_prompts.py b/scripts/tests/test_check_prompts.py index 1e7d0953..9598ebca 100644 --- a/scripts/tests/test_check_prompts.py +++ b/scripts/tests/test_check_prompts.py @@ -6,10 +6,10 @@ an allowlisted one is suppressed (`known`); (b) ``ari-core/ari/agent/loop.py`` yields ZERO candidates (negative control -- its system prompt is externalized to ``agent/system.md``); - (c) a repo-level smoke asserts the checker reproduces the Subtask 036 census - high-value targets (evaluator/paper/plot/vlm/transform/web), every finding - id is unique (no name-collision), and the seeded allowlist yields zero - net-new debt under ``--fail-on-regression``; + (c) a repo-level smoke asserts the checker reproduces the one remaining + reviewed inline prompt, every finding id is unique (no name-collision), + and the seeded allowlist yields zero net-new debt under + ``--fail-on-regression``; (d) ``--with-snapshots`` folds Gate 10's pass/fail into the report and a missing Gate 10 script is an environment error (exit 2). @@ -30,20 +30,16 @@ REPO_ROOT = SCRIPTS_DIR.parent CHECKER = SCRIPTS_DIR / "check_prompts.py" -# High-value 036 targets the inventory slice must reproduce (file, line). -# NOTE: the evaluator ``_METRIC_EXTRACT_SYS`` / ``_SEMANTIC_SYSTEM_PROMPT`` rows -# were EXTRACTED to ``ari-skill-evaluator/src/prompts/*.md`` by subtask 040, and -# the three paper rows (``academic_reviewer`` :542, ``fill_in_writer`` :1487, -# ``global_coherence`` :2544) were EXTRACTED to ``ari-skill-paper/src/prompts/*.md`` -# by subtask 041 — all now loaded via a skill-local loader, no longer inline, so -# they are intentionally absent here (the census slice shrinks as 039/040/041 -# externalize prompts). The remaining rows are inline prompts owned by sibling -# subtasks (plot/vlm/transform/web). -CENSUS_TARGETS = { - ("ari-skill-plot/src/server.py", 560), # viz_expert - ("ari-skill-vlm/src/server.py", 97), # figure_reviewer - ("ari-skill-transform/src/server.py", 834), # node_report_analyst - ("ari-skill-web/src/server.py", 465), # query_librarian +# The historical evaluator/paper/plot/vlm/transform/web census has been +# externalized. This reviewed duplicate is the sole remaining inline baseline. +INLINE_BASELINE_TARGETS = { + ("ari-skill-paper/src/review_engine.py", "system"), +} +EXTERNALIZED_TARGETS = { + ("ari-skill-plot/src/server.py", "system_prompt"), + ("ari-skill-vlm/src/server.py", None), + ("ari-skill-transform/src/server.py", "analysis_prompt"), + ("ari-skill-web/src/server.py", "_QUERY_SYSTEM"), } _SYNTH_PROMPT = ( @@ -135,11 +131,11 @@ def test_agent_loop_yields_no_candidate() -> None: # -- (c) repo smoke --------------------------------------------------------- -def test_repo_smoke_reproduces_census_and_unique_ids() -> None: +def test_repo_smoke_matches_remaining_baseline_and_unique_ids() -> None: code, report = run_checker() # default allowlist, default scope - found = {(f["file"], f["line"]) for f in report["findings"]} - missing = CENSUS_TARGETS - found - assert not missing, f"census targets not detected: {sorted(missing)}" + found = {(f["file"], f["name"]) for f in report["findings"]} + assert found == INLINE_BASELINE_TARGETS + assert found.isdisjoint(EXTERNALIZED_TARGETS) ids = [f["id"] for f in report["findings"]] assert len(ids) == len(set(ids)), "duplicate finding ids" # ari-core/ari contributes nothing (prompts externalized). @@ -148,6 +144,41 @@ def test_repo_smoke_reproduces_census_and_unique_ids() -> None: assert code == 0 +def test_baseline_update_preserves_review_when_anonymous_prompt_line_moves(): + import importlib.util + + spec = importlib.util.spec_from_file_location("cp_line_drift", CHECKER) + mod = importlib.util.module_from_spec(spec) + sys.modules[spec.name] = mod + spec.loader.exec_module(mod) + candidate = mod.Candidate( + file="ari-skill-fixture/src/server.py", + line=90, + name="system_prompt", + lines=8, + chars=400, + markers=["role", "json"], + key="ari-skill-fixture/src/server.py#L90", + ) + previous = { + "ari-skill-fixture/src/server.py#L20": { + "file": candidate.file, + "line": 20, + "name": candidate.name, + "lines": candidate.lines, + "chars": candidate.chars, + "markers": candidate.markers, + "verdict": "EXTRACT_TEMPLATE", + "prompt_id": "skill.fixture.reviewer", + } + } + + assert mod._prior_verdict_for(candidate, previous) == ( + "EXTRACT_TEMPLATE", + "skill.fixture.reviewer", + ) + + def test_repo_smoke_seeded_allowlist_has_zero_new() -> None: proc = subprocess.run( [sys.executable, str(CHECKER), "--fail-on-regression"], diff --git a/scripts/tests/test_check_skill_manifests.py b/scripts/tests/test_check_skill_manifests.py new file mode 100644 index 00000000..20e7e267 --- /dev/null +++ b/scripts/tests/test_check_skill_manifests.py @@ -0,0 +1,83 @@ +"""Focused tests for canonical Skill manifest environment-source analysis.""" + +from __future__ import annotations + +from pathlib import Path + +from scripts.check_skill_manifests import ( + _filter_reviewed_dynamic_environment_forwarders, + _scan_environment_reads, +) + + +def test_environment_scan_resolves_aliases_loops_helpers_and_membership( + tmp_path: Path, +) -> None: + source = tmp_path / "src" + source.mkdir() + (source / "server.py").write_text( + """ +import os as _os +from os import environ as DIRECT_ENV +from os import getenv as direct_getenv + +ENV = _os.environ +NAMES = ("LOOP_ONE", "LOOP_TWO") +for name in NAMES: + ENV.get(name) + +direct_getenv("DIRECT_GETENV") +DIRECT_ENV["SUBSCRIPT_READ"] +DIRECT_ENV["SUBSCRIPT_WRITE"] = "value" +"MEMBERSHIP" in DIRECT_ENV + +def read_env(name): + return _os.getenv(name) + +read_env("HELPER_POSITIONAL") +read_env(name="HELPER_KEYWORD") +read_env(name=dynamic_name) +ENV.get("PREFIX_" + suffix) +""", + encoding="utf-8", + ) + + reads, unresolved = _scan_environment_reads(source) + + assert reads == { + "DIRECT_GETENV", + "HELPER_KEYWORD", + "HELPER_POSITIONAL", + "LOOP_ONE", + "LOOP_TWO", + "MEMBERSHIP", + "SUBSCRIPT_READ", + "SUBSCRIPT_WRITE", + } + assert len(unresolved) == 2 + assert any("dynamic_name" in item for item in unresolved) + assert any("suffix" in item for item in unresolved) + + +def test_environment_scan_fails_closed_on_unparseable_source(tmp_path: Path) -> None: + source = tmp_path / "src" + source.mkdir() + (source / "broken.py").write_text("def broken(:\n", encoding="utf-8") + + reads, unresolved = _scan_environment_reads(source) + + assert reads == set() + assert len(unresolved) == 1 + assert unresolved[0].endswith(":syntax-error") + + +def test_reviewed_dynamic_environment_forwarder_is_byte_and_location_pinned() -> None: + repo_root = Path(__file__).resolve().parents[2] + proxy = ( + repo_root / "ari-skill-tool-registry/src/stdio_process_proxy.py" + ).resolve() + + assert _filter_reviewed_dynamic_environment_forwarders( + repo_root, + [f"{proxy}:102:name", f"{proxy}:103:name"], + ) == [f"{proxy}:103:name"] diff --git a/scripts/tests/test_check_translation_freshness.py b/scripts/tests/test_check_translation_freshness.py new file mode 100644 index 00000000..dbd7b3e1 --- /dev/null +++ b/scripts/tests/test_check_translation_freshness.py @@ -0,0 +1,27 @@ +"""Regression tests for translation-freshness scan boundaries.""" + +from pathlib import Path + +from scripts.docs import check_translation_freshness as freshness + + +def test_readmes_and_generated_dependency_trees_are_exempt(): + assert freshness.is_exempt("docs/guides/README.md") + assert freshness.is_exempt("docs/node_modules/pkg/README.md") + assert freshness.is_exempt("docs/.vitepress/cache/generated.md") + + +def test_english_docs_scans_only_authored_content(tmp_path: Path, monkeypatch): + docs = tmp_path / "docs" + dependency = docs / "node_modules" / "pkg" + translation = docs / "ja" + dependency.mkdir(parents=True) + translation.mkdir(parents=True) + (docs / "index.md").write_text("---\nlast_verified: 2026-08-02\n---\n") + (docs / "README.md").write_text("# navigation\n") + (dependency / "README.md").write_text("# dependency\n") + (translation / "index.md").write_text("---\nlast_verified: 2026-08-02\n---\n") + monkeypatch.setattr(freshness, "REPO_ROOT", tmp_path) + monkeypatch.setattr(freshness, "DOCS", docs) + + assert freshness.english_docs() == [docs / "index.md"] diff --git a/scripts/tests/test_check_viz_api_schema.py b/scripts/tests/test_check_viz_api_schema.py index c4e53686..365967fb 100644 --- a/scripts/tests/test_check_viz_api_schema.py +++ b/scripts/tests/test_check_viz_api_schema.py @@ -14,8 +14,9 @@ server-only route -> known; * repo smoke (§13.4): against the real tree the checker is clean-or-warning — with the seeded allowlist ZERO net-new findings (exit 0 under - ``--fail-on-regression``); with an empty allowlist exactly one client-only - finding, the known F6a POST /report drift. + ``--fail-on-regression``); with an empty allowlist zero client-only + findings (the historical F6a POST /report drift was resolved in + gui_refresh Wave 4a by the routes.py do_POST /report branch). Unit tests import the checker module by file path (it has no package); the repo smoke runs it as a subprocess (matching the §12 manual acceptance runs and the @@ -236,12 +237,13 @@ def test_repo_smoke_fail_on_regression_passes_with_seed() -> None: assert proc.returncode == 0, proc.stdout + proc.stderr -def test_repo_smoke_empty_allowlist_flags_only_f6a_client_only() -> None: +def test_repo_smoke_empty_allowlist_has_no_client_only_findings() -> None: + """F6a (FE POST /report without a do_POST branch) was the only + client-only drift; gui_refresh Wave 4a added the POST branch, so even + an empty allowlist yields zero broken calls.""" code, report = _run("--allow", "/dev/null") client_only = [f for f in report["findings"] if f["kind"] == "client-only"] - assert len(client_only) == 1 - assert client_only[0]["id"] == "POST /api/paperbench/run/{id}/report" - assert client_only[0]["severity"] == "error" + assert client_only == [] assert code == 0 # default posture is warning-mode-first diff --git a/scripts/tests/test_generate_quality_report.py b/scripts/tests/test_generate_quality_report.py index 1dd3a135..e31c1f72 100644 --- a/scripts/tests/test_generate_quality_report.py +++ b/scripts/tests/test_generate_quality_report.py @@ -12,8 +12,9 @@ exits 1 only on net-new findings; --warning-only forces exit 0; * --run-checkers subprocess mode: a fixture checker -> ok, a missing script -> unavailable, a crashing script -> error; - * per-area LOC is computed live from the current tree (viz 8532 after the - 063/064/065 viz decomposition; public 148) and findings attribute to their area. + * per-area LOC is computed live from the current tree (tripwire values are + pinned in test_compute_areas_matches_001_baseline with their update + history) and findings attribute to their area. Unit tests import the checker module by file path (it has no package), matching the sibling test_check_viz_api_schema.py convention. @@ -305,8 +306,139 @@ def test_run_checkers_mode_ok_missing_and_crash(tmp_path): def test_compute_areas_matches_001_baseline(): rows = mod.compute_areas(REPO_ROOT, None, []) by = {r["area"]: r for r in rows} - assert by["ari-core/ari/viz"]["loc"] == 8532 - assert by["ari-core/ari/public"]["loc"] == 148 + # 8532 -> 8565: the PaperBench worker gained the rubric_audit stage + # (api_paperbench_worker.py). This is a drift TRIPWIRE, not a budget — it + # exists so an unnoticed bulk change to viz shows up in review, so update + # it deliberately with the reason, never by pasting the new number. + # 8565 -> 14336: gui_refresh program Waves 1-4b (docs/plans/gui_refresh/, + # exit records in baseline/g0_review_record.md) added the /api/v1 platform + # under ari/viz/v1/ — router/errors/DTOs, RQGM read models (rqgm.py), + # config schema/CRUD/store/secrets/events, deterministic OpenAPI — plus + # api_capabilities.py. Each wave's growth was gate-reviewed and recorded. + # 14336 -> 14554: gui_refresh Wave 4c — durable PaperBench job records + # (api_paperbench.py: atomic {registry}/jobs/{id}.json persistence + + # restart disk-fallback with the additive 'interrupted' status) and the + # /api/v1/runs/{run_id}/idea read model (v1 dto/queries/router/openapi). + # 14554 -> 15080: gui_refresh task 07 Wave 4d — the results/EAR read + # models /api/v1/runs/{run_id}/results and /ear (new ari/viz/v1/ + # results.py reader + dto/router/openapi additions; plan 07 §Evidence, + # Results, and PaperBench). Bounded-scalar read models only — the ORS + # verdict reuses ear.py's synthesis read-only, no legacy file changed. + # 15080 -> 15176: gui_refresh task 07 Wave 4d — Workflow Studio weak + # revision (plan 07 §Workflow Studio, MN-3): api_workflow.py gained + # workflow_revision/_served_workflow_path/_workflow_revision_guard + # (sha256[:12] optimistic concurrency, frozen 409 on stale + # base_revision) and api_settings.py wires the GET revision + the + # POST /api/workflow guard. Additive opt-in — no endpoint added. + # 15176 -> 15399: gui_refresh task 06 Wave 4d — Configuration Studio + # backend slice (ADR-05 delivery): the canonical write-only secret + # assignment PUT /api/v1/secrets/{secret_id} (v1/secrets.py put_secret + # + routes.py do_PUT + router/dto/openapi wiring) and the server-side + # model catalog GET /api/v1/config/catalogs/models (new v1/catalogs.py + # re-serving checkpoint_api._api_models single-source with per-provider + # env keys). No legacy endpoint changed. + # 15399 -> 15595: gui_refresh task 09 Wave 5a — RR-P0-3/MN-4 bind + + # CORS hardening: server.py gained resolve_bind_hosts/_make_http_server + # (loopback-default bind via _bind_http_servers, ARI_GUI_BIND override) + # and routes.py gained + # _origin_allowed/_cors_wildcard_enabled/_cors_origin/_send_cors_headers + # (same-origin CORS echo replacing the unconditional ACAO:*, plus the + # ADR-07 kill-switch docstrings). No endpoint added or removed. + # 15595 -> 15731: gui_refresh task 09 Wave 5a — RR-P0-5/RR-P0-7/MN-5 + # path + proxy hardening: routes.py gained the pure _codefile_resolve + # (canonical /codefile boundary: active checkpoint + checkpoint search + # bases, replacing the loose "*/checkpoints/*" containment test) and + # api_ollama.py gained OLLAMA_PROXY_ALLOWED_PATHS/_explicit_ollama_host/ + # _ollama_proxy_refusal (five-path allowlist + 403 gate, policy + # docstring). No endpoint added or removed. + # 15731 -> 16043: gui_refresh task 09 Wave 5a — RR-P0-6/RR-P0-9/MN-6 + # server-issued confirmation challenges: new v1/challenges.py (bounded + # single-use store + issue/consume/require_challenge + ADR-07 kill-switch + # docstring), ChallengeRequestV1/ChallengeV1 DTOs + router/openapi rows, + # and the enforcement blocks in checkpoint_lifecycle.py / api_process.py + # (delete-checkpoint / stop / gpu-monitor stop now 428 without a valid + # challenge). One endpoint added: POST /api/v1/challenges. + # 16043 -> 16143: gui_refresh task 09 Wave 5a — RR-P0-10/MN-7 browser + # security headers: routes.py gained the pure _csp_policy (default-src + # 'self' CSP with explicit ws/wss connect-src on HTTP port + 1), + # _send_security_headers on the SPA index + /static/ responses + # (CSP + nosniff + Referrer-Policy: no-referrer), the ARI_GUI_CSP + # kill-switch, and the ADR-07 policy docstring. No endpoint added + # or removed; API/JSON responses untouched. + # 16143 -> 16520: gui_refresh task 09 Wave 5b — RR-P0-3 auth + # sub-scope/MN-8/ADR-13 remote token auth: new auth.py (pure + # is_remote_bind/resolve_token/check_authorization/redact_token_in_path + # + process-wide token cache + the ARI_GUI_AUTH ADR-07 register), + # routes.py gained the _auth_gate/_send_unauthorized single gate at + # the top of all five method handlers + access-log token redaction, + # websocket.py gained the _ws_process_request handshake gate, and + # server.py gained init_auth at startup + the one-time + # _print_generated_token_banner. No endpoint added or removed + # (loopback default byte-identical; gate active only on remote binds). + # 16544 -> 16921: gui_refresh task 09 Wave 5b — plan 09 §Operational + # visibility (MN-9): new health.py (GET /health/live constant probe, + # GET /health/ready per-subsystem checks that degrade instead of 500, + # the GET /api/v1/diagnostics bounded-scalar builder, and the + # ARI_GUI_HEALTH ADR-07 kill-switch register), the routes.py probe + # branch + docstring, watcher handle/heartbeat telemetry in + # state_sync.py, the ws-started flag (state.py/server.py), the + # events.py subscriber counter + bus_stats, and the DiagnosticsV1 DTO/ + # router/openapi rows. Endpoints added: GET /health/live, + # GET /health/ready, GET /api/v1/diagnostics. + # 16921 -> 17526: gui_refresh tasks 04/06 Wave 4e — MN-10 canonical + # idempotent launch: new v1/launch.py (validation-first POST + # /api/v1/runs, server-minted _-<6hex> run identity, + # checkpoint materialization incl. the resolved_config.json manifest + # before the same `ari.cli run` spawn as the legacy path, gui_store + # launches/ idempotency records, launch_events.jsonl lifecycle), + # store.py KIND_LAUNCH, config_api.py draft `goal` + + # _draft_validation_errors extraction, and the RunLaunchRequestV1/ + # RunLaunchedV1 DTO/router/openapi rows. Endpoint added: + # POST /api/v1/runs (legacy POST /api/launch unchanged in parallel). + # 17526 -> 17813: gui_refresh task 07 tail — cursor log explorer + # (plan 07 §Artifacts, logs, and diagnostics): new v1/logs.py (bounded + # byte-offset cursor pagination over the append-only {ckpt}/ari.log — + # raw-byte cursor stable under the grep filter, committed lines only, + # <= 1 MiB scanned per request, absent file => present=false), plus the + # LogEntryV1/RunLogsV1 DTO/router/openapi rows. Endpoint added: + # GET /api/v1/runs/{run_id}/logs. + # 17813 -> 17824: gui_refresh tasks 03/04 G2 tail — /state legacy-facade + # freeze (plan 04 §Caching and polling policy / RR-P0-8 disposition): + # FROZEN header docstring + do-not-grow marker comment on + # services/state_service.py build_app_state. Documentation-only lines; + # the frozen top-level key set itself is pinned by + # ari-core/tests/test_gui_state_facade_freeze.py. No endpoint added. + # 17824 -> 18026: ADR-09 mode selection (accepted 2026-07-27) — the GUI + # may select the execution mode (ari.mode + rqgm.enabled) and the paper + # mode (paper.mode + rqgm.paper.enabled) FOR A NEW RUN. v1/launch.py + # gained the narrowed mode_locked carve-out plus the mode + # selection/materialization helpers (_mode_selection, _mode_env, + # _merge_mode_blocks writing the minimal ari:/rqgm:/paper: blocks into + # the per-checkpoint workflow.yaml copy), config_api.py the + # _validate_mode_pairs guard on merged document values + the + # run-scope-values plumbing, and dto.py one docstring line. No endpoint + # added or removed; the default simple_bfts + linear path writes and + # exports NOTHING and stays byte-identical (pinned by + # ari-core/tests/test_gui_v1_mode_selection.py). + # 18026 -> 18097: RQGM erasure-aware GUI read models (checkpoint_api / + # checkpoint_finder / routes / v1 dto+queries+rqgm, +94/-30) plus the + # best-VALID-score filters on the run cards (checkpoint_api, v1 queries + # — erased nodes' retained stale scores no longer display as the run's + # best, +7). No endpoint added or removed. + # 18097 -> 18135: skills integration adds typed retrieval/credential + # settings and paper-build launch plumbing across api_experiment, + # api_paperbench, its worker, api_settings, and state_service. The net +38 + # is the exact union after removing the superseded legacy settings paths. + assert by["ari-core/ari/viz"]["loc"] == 18135 + # 148 -> 159: RQGM-branch public re-exports (claim_gate FORMULAS / + # required_roles; cost_tracker PRICING_TABLE_UNAVAILABLE + logging) — + # both intentional, public_api.json snapshot regenerated accordingly. + # 159 -> 846: skills integration adds the typed, versioned public facades + # for analysis, execution, figures, memory, paper, science data, skill + # manifests/locks, call context, lineage, evaluation, and visual review. + # Private implementations remain behind this boundary; the strict public + # API snapshot below independently pins every exported symbol. + assert by["ari-core/ari/public"]["loc"] == 846 # every discovered area carries a finding_count key (0 with no results). assert all(r["finding_count"] == 0 for r in rows) diff --git a/scripts/tests/test_readme_sync.py b/scripts/tests/test_readme_sync.py new file mode 100644 index 00000000..0824125f --- /dev/null +++ b/scripts/tests/test_readme_sync.py @@ -0,0 +1,73 @@ +"""Regression tests for deterministic, Git-bounded README indexes.""" + +from __future__ import annotations + +import importlib.util +import subprocess +import sys +from pathlib import Path + + +SCRIPT = Path(__file__).resolve().parents[1] / "readme_sync.py" + + +def _load_module(): + spec = importlib.util.spec_from_file_location("_readme_sync", SCRIPT) + assert spec and spec.loader + module = importlib.util.module_from_spec(spec) + sys.modules[spec.name] = module + spec.loader.exec_module(module) + return module + + +sync = _load_module() + + +def _write(root: Path, relative: str, text: str = "") -> Path: + path = root / relative + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(text, encoding="utf-8") + return path + + +def _git(root: Path, *args: str) -> None: + subprocess.run( + ["git", "-C", str(root), *args], + check=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + + +def test_write_ignores_local_artifacts_but_lists_new_source( + tmp_path: Path, + monkeypatch, +) -> None: + _git(tmp_path, "init", "--quiet") + _write(tmp_path, ".gitignore", "__pycache__/\n*.log\nexperiments/\n") + readme = _write( + tmp_path, + "package/README.md", + "# Package\n\nPackage role.\n\n## Contents\n\n- `README.md` — this file.\n", + ) + _write(tmp_path, "package/stable.py", "STABLE = True\n") + _git(tmp_path, "add", ".gitignore", "package/README.md", "package/stable.py") + + # These local-only paths reproduced the CI-only drift: their directories + # exist on a developer checkout but disappear from a clean checkout. + _write(tmp_path, "package/cache/__pycache__/module.pyc", "bytecode") + _write(tmp_path, "package/artifact/run.log", "runtime output") + _write(tmp_path, "scripts/local/experiments/case.md", "local experiment") + _write(tmp_path, "package/new_source.py", "NEW = True\n") + + monkeypatch.setattr(sync, "REPO_ROOT", tmp_path) + sync.git_inventory.cache_clear() + + assert sync.write() == 0 + rendered = readme.read_text(encoding="utf-8") + assert "`stable.py`" in rendered + assert "`new_source.py`" in rendered + assert "cache/" not in rendered + assert "artifact/" not in rendered + assert "experiments/" not in rendered + assert sync.check() == 0