Skip to main content

Class: Change

Defined in: packages/core/src/domain/entities/change.ts:185

The central domain entity representing an in-progress spec change.

Lifecycle state is derived entirely from the history — the to field of the most recent transitioned event. No state snapshot is stored.

Every significant operation appends one or more events to history. Events are never modified or removed.

Constructors

Constructor

new Change(props): Change

Defined in: packages/core/src/domain/entities/change.ts:199

Creates a new Change from the given properties.

Parameters

props

ChangeProps

Change construction properties

Returns

Change

Accessors

activeSignoff

Get Signature

get activeSignoff(): SignedOffEvent | undefined

Defined in: packages/core/src/domain/entities/change.ts:324

The active signoff — the most recent signed-off event that has not been superseded by a subsequent invalidated event, or undefined.

Returns

SignedOffEvent | undefined


activeSpecApproval

Get Signature

get activeSpecApproval(): SpecApprovedEvent | undefined

Defined in: packages/core/src/domain/entities/change.ts:311

The active spec approval — the most recent spec-approved event that has not been superseded by a subsequent invalidated event, or undefined.

Returns

SpecApprovedEvent | undefined


artifacts

Get Signature

get artifacts(): ReadonlyMap<string, ChangeArtifact>

Defined in: packages/core/src/domain/entities/change.ts:334

All artifacts currently attached to this change, keyed by type.

Returns

ReadonlyMap<string, ChangeArtifact>


createdAt

Get Signature

get createdAt(): Date

Defined in: packages/core/src/domain/entities/change.ts:226

Timestamp when the change was created.

Returns

Date


description

Get Signature

get description(): string | undefined

Defined in: packages/core/src/domain/entities/change.ts:231

Optional free-text description of the change's purpose.

Returns

string | undefined


hasEverReachedImplementing

Get Signature

get hasEverReachedImplementing(): boolean

Defined in: packages/core/src/domain/entities/change.ts:301

Whether the change has ever reached the implementing lifecycle state.

Derived from the append-only history by scanning for any transitioned event whose to field is 'implementing'. This is a temporary pragmatic heuristic until specd can detect whether a change has actually modified code files.

The signal is historical, not state-based — it remains true once reached regardless of subsequent state transitions.

Returns

boolean


history

Get Signature

get history(): readonly ChangeEvent[]

Defined in: packages/core/src/domain/entities/change.ts:260

Read-only view of the append-only event history.

Returns

readonly ChangeEvent[]


isArchivable

Get Signature

get isArchivable(): boolean

Defined in: packages/core/src/domain/entities/change.ts:371

Whether this change is in archivable or archiving state and may be archived.

Returns

boolean


isDrafted

Get Signature

get isDrafted(): boolean

Defined in: packages/core/src/domain/entities/change.ts:280

Whether the change is currently shelved in drafts/. Derived from the most recent drafted or restored event.

Returns

boolean


name

Get Signature

get name(): string

Defined in: packages/core/src/domain/entities/change.ts:221

Unique slug name identifying this change.

Returns

string


schemaName

Get Signature

get schemaName(): string

Defined in: packages/core/src/domain/entities/change.ts:236

Schema name recorded at creation time, derived from the created history event.

Returns

string


schemaVersion

Get Signature

get schemaVersion(): number

Defined in: packages/core/src/domain/entities/change.ts:241

Schema version recorded at creation time, derived from the created history event.

Returns

number


specDependsOn

Get Signature

get specDependsOn(): ReadonlyMap<string, readonly string[]>

Defined in: packages/core/src/domain/entities/change.ts:344

Per-spec declared dependencies, keyed by spec ID.

Used by CompileContext as the highest-priority source for dependsOn resolution. Not subject to approval invalidation.

Returns

ReadonlyMap<string, readonly string[]>


specIds

Get Signature

get specIds(): readonly string[]

Defined in: packages/core/src/domain/entities/change.ts:255

Current snapshot of spec paths being created or modified.

Returns

readonly string[]


state

Get Signature

get state(): ChangeState

Defined in: packages/core/src/domain/entities/change.ts:268

The current lifecycle state, derived from the most recent transitioned event's to field. Returns 'drafting' if no transitioned event exists.

Returns

ChangeState


workspaces

Get Signature

get workspaces(): readonly string[]

Defined in: packages/core/src/domain/entities/change.ts:246

Workspace IDs derived from specIds at runtime.

Returns

readonly string[]

Methods

assertArchivable()

assertArchivable(): void

Defined in: packages/core/src/domain/entities/change.ts:700

Asserts that this change is in archivable state.

Returns

void

Throws

If the change is not in archivable state


discard()

discard(reason, actor, supersededBy?, force?): void

Defined in: packages/core/src/domain/entities/change.ts:636

Permanently abandons the change, appending a discarded event.

If the change has ever reached implementing, discarding is blocked by default because implementation may already exist and abandoning the workflow would risk leaving permanent specs and code out of sync. Pass force: true to bypass this guard intentionally.

Parameters

reason

string

Mandatory explanation for discarding

actor

ActorIdentity

Identity of the person discarding the change

supersededBy?

readonly string[]

Optional list of change names that replace this one

force?

boolean

Explicit override for the historical implementation guard

Returns

void

Throws

If the change has ever reached implementing and force is not true


draft()

draft(actor, reason?, force?): void

Defined in: packages/core/src/domain/entities/change.ts:601

Shelves this change to drafts/, appending a drafted event.

If the change has ever reached implementing, drafting is blocked by default because implementation may already exist and shelving the change would risk leaving permanent specs and code out of sync. Pass force: true to bypass this guard intentionally.

Parameters

actor

ActorIdentity

Identity of the person shelving the change

reason?

string

Optional explanation for shelving

force?

boolean

Explicit override for the historical implementation guard

Returns

void

Throws

If the change has ever reached implementing and force is not true


effectiveStatus()

effectiveStatus(type): ArtifactStatus

Defined in: packages/core/src/domain/entities/change.ts:393

Computes the effective artifact status for type, cascading through the dependency graph to reflect blocking dependencies.

An artifact whose own hash matches its validatedHash is still reported as in-progress if any artifact in its requires chain is neither complete nor skipped.

If a required dependency is in a review state (pending-review or drifted-pending-review), this artifact reports as pending-parent-artifact-review.

Includes cycle detection to prevent infinite recursion on circular artifact dependencies.

Parameters

type

string

The artifact type ID to evaluate

Returns

ArtifactStatus

The effective ArtifactStatus after dependency resolution


findBlockingParent()

findBlockingParent(type): { artifactId: string; status: ArtifactStatus; } | null

Defined in: packages/core/src/domain/entities/change.ts:404

Finds the first upstream parent artifact that is causing a recursive block for the given artifact type.

Parameters

type

string

The artifact type ID to evaluate

Returns

{ artifactId: string; status: ArtifactStatus; } | null

The ID and status of the blocking parent, or null if not recursively blocked


getArtifact()

getArtifact(type): ChangeArtifact | null

Defined in: packages/core/src/domain/entities/change.ts:867

Returns the artifact of the given type, or null if not present.

Parameters

type

string

The artifact type ID to look up

Returns

ChangeArtifact | null

The artifact, or null if not found


invalidate()

invalidate(cause, actor, message?, affectedArtifacts?): void

Defined in: packages/core/src/domain/entities/change.ts:500

Records an invalidation, appending an invalidated event followed by a transitioned event rolling back to designing.

Called when specIds or artifact content changes and supersedes any active spec approval or signoff.

Parameters

cause

"spec-change" | "artifact-drift" | "artifact-review-required" | "spec-overlap-conflict"

The reason for invalidation

actor

ActorIdentity

Identity of the actor triggering the change

message?

string = 'Invalidated because artifacts require review.'

Human-readable invalidation summary

affectedArtifacts?

readonly InvalidatedArtifactEntry[] = ...

Artifact/file payload that triggered the invalidation

Returns

void


recordArtifactSkipped()

recordArtifactSkipped(artifactId, actor, reason?): void

Defined in: packages/core/src/domain/entities/change.ts:579

Records that an optional artifact was explicitly skipped.

Parameters

artifactId

string

The artifact type ID that was skipped

actor

ActorIdentity

Identity of the actor skipping the artifact

reason?

string

Optional explanation for skipping

Returns

void


recordSignoff()

recordSignoff(reason, artifactHashes, actor): void

Defined in: packages/core/src/domain/entities/change.ts:564

Records that the signoff gate has been passed.

Parameters

reason

string

Free-text rationale for the sign-off

artifactHashes

Record<string, string>

Hashes of the artifacts reviewed during sign-off

actor

ActorIdentity

Identity of the approver

Returns

void


recordSpecApproval()

recordSpecApproval(reason, artifactHashes, actor): void

Defined in: packages/core/src/domain/entities/change.ts:549

Records that the spec approval gate has been passed.

Parameters

reason

string

Free-text rationale for the approval

artifactHashes

Record<string, string>

Hashes of the artifacts reviewed during approval

actor

ActorIdentity

Identity of the approver

Returns

void


removeSpecDependsOn()

removeSpecDependsOn(specId): void

Defined in: packages/core/src/domain/entities/change.ts:366

Removes the declared dependencies entry for a single spec.

Parameters

specId

string

The spec whose dependencies to remove

Returns

void


restore()

restore(actor): void

Defined in: packages/core/src/domain/entities/change.ts:617

Recovers a drafted change back to changes/, appending a restored event.

Parameters

actor

ActorIdentity

Identity of the person restoring the change

Returns

void


setArtifact()

setArtifact(artifact): void

Defined in: packages/core/src/domain/entities/change.ts:857

Adds or replaces an artifact on this change, keyed by its type.

Parameters

artifact

ChangeArtifact

The artifact to attach

Returns

void


setSpecDependsOn()

setSpecDependsOn(specId, deps): void

Defined in: packages/core/src/domain/entities/change.ts:357

Sets (replaces) the declared dependencies for a single spec.

Does not trigger invalidation — specDependsOn is advisory, not spec content.

Parameters

specId

string

The spec whose dependencies to set

deps

readonly string[]

The new dependency list

Returns

void


syncArtifacts()

syncArtifacts(artifactTypes, specExistence?): boolean

Defined in: packages/core/src/domain/entities/change.ts:729

Synchronises the artifact map against the current schema artifact types and spec IDs.

For each artifact type:

  • Creates the ChangeArtifact if missing
  • For scope: 'change': ensures one ArtifactFile keyed by the type id
  • For scope: 'spec': ensures one ArtifactFile per specId
  • Removes files for specIds no longer in the change
  • Removes artifacts for types no longer in the schema
  • Preserves existing validatedHash and state for surviving entries, including filename normalization

If the sync produces any changes, an artifacts-synced event is appended to the history.

Pure method with no I/O. Called by the repository layer on every get() and save() to keep the artifact map in sync with schema x specIds.

Parameters

artifactTypes

readonly ArtifactType[]

The resolved artifact types from the active schema

specExistence?

ReadonlyMap<string, boolean>

Optional precomputed spec-existence map by specId

Returns

boolean

true if any changes were made, false if the artifact map was already in sync


transition()

transition(to, actor): void

Defined in: packages/core/src/domain/entities/change.ts:480

Attempts a lifecycle state transition, appending a transitioned event.

Parameters

to

ChangeState

The target state

actor

ActorIdentity

Identity of the actor performing the transition

Returns

void

Throws

If the transition is not permitted


updateDescription()

updateDescription(description, actor): void

Defined in: packages/core/src/domain/entities/change.ts:685

Updates the description of this change. Does NOT trigger invalidation — only updates metadata.

Parameters

description

string

The new description

actor

ActorIdentity

Identity of the actor making the change

Returns

void


updateSpecIds()

updateSpecIds(specIds, actor): void

Defined in: packages/core/src/domain/entities/change.ts:661

Updates the spec ID list and appends an invalidation.

Any modification to specIds always appends an invalidated event followed by a transitioned event rolling back to designing.

Parameters

specIds

readonly string[]

The new spec paths

actor

ActorIdentity

Identity of the actor making the change

Returns

void