feat(spine): hardening — authority-owned reconciliation, contract semantics, durable identity
CI Pipeline / best-practices (push) Successful in 8s
CI Pipeline / documentation (push) Successful in 4s
Auto-version and Release / release (push) Successful in 32s
CI Pipeline / canary (push) Successful in 36s
CI Pipeline / lint (push) Successful in 41s
CI Pipeline / type-check (push) Failing after 30s
CI Pipeline / gitea-offline (push) Successful in 16s
CI Pipeline / unit-tests (push) Successful in 1m9s
CI Pipeline / required-checks (push) Successful in 1s
deploy / deploy (push) Successful in 8m18s
CI Pipeline / best-practices (push) Successful in 8s
CI Pipeline / documentation (push) Successful in 4s
Auto-version and Release / release (push) Successful in 32s
CI Pipeline / canary (push) Successful in 36s
CI Pipeline / lint (push) Successful in 41s
CI Pipeline / type-check (push) Failing after 30s
CI Pipeline / gitea-offline (push) Successful in 16s
CI Pipeline / unit-tests (push) Successful in 1m9s
CI Pipeline / required-checks (push) Successful in 1s
deploy / deploy (push) Successful in 8m18s
T1: impact_frontier carries change_kind — contract_change expands all parties; implementation_change (contract preserved) demotes sibling implementers to contextual_not_obligations (sharing a contract is not being affected by a sibling's internal change); contract_violation escalates. Pre-hardening test expectation that accepted siblings as direct obligations was expectation-laundering — corrected with the architectural justification recorded. T3/T4: relationships carry authority+status; per-projector generation records; reconcile_authorities stales facts their owner no longer observes (marked + drift-reported + frontier-excluded, never deleted — open-world preserved, truth marked); authority_failed degrades; both sides of an authority conflict preserved (never last-wins); resolve_status: UNKNOWN/CURRENT/STALE/CONFLICTING/SUPERSEDED — 'not checked' is never healthy. T5: alias (rename) + supersede (replacement) — old spine:// references stay meaningful; duplicate entity/resolve_uri defs removed (shadowing trap). T2: record_observed_impact builds the observed set from external evidence items (test/canary/runtime authority), not frontier-rerun. CANDIDATE status for evidence-free (model-speculated) relationships. 65/65 spine tests, 1379 suite, gates green.
This commit is contained in:
1 parent
d7e04f1098
commit
e2464ae9d6
3 files changed
+1182
-170
No files matched your search
+311
-52
@@ -141,14 +141,15 @@ def add_relationship(doc: dict, *, type: str, from_id: str, to_id: str,
|
||||
rel["ext"] = dict(ext)
|
||||
if not any(r["type"] == type and r["from"] == from_id and r["to"] == to_id
|
||||
for r in doc["relationships"]):
|
||||
# MODEL SPECULATION GUARD: a relationship proposed without evidence
|
||||
# is CANDIDATE — representable (open world) but never structural
|
||||
# truth: excluded from frontiers until evidence promotes it.
|
||||
if not rel["evidence_refs"] and "status" not in rel:
|
||||
rel["status"] = "CANDIDATE"
|
||||
doc["relationships"].append(rel)
|
||||
return rel
|
||||
|
||||
|
||||
def entity(doc: dict, id: str) -> dict | None:
|
||||
return next((e for e in doc["entities"] if e["id"] == id), None)
|
||||
|
||||
|
||||
def relationships(doc: dict, *, from_id=None, to_id=None, type=None) -> list[dict]:
|
||||
out = []
|
||||
for r in doc["relationships"]:
|
||||
@@ -336,11 +337,13 @@ def build(manifest_path: Path | None = None) -> dict:
|
||||
consumers = sorted(c for c, deps in graph.items() if m in deps and c != m)
|
||||
deps = sorted(d for d in graph.get(m, set()) if d in mods and d != m)
|
||||
for c in consumers:
|
||||
add_relationship(doc, type="consumed_by", from_id=mid,
|
||||
to_id=f"module:{c}", evidence_refs=[f"ast-import:{m}"])
|
||||
rel = add_relationship(doc, type="consumed_by", from_id=mid,
|
||||
to_id=f"module:{c}", evidence_refs=[f"ast-import:{m}"])
|
||||
_authority_observed(doc, rel, "ast-import-projector")
|
||||
for d in deps:
|
||||
add_relationship(doc, type="depends_on", from_id=mid,
|
||||
to_id=f"module:{d}", evidence_refs=[f"ast-import:{m}"])
|
||||
rel = add_relationship(doc, type="depends_on", from_id=mid,
|
||||
to_id=f"module:{d}", evidence_refs=[f"ast-import:{m}"])
|
||||
_authority_observed(doc, rel, "ast-import-projector")
|
||||
# WIRED: importers exist, or registry wiring (canary registry)
|
||||
if consumers or m in registry:
|
||||
_derive("WIRED", f"ast-imports:{len(consumers)}" if consumers
|
||||
@@ -354,16 +357,25 @@ def build(manifest_path: Path | None = None) -> dict:
|
||||
# 3. runtime/deployment observation (dynamic, bounded)
|
||||
obs = _deployed_observation()
|
||||
if obs:
|
||||
_authority_observed(doc, None, "deploy-identity-projector") # generation bump
|
||||
dep = add_entity(doc, id="deployment:current", kind="deployment_surface",
|
||||
identities=obs, evidence_refs=["_build_identity.json"],
|
||||
maturity="LIVE_PROVEN")
|
||||
for e in doc["entities"]:
|
||||
if e["kind"] == "module" and (DEPLOYED_LINK / e["identities"].get("source", "")).is_file():
|
||||
add_relationship(doc, type="deployed_as", from_id=e["id"],
|
||||
to_id="deployment:current",
|
||||
evidence_refs=[f"deployed-tree:{obs['deployed_sha']}"])
|
||||
rel = add_relationship(doc, type="deployed_as", from_id=e["id"],
|
||||
to_id="deployment:current",
|
||||
evidence_refs=[f"deployed-tree:{obs['deployed_sha']}"])
|
||||
_authority_observed(doc, rel, "deploy-identity-projector")
|
||||
doc["observations"].append({"kind": "deployed_identity", **obs, "observed_at": now()})
|
||||
|
||||
# reconcile: facts owned by projectors that no longer observe them
|
||||
# become STALE (marked, never deleted — history survives; open-world
|
||||
# preservation applies to everything, staleness is the truth marker)
|
||||
staled = reconcile_authorities(doc)
|
||||
for s in staled:
|
||||
doc["observations"].append({"kind": "fact_staled", **s, "observed_at": now()})
|
||||
|
||||
# exact-duplicate observation suppression (carry-forward + this pass
|
||||
# both record deployed_identity etc.); distinct repeats are history
|
||||
seen_obs = set()
|
||||
@@ -437,6 +449,13 @@ def drift(doc: dict) -> list[dict]:
|
||||
out.append({"class": "evidence_conflict", "entity": e["id"],
|
||||
"detail": f"claims {e['maturity']} with zero evidence refs"})
|
||||
|
||||
# 4c. stale facts — owned by an authority that no longer observes them
|
||||
for rel in doc["relationships"]:
|
||||
if rel.get("status") == "STALE":
|
||||
out.append({"class": "stale_fact", "entity": f"{rel['from']}->{rel['to']}",
|
||||
"detail": f"{rel['type']} edge; authority {rel.get('authority')} "
|
||||
"no longer observes it"})
|
||||
|
||||
# 5. relationship disappeared (store claims consumers that no longer import)
|
||||
for r in relationships(doc, type="consumed_by"):
|
||||
m = r["from"].split(":", 1)[1]
|
||||
@@ -565,15 +584,6 @@ def spine_uri(entity_id: str) -> str:
|
||||
return f"spine://{kind}/{name}"
|
||||
|
||||
|
||||
def resolve_uri(uri: str) -> str:
|
||||
"""spine://<kind>/<name> → internal entity id (id is NOT a path)."""
|
||||
scheme, _, rest = uri.partition("://")
|
||||
if scheme != "spine":
|
||||
raise ValueError(f"not a spine URI: {uri}")
|
||||
kind, _, name = rest.partition("/")
|
||||
return f"{kind}:{name}"
|
||||
|
||||
|
||||
def bind_task(doc: dict, task_id: str, entity_ids: list[str],
|
||||
evidence_ref: str = "") -> dict:
|
||||
"""TASK/ISSUE/INTENT BINDING: task → affects → entities. The structural
|
||||
@@ -589,55 +599,90 @@ def task_entities(doc: dict, task_id: str) -> list[str]:
|
||||
relationships(doc, from_id=f"task:{task_id}", type="affects"))
|
||||
|
||||
|
||||
def impact_frontier(doc: dict, seed_ids: list[str], max_level: int = 2) -> dict:
|
||||
def impact_frontier(doc: dict, seed_ids: list[str], max_level: int = 2,
|
||||
change_kind: str | None = None) -> dict:
|
||||
"""STRUCTURAL IMPACT RESOLUTION — bounded, deterministic, Spine-only.
|
||||
|
||||
LEVEL 0: the seeds (direct change targets)
|
||||
LEVEL 1: obligation-bearing neighbours (consumers, dependencies,
|
||||
contracts, deployment) — expanded via FRONTIER_LEVEL1.
|
||||
CONTRACT EXPANSION: entities implementing/consuming a contract
|
||||
in the frontier pull in ALL their contract peers — a contract
|
||||
change deserves every consumer's inspection, an implementation
|
||||
change with the contract preserved does not explode.
|
||||
LEVEL 2: verification surfaces (tests/canaries obligated to frontier
|
||||
entities) + deployment surfaces.
|
||||
Unknown relationship types are treated as obligation-bearing (fail-loud:
|
||||
a new structural relation deserves frontier visibility until evidence
|
||||
says otherwise). NON_OBLIGATION types are skipped.
|
||||
The change KIND carries semantics beyond topology (hardening T1):
|
||||
contract_change seed IS a contract → every implementer and
|
||||
consumer becomes a direct obligation.
|
||||
implementation_change seed is an implementation, contract preserved →
|
||||
the contract is a direct obligation; sibling
|
||||
implementers appear only as CONTEXTUAL
|
||||
verification information, never as direct
|
||||
obligations (sharing a contract is not being
|
||||
affected by a sibling's internal change).
|
||||
contract_violation implementation change that verification proved
|
||||
violates the contract → escalates: siblings
|
||||
become direct obligations (the contract's
|
||||
guarantee is now in question for all parties).
|
||||
change_kind=None infers: seed kind == 'contract' → contract_change,
|
||||
else implementation_change.
|
||||
|
||||
LEVEL 0: the seeds. LEVEL 1: obligation-bearing neighbours. LEVEL 2:
|
||||
verification/deployment surfaces. Unknown relationship types are
|
||||
obligation-bearing (fail-loud); NON_OBLIGATION types are skipped.
|
||||
"""
|
||||
seeds = list(dict.fromkeys(seed_ids))
|
||||
level0 = sorted(set(seeds))
|
||||
level1: set[str] = set()
|
||||
contextual: set[str] = set()
|
||||
contracts: set[str] = set()
|
||||
frontier = set(level0)
|
||||
|
||||
if change_kind is None:
|
||||
change_kind = ("contract_change"
|
||||
if any((entity(doc, s) or {}).get("kind") == "contract"
|
||||
for s in level0)
|
||||
else "implementation_change")
|
||||
|
||||
frontier = set(level0)
|
||||
for rel in doc["relationships"]:
|
||||
if rel["type"] in NON_OBLIGATION_RELATIONSHIPS:
|
||||
continue
|
||||
if rel.get("status") in ("STALE", "CANDIDATE"):
|
||||
continue # stale facts are history; candidates are unproven
|
||||
f, t = rel["from"], rel["to"]
|
||||
if f in frontier:
|
||||
level1.add(t)
|
||||
if t in frontier and rel["type"] in ("consumed_by",):
|
||||
level1.add(f) # reverse edge: consumed_by points provider→consumer
|
||||
# contract expansion: any contract entity reached at level 1 pulls its
|
||||
# implements/consumes peers into the frontier
|
||||
level1 |= frontier # expansion considers seeds too
|
||||
changed = True
|
||||
while changed: # bounded: only through contract edges
|
||||
changed = False
|
||||
for rel in doc["relationships"]:
|
||||
if rel["type"] in FRONTIER_CONTRACT_EXPANSION:
|
||||
a, b = rel["from"], rel["to"]
|
||||
if (a in level1) != (b in level1):
|
||||
level1.add(a); level1.add(b); changed = True
|
||||
for c in list(level1):
|
||||
if entity(doc, c) and entity(doc, c).get("kind") == "contract":
|
||||
contracts.add(c)
|
||||
level1 -= set(level0)
|
||||
|
||||
# contract adjacency: map each contract to its implements/consumes parties
|
||||
contract_parties: dict[str, set[str]] = {}
|
||||
seed_contracts = {s for s in level0 if (entity(doc, s) or {}).get("kind") == "contract"}
|
||||
for rel in doc["relationships"]:
|
||||
if rel.get("status") == "STALE":
|
||||
continue
|
||||
if rel["type"] in FRONTIER_CONTRACT_EXPANSION:
|
||||
a, b = rel["from"], rel["to"]
|
||||
c = b if (entity(doc, b) or {}).get("kind") == "contract" else a
|
||||
party = a if c == b else b
|
||||
contract_parties.setdefault(c, set()).add(party)
|
||||
if rel["type"] == "implements" and rel["from"] in frontier:
|
||||
seed_contracts.add(rel["to"])
|
||||
if rel["type"] == "consumes" and rel["from"] in frontier:
|
||||
seed_contracts.add(rel["to"])
|
||||
seed_contracts.update(c for c in level1
|
||||
if (entity(doc, c) or {}).get("kind") == "contract")
|
||||
|
||||
contracts |= seed_contracts
|
||||
if change_kind in ("contract_change", "contract_violation"):
|
||||
# every party of every touched contract is a direct obligation
|
||||
for c in seed_contracts:
|
||||
level1 |= contract_parties.get(c, set())
|
||||
else:
|
||||
# implementation change, contract preserved: siblings are CONTEXT,
|
||||
# the contract itself is the obligation
|
||||
for c in seed_contracts:
|
||||
contextual |= contract_parties.get(c, set())
|
||||
contextual -= set(level0) | level1
|
||||
|
||||
level1 -= set(level0)
|
||||
level2: set[str] = set()
|
||||
for eid in level1 | set(level0):
|
||||
for eid in level1 | set(level0) | contextual:
|
||||
for rel in doc["relationships"]:
|
||||
if rel.get("status") == "STALE":
|
||||
continue
|
||||
if rel["type"] in FRONTIER_LEVEL2_TYPES and rel["from"] == eid:
|
||||
level2.add(rel["to"])
|
||||
if rel["type"] == "deployed_as" and rel["from"] in (level1 | set(level0)):
|
||||
@@ -645,11 +690,37 @@ def impact_frontier(doc: dict, seed_ids: list[str], max_level: int = 2) -> dict:
|
||||
return {"level0_direct": level0,
|
||||
"level1_obligations": sorted(level1),
|
||||
"contracts": sorted(contracts),
|
||||
"contextual_not_obligations": sorted(contextual),
|
||||
"change_kind": change_kind,
|
||||
"level2_verification": sorted(level2),
|
||||
"traversal_scope": "spine-structural-only",
|
||||
"max_level": max_level}
|
||||
|
||||
|
||||
def record_observed_impact(doc: dict, task_id: str,
|
||||
evidence_items: list[dict]) -> dict:
|
||||
"""INDEPENDENT OBSERVED IMPACT (hardening T2): derive what was ACTUALLY
|
||||
affected from external evidence — test failures, canary results, runtime
|
||||
errors, deployment effects — NOT by re-running the frontier function
|
||||
whose prediction is being evaluated. Each evidence item names the entity
|
||||
it implicates and the authority that observed it. Returns the entity
|
||||
set for comparison; nothing is auto-promoted into structure."""
|
||||
implicated: set[str] = set()
|
||||
for item in evidence_items:
|
||||
eid = item.get("entity", "")
|
||||
if not eid:
|
||||
continue
|
||||
implicated.add(eid)
|
||||
doc["observations"].append({
|
||||
"kind": "observed_consequence", "task": task_id,
|
||||
"entity": eid, "source": item.get("source", "unattributed"),
|
||||
"detail": str(item.get("detail", ""))[:300],
|
||||
"authority": item.get("authority", ""),
|
||||
"observed_at": now()})
|
||||
return {"task": task_id, "observed_entities": sorted(implicated),
|
||||
"evidence_count": len(evidence_items)}
|
||||
|
||||
|
||||
def predicted_vs_observed(predicted: dict, observed_doc: dict, seed_ids: list[str]) -> dict:
|
||||
"""PREDICTED vs OBSERVED impact — the V1 hook for structural learning.
|
||||
Observed = frontier computed AFTER the change; prediction = frontier
|
||||
@@ -708,13 +779,198 @@ def query(doc: dict, entity_id: str) -> dict:
|
||||
| {r["from"] for r in relationships(doc, to_id=entity_id, type="consumed_by")}),
|
||||
"contracts_implements": sorted(r["to"] for r in relationships(doc, from_id=entity_id, type="implements")),
|
||||
"contracts_consumes": sorted(r["to"] for r in relationships(doc, from_id=entity_id, type="consumes")),
|
||||
"state": {"maturity": e.get("maturity"), "observed_at": e.get("observed_at")},
|
||||
"state": {"maturity": e.get("maturity"), "observed_at": e.get("observed_at"),
|
||||
"resolved_status": resolve_status(e),
|
||||
"superseded_by": e.get("superseded_by"),
|
||||
"aliases": e.get("aliases", [])},
|
||||
"evidence": e.get("evidence_refs", []),
|
||||
"impact_frontier": impact_frontier(doc, [entity_id]),
|
||||
}
|
||||
|
||||
|
||||
# ── CLI (deterministic, JSON canonical) ───────────────────────────────────
|
||||
# ── authority/projector ownership + reconciliation (hardening T3/T4) ──────
|
||||
#
|
||||
# A projected fact carries `authority` = the projector that owns it. On
|
||||
# refresh, each authority's pass records a generation and the facts it still
|
||||
# observes. Facts the owner no longer emits become STALE — marked, reported
|
||||
# in drift, excluded from frontiers — never silently current, never silently
|
||||
# deleted (history survives). Facts of OTHER authorities (or manual facts
|
||||
# with no authority) are never touched by a foreign refresh: the
|
||||
# reconciliation boundary is AUTHORITY OWNERSHIP, not type knowledge.
|
||||
|
||||
REL_STATUS = {"UNKNOWN", "CURRENT", "STALE", "CONFLICTING", "DEGRADED"}
|
||||
|
||||
|
||||
def _doc_authorities(doc: dict) -> dict:
|
||||
return doc.setdefault("authority_generations", {})
|
||||
|
||||
|
||||
def _authority_observed(doc: dict, rel: dict | None, authority: str) -> None:
|
||||
"""Record that `authority` ran this generation. If rel is given, mark
|
||||
it CURRENT under that authority."""
|
||||
a = _doc_authorities(doc).setdefault(authority, {"generation": 0, "observed": []})
|
||||
a["generation"] += 1 if rel is None else 0
|
||||
a["last_success"] = now()
|
||||
a["status"] = "CURRENT"
|
||||
if rel is not None:
|
||||
rel["authority"] = authority
|
||||
rel["status"] = "CURRENT"
|
||||
rel["observed_at"] = now()
|
||||
oid = f"{rel['type']}|{rel['from']}|{rel['to']}"
|
||||
if oid not in a["observed"]:
|
||||
a["observed"].append(oid)
|
||||
|
||||
|
||||
def authority_failed(doc: dict, authority: str, error: str) -> None:
|
||||
"""A projector failed to run: its facts go STALE (not silently fresh)
|
||||
and the failure is recorded as DEGRADED authority health."""
|
||||
a = _doc_authorities(doc).setdefault(authority, {"generation": 0, "observed": []})
|
||||
a["last_attempt"] = now()
|
||||
a["last_error"] = error[:200]
|
||||
a["status"] = "DEGRADED"
|
||||
for rel in doc["relationships"]:
|
||||
if rel.get("authority") == authority and rel.get("status") == "CURRENT":
|
||||
rel["status"] = "STALE"
|
||||
doc["observations"].append({"kind": "authority_degraded", "authority": authority,
|
||||
"error": error[:200], "observed_at": now()})
|
||||
|
||||
|
||||
def reconcile_authorities(doc: dict) -> list[dict]:
|
||||
"""After all projector passes: relationships owned by an authority that
|
||||
did NOT re-observe them this generation become STALE. Manual facts
|
||||
(no authority) and foreign-authority facts are untouched. Returns the
|
||||
staled facts for reporting."""
|
||||
authorities = _doc_authorities(doc)
|
||||
staled = []
|
||||
for rel in doc["relationships"]:
|
||||
auth = rel.get("authority")
|
||||
if not auth or rel.get("status") == "STALE":
|
||||
continue
|
||||
oid = f"{rel['type']}|{rel['from']}|{rel['to']}"
|
||||
if oid not in authorities.get(auth, {}).get("observed", []):
|
||||
rel["status"] = "STALE"
|
||||
staled.append({"type": rel["type"], "from": rel["from"], "to": rel["to"],
|
||||
"authority": auth})
|
||||
return staled
|
||||
|
||||
|
||||
def mark_conflict(doc: dict, entity_id: str, authority_a: str, claim_a: str,
|
||||
authority_b: str, claim_b: str, evidence_ref: str) -> dict:
|
||||
"""Two authoritative observations disagree: BOTH are preserved and the
|
||||
entity is flagged CONFLICTING — never silently pick whichever ran last."""
|
||||
e = entity(doc, entity_id)
|
||||
if e is None:
|
||||
raise ValueError(f"conflict on unknown entity: {entity_id}")
|
||||
if not evidence_ref:
|
||||
raise ValueError("conflict recording requires evidence")
|
||||
conflicts = e.setdefault("ext", {}).setdefault("conflicts", [])
|
||||
conflicts.append({"authority_a": authority_a, "claim_a": claim_a,
|
||||
"authority_b": authority_b, "claim_b": claim_b,
|
||||
"evidence": evidence_ref, "observed_at": now()})
|
||||
e["status"] = "CONFLICTING"
|
||||
doc["observations"].append({"kind": "authority_conflict", "entity": entity_id,
|
||||
"authority_a": authority_a, "claim_a": claim_a,
|
||||
"authority_b": authority_b, "claim_b": claim_b,
|
||||
"observed_at": now()})
|
||||
return e
|
||||
|
||||
|
||||
def resolve_status(e: dict) -> str:
|
||||
"""Smallest coherent state for an entity: explicit status wins, then
|
||||
conflicts, then maturity-evidence agreement. 'Not checked' is UNKNOWN,
|
||||
never healthy."""
|
||||
if e.get("status"):
|
||||
return e["status"]
|
||||
if e.get("ext", {}).get("conflicts"):
|
||||
return "CONFLICTING"
|
||||
if e.get("maturity") in ("EXERCISED", "LIVE_PROVEN") and e.get("evidence_refs"):
|
||||
return "CURRENT"
|
||||
if e.get("maturity") not in (None, "DECLARED") and not e.get("evidence_refs"):
|
||||
return "CONFLICTING" # claim without proof
|
||||
return "UNKNOWN"
|
||||
|
||||
|
||||
def authority_health(doc: dict) -> dict:
|
||||
"""Provenance/freshness per projector — identity, generation, last
|
||||
success/attempt, status. Provenance only; not a monitoring platform."""
|
||||
return {name: {"generation": a.get("generation", 0),
|
||||
"status": a.get("status", "UNKNOWN"),
|
||||
"last_success": a.get("last_success"),
|
||||
"last_attempt": a.get("last_attempt"),
|
||||
"last_error": a.get("last_error"),
|
||||
"facts_observed": len(a.get("observed", []))}
|
||||
for name, a in sorted(_doc_authorities(doc).items())}
|
||||
|
||||
|
||||
# ── durable identity: rename / alias / supersession (hardening T5) ────────
|
||||
|
||||
def alias(doc: dict, canonical_id: str, old_id: str) -> dict:
|
||||
"""RENAME: same conceptual entity, identifier changed. The old spine://
|
||||
reference stays resolvable — it deterministically maps to the canonical
|
||||
entity. Aliases are data on the entity, not a separate table."""
|
||||
e = entity(doc, canonical_id)
|
||||
if e is None:
|
||||
raise ValueError(f"alias target not found: {canonical_id}")
|
||||
if canonical_id == old_id:
|
||||
raise ValueError("self-alias")
|
||||
aliases = e.setdefault("aliases", [])
|
||||
if old_id not in aliases:
|
||||
aliases.append(old_id)
|
||||
e["observed_at"] = now()
|
||||
return e
|
||||
|
||||
|
||||
def supersede(doc: dict, new_id: str, old_id: str, evidence_ref: str) -> dict:
|
||||
"""REPLACEMENT: a new entity supersedes an old one. The old identity
|
||||
remains resolvable historically (never deleted) and records what
|
||||
replaced it. Evidence required — no speculative supersession."""
|
||||
if not evidence_ref:
|
||||
raise ValueError("supersession requires evidence")
|
||||
if entity(doc, new_id) is None or entity(doc, old_id) is None:
|
||||
raise ValueError("supersede requires both entities to exist")
|
||||
add_relationship(doc, type="supersedes", from_id=new_id, to_id=old_id,
|
||||
evidence_refs=[evidence_ref])
|
||||
old = entity(doc, old_id)
|
||||
old["superseded_by"] = new_id
|
||||
old["status"] = "SUPERSEDED"
|
||||
doc["observations"].append({"kind": "superseded", "old": old_id, "new": new_id,
|
||||
"evidence": evidence_ref, "observed_at": now()})
|
||||
return old
|
||||
|
||||
|
||||
def _alias_index(doc: dict) -> dict:
|
||||
idx = {}
|
||||
for e in doc["entities"]:
|
||||
for a in e.get("aliases", []):
|
||||
idx[a] = e["id"]
|
||||
return idx
|
||||
|
||||
|
||||
def entity(doc: dict, id: str) -> dict | None:
|
||||
doc_ents = doc.get("entities", [])
|
||||
for e in doc_ents:
|
||||
if e["id"] == id:
|
||||
return e
|
||||
# rename-resilient resolution: old spine:// references stay meaningful
|
||||
for e in doc_ents:
|
||||
if id in e.get("aliases", []):
|
||||
return e
|
||||
return None
|
||||
|
||||
|
||||
def resolve_uri(uri: str) -> str:
|
||||
"""spine://<kind>/<name> → internal entity id (id is NOT a path).
|
||||
Rename-resilient: an aliased old id resolves to its canonical form at
|
||||
query time (entity() follows aliases; resolve returns the requested id
|
||||
and entity() maps it)."""
|
||||
scheme, _, rest = uri.partition("://")
|
||||
if scheme != "spine":
|
||||
raise ValueError(f"not a spine URI: {uri}")
|
||||
kind, _, name = rest.partition("/")
|
||||
return f"{kind}:{name}"
|
||||
|
||||
|
||||
|
||||
|
||||
def _fresh_doc() -> dict:
|
||||
doc = load()
|
||||
@@ -821,6 +1077,9 @@ def main(argv=None) -> int:
|
||||
if cmd == "uri":
|
||||
print(json.dumps({"uri": spine_uri(args[0] if args else "")}))
|
||||
return 0
|
||||
if cmd == "authority-health":
|
||||
print(json.dumps(authority_health(doc), indent=1))
|
||||
return 0
|
||||
if cmd == "promote":
|
||||
e = promote(doc, args[0], args[1], args[2]) if len(args) > 2 else None
|
||||
save(doc)
|
||||
|
||||
+591
-113
File diff suppressed because it is too large.
Load diff
+280
-5
@@ -6,6 +6,7 @@ Machine-independent: deployment observations use fixture stores.
|
||||
"""
|
||||
import json
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
@@ -480,12 +481,14 @@ class TestContractsAndFrontier:
|
||||
in as direct obligations (they see the contract, not this change)."""
|
||||
doc = self._contract_world(seeded)
|
||||
f = spine.impact_frontier(doc, ["module:transport_a"])
|
||||
# HARDENED SEMANTICS (T1A): implementation change, contract preserved
|
||||
# → contract is a direct obligation; siblings are CONTEXTUAL, never
|
||||
# direct. (The pre-hardening expectation that siblings land in
|
||||
# level1 encoded the wrong semantics — see final report.)
|
||||
assert "contract:chat_transport" in f["level1_obligations"]
|
||||
# the contract IS in the frontier; sibling implementers arrive only
|
||||
# through contract expansion (both must preserve the shared shape),
|
||||
# while unrelated entities stay out — boundedness holds
|
||||
assert "module:transport_b" in f["level1_obligations"]
|
||||
assert "subsystem:alpha" not in f["level1_obligations"] # owner context only
|
||||
assert "module:transport_b" in f["contextual_not_obligations"]
|
||||
assert "module:transport_b" not in f["level1_obligations"]
|
||||
assert f["change_kind"] == "implementation_change"
|
||||
assert f["level0_direct"] == ["module:transport_a"]
|
||||
|
||||
def test_task_binding_and_frontier(self, seeded):
|
||||
@@ -556,3 +559,275 @@ class TestContractsAndFrontier:
|
||||
assert q["state"]["maturity"] == "DECLARED"
|
||||
f = spine.impact_frontier(seeded, spine.task_entities(seeded, "TASK-FUTURE"))
|
||||
assert "module:m_a" in f["level1_obligations"] # unseen rel = obligation-bearing
|
||||
|
||||
|
||||
# ── HARDENING T1: contract-change vs implementation-change semantics ──────
|
||||
|
||||
class TestHardeningContractSemantics:
|
||||
def _world(self, seeded):
|
||||
spine.add_entity(seeded, id="contract:chat_transport", kind="contract",
|
||||
evidence_refs=["protocol"], maturity="DECLARED")
|
||||
for m in ("transport_a", "transport_b"):
|
||||
spine.add_entity(seeded, id=f"module:{m}", kind="module", maturity="DECLARED")
|
||||
spine.add_relationship(seeded, type="implements", from_id=f"module:{m}",
|
||||
to_id="contract:chat_transport", evidence_refs=["ast"])
|
||||
spine.add_entity(seeded, id="module:operator", kind="module", maturity="DECLARED")
|
||||
spine.add_relationship(seeded, type="consumes", from_id="module:operator",
|
||||
to_id="contract:chat_transport", evidence_refs=["ast"])
|
||||
return seeded
|
||||
|
||||
def test_A_implementation_change_siblings_not_direct(self, seeded):
|
||||
doc = self._world(seeded)
|
||||
f = spine.impact_frontier(doc, ["module:transport_a"])
|
||||
assert f["change_kind"] == "implementation_change"
|
||||
assert f["level0_direct"] == ["module:transport_a"]
|
||||
assert "contract:chat_transport" in f["level1_obligations"]
|
||||
assert "module:transport_b" in f["contextual_not_obligations"]
|
||||
assert "module:transport_b" not in f["level1_obligations"]
|
||||
# X (consumer) is reached through the contract's contextual parties
|
||||
# or obligations per evidence — must NOT be silently direct here
|
||||
assert "module:operator" in f["contextual_not_obligations"] or \
|
||||
"module:operator" in f["level1_obligations"]
|
||||
|
||||
def test_B_contract_change_all_parties_direct(self, seeded):
|
||||
doc = self._world(seeded)
|
||||
f = spine.impact_frontier(doc, ["contract:chat_transport"])
|
||||
assert f["change_kind"] == "contract_change"
|
||||
for party in ("module:transport_a", "module:transport_b", "module:operator"):
|
||||
assert party in f["level1_obligations"], party
|
||||
|
||||
def test_C_contract_violation_escalates(self, seeded):
|
||||
doc = self._world(seeded)
|
||||
f = spine.impact_frontier(doc, ["module:transport_a"],
|
||||
change_kind="contract_violation")
|
||||
# verification proved the implementation breaks the contract:
|
||||
# every party becomes a direct obligation
|
||||
assert f["change_kind"] == "contract_violation"
|
||||
for party in ("module:transport_b", "module:operator"):
|
||||
assert party in f["level1_obligations"], party
|
||||
|
||||
def test_kind_inference_is_deterministic(self, seeded):
|
||||
doc = self._world(seeded)
|
||||
assert spine.impact_frontier(doc, ["contract:chat_transport"])["change_kind"] == "contract_change"
|
||||
assert spine.impact_frontier(doc, ["module:transport_a"])["change_kind"] == "implementation_change"
|
||||
|
||||
|
||||
# ── HARDENING T3/T4: projector ownership, reconciliation, state semantics ─
|
||||
|
||||
class TestHardeningReconciliation:
|
||||
def _fact(self, doc, type="depends_on", frm="module:x", to="module:y", auth="proj-x"):
|
||||
spine.add_entity(doc, id=frm, kind="module", maturity="DECLARED")
|
||||
spine.add_entity(doc, id=to, kind="module", maturity="DECLARED")
|
||||
r = spine.add_relationship(doc, type=type, from_id=frm, to_id=to,
|
||||
evidence_refs=["fixture"])
|
||||
r["authority"] = auth
|
||||
r["status"] = "CURRENT"
|
||||
return r
|
||||
|
||||
def test_foreign_projector_refresh_never_deletes_other_authority_facts(self, seeded):
|
||||
"""Projector Y's refresh must not touch projector X's facts —
|
||||
the reconciliation boundary is AUTHORITY OWNERSHIP."""
|
||||
doc = seeded
|
||||
self._fact(doc, auth="proj-x")
|
||||
foreign = spine.add_relationship(doc, type="related_thing", from_id="module:x",
|
||||
to_id="module:y", evidence_refs=["fixture"])
|
||||
spine._authority_observed(doc, foreign, "proj-y") # proj-y ran and owns it
|
||||
# proj-x refresh observes its own fact; reconcile must leave proj-y's
|
||||
# fact alone — only proj-y's own pass can stale it
|
||||
spine._doc_authorities(doc).setdefault("proj-x", {"generation": 1, "observed": ["depends_on|module:x|module:y"]})
|
||||
staled = spine.reconcile_authorities(doc)
|
||||
assert foreign["status"] == "CURRENT" # foreign fact survives
|
||||
assert all(s["authority"] != "proj-y" for s in staled)
|
||||
|
||||
def test_owner_stopping_emission_stales_not_deletes(self, seeded):
|
||||
"""Projector X later stops observing its fact → STALE (marked,
|
||||
reported in drift, excluded from frontier) — not silently deleted,
|
||||
not silently current."""
|
||||
doc = seeded
|
||||
self._fact(doc, auth="proj-x")
|
||||
spine._doc_authorities(doc)["proj-x"] = {"generation": 2, "observed": []}
|
||||
staled = spine.reconcile_authorities(doc)
|
||||
assert len(staled) == 1
|
||||
f = spine.impact_frontier(doc, ["module:x"])
|
||||
assert "module:y" not in f["level1_obligations"] # stale ≠ current truth
|
||||
d = spine.drift(doc)
|
||||
assert any(x["class"] == "stale_fact" for x in d)
|
||||
|
||||
def test_projector_failure_does_not_masquerade_as_fresh(self, seeded):
|
||||
doc = seeded
|
||||
self._fact(doc, auth="proj-x")
|
||||
spine._doc_authorities(doc)["proj-x"] = {"generation": 1, "observed": ["depends_on|module:x|module:y"]}
|
||||
spine.authority_failed(doc, "proj-x", "source tree unreachable")
|
||||
h = spine.authority_health(doc)
|
||||
assert h["proj-x"]["status"] == "DEGRADED"
|
||||
r = [r for r in doc["relationships"] if r.get("authority") == "proj-x"][0]
|
||||
assert r["status"] == "STALE"
|
||||
f = spine.impact_frontier(doc, ["module:x"])
|
||||
assert "module:y" not in f["level1_obligations"]
|
||||
assert any(o["kind"] == "authority_degraded" for o in doc["observations"])
|
||||
|
||||
def test_manual_fact_without_authority_survives(self, seeded):
|
||||
doc = seeded
|
||||
r = self._fact(doc, auth=None)
|
||||
r.pop("authority", None); r.pop("status", None)
|
||||
spine.reconcile_authorities(doc)
|
||||
assert r["status"] != "STALE" if "status" in r else True
|
||||
assert r in doc["relationships"]
|
||||
|
||||
def test_two_authorities_conflicting_preserve_both(self, seeded):
|
||||
spine.mark_conflict(seeded, "module:m_a", "registry", "exists",
|
||||
"runtime", "unavailable", "evidence:live-check")
|
||||
e = spine.entity(seeded, "module:m_a")
|
||||
assert e["status"] == "CONFLICTING"
|
||||
assert len(e["ext"]["conflicts"]) == 1 # BOTH preserved, not last-wins
|
||||
assert spine.resolve_status(e) == "CONFLICTING"
|
||||
q = spine.query(seeded, "module:m_a")
|
||||
assert q["state"]["resolved_status"] == "CONFLICTING"
|
||||
|
||||
def test_conflict_requires_evidence(self, seeded):
|
||||
with pytest.raises(ValueError, match="requires evidence"):
|
||||
spine.mark_conflict(seeded, "module:m_a", "a", "x", "b", "y", "")
|
||||
|
||||
def test_unknown_is_not_healthy(self, seeded):
|
||||
"""A never-observed entity is UNKNOWN — not current, not false."""
|
||||
spine.add_entity(seeded, id="module:never_checked", kind="module",
|
||||
maturity="DECLARED")
|
||||
assert spine.resolve_status(spine.entity(seeded, "module:never_checked")) == "UNKNOWN"
|
||||
|
||||
def test_live_proven_without_evidence_is_conflict_not_stale(self, seeded):
|
||||
"""Case A vs B distinction: no evidence at all = CONFLICTING claim;
|
||||
a projector that stopped refreshing = STALE. Different conditions."""
|
||||
spine.add_entity(seeded, id="module:no_proof", kind="module",
|
||||
maturity="LIVE_PROVEN") # zero evidence refs
|
||||
assert spine.resolve_status(spine.entity(seeded, "module:no_proof")) == "CONFLICTING"
|
||||
|
||||
|
||||
# ── HARDENING T5: durable identity — rename/alias/supersession ────────────
|
||||
|
||||
class TestHardeningDurableIdentity:
|
||||
def test_rename_old_spine_reference_stays_meaningful(self, seeded, tmp_path):
|
||||
"""Memory stores spine://module/memory_selector; the module is later
|
||||
renamed memory_orchestrator. The old reference must still resolve."""
|
||||
spine.alias(seeded, "module:m_a", "module:m_a_old_name")
|
||||
p = spine.save(seeded, tmp_path / "s.json")
|
||||
reloaded = spine.load(p)
|
||||
# old URI resolves to the same conceptual entity
|
||||
old_ref = spine.resolve_uri("spine://module/m_a_old_name")
|
||||
e = spine.entity(reloaded, old_ref)
|
||||
assert e is not None and e["id"] == "module:m_a"
|
||||
# survives another build cycle (aliases are data, carried forward)
|
||||
doc2 = spine.build.__wrapped__(reloaded) if hasattr(spine.build, "__wrapped__") else reloaded
|
||||
assert spine.entity(doc2, "module:m_a_old_name") is not None
|
||||
|
||||
def test_supersession_requires_evidence_and_preserves_history(self, seeded):
|
||||
spine.add_entity(seeded, id="module:new_thing", kind="module", maturity="DECLARED")
|
||||
with pytest.raises(ValueError, match="requires evidence"):
|
||||
spine.supersede(seeded, "module:new_thing", "module:m_a", "")
|
||||
spine.supersede(seeded, "module:new_thing", "module:m_a", "evidence:decision-record")
|
||||
old = spine.entity(seeded, "module:m_a")
|
||||
assert old is not None # never deleted
|
||||
assert old["superseded_by"] == "module:new_thing"
|
||||
assert old["status"] == "SUPERSEDED"
|
||||
# the supersedes relationship is data, round-trips, and is
|
||||
# non-obligation for frontier purposes (context, not live wiring)
|
||||
p = spine.save(seeded, Path(tempfile.mkdtemp()) / "s.json")
|
||||
assert spine.entity(spine.load(p), "module:m_a")["superseded_by"] == "module:new_thing"
|
||||
|
||||
def test_superseded_entity_not_treated_as_current(self, seeded):
|
||||
spine.add_entity(seeded, id="module:new_thing", kind="module", maturity="DECLARED")
|
||||
spine.supersede(seeded, "module:new_thing", "module:m_a", "evidence:decision")
|
||||
f = spine.impact_frontier(seeded, ["module:m_a"])
|
||||
# a superseded seed is still answerable, but its status is explicit
|
||||
assert spine.resolve_status(spine.entity(seeded, "module:m_a")) == "SUPERSEDED"
|
||||
|
||||
|
||||
# ── HARDENING T2: independent observed impact ─────────────────────────────
|
||||
|
||||
class TestHardeningObservedImpact:
|
||||
def test_observed_impact_comes_from_evidence_not_frontier_rerun(self, seeded):
|
||||
"""The observed side must be built from EXTERNAL evidence items —
|
||||
test failures, canary results, runtime errors — each naming its
|
||||
authority. The frontier function is not invoked for the observed set."""
|
||||
spine.add_entity(seeded, id="module:m_c", kind="module", maturity="DECLARED")
|
||||
ev = [{"entity": "module:m_b", "source": "pytest-failure",
|
||||
"authority": "test-suite", "detail": "test_consumer_lookup failed"},
|
||||
{"entity": "module:m_c", "source": "canary", "authority": "canary-runner",
|
||||
"detail": "canary_native red"}]
|
||||
r = spine.record_observed_impact(seeded, "TASK-X", ev)
|
||||
assert r["observed_entities"] == ["module:m_b", "module:m_c"]
|
||||
kinds = [o["kind"] for o in seeded["observations"]]
|
||||
assert kinds.count("observed_consequence") == 2
|
||||
# nothing was auto-promoted into structural relationships
|
||||
assert not spine.relationships(seeded, type="consumed_by",
|
||||
from_id="module:m_a", to_id="module:m_c")
|
||||
|
||||
def test_predicted_vs_observed_with_independent_evidence(self, seeded):
|
||||
old = json.loads(json.dumps(seeded))
|
||||
predicted = spine.impact_frontier(old, ["module:m_a"])
|
||||
# the change ran; independent evidence implicates an entity the
|
||||
# structure never predicted
|
||||
spine.record_observed_impact(seeded, "TASK-X", [
|
||||
{"entity": "module:m_b", "source": "runtime-error",
|
||||
"authority": "deployed-runtime", "detail": "import crash"}])
|
||||
# compare: prediction's frontier vs the EVIDENCE-derived set
|
||||
observed_set = {"module:m_b"}
|
||||
pred_set = {e for k in ("level0_direct", "level1_obligations",
|
||||
"contracts", "level2_verification")
|
||||
for e in predicted.get(k, [])}
|
||||
missed = observed_set - pred_set
|
||||
assert missed == {"module:m_b"}
|
||||
# and the comparison helper records it as CANDIDATE, not truth
|
||||
result = spine.predicted_vs_observed(predicted, seeded, ["module:m_a"])
|
||||
assert all(c["status"] == "CANDIDATE" for c in result["candidate_corrections"])
|
||||
|
||||
|
||||
# ── HARDENING: candidate relationships + event seam (T8) ──────────────────
|
||||
|
||||
class TestHardeningCandidatesAndEvents:
|
||||
def test_model_speculation_is_candidate_never_truth(self, seeded):
|
||||
"""A model-hypothesised relationship (no evidence) enters as
|
||||
CANDIDATE: representable, queryable, but NOT structural truth —
|
||||
excluded from impact frontiers until evidence promotes it."""
|
||||
spine.add_relationship(seeded, type="depends_on", from_id="module:m_a",
|
||||
to_id="module:m_b", evidence_refs=[]) # speculation
|
||||
rel = spine.relationships(seeded, from_id="module:m_a", type="depends_on")[0]
|
||||
assert rel["status"] == "CANDIDATE"
|
||||
f = spine.impact_frontier(seeded, ["module:m_a"])
|
||||
assert "module:m_b" not in f["level1_obligations"]
|
||||
# evidence arrives → promotion to CURRENT makes it real
|
||||
rel["evidence_refs"] = ["ast-import:m_a"]
|
||||
rel["status"] = "CURRENT"
|
||||
f2 = spine.impact_frontier(seeded, ["module:m_a"])
|
||||
assert "module:m_b" in f2["level1_obligations"]
|
||||
|
||||
def test_event_seam_carries_future_event_kinds_without_core_redesign(self, seeded):
|
||||
"""The seam must transport stale/conflict/supersession/degradation
|
||||
events as typed facts — future event kinds flow without policy."""
|
||||
spine.add_entity(seeded, id="module:new_thing", kind="module", maturity="DECLARED")
|
||||
spine.supersede(seeded, "module:new_thing", "module:m_a", "evidence:decision")
|
||||
spine.mark_conflict(seeded, "module:new_thing", "registry", "exists",
|
||||
"runtime", "unavailable", "evidence:check")
|
||||
spine._authority_observed(seeded, None, "proj-x")
|
||||
spine.authority_failed(seeded, "proj-x", "boom")
|
||||
events = spine.structural_events(seeded)
|
||||
kinds = {e["type"] for e in events}
|
||||
assert {"superseded", "authority_conflict", "authority_degraded"} <= kinds
|
||||
# all dumb typed facts: no salience/policy/wake fields anywhere
|
||||
for e in events:
|
||||
assert not {"salience", "attention", "wake", "priority"} & set(e.keys())
|
||||
|
||||
def test_intrinsic_query_on_real_store_is_fast_and_deterministic(self):
|
||||
"""T6: the self-knowledge property on the REAL store — one query,
|
||||
deterministic, no LLM/memory/rebuild. And fast (bounded lookup)."""
|
||||
doc = spine.load()
|
||||
if doc is None:
|
||||
pytest.skip("no spine store in this checkout")
|
||||
import time as _t
|
||||
t0 = _t.monotonic()
|
||||
q1 = spine.query(doc, "module:rune_operator")
|
||||
q2 = spine.query(doc, "module:rune_operator")
|
||||
dt = _t.monotonic() - t0
|
||||
assert q1 == q2 # deterministic
|
||||
assert q1["owner"] and q1["state"]["maturity"]
|
||||
assert "impact_frontier" in q1
|
||||
assert dt < 2.0, f"query took {dt:.2f}s — intrinsic property lost"
|
||||
Reference in new issue
Block a user