generated from coulomb/repo-seed
feat: compare accountability update deltas
This commit is contained in:
@@ -45,6 +45,12 @@ Ownership review schema:
|
|||||||
schemas/accountability-ownership-review.schema.yaml
|
schemas/accountability-ownership-review.schema.yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Update delta schema:
|
||||||
|
|
||||||
|
```text
|
||||||
|
schemas/accountability-update-delta.schema.yaml
|
||||||
|
```
|
||||||
|
|
||||||
## Required Sections
|
## Required Sections
|
||||||
|
|
||||||
- `netkingdom`: root id, name, and king actor.
|
- `netkingdom`: root id, name, and king actor.
|
||||||
@@ -142,3 +148,19 @@ railiance-fabric review-identity identity:repository:example-repo \
|
|||||||
Reviewer decisions are keyed by stable identity key. Later rescans apply the
|
Reviewer decisions are keyed by stable identity key. Later rescans apply the
|
||||||
latest decision for that key, so ordinary evidence refreshes do not lose
|
latest decision for that key, so ordinary evidence refreshes do not lose
|
||||||
reviewed ownership choices.
|
reviewed ownership choices.
|
||||||
|
|
||||||
|
## Update Deltas
|
||||||
|
|
||||||
|
To compare the current run with previous identity and ownership-review outputs:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
railiance-fabric discover-roots \
|
||||||
|
--delta \
|
||||||
|
--previous-identity-projection previous-identities.json \
|
||||||
|
--previous-ownership-review previous-ownership.json
|
||||||
|
```
|
||||||
|
|
||||||
|
The delta separates candidate graph node changes, candidate graph edge changes,
|
||||||
|
ownership changes, containment changes, review-state changes, and blocker
|
||||||
|
changes. When `summary.promotion_needed` is `false`, the update loop can skip
|
||||||
|
promotion because the durable evidence produced no meaningful Fabric change.
|
||||||
|
|||||||
@@ -76,6 +76,14 @@ railiance-fabric review-identity <stable-key> \
|
|||||||
--fabric-id fabric.railiance.primary
|
--fabric-id fabric.railiance.primary
|
||||||
```
|
```
|
||||||
|
|
||||||
|
To compare a new run with saved review outputs:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
railiance-fabric discover-roots --delta \
|
||||||
|
--previous-identity-projection previous-identities.json \
|
||||||
|
--previous-ownership-review previous-ownership.json
|
||||||
|
```
|
||||||
|
|
||||||
The financial export must satisfy these invariants:
|
The financial export must satisfy these invariants:
|
||||||
|
|
||||||
- every accepted node has resolvable ownership;
|
- every accepted node has resolvable ownership;
|
||||||
|
|||||||
@@ -246,6 +246,105 @@ def build_ownership_review(
|
|||||||
return review
|
return review
|
||||||
|
|
||||||
|
|
||||||
|
def build_update_delta(
|
||||||
|
current_identity_projection: dict[str, Any],
|
||||||
|
current_ownership_review: dict[str, Any],
|
||||||
|
*,
|
||||||
|
previous_identity_projection: dict[str, Any] | None = None,
|
||||||
|
previous_ownership_review: dict[str, Any] | None = None,
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
previous_identity_projection = previous_identity_projection or {}
|
||||||
|
previous_ownership_review = previous_ownership_review or {}
|
||||||
|
current_nodes = {
|
||||||
|
item["stable_key"]: item
|
||||||
|
for item in current_identity_projection.get("identity_candidates", [])
|
||||||
|
if isinstance(item, dict) and item.get("stable_key")
|
||||||
|
}
|
||||||
|
previous_nodes = {
|
||||||
|
item["stable_key"]: item
|
||||||
|
for item in previous_identity_projection.get("identity_candidates", [])
|
||||||
|
if isinstance(item, dict) and item.get("stable_key")
|
||||||
|
}
|
||||||
|
current_edges = {
|
||||||
|
item["id"]: item
|
||||||
|
for item in current_identity_projection.get("candidate_graph", {}).get("edges", [])
|
||||||
|
if isinstance(item, dict) and item.get("id")
|
||||||
|
}
|
||||||
|
previous_edges = {
|
||||||
|
item["id"]: item
|
||||||
|
for item in previous_identity_projection.get("candidate_graph", {}).get("edges", [])
|
||||||
|
if isinstance(item, dict) and item.get("id")
|
||||||
|
}
|
||||||
|
current_review = {
|
||||||
|
item["stable_key"]: item
|
||||||
|
for item in current_ownership_review.get("items", [])
|
||||||
|
if isinstance(item, dict) and item.get("stable_key")
|
||||||
|
}
|
||||||
|
previous_review = {
|
||||||
|
item["stable_key"]: item
|
||||||
|
for item in previous_ownership_review.get("items", [])
|
||||||
|
if isinstance(item, dict) and item.get("stable_key")
|
||||||
|
}
|
||||||
|
|
||||||
|
node_delta = _delta_sets(previous_nodes, current_nodes)
|
||||||
|
edge_delta = _delta_sets(previous_edges, current_edges)
|
||||||
|
ownership_changes = _field_changes(previous_review, current_review, "ownership")
|
||||||
|
containment_changes = _field_changes(previous_review, current_review, "containment")
|
||||||
|
review_state_changes = [
|
||||||
|
key
|
||||||
|
for key in sorted(set(previous_review) & set(current_review))
|
||||||
|
if previous_review[key].get("review_state") != current_review[key].get("review_state")
|
||||||
|
]
|
||||||
|
blocker_changes = _field_changes(previous_review, current_review, "blockers")
|
||||||
|
meaningful_changes = _unique_strings(
|
||||||
|
[
|
||||||
|
*node_delta["added"],
|
||||||
|
*node_delta["changed"],
|
||||||
|
*node_delta["removed"],
|
||||||
|
*edge_delta["added"],
|
||||||
|
*edge_delta["changed"],
|
||||||
|
*edge_delta["removed"],
|
||||||
|
*ownership_changes,
|
||||||
|
*containment_changes,
|
||||||
|
*review_state_changes,
|
||||||
|
*blocker_changes,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
delta = {
|
||||||
|
"apiVersion": "railiance.fabric/v1alpha2",
|
||||||
|
"kind": "AccountabilityUpdateDelta",
|
||||||
|
"generated_at": _utc_now(),
|
||||||
|
"current": current_identity_projection.get("evidence_run", {}),
|
||||||
|
"previous": previous_identity_projection.get("evidence_run", {}),
|
||||||
|
"node_delta": node_delta,
|
||||||
|
"edge_delta": edge_delta,
|
||||||
|
"change_sets": {
|
||||||
|
"ownership": ownership_changes,
|
||||||
|
"containment": containment_changes,
|
||||||
|
"review_state": review_state_changes,
|
||||||
|
"blockers": blocker_changes,
|
||||||
|
},
|
||||||
|
"summary": {
|
||||||
|
"nodes_added": len(node_delta["added"]),
|
||||||
|
"nodes_changed": len(node_delta["changed"]),
|
||||||
|
"nodes_removed": len(node_delta["removed"]),
|
||||||
|
"nodes_unchanged": len(node_delta["unchanged"]),
|
||||||
|
"edges_added": len(edge_delta["added"]),
|
||||||
|
"edges_changed": len(edge_delta["changed"]),
|
||||||
|
"edges_removed": len(edge_delta["removed"]),
|
||||||
|
"edges_unchanged": len(edge_delta["unchanged"]),
|
||||||
|
"meaningful_change_count": len(meaningful_changes),
|
||||||
|
"promotion_needed": bool(meaningful_changes),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
validator = draft202012_validator(repo_root() / "schemas" / "accountability-update-delta.schema.yaml")
|
||||||
|
errors = sorted(validator.iter_errors(delta), key=lambda error: list(error.path))
|
||||||
|
if errors:
|
||||||
|
location = ".".join(str(part) for part in errors[0].path) or "<root>"
|
||||||
|
raise ValueError(f"invalid accountability update delta at {location}: {errors[0].message}")
|
||||||
|
return delta
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class AccountabilityEvidenceStore:
|
class AccountabilityEvidenceStore:
|
||||||
path: Path
|
path: Path
|
||||||
@@ -741,6 +840,44 @@ def _ownership_item(
|
|||||||
return item
|
return item
|
||||||
|
|
||||||
|
|
||||||
|
def _delta_sets(previous: dict[str, dict[str, Any]], current: dict[str, dict[str, Any]]) -> dict[str, list[str]]:
|
||||||
|
previous_keys = set(previous)
|
||||||
|
current_keys = set(current)
|
||||||
|
common = previous_keys & current_keys
|
||||||
|
changed = [
|
||||||
|
key
|
||||||
|
for key in sorted(common)
|
||||||
|
if short_fingerprint(_stable_payload(previous[key]), length=16)
|
||||||
|
!= short_fingerprint(_stable_payload(current[key]), length=16)
|
||||||
|
]
|
||||||
|
return {
|
||||||
|
"added": sorted(current_keys - previous_keys),
|
||||||
|
"changed": changed,
|
||||||
|
"removed": sorted(previous_keys - current_keys),
|
||||||
|
"unchanged": sorted(common - set(changed)),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _field_changes(
|
||||||
|
previous: dict[str, dict[str, Any]],
|
||||||
|
current: dict[str, dict[str, Any]],
|
||||||
|
field: str,
|
||||||
|
) -> list[str]:
|
||||||
|
return [
|
||||||
|
key
|
||||||
|
for key in sorted(set(previous) & set(current))
|
||||||
|
if previous[key].get(field) != current[key].get(field)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def _stable_payload(value: dict[str, Any]) -> dict[str, Any]:
|
||||||
|
return {
|
||||||
|
key: data
|
||||||
|
for key, data in value.items()
|
||||||
|
if key not in {"generated_at", "decision"}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def _add_identity_candidate(
|
def _add_identity_candidate(
|
||||||
candidates: dict[str, dict[str, Any]],
|
candidates: dict[str, dict[str, Any]],
|
||||||
*,
|
*,
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ from .accountability_roots import (
|
|||||||
AccountabilityEvidenceStore,
|
AccountabilityEvidenceStore,
|
||||||
build_identity_projection,
|
build_identity_projection,
|
||||||
build_ownership_review,
|
build_ownership_review,
|
||||||
|
build_update_delta,
|
||||||
collect_accountability_root_evidence,
|
collect_accountability_root_evidence,
|
||||||
load_accountability_root_manifest,
|
load_accountability_root_manifest,
|
||||||
)
|
)
|
||||||
@@ -125,6 +126,9 @@ def build_parser() -> argparse.ArgumentParser:
|
|||||||
discover_roots.add_argument("--max-items-per-root", type=int, default=200)
|
discover_roots.add_argument("--max-items-per-root", type=int, default=200)
|
||||||
discover_roots.add_argument("--identity-projection", action="store_true", help="Print normalized identity candidates instead of raw evidence.")
|
discover_roots.add_argument("--identity-projection", action="store_true", help="Print normalized identity candidates instead of raw evidence.")
|
||||||
discover_roots.add_argument("--ownership-review", action="store_true", help="Print ownership resolution and review blockers.")
|
discover_roots.add_argument("--ownership-review", action="store_true", help="Print ownership resolution and review blockers.")
|
||||||
|
discover_roots.add_argument("--delta", action="store_true", help="Print a delta against previous identity/ownership review files.")
|
||||||
|
discover_roots.add_argument("--previous-identity-projection", type=Path, default=None)
|
||||||
|
discover_roots.add_argument("--previous-ownership-review", type=Path, default=None)
|
||||||
discover_roots.add_argument("--store-db", type=Path, default=None, help="Persist evidence and identity candidates in a SQLite store.")
|
discover_roots.add_argument("--store-db", type=Path, default=None, help="Persist evidence and identity candidates in a SQLite store.")
|
||||||
|
|
||||||
review_identity = sub.add_parser(
|
review_identity = sub.add_parser(
|
||||||
@@ -363,9 +367,27 @@ def main(argv: list[str] | None = None) -> int:
|
|||||||
store = AccountabilityEvidenceStore(args.store_db) if args.store_db else None
|
store = AccountabilityEvidenceStore(args.store_db) if args.store_db else None
|
||||||
decisions = store.latest_review_decisions() if store else {}
|
decisions = store.latest_review_decisions() if store else {}
|
||||||
ownership_review = build_ownership_review(projection, manifest, review_decisions=decisions)
|
ownership_review = build_ownership_review(projection, manifest, review_decisions=decisions)
|
||||||
|
update_delta = build_update_delta(
|
||||||
|
projection,
|
||||||
|
ownership_review,
|
||||||
|
previous_identity_projection=_load_json_file(args.previous_identity_projection)
|
||||||
|
if args.previous_identity_projection
|
||||||
|
else None,
|
||||||
|
previous_ownership_review=_load_json_file(args.previous_ownership_review)
|
||||||
|
if args.previous_ownership_review
|
||||||
|
else None,
|
||||||
|
)
|
||||||
if args.store_db:
|
if args.store_db:
|
||||||
store.add_evidence_run(payload, projection)
|
store.add_evidence_run(payload, projection)
|
||||||
output = ownership_review if args.ownership_review else projection if args.identity_projection else payload
|
output = (
|
||||||
|
update_delta
|
||||||
|
if args.delta
|
||||||
|
else ownership_review
|
||||||
|
if args.ownership_review
|
||||||
|
else projection
|
||||||
|
if args.identity_projection
|
||||||
|
else payload
|
||||||
|
)
|
||||||
print(json.dumps(output, indent=2, sort_keys=True))
|
print(json.dumps(output, indent=2, sort_keys=True))
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
@@ -1648,6 +1670,13 @@ def _load_graph_or_exit(paths: list[Path]) -> FabricGraph:
|
|||||||
return graph
|
return graph
|
||||||
|
|
||||||
|
|
||||||
|
def _load_json_file(path: Path) -> dict[str, Any]:
|
||||||
|
payload = json.loads(path.read_text(encoding="utf-8"))
|
||||||
|
if not isinstance(payload, dict):
|
||||||
|
raise ValueError(f"JSON file must contain an object: {path}")
|
||||||
|
return payload
|
||||||
|
|
||||||
|
|
||||||
def _print_providers(graph: FabricGraph, capability: str) -> None:
|
def _print_providers(graph: FabricGraph, capability: str) -> None:
|
||||||
providers = graph.providers(capability)
|
providers = graph.providers(capability)
|
||||||
if not providers:
|
if not providers:
|
||||||
|
|||||||
120
schemas/accountability-update-delta.schema.yaml
Normal file
120
schemas/accountability-update-delta.schema.yaml
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
$schema: "https://json-schema.org/draft/2020-12/schema"
|
||||||
|
$id: "https://railiance.local/fabric/schemas/accountability-update-delta.schema.yaml"
|
||||||
|
title: "AccountabilityUpdateDelta"
|
||||||
|
type: object
|
||||||
|
additionalProperties: false
|
||||||
|
required:
|
||||||
|
- apiVersion
|
||||||
|
- kind
|
||||||
|
- generated_at
|
||||||
|
- current
|
||||||
|
- previous
|
||||||
|
- node_delta
|
||||||
|
- edge_delta
|
||||||
|
- change_sets
|
||||||
|
- summary
|
||||||
|
properties:
|
||||||
|
apiVersion:
|
||||||
|
type: string
|
||||||
|
const: "railiance.fabric/v1alpha2"
|
||||||
|
kind:
|
||||||
|
type: string
|
||||||
|
const: AccountabilityUpdateDelta
|
||||||
|
generated_at:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
current:
|
||||||
|
type: object
|
||||||
|
additionalProperties: true
|
||||||
|
previous:
|
||||||
|
type: object
|
||||||
|
additionalProperties: true
|
||||||
|
node_delta:
|
||||||
|
$ref: "#/$defs/deltaSets"
|
||||||
|
edge_delta:
|
||||||
|
$ref: "#/$defs/deltaSets"
|
||||||
|
change_sets:
|
||||||
|
type: object
|
||||||
|
additionalProperties: false
|
||||||
|
required:
|
||||||
|
- ownership
|
||||||
|
- containment
|
||||||
|
- review_state
|
||||||
|
- blockers
|
||||||
|
properties:
|
||||||
|
ownership:
|
||||||
|
$ref: "#/$defs/stringList"
|
||||||
|
containment:
|
||||||
|
$ref: "#/$defs/stringList"
|
||||||
|
review_state:
|
||||||
|
$ref: "#/$defs/stringList"
|
||||||
|
blockers:
|
||||||
|
$ref: "#/$defs/stringList"
|
||||||
|
summary:
|
||||||
|
type: object
|
||||||
|
additionalProperties: false
|
||||||
|
required:
|
||||||
|
- nodes_added
|
||||||
|
- nodes_changed
|
||||||
|
- nodes_removed
|
||||||
|
- nodes_unchanged
|
||||||
|
- edges_added
|
||||||
|
- edges_changed
|
||||||
|
- edges_removed
|
||||||
|
- edges_unchanged
|
||||||
|
- meaningful_change_count
|
||||||
|
- promotion_needed
|
||||||
|
properties:
|
||||||
|
nodes_added:
|
||||||
|
type: integer
|
||||||
|
minimum: 0
|
||||||
|
nodes_changed:
|
||||||
|
type: integer
|
||||||
|
minimum: 0
|
||||||
|
nodes_removed:
|
||||||
|
type: integer
|
||||||
|
minimum: 0
|
||||||
|
nodes_unchanged:
|
||||||
|
type: integer
|
||||||
|
minimum: 0
|
||||||
|
edges_added:
|
||||||
|
type: integer
|
||||||
|
minimum: 0
|
||||||
|
edges_changed:
|
||||||
|
type: integer
|
||||||
|
minimum: 0
|
||||||
|
edges_removed:
|
||||||
|
type: integer
|
||||||
|
minimum: 0
|
||||||
|
edges_unchanged:
|
||||||
|
type: integer
|
||||||
|
minimum: 0
|
||||||
|
meaningful_change_count:
|
||||||
|
type: integer
|
||||||
|
minimum: 0
|
||||||
|
promotion_needed:
|
||||||
|
type: boolean
|
||||||
|
|
||||||
|
$defs:
|
||||||
|
stringList:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
|
||||||
|
deltaSets:
|
||||||
|
type: object
|
||||||
|
additionalProperties: false
|
||||||
|
required:
|
||||||
|
- added
|
||||||
|
- changed
|
||||||
|
- removed
|
||||||
|
- unchanged
|
||||||
|
properties:
|
||||||
|
added:
|
||||||
|
$ref: "#/$defs/stringList"
|
||||||
|
changed:
|
||||||
|
$ref: "#/$defs/stringList"
|
||||||
|
removed:
|
||||||
|
$ref: "#/$defs/stringList"
|
||||||
|
unchanged:
|
||||||
|
$ref: "#/$defs/stringList"
|
||||||
@@ -5,6 +5,7 @@ from railiance_fabric.accountability_roots import (
|
|||||||
AccountabilityEvidenceStore,
|
AccountabilityEvidenceStore,
|
||||||
build_identity_projection,
|
build_identity_projection,
|
||||||
build_ownership_review,
|
build_ownership_review,
|
||||||
|
build_update_delta,
|
||||||
collect_accountability_root_evidence,
|
collect_accountability_root_evidence,
|
||||||
load_accountability_root_manifest,
|
load_accountability_root_manifest,
|
||||||
)
|
)
|
||||||
@@ -191,6 +192,78 @@ def test_review_identity_cli_persists_decision_for_ownership_review(tmp_path: Pa
|
|||||||
assert repo_item["decision"]["reviewer"] == "tester"
|
assert repo_item["decision"]["reviewer"] == "tester"
|
||||||
|
|
||||||
|
|
||||||
|
def test_update_delta_detects_ownership_changes_and_unchanged_runs(tmp_path: Path) -> None:
|
||||||
|
manifest_path = _fixture_manifest(tmp_path)
|
||||||
|
manifest = load_accountability_root_manifest(manifest_path)
|
||||||
|
projection = build_identity_projection(collect_accountability_root_evidence(manifest_path), manifest)
|
||||||
|
review = build_ownership_review(projection, manifest)
|
||||||
|
|
||||||
|
unchanged = build_update_delta(
|
||||||
|
projection,
|
||||||
|
review,
|
||||||
|
previous_identity_projection=projection,
|
||||||
|
previous_ownership_review=review,
|
||||||
|
)
|
||||||
|
validator = draft202012_validator(Path("schemas/accountability-update-delta.schema.yaml"))
|
||||||
|
assert list(validator.iter_errors(unchanged)) == []
|
||||||
|
assert unchanged["summary"]["promotion_needed"] is False
|
||||||
|
assert unchanged["node_delta"]["unchanged"]
|
||||||
|
|
||||||
|
store = AccountabilityEvidenceStore(tmp_path / "evidence.sqlite3")
|
||||||
|
store.add_review_decision(
|
||||||
|
stable_key="identity:repository:fixture-repo",
|
||||||
|
decision="accept",
|
||||||
|
reviewer="tester",
|
||||||
|
owner_actor_id="actor.fixture.lord",
|
||||||
|
fabric_id="fabric.fixture.primary",
|
||||||
|
)
|
||||||
|
accepted_review = build_ownership_review(
|
||||||
|
projection,
|
||||||
|
manifest,
|
||||||
|
review_decisions=store.latest_review_decisions(),
|
||||||
|
)
|
||||||
|
changed = build_update_delta(
|
||||||
|
projection,
|
||||||
|
accepted_review,
|
||||||
|
previous_identity_projection=projection,
|
||||||
|
previous_ownership_review=review,
|
||||||
|
)
|
||||||
|
assert changed["summary"]["promotion_needed"] is True
|
||||||
|
assert "identity:repository:fixture-repo" in changed["change_sets"]["ownership"]
|
||||||
|
assert "identity:repository:fixture-repo" in changed["change_sets"]["review_state"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_discover_roots_cli_can_emit_delta(tmp_path: Path, capsys) -> None:
|
||||||
|
manifest = _fixture_manifest(tmp_path)
|
||||||
|
manifest_data = load_accountability_root_manifest(manifest)
|
||||||
|
projection = build_identity_projection(collect_accountability_root_evidence(manifest), manifest_data)
|
||||||
|
review = build_ownership_review(projection, manifest_data)
|
||||||
|
projection_path = tmp_path / "previous-identities.json"
|
||||||
|
review_path = tmp_path / "previous-ownership.json"
|
||||||
|
projection_path.write_text(json.dumps(projection), encoding="utf-8")
|
||||||
|
review_path.write_text(json.dumps(review), encoding="utf-8")
|
||||||
|
|
||||||
|
assert (
|
||||||
|
cli_main(
|
||||||
|
[
|
||||||
|
"discover-roots",
|
||||||
|
"--manifest",
|
||||||
|
str(manifest),
|
||||||
|
"--delta",
|
||||||
|
"--previous-identity-projection",
|
||||||
|
str(projection_path),
|
||||||
|
"--previous-ownership-review",
|
||||||
|
str(review_path),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
== 0
|
||||||
|
)
|
||||||
|
|
||||||
|
payload = json.loads(capsys.readouterr().out)
|
||||||
|
assert payload["kind"] == "AccountabilityUpdateDelta"
|
||||||
|
assert payload["summary"]["promotion_needed"] is False
|
||||||
|
|
||||||
|
|
||||||
def _fixture_manifest(tmp_path: Path) -> Path:
|
def _fixture_manifest(tmp_path: Path) -> Path:
|
||||||
workspace = tmp_path / "workspace"
|
workspace = tmp_path / "workspace"
|
||||||
repo = workspace / "fixture-repo"
|
repo = workspace / "fixture-repo"
|
||||||
|
|||||||
@@ -231,7 +231,7 @@ Result:
|
|||||||
|
|
||||||
```task
|
```task
|
||||||
id: RAIL-FAB-WP-0018-T05
|
id: RAIL-FAB-WP-0018-T05
|
||||||
status: todo
|
status: done
|
||||||
priority: medium
|
priority: medium
|
||||||
state_hub_task_id: "c2f28b34-de32-4090-8782-5d00541b9018"
|
state_hub_task_id: "c2f28b34-de32-4090-8782-5d00541b9018"
|
||||||
```
|
```
|
||||||
@@ -257,6 +257,25 @@ Done when:
|
|||||||
changes are highlighted;
|
changes are highlighted;
|
||||||
- unchanged sources are not needlessly promoted.
|
- unchanged sources are not needlessly promoted.
|
||||||
|
|
||||||
|
Result:
|
||||||
|
|
||||||
|
- Added `schemas/accountability-update-delta.schema.yaml` for
|
||||||
|
`AccountabilityUpdateDelta` payloads.
|
||||||
|
- Added `build_update_delta()` to compare current and previous identity
|
||||||
|
projections plus ownership reviews.
|
||||||
|
- Deltas distinguish candidate graph node additions/changes/removals,
|
||||||
|
candidate graph edge additions/changes/removals, ownership changes,
|
||||||
|
containment changes, review-state changes, blocker changes, and unchanged
|
||||||
|
nodes/edges.
|
||||||
|
- Added `railiance-fabric discover-roots --delta` with optional
|
||||||
|
`--previous-identity-projection` and `--previous-ownership-review` inputs.
|
||||||
|
- Added tests proving unchanged runs do not require promotion and ownership
|
||||||
|
review changes are highlighted.
|
||||||
|
- Documented update deltas in the manifest and operator docs.
|
||||||
|
- Verified with `python3 -m pytest tests/test_accountability_root_adapters.py -q`,
|
||||||
|
`python3 -m railiance_fabric.cli discover-roots --max-items-per-root 5 --delta`,
|
||||||
|
and full `python3 -m pytest`.
|
||||||
|
|
||||||
## T06 - Bootstrap The Current Railiance Rebuild
|
## T06 - Bootstrap The Current Railiance Rebuild
|
||||||
|
|
||||||
```task
|
```task
|
||||||
|
|||||||
Reference in New Issue
Block a user