From ad2679dc24299e420f2c157198e95584bf674838 Mon Sep 17 00:00:00 2001 From: qnbs <155236708+qnbs@users.noreply.github.com> Date: Wed, 9 Sep 2026 04:17:12 +0200 Subject: [PATCH 01/15] fix(docs): upgrade CHANGELOG completeness check, backfill Unreleased MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit audit F-2: scanUnreleasedTruth's own gate fired only when [Unreleased] was entirely empty, so a single unrelated doc-sync bullet satisfied "meaningful content" forever — 13 real feat/fix commits since v1.28.4 went completely undocumented, and the gate never noticed. - scanUnreleasedTruth now requires every post-tag feat|fix|perf commit to be individually referenced in [Unreleased], either by its trailing PR number (the mid-subject issue ref some commits also carry, e.g. "(#553) (#621)", is correctly ignored in favor of the true trailing PR number) or by a bounded subject-slug match (>=60% of its most identifying words) for the few commits merged without one. Missing commits are named in the failure output. - Backfilled CHANGELOG.md's [Unreleased] with all 13 currently- undocumented commits since v1.28.4 (project schema-version classification Slices A/B, the canonical document-projection foundation, the legacy-to-v1 admission primitive, filesystem- admission convergence, i18n startup-copy fixes, PR-size governance hardening, the attribution guard, and this session's own PR-1 security-doc fix). - Pulled TODO.md's Current Sprint forward: the 5 landed ledger-row-9 PRs are now named, still correctly marked not-complete (no target release invented — none exists yet). - 6 new regression tests for the completeness logic; README test-count resynced. --- CHANGELOG.md | 56 +++++++++++++++++++ README.md | 8 +-- TODO.md | 18 +++++-- scripts/check-doc-metrics.mjs | 87 +++++++++++++++++++++++++++--- tests/unit/checkDocMetrics.test.ts | 69 ++++++++++++++++++++++++ 5 files changed, 225 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9036b59cd..12fa4d6ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,62 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- **Project schema-version classification (Slice A, #553):** `PROJECT_SCHEMA_V1` as a fresh + production version marker, disjoint classification + (`LEGACY_UNVERSIONED`/`SUPPORTED_OLDER`/`UNSUPPORTED_OLDER`/`CURRENT`/`FUTURE`/`MALFORMED`), + raw/header parsing before typed parsing so a `FUTURE` document with a breaking shape still + classifies `FUTURE` rather than `MALFORMED`, and the `LEGACY_TO_V1` in-memory admission + primitive. PR #618. +- **Schema-version classification observed on IDB project load (Slice B, 1/N, #553):** the first + ingress path wired to Slice A's classifiers, observation-only (classify and log, never alter + load behavior, never throw), matching the established Core shadow-validation pattern. PR #619. +- **Canonical document projection foundation:** establishes the canonical parser/import/admission + boundary that retains the original JSON text alongside a bounded typed projection, the + foundation Slice C's admission primitive builds on. +- **Explicit legacy-to-v1 admission primitive (#653):** the non-destructive `LEGACY_TO_V1` + in-memory admission path recognizes, fully validates, losslessly overlays the V1 version marker, + and revalidates the destination. PR #653. +- **Filesystem admission converged after #654 (#658):** carries forward canonical filesystem + admission and closes legacy editable-admission authority gaps. PR #658. + +### Fixed + +- **`schemaVersion` now enforces raw integer grammar (#553):** rejects rounded fractional schema + versions and non-number schema-version tokens instead of silently coercing them. PR #621. +- **Canonical document projection now reuses its own parsed input** instead of re-parsing, + removing a redundant-parse divergence risk between the raw and typed projections. +- **Projection failure now retains the raw header verdict** instead of discarding the + already-classified version header when the typed projection itself fails. +- **Unsupported-project startup copy added** alongside the existing migration-gap copy, so a + project whose schema version this build cannot open at all gets its own distinct startup + message rather than reusing the migration-gap wording. +- **Migration-gap startup copy now distinguished from the unsupported-project case (#656):** the + two failure classes previously shared wording; each now gets copy specific to its actual cause. + PR #656. +- **PR-size exception governance hardened (#657):** closes control gaps in how per-PR size-ceiling + exceptions are recorded and validated. PR #657. +- **Security docs no longer cite closed PR #356 as active desktop-encryption remediation:** + `docs/SECURITY-THREAT-MODEL.md` and `docs/IDB-ENCRYPTION.md` both asserted PR #356 was the + active/pending remediation for desktop plaintext storage after it was closed as superseded on + 2026-08-18. Rewritten to anchor on the living Ledger-row-9/R-15 state instead of a PR number, and + `check-doc-metrics.mjs` now mechanically rejects an unqualified live/pending-remediation claim + tied to a bare PR number in these two files. PR #673. +- **CHANGELOG completeness-check upgrade, backfill Unreleased (audit F-2):** `scanUnreleasedTruth` + previously accepted any non-empty `[Unreleased]` section forever, so a single unrelated bullet + let arbitrarily many later `feat`/`fix`/`perf` commits go completely undocumented — 13 real + commits since `v1.28.4` had gone unrecorded. It now requires every governed commit to be + individually referenced by PR number or subject slug, naming any that aren't; this section was + backfilled with all 13 currently-undocumented entries above. + +### Security + +- **AI/session attribution now rejected in commits and PRs:** a fail-closed guard + (`check-commit-attribution.mjs`, a commit-msg hook, a pre-push scan of outgoing commits, and a + CI check on the commit range and PR title/body) blocks Claude/Anthropic attribution trailers, + session URLs, and generated-by footers before they can land in repository history. PR #672. + ### Documentation - **Post-release v1.28.4 truth sync:** removed the now-stale release-candidate markers from diff --git a/README.md b/README.md index 381806a19..11eb7a6fb 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ IndexedDB v8 PWA v3.0 i18n 19 locales — 2942 keys - 7593+ tests / 603 files + 7599+ tests / 603 files Codecov Coverage License MIT CI Status @@ -511,7 +511,7 @@ The Settings → AI panel shows a live GPU status badge with adapter details and | **Document Export** | docx + jszip | Word-compatible `.docx` generation (lazy-loaded) | | **PWA** | Service Worker + Web App Manifest v3 | Offline support, installability, Workbox chunking | | **i18n** | Custom React Context (`I18nContext.tsx`) | 2942 keys × 19 locales (de/en/es/fr/it + ar/he/fa RTL Beta + ja/zh/pt/el/fi/sv/hu/is/eu/ru/ko Beta); EN fallback; `localStorage` persistence | -| **Testing** | Vitest 4.x (7593+ tests / 603 files) + Playwright E2E | Unit/integration + cross-browser E2E; Stryker mutation (manual workflow) | +| **Testing** | Vitest 4.x (7599+ tests / 603 files) + Playwright E2E | Unit/integration + cross-browser E2E; Stryker mutation (manual workflow) | | **Code Quality** | Biome (lint + format) + TypeScript 7 (tsgo) strict | `--error-on-warnings` in CI; zero `any` policy | | **Visualization** | Force-directed graph | Interactive character relationship network | | **Desktop** | Tauri v2 | Cross-platform installer; auto-updater via `latest.json` | @@ -549,7 +549,7 @@ WorldScript-Studio/ │ ├── sw.js # PWA Service Worker │ └── manifest.json # PWA Web App Manifest v3 ├── tests/ -│ ├── unit/ # Vitest unit tests (7593+ tests, 603 files) — count spans tests/, components/, packages/*/tests/, not just this folder +│ ├── unit/ # Vitest unit tests (7599+ tests, 603 files) — count spans tests/, components/, packages/*/tests/, not just this folder │ │ ├── ai/ # aiSmallModules, aiCoreFallbackPaths │ │ └── settings/ # WebLlmPanel, AiSections │ └── e2e/ # Playwright specs + helpers.ts @@ -714,7 +714,7 @@ The main pipeline is [`.github/workflows/ci.yml`](.github/workflows/ci.yml). Opt Raw bundle-budget ceilings (KB per uncompressed asset): entry **2500 KB**, vendor **6200 KB**, other JavaScript **2500 KB**, and WASM **30000 KB**. **Current test metrics (2026-09-07, source-synchronized; CI remains authoritative for pass/fail):** -- **7593+ unit tests** across **603 test files** — CI is authoritative for pass/fail +- **7599+ unit tests** across **603 test files** — CI is authoritative for pass/fail - Coverage thresholds: lines ≥ 80 · branches ≥ 66 · functions ≥ 72 · statements ≥ 78 — enforced in CI (see Codecov badge for live metrics) - i18n: **2942 keys × 19 locales** (en/de/fr/es/it + ar/he/fa RTL Beta + ja/zh/pt/el/fi/sv/hu/is/eu/ru/ko Beta) diff --git a/TODO.md b/TODO.md index be2beae0b..0d12e63ff 100644 --- a/TODO.md +++ b/TODO.md @@ -35,9 +35,21 @@ Status: 🔄 in progress | ⬜ open | ✅ done `TODO.md`/`README.md` reconciled, `v1.28.4` tag and GitHub Release published with all expected Desktop artifacts; post-merge main CI and CodeQL green (CodeQL has no tag trigger), tag-triggered CI/Tauri/Docker all green, and `AUDIT.md` updated with real evidence. -- ⬜ Close the outstanding Wave 2 prerequisite (ledger row 9: project state-shape compatibility - adapter) — currently in progress, not complete. Wave 3/4 R-15 implementation stays blocked - (`S5_IMPLEMENTATION_READY=NO`) until this and `S5_TERMINAL=YES` are both true. +- ✅ Attribution-hygiene guard merged (PR #672): fail-closed rejection of AI/session attribution + trailers and footers in commits, tags, and PR titles/bodies (commit-msg hook, pre-push scan, CI). +- ✅ Post-audit truth/governance remediation PR #673 merged: stale `#356`-active-remediation + citations in `docs/SECURITY-THREAT-MODEL.md`/`docs/IDB-ENCRYPTION.md` rewritten to anchor on the + living ledger state, with a mechanical `check-doc-metrics.mjs` guard against recurrence (first + slice of the 2026-09-08 external audit's S1–S10 remediation sequence; further slices in progress). +- 🔄 Ledger row 9 (project state-shape compatibility adapter) — five `feat`/`fix` PRs landed + (#618 Slice A persisted schema-version classification, #619 Slice B IDB-load observation, #621 + raw-integer-grammar hardening, #653 the non-destructive `LEGACY_TO_V1` admission primitive, #658 + filesystem-admission convergence) but **still not complete**: durable source-generation fencing, + writer integration, and universal ingress/egress remain open per + `docs/native/CORE-MIGRATION-LEDGER.md` row 9. Wave 3/4 R-15 implementation stays blocked + (`S5_IMPLEMENTATION_READY=NO`, recorded on row 10) until row 9 and `S5_TERMINAL=YES` are both + true — no target release is set for that yet (tracked, not invented; see the S1–S10 remediation + plan's exit-criterion work). - ⬜ #614 (narrow concurrent-first-install multi-tab race, requires cross-tab coordination) and #532 (WelcomePortal E2E entry nondeterminism root cause) remain open, tracked separately — not part of this sprint unless they directly block release or R-15 work. diff --git a/scripts/check-doc-metrics.mjs b/scripts/check-doc-metrics.mjs index 7bd124321..77dd541e2 100644 --- a/scripts/check-doc-metrics.mjs +++ b/scripts/check-doc-metrics.mjs @@ -330,19 +330,84 @@ export function scanReadmeReleaseTruth(readme, taggedVersions) { return findings; } -function hasMeaningfulUnreleasedContent(changelog) { +// QNBS-v3 (audit F-2): shared by the presence check below and the completeness check further +// down — both need the raw [Unreleased] section text with HTML comments stripped. +function getUnreleasedSectionText(changelog) { const heading = /^## \[Unreleased\]\s*$/m.exec(changelog); - if (!heading) return false; + if (!heading) return ''; const afterHeading = changelog.slice(heading.index + heading[0].length); const nextHeading = afterHeading.search(/^##\s/m); const section = nextHeading === -1 ? afterHeading : afterHeading.slice(0, nextHeading); - const withoutHtmlComments = section.replace(/|$)/g, ''); - return withoutHtmlComments.split('\n').some((line) => { + return section.replace(/|$)/g, ''); +} + +function hasMeaningfulUnreleasedContent(changelog) { + const section = getUnreleasedSectionText(changelog); + return section.split('\n').some((line) => { const trimmed = line.trim(); return trimmed.length > 0 && !trimmed.startsWith('