@bookedsolid/rea 0.40.0 → 0.42.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -460,10 +460,52 @@ export async function runUpgrade(options = {}) {
460
460
  if (options.force === true && !dryRun) {
461
461
  warn('--force: overwriting locally-modified files and deleting removed-upstream entries without prompt.');
462
462
  }
463
+ // 0.42.0 codex round 2 P2 (2026-05-16): pre-flight the merged
464
+ // settings validation BEFORE any file writes happen. Pre-correction,
465
+ // `upgradeSettings` ran AFTER the canonical-file write loop and only
466
+ // then validated the merged result; throwing here meant canonical
467
+ // hook + agent files had already been written to disk, breaking
468
+ // `rea upgrade --check`'s implicit "preview = real" contract (the
469
+ // check footer correctly claimed validation would refuse, but did
470
+ // not warn that hook files would still be written first).
471
+ //
472
+ // Codex round 2 P2 (2026-05-16, follow-up): this MUST run before
473
+ // `migrateReviewPolicyFor0110` as well — the 0.11.0 policy migration
474
+ // rewrites `.rea/policy.yaml` and creates a `policy.yaml.bak-*`
475
+ // sibling. If the pre-flight runs after that migration, the
476
+ // "no files have been written" claim in the refusal message
477
+ // becomes false for pre-0.11 consumers with malformed settings.
478
+ // Ordering: pre-flight FIRST, then migration, then canonical
479
+ // enumeration + write loop.
480
+ //
481
+ // The pre-flight reuses the same helpers as `upgradeSettings` —
482
+ // `readSettings` / `pruneHookCommands` / `mergeSettings` /
483
+ // `validateSettings` are all pure functions over existing on-disk
484
+ // state and `defaultDesiredHooks()`, so running them twice is
485
+ // cheap (microseconds — single JSON parse + small object merge)
486
+ // and the second run inside `upgradeSettings` re-derives the same
487
+ // result before writing. Atomicity guarantee: if validation fails,
488
+ // `runUpgrade` aborts here with ZERO mutations to disk.
489
+ {
490
+ const desired = defaultDesiredHooks();
491
+ const { settings: existingSettings } = readSettings(resolvedRoot);
492
+ const pruned = pruneHookCommands(existingSettings, STALE_HOOK_COMMAND_TOKENS);
493
+ const mergeResult = mergeSettings(pruned.merged, desired);
494
+ const validation = validateSettings(mergeResult.merged);
495
+ if (!validation.parsed) {
496
+ throw new Error(`rea upgrade: refusing to start because the merged .claude/settings.json would ` +
497
+ `fail schema validation. This is a safety guardrail — no files have been written ` +
498
+ `(including no .rea/policy.yaml 0.11.0 migration). Your existing install is ` +
499
+ `unchanged. zod errors: ${validation.errors.join('; ')}`);
500
+ }
501
+ }
463
502
  // 0.11.0 migration — strip removed review.* fields and backfill the new
464
503
  // concerns_blocks default. Runs before canonical file reconciliation so a
465
504
  // policy that fails strict schema load (which happens on upgrade from
466
505
  // 0.10.x the moment we re-read `.rea/policy.yaml`) is cleaned up first.
506
+ // 0.42.0 round 2 P2 follow-up: ordered AFTER the pre-flight validation
507
+ // above so a settings-validation refusal does not leave a half-migrated
508
+ // .rea/policy.yaml + backup behind.
467
509
  await migrateReviewPolicyFor0110(resolvedRoot, { dryRun });
468
510
  const canonicalFiles = await enumerateCanonicalFiles();
469
511
  if (canonicalFiles.length === 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bookedsolid/rea",
3
- "version": "0.40.0",
3
+ "version": "0.42.0",
4
4
  "description": "Agentic governance layer for Claude Code — policy enforcement, hook-based safety gates, audit logging, and Codex-integrated adversarial review for AI-assisted projects",
5
5
  "license": "MIT",
6
6
  "author": "Booked Solid Technology <oss@bookedsolid.tech> (https://bookedsolid.tech)",