Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

- **Docs truth: R-15 gate status is now checked automatically (#933):** the storage contract carries
one machine-readable status line per encryption gate, and the documentation check fails when the
migration ledger disagrees with it or when current text calls an already-implemented gate "not
admitted", a contradiction reviewers had to catch by hand in four consecutive pull requests. The
README test-metrics synchronizer also covers one more line it previously missed. PR #934.
- **R-15 Gate 3, slice 3A — durable staging and promotion (#445):** the protected-storage core can
now write a new encrypted version of a record next to the previous one: it seals the record in
memory, writes only the encrypted bytes to a temporary staging file, flushes it to disk, checks
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
<img src="https://img.shields.io/badge/TypeScript-tsgo_native_preview-3178C6?logo=typescript&logoColor=white" alt="TypeScript native preview (tsgo)">
<img src="https://img.shields.io/badge/Desktop-Tauri_2-FFC131?logo=tauri&logoColor=black" alt="Tauri 2">
<img src="https://img.shields.io/badge/i18n-19_locales-2947_keys-0EA5E9" alt="19 locales — 2947 keys">
<img src="https://img.shields.io/badge/Tests-8504%2B_%2F_632_files-22C55E" alt="8504+ tests / 632 files">
<img src="https://img.shields.io/badge/Tests-8523%2B_%2F_633_files-22C55E" alt="8523+ tests / 633 files">
<img src="https://img.shields.io/codecov/c/github/qnbs/WorldScript-Studio?logo=codecov&label=Coverage" alt="Codecov Coverage">
<img src="https://img.shields.io/github/actions/workflow/status/qnbs/WorldScript-Studio/.github/workflows/ci.yml?branch=main&logo=github&label=CI" alt="CI status">
<img src="https://img.shields.io/badge/License-MIT-22C55E" alt="MIT License">
Expand Down Expand Up @@ -809,7 +809,7 @@ The normal web PR pipeline is not the complete native release qualification surf
| DOCX | `docx` + JSZip | Word-compatible export |
| PWA | Service Worker + Web App Manifest | Offline shell/installability |
| i18n | Custom React i18n context | 2947 keys × 19 locales |
| Testing | Vitest 5.x (8504+ tests / 632 files) + Playwright | Unit/integration/E2E |
| Testing | Vitest 5.x (8523+ tests / 633 files) + Playwright | Unit/integration/E2E |
| Quality | Biome + tsgo + CodeQL/security tooling | Static and CI gates |
| Desktop | Tauri 2 | Current native shell |

Expand Down Expand Up @@ -886,7 +886,7 @@ WorldScript-Studio/
├── locales/ # Source locale trees
├── public/ # PWA assets, manifest, SW, runtime locale bundles
├── tests/
│ ├── unit/ # Vitest unit tests (the 8504+ total also includes components/ and packages/*/tests; tests/e2e is excluded)
│ ├── unit/ # Vitest unit tests (the 8523+ total also includes components/ and packages/*/tests; tests/e2e is excluded)
│ └── e2e/ # Playwright
├── docs/ # Canonical product/engineering documentation + ADRs
├── src-tauri/ # Tauri v2 desktop shell / Rust
Expand Down Expand Up @@ -1031,7 +1031,7 @@ Raw bundle-budget ceilings (KB per uncompressed asset): entry **2500 KB**, vendo

Current source-synchronized README metrics:

- **8504+ unit tests** across **632 test files**
- **8523+ unit tests** across **633 test files**
- i18n: **2947 keys × 19 locales**

CI remains authoritative for actual pass/fail and live coverage.
Expand Down
2 changes: 1 addition & 1 deletion docs/native/CORE-MIGRATION-LEDGER.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ scope shifts — it is a living decision record, not a one-time snapshot.
| 7 | `features/project/` domain logic | TS, `features/project/` (24 files, 2,114 lines) — real logic concentrated in `thunks/` + `projectSelectors.ts` (~450-500 lines); `reducers/` (11 files) is CRUD bookkeeping | High — Redux-store-shape/dispatch bound; `reducers/` stays TS-side permanently | Low | Medium (import/restore orchestration) | Low-medium | Medium (only the thunks/selectors subset) | Deferred | Candidate after the schema crate is proven; only thunks/selectors, never `reducers/` | Not started |
| 8 | AI services | TS, `services/ai/` (44 files, 5,401 lines), mixed portability (retry/routing/error-taxonomy renderer-neutral vs. `computeShaderFactory.ts`/`webGpuDetectorService.ts`/`.wgsl` inherently WebGPU-coupled) | Mixed | Medium-high (API keys) | Low-medium | Medium | Uncertain — too large/mixed to assess narrowly | **Out of scope for all of Wave 2** | None proposed | None |
| 9 | Project state-shape compatibility adapter | TS, `features/project/coreBoundaryAdapter.ts` at the Core boundary + Rust, `crates/worldscript-project` schema | High at the boundary — production Redux `EntityState` must be translated without importing Redux into Core | Low | High — ID/order preservation is part of project identity | Medium | High — every native renderer needs the same conversion contract | **2 — Wave 2 prerequisite before G1 evaluation** | **Current-production #553 closure complete; Rust Core authority switch not started (#836).** `IMPLEMENTATION_STARTED = YES`. Every current-production Project path is canonical and no-loss according to backend semantics (#553, PRs #773–#849): textual raw carrier and lexical tokens on the filesystem, structured-value semantics in IndexedDB. Persistence and admission: shared TS/Rust classification including the raw-token grammar; `LEGACY_TO_V1` admitted in memory and migrated durably on both backends (IndexedDB authority; filesystem under the project lock with a pre-migration snapshot, #849); the generation-fenced canonical IDB authority for web/PWA autosave, flush and manual save; the desktop filesystem writer as a preserve-first raw-carrier writeback under the project lock with a generation/incarnation fence. Egress: export and library backup (projects and snapshots), stripped to portable form. Snapshots: creation and restore, each admitted, with an exact restore carrier. Import: every modeled field admitted and projected, then an admitted-raw first save (#842, #848); a `null` tension score is admitted so the project remains loadable, omitted from the typed editor projection, and preserved in the canonical raw carrier. Export: every JSON surface, including Advanced import/export, through the canonical egress (#847). Replacement and authority: same-ID replacement writes a fresh canonical document, with carriers bound to target, epoch and authority; the desktop fails closed when filesystem storage is unavailable, with no IndexedDB fallback; an unloadable browser record is refused and kept. The closure guarantees no silent loss or replacement of stored data; it does not promise that every malformed shape boots into the editor (malformed manuscript-section hardening is #845). TypeScript remains the production Project authority; the renderer-neutral Rust Core authority switch is separate future work (#836). Normalizes array or Redux `EntityState` to renderer-neutral arrays and reconstructs the TS-side shape only at the integration boundary. The Rust verdict remains partial because unknown fields are not rejected (Rust is observation-only until #836); within the current TypeScript authority, every current-production ingress, writer, migration and egress preserves the authoritative canonical carrier according to backend semantics — the textual raw carrier and its lexical tokens where textual authority exists (filesystem), the stored structured value in IndexedDB (canonically serialized where text is needed, with no claim that original JSON text survives). Both required decisions (persisted version authority; a field-class-staged unknown-field policy, not one global policy) are resolved and maintainer-admitted in [`docs/native/PROJECT-CORE-COMPATIBILITY-CONTRACT.md`](PROJECT-CORE-COMPATIBILITY-CONTRACT.md), `PROPOSED = YES` / `ADMITTED = YES`. Issue #553's current-production implementation of that contract is complete; its terminal acceptance is QNB-99 (pending); the authority switch it gates is #836. | `tests/unit/features/project/coreBoundaryAdapter.test.ts` covers array and `EntityState` inputs, round-trip ID/order preservation, and rejection of duplicate IDs, missing references, and orphaned entities for both characters and worlds; `tests/unit/features/project/projectSchemaVersion.test.ts` and `crates/worldscript-project/tests/version_test.rs` cover classification/parity; the IDB load observation is covered by `tests/unit/services/storage/idbProjectStoreLoadStateObservation.test.ts`; the canonical parser/import/admission foundation is covered by `tests/unit/projectDocument.test.ts` and `tests/unit/projectImportSchema.test.ts`; the writeback overlay/verify/fence primitive by `tests/unit/services/projectDocumentWriteback.test.ts`; the IDB canonical admission/durable-commit boundary (against real fake-indexeddb, including a generation-conflict rejection, a §2.7 downgrade-contradiction, and an encrypted round trip) by `tests/unit/services/storage/idbProjectCanonicalAuthority.test.ts`; production routing/refusal-success coverage by `tests/unit/services/projectAutosavePersistence.test.ts`, `tests/unit/persistedStateFlush.test.ts`, and the listener/shortcut tests; the envelope fixture is accepted by Rust after migration and validation; the #553 current-production closure (a1–a11) by `tests/unit/services/projectCanonicalEgress.test.ts`, `tests/unit/libraryBackupService.test.ts`, `tests/unit/services/fs/fsStores.test.ts`, `tests/unit/services/projectAutosaveCanonicalWriter.test.ts`, `tests/unit/services/projectImportCarrier.test.ts`, `tests/unit/storageServiceDesktopAuthority.test.ts` and `tests/unit/malformedProjectBoot.test.ts` |
| 10 | R-15 protected desktop storage contract | **Contract `docs/native/R15-SECURE-STORAGE-CONTRACT.md` + headless Rust implementation (Gates 1a/1b/2) in `crates/worldscript-secure-storage`, not production authority**; current desktop records remain TS/Tauri filesystem authority | High — future Core must serve Tauri and Qt without renderer-private crypto semantics | High | High — durability, migration, and identity binding protect user data | High | **Highest — cross-renderer security/durability contract** | **3 — S5-A, S5-B1, S5-B2, and S5-B3 all admitted; final cross-contract audit complete, S5_TERMINAL=YES (PR #584 merged `c24aa645`, post-merge CI/CD + CodeQL green); Gate 1a re-admitted by QNB-100 (2026-09-26) and implemented headless in `crates/worldscript-secure-storage`; Gate 1b decided 2026-09-26 (Option C: platform secure store primary, optional `WSS_ARGON2ID_V1` passphrase recovery); 1b-core landed (#850); 1b-platform delivered as small sequential slices — §8.2.2 item layout (#854), durable authority (#855), runtime key handles (#914), anchor transitions + `KeyProvider` (#915), OS secure-store adapter (§8.2.5, #916; evidence Linux `CI_ONLY`/`LOCAL_ONLY`, macOS/Windows `CI_ONLY`, no packaged evidence); Gate 2 (typed identity registry, identity-bound record codec and §10.4.1 record-class disposition) admitted and implemented headless (§20, #920); legacy source-locator mapping belongs to Gate 5; #361's shipped-helper gap closes only with Gate 7; Gate 3 slice 3A (durable staging and promotion, §9 steps 3–8) implemented headless, with commit markers/reconciliation (3B) and the root/catalog commit (3C) remaining (#921), the rest of Gate 3 and Gates 4–7 (including Gate 4 cross-process serialization) not admitted** | **S5_A_ADMITTED=YES / S5_B1_ADMITTED=YES / S5_B2_ADMITTED=YES / S5_B3_ADMITTED=YES / S5_IMPLEMENTATION_READY=NO / S5_TERMINAL=YES / R15_GATE1A=IMPLEMENTED_HEADLESS / R15_GATE1B=IMPLEMENTED_HEADLESS_AND_PLATFORM_ADAPTER / R15_GATE2=IMPLEMENTED_HEADLESS / R15_GATE3=SLICE_3A_DURABLE_STAGING / PRODUCTION_AUTHORITY_SWITCH_ALLOWED=NO**; inventory, identity/AAD envelope, key epochs, fail-closed reads, durable replacement, crash-resumable migration, unified admission, race-free `AuthoritySnapshot` acquisition/lifetime (`docs/native/r15/AUTHORITY-SNAPSHOT-LIFETIME.md`), canonical migration source/payload evidence (`docs/native/r15/MIGRATION-SOURCE-EVIDENCE.md`), and the chunked large-object envelope (`docs/native/r15/CHUNKED-LARGE-OBJECT-ENVELOPE.md`) are all specified. No production authority switch or plaintext migration is claimed. | Final S5 cross-contract consistency audit (mutual reference integrity across all four documents) is complete — two mechanical citation-drift notes (a stale disposition-count note in §10.4.1, and S5-B3's mis-citation of S5-B1's migration-time mechanism for its own ordinary-write staging debris) and three substantive gaps were corrected: S5-B3's chunk-locator carried no operation/generation identity, so recovery could not distinguish a superseded attempt's orphaned chunk from the current one; §10.4.1's atomic-write-temporary-files carve-out contradicted its own "exactly one of three groups" exhaustiveness claim; and fixing that carve-out into an explicit `REFUSE_AUTHORITY_SWITCH` group in turn made Gate 7's class-level rule permanently unsatisfiable for that one class, fixed by making Gate 7 instance-aware. `S5_TERMINAL` is YES: PR #584 merged and its post-merge main CI (incl. CodeQL) was green. Gate 1a's headless vectors (contract header fixture, fixed-key AEAD for absent/present `project_id`, rule-D boundary, malformed-input and substitution tests, cross-checked against an independent implementation) now exist; the Gate 1b platform secure-store adapter exists with CI/local per-platform evidence (#916, contract §8.2.5); packaged secure-store evidence, Gate 4 cross-process serialization, per-record migration tests, packaged durability evidence, and explicit #357/#359/#360/#361 reconciliation are still required before the later implementation gates can close |
| 10 | R-15 protected desktop storage contract | **Contract `docs/native/R15-SECURE-STORAGE-CONTRACT.md` + headless Rust implementation (Gates 1a/1b/2) in `crates/worldscript-secure-storage`, not production authority**; current desktop records remain TS/Tauri filesystem authority | High — future Core must serve Tauri and Qt without renderer-private crypto semantics | High | High — durability, migration, and identity binding protect user data | High | **Highest — cross-renderer security/durability contract** | **3 — S5-A, S5-B1, S5-B2, and S5-B3 all admitted; final cross-contract audit complete, S5_TERMINAL=YES (PR #584 merged `c24aa645`, post-merge CI/CD + CodeQL green); Gate 1a re-admitted by QNB-100 (2026-09-26) and implemented headless in `crates/worldscript-secure-storage`; Gate 1b decided 2026-09-26 (Option C: platform secure store primary, optional `WSS_ARGON2ID_V1` passphrase recovery); 1b-core landed (#850); 1b-platform delivered as small sequential slices — §8.2.2 item layout (#854), durable authority (#855), runtime key handles (#914), anchor transitions + `KeyProvider` (#915), OS secure-store adapter (§8.2.5, #916; evidence Linux `CI_ONLY`/`LOCAL_ONLY`, macOS/Windows `CI_ONLY`, no packaged evidence); Gate 2 (typed identity registry, identity-bound record codec and §10.4.1 record-class disposition) admitted and implemented headless (§20, #920); legacy source-locator mapping belongs to Gate 5; #361's shipped-helper gap closes only with Gate 7; Gate 3 slice 3A (durable staging and promotion, §9 steps 3–8) implemented headless, with commit markers/reconciliation (3B) and the root/catalog commit (3C) remaining (#921); the rest of Gate 3 and Gates 4–7 (including Gate 4 cross-process serialization) not admitted** | **S5_A_ADMITTED=YES / S5_B1_ADMITTED=YES / S5_B2_ADMITTED=YES / S5_B3_ADMITTED=YES / S5_IMPLEMENTATION_READY=NO / S5_TERMINAL=YES / R15_GATE1A=IMPLEMENTED_HEADLESS / R15_GATE1B=IMPLEMENTED_HEADLESS_AND_PLATFORM_ADAPTER / R15_GATE2=IMPLEMENTED_HEADLESS / R15_GATE3=SLICE_3A_DURABLE_STAGING / PRODUCTION_AUTHORITY_SWITCH_ALLOWED=NO**; inventory, identity/AAD envelope, key epochs, fail-closed reads, durable replacement, crash-resumable migration, unified admission, race-free `AuthoritySnapshot` acquisition/lifetime (`docs/native/r15/AUTHORITY-SNAPSHOT-LIFETIME.md`), canonical migration source/payload evidence (`docs/native/r15/MIGRATION-SOURCE-EVIDENCE.md`), and the chunked large-object envelope (`docs/native/r15/CHUNKED-LARGE-OBJECT-ENVELOPE.md`) are all specified. No production authority switch or plaintext migration is claimed. | Final S5 cross-contract consistency audit (mutual reference integrity across all four documents) is complete — two mechanical citation-drift notes (a stale disposition-count note in §10.4.1, and S5-B3's mis-citation of S5-B1's migration-time mechanism for its own ordinary-write staging debris) and three substantive gaps were corrected: S5-B3's chunk-locator carried no operation/generation identity, so recovery could not distinguish a superseded attempt's orphaned chunk from the current one; §10.4.1's atomic-write-temporary-files carve-out contradicted its own "exactly one of three groups" exhaustiveness claim; and fixing that carve-out into an explicit `REFUSE_AUTHORITY_SWITCH` group in turn made Gate 7's class-level rule permanently unsatisfiable for that one class, fixed by making Gate 7 instance-aware. `S5_TERMINAL` is YES: PR #584 merged and its post-merge main CI (incl. CodeQL) was green. Gate 1a's headless vectors (contract header fixture, fixed-key AEAD for absent/present `project_id`, rule-D boundary, malformed-input and substitution tests, cross-checked against an independent implementation) now exist; the Gate 1b platform secure-store adapter exists with CI/local per-platform evidence (#916, contract §8.2.5); packaged secure-store evidence, Gate 4 cross-process serialization, per-record migration tests, packaged durability evidence, and explicit #357/#359/#360/#361 reconciliation are still required before the later implementation gates can close |

## Decisions this table records

Expand Down
19 changes: 19 additions & 0 deletions docs/native/R15-SECURE-STORAGE-CONTRACT.md
Original file line number Diff line number Diff line change
Expand Up @@ -3426,6 +3426,25 @@ contract. It does not mean any child issue is implemented or closed.

## 20. Staged implementation admission plan

**Current gate status (machine-checked).** This block is the single source of R-15 gate status.
`docs:check` (#933) requires the Core Migration Ledger's `R15_GATE*` tokens to equal it, the ledger
to carry every gate that is not `NOT_ADMITTED`, and no current prose in this contract or the ledger
to call a gate "not admitted" that this block marks as implemented, or as partial (`SLICE_*`)
without saying "the rest of" that gate. Every PR that changes a gate's status updates this block in
the same change.

```text
R15_GATE_STATUS
R15_GATE1A=IMPLEMENTED_HEADLESS
R15_GATE1B=IMPLEMENTED_HEADLESS_AND_PLATFORM_ADAPTER
R15_GATE2=IMPLEMENTED_HEADLESS
R15_GATE3=SLICE_3A_DURABLE_STAGING
R15_GATE4=NOT_ADMITTED
R15_GATE5=NOT_ADMITTED
R15_GATE6=NOT_ADMITTED
R15_GATE7=NOT_ADMITTED
```

Later implementation may be admitted only in these bounded gates:

1. **Core primitive and vectors:** implement the selected AEAD/KDF/randomness profile behind a
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
"content:guard": "node scripts/content-guard.mjs",
"parity:check": "tsx scripts/audit-feature-parity.ts",
"test-coverage:check": "tsx scripts/check-feature-test-coverage.ts",
"docs:check": "node scripts/check-doc-metrics.mjs",
"docs:check": "node scripts/check-doc-metrics.mjs && node scripts/check-r15-gate-status.mjs",
"csp:sync": "node scripts/sync-csp.mjs",
"csp:check": "node scripts/check-csp-policy.mjs",
"csp:verify": "node scripts/sync-csp.mjs && git diff --exit-code -- index.html nginx.conf public/_headers vercel.json src-tauri/tauri.conf.json && node scripts/check-csp-policy.mjs",
Expand Down
9 changes: 9 additions & 0 deletions scripts/check-r15-gate-status.d.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export const R15_CONTRACT_DOC: string;
export const R15_LEDGER_DOC: string;
export const R15_GATE_IDS: readonly string[];
export const R15_FIXED_STATUSES: readonly string[];
export function parseR15GateEntries(text: string): { gate: string; status: string }[];
export function currentProse(markdown: string): string;
export function scanR15GateStatusTruth(contract: string, ledger: string): string[];
export function isValidR15Status(gate: string, status: string): boolean;
export function stripHtmlComments(text: string): string;
Loading
Loading