Skip to content

feat(provision): apply/undo orchestration + run records (U10 U5) - #52

Merged
jarodtaylor merged 8 commits into
mainfrom
feat/u10-u5-apply-undo-runs
Jul 22, 2026
Merged

jarodtaylor merged 8 commits into
mainfrom
feat/u10-u5-apply-undo-runs

Conversation

@jarodtaylor

@jarodtaylor jarodtaylor commented Jul 22, 2026 •

Copy link
Copy Markdown
Owner

U10 U5 — Apply/undo orchestration + run records

The Act half of the provisioning engine (plan, ### U5). Composes the pure U3 render+diff, the U4 target registry, and the U14 config-write engine into an abort-all batch apply with LIFO rollback, plus journal-derived run records and batch undo.

  • src/provision/apply.ts — preflight-then-write: an incompatible / unresolvable / duplicate-destination / secret-bearing / merge-surface-clobbering row aborts before any file is written; a genuine mid-write fault rolls this batch's earlier writes back newest-first; an applied-but-unjournaled write is annotated un-undoable and survives honestly in the outcome.
  • src/provision/runs.ts — batches derived from the undo journal (no second record file, fresh-process recoverable); LIFO reversal with per-file reversed / alreadyReversed / superseded / failed classification; journal-ordering supersession (byte-independent, ABA-proof).
  • Satisfies R5/R7/R8 (write side), AE1/AE3/AE7, KTD2. 620 tests green, tsc clean.

Review + gate journey

Full loop: ce-work (opus) → ce-simplify (3 reviewers, 7 fixes) → ce-code-review (9-persona roster + security + reliability + adversarial) → Codex adversarial gate — 4 fold rounds. The gate earned its keep: round 1 caught a real security gap — issue #43's effective-form secret scan, whose promotion trigger was literally "before U5/apply ships" (U5/apply is the first code that writes parser-consumed formats to disk), which the brief + ce-work + all 9 personas + simplify had missed. Later rounds folded whole-file clobber guards, disk-independent blueprint validity, an A→B→A undo-clobber fix (journal-ordering supersession), and project-root canonicalization for undo batch selection. Learning compounded → docs/solutions/conventions/check-promotion-triggered-deferred-issues-at-unit-entry.md.

⚠️ Scoped defers — flagged for CEO veto

Per the plan's write-path gate budget ("budget for 2+ rounds and use scoped defers with triggers rather than a third patch"), the gate's remaining findings are exotic-state-conditional, unreachable in normal single-user use, and deferred (each narrowed honestly in-code, filed with a promotion trigger). Codex confirmed the final production code has no newly established defect beyond these:

None is a data-loss or security path in ordinary operation; the durable fixes land where the model they touch is actually built (U6). Ship-then-defer was authorized (fold finding 1, defer the rest).

Summary by CodeRabbit

  • New Features
    • Added all-or-nothing blueprint provisioning with render/diff preflight validation, secret scanning, safe writes, journaling, and automatic LIFO rollback.
    • Added batch history with undo, including supersession protection and detailed outcome reporting.
    • Undo now reports whether it reversed changes or was a noop; improved exported provisioning utilities (root normalization, error text, hashing/undo status exports).
  • Documentation
    • Updated product continuity progress and refreshed next execution instructions.
    • Added documentation for deferred-issue promotion triggers and updated decision logs for review/shipping mechanics.
  • Tests
    • Added comprehensive provisioning apply/undo integration tests covering idempotency, rollback/error regimes, security checks, and journal batch reconstruction.

U3 (render/diff, #47) + U4 (target registry + Cursor scanner, #46) merged via
the inverted Codex-builds/Claude-reviews dogfood loop (decisions #55-#56).
START-HERE ▶ NEXT → /unit-loop U5; PRODUCT 2/7 → 4/7; DECISIONS #55 (inverted
loop, proven) + #56 (codex-gate doesn't fit worktree/inverted PRs — dogfood
finding, fix deferred). Post-merge main green: 580 tests, tsc clean.
The Act half of the provisioning engine: ordered abort-all batch apply with
LIFO rollback, journal-derived run records, and batch undo. apply.ts composes
the pure U3 render+diff, the U4 target registry, and the U14 config-write engine
behind a preflight-then-write discipline — an incompatible/unresolvable/
duplicate-destination target aborts before any file is written; a genuine
mid-write fault rolls this batch's earlier writes back newest-first; an
applied-but-unjournaled write is annotated un-undoable and survives honestly in
the outcome. runs.ts derives batches from the undo journal (no second record
file, fresh-process recoverable) and reverses them LIFO with per-file
superseded/failed classification.

Why LIFO everywhere: a blueprint may write one destination twice (two roles
config-merging into a shared .cursor/mcp.json), forming a postHash chain that
must reverse newest-first. Why the duplicate-destination guard: 2+ non-mergeable
(whole-file) writers to one destination is a silent last-writer-wins blueprint —
refused loudly at preflight; all-config-merge collisions still compose.

602 tests green, tsc clean.
… (U10 U5)

Codex adversarial gate folds:
- #43 effective-form secret scan (its promotion trigger — "before U5/apply
  ships" — fired here): apply now scans the DECODED form of every parser-consumed
  (json/toml) write, so a value secret hidden behind a JSON \uXXXX escape that
  the raw front-gate misses is refused before any write. Scans only the patch we
  contribute, never foreign disk content. Fails closed on unparseable sources.
- duplicate-destination guard now groups ALL rows (incl. current-noop), and
  rejects conflicting config-merge patches (overlapping leaf, different value) —
  closing the oscillation; disjoint same-dest config-merge still composes.
- transform-vs-surface-shape guard: a whole-file copy/compose/scaffold aimed at a
  merge-shaped surface (.cursor/mcp.json etc.) is refused before it can clobber
  the user's foreign config.

Deferred (docstring-narrowed, honest): default undo targets the newest JOURNALED
batch, not the newest SUCCESSFUL one — a failed+rolled-back batch is the default
undo target and reports an honest no-op; durable fix lands with U6's CLI undo.

611 tests green, tsc clean.
… (U10 U5)

Codex adversarial gate round 2 folds:
- Blueprint-validity checks (shape guard + secret scan) now run for EVERY
  resolving row BEFORE the skip-continue — validity is a property of the
  blueprint, not of current disk. A whole-file copy to a merge surface, or a
  copy of a secret-bearing source, that currently no-ops is no longer accepted
  just because the target already matches.
- The secret scan now RAW-scans whole-file payloads (closes the text-format gap:
  CLAUDE.md/AGENTS.md/role .md files) in addition to the decoded parser-format
  scan — apply is egress defense on the bytes it writes. (Keyword-only secrets
  in a config-merge source stay the verb front-gate's job — named in-comment.)
- undo() returns a "reversed"|"noop" disposition; runs.ts routes an idempotent
  no-op to a new alreadyReversed bucket instead of falsely claiming reversed —
  making the deferred failed-batch undo an honest no-op in fact.
- Supersession is now decided by JOURNAL ORDERING (a later, different batch that
  wrote the same path), not a live byte-hash — closing an A->B->A content-cycle
  clobber where the old hash-check would delete/restore over a newer batch.
  Cross-batch only, so the same-batch LIFO chain still reverses intact.

Deferred to #50/U6 (durable batch terminal-state): the fail-OPEN precision that
would let an old-batch undo proceed once a later batch is proven itself reversed.

616 tests green, tsc clean.
…ssion (U10 U5)

Codex gate round 3 — surface-reducing folds (the last fold-and-rerun cycle):
- Reject a non-provisionable-harness or unresolvable-destination row for EVERY
  row, including a noop/scaffold-skip whose bytes match disk — completing round
  2's "validity is a property of the blueprint, not of current disk" class-fix
  (it previously still leaked for harness/resolve).
- Journal-ordering supersession now keys on the posix-normalized absolute
  targetPath ALONE, dropping the redundant unnormalized projectRoot from the key,
  so equivalent root spellings (/p, /p/, /p/x/..) collapse and a later batch under
  a different spelling still supersedes — closing an alias-bypass ABA clobber.

Deferred to #50/U6 (exotic-state, per plan's write-path gate budget), narrowed
honestly in-code: symlink-root aliases (need realpath canonicalization at the
registry ingress); an unjournaled write buried by a later same-path config-merge
in one batch (needs path-poisoning); undo's created-file existsSync mapping EACCES
to alreadyReversed (needs guarded lstat in the shared U14 primitive).

619 tests green, tsc clean.
…(U10 U5)

Codex gate round 4: undo batch SELECTION matched the stored projectRoot by exact
string, so applying under /p/ then undoing under /p missed the batch — default
undo could reverse an OLDER batch and leave the latest applied. Canonicalize the
root (posix.normalize + strip trailing slash, which normalize keeps) once in a
shared helper, applied at apply ingress (canonical storage) and on both sides of
every batch-selection comparison (covers legacy journals + spelling mismatch).
Closes the ordinary lexical case; symlink-root realpath stays deferred (#51).

Deferred (unchanged): R4 decoded machine-path scan is best-effort per spec and
needs crafted encoded input — the R4 twin of the #43 secret gap, filed context
in #51's neighborhood.

620 tests green, tsc clean.
… (U10 U5 learning)

The U5 gate caught issue #43 (effective-form secret scan, trigger 'before
U5/apply ships') that the brief + ce-work + 9-persona review + simplify all
missed — because they reason from the plan and code, not from a separate
deferred issue. Convention: at unit-loop entry, grep open deferred issues for
promotion triggers naming this unit and fold them into the ce-work brief.
Copilot AI review requested due to automatic review settings July 22, 2026 15:03
@coderabbitai

coderabbitai Bot commented Jul 22, 2026 •

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ea398193-3319-4dd9-a2f7-e27442dba2d0

📥 Commits

Reviewing files that changed from the base of the PR and between 4ff17d2 and 3b2a54c.

📒 Files selected for processing (2)
  • src/provision/runs.ts
  • tests/provision-apply.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/provision/runs.ts
  • tests/provision-apply.test.ts

📝 Walkthrough

Walkthrough

The PR adds all-or-nothing provisioning apply orchestration, explicit undo dispositions, journal-based batch discovery and undo, supersession protection, extensive integration tests, and updated U5 execution and project-status documentation.

Changes

Provisioning apply and undo

Layer / File(s) Summary
Undo and path contracts
src/configwrite/index.ts, src/configwrite/undo.ts, src/provision/internal.ts
Undo reports reversed or noop, with shared path and error helpers exported for provisioning flows.
Apply preflight and write orchestration
src/provision/apply.ts
Adds render, diff, validation, conflict detection, engine dispatch, journaling, no-op reporting, and rollback handling.
Journal batch discovery and supersession
src/provision/runs.ts
Adds batch grouping, canonical project selection, LIFO undo, and supersession classification.
Apply and undo integration validation
tests/provision-apply.test.ts
Covers successful applies, no-ops, merges, rollback, security guards, journal reconstruction, root aliases, and fail-closed batch ownership.
Execution and status documentation
docs/DECISIONS.md, docs/PRODUCT.md, docs/START-HERE.md, docs/solutions/conventions/...
Updates U10 progress, directs execution toward U5, and documents deferred-issue scanning and review mechanics.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant Apply
  participant Engine
  participant Journal
  participant BatchUndo
  Caller->>Apply: apply blueprint
  Apply->>Engine: write or merge validated rows
  Engine->>Journal: record mutations
  Apply-->>Caller: ApplyOutcome
  Caller->>BatchUndo: undoBatch project
  BatchUndo->>Journal: read and group entries
  BatchUndo->>Journal: classify supersession
  BatchUndo-->>Caller: UndoOutcome
Loading

Possibly related issues

  • jarodtaylor/agent-os#50 — Directly covers newest-batch selection and batch undo implemented in this PR.
  • jarodtaylor/agent-os#48 — Documents the Codex-gate hook and inverted/worktree PR mismatch addressed in the decision log.

Possibly related PRs

  • jarodtaylor/agent-os#47 — Provides the render/diff classification core consumed by the new apply orchestration.

Poem

I hop through blueprints, row by row,
Then journal every file I sow.
If paths collide, I guard the door,
And undo hops in reverse once more.
No-op or reversed, the trail is clear—
A tidy provisioning burrow here!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 74.07% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main provisioning change: apply/undo orchestration plus run records for U10 U5.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/u10-u5-apply-undo-runs

Comment @coderabbitai help to get the list of available commands.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements the “Act” half of the U10 provisioning engine by adding an abort-all apply orchestrator with LIFO rollback, plus journal-derived run/batch records and batch undo. It composes the existing render+diff pipeline, target registry compatibility checks, and the config-write engine into a single write path with deterministic recovery behavior.

Changes:

  • Add apply() orchestration with preflight-then-write, secret egress scanning (including effective/decoded-form scanning for config formats), and LIFO rollback with unjournaled-write honesty.
  • Add journal-derived run records (readBatches, newestBatchForProject) and undoBatch() with cross-batch supersession protection and per-file outcome classification.
  • Expand undo semantics to return an explicit disposition ("reversed" vs "noop") and add comprehensive apply/undo tests.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
tests/provision-apply.test.ts New end-to-end tests covering apply/rollback/undo behavior, journal-derived runs, and secret egress defenses.
src/provision/apply.ts New apply orchestrator with preflight validation, secret scanning, write pass, and LIFO rollback behavior.
src/provision/runs.ts New run/batch reader and batch undo implementation derived purely from the undo journal.
src/provision/internal.ts Adds project-root canonicalization and shared error formatting helpers used by provision modules.
src/configwrite/undo.ts Changes undo() to return an explicit disposition to support honest no-op reporting.
src/configwrite/index.ts Updates public exports to include UndoDisposition and hashContent.
docs/START-HERE.md Updates the project status narrative to reflect U3+U4 shipping and U5 as next.
docs/solutions/conventions/check-promotion-triggered-deferred-issues-at-unit-entry.md New workflow convention doc for promotion-triggered deferred issues.
docs/PRODUCT.md Updates product status to reflect U10 progress (4/7 sub-units shipped; U5 next).
docs/DECISIONS.md Adds decision ledger entries for U3+U4 shipping and related gating/worktree notes.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/provision/runs.ts
Comment on lines +30 to +44
function groupBatches(entries: readonly UndoEntry[]): ProvisionBatch[] {
const order: string[] = [];
const byId = new Map<string, ProvisionBatch>();
for (const entry of entries) {
if (!entry.batchId || !entry.projectRoot) continue;
let batch = byId.get(entry.batchId);
if (!batch) {
batch = { batchId: entry.batchId, projectRoot: entry.projectRoot, entries: [] };
byId.set(entry.batchId, batch);
order.push(entry.batchId);
}
batch.entries.push(entry);
}
return order.map((id) => byId.get(id)!);
}
…, PR #52)

groupBatches keyed only on batchId, but the module treats the journal as
untrusted (validated on read for hand-edits/corruption). A corrupted journal
reusing a batchId under a different projectRoot could let undoBatch reverse
another project's paths. Drop any entry whose canonicalized root doesn't match
the batch's established root — a legit batch's entries all share the
ingress-canonicalized root, so this only excludes corrupt rows.

621 tests green, tsc clean.
@jarodtaylor
jarodtaylor merged commit bb1b32f into main Jul 22, 2026
1 check passed
jarodtaylor added a commit that referenced this pull request Jul 22, 2026
U5 apply/undo orchestration merged (PR #52); follow-up #54 open (gate-approved).
Decisions #57 (U5 shipped) + #58 (decision-#45 applied — crafted-journal undo
attacks out of scope). Deferred #50/#51/#53 → U6. ▶ NEXT = /unit-loop U6 then U7.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants