You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
When working on an active change that modifies existing specs via deltas, the canonical spec remains unchanged until archive. This means:
Agents and humans reading specs during implementation see stale content — the spec doesn't reflect the changes being implemented. CompileContext provides delta instructions and outlines of the current spec, but never the merged result.
There's no way to see "what the spec will look like" after delta application, except mentally reconstructing it or waiting until archive.
Other changes targeting the same spec don't see the pending modifications from an in-progress change, leading to potential conflicts discovered only at archive time.
Current Behavior
Operation
Reads base spec
Merges deltas
Mutates canonical spec
CompileContext
Yes (outlines only)
No
No
ValidateArtifacts
Yes (preview for validation)
Yes (internal, discarded)
No
ArchiveChange
Yes
Yes
Yes (only here)
The delta merge algorithm already exists and works (apply-delta.ts, ~350 lines). ValidateArtifacts already does a merge internally to validate the result. The infrastructure is there — it's just not exposed.
Prerequisites
Level 1 and Level 2 have no prerequisites — they are read-only operations that can be implemented immediately using existing merge infrastructure.
Note: Once #22 is implemented, Level 1 and 2 can be enhanced with drift detection (warn if the base spec changed since the delta was authored). This is a nice-to-have safety guard, not a blocker.
Proposal
Three levels of delta visibility, ordered by value and risk:
Level 1 — Materialized view in CompileContext (zero risk)
When compiling context for an active change with delta artifacts, include the merged spec content instead of (or alongside) the raw outline.
How it works:
For each spec in change.specIds with a delta artifact:
Load base spec from SpecRepository
Load delta from change directory
Apply via ArtifactParser.apply(baseAst, deltaEntries)
Serialize merged result
Include in the instruction block as "Spec (with pending changes applied)"
The canonical spec is never mutated — this is a read-only view
If delta application fails, fall back to current behavior (outline only) with a warning
Output: The spec as it would look after delta application. Useful for human review.
Could also support --diff mode to show what changed.
Level 3 — Sync deltas to canonical specs
Allow syncing validated deltas to canonical specs before archive.
specd change spec-sync <name>
Requires:#22 (delta baselines) for drift detection.
Preconditions
All artifacts in the change must have passed validation. Sync operates on validated deltas only, regardless of whether governance gates are configured.
New spec artifacts (specs/) → copy to canonical spec location
Delta artifacts (deltas/) → merge into existing canonical specs (same merge logic as archive)
Archive synced artifacts within the change directory:
.specd/changes/<change-name>/
├── history/
│ └── 2026-03-26T14-30-00/ # timestamp of sync
│ ├── originals/ # copy of canonical specs BEFORE sync
│ ├── specs/ # new spec artifacts that were created
│ ├── deltas/ # delta artifacts that were applied
│ └── sync-manifest.yaml # rollback metadata
├── deltas/ # recreated: no-op deltas for all specs
└── (no specs/ folder) # all specs are now canonical — future changes are deltas
Copy each canonical spec (before mutation) into history/<timestamp>/originals/ — these are the pre-sync snapshots needed for rollback and triple diff
Move the current specs/ and deltas/ folders into history/<timestamp>/
Generate a sync-manifest.yaml in the history entry with rollback metadata:
syncedAt: "2026-03-26T14:30:00Z"vcsRef: "a1b2c3d"# commit hash at sync time (if VCS available)specs:
"default:auth/login":
type: delta # was a delta mergeoriginalContentHash: "sha256:..."# canonical spec hash before merge"default:auth/register":
type: new # was a new spec creation
Recreate all artifacts as no-op deltas — both previously-new specs and previously-delta specs are now canonical, so all become no-op deltas
The change remains active in its current lifecycle state. It is still a valid change with valid (no-op) artifacts.
Why all no-op deltas after sync?
After sync, all specs targeted by the change exist as canonical specs — including those that were originally new. The artifacts must reflect this:
Previously new specs are now canonical → they become no-op deltas (the spec already exists as synced)
Previously delta specs were already merged → they become no-op deltas (the canonical spec already matches)
This means:
The change remains valid and can proceed through its lifecycle normally
CompileContext won't try to re-merge already-applied changes
ArchiveChange becomes a clean close (no mutations needed, just lifecycle transition)
If new modifications are needed, new deltas are authored against the now-updated canonical specs — all future artifacts are deltas, since no spec is "new" anymore
Governance interaction
Sync is allowed regardless of governance gates — spec approval gates govern the design phase (authoring deltas), not the act of applying validated work. If deltas passed validation, the spec changes have already been reviewed to the extent governance requires at that stage.
However, governance gates still apply to the archive step. A synced change with no-op deltas still needs to complete its full lifecycle (including signoff if configured) before being archived.
Lifecycle restrictions after sync
Once a change has been synced, the canonical specs have been mutated. To prevent orphaned mutations, the following lifecycle transitions are blocked while a sync is active:
Discard — blocked. The canonical specs contain synced changes; discarding would leave them orphaned.
Draft — blocked. Moving back to draft implies the design isn't final, but the specs already reflect it.
To unblock these transitions, the user must first rollback the sync:
specd change spec-rollback <name>
Rollback uses the originals/ snapshots and sync-manifest.yaml from the most recent history/ entry:
Clean rollback (no drift):
For each synced spec, compare originalContentHash from the manifest against hash(current canonical - synced changes). If no other changes occurred:
Restore canonical specs from history/<timestamp>/originals/
Move the original artifacts back from history/<timestamp>/specs/ and history/<timestamp>/deltas/
Remove the history entry
Rollback with drift (someone else changed the spec):
If the current canonical spec differs from what the sync produced (i.e., other changes landed on top), rollback is blocked and a two-panel diff is shown:
── Your sync (original → synced) ─────────────────
Shows what your sync changed in the spec.
Source: diff(originals/<spec>, apply(originals/<spec>, deltas/<spec>))
── Changes since sync (synced → current) ─────────
Shows what others changed after your sync.
Source: diff(apply(originals/<spec>, deltas/<spec>), canonical/<spec>)
This gives the user full context to decide how to proceed manually. All three versions are available:
Original → history/<timestamp>/originals/<spec>
Synced → reconstructed by applying history/<timestamp>/deltas/ to the original
Current → the canonical spec as it is now
After a clean rollback, the change is back to its pre-sync state and all lifecycle transitions are available again.
Multiple syncs
A change can be synced multiple times if new deltas are added after a previous sync. Each sync creates a new history/<timestamp>/ entry, moves current artifacts there, and recreates no-ops. Rollback always operates on the most recent sync entry — each rollback peels back one layer in reverse chronological order.
Design Tensions
Approval invalidation
If governance is enabled and we allow Level 1 (materialized view), there's no issue — the canonical spec hasn't changed. Level 3 applies only validated artifacts, so governance over the design phase is respected.
Multiple changes targeting the same spec
If change A syncs its deltas and change B also targets the same spec, change B's delta was authored against the pre-sync base. This is detectable via baselines (#22) — change B's validation would flag the drift before it could sync stale deltas.
Recommended Implementation Order
Level 1 — highest value, zero risk, builds on existing ValidateArtifacts pattern
Level 2 — simple CLI wrapper around the same merge logic
Problem
When working on an active change that modifies existing specs via deltas, the canonical spec remains unchanged until archive. This means:
Agents and humans reading specs during implementation see stale content — the spec doesn't reflect the changes being implemented.
CompileContextprovides delta instructions and outlines of the current spec, but never the merged result.There's no way to see "what the spec will look like" after delta application, except mentally reconstructing it or waiting until archive.
Other changes targeting the same spec don't see the pending modifications from an in-progress change, leading to potential conflicts discovered only at archive time.
Current Behavior
CompileContextValidateArtifactsArchiveChangeThe delta merge algorithm already exists and works (
apply-delta.ts, ~350 lines).ValidateArtifactsalready does a merge internally to validate the result. The infrastructure is there — it's just not exposed.Prerequisites
Proposal
Three levels of delta visibility, ordered by value and risk:
Level 1 — Materialized view in CompileContext (zero risk)
When compiling context for an active change with delta artifacts, include the merged spec content instead of (or alongside) the raw outline.
How it works:
change.specIdswith a delta artifact:SpecRepositoryArtifactParser.apply(baseAst, deltaEntries)deltaBaselines— if they differ, skip merge and warn about driftImpact: Agents always see the "true" spec state while working on a change. No governance concerns because nothing is mutated.
Level 2 — Explicit preview command (zero risk)
A CLI command to inspect the merged result without mutating anything:
Output: The spec as it would look after delta application. Useful for human review.
Could also support
--diffmode to show what changed.Level 3 — Sync deltas to canonical specs
Allow syncing validated deltas to canonical specs before archive.
Preconditions
Sync flow
specs/) → copy to canonical spec locationdeltas/) → merge into existing canonical specs (same merge logic as archive)history/<timestamp>/originals/— these are the pre-sync snapshots needed for rollback and triple diffspecs/anddeltas/folders intohistory/<timestamp>/sync-manifest.yamlin the history entry with rollback metadata:Why all no-op deltas after sync?
After sync, all specs targeted by the change exist as canonical specs — including those that were originally new. The artifacts must reflect this:
This means:
CompileContextwon't try to re-merge already-applied changesArchiveChangebecomes a clean close (no mutations needed, just lifecycle transition)Governance interaction
Sync is allowed regardless of governance gates — spec approval gates govern the design phase (authoring deltas), not the act of applying validated work. If deltas passed validation, the spec changes have already been reviewed to the extent governance requires at that stage.
However, governance gates still apply to the archive step. A synced change with no-op deltas still needs to complete its full lifecycle (including signoff if configured) before being archived.
Lifecycle restrictions after sync
Once a change has been synced, the canonical specs have been mutated. To prevent orphaned mutations, the following lifecycle transitions are blocked while a sync is active:
To unblock these transitions, the user must first rollback the sync:
Rollback uses the
originals/snapshots andsync-manifest.yamlfrom the most recenthistory/entry:Clean rollback (no drift):
For each synced spec, compare
originalContentHashfrom the manifest againsthash(current canonical - synced changes). If no other changes occurred:history/<timestamp>/originals/history/<timestamp>/specs/andhistory/<timestamp>/deltas/Rollback with drift (someone else changed the spec):
If the current canonical spec differs from what the sync produced (i.e., other changes landed on top), rollback is blocked and a two-panel diff is shown:
This gives the user full context to decide how to proceed manually. All three versions are available:
history/<timestamp>/originals/<spec>history/<timestamp>/deltas/to the originalAfter a clean rollback, the change is back to its pre-sync state and all lifecycle transitions are available again.
Multiple syncs
A change can be synced multiple times if new deltas are added after a previous sync. Each sync creates a new
history/<timestamp>/entry, moves current artifacts there, and recreates no-ops. Rollback always operates on the most recent sync entry — each rollback peels back one layer in reverse chronological order.Design Tensions
Approval invalidation
If governance is enabled and we allow Level 1 (materialized view), there's no issue — the canonical spec hasn't changed. Level 3 applies only validated artifacts, so governance over the design phase is respected.
Multiple changes targeting the same spec
If change A syncs its deltas and change B also targets the same spec, change B's delta was authored against the pre-sync base. This is detectable via baselines (#22) — change B's validation would flag the drift before it could sync stale deltas.
Recommended Implementation Order
ValidateArtifactspatternRelated Specs
specs/core/delta-format/spec.md— delta structure and apply algorithmspecs/core/archive-change/spec.md— current merge-on-archive flowspecs/core/compile-context/spec.md— context assembly (delta context block)specs/core/validate-artifacts/spec.md— already does internal merge for validationspecs/core/config/spec.md— governance gates (approvals.spec,approvals.signoff)specs/core/change/spec.md— change lifecycle and discard semantics