Skip to content

Commit 8778b90

Browse files
refactor(compass): align impl vocabulary with the refined ontology
- Rename predecessor → parent across src and tests (helper fns, error strings, doc-comments, test names). The JSON already emitted `parents`; this closes the gap between the code's internal words and what it produces. - Rename receipt_json → commit_result_json: the output has no `receipt` key, and the ontology no longer defines Receipt — the returned value is the Plan Version. - Rewrite the readiness "On gates" note: the spec no longer says "dependencies and gates"; the ontology states the acceptance predicate *is* the gate. Frozen example modules under catalog/plans/*/versions/*.ts are untouched — they keep their period vocabulary, and the acceptance hash oracle (example_filenames_reproduce_from_source_bytes) confirms none drifted. cargo test: 15 passed. clippy + fmt clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent eb7ffe8 commit 8778b90

10 files changed

Lines changed: 83 additions & 88 deletions

File tree

context/.decisions/0001-compass-is-an-independent-authority.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,10 @@ not planning. Every later question — where plans live, how they replicate, wha
2323
happens when the bus changes shape — inherits that coupling, and none of them
2424
can be answered on planning's own terms.
2525

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

3031
## Options
3132

src/catalog.rs

Lines changed: 27 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@
1515
//! an error, never a warning.
1616
//!
1717
//! The identity is the hash of the raw bytes as they sit on disk. Because each
18-
//! version imports its predecessors by filename, and each filename carries a
18+
//! version imports its parents by filename, and each filename carries a
1919
//! hash of content, the lineage is walkable from the bytes alone: the parents of
2020
//! a version are the versions whose hash-prefix appears in its import
21-
//! specifiers. A prefix that resolves to no local file is a missing predecessor
21+
//! specifiers. A prefix that resolves to no local file is a missing parent
2222
//! (an orphan), repaired by waiting.
2323
2424
use crate::event::Event;
@@ -34,9 +34,9 @@ pub struct Admitted {
3434
pub hash: String,
3535
pub path: PathBuf,
3636
pub plan: String,
37-
/// A reading aid: one past the longest predecessor. Never a key.
37+
/// A reading aid: one past the longest parent. Never a key.
3838
pub seq: u64,
39-
/// Predecessor references: a full hash when the predecessor is present, or
39+
/// Parent references: a full hash when the parent is present, or
4040
/// the raw 12-hex prefix when it has not arrived (an orphan edge).
4141
pub parents: Vec<String>,
4242
}
@@ -186,7 +186,7 @@ pub fn load_plan(root: &Path, plan: &str) -> Result<PlanStore, String> {
186186
}
187187
}
188188

189-
// Resolve import-prefixes to full predecessor hashes among the siblings.
189+
// Resolve import-prefixes to full parent hashes among the siblings.
190190
let by_prefix: std::collections::HashMap<&str, &str> = raws
191191
.iter()
192192
.map(|r| (&r.hash[..crate::model::HASH_PREFIX_LEN], r.hash.as_str()))
@@ -290,7 +290,7 @@ fn admit_version(path: &Path, expected_plan: &str) -> Result<Raw, String> {
290290
}
291291

292292
let source = std::str::from_utf8(&bytes).map_err(|e| format!("not valid UTF-8: {e}"))?;
293-
let import_prefixes = predecessor_prefixes(source, path, expected_plan)?;
293+
let import_prefixes = parent_prefixes(source, path, expected_plan)?;
294294

295295
Ok(Raw {
296296
hash: actual,
@@ -301,23 +301,19 @@ fn admit_version(path: &Path, expected_plan: &str) -> Result<Raw, String> {
301301
})
302302
}
303303

304-
/// The hash-prefixes of the *predecessor* version files a module imports, read
305-
/// statically. A predecessor is a version of the SAME plan; a version of another
304+
/// The hash-prefixes of the *parent* version files a module imports, read
305+
/// statically. A parent is a version of the SAME plan; a version of another
306306
/// plan is a cross-plan reference (CMP.API-R05), not a parent, and is excluded
307-
/// from the lineage so it never shows as an orphan predecessor edge.
308-
fn predecessor_prefixes(
309-
source: &str,
310-
path: &Path,
311-
expected_plan: &str,
312-
) -> Result<Vec<String>, String> {
307+
/// from the lineage so it never shows as an orphan parent edge.
308+
fn parent_prefixes(source: &str, path: &Path, expected_plan: &str) -> Result<Vec<String>, String> {
313309
let specs = crate::eval::import_specifiers(source, path)
314310
.map_err(|e| format!("cannot read imports: {}", e.message()))?;
315311
let mut out = Vec::new();
316312
for spec in specs {
317-
// A predecessor import is a relative path to a version file.
313+
// A parent import is a relative path to a version file.
318314
let file = spec.rsplit('/').next().unwrap_or(&spec);
319315
if let Some((_seq, prefix)) = parse_filename(file) {
320-
// Only a same-plan version is a predecessor. A cross-plan reference
316+
// Only a same-plan version is a parent. A cross-plan reference
321317
// (target plan differs) is not part of this plan's lineage.
322318
match crate::eval::import_target_plan(path, &spec) {
323319
Some(other) if other != expected_plan => continue,
@@ -335,8 +331,8 @@ fn predecessor_prefixes(
335331
/// rejected — never reinterpreted into the Plan it was filed under — on the same
336332
/// terms as a version whose content hash disagrees with its own filename.
337333
///
338-
/// The origin is derived by walking resolved predecessor pointers within the
339-
/// store (no evaluation, no extra IO) to the predecessor-less ancestor. When the
334+
/// The origin is derived by walking resolved parent pointers within the
335+
/// store (no evaluation, no extra IO) to the parent-less ancestor. When the
340336
/// walk reaches an ancestor that is absent locally the version is an orphan, not
341337
/// a misfiling: its Plan cannot yet be confirmed, so it is left alone.
342338
fn reject_misfiled(store: &mut PlanStore, plan: &str) {
@@ -388,27 +384,27 @@ fn reject_misfiled(store: &mut PlanStore, plan: &str) {
388384
/// Derive a version's PlanId from its authored bytes (decision 0017).
389385
///
390386
/// A Plan's identity is the content hash of its origin — the single
391-
/// predecessor-less version. An origin (a module that imports no predecessor) is
387+
/// parent-less version. An origin (a module that imports no parent) is
392388
/// its own PlanId: the hash of its bytes, the same hash its version filename
393-
/// carries. A revision inherits its Plan from its predecessor: its origin is
394-
/// found by walking the predecessor imports back to the predecessor-less version,
389+
/// carries. A revision inherits its Plan from its parent: its origin is
390+
/// found by walking the parent imports back to the parent-less version,
395391
/// and hashing that. The operator names nothing; identity is derived, and the
396392
/// prefix width matches the version filenames' for consistency.
397393
pub fn derive_planid(path: &Path, source: &[u8]) -> Result<String, String> {
398394
let src = std::str::from_utf8(source)
399395
.map_err(|e| format!("{}: not valid UTF-8: {e}", path.display()))?;
400-
let origin_bytes = match sibling_predecessor_paths(path, src)?.into_iter().next() {
396+
let origin_bytes = match sibling_parent_paths(path, src)?.into_iter().next() {
401397
None => source.to_vec(),
402398
Some(pred) => walk_to_origin(&pred)?,
403399
};
404400
Ok(crate::sha256::sha256_hex(&origin_bytes)[..crate::model::HASH_PREFIX_LEN].to_string())
405401
}
406402

407-
/// The resolved paths of the *predecessor* version files a module imports — the
403+
/// The resolved paths of the *parent* version files a module imports — the
408404
/// same-plan siblings, sitting in the importer's own directory. A cross-plan
409-
/// reference resolves elsewhere and is not a predecessor, so it is excluded, as
405+
/// reference resolves elsewhere and is not a parent, so it is excluded, as
410406
/// is the `compass` prelude (which is not a version reference).
411-
fn sibling_predecessor_paths(path: &Path, source: &str) -> Result<Vec<PathBuf>, String> {
407+
fn sibling_parent_paths(path: &Path, source: &str) -> Result<Vec<PathBuf>, String> {
412408
let dir = path.parent().unwrap_or_else(|| Path::new("."));
413409
let specs = crate::eval::import_specifiers(source, path)
414410
.map_err(|e| format!("cannot read imports: {}", e.message()))?;
@@ -427,28 +423,26 @@ fn sibling_predecessor_paths(path: &Path, source: &str) -> Result<Vec<PathBuf>,
427423
Ok(out)
428424
}
429425

430-
/// Walk a predecessor chain to its origin and return the origin's raw bytes.
431-
/// Any predecessor of a version shares that version's origin, so following one
432-
/// predecessor at each step suffices.
426+
/// Walk a parent chain to its origin and return the origin's raw bytes.
427+
/// Any parent of a version shares that version's origin, so following one
428+
/// parent at each step suffices.
433429
fn walk_to_origin(pred: &Path) -> Result<Vec<u8>, String> {
434430
let mut current = pred.to_path_buf();
435431
let mut seen = std::collections::HashSet::new();
436432
loop {
437433
if !seen.insert(current.clone()) {
438-
return Err(
439-
"predecessor lineage forms a cycle; a plan's identity cannot be derived".into(),
440-
);
434+
return Err("parent lineage forms a cycle; a plan's identity cannot be derived".into());
441435
}
442436
let bytes = fs::read(&current).map_err(|_| {
443437
format!(
444-
"predecessor {} has not arrived: a plan's identity is its origin, which cannot be \
438+
"parent {} has not arrived: a plan's identity is its origin, which cannot be \
445439
derived until the origin is present (decision 0017)",
446440
current.display()
447441
)
448442
})?;
449443
let src = std::str::from_utf8(&bytes)
450444
.map_err(|e| format!("{}: not valid UTF-8: {e}", current.display()))?;
451-
match sibling_predecessor_paths(&current, src)?.into_iter().next() {
445+
match sibling_parent_paths(&current, src)?.into_iter().next() {
452446
None => return Ok(bytes),
453447
Some(next) => current = next,
454448
}

src/chain.rs

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@
88
//! The ontology is explicit and the distinction is the one Compass must not
99
//! get wrong:
1010
//!
11-
//! - **Divergence** — two or more versions share the same predecessor. Intent
11+
//! - **Divergence** — two or more versions share the same parent. Intent
1212
//! genuinely disagreed. Repaired by authoring a Reconciliation.
13-
//! - **Orphan** — a version whose predecessor is *absent locally*. Ordinarily
13+
//! - **Orphan** — a version whose parent is *absent locally*. Ordinarily
1414
//! replication is simply incomplete. Repaired by **waiting**.
1515
//!
1616
//! Reconciling around a version that is merely in-flight writes permanent
@@ -20,23 +20,23 @@
2020
//! A version can be both a head member and an orphan: decision 0002
2121
//! Amendment 1 describes receiving versions 1, 2 and 4, where 4's parent has
2222
//! not arrived. Head is then `{2, 4}` — and reporting that as divergence would
23-
//! be a lie, because 2 and 4 share no predecessor.
23+
//! be a lie, because 2 and 4 share no parent.
2424
2525
use crate::catalog::{Admitted, PlanStore};
2626
use std::collections::{BTreeMap, HashSet};
2727

28-
/// A version whose predecessor is not present locally.
28+
/// A version whose parent is not present locally.
2929
#[derive(Debug, Clone)]
3030
pub struct Orphan<'a> {
3131
pub version: &'a Admitted,
3232
/// Parent hashes that are absent.
3333
pub missing: Vec<String>,
3434
}
3535

36-
/// Two or more versions sharing a predecessor.
36+
/// Two or more versions sharing a parent.
3737
#[derive(Debug, Clone)]
3838
pub struct Divergence<'a> {
39-
/// The shared predecessor hash, or `None` when several root versions exist.
39+
/// The shared parent hash, or `None` when several root versions exist.
4040
pub parent: Option<String>,
4141
pub children: Vec<&'a Admitted>,
4242
/// Whether this divergence is still unresolved.
@@ -137,7 +137,7 @@ pub fn analyze(store: &PlanStore) -> Analysis<'_> {
137137
})
138138
.collect();
139139

140-
// Group by predecessor. Only predecessors that are actually present count:
140+
// Group by parent. Only parents that are actually present count:
141141
// two versions both naming an absent parent are two orphans, not a
142142
// divergence we can reason about.
143143
let mut by_parent: BTreeMap<&str, Vec<&Admitted>> = BTreeMap::new();
@@ -274,7 +274,7 @@ pub fn lineage<'a>(store: &'a PlanStore, hash: &str) -> Vec<&'a Admitted> {
274274
out
275275
}
276276

277-
/// The `seq` a new version should carry: one past the longest predecessor.
277+
/// The `seq` a new version should carry: one past the longest parent.
278278
pub fn next_seq(parents: &[&Admitted]) -> u64 {
279279
parents.iter().map(|p| p.seq).max().unwrap_or(0) + 1
280280
}
@@ -340,7 +340,7 @@ mod tests {
340340
}
341341

342342
#[test]
343-
fn a_missing_predecessor_is_an_orphan_not_a_divergence() {
343+
fn a_missing_parent_is_an_orphan_not_a_divergence() {
344344
// Decision 0002 Amendment 1: versions 1, 2 and 4 arrive; 3 has not.
345345
let a = v("pl_1000000000", 1, "first", vec![]);
346346
let b = v("pl_1000000000", 2, "second", vec![a.hash.clone()]);
@@ -353,7 +353,7 @@ mod tests {
353353
assert_eq!(an.head.len(), 2, "2 and 4 both lack a successor");
354354
assert!(
355355
!an.diverged(),
356-
"2 and 4 share no predecessor, so this is not divergence"
356+
"2 and 4 share no parent, so this is not divergence"
357357
);
358358
assert_eq!(an.orphans.len(), 1);
359359
assert_eq!(an.orphans[0].version.hash, d_hash);
@@ -364,7 +364,7 @@ mod tests {
364364

365365
#[test]
366366
fn two_versions_missing_the_same_parent_are_orphans_not_divergent() {
367-
// The shared predecessor is absent, so nothing local proves they
367+
// The shared parent is absent, so nothing local proves they
368368
// disagreed — only that replication is behind.
369369
let absent = "e".repeat(64);
370370
let a = v("pl_1000000000", 2, "one", vec![absent.clone()]);
@@ -523,7 +523,7 @@ mod tests {
523523
}
524524

525525
#[test]
526-
fn lineage_stops_at_an_absent_predecessor() {
526+
fn lineage_stops_at_an_absent_parent() {
527527
let absent = "d".repeat(64);
528528
let a = v("pl_1000000000", 2, "orphaned", vec![absent]);
529529
let tip = a.hash.clone();
@@ -532,7 +532,7 @@ mod tests {
532532
}
533533

534534
#[test]
535-
fn next_seq_follows_the_longest_predecessor() {
535+
fn next_seq_follows_the_longest_parent() {
536536
let short = v("pl_1000000000", 2, "short", vec![]);
537537
let long = v("pl_1000000000", 9, "long", vec![]);
538538
assert_eq!(next_seq(&[&short, &long]), 10);

src/cli.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,7 @@ compass commit <module.ts>
328328
- Committing content already present is a no-op success.
329329
- New content that revises nothing is refused, with a distinct message.
330330
- A module uses plan() for a first version, prior.revise({...}) for a
331-
revision, or reconcile({revises:[...]}) for a reconciliation. Predecessors
331+
revision, or reconcile({revises:[...]}) for a reconciliation. Parents
332332
are the version files it imports; the Plan is derived from the origin they
333333
descend from.
334334
"

src/cmd.rs

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,7 @@ fn cmd_commit(root: &Path, path: &Path) -> Result<Output, String> {
296296

297297
// A Plan's identity is derived from its origin (decision 0017): the operator
298298
// names nothing. An origin is its own PlanId; a revision inherits its Plan
299-
// from the predecessor it descends from.
299+
// from the parent it descends from.
300300
let plan = catalog::derive_planid(path, &source)?;
301301

302302
// Evaluate the authored module (imports resolve at its location).
@@ -315,11 +315,11 @@ fn cmd_commit(root: &Path, path: &Path) -> Result<Output, String> {
315315
.ok_or_else(|| "the module did not export a plan".to_string())?;
316316

317317
// Classify each version import (CMP.API-R05). An import of a version of the
318-
// SAME plan is a predecessor, resolved against this plan's store and made a
318+
// SAME plan is a parent, resolved against this plan's store and made a
319319
// parent of the new version. An import of ANOTHER plan's version is a
320320
// cross-plan reference: it must resolve against that plan's store (the other
321-
// version must be admitted), but it is not a predecessor and does not make
322-
// this commit "have an uncommitted predecessor".
321+
// version must be admitted), but it is not a parent and does not make
322+
// this commit "have an uncommitted parent".
323323
let store = catalog::load_plan(root, &plan)?;
324324
let mut parents: Vec<String> = Vec::new();
325325
for spec in crate::eval::import_specifiers(&source_str, path)
@@ -331,7 +331,7 @@ fn cmd_commit(root: &Path, path: &Path) -> Result<Output, String> {
331331
};
332332
// A target plan that differs from the current one is a cross-plan
333333
// reference; anything else (a sibling, or a version of this same plan)
334-
// is a predecessor.
334+
// is a parent.
335335
match crate::eval::import_target_plan(path, &spec) {
336336
Some(other) if other != plan => {
337337
let other_store = catalog::load_plan(root, &other)?;
@@ -346,7 +346,7 @@ fn cmd_commit(root: &Path, path: &Path) -> Result<Output, String> {
346346
Some(a) => parents.push(a.hash.clone()),
347347
None => {
348348
return Err(format!(
349-
"predecessor {prefix} is not committed in {plan}; nothing was recorded"
349+
"parent {prefix} is not committed in {plan}; nothing was recorded"
350350
))
351351
}
352352
},
@@ -388,7 +388,7 @@ fn cmd_commit(root: &Path, path: &Path) -> Result<Output, String> {
388388
);
389389
return Ok(Output::ok(
390390
text,
391-
receipt_json("already-committed", &plan, &hash, seq, &parents),
391+
commit_result_json("already-committed", &plan, &hash, seq, &parents),
392392
));
393393
}
394394

@@ -434,11 +434,11 @@ fn cmd_commit(root: &Path, path: &Path) -> Result<Output, String> {
434434
);
435435
Ok(Output::ok(
436436
text,
437-
receipt_json(kind, &plan, &hash, seq, &parents),
437+
commit_result_json(kind, &plan, &hash, seq, &parents),
438438
))
439439
}
440440

441-
fn receipt_json(kind: &str, plan: &str, hash: &str, seq: u64, parents: &[String]) -> Json {
441+
fn commit_result_json(kind: &str, plan: &str, hash: &str, seq: u64, parents: &[String]) -> Json {
442442
Json::obj(vec![
443443
("command", Json::str("commit")),
444444
("result", Json::str(kind)),
@@ -570,7 +570,7 @@ fn divergence_report(an: &Analysis) -> String {
570570
let mut out = String::new();
571571
for o in &an.orphans {
572572
out.push_str(&format!(
573-
"{} {} is an orphan: predecessor {} has not arrived — wait\n",
573+
"{} {} is an orphan: parent {} has not arrived — wait\n",
574574
style::warning(),
575575
style::short(&o.version.hash),
576576
o.missing
@@ -582,7 +582,7 @@ fn divergence_report(an: &Analysis) -> String {
582582
}
583583
for d in an.open_divergences() {
584584
out.push_str(&format!(
585-
"{} open divergence: {} head members share a predecessor — reconcile by authoring \
585+
"{} open divergence: {} head members share a parent — reconcile by authoring \
586586
a version importing both\n",
587587
style::warning(),
588588
d.children.len()
@@ -852,7 +852,7 @@ fn cmd_verify(root: &Path, plan: Option<&str>, all: bool) -> Result<Output, Stri
852852
for o in &an.orphans {
853853
clean = false;
854854
let reason = format!(
855-
"predecessor {} not present",
855+
"parent {} not present",
856856
o.missing
857857
.iter()
858858
.map(|m| style::short(m))
@@ -949,7 +949,7 @@ fn cmd_repair(root: &Path, plan: &str) -> Result<Output, String> {
949949
));
950950
}
951951

952-
// Identify the last intact predecessor to continue from.
952+
// Identify the last intact parent to continue from.
953953
let intact: Vec<&Admitted> = store
954954
.versions
955955
.iter()
@@ -973,7 +973,7 @@ fn cmd_repair(root: &Path, plan: &str) -> Result<Output, String> {
973973
Some(b) => {
974974
let rel = crate::model::filename_for(b.seq, &b.hash);
975975
text.push_str(&format!(
976-
"\nAuthor a damage-recording version continuing from the last intact predecessor \
976+
"\nAuthor a damage-recording version continuing from the last intact parent \
977977
({}):\n\n import prior from \"./{}\"\n export default prior.revise({{\n \
978978
author: \"you\",\n why: \"Records the damage to <hash> and continues.\",\n \
979979
}})\n\nthen `compass commit` it. Verification stays read-only.\n",
@@ -983,7 +983,7 @@ fn cmd_repair(root: &Path, plan: &str) -> Result<Output, String> {
983983
}
984984
None => {
985985
text.push_str(
986-
"\nNo intact predecessor remains; author a fresh plan recording what is known \
986+
"\nNo intact parent remains; author a fresh plan recording what is known \
987987
of the lost intent.\n",
988988
);
989989
}

0 commit comments

Comments
 (0)