Skip to content

Commit 6a9cb8d

Browse files
committed
fix: address v6 release review findings
1 parent a2f3917 commit 6a9cb8d

46 files changed

Lines changed: 621 additions & 298 deletions

Some content is hidden

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

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,5 @@ CODE-EVAL.md
1616
.mcp.json
1717
build-info.json
1818
.obsidian/*
19+
/Untitled.md
20+
/output-*.txt

CHANGELOG.md

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

1818
### Added
1919

20-
- **Migration script (`npm run upgrade`)** — fully implemented `scripts/migrate-encryption.js` for v6.0.0 encryption scheme upgrades. Two modes: **fast** (rename-only for v2 schemes and `convergent-v1`) and **full** (re-encryption for v1 whole/framed schemes that lacked AAD). Supports `--execute` (default dry-run), `--passphrase`, and `--cwd`. Reads every vault entry, classifies it, and reports what will/did change.
20+
- **Migration script (`npm run upgrade`)** — fully implemented `scripts/migrate-encryption.js` for v6.0.0 encryption scheme upgrades. Two modes: **fast** (rename-only for v2 schemes and `convergent-v1`) and **full** (re-encryption for v1 whole/framed schemes that lacked AAD). Supports `--execute` (default dry-run), `--passphrase`, `--key-file`, privacy-vault key options, and `--cwd`. Reads every vault entry, classifies it, and reports what will/did change.
2121
- **`CasService.readManifestRaw()`** — reads a manifest from a Git tree OID and returns the raw decoded object without Manifest construction or scheme assertion. Migration entry point for inspecting legacy manifests.
2222
- **`CasService` `legacyMode` constructor option** — when `true`, `readManifest()` maps legacy scheme identifiers (v1/v2) to their current names instead of throwing `LEGACY_SCHEME`. Legacy v1 manifests (no AAD) are correctly decrypted without AAD during restore.
2323
- **`mapToCurrentScheme()` and `isLegacyNoAad()` in `schemes.js`** — public helpers for mapping legacy scheme strings to current names and detecting v1 no-AAD schemes.
@@ -58,7 +58,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
5858
- **Plaintext+compressed restore is now streaming** — compressed unencrypted data uses `_restoreCompressedStreaming` instead of the buffered path, eliminating the `maxRestoreBufferSize` constraint for this case.
5959
- **`formatVersion` stamped into new manifests** — new manifests include a `formatVersion` field carrying the package semver at store time. The field is optional on read for backward compatibility with older manifests.
6060
- **`CryptoPort._buildMeta` default scheme** — changed to the current `whole` identifier.
61-
- **CompressionPort extraction**`CasService` no longer imports `node:zlib`, `node:stream`, or `node:util` directly. Compression is now delegated through a `CompressionPort` abstract port with a `NodeCompressionAdapter` default implementation. The `CasService` constructor accepts an optional `compressionAdapter` parameter for injecting alternative implementations. Public API unchanged; internal refactor only.
61+
- **CompressionPort extraction**`CasService` no longer imports `node:zlib`, `node:stream`, or `node:util` directly. Compression is now delegated through a `CompressionPort` abstract port. Direct `CasService` construction must inject `compressionAdapter`; the facade still provides the Node gzip default for normal callers.
6262
- **AES-GCM adapter enforcement** — Node, Bun, and Web Crypto decrypt paths now all reject malformed AES-256-GCM metadata at the adapter boundary, enforce the declared algorithm before decrypting, and reject short or malformed nonce/tag fields before any runtime-specific decrypt call runs.
6363
- **Buffered restore adapter contract** — hard-limited buffered restore modes now require `readBlobStream()` on the persistence adapter instead of silently degrading to whole-blob `readBlob()` fallback behavior. Plaintext restore keeps the compatibility fallback.
6464
- **KDF salt shape hardening** — stored KDF salt metadata now rejects malformed base64 at both the manifest schema layer and the runtime stored-KDF policy path, keeping vault metadata and passphrase-restore behavior aligned before derive work starts.

GUIDE.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -295,9 +295,11 @@ Manifests created with earlier versions may use v1/v2 scheme identifiers (`whole
295295

296296
```sh
297297
node scripts/migrate-encryption.js
298+
node scripts/migrate-encryption.js --execute --passphrase my-secret
299+
node scripts/migrate-encryption.js --execute --key-file ./asset.key
298300
```
299301

300-
The migration script handles both fast (rename-only for v2 schemes) and full (re-encryption with AAD for v1 schemes) migration paths. The main `src/` codebase throws `LEGACY_SCHEME` if it encounters a v1/v2 identifier.
302+
The migration script handles both fast (rename-only for v2 schemes) and full (re-encryption with AAD for v1 schemes) migration paths. Full migration accepts either `--passphrase` or `--key-file`. For privacy-enabled vaults, add `--vault-passphrase`, `--vault-passphrase-file`, or `--vault-key-file` when the vault passphrase differs from the content passphrase. The main `src/` codebase throws `LEGACY_SCHEME` if it encounters a v1/v2 identifier.
301303

302304
---
303305

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ Three encryption schemes are supported:
8989
| `framed` | Bounded frames | Slug + frame index | Default for fixed-chunk encrypted stores — streaming decrypt with per-frame AAD binding |
9090
| `convergent` | Per-chunk deterministic | Derived from content hash | **Default for CDC + encryption** — preserves deduplication across encrypted stores. Implemented as a standalone `ConvergentEncryption` service. |
9191

92-
Legacy schemes (`whole-v1`, `whole-v2`, `framed-v1`, `framed-v2`, `convergent-v1`) are no longer accepted and throw a `LEGACY_SCHEME` error. Run `npm run upgrade` (or `node scripts/migrate-encryption.js`) to migrate existing vault entries. The script auto-detects whether each entry needs a rename-only (fast) or full re-encryption (v1 schemes without AAD) and defaults to dry-run mode.
92+
Legacy schemes (`whole-v1`, `whole-v2`, `framed-v1`, `framed-v2`, `convergent-v1`) are no longer accepted and throw a `LEGACY_SCHEME` error. Run `npm run upgrade` (or `node scripts/migrate-encryption.js`) to migrate existing vault entries. The script auto-detects whether each entry needs a rename-only (fast) or full re-encryption (v1 schemes without AAD), accepts either `--passphrase` or `--key-file` for full migrations, supports privacy-vault key options, and defaults to dry-run mode.
9393

9494
**Envelope encryption** wraps a random Data Encryption Key (DEK) with one or more Key Encryption Keys (KEKs). Each recipient is labeled, enabling multi-recipient access to the same encrypted content. Key rotation replaces the KEK wrapping without re-encrypting data blobs.
9595

UPGRADING.md

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ If you have an existing vault with encrypted assets:
1111
npm run upgrade
1212

1313
# Apply the migration
14-
npm run upgrade -- --execute --passphrase <your-vault-passphrase>
14+
npm run upgrade -- --execute --passphrase <your-content-passphrase>
1515
```
1616

1717
If you only use the library API (no vault), skip to [API Changes](#api-changes).
@@ -54,12 +54,26 @@ npm run upgrade
5454

5555
# Execute: migrate all vault entries
5656
npm run upgrade -- --execute --passphrase <passphrase>
57+
58+
# Or migrate entries encrypted with a raw 32-byte key
59+
npm run upgrade -- --execute --key-file ./asset.key
5760
```
5861

5962
The migration script has two modes:
6063

6164
- **Fast mode** (v2 schemes + convergent): renames the scheme in the manifest metadata. No re-encryption. Seconds.
62-
- **Full mode** (v1 schemes): restores through the legacy pipeline (decrypts without AAD), then re-stores with the current scheme (encrypts with AAD). Requires passphrase.
65+
- **Full mode** (v1 schemes): restores through the legacy pipeline (decrypts without AAD), then re-stores with the current scheme (encrypts with AAD). Requires `--passphrase` or `--key-file`.
66+
67+
Privacy-enabled vaults need the vault encryption key to list and update slugs.
68+
If the vault passphrase differs from the content passphrase, pass
69+
`--vault-passphrase`, `--vault-passphrase-file`, or `--vault-key-file`.
70+
When no explicit vault key option is provided, `--passphrase` is reused for the
71+
privacy vault.
72+
73+
Recipient-encrypted v1 manifests are not automatically full-migrated because
74+
preserving recipient access requires the original recipient key set. Re-store
75+
those assets with current recipients after restoring them with a matching
76+
recipient key.
6377

6478
Original blobs are never deleted — Git's garbage collection only removes unreferenced objects after `git gc`.
6579

@@ -202,7 +216,8 @@ These are new capabilities that don't require migration:
202216
CasError: Legacy encryption scheme "whole-v1" is no longer supported.
203217
```
204218

205-
Run `npm run upgrade -- --execute --passphrase <pass>` to migrate.
219+
Run `npm run upgrade -- --execute --passphrase <pass>` or
220+
`npm run upgrade -- --execute --key-file <path>` to migrate.
206221

207222
### `KDF_POLICY_VIOLATION` on deriveKey
208223

Untitled.md

Whitespace-only changes.
Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
1616
* Reads the git SHA from build-info.json (published package).
1717
* @returns {string|null}
1818
*/
19-
function readStampedSha() {
19+
export function readStampedSha() {
2020
try {
2121
const info = JSON.parse(
2222
readFileSync(path.resolve(__dirname, '../build-info.json'), 'utf8'),
@@ -31,7 +31,7 @@ function readStampedSha() {
3131
* Reads the git SHA from the live repo (development).
3232
* @returns {string|null}
3333
*/
34-
function readGitSha() {
34+
export function readGitSha() {
3535
try {
3636
return execSync('git rev-parse --short HEAD', {
3737
encoding: 'utf8',
@@ -47,7 +47,10 @@ function readGitSha() {
4747
* @param {string} semver - The package version from package.json.
4848
* @returns {string}
4949
*/
50-
export function resolveVersionString(semver) {
51-
const sha = readStampedSha() || readGitSha();
50+
export function resolveVersionString(
51+
semver,
52+
{ readGitSha: readGit = readGitSha, readStampedSha: readStamped = readStampedSha } = {}
53+
) {
54+
const sha = readGit() || readStamped();
5255
return sha ? `${semver}+${sha}` : semver;
5356
}

bin/git-cas.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ import {
3333
} from './passphrase-source.js';
3434
import { loadConfig, mergeConfig } from './config.js';
3535

36-
import { resolveVersionString } from '../src/build-version.js';
36+
import { resolveVersionString } from './build-version.js';
3737

3838
const __dirname = path.dirname(fileURLToPath(import.meta.url));
3939
const CLI_VERSION = resolveVersionString(

bin/ui/encryption-card.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ export function renderEncryptionCard({ metadata, unlocked = false }) {
5858

5959
const rows = buildKdfRows(metadata.encryption);
6060
const maxKey = Math.max(...rows.map(([k]) => k.length));
61-
61+
6262
const kvSurfaces = rows.map(([k, v]) => {
6363
const label = themeText(ctx, k.padEnd(maxKey), { tone: 'accent' });
6464
return hstackSurface(2,
@@ -72,7 +72,7 @@ export function renderEncryptionCard({ metadata, unlocked = false }) {
7272
variant: unlocked ? 'success' : 'danger',
7373
ctx,
7474
});
75-
75+
7676
const statusLabel = themeText(ctx, 'status'.padEnd(maxKey), { tone: 'accent' });
7777
const statusRow = hstackSurface(2,
7878
createSurface(2, 1),

bin/ui/shaders/bak-sneppen.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ function drawLine({ x0, y0, x1, y1, color }) {
110110
function drawPoint({ x, y, color, radius = 1 }) {
111111
const ix = Math.round(((x / 1.6) + 1) * 0.5 * (bufferW - 1));
112112
const iy = Math.round(((y / 1.2) + 1) * 0.5 * (bufferH - 1));
113-
113+
114114
for (let dy = -radius; dy <= radius; dy++) {
115115
for (let dx = -radius; dx <= radius; dx++) {
116116
const cx = ix + dx;
@@ -133,11 +133,11 @@ function rasterizeFrame(time) {
133133
const headProj = project3D({ x: Math.cos(theta), y: Math.sin(theta), z: fitnessState[i], elevation, azimuth });
134134

135135
drawLine({ x0: baseProj.px, y0: baseProj.py, x1: headProj.px, y1: headProj.py, color: STEM_INT });
136-
136+
137137
const nextTheta = ((i + 1) / currentNumSpecies) * 2 * Math.PI + (time * 0.5);
138138
const nextBaseProj = project3D({ x: Math.cos(nextTheta), y: Math.sin(nextTheta), z: 0, elevation, azimuth });
139139
drawLine({ x0: baseProj.px, y0: baseProj.py, x1: nextBaseProj.px, y1: nextBaseProj.py, color: DR_INT });
140-
140+
141141
drawPoint({ x: headProj.px, y: headProj.py, color: HEAD_INT });
142142
}
143143
}

0 commit comments

Comments
 (0)