From 88467c00f722607acac1cfebe3269bed049b4c73 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 9 Sep 2026 14:57:08 +0900 Subject: [PATCH 1/4] chore(review): use central OpenCode configuration --- AGENTS.md | 7 ++- ARCHITECTURE.md | 6 +- CHANGELOG.md | 4 ++ CLAUDE.md | 3 + README.md | 3 +- backend/tests/test_release_governance.py | 8 ++- docs/product-technical-gap-baseline.md | 16 +++++ opencode.jsonc | 77 ------------------------ 8 files changed, 42 insertions(+), 82 deletions(-) delete mode 100644 opencode.jsonc diff --git a/AGENTS.md b/AGENTS.md index 9104dd1f4..1231a4f5d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -653,8 +653,11 @@ in this repo. `codegraph init -i` autonomously without asking first; keep generated `.codegraph/` and `.cursor/rules/codegraph.mdc` artifacts local unless a future repository policy explicitly says to commit them. OpenCode PR review - uses the project `opencode.jsonc` MCP servers for CodeGraph, DeepWiki, - Context7, and web search. It must initialize CodeGraph before review so + receives the canonical `opencode.jsonc` from the central required workflow; + do not add repository-local `opencode.json` or `opencode.jsonc` files. The + central runtime owns model routing and MCP registration, including Graphify; + consumer repositories own no provider or MCP fallback configuration. It must + initialize CodeGraph before review so structural findings cite graph-backed evidence instead of relying only on grep or raw file reads; use Context7 for current library docs, DeepWiki for repository documentation, and web search only for bounded external lookups. diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 9d2cbba18..99580f42c 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -232,7 +232,11 @@ and grounding: [`docs/architecture/kg-extractor-seam.md`](docs/architecture/kg-e OpenCode Review, Strix Security Scan, and PR Review Merge Scheduler are supplied by the ContextualWisdomLab central required workflows in `ContextualWisdomLab/.github`; this repository does not carry repo-local copies -of those workflows or their dedicated support scripts. The central Strix +of those workflows, their dedicated support scripts, or OpenCode JSON files. +The required review runtime copies the central `opencode.jsonc` into its +exact-head isolation directory, so a repository-local `opencode.json` or +`opencode.jsonc` would only create an unaudited provider and MCP policy fork. +The central Strix workflow treats pull request code as untrusted whenever repository secrets are available: privileged PR scans run from trusted workflow code, fetch the pull request head as Git objects, and copy PR-head blobs into temporary scan scopes diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ec84c36f..4efa7af58 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,8 @@ ## [Unreleased] +- OpenCode 모델·MCP 정책의 저장소별 복사본을 제거했습니다. Naruon의 required + review는 중앙 workflow가 격리 경로에 주입하는 단일 `opencode.jsonc`만 + 사용하며, release-governance 테스트가 두 OpenCode JSON 파일의 재도입을 + 막습니다. - 긴 이메일·첨부 본문을 의미 단위 청크로 임베딩한 뒤 기존 email/attachment 벡터 계약으로 평균화하고, 청크 요청·벡터 누적을 제한된 창으로 처리합니다. OpenAI `text-embedding-3-*`에는 저장 차원(`1536`)을 직접 요청하도록 보강했습니다. 합성 메일 fixture 5건(70청크)과 provider 요청 계약으로 1,536차원 벡터 경로를 검증했으며, 실행 시 선택한 임베딩 제공자에 본문·파싱된 첨부 텍스트를 전송할 수 있습니다. 회사 기밀 데이터는 fixture·commit·PR·log에 포함하지 않습니다. - EmailDetail 테스트가 지원하지 않는 스레드 병합/분리 버튼을 `textContent`뿐 아니라 `aria-label`과 `title` 접근 가능 이름으로도 검출하도록 바꿔, 아이콘 전용 버튼 회귀를 놓치지 않습니다. diff --git a/CLAUDE.md b/CLAUDE.md index be67bc80c..c2e9a27ef 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -17,6 +17,9 @@ merged by metadata-only robot governance (see `docs/development/merge-gate-policy.md`); OpenCode Review, Strix Security Scan, and the merge scheduler come from central workflows in `ContextualWisdomLab/.github` — do not reintroduce repo-local copies. +That owner also supplies the only `opencode.jsonc` used by required review; +do not add a repository-local `opencode.json` or `opencode.jsonc`. A local copy +forks model and MCP policy without changing the isolated central review runtime. ## Common commands diff --git a/README.md b/README.md index ff64840e9..5518699db 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,8 @@ mail/calendar/file systems. - OpenCode Review, Strix Security Scan, and PR Review Merge Scheduler are supplied by the ContextualWisdomLab central required workflows from `ContextualWisdomLab/.github`. This repository does not carry repo-local - OpenCode, Strix, or merge-scheduler workflow copies; branch updates, + OpenCode configuration copies, Strix workflows, or merge-scheduler workflows; + branch updates, auto-merge, and mechanical merge actions run as the target repository's `github-actions[bot]` through the central workflow. Pending CodeRabbit or required-check evidence is a wait state, not a hard blocker. diff --git a/backend/tests/test_release_governance.py b/backend/tests/test_release_governance.py index a23c70746..f33976271 100644 --- a/backend/tests/test_release_governance.py +++ b/backend/tests/test_release_governance.py @@ -633,14 +633,20 @@ def test_review_automation_uses_central_required_workflows_without_local_copies( f"central review automation must not be copied locally: {relative_path}" ) + for relative_path in ("opencode.json", "opencode.jsonc"): + assert not (REPO_ROOT / relative_path).exists(), ( + f"central OpenCode configuration must not be copied locally: {relative_path}" + ) + assert "ContextualWisdomLab central required workflows" in normalized_readme assert "This repository does not carry repo-local" in normalized_readme - assert "OpenCode, Strix, or merge-scheduler workflow copies" in normalized_readme + assert "OpenCode configuration copies" in normalized_readme assert ( "branch updates, auto-merge, and mechanical merge actions" in normalized_readme ) assert "central required workflows" in architecture assert "ContextualWisdomLab/.github" in architecture + assert "central `opencode.jsonc`" in architecture assert "central required workflow" in normalized_security assert "openai/openai/gpt-4.1" not in architecture diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index 98bc17d2a..382226e90 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -1,5 +1,21 @@ # Naruon Product and Technical Gap Baseline +## 2026-09-09 중앙 OpenCode 설정 소비 경계 + +- **Gap:** Naruon의 저장소별 `opencode.jsonc`가 모델과 MCP를 별도로 선언해 + 중앙 required review의 격리 설정과 서로 다른 정책을 만들 수 있었다. +- **조치:** 저장소별 OpenCode JSON을 제거하고, release-governance 계약이 + `opencode.json`과 `opencode.jsonc`의 재도입을 모두 거부하도록 고정했다. + README·ARCHITECTURE·AGENTS·CLAUDE는 중앙 owner가 exact-head 격리 경로에 + 설정을 주입한다는 같은 책임 경계를 설명한다. +- **선행조건:** 중앙 Graphify owner + [ContextualWisdomLab/.github#2052](https://github.com/ContextualWisdomLab/.github/pull/2052)가 + 보호 병합되기 전까지 이 consumer delta는 Proposed/Draft다. 중앙 실패 시 + consumer 파일을 되살리는 대신 owner를 수리한다. +- **검증 범위:** focused release-governance 테스트와 파일 부재 검증이다. + 실제 중앙 review 실행·Graphify handshake·보호 병합은 owner PR의 exact-head + evidence이며 이 변경만으로 재증명하지 않는다. + **Baseline version:** 1.2 **Observed on:** 2026-08-26 (Asia/Seoul) **Observed protected branch (current scan; row Base-SHA values remain historical):** `develop@e5e99b4e3bb081b92c602358878856536030e2ca` diff --git a/opencode.jsonc b/opencode.jsonc deleted file mode 100644 index 536d212fb..000000000 --- a/opencode.jsonc +++ /dev/null @@ -1,77 +0,0 @@ -{ - "$schema": "https://opencode.ai/config.json", - "model": "github-models/openai/gpt-5", - "small_model": "github-models/deepseek/deepseek-v3-0324", - "enabled_providers": ["github-models"], - "mcp": { - "codegraph": { - "type": "local", - "command": ["npx", "-y", "@colbymchenry/codegraph@0.9.9", "serve", "--mcp"], - "enabled": true - }, - "deepwiki": { - "type": "remote", - "url": "https://mcp.deepwiki.com/mcp", - "enabled": true, - "timeout": 300000 - }, - "context7": { - "type": "local", - "command": ["npx", "-y", "@upstash/context7-mcp@3.1.0", "--transport", "stdio"], - "enabled": true, - "timeout": 300000, - "environment": { - "NPM_CONFIG_IGNORE_SCRIPTS": "true", - "NPM_CONFIG_LOGLEVEL": "error" - } - }, - "web_search": { - "type": "local", - "command": ["npx", "-y", "@guhcostan/web-search-mcp@1.0.5"], - "enabled": true, - "timeout": 300000, - "environment": { - "NPM_CONFIG_IGNORE_SCRIPTS": "true", - "NPM_CONFIG_LOGLEVEL": "error" - } - } - }, - "provider": { - "github-models": { - "npm": "@ai-sdk/openai-compatible", - "name": "GitHub Models", - "options": { - "baseURL": "https://models.github.ai/inference", - "apiKey": "{env:STRIX_GITHUB_MODELS_TOKEN}" - }, - "models": { - "openai/gpt-5": { - "name": "OpenAI GPT-5", - "tool_call": true, - "reasoning": true, - "limit": { - "context": 200000, - "output": 100000 - } - }, - "deepseek/deepseek-r1-0528": { - "name": "DeepSeek R1 0528", - "tool_call": true, - "reasoning": true, - "limit": { - "context": 128000, - "output": 4096 - } - }, - "deepseek/deepseek-v3-0324": { - "name": "DeepSeek V3 0324", - "tool_call": true, - "limit": { - "context": 128000, - "output": 4096 - } - } - } - } - } -} From 19d54c334ad75e9657a952350b177180c63adf8b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 9 Sep 2026 15:15:43 +0900 Subject: [PATCH 2/4] test(review): narrow central OpenCode consumer contract --- AGENTS.md | 110 ++++-- ARCHITECTURE.md | 73 ++-- CLAUDE.md | 24 +- README.md | 3 +- .../tests/test_agent_llm_authority_docs.py | 83 ++++ .../test_frontend_framework_security_floor.py | 166 ++++++++ .../test_opencode_central_config_consumer.py | 16 + backend/tests/test_release_governance.py | 22 +- frontend/package.json | 4 +- frontend/pnpm-lock.yaml | 370 ++++++++++-------- frontend/pnpm-workspace.yaml | 2 +- 11 files changed, 612 insertions(+), 261 deletions(-) create mode 100644 backend/tests/test_agent_llm_authority_docs.py create mode 100644 backend/tests/test_frontend_framework_security_floor.py create mode 100644 backend/tests/test_opencode_central_config_consumer.py diff --git a/AGENTS.md b/AGENTS.md index 1231a4f5d..1ccdd17ea 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,5 +1,16 @@ # AGENTS.md + +> **Agents: read the master context FIRST.** Before any work, read: +> +> - [`docs/architecture/naruon-product-spec.md`](docs/architecture/naruon-product-spec.md) for product vision, architecture, RBAC/ABAC, core features, AI agent ontology, branding/UX, observability, and governance; +> - the live [GitHub Project #1](https://github.com/orgs/ContextualWisdomLab/projects/1), the work and roadmap source of truth; +> - [`docs/product-technical-gap-baseline.md`](docs/product-technical-gap-baseline.md), the live gap snapshot but not merge authorization, and canonical completion issue [#1428](https://github.com/ContextualWisdomLab/naruon/issues/1428); +> - `ContextualWisdomLab/.github`'s [`docs/agent-github-project-protocol.md`](https://github.com/ContextualWisdomLab/.github/blob/main/docs/agent-github-project-protocol.md) for Project operation; and +> - `ContextualWisdomLab/.github`'s [`docs/product-goal-directive.md`](https://github.com/ContextualWisdomLab/.github/blob/main/docs/product-goal-directive.md) before running or configuring the continuous PR review→fix→merge→develop loop. A length-capped `/goal` pointer does not replace the full directive. +> +> This repository's Figma file ID is not yet canonical on `develop`: ADR-0013 and file ID `68b5XB58w8nwT2LYOOnikK` remain Proposed in open PR [#1436](https://github.com/ContextualWisdomLab/naruon/pull/1436). The repository and Project, not private agent memory, are authoritative. + ## Agent guidance (CWL governance) @@ -136,35 +147,23 @@ in this repo. `.github/workflows/opencode-review.yml`, `.github/workflows/strix.yml`, `.github/workflows/strix-selftest.yml`, or `.github/workflows/pr-review-merge-scheduler.yml`. -- The central Strix Security Scan uses GitHub Models by default through - `STRIX_GITHUB_MODELS_TOKEN`, `STRIX_LLM=openai/gpt-5`, and - `LLM_API_BASE_FILE` pointing at a trusted file containing - `https://models.github.ai/inference`; GitHub Models scans must try the - configured GPT-5-or-newer model first and may fall back to the explicit - workflow fallback list, currently - `github_models/deepseek/deepseek-r1-0528` and - `github_models/deepseek/deepseek-v3-0324`, when GitHub Models provider - capacity or model availability blocks the primary run. The Strix gate must - route these fallback names through the GitHub Models endpoint with - OpenAI-compatible child model names such as - `openai/deepseek/deepseek-r1-0528`, not the public DeepSeek API. Do not use - GPT-4.1 or weaker GitHub Models fallbacks for Strix or OpenCode PR review - evidence. Keep the GitHub Models endpoint in a trusted input file and pass - the token only through - the provider-scoped Strix child-process key path. Legacy `STRIX_LLM` secrets - must not override PR, push, or scheduled Strix defaults. Vertex remains - available only for manual - `workflow_dispatch` evidence when the `strix_llm` input - explicitly selects `vertex_ai/gemini-3.1-pro-preview-customtools` or - `vertex_ai/gemini-2.5-flash` with `GCP_SA_KEY`; expose Google/Vertex - credentials only for Vertex provider mode. Direct OpenAI GPT-5.4-or-newer - scans remain supported only for manual `strix_llm` selections with - `STRIX_OPENAI_API_KEY`. Do not silently fall back between providers, and - do not treat timeout-class provider infrastructure failures as clean PR - evidence even when Strix printed zero vulnerabilities before failing. Disable - silent Vertex fallback models in the workflow unless a future PR proves a new - exact fallback contract with no Timeout/Fatal/Warn/Denied output. Record - provider evidence in the PR. Known third-party Strix/Pydantic +- Central model-backed Actions are owned by `ContextualWisdomLab/.github` and + request only the logical `orchestrator/free` pool with a gateway token. Thin + callers in this repository must not select or forward provider names, model + names, provider groups, endpoints, or paid fallback credentials. +- `ContextualWisdomLab/contextual-orchestrator` owns provider discovery, + capability-based routing, free-pool membership, and fallback. Naruon owns + product/domain truth, authorization, tools, and context assembly. Production + use requires an immutable released owner API/client/schema; verify protected + release evidence before adoption and fail closed when that contract or a + required capability is unavailable. Do not copy owner source, query owner + storage, or treat an open PR or unreleased branch as a consumable contract. +- Do not impose a shared application/agent/gateway wall-clock timeout on model + work. A configured administrator limit, explicit user cancellation, or the + upstream provider's terminal result may end it; reasoning, streaming, and + tool calls are not failed merely because elapsed time is long. +- A failed model-backed check remains failed evidence even when an intermediate + report printed zero findings. Known third-party Strix/Pydantic serializer warnings must be filtered narrowly inside the Strix gate child process, not as a visible workflow env entry, so Warn-class logs are not accepted as clean evidence and warning-filter variable names do not pollute @@ -274,6 +273,19 @@ in this repo. ## Workspace and task tracking defaults +- Browser smoke must check collected console and page errors after interaction, + accessibility, screenshot capture, and page cleanup, not only after navigation. + Keep the early check for fast failure and a final check before returning success. + Regression tests must inject late errors into the actual route-smoke execution; + a successful screenshot or a passing render assertion does not prove clean interactions. + If screenshot retries are exhausted, throw the capture failure; do not create + diagnostic text files in the temporary capture directory or count them as screenshots. + Close the page in a finally block, including navigation and capture failures. +- Browser-smoke API mocks must reject unregistered requests with a non-success + response and record a harness failure even when the UI suppresses the error. + Cover requests emitted during cleanup; never return a default `{ ok: true }`. + Keep request URLs, query values, and payloads out of failure diagnostics. + - First-run frontend sessions should open the Today execution dashboard while preserving explicit Dashboard, Email, and Calendar startup choices. - Workspace navigation changes must keep the desktop primary nav and the @@ -457,10 +469,10 @@ in this repo. update both backend header tests and frontend link tests. Global backend responses must include `Referrer-Policy`, and `target="_blank"` links must use explicit `rel="noopener noreferrer"`. -- When robot review cites an obsolete Strix provider policy, update the docs and - tests to the current GitHub Models default contract before accepting a - rollback suggestion; do not reintroduce generic `LLM_API_KEY` or - cross-provider credential forwarding while trying to satisfy old comments. +- When robot review cites obsolete provider-specific Strix policy, compare it + with the released `.github` and `contextual-orchestrator` contracts before + accepting a rollback suggestion. Do not reintroduce direct-provider routing, + generic provider credentials, or cross-provider credential forwarding. - When reviews find inert navigation/dead-space controls, either wire them to an implemented workspace route/API or remove the control; do not leave high-traffic drawer/sidebar entries as permanent `준비 중` copy. @@ -649,15 +661,37 @@ in this repo. ## Development environment and tooling defaults +- Read the applicable repository skill before changing its contract: + [fix-development-mistakes](.agents/skills/fix-development-mistakes/SKILL.md) + for failures and security findings, + [github-actions-privileged-pr-scan](.agents/skills/github-actions-privileged-pr-scan/SKILL.md) + for privileged PR scanners, and + [github-robot-review-gate](.agents/skills/github-robot-review-gate/SKILL.md) + for check/review diagnosis. Record the failing reproduction, smallest causal + repair, exact verification command, and remaining gates in the existing PR. +- Visual Inspection requires opening the actual rendered pages or captured + images, not merely counting PNG files. Record app/build SHA separately from + runner SHA, viewport, routes, and observed defects. Mocked browser evidence + does not prove live provider behavior, all locales, or deployment readiness. + Inspect the changed AGENTS.md rendering on the pushed revision as well. +- A server-ready timeout before browser navigation is failed startup evidence, + not a completed visual inspection. Record the exact source and execution result; + host load alone does not prove the cause or justify increasing test timeouts. +- Preserve concurrent commits with ordinary history integration, then verify + the combined tree before pushing. Update these instructions with reusable + failure-prevention lessons; keep dated findings in the gap baseline/PR. +- Package registry credentials alone do not authorize or configure a cluster + deployment. Use the existing release workflow only after its protected-source, + checks, target credentials, and destination prerequisites are verified; report + package publication and live deployment as separate outcomes. +- Before relocating guidance out of a PR, verify its complete successor delta + and record the destination PR, commit, and path; a named owner alone is not succession. - If CodeGraph is not initialized for this repository, agents may run `codegraph init -i` autonomously without asking first; keep generated `.codegraph/` and `.cursor/rules/codegraph.mdc` artifacts local unless a future repository policy explicitly says to commit them. OpenCode PR review - receives the canonical `opencode.jsonc` from the central required workflow; - do not add repository-local `opencode.json` or `opencode.jsonc` files. The - central runtime owns model routing and MCP registration, including Graphify; - consumer repositories own no provider or MCP fallback configuration. It must - initialize CodeGraph before review so + uses the project `opencode.jsonc` MCP servers for CodeGraph, DeepWiki, + Context7, and web search. It must initialize CodeGraph before review so structural findings cite graph-backed evidence instead of relying only on grep or raw file reads; use Context7 for current library docs, DeepWiki for repository documentation, and web search only for bounded external lookups. diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 99580f42c..0ad757b07 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -6,7 +6,7 @@ flowchart LR UI[Next.js frontend] --> API[FastAPI backend / Naruon control plane] API --> DB[(Postgres + pgvector)] - API --> LLM[OpenAI APIs when configured] + API --> LLM[contextual-orchestrator released consumer API] API --> CONN[Outbound-only self-hosted connector] CONN --> MAIL[Customer IMAP/POP3/SMTP] CONN --> DAV[Customer CalDAV/CardDAV/WebDAV] @@ -20,6 +20,15 @@ Runtime database connectivity is secret-injected: `backend/core/config.py` has no fallback `DATABASE_URL`, so missing database configuration fails at startup rather than silently using shared development credentials. +Production LLM work crosses an immutable released `contextual-orchestrator` +API/client/schema. `contextual-orchestrator` owns provider discovery, +capability-based routing, free-pool membership, and provider fallback; Naruon +owns product/domain truth, authorization, tools, and context assembly. A missing +or incompatible released owner contract fails closed instead of creating a +Naruon-local provider/model fallback. Local deterministic fixtures and legacy +provider surfaces may remain for migration/testing only when they are explicitly +identified as non-production authority. + ## Topic-intelligence boundary Naruon has no live Structural Topic Modeling endpoint, fitted topic artifact, @@ -83,7 +92,10 @@ registry/list/update/delete plus prompt-preview provider selection paths filter the authenticated organization. Existing local databases get the same fail-closed owner backfill through `scripts/bootstrap_db.py`; legacy provider rows require explicit non-default `NARUON_IMPORT_USER_ID` and `NARUON_IMPORT_ORGANIZATION_ID` -before bootstrap will set the new columns non-null. +before bootstrap will set the new columns non-null. This table and its direct +provider-routing fields are legacy migration surfaces, not the production +provider/model authority; new production LLM work must consume the released +`contextual-orchestrator` contract instead of extending this registry as a router. `ticket_tasks` stores email-derived execution items as ticket-like work records. The table and its new columns use at least two-word `snake_case` database names @@ -170,13 +182,15 @@ ETag/If-Match-guarded CalDAV/WebDAV PUT local adapter handlers for `SelfHostedConnector`; packaging, registration-token lifecycle, backend dispatch, and non-DAV protocol adapters remain separate connector delivery work. -LLM provider `base_url` is also a server-side egress boundary, not an arbitrary -URL field. Provider registry create/update paths are organization-admin scoped, -and LLM call sinks validate custom OpenAI-compatible base URLs with HTTPS-only -syntax, no userinfo/query or fragment, exact host membership in -`ALLOWED_LLM_BASE_URL_HOSTS`, and DNS answers that are all globally routable. -Missing allowlist configuration fails closed; the default provider path should -leave `base_url` unset. +The legacy LLM-provider `base_url` path remains a server-side egress boundary +while migration is incomplete, not an arbitrary URL field or production routing +authority. Existing provider registry create/update paths remain +organization-admin scoped, and current call sinks retain HTTPS-only syntax, +userinfo/query/fragment rejection, exact `ALLOWED_LLM_BASE_URL_HOSTS` membership, +and globally routable DNS validation until the legacy path is removed. Missing +allowlist configuration still fails closed. New production model work must not +select a provider endpoint through this surface; it uses the immutable released +`contextual-orchestrator` consumer contract. ## Batch embedding routing boundary @@ -192,12 +206,13 @@ SSRF-guarded, allowlisted, pinned-address HTTP client (`build_llm_provider_http_client`) that fronts every other outbound LLM call. naruon records a durable `llm_batch_jobs` / `llm_batch_items` audit trail (routing mode, the orchestrator's batch id, reported cost) for observability. -The path degrades gracefully: if batching is disabled, the orchestrator base URL -is rejected by the egress guard, or the orchestrator is unreachable, the caller -transparently falls back to the per-item embedding path. A local `pg-llm-batch` -package/checkout remains only as an optional offline-dev fallback, gated behind -orchestrator-unavailable and an explicit local DSN; naruon does not vendor a -gitlink for that fallback in this PR. +The currently implemented per-item fallback is migration behavior rather than +canonical provider authority: if batching is disabled or the orchestrator path +cannot be used, callers may still reach the legacy per-item embedding path, but +that path must not grow new production provider/model selection. A local +`pg-llm-batch` package/checkout remains only as an optional offline-dev fallback, +gated behind orchestrator-unavailable and an explicit local DSN; naruon does not +vendor a gitlink for that fallback in this PR. ## Semantic project-graph extractor seam @@ -216,27 +231,23 @@ and the runner degrades down the chain instead of losing the projection. New extractors (including future plugins on the platform plan's `kg.extractor` extension point) register a selector without editing ingest. -Routing LLM extraction through **contextual-orchestrator** is modelled as a -transport concern: the orchestrator is an OpenAI-compatible gateway, so the -`orchestrator` selector reuses the identical grounded LLM extractor -(`extract_project_semantics_llm`, which enforces segment citations) but points -its SSRF-allowlisted client (`build_llm_provider_http_client`) at -`PROJECT_GRAPH_ORCHESTRATOR_BASE_URL` instead of the raw provider. The provider -API key stays the tenant's Fernet-encrypted credential, and the orchestrator base -URL must be HTTPS and exact-host allowlisted by `ALLOWED_LLM_BASE_URL_HOSTS`; -an unset or rejected endpoint fails closed to the deterministic extractor. Design -and grounding: [`docs/architecture/kg-extractor-seam.md`](docs/architecture/kg-extractor-seam.md). +The current `orchestrator` selector still reuses the grounded LLM extractor and +an OpenAI-compatible transport adapter, but that compatibility layer is legacy +migration plumbing rather than Naruon-owned provider/model routing. It points the +SSRF-allowlisted client (`build_llm_provider_http_client`) at +`PROJECT_GRAPH_ORCHESTRATOR_BASE_URL`; its existing credential and allowlist +handling remains in place until the released owner client replaces it. New +production behavior must consume the immutable released `contextual-orchestrator` +API/client/schema and must not add raw-provider fallback here. An unset or +rejected endpoint fails closed to the deterministic extractor. Design and +grounding: [`docs/architecture/kg-extractor-seam.md`](docs/architecture/kg-extractor-seam.md). ## CI security boundary OpenCode Review, Strix Security Scan, and PR Review Merge Scheduler are supplied by the ContextualWisdomLab central required workflows in `ContextualWisdomLab/.github`; this repository does not carry repo-local copies -of those workflows, their dedicated support scripts, or OpenCode JSON files. -The required review runtime copies the central `opencode.jsonc` into its -exact-head isolation directory, so a repository-local `opencode.json` or -`opencode.jsonc` would only create an unaudited provider and MCP policy fork. -The central Strix +of those workflows or their dedicated support scripts. The central Strix workflow treats pull request code as untrusted whenever repository secrets are available: privileged PR scans run from trusted workflow code, fetch the pull request head as Git objects, and copy PR-head blobs into temporary scan scopes @@ -323,4 +334,4 @@ target source id, but it must not provide source ownership or capability records `backend/api/calendar.py` obtains writeback sources through a FastAPI dependency that reads DB-backed `calendar_writeback_sources` records scoped to the authenticated user. Provider execution remains opt-in and fail-closed without an -active outbound runner or If-Match/ETag evidence. +active outbound runner or If-Match/ETag evidence. \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md index c2e9a27ef..0bfd4f580 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -17,9 +17,6 @@ merged by metadata-only robot governance (see `docs/development/merge-gate-policy.md`); OpenCode Review, Strix Security Scan, and the merge scheduler come from central workflows in `ContextualWisdomLab/.github` — do not reintroduce repo-local copies. -That owner also supplies the only `opencode.jsonc` used by required review; -do not add a repository-local `opencode.json` or `opencode.jsonc`. A local copy -forks model and MCP policy without changing the isolated central review runtime. ## Common commands @@ -113,11 +110,22 @@ indexes, and auditable writeback intent. `ARCHITECTURE.md` and ``` Next.js frontend ──> FastAPI backend (control plane) ──> Postgres + pgvector │ - ├──> OpenAI-compatible LLM providers (Ollama locally) + ├──> contextual-orchestrator consumer boundary + │ └──> provider/model routing owned by contextual-orchestrator └──> outbound-only self-hosted connector (connector/) └──> customer IMAP/POP3/SMTP + CalDAV/CardDAV/WebDAV ``` +Naruon owns product behavior and model-call authorization, but not provider/model routing. +Production model work requires an immutable released `contextual-orchestrator` +API/client/schema; a missing or incompatible owner contract fails closed instead +of falling back to a direct provider. The Ollama/MLX paths in local Compose are +explicit non-production development fixtures. Central model-backed GitHub Actions +are owned by `ContextualWisdomLab/.github` and use `orchestrator/free`; do not copy +that pool selection into Naruon runtime. Verify protected release evidence before +claiming the owner contract is available; an open PR or unreleased branch is only +proposed evidence. + - `backend/` — FastAPI app (`main.py`, routers in `api/`, domain logic in `services/`, SQLAlchemy models in `db/`, Alembic in `alembic/`). Owns persistence, canonical email threading @@ -143,8 +151,12 @@ Next.js frontend ──> FastAPI backend (control plane) ──> Postgres + pgve ETag/If-Match); intents are the default response. - Auth: signed HS256 bearer sessions (HMAC via `AUTH_SESSION_HMAC_SECRET`) or enterprise OIDC/JWKS; private `/api/*` routers register the default - `get_auth_context` dependency. LLM `base_url` and OIDC/SMTP/IMAP/POP3 hosts - are strict egress allowlists that resolve only to pinned global addresses. + `get_auth_context` dependency. Naruon owns domain tools, authorization, and + context assembly; provider/model routing belongs to `contextual-orchestrator`. + Existing direct-provider `base_url` surfaces are legacy migration debt and must + not be extended as production routing authority; keep their SSRF controls until + they are removed. OIDC/SMTP/IMAP/POP3 hosts remain strict egress allowlists + that resolve only to pinned global addresses. - CI (`.github/workflows/`): `app-ci.yml` (backend ruff+pytest, frontend test/lint/build), plus `bandit`, `codeql`, `trivy`, `scorecard`, `pr-governance`, `docker-publish` (GHCR on `v*` tags matching `VERSION`), and diff --git a/README.md b/README.md index 5518699db..ff64840e9 100644 --- a/README.md +++ b/README.md @@ -46,8 +46,7 @@ mail/calendar/file systems. - OpenCode Review, Strix Security Scan, and PR Review Merge Scheduler are supplied by the ContextualWisdomLab central required workflows from `ContextualWisdomLab/.github`. This repository does not carry repo-local - OpenCode configuration copies, Strix workflows, or merge-scheduler workflows; - branch updates, + OpenCode, Strix, or merge-scheduler workflow copies; branch updates, auto-merge, and mechanical merge actions run as the target repository's `github-actions[bot]` through the central workflow. Pending CodeRabbit or required-check evidence is a wait state, not a hard blocker. diff --git a/backend/tests/test_agent_llm_authority_docs.py b/backend/tests/test_agent_llm_authority_docs.py new file mode 100644 index 000000000..e8dcc9c40 --- /dev/null +++ b/backend/tests/test_agent_llm_authority_docs.py @@ -0,0 +1,83 @@ +"""Regression contracts for current LLM-routing authority guidance.""" + +import json +from pathlib import Path + + +REPO_ROOT = Path(__file__).resolve().parents[2] + + +def _read(path: str) -> str: + """Read repository guidance as UTF-8 text.""" + return (REPO_ROOT / path).read_text(encoding="utf-8") + + +def test_agent_guidance_does_not_reintroduce_direct_model_routing_authority() -> None: + """Current agent and architecture guidance must not prescribe direct routing.""" + agents = _read("AGENTS.md") + claude = _read("CLAUDE.md") + architecture = _read("ARCHITECTURE.md") + + forbidden_current_guidance = ( + "STRIX_GITHUB_MODELS_TOKEN", + "https://models.github.ai/inference", + "Direct OpenAI GPT-5.4-or-newer", + "OpenAI-compatible LLM providers", + "API --> LLM[OpenAI APIs when configured]", + ) + combined = f"{agents}\n{claude}\n{architecture}" + for phrase in forbidden_current_guidance: + assert phrase not in combined + + +def test_agent_guidance_names_canonical_llm_owner_and_fail_closed_boundary() -> None: + """Guidance must preserve product ownership while delegating LLM routing.""" + agents = _read("AGENTS.md") + claude = _read("CLAUDE.md") + architecture = _read("ARCHITECTURE.md") + + assert "ContextualWisdomLab/.github" in agents + assert "contextual-orchestrator" in agents + assert "orchestrator/free" in agents + assert "fail closed" in agents.lower() + assert "contextual-orchestrator" in claude + assert "Naruon owns" in claude + assert "provider/model routing" in claude + assert "gateway token" in agents + assert "provider names, model" in agents + assert "immutable released owner API/client/schema" in agents + assert "open PR or unreleased branch" in agents + assert "shared application/agent/gateway wall-clock timeout" in agents + assert "contextual-orchestrator" in architecture + assert "provider discovery" in architecture + assert "immutable released" in architecture + assert "fails closed" in architecture + + +def test_opencode_config_uses_only_contextual_orchestrator_free() -> None: + """Repository OpenCode model work must use only the canonical logical pool.""" + raw_config = _read("opencode.jsonc") + config = json.loads(raw_config) + + assert config["model"] == "contextual-orchestrator/orchestrator/free" + assert config["small_model"] == "contextual-orchestrator/orchestrator/free" + assert config["enabled_providers"] == ["contextual-orchestrator"] + assert set(config["provider"]) == {"contextual-orchestrator"} + + provider = config["provider"]["contextual-orchestrator"] + assert provider["options"] == { + "baseURL": "{env:CONTEXTUAL_ORCHESTRATOR_BASE_URL}", + "apiKey": "{env:CONTEXTUAL_ORCHESTRATOR_TOKEN}", + } + assert set(provider["models"]) == {"orchestrator/free"} + + forbidden_direct_routing = ( + "github-models", + "STRIX_GITHUB_MODELS_TOKEN", + "https://models.github.ai/inference", + '"openai/gpt-5"', + '"deepseek/deepseek-r1-0528"', + '"deepseek/deepseek-v3-0324"', + ) + for phrase in forbidden_direct_routing: + assert phrase not in raw_config diff --git a/backend/tests/test_frontend_framework_security_floor.py b/backend/tests/test_frontend_framework_security_floor.py new file mode 100644 index 000000000..bf318b5ad --- /dev/null +++ b/backend/tests/test_frontend_framework_security_floor.py @@ -0,0 +1,166 @@ +"""Fail closed when frontend framework/image dependencies regress below patched floors.""" + +from __future__ import annotations + +import json +import re +from pathlib import Path +from typing import Any + +import pytest +import yaml + + +REPO_ROOT = Path(__file__).resolve().parents[2] +FRONTEND_ROOT = REPO_ROOT / "frontend" +NEXT_SECURITY_FLOOR = (16, 3, 3) +SHARP_SECURITY_FLOOR = (0, 35, 4) + + +def _exact_version(value: str) -> tuple[int, int, int]: + """Return a three-part exact version, rejecting ranges and prereleases.""" + + match = re.fullmatch(r"(\d+)\.(\d+)\.(\d+)", value) + assert match is not None, f"expected exact semantic version, got {value!r}" + return tuple(int(part) for part in match.groups()) + + +def _resolved_version(value: str) -> tuple[int, int, int]: + """Return the exact version prefix from a pnpm peer-qualified resolution.""" + + version = value.split("(", 1)[0] + return _exact_version(version) + + +def _package_key_version(package_key: str, package_name: str) -> tuple[int, int, int]: + """Return the version encoded by one pnpm package/snapshot key.""" + + prefix = f"{package_name}@" + assert package_key.startswith(prefix), ( + f"expected {package_name!r} lock key, got {package_key!r}" + ) + return _resolved_version(package_key[len(prefix) :]) + + +def _assert_lock_contract( + lock: dict[str, Any], + next_value: str, + eslint_next_value: str, + sharp_value: str, +) -> None: + """Validate root resolution identity and every locked Next.js/sharp security floor.""" + + importer = lock["importers"]["."] + next_import = importer["dependencies"]["next"] + assert next_import["specifier"] == next_value, ( + "root importer must preserve the package.json Next.js specifier" + ) + assert _resolved_version(str(next_import["version"])) == _exact_version(next_value), ( + "root importer must resolve the reviewed Next.js release" + ) + assert f"next@{next_import['version']}" in lock["snapshots"], ( + "root importer Next.js resolution must reference an existing snapshot" + ) + + eslint_next_import = importer["devDependencies"]["eslint-config-next"] + assert eslint_next_import["specifier"] == eslint_next_value, ( + "root importer must preserve the eslint-config-next specifier" + ) + assert _resolved_version(str(eslint_next_import["version"])) == _exact_version( + eslint_next_value + ), "root importer must resolve the reviewed eslint-config-next release" + assert f"eslint-config-next@{eslint_next_import['version']}" in lock["snapshots"], ( + "root importer eslint-config-next resolution must reference an existing snapshot" + ) + + assert str(lock["overrides"]["sharp"]) == sharp_value, ( + "lockfile sharp override must match the reviewed workspace override" + ) + + expected_next = _exact_version(next_value) + expected_sharp = _exact_version(sharp_value) + for section_name in ("packages", "snapshots"): + section = lock[section_name] + next_keys = [key for key in section if key.startswith("next@")] + sharp_keys = [key for key in section if key.startswith("sharp@")] + + assert next_keys, f"{section_name} must contain a Next.js resolution" + assert sharp_keys, f"{section_name} must contain a sharp resolution" + assert any( + _package_key_version(key, "next") == expected_next for key in next_keys + ), f"{section_name} must contain the reviewed Next.js release" + assert any( + _package_key_version(key, "sharp") == expected_sharp for key in sharp_keys + ), f"{section_name} must contain the reviewed sharp release" + + for package_key in next_keys: + assert _package_key_version(package_key, "next") >= NEXT_SECURITY_FLOOR, ( + f"{section_name} contains Next.js below the reviewed security floor: " + f"{package_key}" + ) + for package_key in sharp_keys: + assert _package_key_version(package_key, "sharp") >= SHARP_SECURITY_FLOOR, ( + f"{section_name} contains sharp below the reviewed security floor: " + f"{package_key}" + ) + + +def _frontend_security_inputs() -> tuple[str, str, str, dict[str, Any]]: + """Load the manifest, workspace override, and generated lock contract.""" + + package = json.loads((FRONTEND_ROOT / "package.json").read_text(encoding="utf-8")) + next_value = package["dependencies"]["next"] + eslint_next_value = package["devDependencies"]["eslint-config-next"] + workspace = yaml.safe_load( + (FRONTEND_ROOT / "pnpm-workspace.yaml").read_text(encoding="utf-8") + ) + sharp_value = str(workspace["overrides"]["sharp"]) + lock = yaml.safe_load( + (FRONTEND_ROOT / "pnpm-lock.yaml").read_text(encoding="utf-8") + ) + return next_value, eslint_next_value, sharp_value, lock + + +def test_frontend_framework_and_image_security_floors() -> None: + """Keep manifests and every generated lock resolution at reviewed patched releases.""" + + next_value, eslint_next_value, sharp_value, lock = _frontend_security_inputs() + + assert _exact_version(next_value) >= NEXT_SECURITY_FLOOR, ( + "Next.js must include the fixes for CVE-2026-75604 and " + "GHSA-2xp9-vwfh-vxw4" + ) + assert eslint_next_value == next_value, ( + "eslint-config-next must stay on the same reviewed release as Next.js" + ) + assert _exact_version(sharp_value) >= SHARP_SECURITY_FLOOR, ( + "sharp must include the fix for GHSA-rgj7-g3m4-5g8c" + ) + _assert_lock_contract(lock, next_value, eslint_next_value, sharp_value) + + +@pytest.mark.parametrize("field", ["specifier", "version"]) +def test_security_floor_rejects_root_importer_drift(field: str) -> None: + """Reject a partially regenerated lock whose root Next.js importer drifts.""" + + next_value, eslint_next_value, sharp_value, lock = _frontend_security_inputs() + lock["importers"]["."]["dependencies"]["next"][field] = "16.3.2" + + with pytest.raises(AssertionError): + _assert_lock_contract(lock, next_value, eslint_next_value, sharp_value) + + +@pytest.mark.parametrize( + ("section_name", "package_key"), + [("packages", "next@16.3.2"), ("snapshots", "sharp@0.35.3")], +) +def test_security_floor_rejects_every_below_floor_lock_entry( + section_name: str, package_key: str +) -> None: + """Reject any stale vulnerable Next.js or sharp package/snapshot entry.""" + + next_value, eslint_next_value, sharp_value, lock = _frontend_security_inputs() + lock[section_name][package_key] = {} + + with pytest.raises(AssertionError): + _assert_lock_contract(lock, next_value, eslint_next_value, sharp_value) diff --git a/backend/tests/test_opencode_central_config_consumer.py b/backend/tests/test_opencode_central_config_consumer.py new file mode 100644 index 000000000..2e34e8771 --- /dev/null +++ b/backend/tests/test_opencode_central_config_consumer.py @@ -0,0 +1,16 @@ +"""Contract for consuming OpenCode review configuration from the central owner.""" + +from pathlib import Path + + +REPO_ROOT = Path(__file__).resolve().parents[2] + + +def test_required_review_has_no_repository_local_opencode_configuration() -> None: + """Repository-local OpenCode JSON must not fork the central review policy.""" + + for relative_path in ("opencode.json", "opencode.jsonc"): + assert not (REPO_ROOT / relative_path).exists(), ( + "central OpenCode configuration must remain owner-controlled: " + f"{relative_path}" + ) diff --git a/backend/tests/test_release_governance.py b/backend/tests/test_release_governance.py index f33976271..4d879f731 100644 --- a/backend/tests/test_release_governance.py +++ b/backend/tests/test_release_governance.py @@ -601,6 +601,20 @@ def test_scorecard_sarif_normalizer_rejects_escape_links_and_large_input( assert module.main([str(normalizer), str(expected)]) == 65 +def test_agents_entry_points_to_current_product_and_governance_sources() -> None: + agents = read_repo_text("AGENTS.md") + + assert "" in agents + assert "docs/architecture/naruon-product-spec.md" in agents + assert "docs/product-technical-gap-baseline.md" in agents + assert "ContextualWisdomLab/projects/1" in agents + assert "ContextualWisdomLab/naruon/issues/1428" in agents + assert "ContextualWisdomLab/naruon/pull/1436" in agents + assert "docs/agent-github-project-protocol.md" in agents + assert "docs/product-goal-directive.md" in agents + assert "not private agent memory" in agents + + def test_review_automation_uses_central_required_workflows_without_local_copies() -> ( None ): @@ -633,20 +647,14 @@ def test_review_automation_uses_central_required_workflows_without_local_copies( f"central review automation must not be copied locally: {relative_path}" ) - for relative_path in ("opencode.json", "opencode.jsonc"): - assert not (REPO_ROOT / relative_path).exists(), ( - f"central OpenCode configuration must not be copied locally: {relative_path}" - ) - assert "ContextualWisdomLab central required workflows" in normalized_readme assert "This repository does not carry repo-local" in normalized_readme - assert "OpenCode configuration copies" in normalized_readme + assert "OpenCode, Strix, or merge-scheduler workflow copies" in normalized_readme assert ( "branch updates, auto-merge, and mechanical merge actions" in normalized_readme ) assert "central required workflows" in architecture assert "ContextualWisdomLab/.github" in architecture - assert "central `opencode.jsonc`" in architecture assert "central required workflow" in normalized_security assert "openai/openai/gpt-4.1" not in architecture diff --git a/frontend/package.json b/frontend/package.json index 191b7c90b..c86342aa1 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -22,7 +22,7 @@ "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "lucide-react": "^1.27.0", - "next": "16.2.12", + "next": "16.3.4", "react": "19.2.8", "react-dom": "19.2.8", "react-resizable-panels": "^4.12.2", @@ -39,7 +39,7 @@ "@types/react-dom": "^19", "@vitest/coverage-v8": "4.1.10", "eslint": "^9", - "eslint-config-next": "16.2.12", + "eslint-config-next": "16.3.4", "fast-check": "^4.9.0", "jsdom": "^30.0.1", "postcss": "8.5.24", diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 610a0e7ca..add535314 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -7,7 +7,7 @@ settings: overrides: brace-expansion: 5.0.9 postcss: 8.5.24 - sharp: 0.35.0 + sharp: 0.35.4 undici: 8.9.0 pnpmfileChecksum: sha256-RXPq3MmEdRb3xD3rhbER9kciz9nBr/i0J/uMUjql5t0= @@ -38,8 +38,8 @@ importers: specifier: ^1.27.0 version: 1.27.0(react@19.2.8) next: - specifier: 16.2.12 - version: 16.2.12(@babel/core@7.29.7)(@playwright/test@1.62.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + specifier: 16.3.4 + version: 16.3.4(@babel/core@7.29.7)(@playwright/test@1.62.0)(@types/node@26.1.2)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) react: specifier: 19.2.8 version: 19.2.8 @@ -84,8 +84,8 @@ importers: specifier: ^9 version: 9.39.5(jiti@2.7.0) eslint-config-next: - specifier: 16.2.12 - version: 16.2.12(@typescript-eslint/parser@8.65.0(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3))(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3) + specifier: 16.3.4 + version: 16.3.4(@typescript-eslint/parser@8.65.0(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3))(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3) fast-check: specifier: ^4.9.0 version: 4.9.0 @@ -289,6 +289,12 @@ packages: peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + '@eslint-community/eslint-utils@4.9.1': + resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + '@eslint-community/regexpp@4.12.2': resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} @@ -369,160 +375,160 @@ packages: resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==} engines: {node: '>=18'} - '@img/sharp-darwin-arm64@0.35.0': - resolution: {integrity: sha512-ZgaYEwaj+lx/5n4W8GmZ2IYz0PQHjN5eqRcfijWGB+2Aq7ZInZGa0qJyAn6DEtyLuWHRSrmWOqT9q3qqTBvmUQ==} + '@img/sharp-darwin-arm64@0.35.4': + resolution: {integrity: sha512-Uhfl4V4lhP2nbUVF9+hyH1+luj86f1gUFeo8ALYxFoULoU+G87D43BfeMP8XHsk9boxAnCY/bf2EHwhA7MuGsA==} engines: {node: '>=20.9.0'} cpu: [arm64] os: [darwin] - '@img/sharp-darwin-x64@0.35.0': - resolution: {integrity: sha512-c1z9LFpKB0slQW3RchwBE8iSVzGp70TNjUUO9k4BZwwW4HH7JBGHeIy4b+kk4n/kcBASb9evKCE3/7Slmslgiw==} + '@img/sharp-darwin-x64@0.35.4': + resolution: {integrity: sha512-hWniXY3bG5qKpkKrAwPe4y+VTPmf086YQAnkxWh7uA1YrlRouWGa0M0Mxj3ZjnXFkv7/TD1bTy9lGUK26vRvWw==} engines: {node: '>=20.9.0'} cpu: [x64] os: [darwin] - '@img/sharp-freebsd-wasm32@0.35.0': - resolution: {integrity: sha512-Li2KTev0H90kEtnJHkI9xQojXt1AqWmFBMXiPw5kqd1jQgP7gi5HVK/qC5Rmh/59NuAwUuPzzPITmX22NomYYQ==} + '@img/sharp-freebsd-wasm32@0.35.4': + resolution: {integrity: sha512-lIsKw/BU+kjB4eZjxrYrZmwOJYi3Ajrv66iAlBmUPyKc3HpnloevB1g3wxGD9P/5BbQ1brBGl65VRRrCvQDEqA==} engines: {node: '>=20.9.0'} os: [freebsd] - '@img/sharp-libvips-darwin-arm64@1.3.0': - resolution: {integrity: sha512-EKbmBKtyTH+GPFDRw2TgK2oV6hyxxlJVIar4hoTYSNmIwipgMFdxPQqR392GmfdsPGWga0mCFN1cCKjRb9cljw==} + '@img/sharp-libvips-darwin-arm64@1.3.3': + resolution: {integrity: sha512-suTBPTDGrI9WodccaDdwZItTSaBYASlBk1NSfElSHrUfzu3szG6lvIF58+WiFvnfzuK8ZBFS5zE00PxqxnRiPg==} cpu: [arm64] os: [darwin] - '@img/sharp-libvips-darwin-x64@1.3.0': - resolution: {integrity: sha512-Pl2OmOvrJ42adUllESxBsG54PfXLo1OYg9i3c5/5Ln/qJ0gZuTM9YMhQJPIbXqwidLRc/c2zuHt4RsrymmNv7A==} + '@img/sharp-libvips-darwin-x64@1.3.3': + resolution: {integrity: sha512-FVJZ5mITMobmXIz/hPDTw0EintTW5H3WfrxwLqEqjiIihlu+hVRyGrFQ60xl0Lxn7Bt3zdpevPaQi0HEzqz9fw==} cpu: [x64] os: [darwin] - '@img/sharp-libvips-linux-arm64@1.3.0': - resolution: {integrity: sha512-C0SqjoFKnszqa44EQ7xoaT48nnO0lOyXEULfXMWi8krrjOPGYkeK30Okzla6ATbBYsyZ0ySinK0FVkpv3DwzfQ==} + '@img/sharp-libvips-linux-arm64@1.3.3': + resolution: {integrity: sha512-0DaL0A6Xu6sQSQFwe4iVCrKWU2cCTItnRsYsCdxAMm9NF6twAA9BKnoqy4hqz4+azQ0JHuA26qiUKsf1XJ/v5A==} cpu: [arm64] os: [linux] libc: [glibc] - '@img/sharp-libvips-linux-arm@1.3.0': - resolution: {integrity: sha512-A8UpHoUDW4DwnXoV6+q3C1s7QLRAHtPDEjWuNZjwHMyoCNZnm0GeNN8ls9f/bsEYTRQRW96C/n34XJQHJ2fT7A==} + '@img/sharp-libvips-linux-arm@1.3.3': + resolution: {integrity: sha512-3rbU4vqXXc3hY/OiXdl52xZvT0F1yEngWfvqudtPJg/KkyiaQw2DRsFrNzpmLvfavbwOq3qXn36GP8obHRULQA==} cpu: [arm] os: [linux] libc: [glibc] - '@img/sharp-libvips-linux-ppc64@1.3.0': - resolution: {integrity: sha512-WOpkVxAjFd369iaIzEgNRreFD+gWdUMIGD5zplhNKNeqS6mm5dac3q2AFyCBmzYoAdouzZvRBgxy4z8QHZb4/A==} + '@img/sharp-libvips-linux-ppc64@1.3.3': + resolution: {integrity: sha512-cdn1OvUBwsXhbC0zSzJnNzf5MZ/mTrobawDvNXBTxe8VtqKAm0sRuEY2Evzovb/w9JMk4TvRxqt1mekSuJz64w==} cpu: [ppc64] os: [linux] libc: [glibc] - '@img/sharp-libvips-linux-riscv64@1.3.0': - resolution: {integrity: sha512-DRWw0mOHusrCCuw2rqP87oLg6PGlkomVDFqw2hIwsSfwWpu4k3XLcBPaKKl6ct/GtL/cwNkgwjV/tc0Mqht3VA==} + '@img/sharp-libvips-linux-riscv64@1.3.3': + resolution: {integrity: sha512-HjPVx7yKz+0lqdhDlTw1tt90wamBoxhiXpvl1XZpJLiHH4RCJ5yDTqH+VlYPv2fwFs89JFw4c1IexYOcQUi4IQ==} cpu: [riscv64] os: [linux] libc: [glibc] - '@img/sharp-libvips-linux-s390x@1.3.0': - resolution: {integrity: sha512-9APy+nFWhHS+kzLgWZfLcyrUd7YqnAQVa4BPOo4xkoHpdoktOAPG4cEr9+Jpl0TtqfVmcMJimNL5qNTyyOHZNA==} + '@img/sharp-libvips-linux-s390x@1.3.3': + resolution: {integrity: sha512-neWLh+3yCNThxnfy3c4BbVBeGgt9aftno+XbT56iK28RgeDs3UOFWviLWlUu0bArYVYJaFDK+RRohbicUNCm8Q==} cpu: [s390x] os: [linux] libc: [glibc] - '@img/sharp-libvips-linux-x64@1.3.0': - resolution: {integrity: sha512-y9RNUYDe2A1UAdhLyfeOodGRszQdaEoe4nfOpp/sNVPl2CWIcUyFaDoCh4vPLPxu19803j2naLqZup2WxDXCLA==} + '@img/sharp-libvips-linux-x64@1.3.3': + resolution: {integrity: sha512-4vKmvAst9nrowcqquKFAyZJUDolUaIp8uRiN0mWFguJ1IplC9/pitXtlnnlU4aa/eJw3J7i67V+pwUL+wZGdsA==} cpu: [x64] os: [linux] libc: [glibc] - '@img/sharp-libvips-linuxmusl-arm64@1.3.0': - resolution: {integrity: sha512-cC1wkC0Mlucd0KSiGrLkJnB/ZqPvZCntc/Lk7ZnYO5ZSbF2euNek4Xvxafojq+wN1q/W0eprdpUIjUr/EV2PBg==} + '@img/sharp-libvips-linuxmusl-arm64@1.3.3': + resolution: {integrity: sha512-Y9kQaLMuNoB0bPYOOdcZMaseNrFpPodIWWMrx+CZyydf2xn68j9WYc6sWWRrDwNkzCQjKYfc68L7jKjGlHMibw==} cpu: [arm64] os: [linux] libc: [musl] - '@img/sharp-libvips-linuxmusl-x64@1.3.0': - resolution: {integrity: sha512-LiYMhUZicB1QG//+RvmYZpXJO8fYRENfp+MZUCnG9aw+AKvGAy9gPaCnuwsPcBFs8EV66M0NNxj9VHcNklE8zw==} + '@img/sharp-libvips-linuxmusl-x64@1.3.3': + resolution: {integrity: sha512-fj8Mv0HHfD1Rr+4I68+3agJynxDWtBFgicTbSOb9Bke6pIwzGcJ+RX/yHjmiEGFMCavY/dxvem7MyNaJF+wDiw==} cpu: [x64] os: [linux] libc: [musl] - '@img/sharp-linux-arm64@0.35.0': - resolution: {integrity: sha512-4+4XHLNT5wDT0roYlHTEmH9lDKt0acf9Tv+3hM3iceOirkxrR404/3WjAYZ9F9CkHrxeRcGLJXbi4vluMZ9O+A==} + '@img/sharp-linux-arm64@0.35.4': + resolution: {integrity: sha512-De4jpEnAU8Hd5oT0j1G3uL4ZvTuipVMn7YC6vPaJhy6/7EwEae0SVAoBrUMYQbkLGDm85taVWwuPc1a44LTzCQ==} engines: {node: '>=20.9.0'} cpu: [arm64] os: [linux] libc: [glibc] - '@img/sharp-linux-arm@0.35.0': - resolution: {integrity: sha512-VVlpEWwizEFIOom0zdoeKuO5nuTswzVE5uHcBNvHzmeHUpNFajY3HFfbQ+zIH4E2kVaZ/yVxmsShW56TtEy4uA==} + '@img/sharp-linux-arm@0.35.4': + resolution: {integrity: sha512-7OAS8gI0EReKGVN2HssHlM6umJgxF5VI3xN0p9FA91p/YO+ou5hiNghLdZ5BEHztwaaK5+bLKRf8x/o2L2nk9A==} engines: {node: '>=20.9.0'} cpu: [arm] os: [linux] libc: [glibc] - '@img/sharp-linux-ppc64@0.35.0': - resolution: {integrity: sha512-N3hzbEpUTJC8pWpPVJvgzGxM+so/MAXc8O2s/53B0LL9ZGpfXpME7Wizkc5d/8fRBlBtkDjzoZGDCqqNDHqLEw==} + '@img/sharp-linux-ppc64@0.35.4': + resolution: {integrity: sha512-2oYZJeIl4kCcMGk4ouZVjnkCtFrpQFlNEtJ6GbxzhHQchwH0NH/qEb9ykmOl29dqwMq+JhFdZn+1ak2FKhI9fQ==} engines: {node: '>=20.9.0'} cpu: [ppc64] os: [linux] libc: [glibc] - '@img/sharp-linux-riscv64@0.35.0': - resolution: {integrity: sha512-l6vmKVPnbS0RhVMbyxP5meAARsbhCnBN4fy31qz0+3a6Rv4jEqfzDrT89y6ZPkCi0AJGnwp2En528yXo401Hpw==} + '@img/sharp-linux-riscv64@0.35.4': + resolution: {integrity: sha512-cPbNChoRURAWdebDIHSenxRpgEdy7JkPydSnUxRm9VvKD7m0/xVaR/8Fzlu81pk5nHEvHH87UZUA7cTtwnbJSA==} engines: {node: '>=20.9.0'} cpu: [riscv64] os: [linux] libc: [glibc] - '@img/sharp-linux-s390x@0.35.0': - resolution: {integrity: sha512-MYlMiPFiv/EKPAHnp3yNZ9AAWFsxga9c5Bkc6wkar6bqzHLlkGVJHRm0u1ei+VXnZxp3Mz9MG9ZIsI8vSOf3sQ==} + '@img/sharp-linux-s390x@0.35.4': + resolution: {integrity: sha512-RY0JFY8Fd6RonCBtHz+DvadaPkXDSI1AUn6yWL9TipqkZ1vY8w8evqdgyDFnkm4/K1ve1TvZiaePP5oSd4+WVQ==} engines: {node: '>=20.9.0'} cpu: [s390x] os: [linux] libc: [glibc] - '@img/sharp-linux-x64@0.35.0': - resolution: {integrity: sha512-TYaItB5oj1ioXjhyn2xrR208vf+YuIIcHptQWRRaBmFhvIvL9D72DXN8w75xup0KXA8UdEAhQ9Qb2S49FD/9Cw==} + '@img/sharp-linux-x64@0.35.4': + resolution: {integrity: sha512-9qvvEAuk8k89TfWUoX2htWjbAMX8p+NxCppjpcg5k6xMsjhBQPTsoIh36h9Qde4WRuGpJeYnOjdosDn/cnv+OA==} engines: {node: '>=20.9.0'} cpu: [x64] os: [linux] libc: [glibc] - '@img/sharp-linuxmusl-arm64@0.35.0': - resolution: {integrity: sha512-DSTb6ijQzqe6DdAaOBVqJ/SYf1vO8EW5bK6X6LRXufEBebf2722VCdvBUtZ3rtV0x2ApfPNDy/p7LrrjaWjiyQ==} + '@img/sharp-linuxmusl-arm64@0.35.4': + resolution: {integrity: sha512-KB5jxpfWQTr0nc3xdHtWChdbifHrBGsd2SM62Eyxrl8afikm+f5qGBU75SJIZBT/S1MC8XyacdlXBMSWq6OURA==} engines: {node: '>=20.9.0'} cpu: [arm64] os: [linux] libc: [musl] - '@img/sharp-linuxmusl-x64@0.35.0': - resolution: {integrity: sha512-K7ykQ+26Rt6+4BTU80AuGgTPIYX86UxiAKT4rcXX/WNTo7k1ZxpKz+TguHnwVpCqQK3B5PK0vZ0ZBe6nz/ib1w==} + '@img/sharp-linuxmusl-x64@0.35.4': + resolution: {integrity: sha512-f+eZJZIQNEEd26RPSW+76chwOf1XtA2Y/O+5ocVyLliHkeih3e+jhLVBdNTd2rS3IbNXK8+ug93Vf5ZXtF5Lxg==} engines: {node: '>=20.9.0'} cpu: [x64] os: [linux] libc: [musl] - '@img/sharp-wasm32@0.35.0': - resolution: {integrity: sha512-9woLIFORERCr+6cWu87dQ22J34EExkhc73U1kZW0c+RclQqWetoodByp4dWZ/hN8/KVmTRAx2HOnUwib8AwZdA==} + '@img/sharp-wasm32@0.35.4': + resolution: {integrity: sha512-zQnl4Kwp7Q6NHsENtU2T/00Zi+w3AQNwz3+UaTyVBy2FpXrzXzGjndpK61onhZjRtRpQXxCTeqw19bVyXOh7jA==} engines: {node: '>=20.9.0'} - '@img/sharp-webcontainers-wasm32@0.35.0': - resolution: {integrity: sha512-t+kie1TOyaDM6Dho+f+y0VqIUNhYQaKCUahuZVi0E0frgdiaOaPsDxDW3wfKacUdaNBCnK/ZDBMg33ydvHj8uA==} + '@img/sharp-webcontainers-wasm32@0.35.4': + resolution: {integrity: sha512-ESfNkywmCfPNyaZjxooddJQiQ+l/nTpGEOGthxiLnIHXC/CmcBixnfwUleX9mCz9ovrUUvKMap/pm8RYbzfwaA==} engines: {node: '>=20.9.0'} cpu: [wasm32] - '@img/sharp-win32-arm64@0.35.0': - resolution: {integrity: sha512-M5eKxug0dabbaWgFKvPa3odNs2OpaP+81NASfGKkt4GcYXpNhSu7CaeYxWkLNV6vHmUp4hnCxnxrUyhUJhXbKA==} + '@img/sharp-win32-arm64@0.35.4': + resolution: {integrity: sha512-iNdlBX9gLVvqe2I3uIJSIKTq6wckP/DYxZtcqxm09x5Gi24DnFBmPAWZmr60ZyYMG0xlzo6goG3670ar+RXvRw==} engines: {node: '>=20.9.0'} cpu: [arm64] os: [win32] - '@img/sharp-win32-ia32@0.35.0': - resolution: {integrity: sha512-z0+pZ03QCDvdVN0Ez9IX/yjWC19ikMlXrmdYMwYNLTh2BLPx3hXWPvyqWfquZ0BTO9O6GVOjIVoTcyyacMnWlQ==} + '@img/sharp-win32-ia32@0.35.4': + resolution: {integrity: sha512-kqRsbaa5CS6KHlpxnN7WhE6vAAugXyZButpRdvDWetlv6Qv4N9WTcrWzF7tXfB9T7MsoadqdI8hmwLq6UlLvtw==} engines: {node: ^20.9.0} cpu: [ia32] os: [win32] - '@img/sharp-win32-x64@0.35.0': - resolution: {integrity: sha512-feNnlz5ZHKr0MY1LPHvZQyJeBkbo4ctsn0D8FvA53VTw5TC63rfEL2UrWbkSBR19htSE7Mw78xYVwdJqoMWVHw==} + '@img/sharp-win32-x64@0.35.4': + resolution: {integrity: sha512-XtmnYhBcrORsJ4XJngyzr/EWP0hRZLAZRFaApdKuviyqF78+ylxh2y06ZmtULAMOnObJ3ucpN0AcwSWnMowTRg==} engines: {node: '>=20.9.0'} cpu: [x64] os: [win32] @@ -549,60 +555,60 @@ packages: '@emnapi/core': ^1.7.1 '@emnapi/runtime': ^1.7.1 - '@next/env@16.2.12': - resolution: {integrity: sha512-d0Z5Bc13Fa4nR8pFAKx2jay2yhJM16vlfHbTzYnUQAxlNb6B6lmn4hjt69lYNt4kRtyYP6gEM49lPRHNbIyneg==} + '@next/env@16.3.4': + resolution: {integrity: sha512-cjWZnUUa6jZq2kFaNe/ZyJdZonOZ/QoN0Zka2nz/FLOrfx14pQuM9c5RaSVkWMqgdt4ksgPAMWPyHSs/CyV48Q==} - '@next/eslint-plugin-next@16.2.12': - resolution: {integrity: sha512-uF2z/qAK2q7B5/6CpnFcBRX6jOq5iCO+Uqh1UkJhXljX1JwLarLYhhoJadO6dPb6moTprOKewMXheBcbIoSbug==} + '@next/eslint-plugin-next@16.3.4': + resolution: {integrity: sha512-szW9y2Aumu4z88YXfTzcFsgUAg2k64uzbtcO5L9f1AKS4w/GUKJcbFllRflROVyNPgJtGOnvNxiyp3v6b+prIA==} - '@next/swc-darwin-arm64@16.2.12': - resolution: {integrity: sha512-0W1R0teHWJrqKX0FH20IzzIWAOuGtBxPGuObrxy1lE8hQvCFj49KE8a3WUg0D7sq6rn6zkM4c7YGUnhudBS6oA==} + '@next/swc-darwin-arm64@16.3.4': + resolution: {integrity: sha512-iBr3I5LZNk5/bgl5//iTgD2tcym14MX0Xo7fD//u9dYAEgGzza1y9oywluPtf74YnOswVdH1908aK9xVz7zQTw==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@next/swc-darwin-x64@16.2.12': - resolution: {integrity: sha512-Hy5Ls099+aFUmOLmIgPfLqNi6iCwhL3uQCssz5rWk+5Nkc6TUKCE83DY5BbNylfm3+mfwcSFnLRfrZDJhVxdtw==} + '@next/swc-darwin-x64@16.3.4': + resolution: {integrity: sha512-2dpiSyl2Jw/NrBPaU2MAKGSa+2MR82pJIn4Sm5Rjr+gxAeuh0z158Su3Z2O8zn7UNNq+ej4bToed6RcRN/Lydg==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@next/swc-linux-arm64-gnu@16.2.12': - resolution: {integrity: sha512-+YqU2h1cQkHsGfvjAsrSmst8UIFBibBGm5x3Xgel8NLMiDQtNOM4sM2GOEMvG5YiOBNeN/Ykk8cQC2S0Xrqljg==} + '@next/swc-linux-arm64-gnu@16.3.4': + resolution: {integrity: sha512-+t+U8HZT+fApePCS5h89CSH3datz29MkzyfCn+6fpsZBG/oiEOhINcb9rtkv6sdpToLGFn2e6146NzaKCXkqrA==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] libc: [glibc] - '@next/swc-linux-arm64-musl@16.2.12': - resolution: {integrity: sha512-0qjhiYBaKAqF63LA1ZWAAnKTzFUguAaZiRa5etMLGGPj/B6uEVjtIZldIzFEp3wHlB0koK6aTzqPtSdplTCjoA==} + '@next/swc-linux-arm64-musl@16.3.4': + resolution: {integrity: sha512-mx03GNs1ocQA5JQ4FxDMmIsNkdrZh8cuezKCrId28e5/gIPU/l7Kcy2+vmCCzdjnnmXJy+iOAu+7K0QppO6Urg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] libc: [musl] - '@next/swc-linux-x64-gnu@16.2.12': - resolution: {integrity: sha512-7A3q26W+h7gnA15uqBToNuDqBEFZZcqh0mW2mn4AJh/G5pdg2RVE3n4slzLEliASZFG3NmsbEzng/x2Sh09mBg==} + '@next/swc-linux-x64-gnu@16.3.4': + resolution: {integrity: sha512-YIhGY6fSMfha52bnVxnzc9zaVBzJg+cqQTOD8tXIBSx4fuv0pVMxQTE0PaS59YhnMOiYiG09IMwxJAf/CFm/Dw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] libc: [glibc] - '@next/swc-linux-x64-musl@16.2.12': - resolution: {integrity: sha512-qSjL/uppm+cbh21s72Ss8gkiOhQ4dExWHNGOWy6eZV7STj5WsKehgxT61beSsOj+YYQuTplL376lOCdMQU5T8w==} + '@next/swc-linux-x64-musl@16.3.4': + resolution: {integrity: sha512-+eaaX6axpDb0yF1GCpiERe6njplvdC+nks/fKfcHu3XPGRrald8P3/X7yv7QLdjA51knnxwl9pxdIJsg+w1L+Q==} engines: {node: '>= 10'} cpu: [x64] os: [linux] libc: [musl] - '@next/swc-win32-arm64-msvc@16.2.12': - resolution: {integrity: sha512-X6hzsOUJac/e7AWSbn9gQ9nzHld1xWP5iyjHpYWvud8pufB679O1xg4JDyKr8Xd69Jvd+kM2Der6uftiZCmjYA==} + '@next/swc-win32-arm64-msvc@16.3.4': + resolution: {integrity: sha512-0jcXW7Xs/uzICrmgV3MhDYDeRy++1CqnpDIerlPIqYO4bhzB4WNbX/aRnQclustsAyTkFKB0z6rbcjmNg5tR8A==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@next/swc-win32-x64-msvc@16.2.12': - resolution: {integrity: sha512-F6fakeHuFTLOPt0bslQJdf+xtT+WIP9DVn/m4y1w1mRnVPyh3D/cNvzlRkxM444xfm+IvvYNSOrKiA2CDJ0Uxw==} + '@next/swc-win32-x64-msvc@16.3.4': + resolution: {integrity: sha512-vvBzwu1pYQCp92maZCFCIw/XgOTMR5tur9GjakwIo2cmwRTMKajRZZDS9+e4KsUZWKu1E007WUeAFXRRjZeuzw==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -893,8 +899,8 @@ packages: '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} - '@swc/helpers@0.5.15': - resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} + '@swc/helpers@0.5.23': + resolution: {integrity: sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==} '@tailwindcss/node@4.3.3': resolution: {integrity: sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==} @@ -1529,8 +1535,8 @@ packages: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} - eslint-config-next@16.2.12: - resolution: {integrity: sha512-iaaf4vvKo5h2LBdGt0JuRv7t0Ysqr9FMCiFxbptDg8LqOE//mIKR80DdpOnSVM7qjLH3jT8P0aFiwXxBEGZRXw==} + eslint-config-next@16.3.4: + resolution: {integrity: sha512-35/8RM10huEL9vlr8hUZMERMENHBrnyHN3ZZkF9efSgzGaqK34jIqry44A956//zriUhUAUW0XSkcolhrryqAA==} peerDependencies: eslint: '>=9.0.0' typescript: '>=3.3.1' @@ -1622,6 +1628,7 @@ packages: eslint@9.39.5: resolution: {integrity: sha512-DgZS62aPLXKlnxILS/AYCoRvHaZeXceIzlXPkkGGzJWSow1aEk0lbTlxUSlyjC8jcaKxAdOnTDz+o1JFSBsyjw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. hasBin: true peerDependencies: jiti: '*' @@ -2259,8 +2266,8 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - next@16.2.12: - resolution: {integrity: sha512-iD59eYQWmbFcEbX7v/acG5DRym9iw1DdaPoD0WTA920naWsE25wShzJW4+UvAs8MK9EC2kBfIH6vtto1H1PHGw==} + next@16.3.4: + resolution: {integrity: sha512-/Ztf6CeRH+ejEXUrYtqI4gkS66eFIHuSwqi60RgcpWKodxFZx2/dqVCMKBwILfAHXQ+F1b1vAudgj3mnxqtoIA==} engines: {node: '>=20.9.0'} hasBin: true peerDependencies: @@ -2504,9 +2511,14 @@ packages: resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} engines: {node: '>= 0.4'} - sharp@0.35.0: - resolution: {integrity: sha512-BqvG5XbwPZ4NV0DK90d86leEECMsoa8bO0nqnKWlBDYxri4GJ7c4EDInaF6q20lTh/mATmnDIKWJFfXnoVfH5g==} + sharp@0.35.4: + resolution: {integrity: sha512-n++8XWcj+jCOr2IOl7h8LbKnGBDY4aPbmprMONBNFdn0ImXqpGVv5zliDs0V9HbmbCQLpbuo2ej9rAoOQTvMDA==} engines: {node: '>=20.9.0'} + peerDependencies: + '@types/node': '*' + peerDependenciesMeta: + '@types/node': + optional: true shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} @@ -3130,6 +3142,11 @@ snapshots: eslint: 9.39.5(jiti@2.7.0) eslint-visitor-keys: 3.4.3 + '@eslint-community/eslint-utils@4.9.1(eslint@9.39.5(jiti@2.7.0))': + dependencies: + eslint: 9.39.5(jiti@2.7.0) + eslint-visitor-keys: 3.4.3 + '@eslint-community/regexpp@4.12.2': {} '@eslint/config-array@0.21.2': @@ -3209,108 +3226,108 @@ snapshots: '@img/colour@1.1.0': optional: true - '@img/sharp-darwin-arm64@0.35.0': + '@img/sharp-darwin-arm64@0.35.4': optionalDependencies: - '@img/sharp-libvips-darwin-arm64': 1.3.0 + '@img/sharp-libvips-darwin-arm64': 1.3.3 optional: true - '@img/sharp-darwin-x64@0.35.0': + '@img/sharp-darwin-x64@0.35.4': optionalDependencies: - '@img/sharp-libvips-darwin-x64': 1.3.0 + '@img/sharp-libvips-darwin-x64': 1.3.3 optional: true - '@img/sharp-freebsd-wasm32@0.35.0': + '@img/sharp-freebsd-wasm32@0.35.4': dependencies: - '@img/sharp-wasm32': 0.35.0 + '@img/sharp-wasm32': 0.35.4 optional: true - '@img/sharp-libvips-darwin-arm64@1.3.0': + '@img/sharp-libvips-darwin-arm64@1.3.3': optional: true - '@img/sharp-libvips-darwin-x64@1.3.0': + '@img/sharp-libvips-darwin-x64@1.3.3': optional: true - '@img/sharp-libvips-linux-arm64@1.3.0': + '@img/sharp-libvips-linux-arm64@1.3.3': optional: true - '@img/sharp-libvips-linux-arm@1.3.0': + '@img/sharp-libvips-linux-arm@1.3.3': optional: true - '@img/sharp-libvips-linux-ppc64@1.3.0': + '@img/sharp-libvips-linux-ppc64@1.3.3': optional: true - '@img/sharp-libvips-linux-riscv64@1.3.0': + '@img/sharp-libvips-linux-riscv64@1.3.3': optional: true - '@img/sharp-libvips-linux-s390x@1.3.0': + '@img/sharp-libvips-linux-s390x@1.3.3': optional: true - '@img/sharp-libvips-linux-x64@1.3.0': + '@img/sharp-libvips-linux-x64@1.3.3': optional: true - '@img/sharp-libvips-linuxmusl-arm64@1.3.0': + '@img/sharp-libvips-linuxmusl-arm64@1.3.3': optional: true - '@img/sharp-libvips-linuxmusl-x64@1.3.0': + '@img/sharp-libvips-linuxmusl-x64@1.3.3': optional: true - '@img/sharp-linux-arm64@0.35.0': + '@img/sharp-linux-arm64@0.35.4': optionalDependencies: - '@img/sharp-libvips-linux-arm64': 1.3.0 + '@img/sharp-libvips-linux-arm64': 1.3.3 optional: true - '@img/sharp-linux-arm@0.35.0': + '@img/sharp-linux-arm@0.35.4': optionalDependencies: - '@img/sharp-libvips-linux-arm': 1.3.0 + '@img/sharp-libvips-linux-arm': 1.3.3 optional: true - '@img/sharp-linux-ppc64@0.35.0': + '@img/sharp-linux-ppc64@0.35.4': optionalDependencies: - '@img/sharp-libvips-linux-ppc64': 1.3.0 + '@img/sharp-libvips-linux-ppc64': 1.3.3 optional: true - '@img/sharp-linux-riscv64@0.35.0': + '@img/sharp-linux-riscv64@0.35.4': optionalDependencies: - '@img/sharp-libvips-linux-riscv64': 1.3.0 + '@img/sharp-libvips-linux-riscv64': 1.3.3 optional: true - '@img/sharp-linux-s390x@0.35.0': + '@img/sharp-linux-s390x@0.35.4': optionalDependencies: - '@img/sharp-libvips-linux-s390x': 1.3.0 + '@img/sharp-libvips-linux-s390x': 1.3.3 optional: true - '@img/sharp-linux-x64@0.35.0': + '@img/sharp-linux-x64@0.35.4': optionalDependencies: - '@img/sharp-libvips-linux-x64': 1.3.0 + '@img/sharp-libvips-linux-x64': 1.3.3 optional: true - '@img/sharp-linuxmusl-arm64@0.35.0': + '@img/sharp-linuxmusl-arm64@0.35.4': optionalDependencies: - '@img/sharp-libvips-linuxmusl-arm64': 1.3.0 + '@img/sharp-libvips-linuxmusl-arm64': 1.3.3 optional: true - '@img/sharp-linuxmusl-x64@0.35.0': + '@img/sharp-linuxmusl-x64@0.35.4': optionalDependencies: - '@img/sharp-libvips-linuxmusl-x64': 1.3.0 + '@img/sharp-libvips-linuxmusl-x64': 1.3.3 optional: true - '@img/sharp-wasm32@0.35.0': + '@img/sharp-wasm32@0.35.4': dependencies: '@emnapi/runtime': 1.11.3 optional: true - '@img/sharp-webcontainers-wasm32@0.35.0': + '@img/sharp-webcontainers-wasm32@0.35.4': dependencies: - '@img/sharp-wasm32': 0.35.0 + '@img/sharp-wasm32': 0.35.4 optional: true - '@img/sharp-win32-arm64@0.35.0': + '@img/sharp-win32-arm64@0.35.4': optional: true - '@img/sharp-win32-ia32@0.35.0': + '@img/sharp-win32-ia32@0.35.4': optional: true - '@img/sharp-win32-x64@0.35.0': + '@img/sharp-win32-x64@0.35.4': optional: true '@jridgewell/gen-mapping@0.3.13': @@ -3346,34 +3363,37 @@ snapshots: '@tybys/wasm-util': 0.10.3 optional: true - '@next/env@16.2.12': {} + '@next/env@16.3.4': {} - '@next/eslint-plugin-next@16.2.12': + '@next/eslint-plugin-next@16.3.4(eslint@9.39.5(jiti@2.7.0))': dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.5(jiti@2.7.0)) fast-glob: 3.3.1 + transitivePeerDependencies: + - eslint - '@next/swc-darwin-arm64@16.2.12': + '@next/swc-darwin-arm64@16.3.4': optional: true - '@next/swc-darwin-x64@16.2.12': + '@next/swc-darwin-x64@16.3.4': optional: true - '@next/swc-linux-arm64-gnu@16.2.12': + '@next/swc-linux-arm64-gnu@16.3.4': optional: true - '@next/swc-linux-arm64-musl@16.2.12': + '@next/swc-linux-arm64-musl@16.3.4': optional: true - '@next/swc-linux-x64-gnu@16.2.12': + '@next/swc-linux-x64-gnu@16.3.4': optional: true - '@next/swc-linux-x64-musl@16.2.12': + '@next/swc-linux-x64-musl@16.3.4': optional: true - '@next/swc-win32-arm64-msvc@16.2.12': + '@next/swc-win32-arm64-msvc@16.3.4': optional: true - '@next/swc-win32-x64-msvc@16.2.12': + '@next/swc-win32-x64-msvc@16.3.4': optional: true '@nodelib/fs.scandir@2.1.5': @@ -3584,7 +3604,7 @@ snapshots: '@standard-schema/spec@1.1.0': {} - '@swc/helpers@0.5.15': + '@swc/helpers@0.5.23': dependencies: tslib: 2.8.1 @@ -4287,9 +4307,9 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-config-next@16.2.12(@typescript-eslint/parser@8.65.0(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3))(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3): + eslint-config-next@16.3.4(@typescript-eslint/parser@8.65.0(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3))(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3): dependencies: - '@next/eslint-plugin-next': 16.2.12 + '@next/eslint-plugin-next': 16.3.4(eslint@9.39.5(jiti@2.7.0)) eslint: 9.39.5(jiti@2.7.0) eslint-import-resolver-node: 0.3.10 eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.65.0(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3))(eslint@9.39.5(jiti@2.7.0)))(eslint@9.39.5(jiti@2.7.0)) @@ -5055,10 +5075,10 @@ snapshots: natural-compare@1.4.0: {} - next@16.2.12(@babel/core@7.29.7)(@playwright/test@1.62.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8): + next@16.3.4(@babel/core@7.29.7)(@playwright/test@1.62.0)(@types/node@26.1.2)(react-dom@19.2.8(react@19.2.8))(react@19.2.8): dependencies: - '@next/env': 16.2.12 - '@swc/helpers': 0.5.15 + '@next/env': 16.3.4 + '@swc/helpers': 0.5.23 baseline-browser-mapping: 2.11.5 caniuse-lite: 1.0.30001806 postcss: 8.5.24 @@ -5066,18 +5086,19 @@ snapshots: react-dom: 19.2.8(react@19.2.8) styled-jsx: 5.1.6(@babel/core@7.29.7)(react@19.2.8) optionalDependencies: - '@next/swc-darwin-arm64': 16.2.12 - '@next/swc-darwin-x64': 16.2.12 - '@next/swc-linux-arm64-gnu': 16.2.12 - '@next/swc-linux-arm64-musl': 16.2.12 - '@next/swc-linux-x64-gnu': 16.2.12 - '@next/swc-linux-x64-musl': 16.2.12 - '@next/swc-win32-arm64-msvc': 16.2.12 - '@next/swc-win32-x64-msvc': 16.2.12 + '@next/swc-darwin-arm64': 16.3.4 + '@next/swc-darwin-x64': 16.3.4 + '@next/swc-linux-arm64-gnu': 16.3.4 + '@next/swc-linux-arm64-musl': 16.3.4 + '@next/swc-linux-x64-gnu': 16.3.4 + '@next/swc-linux-x64-musl': 16.3.4 + '@next/swc-win32-arm64-msvc': 16.3.4 + '@next/swc-win32-x64-msvc': 16.3.4 '@playwright/test': 1.62.0 - sharp: 0.35.0 + sharp: 0.35.4(@types/node@26.1.2) transitivePeerDependencies: - '@babel/core' + - '@types/node' - babel-plugin-macros node-exports-info@1.6.2: @@ -5338,37 +5359,38 @@ snapshots: es-errors: 1.3.0 es-object-atoms: 1.1.2 - sharp@0.35.0: + sharp@0.35.4(@types/node@26.1.2): dependencies: '@img/colour': 1.1.0 detect-libc: 2.1.2 semver: 7.8.5 optionalDependencies: - '@img/sharp-darwin-arm64': 0.35.0 - '@img/sharp-darwin-x64': 0.35.0 - '@img/sharp-freebsd-wasm32': 0.35.0 - '@img/sharp-libvips-darwin-arm64': 1.3.0 - '@img/sharp-libvips-darwin-x64': 1.3.0 - '@img/sharp-libvips-linux-arm': 1.3.0 - '@img/sharp-libvips-linux-arm64': 1.3.0 - '@img/sharp-libvips-linux-ppc64': 1.3.0 - '@img/sharp-libvips-linux-riscv64': 1.3.0 - '@img/sharp-libvips-linux-s390x': 1.3.0 - '@img/sharp-libvips-linux-x64': 1.3.0 - '@img/sharp-libvips-linuxmusl-arm64': 1.3.0 - '@img/sharp-libvips-linuxmusl-x64': 1.3.0 - '@img/sharp-linux-arm': 0.35.0 - '@img/sharp-linux-arm64': 0.35.0 - '@img/sharp-linux-ppc64': 0.35.0 - '@img/sharp-linux-riscv64': 0.35.0 - '@img/sharp-linux-s390x': 0.35.0 - '@img/sharp-linux-x64': 0.35.0 - '@img/sharp-linuxmusl-arm64': 0.35.0 - '@img/sharp-linuxmusl-x64': 0.35.0 - '@img/sharp-webcontainers-wasm32': 0.35.0 - '@img/sharp-win32-arm64': 0.35.0 - '@img/sharp-win32-ia32': 0.35.0 - '@img/sharp-win32-x64': 0.35.0 + '@img/sharp-darwin-arm64': 0.35.4 + '@img/sharp-darwin-x64': 0.35.4 + '@img/sharp-freebsd-wasm32': 0.35.4 + '@img/sharp-libvips-darwin-arm64': 1.3.3 + '@img/sharp-libvips-darwin-x64': 1.3.3 + '@img/sharp-libvips-linux-arm': 1.3.3 + '@img/sharp-libvips-linux-arm64': 1.3.3 + '@img/sharp-libvips-linux-ppc64': 1.3.3 + '@img/sharp-libvips-linux-riscv64': 1.3.3 + '@img/sharp-libvips-linux-s390x': 1.3.3 + '@img/sharp-libvips-linux-x64': 1.3.3 + '@img/sharp-libvips-linuxmusl-arm64': 1.3.3 + '@img/sharp-libvips-linuxmusl-x64': 1.3.3 + '@img/sharp-linux-arm': 0.35.4 + '@img/sharp-linux-arm64': 0.35.4 + '@img/sharp-linux-ppc64': 0.35.4 + '@img/sharp-linux-riscv64': 0.35.4 + '@img/sharp-linux-s390x': 0.35.4 + '@img/sharp-linux-x64': 0.35.4 + '@img/sharp-linuxmusl-arm64': 0.35.4 + '@img/sharp-linuxmusl-x64': 0.35.4 + '@img/sharp-webcontainers-wasm32': 0.35.4 + '@img/sharp-win32-arm64': 0.35.4 + '@img/sharp-win32-ia32': 0.35.4 + '@img/sharp-win32-x64': 0.35.4 + '@types/node': 26.1.2 optional: true shebang-command@2.0.0: diff --git a/frontend/pnpm-workspace.yaml b/frontend/pnpm-workspace.yaml index d028031d2..784b361c1 100644 --- a/frontend/pnpm-workspace.yaml +++ b/frontend/pnpm-workspace.yaml @@ -16,7 +16,7 @@ supportedArchitectures: overrides: brace-expansion: "5.0.9" postcss: "8.5.24" - sharp: "0.35.0" + sharp: "0.35.4" undici: 8.9.0 patchedDependencies: From 09500e4ed7be4a3d770595c1cbf3bddf1c7bdb2e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 9 Sep 2026 15:19:43 +0900 Subject: [PATCH 3/4] fix(review): keep OpenCode config owner-controlled --- AGENTS.md | 6 ++-- CHANGELOG.md | 2 ++ CLAUDE.md | 3 ++ .../tests/test_agent_llm_authority_docs.py | 30 ------------------- .../test_opencode_central_config_consumer.py | 6 ++++ docs/product-technical-gap-baseline.md | 7 +++++ 6 files changed, 22 insertions(+), 32 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 1ccdd17ea..6766545e0 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -690,8 +690,10 @@ in this repo. `codegraph init -i` autonomously without asking first; keep generated `.codegraph/` and `.cursor/rules/codegraph.mdc` artifacts local unless a future repository policy explicitly says to commit them. OpenCode PR review - uses the project `opencode.jsonc` MCP servers for CodeGraph, DeepWiki, - Context7, and web search. It must initialize CodeGraph before review so + receives the only `opencode.jsonc` from the central required workflow; do not + add repository-local `opencode.json` or `opencode.jsonc`. The central owner + registers review MCP servers, including Graphify. It must initialize + CodeGraph before review so structural findings cite graph-backed evidence instead of relying only on grep or raw file reads; use Context7 for current library docs, DeepWiki for repository documentation, and web search only for bounded external lookups. diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ec84c36f..83cb58dd6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,6 @@ ## [Unreleased] +- 중앙 required workflow가 제공하는 단일 `opencode.jsonc`와 Graphify MCP를 + 소비하도록 저장소 로컬 OpenCode JSON을 제거하고 재도입 방지 계약을 둡니다. - 긴 이메일·첨부 본문을 의미 단위 청크로 임베딩한 뒤 기존 email/attachment 벡터 계약으로 평균화하고, 청크 요청·벡터 누적을 제한된 창으로 처리합니다. OpenAI `text-embedding-3-*`에는 저장 차원(`1536`)을 직접 요청하도록 보강했습니다. 합성 메일 fixture 5건(70청크)과 provider 요청 계약으로 1,536차원 벡터 경로를 검증했으며, 실행 시 선택한 임베딩 제공자에 본문·파싱된 첨부 텍스트를 전송할 수 있습니다. 회사 기밀 데이터는 fixture·commit·PR·log에 포함하지 않습니다. - EmailDetail 테스트가 지원하지 않는 스레드 병합/분리 버튼을 `textContent`뿐 아니라 `aria-label`과 `title` 접근 가능 이름으로도 검출하도록 바꿔, 아이콘 전용 버튼 회귀를 놓치지 않습니다. diff --git a/CLAUDE.md b/CLAUDE.md index 0bfd4f580..b067360d8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -17,6 +17,9 @@ merged by metadata-only robot governance (see `docs/development/merge-gate-policy.md`); OpenCode Review, Strix Security Scan, and the merge scheduler come from central workflows in `ContextualWisdomLab/.github` — do not reintroduce repo-local copies. +That owner also supplies the only `opencode.jsonc` used by required review; +Naruon must not add `opencode.json` or `opencode.jsonc`. Central review MCP +registration, including Graphify, stays owner-controlled. ## Common commands diff --git a/backend/tests/test_agent_llm_authority_docs.py b/backend/tests/test_agent_llm_authority_docs.py index e8dcc9c40..7104302a5 100644 --- a/backend/tests/test_agent_llm_authority_docs.py +++ b/backend/tests/test_agent_llm_authority_docs.py @@ -1,6 +1,5 @@ """Regression contracts for current LLM-routing authority guidance.""" -import json from pathlib import Path @@ -52,32 +51,3 @@ def test_agent_guidance_names_canonical_llm_owner_and_fail_closed_boundary() -> assert "provider discovery" in architecture assert "immutable released" in architecture assert "fails closed" in architecture - - -def test_opencode_config_uses_only_contextual_orchestrator_free() -> None: - """Repository OpenCode model work must use only the canonical logical pool.""" - raw_config = _read("opencode.jsonc") - config = json.loads(raw_config) - - assert config["model"] == "contextual-orchestrator/orchestrator/free" - assert config["small_model"] == "contextual-orchestrator/orchestrator/free" - assert config["enabled_providers"] == ["contextual-orchestrator"] - assert set(config["provider"]) == {"contextual-orchestrator"} - - provider = config["provider"]["contextual-orchestrator"] - assert provider["options"] == { - "baseURL": "{env:CONTEXTUAL_ORCHESTRATOR_BASE_URL}", - "apiKey": "{env:CONTEXTUAL_ORCHESTRATOR_TOKEN}", - } - assert set(provider["models"]) == {"orchestrator/free"} - - forbidden_direct_routing = ( - "github-models", - "STRIX_GITHUB_MODELS_TOKEN", - "https://models.github.ai/inference", - '"openai/gpt-5"', - '"deepseek/deepseek-r1-0528"', - '"deepseek/deepseek-v3-0324"', - ) - for phrase in forbidden_direct_routing: - assert phrase not in raw_config diff --git a/backend/tests/test_opencode_central_config_consumer.py b/backend/tests/test_opencode_central_config_consumer.py index 2e34e8771..32b20a186 100644 --- a/backend/tests/test_opencode_central_config_consumer.py +++ b/backend/tests/test_opencode_central_config_consumer.py @@ -14,3 +14,9 @@ def test_required_review_has_no_repository_local_opencode_configuration() -> Non "central OpenCode configuration must remain owner-controlled: " f"{relative_path}" ) + + agents = (REPO_ROOT / "AGENTS.md").read_text(encoding="utf-8") + claude = (REPO_ROOT / "CLAUDE.md").read_text(encoding="utf-8") + for guidance in (agents, claude): + assert "only `opencode.jsonc`" in guidance + assert "Graphify" in guidance diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index 98bc17d2a..ec2bdfa81 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -6,6 +6,13 @@ **Observed product version:** `0.14.4` **Canonical completion issue:** [#1428](https://github.com/ContextualWisdomLab/naruon/issues/1428) +**Central OpenCode consumer boundary:** Draft PR +[#1625](https://github.com/ContextualWisdomLab/naruon/pull/1625) removes both +repository-local OpenCode JSON names and pins that absence in a focused +contract. Central owner PR [#2052](https://github.com/ContextualWisdomLab/.github/pull/2052) +remains the prerequisite for Graphify and the canonical `opencode.jsonc`; +neither open PR is protected-release or deployment evidence. + **Inventory observation:** the 106-PR open surface below is a fresh live scan captured at `2026-08-25T15:52:01Z`, which returned 106 open PRs after PR #1337 merged into protected `develop` at `2026-08-25T00:10:39Z` and From 9b19ceddf9f305f43e6016cebb46a90a61ad475f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 9 Sep 2026 15:43:41 +0900 Subject: [PATCH 4/4] chore(review): restore canonical release and gap writers --- CHANGELOG.md | 2 -- docs/product-technical-gap-baseline.md | 7 ------- 2 files changed, 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 83cb58dd6..7ec84c36f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,4 @@ ## [Unreleased] -- 중앙 required workflow가 제공하는 단일 `opencode.jsonc`와 Graphify MCP를 - 소비하도록 저장소 로컬 OpenCode JSON을 제거하고 재도입 방지 계약을 둡니다. - 긴 이메일·첨부 본문을 의미 단위 청크로 임베딩한 뒤 기존 email/attachment 벡터 계약으로 평균화하고, 청크 요청·벡터 누적을 제한된 창으로 처리합니다. OpenAI `text-embedding-3-*`에는 저장 차원(`1536`)을 직접 요청하도록 보강했습니다. 합성 메일 fixture 5건(70청크)과 provider 요청 계약으로 1,536차원 벡터 경로를 검증했으며, 실행 시 선택한 임베딩 제공자에 본문·파싱된 첨부 텍스트를 전송할 수 있습니다. 회사 기밀 데이터는 fixture·commit·PR·log에 포함하지 않습니다. - EmailDetail 테스트가 지원하지 않는 스레드 병합/분리 버튼을 `textContent`뿐 아니라 `aria-label`과 `title` 접근 가능 이름으로도 검출하도록 바꿔, 아이콘 전용 버튼 회귀를 놓치지 않습니다. diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index ec2bdfa81..98bc17d2a 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -6,13 +6,6 @@ **Observed product version:** `0.14.4` **Canonical completion issue:** [#1428](https://github.com/ContextualWisdomLab/naruon/issues/1428) -**Central OpenCode consumer boundary:** Draft PR -[#1625](https://github.com/ContextualWisdomLab/naruon/pull/1625) removes both -repository-local OpenCode JSON names and pins that absence in a focused -contract. Central owner PR [#2052](https://github.com/ContextualWisdomLab/.github/pull/2052) -remains the prerequisite for Graphify and the canonical `opencode.jsonc`; -neither open PR is protected-release or deployment evidence. - **Inventory observation:** the 106-PR open surface below is a fresh live scan captured at `2026-08-25T15:52:01Z`, which returned 106 open PRs after PR #1337 merged into protected `develop` at `2026-08-25T00:10:39Z` and