Skip to content

Commit 34b83a2

Browse files
authored
fix(release): restore v1.28.0 release truth (#444)
* fix(release): restore v1.28.0 truth * fix(release): harden candidate truth checks * fix: close release gate coverage and security hygiene * fix: harden release gate correction paths * fix(csp): enforce policy and restore patch coverage * chore(csp): add shared origin source * fix(csp): classify blocked local endpoints * fix(csp): close review gaps in endpoint policy
1 parent f232c3f commit 34b83a2

116 files changed

Lines changed: 1441 additions & 333 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,9 @@ jobs:
171171
- name: Doc metrics drift gate (locale/key counts, stale PLANNED status)
172172
run: pnpm run docs:check
173173

174+
- name: CSP source synchronization and parity
175+
run: pnpm run csp:verify
176+
174177
- name: Native-readiness roadmap policy gate
175178
run: pnpm run native-readiness:check
176179

@@ -183,11 +186,10 @@ jobs:
183186
- name: Typecheck (tsgo)
184187
run: npx tsgo --project tsconfig.tsgo.json --noEmit --checkers 4
185188

186-
# QNBS-v3: Vitest mit --retry=2 für stabile Runs + NODE_OPTIONS für Node 24 Kompatibilität.
187-
# --no-experimental-webstorage deaktiviert native Node 24+ Web Storage API (verhindert localStorage.clear() Fehler).
188-
# Best Practice 2026: Retry-Logik fängt transiente Fehler ab, die bei CI-Runner-Überlastung auftreten können.
189-
- name: Unit tests (Vitest)
190-
run: pnpm exec vitest run --coverage --reporter=json --outputFile=test-results.json --retry=2
189+
# QNBS-v3: no retry — a first-attempt failure remains visible instead of being masked as a pass.
190+
# --no-experimental-webstorage disables Node 24's native Web Storage API for deterministic setup.
191+
- name: Unit tests (Vitest, no retry)
192+
run: pnpm exec vitest run --coverage --reporter=json --outputFile=test-results.json
191193
env:
192194
NODE_OPTIONS: "--no-experimental-webstorage --max-old-space-size=4096"
193195
CI: "true"

AGENTS.md

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,7 @@ hooks are not installed.
294294
repository lint/tests, E2E, coverage, Lighthouse, and mutation testing are cloud-CI work unless
295295
the user explicitly requests a narrowly scoped local run.
296296
- **Vitest watch-mode hard rule:** Never invoke `pnpm test`, `npm run test`, or a bare Vitest wrapper; use an explicit targeted `pnpm exec vitest run <path>` command so constrained hardware never waits on watch mode.
297+
- **First-attempt CI evidence:** The required Vitest job intentionally has no `--retry`; a first-attempt failure remains authoritative. Do not hide a suspected flake with `skip`/`todo` or retries. Temporary quarantine requires a `flaky-test` issue, owner, reproduction evidence, adjacent QNBS-v3 issue/expiry comment, visible CI summary, and removal or explicit renewal within 14 days; see `docs/CI.md`.
297298
- **Heavy tier (CI):** Vitest with coverage thresholds, Playwright E2E (desktop + mobile emulation), Lighthouse CI, Stryker mutation, Storybook static build, bundle budget + analyze.
298299

299300
### Unit Tests (Vitest)
@@ -353,7 +354,7 @@ deploy (main, non-PR) needs: ci-success ──► GitHub Pages
353354
| Job | Purpose |
354355
|-----|---------|
355356
| `security` | `pnpm audit --audit-level=high`, OSV scanner (pnpm + `src-tauri/` + `crates/` Cargo lockfiles), gitleaks secrets scan, dependency review on PRs |
356-
| `quality` | Node 22 + 24 matrix → Biome lint, suppression-debt ratchet, `i18n:check`, `parity:check`, `tsgo --noEmit`, Storybook build, Vitest + coverage, Codecov upload |
357+
| `quality` | Node 22 + 24 matrix → Biome lint, suppression-debt ratchet, `i18n:check`, `docs:check`, `csp:verify`, `parity:check`, `tsgo --noEmit`, Storybook build, Vitest + coverage, Codecov upload |
357358
| `rust-tauri` | `fmt`/`check`/`clippy`/`test` for `src-tauri/`; path-scoped (skips on PRs that don't touch it), needs GTK/WebKit apt-get steps |
358359
| `core-rust` | Same `fmt`/`check`/`clippy`/`test` for `crates/worldscript-project` (renderer-neutral Rust Core); path-scoped, no GUI deps so no apt-get steps needed |
359360
| `build` | Production build, smoke-test prod build in Chromium, bundle budget, rollup analyze artifact; on `main`: SLSA build provenance attestation + Pages artifact |
@@ -407,9 +408,7 @@ Never commit directly to `main` — always a feature branch + PR, even for a sin
407408
## Security Considerations
408409

409410
- **No build-time secrets.** API keys are entered via Settings UI and stored encrypted in IndexedDB (AES-256-GCM via Web Crypto API). Do not put AI keys in `.env` or host environment variables for inference.
410-
- **CSP:**
411-
- Web (`index.html`): `connect-src` includes `https:` scheme-source to support BYOK custom base URLs, plus explicit localhost endpoints for Ollama/LM Studio/local AI and explicit `wss://` signaling endpoints. See ADR-0004 for the web-vs-Tauri rationale.
412-
- Tauri (`src-tauri/tauri.conf.json`): strict `connect-src` with enumerated cloud provider endpoints, no `https:` blanket.
411+
- **CSP:** Web and Tauri use the same explicit `connect-src` origin allowlist from `config/csp-connect-src.json`; arbitrary `https:`, `http:`, and `ws:` scheme wildcards are forbidden. Run `pnpm run csp:sync` followed by `pnpm run csp:check` after changing a provider, local service, or signaling endpoint; CI enforces the non-mutating `pnpm run csp:verify` drift check. Runtime preflight rejects unlisted browser BYOK endpoints with an actionable error.
413412
- **No `dangerouslySetInnerHTML` without DOMPurify.** Biome flags `noDangerouslySetInnerHtml` as error.
414413
- **Never log API keys, IVs, or plaintext payloads.** Use `services/logger.ts` (`createLogger(module)` factory — IDB + Tauri JSONL + DEV console sinks; GDPR `sanitizeLogContext`). `console.log` is blocked by Biome in production paths.
415414
- **Service Worker:** AI hosts are network-only (`public/sw.js`). WASM/ONNX chunks are excluded from precache.

AUDIT.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
**Date:** 2026-04-17 (baseline); **follow-up chain:** … → 2026-05-28 (v1.19.0 — Security/Voice/RTL/Logger B-1..B-8) → **2026-05-30 (B-1 passphrase UX + CI unblock)** → **2026-05-31 (i18n audit + settings features + CI stabilization)** → **2026-05-31 (Edge-AI Perfection Cycle — Phases 0-7 complete)** → **2026-06-01 (Post-crash session: CI stabilisation + 14 CodeAnt AI fixes + E2E hardening)** → **2026-06-02 (Perf Phase 2.3 — pipeline-LRU unification + PR #69 CodeAnt fixes)** → **2026-06-03 (WorkerBus v2 Phase 3 — Rust TaskSupervisor + Tauri-build unblock)** → **2026-06-06 (Phase 3 i18n Expansion — ja/zh/pt/el + Intl APIs)** → **2026-06-09 (v1.21 Deep Audit Correction — Whisper WASM download UI + 3 CodeAnt fixes + CloudSync LWW)** → **2026-06-09 (feat/deep-audit-v1.21 — CSP hardening, zh locale ≤5% EN, coverage Batches A/B/C, VoiceActivityCoordinator B-2 bridge)** → **2026-06-11 (Ultimate Copilot v2 Phase 2+3 — markdown, sidebar, Apply-to-chapter, InlineAnnotation, ProForge chip; PR #110+#111)** → **2026-06-11 (v1.22.0 release — OpenRouter Cloud 5 provider, AI Execution Modes hybrid/cloud/local/eco, AiModeIndicator, SW cache-invalidation fix)** → **2026-06-13/14 (v1.23 perfection batch — OpenRouter + AI-Execution-Mode settings sections localized/modernized, i18n single-brace interpolation bug-class fix + `i18nPlaceholders` regression guard, bundle split + budget tightening PR #130)** → **2026-06-16 (v1.23.0 release — rebrand StoryCraft → WorldScript Studio, local-first data foundation ADR-0008, Tauri blank-screen + asset-URL fixes, AI error taxonomy + retry hardening, command-palette & local-AI settings localization, WorldScript W monogram icons)** → **2026-06-16 (post-release documentation perfection pass — corpus sync, metric reconciliation, history archival, dependabot hardening)** → **2026-06-17 (Language expansion — +6 locales fi/sv/hu/is/eu/fa (RTL); PR #174 merged; `LanguageSelector` exonym localization via `portal.language.names.*`; portal chrome 100 % for the 6 new langs; README/AUDIT/CHANGELOG docs sync)** → **2026-07-28 (v1.24.1 — Local-AI reliability fixes #266, Dependabot backlog triage, Issue #60 audit closeout, security/build hardening)** → **2026-07-29 (v1.24.2, PR #284 — CSP functional-truth, desktop crypto, and doc-truth hardening; the local-inference stack had been silently non-functional in production for two months; merged and tagged)** → **2026-07-30 (v1.24.3, WorkerBus v2 worker-generation consolidation — 5 stacked PRs #286–288/#290–291 + independent #289 recovered after an interrupted merge queue; ADR-0015; merged and tagged)** → **2026-08-12 (encryption lifecycle + desktop reliability + recovery journal — stacked PRs #335/#336/#337 merged into `main`; fail-closed IDB encryption lifecycle, desktop AI/Python provider hardening for #332/#333, durable resumable migration journal with secondary-store adapters; PR #339 closes the write-vs-migration TOCTOU race CodeRabbit and Qodo independently found on #337; PR #310 closed as superseded; Phase-4 production wiring for disable/rotate tracked as issue #338)** → **2026-08-13 (v1.27.0 — Phase 4 encryption disable/rotate production wiring + i18n (#342/#343 close issue #338), Tauri desktop boot-hydration + flush-on-quit + Settings re-render perf fixes (#332, PR #345), AI Writing Studio unreadable-text fix (#341, PR #344), download-progress bytes/speed for voice + WebLLM models (#333 item 1, PR #346); all 5 PRs' correction loops ran to full quiescence — codebase-wide QNBS-v3/i18n fixes, a real data-integrity bug found and fixed in the rekey-recovery try/catch scoping, and sequential main-reconciliation merges across the whole 5-PR stack)** → **2026-08-14 (v1.27.1 — desktop persistence/security stabilization; #363: atomic writes across every Tauri fs-backed store, unified fail-closed desktop API-key routing, factory-reset UI/logic consolidated into `useFactoryReset` + `FactoryResetDangerZone`, packaged-build factory-reset capability-scope fix, `#[cfg(desktop)]`-gated menu-event handler + re-wired native menu-action bridge, `rust-tauri`/`e2e`/`vrt` promoted to required CI gates; plus the nanoid security-advisory patch, PR #362)**
44
**Scope:** Full application, repository configuration, CI/CD, documentation, release validation
5-
**Current version:** **v1.27.1**Desktop persistence/security stabilization (#363: atomic writes, fail-closed key routing, factory-reset consolidation, packaged-build capability fix, Rust/E2E/VRT promoted to required CI gates) plus the nanoid security-advisory patch (#362). **2919 keys × 19 locales**. Historical release sections below remain historical.
5+
**Current version:** **v1.28.0 release candidate**tag pending the dedicated release merge; includes the post-v1.27.1 diagnostics, Core-boundary, CI/Stryker-plumbing, and release-truth work. **6870 Vitest source-declared tests across 564 files · 2923 keys × 19 locales**. Historical release sections below remain historical.
66

77
**Quality gate (2026-08-14 — v1.27.1):** lint ✅ · typecheck ✅ (tsgo) · i18n:check ✅ (**2919 keys × 19 locales**) — verified locally per the CI-cloud-first workflow; full CI suite (Quality Gate Node 22+24, rust-tauri, Build, E2E, E2E Deep Coverage, Storybook, Lighthouse, Visual Regression) tracked on the `release/v1.27.1``main` PR before merge/tag.
88

App.tsx

Lines changed: 16 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ import {
3232
import { Header } from './components/Header';
3333
import { Sidebar } from './components/Sidebar';
3434
import { EncryptionRecoveryModal } from './components/settings/EncryptionRecoveryModal';
35-
import { IdbUnlockModal } from './components/settings/IdbUnlockModal';
35+
import { IdbUnlockModalGate } from './components/settings/IdbUnlockModalGate';
3636
import { Button } from './components/ui/Button';
3737
import { DuckDbMigrationBanner } from './components/ui/DuckDbMigrationBanner';
3838
import { ErrorBoundary } from './components/ui/ErrorBoundary';
@@ -59,6 +59,7 @@ import { projectActions } from './features/project/projectSlice';
5959
import { statusActions } from './features/status/statusSlice';
6060
import { useApp } from './hooks/useApp';
6161
import { useGlobalKeyboardShortcuts } from './hooks/useGlobalKeyboardShortcuts';
62+
import { useIdbUnlockStartupGuard } from './hooks/useIdbUnlockStartupGuard';
6263
import { useNativeNotifications } from './hooks/useNativeNotifications';
6364
import { usePushToTalk } from './hooks/usePushToTalk';
6465
import { useTranslation } from './hooks/useTranslation';
@@ -77,10 +78,7 @@ import {
7778
type EncryptionMigrationJournal,
7879
readEncryptionMigrationJournal,
7980
} from './services/storage/encryptionMigrationJournal';
80-
import {
81-
hasPassphraseSentinel,
82-
isIdbEncryptionReady,
83-
} from './services/storage/storageEncryptionService';
81+
import { isIdbEncryptionReady } from './services/storage/storageEncryptionService';
8482
import { initTauriDeepLink } from './services/tauriDeepLink';
8583
import {
8684
registerTauriMenuHandler,
@@ -382,21 +380,14 @@ const App: FC<AppProps> = ({ isNewUser }) => {
382380
})();
383381
}, []);
384382

385-
// QNBS-v3: B-1 sentinel guard (async) — skips if flag off/unlocked/recovery-pending, auto-disables on a missing sentinel, else shows the unlock modal.
386-
useEffect(() => {
387-
if (!featureFlags.enableIdbAtRestEncryption || isIdbEncryptionReady() || recoveryJournal)
388-
return;
389-
void (async () => {
390-
const hasSentinel = await hasPassphraseSentinel();
391-
if (!hasSentinel) {
392-
dispatch(featureFlagsActions.setEnableIdbAtRestEncryption(false));
393-
return;
394-
}
395-
// QNBS-v3 (CodeAnt #342): re-check via the ref, not the closed-over `recoveryJournal`, since the sibling journal-check effect may have resolved mid-await.
396-
if (recoveryJournalRef.current) return;
397-
setIdbUnlockOpen(true);
398-
})();
399-
}, [featureFlags.enableIdbAtRestEncryption, recoveryJournal, dispatch, setIdbUnlockOpen]);
383+
// QNBS-v3: desktop project files remain plaintext until R-15; the startup guard is web-only.
384+
useIdbUnlockStartupGuard({
385+
isDesktop: desktopPlatform.runtime.isDesktop,
386+
encryptionEnabled: featureFlags.enableIdbAtRestEncryption,
387+
encryptionReady: isIdbEncryptionReady(),
388+
hasRecoveryJournal: Boolean(recoveryJournal),
389+
dispatch,
390+
});
400391

401392
// QNBS-v3: PWA share_target GET params → toast + stash for Writer paste flows; strip query to avoid leaking shared text in URL bar.
402393
useEffect(() => {
@@ -907,11 +898,11 @@ const App: FC<AppProps> = ({ isNewUser }) => {
907898
/>
908899
</ErrorBoundary>
909900
)}
910-
{isIdbUnlockOpen && !recoveryJournal && (
911-
<ErrorBoundary onReset={() => setIdbUnlockOpen(false)}>
912-
<IdbUnlockModal onUnlocked={() => setIdbUnlockOpen(false)} />
913-
</ErrorBoundary>
914-
)}
901+
<IdbUnlockModalGate
902+
isOpen={isIdbUnlockOpen}
903+
encryptionEnabled={featureFlags.enableIdbAtRestEncryption}
904+
hasRecoveryJournal={Boolean(recoveryJournal)}
905+
/>
915906
</div>
916907
</AppContext.Provider>
917908
</ToastProvider>

CHANGELOG.md

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
<!-- release-candidate: v1.28.0 — the tag is created only after the release PR merges. -->
11+
12+
## [1.28.0] — 2026-08-21
13+
1014
### Added
1115

1216
- **Cloud model catalog defense-in-depth:** current Anthropic, OpenAI, and xAI model IDs now
1317
share one typed catalog across settings, fallbacks, and the Claude proxy allowlist.
1418
- **Native bundle and translation-quality floors:** bundle budgets now distinguish entry, vendor,
1519
regular JS, and WASM assets; CI also enforces the existing translation coverage/outlier floor.
20+
- **Renderer-neutral Rust Core seed:** `crates/worldscript-project` now provides headless project
21+
schema, validation, migration, plain JSON I/O, and a test/CLI harness, with one narrow Tauri
22+
validation command wired through the cross-workspace path dependency.
23+
- **Rust TaskSupervisor bounded text proof:** adds bounded renderer-neutral `text.analyze` and
24+
`text.diff` tasks, typed qualification wrappers, deterministic LCS/whitespace contracts, and
25+
explicit fallback to the existing TypeScript path; no production caller is switched yet.
1626

1727
### Fixed
1828

@@ -21,23 +31,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2131
tagless or shallow checkouts as a false failure.
2232
- **Spotlight tour theme:** the post-rebrand driver.js popover now binds to its WorldScript CSS
2333
selector, with a regression test protecting the binding.
34+
- **Desktop encryption scope truth:** desktop project files remain explicitly documented as
35+
plaintext until the renderer-neutral R-15 storage work ships; the IndexedDB passphrase flow no
36+
longer presents itself as a project-file encryption gate on Tauri.
37+
- **CI authority closure:** Core path changes now select the Tauri consumer gate, workflow-policy tests protect local path-dependency coverage and aggregate deployment gating, and Pages deployment waits for `ci-success`.
38+
- **Accessibility contrast:** command-palette heading contrast was corrected to meet the intended
39+
WCAG threshold.
2440

2541
### Deprecated
2642

2743
- **`storycraft://` deep links:** the legacy scheme remains accepted for this release and displays
2844
a migration notice; it is scheduled for removal in the next release.
2945

30-
- **Renderer-neutral Rust Core seed:** `crates/worldscript-project` now provides headless project
31-
schema, validation, migration, plain JSON I/O, and a test/CLI harness, with one narrow Tauri
32-
validation command wired through the cross-workspace path dependency.
46+
### Changed
47+
48+
- **CSP egress parity:** Web/PWA and Tauri now use one explicit provider, local-service, and Yjs
49+
signaling origin allowlist; arbitrary HTTPS egress and the contradictory loopback upgrade policy
50+
are no longer accepted. New BYOK endpoints require an explicit CSP policy update.
3351
- **Qt Early Killer-Gate qualification:** the native roadmap now requires cheap, evidence-backed
3452
lifecycle, accessibility/input, packaging/update-trust, crash/recovery, and security checks
3553
before substantial Qt UI work can create sunk cost.
36-
- **Rust TaskSupervisor bounded text proof:** adds bounded renderer-neutral `text.analyze` and
37-
`text.diff` tasks, typed qualification wrappers, deterministic LCS/whitespace contracts, and
38-
explicit fallback to the existing TypeScript path; no production caller is switched yet.
39-
40-
### Changed
4154

4255
- **Diagnostics sink boundary:** structured log construction and recursive redaction now remain
4356
portable before serialized IDB, Tauri JSONL, and development-console adapters receive the record;
@@ -55,17 +68,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
5568
versions before native execution (#437, #439, #440).
5669
- **Native maturity evidence:** the G1 qualification ledger now records the partial, evidence-backed
5770
status of the native migration without claiming a production authority switch (#438).
58-
59-
### Fixed
60-
61-
- **CI authority closure:** Core path changes now select the Tauri consumer gate, workflow-policy tests protect local path-dependency coverage and aggregate deployment gating, and Pages deployment waits for `ci-success`.
62-
- **Accessibility contrast:** command-palette heading contrast was corrected to meet the intended
63-
WCAG threshold.
71+
- **Mutation-test plumbing:** the manual Stryker workflow now uses explicit per-module incremental
72+
files, preserves module report identity, validates aggregate metrics, fails closed on missing
73+
shards, and propagates aggregation failures through the summary pipeline.
74+
- **Quality-gate truth:** Vitest CI no longer retries the suite, the suppression ratchet was
75+
reduced from 52 to 48 through real test-mock cleanup, and docs checks now validate README test
76+
file/case metrics against the current Vitest source set.
6477

6578
### Security
6679

6780
- **CI supply-chain policy:** CodeQL action references and token permissions were hardened and
6881
Dependabot grouping was corrected so related action paths move together.
82+
- **Diagnostics redaction:** sensitive nested diagnostic context is recursively redacted before
83+
serialized IDB, Tauri JSONL, or development-console sinks receive it.
6984

7085
## [1.27.1] — 2026-08-14
7186

0 commit comments

Comments
 (0)