refactor(project_registry): Phase B language-advantage pass — dichotomy wave
CI Pipeline / best-practices (push) Successful in 16s
CI Pipeline / rust-surface (push) Failing after 18s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / go-surface (push) Failing after 22s
CI Pipeline / gitea-offline (push) Successful in 22s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
CI Pipeline / type-check (push) Canceled after 25s
CI Pipeline / canary (push) Canceled after 47s
deploy / deploy (push) Canceled after 51s
Auto-version and Release / release (push) Canceled after 52s
CI Pipeline / lint (push) Canceled after 51s
CI Pipeline / unit-tests (push) Canceled after 51s
CI Pipeline / best-practices (push) Successful in 16s
CI Pipeline / rust-surface (push) Failing after 18s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / go-surface (push) Failing after 22s
CI Pipeline / gitea-offline (push) Successful in 22s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
CI Pipeline / type-check (push) Canceled after 25s
CI Pipeline / canary (push) Canceled after 47s
deploy / deploy (push) Canceled after 51s
Auto-version and Release / release (push) Canceled after 52s
CI Pipeline / lint (push) Canceled after 51s
CI Pipeline / unit-tests (push) Canceled after 51s
This commit is contained in:
1 parent
e978510b0b
commit
4bd283c1a8
1 file changed
+92
-15
@@ -36,6 +36,20 @@
|
||||
//! No pr_free_string export — sibling crates sharing the Python process
|
||||
//! already export identical free symbols; callers free through
|
||||
//! capability_scoring's cs_free_string.
|
||||
//!
|
||||
//! Language-advantage pass (Phase B, battery-pinned): the four-state
|
||||
//! health lattice is a `HealthState` enum with an exhaustive match
|
||||
//! (classify once from the caller's string, render the canonical form
|
||||
//! when stamping); the resolve_cwd precedence outcome is a
|
||||
//! `CwdResolution` enum (ActiveWorktree / RepoRoot) so the 21.2B-3
|
||||
//! ordering is a type, not an if-chain; the migration sweep compares
|
||||
//! through a `SchemaVersion` newtype (missing version → the oracle's
|
||||
//! default of 1). FFI errors are a typed `RegistryError` enum
|
||||
//! (ValueError / KeyError) behind the Result envelope, so the loader
|
||||
//! re-raises the SAME Python exception kind the oracle raised. The
|
||||
//! registry DOC stays pass-through Values — the oracle forwards any
|
||||
//! caller shape verbatim, so a closed stored lattice would be a parity
|
||||
//! break, not an advantage. Battery green throughout.
|
||||
|
||||
use serde_json::{json, Map, Value};
|
||||
use std::fmt;
|
||||
@@ -50,8 +64,57 @@ pub const DRIFTED: &str = "DRIFTED";
|
||||
pub const MIGRATION_REQUIRED: &str = "MIGRATION_REQUIRED";
|
||||
pub const UNKNOWN: &str = "UNKNOWN";
|
||||
|
||||
/// The four legal health states — the oracle's ValueError lattice.
|
||||
const LEGAL_HEALTH: [&str; 4] = [HEALTHY, DRIFTED, MIGRATION_REQUIRED, UNKNOWN];
|
||||
/// The four legal health states — an exhaustive lattice, not a string
|
||||
/// soup: classify once from the caller's string, match exhaustively when
|
||||
/// stamping (the oracle's ValueError lattice made a type).
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum HealthState {
|
||||
Healthy,
|
||||
Drifted,
|
||||
MigrationRequired,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl HealthState {
|
||||
fn classify(health: &str) -> Option<HealthState> {
|
||||
match health {
|
||||
HEALTHY => Some(HealthState::Healthy),
|
||||
DRIFTED => Some(HealthState::Drifted),
|
||||
MIGRATION_REQUIRED => Some(HealthState::MigrationRequired),
|
||||
UNKNOWN => Some(HealthState::Unknown),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
HealthState::Healthy => HEALTHY,
|
||||
HealthState::Drifted => DRIFTED,
|
||||
HealthState::MigrationRequired => MIGRATION_REQUIRED,
|
||||
HealthState::Unknown => UNKNOWN,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A project's structural schema version — newtype so the migration
|
||||
/// sweep reads as a version comparison, not a bare i64 dance (missing →
|
||||
/// the oracle's default of 1).
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
struct SchemaVersion(i64);
|
||||
|
||||
impl SchemaVersion {
|
||||
fn of(p: &Value) -> SchemaVersion {
|
||||
SchemaVersion(
|
||||
p.get("structural_schema_version")
|
||||
.and_then(Value::as_i64)
|
||||
.unwrap_or(1),
|
||||
)
|
||||
}
|
||||
|
||||
fn older_than(self, proposed: i64) -> bool {
|
||||
self.0 < proposed
|
||||
}
|
||||
}
|
||||
|
||||
// ── oracle exceptions (typed, behind the doc envelope) ──────────────────────
|
||||
|
||||
@@ -179,9 +242,9 @@ pub fn set_health(
|
||||
note: &str,
|
||||
now: &str,
|
||||
) -> Result<Map<String, Value>, RegistryError> {
|
||||
if !LEGAL_HEALTH.contains(&health) {
|
||||
let Some(lattice) = HealthState::classify(health) else {
|
||||
return Err(unknown_health(health));
|
||||
}
|
||||
};
|
||||
let mut out = doc.clone();
|
||||
let p = projects_mut(&mut out)?
|
||||
.get_mut(project_id)
|
||||
@@ -190,7 +253,7 @@ pub fn set_health(
|
||||
.get_mut("scaffold_state")
|
||||
.and_then(Value::as_object_mut)
|
||||
.ok_or_else(|| RegistryError::Key("scaffold_state".to_string()))?;
|
||||
state.insert("health".to_string(), json!(health));
|
||||
state.insert("health".to_string(), json!(lattice.as_str()));
|
||||
state
|
||||
.insert("last_material_update".to_string(), json!(now));
|
||||
if !note.is_empty() {
|
||||
@@ -245,6 +308,23 @@ pub fn mark_generation(
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
/// The 21.2B-3 precedence outcome — classified once in resolve_cwd,
|
||||
/// rendered by an exhaustive match (worktree beats repo root; the
|
||||
/// refusal guards fire BEFORE classification).
|
||||
enum CwdResolution {
|
||||
ActiveWorktree(String),
|
||||
RepoRoot(String),
|
||||
}
|
||||
|
||||
impl CwdResolution {
|
||||
fn into_cwd(self) -> String {
|
||||
match self {
|
||||
CwdResolution::ActiveWorktree(wt) => wt,
|
||||
CwdResolution::RepoRoot(repo) => repo,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// `resolve_cwd` — T21.2 §21.2B: canonical REPOSITORY/worktree execution
|
||||
/// cwd for a project — NOT scaffold_location. Precedence (21.2B-3):
|
||||
/// explicit contract/worktree cwd > programme cwd > registry repo cwd >
|
||||
@@ -275,10 +355,12 @@ pub fn resolve_cwd(
|
||||
.get("active_worktree")
|
||||
.and_then(Value::as_str)
|
||||
.unwrap_or("");
|
||||
if !wt.is_empty() {
|
||||
return Ok(wt.to_string());
|
||||
}
|
||||
Ok(repo)
|
||||
let resolution = if wt.is_empty() {
|
||||
CwdResolution::RepoRoot(repo)
|
||||
} else {
|
||||
CwdResolution::ActiveWorktree(wt.to_string())
|
||||
};
|
||||
Ok(resolution.into_cwd())
|
||||
}
|
||||
|
||||
/// `needs_migration` — projects whose structural schema is older than a
|
||||
@@ -287,12 +369,7 @@ pub fn resolve_cwd(
|
||||
pub fn needs_migration(doc: &Map<String, Value>, proposed: i64) -> Result<Vec<String>, RegistryError> {
|
||||
let mut ids: Vec<String> = projects_of(doc)?
|
||||
.iter()
|
||||
.filter(|(_, p)| {
|
||||
p.get("structural_schema_version")
|
||||
.and_then(Value::as_i64)
|
||||
.unwrap_or(1)
|
||||
< proposed
|
||||
})
|
||||
.filter(|(_, p)| SchemaVersion::of(p).older_than(proposed))
|
||||
.map(|(pid, _)| pid.clone())
|
||||
.collect();
|
||||
ids.sort();
|
||||
|
||||
Reference in new issue
Block a user