Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
{
"name": "govctl",
"description": "Governed workflow skills, reviewer agents, and enforcement hooks for govctl",
"version": "0.10.0",
"version": "0.10.1",
"source": "./.claude",
"author": {
"name": "govctl-org"
Expand Down
2 changes: 1 addition & 1 deletion .claude/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "govctl",
"version": "0.10.0",
"version": "0.10.1",
"description": "Governed workflow skills, reviewer agents, and enforcement hooks for govctl"
}
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,26 @@ Release entries are curated summaries for readers. Work item traceability remain

## [Unreleased]

## [0.10.1] - 2026-06-29

0.10.1 is a lifecycle correctness and write-safety patch. Clause supersession
now preserves direct history across replacement chains, validates replacement
targets consistently, and avoids partial updates when lifecycle commands fail.

### Fixed

- Clause supersession now preserves direct replacement history, so chains such
as `A -> B -> C` remain valid after B is superseded or deprecated.
- `govctl clause supersede` now accepts active same-RFC shorthand and qualified
cross-RFC replacements while rejecting missing, self-referential, and cyclic
targets.
- Supersession validation now handles very large acyclic graphs without
exhausting the call stack.
- RFC bump and finalize operations now stop before making changes when a
pending clause cannot be read or parsed.
- Lifecycle writes are now failure-atomic: failed multi-file operations restore
earlier changes, and diagnostics report any incomplete rollback.

## [0.10.0] - 2026-06-17

0.10.0 tightens loop execution semantics and governance validation. Loop rounds
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "govctl"
version = "0.10.0"
version = "0.10.1"
edition = "2024"
rust-version = "1.96"
description = "Project governance CLI for RFC, ADR, and Work Item management"
Expand Down
62 changes: 57 additions & 5 deletions docs/rfc/RFC-0001.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
<!-- GENERATED: do not edit. Source: RFC-0001 -->
<!-- SIGNATURE: sha256:4dd4bee2eed79a129762feca923cde2094cf323fef5734784529c43a6c270572 -->
<!-- SIGNATURE: sha256:1ec031f45ae7ff70a8257a1b32bbf048c58d003be81e56825115c888a08e5f01 -->

# RFC-0001: Lifecycle State Machines

> **Version:** 0.3.0 | **Status:** normative | **Phase:** stable
> **Version:** 0.4.2 | **Status:** normative | **Phase:** stable

---

Expand Down Expand Up @@ -148,9 +148,28 @@ Invalid transitions (MUST be rejected):
- deprecated → active (no resurrection)
- superseded → any (superseded is terminal)

When a clause is superseded:
- The `superseded_by` field MUST be set to the ID of the replacing clause
- The replacing clause SHOULD have a `since` field indicating the version it was introduced
A clause supersession transition has the following preconditions:
- The replacing clause MUST exist.
- The replacing clause MUST be active when the transition is performed.
- The replacing clause MUST be different from the clause being superseded.
- A replacing clause in another RFC MUST be identified by its qualified `RFC-NNNN:C-NAME` ID.

The transition MUST be rejected if any required precondition is not satisfied.

After a successful clause supersession transition:
- The source clause status MUST be `superseded`.
- The source clause `superseded_by` field MUST identify the direct replacing clause selected for that transition.
- The replacing clause SHOULD have a `since` field indicating the version it was introduced.

Every populated `superseded_by` field defines a directed edge from its source clause to its target clause. The clause supersession graph consists of these edges across every RFC in the governed project.

Repository validation MUST reject an edge whose target clause does not exist. Repository validation MUST NOT reject an existing edge solely because its target clause was later deprecated or superseded.

If clause A identifies clause B as its direct replacement and B is later replaced by clause C, B MUST identify C as its direct replacement. Clause A MUST continue to identify B. The clause supersession graph MUST NOT contain a cycle. Repository validation MUST reject a graph that contains a cycle.

**Rationale:**

Transition-time checks ensure that a newly selected replacement is in effect. Persisted edges record historical direct replacements, so later clause evolution does not invalidate or erase prior provenance. Qualified IDs allow the same model to represent unambiguous replacements across RFC boundaries.

> **Tags:** `lifecycle`

Expand Down Expand Up @@ -194,6 +213,39 @@ Future gates MAY be added via RFC amendment, but MUST NOT break existing valid w

## Changelog

### v0.4.2 (2026-06-28)

Split direct-edge preservation requirements

#### Changed

- Express B-to-C insertion and A-to-B preservation as separate testable obligations

### v0.4.1 (2026-06-28)

Clarify clause supersession validation boundaries

#### Added

- Define project-wide direct-edge graph semantics and explicit rejection behavior for missing targets and cycles

#### Changed

- Separate transition-time replacement checks from persisted graph validation
- Document the distinct-target rule and preserve historical edges after target status changes

### v0.4.0 (2026-06-28)

Define direct clause supersession chains

#### Added

- Require active transition targets, qualified cross-RFC references, and acyclic supersession graphs

#### Changed

- Treat superseded_by as a persistent direct replacement edge

### v0.3.0 (2026-03-17)

Add executable verification gates to work completion
Expand Down
25 changes: 19 additions & 6 deletions docs/rfc/RFC-0002.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
<!-- GENERATED: do not edit. Source: RFC-0002 -->
<!-- SIGNATURE: sha256:10d3e6d4fb3468159ccb4d1bb48e79bac70dee20a76c213fd8e5f1647cd502f6 -->
<!-- SIGNATURE: sha256:6897d0b025a8fb3e5fcb2eb7e35e1f58619b567237320b823b6378cfa6b46021 -->

# RFC-0002: CLI Resource Model and Command Architecture

> **Version:** 0.10.3 | **Status:** normative | **Phase:** test
> **Version:** 0.10.4 | **Status:** normative | **Phase:** test

---

Expand Down Expand Up @@ -322,9 +322,14 @@ Resource-specific lifecycle verbs implement state transitions defined in [RFC-00

1. All lifecycle verbs MUST validate transitions per [RFC-0001](../rfc/RFC-0001.md)
2. All lifecycle verbs MUST update timestamp fields where applicable
3. All lifecycle verbs MUST be atomic (no partial state changes)
4. All lifecycle verbs MUST support global `--dry-run` flag
5. Invalid transitions MUST error with clear explanation of valid transitions
3. Except for the rollback-failure path in requirement 4, a lifecycle invocation that handles an error and completes through govctl's normal error path with a non-zero exit status MUST restore every governed artifact changed by that invocation byte-for-byte before returning
4. If an I/O or storage error prevents complete restoration, the invocation MUST return a non-zero exit status
5. The rollback-failure diagnostic MUST contain the term `rollback`
6. The rollback-failure diagnostic MUST state that governed-artifact restoration may be incomplete
7. The restoration baseline is the artifact content observed after the invocation acquires its exclusive write scope under [RFC-0004:C-SCOPE](../rfc/RFC-0004.md#rfc-0004c-scope) and before its first governed-artifact mutation
8. Unexpected process termination before normal error completion, host failure, and the rollback-failure path in requirement 4 are outside the lifecycle atomicity guarantee
9. All lifecycle verbs MUST support global `--dry-run` flag
10. Invalid transitions MUST error with clear explanation of valid transitions

**Rationale:**

Expand All @@ -334,7 +339,7 @@ Lifecycle verbs are resource-specific because:
- Work items have gate conditions (acceptance criteria)
- Each resource has different valid transitions

Scoping these verbs to resources makes their applicability explicit and prevents confusion.
Scoping these verbs to resources makes their applicability explicit and prevents confusion. Lifecycle atomicity defines the byte content observed when an invocation completes through its normal error path; it does not require a crash-recovery subsystem for the flat-file artifact store.

> **Tags:** `cli`, `lifecycle`

Expand Down Expand Up @@ -736,6 +741,14 @@ Search is discovery across the governance corpus, not a resource-specific CRUD o

## Changelog

### v0.10.4 (2026-06-28)

Clarify lifecycle failure atomicity boundary

#### Changed

- define byte-for-byte restoration for normally returned lifecycle errors and explicit rollback-failure boundaries

### v0.10.3 (2026-06-15)

Reject empty RFC version bumps
Expand Down
89 changes: 89 additions & 0 deletions gov/adr/ADR-0050-preserve-direct-clause-supersession-chains.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
#:schema ../schema/adr.schema.json

[govctl]
id = "ADR-0050"
title = "Preserve direct clause supersession chains"
status = "accepted"
date = "2026-06-28"
refs = [
"RFC-0001:C-CLAUSE-STATUS",
"RFC-0002:C-LIFECYCLE-VERBS",
]
tags = [
"lifecycle",
"validation",
]

[content]
context = """
[[RFC-0001:C-CLAUSE-STATUS]] records the clause that replaced a superseded clause. Existing validation interpreted that reference as a permanently active destination, so a valid `A -> B` replacement became invalid after `B` was replaced by `C`.

### Problem Statement

The stored relation needs an unambiguous meaning that supports repeated clause evolution without rewriting or invalidating prior history.

### Constraints

- Supersession history must remain auditable.
- A lifecycle transition should update only the clause being transitioned.
- Malformed references and cycles must remain detectable.
- Qualified clause IDs already provide an unambiguous representation for cross-RFC references.

### Options Considered

The discussion considered preserving direct historical edges, flattening all predecessors to the latest replacement, and retaining the active-target invariant that prevents chains."""
decision = """
We will preserve each `superseded_by` value as the direct replacement selected when its source clause was superseded. Later replacements extend the chain instead of rewriting earlier clauses because:

1. **Auditability:** Direct edges retain the exact sequence of clause evolution.
2. **Write locality:** Each transition changes only its source clause instead of rewriting every predecessor.
3. **Composability:** The same relation represents repeated and cross-RFC replacement without a second storage model.

This separates two concerns: transition-time eligibility determines whether a new replacement edge may be created, while repository validation determines whether persisted history is structurally sound. Cross-RFC replacements use qualified clause IDs so the edge remains unambiguous."""
consequences = """
### Positive

- Historical provenance remains intact across any number of replacements.
- Superseding a clause requires only a local write to that clause.
- The same model supports both same-RFC and qualified cross-RFC replacements.

### Negative

- Resolving the latest replacement can require following multiple direct edges.
- Malformed direct-edge histories can contain cycles or unresolved references.

### Neutral

- A chain may end at a clause that is no longer active; this describes history rather than asserting that an active replacement currently exists."""

[[content.alternatives]]
text = "Preserve each direct replacement as a historical edge and resolve later replacements by traversing the chain"
status = "accepted"
pros = [
"Retains the exact sequence of clause evolution",
"Each transition changes only its source clause",
]
cons = [
"Consumers that need the latest replacement must traverse the chain",
"Validation must detect self-references and cycles",
]

[[content.alternatives]]
text = "Flatten predecessors to the newest replacement whenever another clause is superseded"
status = "rejected"
pros = ["A stored reference always points directly to the latest replacement"]
cons = [
"Destroys the direct replacement history",
"Requires multi-file rewrites for one lifecycle transition",
]
rejection_reason = "The simpler lookup does not justify loss of provenance, wider writes, and additional merge conflicts"

[[content.alternatives]]
text = "Require every stored replacement target to remain active and disallow supersession chains"
status = "rejected"
pros = ["Keeps validation and lookup limited to one edge"]
cons = [
"A later replacement invalidates previously valid history",
"Prevents normal repeated evolution of a clause contract",
]
rejection_reason = "It creates the issue being addressed and conflates transition-time eligibility with historical validity"
10 changes: 10 additions & 0 deletions gov/releases.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@

[govctl]

[[releases]]
version = "0.10.1"
date = "2026-06-29"
refs = [
"WI-2026-06-28-001",
"WI-2026-06-28-002",
"WI-2026-06-28-003",
"WI-2026-06-28-004",
]

[[releases]]
version = "0.10.0"
date = "2026-06-17"
Expand Down
25 changes: 22 additions & 3 deletions gov/rfc/RFC-0001/clauses/C-CLAUSE-STATUS.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,25 @@ Invalid transitions (MUST be rejected):
- deprecated → active (no resurrection)
- superseded → any (superseded is terminal)

When a clause is superseded:
- The `superseded_by` field MUST be set to the ID of the replacing clause
- The replacing clause SHOULD have a `since` field indicating the version it was introduced"""
A clause supersession transition has the following preconditions:
- The replacing clause MUST exist.
- The replacing clause MUST be active when the transition is performed.
- The replacing clause MUST be different from the clause being superseded.
- A replacing clause in another RFC MUST be identified by its qualified `RFC-NNNN:C-NAME` ID.

The transition MUST be rejected if any required precondition is not satisfied.

After a successful clause supersession transition:
- The source clause status MUST be `superseded`.
- The source clause `superseded_by` field MUST identify the direct replacing clause selected for that transition.
- The replacing clause SHOULD have a `since` field indicating the version it was introduced.

Every populated `superseded_by` field defines a directed edge from its source clause to its target clause. The clause supersession graph consists of these edges across every RFC in the governed project.

Repository validation MUST reject an edge whose target clause does not exist. Repository validation MUST NOT reject an existing edge solely because its target clause was later deprecated or superseded.

If clause A identifies clause B as its direct replacement and B is later replaced by clause C, B MUST identify C as its direct replacement. Clause A MUST continue to identify B. The clause supersession graph MUST NOT contain a cycle. Repository validation MUST reject a graph that contains a cycle.

**Rationale:**

Transition-time checks ensure that a newly selected replacement is in effect. Persisted edges record historical direct replacements, so later clause evolution does not invalidate or erase prior provenance. Qualified IDs allow the same model to represent unambiguous replacements across RFC boundaries."""
29 changes: 26 additions & 3 deletions gov/rfc/RFC-0001/rfc.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,17 @@
[govctl]
id = "RFC-0001"
title = "Lifecycle State Machines"
version = "0.3.0"
version = "0.4.2"
status = "normative"
phase = "stable"
owners = ["@govctl-org"]
created = "2026-01-17"
updated = "2026-03-17"
updated = "2026-06-28"
tags = [
"core",
"lifecycle",
]
signature = "a914b5def2a5a95e84ae3fd0d26f81b166e1fc218b204a9aabf3a948ae662bbc"
signature = "9bf9e3a853547590af3b22db2ad5c8ae4296dae6b79fe652a8a2cea9ba5085a1"

[[sections]]
title = "Summary"
Expand All @@ -30,6 +30,29 @@ clauses = [
"clauses/C-GATE-CONDITIONS.toml",
]

[[changelog]]
version = "0.4.2"
date = "2026-06-28"
notes = "Split direct-edge preservation requirements"
changed = ["Express B-to-C insertion and A-to-B preservation as separate testable obligations"]

[[changelog]]
version = "0.4.1"
date = "2026-06-28"
notes = "Clarify clause supersession validation boundaries"
added = ["Define project-wide direct-edge graph semantics and explicit rejection behavior for missing targets and cycles"]
changed = [
"Separate transition-time replacement checks from persisted graph validation",
"Document the distinct-target rule and preserve historical edges after target status changes",
]

[[changelog]]
version = "0.4.0"
date = "2026-06-28"
notes = "Define direct clause supersession chains"
added = ["Require active transition targets, qualified cross-RFC references, and acyclic supersession graphs"]
changed = ["Treat superseded_by as a persistent direct replacement edge"]

[[changelog]]
version = "0.3.0"
date = "2026-03-17"
Expand Down
Loading
Loading