ruvnet/ruflo · error
Cannot remove immutable invariant
Error message
Cannot remove immutable invariant: ${id} What it means
MetaGovernor.removeInvariant(id) throws when the targeted constitutional invariant was registered with immutable: true. This is deliberate tamper protection: immutable invariants are the constitution's floor and the normal API offers no flag, override, or force option to remove them. Unknown IDs return false instead of throwing.
Solutions
- Do not remove it — express the change as a new invariant via addInvariant that supersedes the old rule
- Inspect governor.getInvariants() first and skip entries with immutable: true
- If you own the invariant definition and genuinely need removability, construct the MetaGovernor with that invariant registered as immutable: false from the start
- Reserve removeInvariant for non-immutable, operational invariants only
Example fix
// before
governor.removeInvariant('no-unverified-write'); // immutable -> throws
// after
const inv = governor.getInvariants().find(i => i.id === 'no-unverified-write');
if (inv?.immutable) {
governor.addInvariant({ ...inv, id: 'no-unverified-write-v2', description: 'Relaxed rule' });
} else {
governor.removeInvariant('no-unverified-write');
} Defensive patterns
Strategy: type-guard
Validate before calling
const target = governor.getInvariants().find(i => i.id === id);
if (!target) return false; // removeInvariant returns false for unknown ids
if (target.immutable) {
throw new Error(`Invariant ${id} is immutable; supersede it with addInvariant instead`);
}
return governor.removeInvariant(id); Type guard
function isRemovableInvariant(governor: MetaGovernor, id: string): boolean {
const inv = governor.getInvariants().find(i => i.id === id);
return inv !== undefined && inv.immutable === false;
} Prevention
- Model constitutional changes as new superseding invariants, not removals
- Register operational invariants with immutable: false if teardown must be possible
- Audit getInvariants() for immutable entries before writing cleanup code
When it happens
Trigger: Calling removeInvariant() on a core/default invariant flagged immutable; calling it on an invariant you yourself added with immutable: true; cleanup code that tries to sweep all invariants via getInvariants() without checking the flag.
Common situations: Governance tests that add invariants then try to tear them down; runtime code attempting to loosen constitutional constraints after deployment; refactors that assume all invariants are removable.
Related errors
- Cannot modify immutable invariant
- Amendment already resolved
- Amendment not found
- Amendment rate limit exceeded
- Cannot enact amendment with status
AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18).
Data as JSON: /api/errors/efb13e7527c153f3.
Report an issue: GitHub.
Appendix: source
Thrown at v3/@claude-flow/guidance/src/meta-governance.ts:328
}
/**
* Add a constitutional invariant
*/
addInvariant(invariant: ConstitutionalInvariant): void {
this.invariants.set(invariant.id, invariant);
}
/**
* Remove an invariant (only if not immutable)
*/
removeInvariant(id: string): boolean {
const invariant = this.invariants.get(id);
if (!invariant) {
return false;
}
if (invariant.immutable) {
throw new Error(`Cannot remove immutable invariant: ${id}`);
}
return this.invariants.delete(id);
}
/**
* Check all constitutional invariants against current state
*/
checkAllInvariants(state: GovernanceState): InvariantReport {
const results: Array<{ invariant: ConstitutionalInvariant; result: InvariantCheckResult }> = [];
let allHold = true;
for (const invariant of this.invariants.values()) {
const result = invariant.check(state);
results.push({ invariant, result });
if (!result.holds) {
allHold = false;
}
}View on GitHub (pinned to fa13ee4ad6)