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
20 changes: 11 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ Durable planning intent for coding agents.
**A plan is immutable. Planning is continuous.**

Compass stores plans as a chain of immutable versions. You never edit a plan —
you revise it, which appends a new version naming its predecessor and stating
you revise it, which appends a new version naming its parent and stating
*why* intent changed. The plan at the tip is a disposable guess; the chain of
reasons that produced it is what compounds.

A plan is a TypeScript module. A step is a named declaration, a dependency is a
reference to another step, and a revision is a function of its predecessor. See
reference to another step, and a revision is a function of its parent. See
[`examples/`](./examples/) for three worked plans, and
[`context/`](./context/) for why it is shaped this way.

Expand Down Expand Up @@ -89,7 +89,7 @@ validates the *structure* of a criterion and stays neutral about what it means.
## "Isn't this just git?"

Its object model is close to git's: immutable snapshots naming their
predecessors, a required message per change, divergence as a legitimate state,
parents, a required message per change, divergence as a legitimate state,
reconciliation with multiple parents. If you're thinking *why not a `plan.md` and
good commit messages* — that gets you a lot of this.

Expand All @@ -113,14 +113,16 @@ you resolve by hand. That trade is recorded, not hidden:
## How it is stored

```
catalog/plans/<plan>/versions/<seq>-<hash>.ts immutable, mode 0444
catalog/plans/<plan>/events/<ts>-<id>... append-only
catalog/plans/<planref>/versions/<seq>-<hash>.ts immutable, mode 0444
catalog/plans/<planref>/events/<ts>-<id>... append-only
```

A committed version *is* the module you wrote, stored unchanged and named by the
hash of its bytes. There is no separate rendered form, so nothing can drift from
A `<planref>` is the Plan's identity: the content hash of its origin — the first
version — so a Plan is named by nothing and filed under a value it derives
(decision 0017). A committed version *is* the module you wrote, stored unchanged
and named by the hash of its bytes. There is no separate rendered form, so nothing can drift from
what you authored, and altering a committed version changes its hash — which is
how tampering is caught. A revision imports its predecessor by that hashed name,
how tampering is caught. A revision imports its parent by that hashed name,
so the lineage is a real module graph.

There is no head file. That removes the cell concurrent writers would contend
Expand All @@ -129,7 +131,7 @@ flight, so convergence comes from the sync layer instead. Point a file-sync
mechanism with union / newer-wins / no-delete semantics at the catalog. Without
one, Compass runs single-machine.

Reading a plan evaluates it — and, through its imports, its predecessors and any
Reading a plan evaluates it — and, through its imports, its parents and any
plans it references. That evaluation runs against a locked-down engine with no
clock, no filesystem, no network, and no way to run code the plan didn't declare;
it has to be, because under replication those modules were authored on another
Expand Down
15 changes: 8 additions & 7 deletions context/.decisions/0001-compass-is-an-independent-authority.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,10 @@ not planning. Every later question — where plans live, how they replicate, wha
happens when the bus changes shape — inherits that coupling, and none of them
can be answered on planning's own terms.

Defining opaque references, an idempotent port, and stable receipts before the
first authoritative write costs boundary work now and avoids an identity
migration later. Because no live plan state exists, that cost is at its minimum.
Defining opaque references, an idempotent write path, and stable version
identities before the first authoritative write costs boundary work now and
avoids an identity migration later. Because no live plan state exists, that cost
is at its minimum.

## Options

Expand All @@ -41,9 +42,9 @@ Compass is a standalone authority. Its core owns goals, Steps, dependencies,
acceptance, revisions, and accepted progress. It depends on no other tool's
paths, schemas, event envelopes, or storage layouts.

Other systems compose with Compass through opaque references, mutations,
queries, and receipts. They may record operational facts referencing a
Receipt, but such facts never become Compass state. Compass exposes no
Other systems compose with Compass through opaque references, commits,
queries, and version identities. They may record operational facts referencing a
Plan Version, but such facts never become Compass state. Compass exposes no
subcommand inside another tool's CLI namespace; a facade would make the
namespace imply authority.

Expand All @@ -52,6 +53,6 @@ namespace imply authority.
- The Catalog root is configuration, not a compiled-in path.
- Composition is one-directional: Compass never reads another tool to
reconstruct its own state.
- Surrounding systems store opaque refs and receipts only.
- Surrounding systems store opaque refs and version identities only.
- Moving Compass behind a different transport or process changes packaging, not
identifiers or persisted semantics.
90 changes: 90 additions & 0 deletions context/.decisions/0017-a-plans-identity-is-its-origin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# A Plan's identity is its origin

Status: accepted

Resolves DQ04. Completes [0012](./0012-intent-is-authored-as-code-and-identity-is-declared.md),
which settled Step identity and removed the minting that had supplied Plan
references too, without saying what replaced them.

## Context

A Step's identity is the name it is declared under (0012). A Plan has no such
declaration site — it is not an export in another module — so that mechanism does
not transfer. Minting is gone. In its absence the implementation fell back on the
catalog directory as the Plan's handle, which makes identity a filesystem
location: exactly what the ontology's PlanId definition forbids, and it means moving or
misfiling a version silently changes which Plan it belongs to.

Three candidates were on the table: a declared name, a path segment, and the
content hash of the first version.

## Evidence and Argument

The anti-minting principle (CMP-R10) is really about non-determinism — a random
value a retry regenerates. A *chosen* name does not have that defect: it is
deterministic and idempotent under retry. So a Plan being named is not the sin
minting was. The sin the implementation committed is a different one: identity in
the *path* rather than in the *content*, which no principle here permits.

That narrows it to two honest options — a name declared *in the content*, or the
content itself — and a required human summary decides between them. A Plan
already carries a required `goal`, which is human-readable and surfaced
everywhere a Plan is listed or referenced. Human intuition is therefore already
covered without the identity carrying it. Once identity does not have to be
readable, the derived option dominates: making it a declared name would overload
one value with two jobs and reintroduce an assertion that can be typed wrong,
for a readability that `goal` already provides.

So identity is the hash of the **origin** — the single predecessor-less version.
It is fully derived (CMP-R10), encodes no location (its PlanId ontology entry), cannot collide,
and makes "the same Plan" a content fact: two versions belong to the same Plan
iff they descend from the same origin. There is a clean invariant in it — the
origin version's own identity *is* the PlanId, since both are the hash of the
same bytes.

The one objection DQ04 itself raised — a hash is unavailable before the first
version exists, which seems to collide with CMP-R11 (starting must be trivial) —
does not hold. Authoring a Plan references nothing by PlanId: a first version
declares steps and a goal, imports only `compass`, and names no plan identity.
The ref comes into being when the origin is committed, which is exactly when a
Plan first exists. Starting stays a single command; the author never types or
needs a ref.

## Options

| Option | Tradeoffs |
| --- | --- |
| Origin content hash | Fully derived, collision-free, location-independent, "same Plan" is a content fact; opaque, and unavailable until the origin is committed |
| Name declared in `plan()` | Readable and idempotent; asserts an identity that `goal` already makes readable, and can be typed wrong |
| Catalog path segment | Simplest and matches a naive implementation; makes identity a location, which the PlanId ontology entry forbids, so a moved file changes identity |

## Decision

A Plan's identity, its PlanId, is the content hash of its origin version — the
version with no predecessor. It is derived, never declared and never minted.

Two versions are the same Plan when they share an origin. The catalog files a
Plan under its PlanId, and a version whose derived Plan does not match where it
is filed is rejected rather than reinterpreted, on the same terms as a version
whose content does not match its own name.

`goal` is required on every version and is the human handle: what `compass`
shows in listings and references in place of the hash. Identity is machine-facing
and derived; readability is human-facing and lives in `goal`. Neither carries the
other's job.

## Consequences

- The PlanId is not known until the origin is committed. This does not affect
starting or authoring, which reference no PlanId; it affects only how a Plan
is addressed afterwards, where `goal` is the readable handle and the hash is
the exact one.
- The origin version's identity and the PlanId are the same hash. A Plan is,
precisely, its first stated intent.
- Cross-plan references resolve to a PlanId and so are content-addressed; their
import paths are opaque, which is acceptable because they are machine-written.
- Renaming is not an operation. A Plan cannot be renamed because it was never
named; its `goal` can be revised like any other intent, and its identity is
unaffected because identity is the origin, not the goal.
- Moving or misfiling a version cannot change its Plan: the Plan is derived from
the origin it descends from, and a mismatch with where it is filed is rejected.
45 changes: 30 additions & 15 deletions context/01-data-model/requirements.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
### Lineage

- **CMP.DM-R01 A Plan is a lineage of versions.** Each version records its
predecessors, so the history of intent is reconstructible from the versions
parents, so the history of intent is reconstructible from the versions
alone. _refines: CMP-R02._

- **CMP.DM-R02 Every version carries a Rationale.** The reason for the revision
Expand All @@ -22,10 +22,10 @@
_refines: CMP-R02, CMP-R04._

- **CMP.DM-R04 Divergence is a state, not an error.** Versions sharing a
predecessor are both valid and both reported. _refines: CMP-R04._
parent are both valid and both reported. _refines: CMP-R04._

- **CMP.DM-R05 Divergence resolves by authorship.** Reconciliation is an
ordinary version naming every predecessor it reconciles, with its own
ordinary version naming every parent it reconciles, with its own
Rationale. Nothing reconciles automatically, and a reconciliation may itself
diverge. _refines: CMP-R03, CMP-R04._

Expand All @@ -42,15 +42,15 @@
must be visible at the moment of retirement rather than discovered later
through readiness that never advances. _refines: CMP-R01._

- **CMP.DM-R06 An absent predecessor is not divergence.** A version whose
predecessor is unknown must be distinguished from one that disagrees. The
- **CMP.DM-R06 An absent parent is not divergence.** A version whose
parent is unknown must be distinguished from one that disagrees. The
first ordinarily means state is still arriving; treating it as the second
writes permanent intent to resolve a transient condition.
_refines: CMP-R04, CMP-R05._

- **CMP.DM-R06a An unreadable Plan is distinguished from an incomplete one.** A
Plan that cannot be evaluated because something it references is absent must
be reported as unresolved, distinctly from a version whose predecessor is
be reported as unresolved, distinctly from a version whose parent is
merely missing. The two look alike and are not: an incomplete lineage still
answers what the Plan says, while an unresolved Plan answers nothing at all.
Reporting the second as the first invites waiting for a repair that has
Expand All @@ -63,11 +63,11 @@
siblings, since neither observed the other, and where one version does precede
another the lineage already says so. _refines: CMP-R03, CMP-R04, CMP-R10._

- **CMP.DM-R07a Repeating a mutation does not repeat its effect.** A mutation
- **CMP.DM-R07a Repeating a Commit does not repeat its effect.** A Commit
applied twice produces one version. This must follow from the data — an
identical mutation yields an identical version, therefore the same identity —
identical Commit yields an identical version, therefore the same identity —
rather than from a token a caller supplies and could supply wrongly. It holds
only because a revision states its predecessor as part of its own content: a
only because a revision states its parent as part of its own content: a
base that were read at the moment of application would have moved by the time
a retry arrived, and the retry would differ from the attempt it repeats.
_refines: CMP-R02, CMP-R10._
Expand All @@ -80,7 +80,7 @@
The cost is that a deliberate non-change cannot be recorded.
_refines: CMP-R02, CMP-R03._

- **CMP.DM-R07c A revision carries its predecessor forward.** A revision is
- **CMP.DM-R07c A revision carries its parent forward.** A revision is
expressed against the version before it and can edit a Step, add one, or
retire one. It has no way to remove one. Dropping a Step is therefore not
something Compass detects and refuses but something a revision cannot express
Expand Down Expand Up @@ -125,6 +125,21 @@
incidental, because without it a committed Step can be re-identified while
every hash in the lineage stays constant. _refines: CMP-R02, CMP-R07._

- **CMP.DM-R17 A Plan's identity is its origin.** A Plan is identified by the
content hash of its origin — the one version with no parent. It is
derived, never declared and never minted, and encodes no location. Two
versions are the same Plan when they share an origin. A version whose derived
Plan disagrees with where it is filed is rejected, not reinterpreted.
_refines: CMP-R10, CMP-R02._

- **CMP.DM-R18 A goal is required and is the human handle.** Every version
states a goal, and the goal is what identifies a Plan to a person — surfaced
wherever a Plan is listed or referenced, in place of its hash. Identity is
derived and machine-facing; readability is the goal's job, so neither carries
the other's. A Plan is never renamed, because it was never named: its goal is
revised like any other intent, and its identity, being the origin, is
unaffected. _refines: CMP-R03, CMP-R11._

### Progress and acceptance

- **CMP.DM-R11 Progress is append-only.** Progress records never alter intent
Expand Down Expand Up @@ -190,13 +205,13 @@
### Readiness

- **CMP.DM-R14 Readiness is derived.** What can be worked on now follows from
the Step graph at head, accepted progress, and gates. It is part of the model,
not a projection over it. _refines: CMP-R01._
the Step graph at head, accepted progress, and each Step's acceptance
criterion. It is part of the model, not a projection over it. _refines: CMP-R01._

- **CMP.DM-R15 Readiness explains itself.** Every answer names the unsatisfied
dependencies and gates. An answer that cannot say why is neither trustworthy
nor debuggable, and this constrains what an acceptance criterion may express.
_refines: CMP-R01._
dependencies and unmet criteria. An answer that cannot say why is neither
trustworthy nor debuggable, and this constrains what an acceptance criterion
may express. _refines: CMP-R01._

- **CMP.DM-R16 Readiness is defined under divergence.** With more than one head
member, readiness is reported per member and labelled. It never selects a side
Expand Down
Loading
Loading