From e4ffe138e5cf159f77e7e82c2c57740bc7cf47d1 Mon Sep 17 00:00:00 2001 From: Unisphere Dev Date: Wed, 21 Oct 2026 14:16:18 +0300 Subject: [PATCH 01/11] Preserve trusted CI history across inadmissible current observations --- docs/spec/integrity-ci.md | 19 +++++ src/devostasis/activity.py | 7 +- src/devostasis/revision_history.py | 62 ++++++++++++--- src/devostasis/vitals/integrity.py | 31 +++++++- tests/test_ci_history_admission.py | 121 +++++++++++++++++++++++++++++ 5 files changed, 226 insertions(+), 14 deletions(-) create mode 100644 tests/test_ci_history_admission.py diff --git a/docs/spec/integrity-ci.md b/docs/spec/integrity-ci.md index 7b854ad..3873595 100644 --- a/docs/spec/integrity-ci.md +++ b/docs/spec/integrity-ci.md @@ -157,6 +157,25 @@ When the current series is partial or unusable, carried records are kept, so history crosses a bundle whose verification evidence could not be read. A current verdict is never carried (HIST-16). +Current inventory admission validates every row before treating absence as +branch departure. Duplicate current ids are diagnosed as +`REVISION_RECORD_DUPLICATE:`; malformed rows and contradictory +coverage prevent a completeness claim. Ambiguous current records cannot +replace carried facts. Current timestamps are parsed before reconciliation; +a different instant for a known revision yields +`IMMUTABLE_COMMIT_TIME_MISMATCH` and preserves its prior time and attempts. +Equivalent timezone representations name the same instant. + +Attempt completeness requires exactly the observed identities 1..latest, +with work bounded by the observed cardinality. A missing or extra identity +cannot establish completeness. Unknown state vocabulary is malformed input; +the legitimate `UNKNOWN` outcome is retained as incomplete history and never +proves absence of failure. Successive states of one attempt, including +`VERIFY_UNRESOLVED` then `VERIFY_PASS`, remain valid observations. An observed +failure remains absorbing even when the rest of history is incomplete. +These are admission repairs within the existing history lineage and accepted +rules, not new thresholds, external-check semantics or a migration. + **Lineage.** A carried union of `devostasis.ci-history.v2` is read as it is. The revision records a 0.1.x bundle keeps in `ci.revision_verdicts_14d` (`devostasis.ci-history.v1`) are replayed from the attempts they kept, never diff --git a/src/devostasis/activity.py b/src/devostasis/activity.py index 88a4650..50aa822 100644 --- a/src/devostasis/activity.py +++ b/src/devostasis/activity.py @@ -14,6 +14,7 @@ from .normalize import CI_REVISIONS, INV_COMMITS, INV_CRS, INV_ISSUES, INV_RELEASES from .observations import ObservationSet from .policy import ACTIVITY +from .revision_history import _series_records BASIS_PREVIOUS = "PREVIOUS_BUNDLE" BASIS_WINDOW = "OBSERVATION_WINDOW_28D" @@ -130,7 +131,11 @@ def build_activity( verification_items: list[dict[str, Any]] = [] verified = failed = unresolved = 0 if ci is not None and ci.has_value: - for rev in ci.value: + admitted, _, problems = _series_records(ci) + coverage_notes.extend(f"VERIFICATION_INPUT_INADMISSIBLE:{problem}" for problem in problems) + if admitted is None: + coverage_notes.append(f"VERIFICATION:{ci.status}:{ci.freshness}") + for rev in admitted or []: if not rev.get("parents") or not _in_interval(rev.get("committed_at"), start, end): continue verified += 1 diff --git a/src/devostasis/revision_history.py b/src/devostasis/revision_history.py index 8c24663..e7c8719 100644 --- a/src/devostasis/revision_history.py +++ b/src/devostasis/revision_history.py @@ -67,6 +67,7 @@ CONTRIBUTION = {FAILURE_OBSERVED: "VERIFY_FAIL", PASS_ONLY_OBSERVED: "VERIFY_PASS", NO_DECISIVE_OBSERVED: None, UNKNOWN_HISTORY: None} ACTIONS_KIND = "github_actions_workflow_run" +ATTEMPT_STATES = {"VERIFY_PASS", "VERIFY_FAIL", "VERIFY_UNRESOLVED", "NON_VERIFY_TERMINAL", "NOT_EXECUTED", "UNKNOWN"} # Where the carried history came from. SOURCE_CARRIED = "CARRIED" @@ -102,6 +103,8 @@ def _attempts(raw: Any, where: str) -> list[dict[str, Any]]: for attempt in raw: if not isinstance(attempt, dict) or not isinstance(attempt.get("state"), str): raise HistoryShapeError(f"{where}: an attempt carries no state") + if attempt["state"] not in ATTEMPT_STATES: + raise HistoryShapeError(f"{where}: unknown attempt state {attempt['state']!r}") number = attempt.get("attempt") if number is not None and (isinstance(number, bool) or not isinstance(number, int) or number < 1): raise HistoryShapeError(f"{where}: attempt number {number!r} is not a positive integer") @@ -183,10 +186,15 @@ def merge_parents(*groups: list[dict[str, Any]]) -> tuple[list[dict[str, Any]], def parent_complete(parent: dict[str, Any]) -> bool: """Every attempt of an Actions run, 1..latest, is in the union. A check suite never is.""" latest = parent.get("latest_attempt") - if parent.get("kind") != ACTIONS_KIND or not isinstance(latest, int) or latest < 1: + if parent.get("kind") != ACTIONS_KIND or type(latest) is not int or latest < 1: return False - numbers = {a["attempt"] for a in parent["attempts"] if isinstance(a.get("attempt"), int)} - return set(range(1, latest + 1)) <= numbers + attempts = parent["attempts"] + if any(type(a.get("attempt")) is not int or not 1 <= a["attempt"] <= latest + or a.get("state") not in ATTEMPT_STATES - {"UNKNOWN"} for a in attempts): + return False + # Bound work by observed evidence, never by an untrusted declared maximum. + # Several successive states of one attempt still represent one identity. + return len({a["attempt"] for a in attempts}) == latest def union_state(parents: list[dict[str, Any]], unresolved: list[str]) -> tuple[str, bool]: @@ -299,11 +307,40 @@ def output(self) -> dict[str, Any]: return {"lineage": LINEAGE, "source": dict(self.source), "records": [encode(record) for record in ordered]} -def _series_records(series: Observation | None) -> tuple[list[dict[str, Any]] | None, bool]: +def _series_records(series: Observation | None) -> tuple[list[dict[str, Any]] | None, bool, list[str]]: """The current revision records and whether they are the whole 14-day inventory.""" if series is None or series.status not in (AVAILABLE, PARTIAL) or series.freshness != FRESH or not isinstance(series.value, list): - return None, False - return [r for r in series.value if isinstance(r, dict) and isinstance(r.get("revision"), str)], series.status == AVAILABLE + return None, False, [] + records, problems, seen, duplicates = [], [], set(), set() + for position, record in enumerate(series.value): + ref = record.get("revision") if isinstance(record, dict) else None + if isinstance(ref, str) and ref: + if ref in seen: + duplicates.add(ref) + seen.add(ref) + try: + if not isinstance(record, dict) or not isinstance(ref, str) or not ref: + raise HistoryShapeError("no immutable revision") + if not isinstance(record.get("committed_at"), str): + raise HistoryShapeError("no committed_at") + timeutil.parse_ts(record["committed_at"]) + if not isinstance(record.get("parents"), list): + raise HistoryShapeError("parents is not a list") + for parent in record["parents"]: + parent_from_current(parent, f"revision {ref}") + if (record.get("history_state") not in CONTRIBUTION + or record.get("historical_contribution") != CONTRIBUTION[record["history_state"]] + or not isinstance(record.get("current_verdict"), (str, type(None)))): + raise HistoryShapeError("inconsistent history contribution or current verdict") + except (HistoryShapeError, ValueError, TypeError) as exc: + problems.append(f"REVISION_RECORD_INCONSISTENT:{ref if isinstance(ref, str) else position}:{exc}") + continue + records.append(record) + problems.extend(f"REVISION_RECORD_DUPLICATE:{ref}" for ref in sorted(duplicates)) + if series.status == AVAILABLE and isinstance(series.coverage, dict) and any(series.coverage.get(key) is False + for key in ("complete", "runs_complete", "attempts_complete", "suites_complete")): + problems.append("REVISION_SERIES_COVERAGE_INCOMPLETE") + return [r for r in records if r["revision"] not in duplicates], series.status == AVAILABLE and not problems, problems def reconcile(obs: ObservationSet) -> Reconciled: @@ -332,7 +369,8 @@ def reconcile(obs: ObservationSet) -> Reconciled: source = {"status": SOURCE_MALFORMED} problems.append(f"REVISION_HISTORY_CARRY_MALFORMED:{exc}") - current, inventory_complete = _series_records(obs.get(REVISIONS)) + current, inventory_complete, current_problems = _series_records(obs.get(REVISIONS)) + problems.extend(current_problems) current_by_rev = {r["revision"]: r for r in current or []} records: dict[str, dict[str, Any]] = {} @@ -345,15 +383,17 @@ def reconcile(obs: ObservationSet) -> Reconciled: for revision, record in current_by_rev.items(): committed_at = record.get("committed_at") - if not isinstance(committed_at, str): - problems.append(f"REVISION_RECORD_INCONSISTENT:{revision}") - continue + prior = records.get(revision) + if prior: + if timeutil.parse_ts(committed_at) != timeutil.parse_ts(prior["committed_at"]): + problems.append(f"IMMUTABLE_COMMIT_TIME_MISMATCH:{revision}") + continue + committed_at = prior["committed_at"] try: visible = [parent_from_current(p, f"revision {revision}") for p in record.get("parents") or []] except HistoryShapeError as exc: problems.append(f"REVISION_RECORD_INCONSISTENT:{revision}:{exc}") continue - prior = records.get(revision) prior_parents = prior["parents"] if prior else [] unresolved = list(prior.get("unresolved") or []) if prior else [] if unresolved and visible and all(parent_complete(p) for p in visible): diff --git a/src/devostasis/vitals/integrity.py b/src/devostasis/vitals/integrity.py index 1430dc2..ba69dd3 100644 --- a/src/devostasis/vitals/integrity.py +++ b/src/devostasis/vitals/integrity.py @@ -194,6 +194,25 @@ def _bearing(history: Reconciled, revisions: list[dict[str, Any]]) -> list[dict[ return [r for r in records if r["parents"] or r.get("unresolved")] +def _unknown_current(revisions: list[dict[str, Any]], diagnostics: list[str]) -> dict[str, Any]: + """Retain the accepted current-verdict diagnostic beside an independent admission/history gap.""" + active = [r for r in revisions if _record_consistent(r) and r.get("parents") + and isinstance(r.get("committed_at"), str)] + if not active: + return {} + latest = _newest(active) + current = latest.get("current_verdict") or VERDICT_UNKNOWN + if current not in VERDICTS: + diagnostics.append(f"CURRENT_VERDICT_UNRECOGNIZED:{current}") + current = VERDICT_UNKNOWN + if current != VERDICT_UNKNOWN: + return {} + diagnostics.append(f"CURRENT_VERDICT_UNKNOWN:{latest['revision']}") + return {"unknown_verdict_rule": UNKNOWN_RULE, "current_revision": { + "revision": latest["revision"], "committed_at": latest["committed_at"], + "current_verdict": current, "history_state": latest["history_state"]}} + + def evaluate(obs: ObservationSet) -> VitalResult: diagnostics: list[str] = [] reconciled = reconcile(obs) @@ -230,7 +249,7 @@ def evaluate(obs: ObservationSet) -> VitalResult: # shape its lineage declares, is a coverage defect, not a pass. diagnostics.extend(f"REVISION_RECORD_INCONSISTENT:{_record_label(r)}" for r in inconsistent) diagnostics.extend(reconciled.problems) - return _unknown(obs, history, diagnostics) + return _unknown(obs, {**history, **_unknown_current(revisions, diagnostics)}, diagnostics) active = [r for r in revisions if r.get("parents")] provenance: dict[str, int] = {} for r in active: @@ -276,8 +295,16 @@ def evaluate(obs: ObservationSet) -> VitalResult: # R52 and PV-HIST-002: history nobody can prove is neither a pass nor a # zero, and T9 puts it above every conservative branch. diagnostics.extend(f"REVISION_HISTORY_UNKNOWN:{r['revision']}" for r in unknown_history) + current_gap = _unknown_current(revisions, diagnostics) + if current_gap: + current_gap["failure_ratio_14d"] = ratio(f, n) if n else None + strength = sample_strength(n) + if strength is not None: + current_gap["sample_strength"] = strength + if strength == SAMPLE_SPARSE: + diagnostics.append(SPARSE_DIAGNOSTIC) return _result( - obs, None, EVAL_UNKNOWN, None, None, {**counts, **history}, diagnostics, + obs, None, EVAL_UNKNOWN, None, None, {**counts, **history, **current_gap}, diagnostics, f"The verification history of {len(unknown_history)} recent revisions cannot be proven complete; {f} of {n} decisive revisions failed, and no band is claimed around the unknown ones.", ) diff --git a/tests/test_ci_history_admission.py b/tests/test_ci_history_admission.py new file mode 100644 index 0000000..c519c3e --- /dev/null +++ b/tests/test_ci_history_admission.py @@ -0,0 +1,121 @@ +"""Release-review regressions at real observation, evaluator and bundle boundaries.""" + +from copy import deepcopy +import json + +import pytest + +from devostasis.bundle import verify_dir +from devostasis.config import single_project +from devostasis.history import FilesystemHistoryStore +from devostasis.observations import ObservationSet +from devostasis.revision_history import ( + CARRIED, LINEAGE, REPLAYABLE_LINEAGE, parent_from_current, union_state, +) +from devostasis.runner import build_from_observations +from devostasis.vitals import integrity +from helpers import add +from test_revision_history import ( + LOCATOR, _build, _integrity, _obs, _passing, _record, _run, _states, +) + + +@pytest.mark.parametrize("conflicting,reverse", [(False, False), (True, False), (True, True)]) +def test_duplicate_current_revisions_never_improve_sample_or_overwrite_failure(tmp_path, conflicting, reverse): + rows = _passing([1, 2, 3]) + duplicate = deepcopy(rows[0]) if not conflicting else _record("p1", 1, [_run(801, [(1, "VERIFY_FAIL")])]) + rows.append(duplicate) + if reverse: + rows.reverse() + obs = ObservationSet.from_dict(_obs("2026-09-08T12:00:00Z", rows).to_dict()) + original = deepcopy(obs.to_dict()) + store = FilesystemHistoryStore(tmp_path) + project = single_project(LOCATOR, config_version="1") + bundle = build_from_observations(project, obs, store) + result = _integrity(bundle) + assert (result["band"], result["evaluation_status"]) == (None, "UNKNOWN") + assert "REVISION_RECORD_DUPLICATE:p1" in result["diagnostics"] + assert obs.to_dict() == original + assert verify_dir(store.commit(bundle)) == [] + saved = ObservationSet.from_dict(json.loads(bundle.members["observations.json"])) + assert build_from_observations(project, saved, FilesystemHistoryStore(tmp_path / "replay")).members == bundle.members + + +@pytest.mark.parametrize("bad", [None, [], {}, {"revision": "r"}, {"revision": "", "committed_at": "2026-09-04T10:00:00Z"}, + {"revision": 7, "committed_at": "2026-09-04T10:00:00Z"}, {"revision": "r", "committed_at": None}, + {"revision": "r", "committed_at": "invalid"}]) +def test_malformed_current_inventory_cannot_prove_carried_failure_left_branch(tmp_path, bad): + store = FilesystemHistoryStore(tmp_path) + _build(store, "2026-09-06T12:00:00Z", [_record("r", 4, [_run(41, [(1, "VERIFY_FAIL")])])]) + bundle, path = _build(store, "2026-09-07T12:00:00Z", _passing([1, 2, 3, 5]) + [bad]) + assert _integrity(bundle)["evaluation_status"] == "UNKNOWN" + assert _states(bundle)["r"] == "FAILURE_OBSERVED" + assert verify_dir(path) == [] + + +@pytest.mark.parametrize("bad_time", ["invalid", "2026-08-01T10:00:00Z", "2026-10-01T10:00:00Z"]) +def test_four_bundles_preserve_failure_after_rejected_immutable_timestamp(tmp_path, bad_time): + store = FilesystemHistoryStore(tmp_path) + passing = _passing([1, 2, 3, 5]) + failed = _record("r", 4, [_run(41, [(1, "VERIFY_FAIL")])]) + _build(store, "2026-09-06T12:00:00Z", passing + [failed]) + corrupted = _record("r", 4, [_run(41, [(2, "VERIFY_PASS")], latest=2)]) + corrupted["committed_at"] = bad_time + second, second_path = _build(store, "2026-09-07T12:00:00Z", passing + [corrupted]) + assert _states(second)["r"] == "FAILURE_OBSERVED" + assert _integrity(second)["evaluation_status"] == "UNKNOWN" + assert verify_dir(second_path) == [] + for day in (8, 9): + bundle, path = _build(store, f"2026-09-{day:02d}T12:00:00Z", passing + [_record("r", 4, [_run(41, [(2, "VERIFY_PASS")], latest=2)])]) + result = _integrity(bundle) + record = next(r for r in result["derived"]["revision_history"]["records"] if r["revision"] == "r") + assert record["committed_at"] == failed["committed_at"] + assert record["history_state"] == "FAILURE_OBSERVED" + assert result["derived"]["failed_count_14d"] == 1 and result["band"] == "FLAKY" + assert verify_dir(path) == [] + + +def test_equivalent_timestamp_and_proven_departure_remain_admissible(tmp_path): + store = FilesystemHistoryStore(tmp_path) + failed = _record("r", 4, [_run(41, [(1, "VERIFY_FAIL")])]) + _build(store, "2026-09-06T12:00:00Z", [failed]) + equivalent = deepcopy(failed); equivalent["committed_at"] = "2026-09-04T12:00:00+02:00" + bundle, _ = _build(store, "2026-09-07T12:00:00Z", [equivalent]) + assert _states(bundle)["r"] == "FAILURE_OBSERVED" + assert not any("IMMUTABLE_COMMIT_TIME_MISMATCH" in d for d in _integrity(bundle)["diagnostics"]) + partial, _ = _build(store, "2026-09-08T12:00:00Z", [], series_status="PARTIAL") + assert _states(partial)["r"] == "FAILURE_OBSERVED" + departed, _ = _build(store, "2026-09-09T12:00:00Z", []) + assert "r" not in _states(departed) + + +@pytest.mark.parametrize("attempts,latest", [([(1, "VERIFY_PASS"), (3, "VERIFY_PASS")], 3), + ([(1, "VERIFY_PASS"), (2, "VERIFY_PASS"), (3, "VERIFY_PASS")], 2), + ([(1, "VERIFY_PASS")], 10**12), ([(1, "UNKNOWN"), (2, "VERIFY_PASS")], 2)]) +def test_incomplete_or_unknown_attempts_never_prove_pass_only(attempts, latest): + parent = parent_from_current(_run(41, attempts, latest=latest), "test") + assert union_state([parent], []) == ("UNKNOWN_HISTORY", False) + + +@pytest.mark.parametrize("lineage", [LINEAGE, REPLAYABLE_LINEAGE]) +def test_unknown_attempts_stay_unknown_in_carried_lineages(lineage): + from devostasis.revision_history import encode, union_record + raw = _record("r", 4, [_run(41, [(1, "UNKNOWN"), (2, "VERIFY_PASS")])]) + records = [raw] if lineage == REPLAYABLE_LINEAGE else [encode(union_record("r", raw["committed_at"], [parent_from_current(raw["parents"][0], "test")], []))] + obs = _obs("2026-09-08T12:00:00Z", [_record("r", 4, [])]) + add(obs, CARRIED, {"lineage": lineage, "records": records}, "record") + assert integrity.evaluate(obs).band is None + assert integrity.evaluate(obs).derived["revision_history"]["records"][0]["history_state"] == "UNKNOWN_HISTORY" + + +def test_unknown_vocabulary_is_rejected_but_legitimate_transitions_and_failures_survive(): + from devostasis.revision_history import HistoryShapeError, merge_parents + with pytest.raises(HistoryShapeError): + parent_from_current(_run(41, [(1, "NOT_A_CI_STATE")]), "test") + pending = parent_from_current(_run(41, [(1, "VERIFY_UNRESOLVED")]), "test") + passed = parent_from_current(_run(41, [(1, "VERIFY_PASS")]), "test") + for first, second in ((pending, passed), (passed, pending)): + merged, conflicts = merge_parents([first], [second]) + assert union_state(merged, conflicts) == ("PASS_ONLY_OBSERVED", True) + failed = parent_from_current(_run(41, [(1, "VERIFY_FAIL"), (3, "UNKNOWN")], latest=3), "test") + assert union_state([failed], []) == ("FAILURE_OBSERVED", False) From fdd594aa2f9e6064865891b32b48794493f90f8f Mon Sep 17 00:00:00 2001 From: Unisphere Dev Date: Wed, 21 Oct 2026 14:16:20 +0300 Subject: [PATCH 02/11] Validate source omissions and expose unproven size claims --- docs/code-analysis.md | 6 +++ docs/spec/code-analysis.md | 12 ++++++ src/devostasis/codeanalysis/bundle.py | 12 +++++- src/devostasis/codeanalysis/cli.py | 5 ++- src/devostasis/codeanalysis/git.py | 42 ++++++++++-------- tests/test_source_admission.py | 61 +++++++++++++++++++++++++++ 6 files changed, 118 insertions(+), 20 deletions(-) create mode 100644 tests/test_source_admission.py diff --git a/docs/code-analysis.md b/docs/code-analysis.md index ba700b8..ca29021 100644 --- a/docs/code-analysis.md +++ b/docs/code-analysis.md @@ -48,6 +48,12 @@ The local identity derives from the recorded root commit IDs, not a forge URL; shallow or unrelated roots and changed policy make comparisons INCOMPARABLE. History counts and root traversal are acquisition observations; their complete ancestry is not independently proved by the source tree proof (issue #73). +Likewise, a tree binds an omitted blob's identity, not its separately recorded +size. Verification checks the deterministic selection predicates and marks +omitted blob sizes `UNPROVEN` in its operational `proof_scope` receipt. Byte-cap +reasons that depend on those sizes remain acquisition claims; `VERIFIED` means +the admitted proofs and recorded bundle replay, not proof of those omissions. +The receipt is outside the immutable artifact and does not rewrite v1 bundles. Compare two verified bundles under the same policy: diff --git a/docs/spec/code-analysis.md b/docs/spec/code-analysis.md index 433a6b0..a10d93d 100644 --- a/docs/spec/code-analysis.md +++ b/docs/spec/code-analysis.md @@ -45,6 +45,18 @@ UNSUPPORTED/LANGUAGE_UNSUPPORTED. UNAVAILABLE reasons are UNSAFE_PATH, SOURCE_LINK_OR_SUBMODULE, FILE_BYTE_CAP, FILE_COUNT_CAP and TOTAL_BYTE_CAP. `sources` maps exactly admitted paths to canonical base64 of unmodified bytes. +Offline admission applies the collector's same ordered predicates: safe path, +policy scope, regular blob mode, supported language, per-file bytes, preceding +admitted-file count and preceding verified bytes plus current declared size. +A recognized reason alone is insufficient to omit an otherwise eligible file. +Path/mode and policy/count predicates are independently checked. A tree does +not authenticate a blob's size when its bytes are omitted; byte-based omission +predicates can only be replayed against that acquisition claim. They do not +prove that the omission was necessary. `code verify` reports `proof_scope` +outside the canonical members: source tree and admitted blobs VERIFIED, +omitted blob sizes UNPROVEN (or NOT_APPLICABLE), history ancestry UNPROVEN. +No unbounded blob read or new compatibility/migration rule is implied. + `proof` contains the raw base64 commit object and an object-ID-to-base64 map of every reachable tree. Verification computes framed Git object hashes, checks the commit/tree link, expands all trees, checks the entire inventory and diff --git a/src/devostasis/codeanalysis/bundle.py b/src/devostasis/codeanalysis/bundle.py index ca84c37..a291775 100644 --- a/src/devostasis/codeanalysis/bundle.py +++ b/src/devostasis/codeanalysis/bundle.py @@ -17,7 +17,7 @@ from ..workscope.operations import no_links from . import CONTRACT, ENGINE from .engine import RULES, evaluate, policy -from .git import object_digest +from .git import object_digest, source_selection MEMBERS = ("inputs.json", "policy.json", "analysis.json", "findings.sarif", "report.md") MAX_BUNDLE_BYTES = 128 * 1024 * 1024 @@ -92,6 +92,8 @@ def admit(raw, config): require(entry["status"] in ("ADMITTED", "UNAVAILABLE", "UNSUPPORTED", "OUT_OF_SCOPE"), "inventory availability unknown") if entry["kind"] == "blob": number(entry["size"], 0, 2**63 - 1) else: require(entry["size"] is None, "submodule must not claim blob size") + selection = source_selection(safe, entry["mode"], entry["kind"], entry["size"], config, len(admitted), source_total) + require((entry["status"], entry["reason"]) == selection, "unjustified source selection") included = safe is not None and (not config["include"] or any(safe == p or safe.startswith(p + "/") for p in config["include"])) excluded = safe is not None and any(safe == p or safe.startswith(p + "/") for p in config["exclude"]) if entry["status"] == "ADMITTED": @@ -210,6 +212,14 @@ def verify(root): return verify_content(read(root)) +def proof_scope(decoded): + """Operational verification limits, outside the immutable v1 artifact bytes.""" + omitted = any(e["kind"] == "blob" and e["status"] != "ADMITTED" for e in decoded["inputs.json"]["entries"]) + return {"source_tree": "VERIFIED", "admitted_blobs": "VERIFIED", + "omitted_blob_sizes": "UNPROVEN" if omitted else "NOT_APPLICABLE", + "history_ancestry": "UNPROVEN"} + + def publish(root, content): verify_content(content); root = no_links(root) root.parent.mkdir(parents=True, exist_ok=True); no_links(root.parent) diff --git a/src/devostasis/codeanalysis/cli.py b/src/devostasis/codeanalysis/cli.py index c053de9..29f0ae8 100644 --- a/src/devostasis/codeanalysis/cli.py +++ b/src/devostasis/codeanalysis/cli.py @@ -5,7 +5,7 @@ from ..canonical import canonical_bytes, loads from ..workscope.model import require -from .bundle import MAX_BUNDLE_BYTES, build, publish, verify +from .bundle import MAX_BUNDLE_BYTES, build, proof_scope, publish, verify from .consumer import compare, packet, verify_packet, work_evidence from .engine import default_policy from .git import collect @@ -42,7 +42,8 @@ def cmd(args): if args.operation == "compare": emit(compare(verify(args.before), verify(args.after)), args.output); return 0 decoded = verify(args.bundle) - if args.operation == "verify": emit({"status": "VERIFIED", "bundle_id": decoded["manifest.json"]["bundle_id"]}) + if args.operation == "verify": emit({"status": "VERIFIED", "bundle_id": decoded["manifest.json"]["bundle_id"], + "proof_scope": proof_scope(decoded)}) elif args.operation == "show": emit(decoded["analysis.json"], args.output) elif args.operation == "sarif": emit(decoded["findings.sarif"], args.output) elif args.operation == "packet": emit(packet(decoded, args.path, args.max_files, args.max_bytes), args.output) diff --git a/src/devostasis/codeanalysis/git.py b/src/devostasis/codeanalysis/git.py index 7dc1576..1dcb409 100644 --- a/src/devostasis/codeanalysis/git.py +++ b/src/devostasis/codeanalysis/git.py @@ -32,6 +32,27 @@ def blob_digest(data, object_format): return object_digest(data, object_format) +def source_selection(name, mode, kind, size, config, count, total): + """Deterministic acquisition priority; omitted blob sizes remain caller observations.""" + if name is None: + return "UNAVAILABLE", "UNSAFE_PATH" + included = not config["include"] or any(name == p or name.startswith(p + "/") for p in config["include"]) + excluded = any(name == p or name.startswith(p + "/") for p in config["exclude"]) + if not included or excluded: + return "OUT_OF_SCOPE", "POLICY_EXCLUSION" + if mode not in ("100644", "100755") or kind != "blob": + return "UNAVAILABLE", "SOURCE_LINK_OR_SUBMODULE" + if not name.endswith(".py"): + return "UNSUPPORTED", "LANGUAGE_UNSUPPORTED" + if size > config["budget"]["max_file_bytes"]: + return "UNAVAILABLE", "FILE_BYTE_CAP" + if count >= config["budget"]["max_files"]: + return "UNAVAILABLE", "FILE_COUNT_CAP" + if total + size > config["budget"]["max_total_bytes"]: + return "UNAVAILABLE", "TOTAL_BYTE_CAP" + return "ADMITTED", None + + class Git: def __init__(self, directory, seconds=120): self.directory = Path(directory).resolve() @@ -133,23 +154,10 @@ def collect(directory, ref, config, at=None): entry["path"] = path(filename.decode("utf-8")) except (UnicodeError, ValueError): entries.append(entry); continue - name = entry["path"] - included = not config["include"] or any(name == p or name.startswith(p + "/") for p in config["include"]) - excluded = any(name == p or name.startswith(p + "/") for p in config["exclude"]) - if not included or excluded: - entry.update(status="OUT_OF_SCOPE", reason="POLICY_EXCLUSION") - elif mode not in (b"100644", b"100755") or kind != b"blob": - entry.update(reason="SOURCE_LINK_OR_SUBMODULE") - elif not name.endswith(".py"): - entry.update(status="UNSUPPORTED", reason="LANGUAGE_UNSUPPORTED") - elif entry["size"] > config["budget"]["max_file_bytes"]: - entry.update(reason="FILE_BYTE_CAP") - elif len(selected) >= config["budget"]["max_files"]: - entry.update(reason="FILE_COUNT_CAP") - elif total + entry["size"] > config["budget"]["max_total_bytes"]: - entry.update(reason="TOTAL_BYTE_CAP") - else: - entry.update(status="ADMITTED", reason=None); selected.append(entry); total += entry["size"] + status, reason = source_selection(entry["path"], entry["mode"], entry["kind"], entry["size"], config, len(selected), total) + entry.update(status=status, reason=reason) + if status == "ADMITTED": + selected.append(entry); total += entry["size"] entries.append(entry) sources = {} if selected: diff --git a/tests/test_source_admission.py b/tests/test_source_admission.py new file mode 100644 index 0000000..21d8e9e --- /dev/null +++ b/tests/test_source_admission.py @@ -0,0 +1,61 @@ +"""Independent source-selection predicates and explicit omitted-size proof limits.""" + +from copy import deepcopy +import json + +import pytest + +from devostasis.cli import main +from devostasis.codeanalysis.bundle import build, proof_scope, publish, verify_content +from devostasis.codeanalysis.engine import default_policy +from devostasis.codeanalysis.git import collect +from test_code_analysis import AT, repository + + +@pytest.mark.parametrize("reason", ["UNSAFE_PATH", "SOURCE_LINK_OR_SUBMODULE", "FILE_BYTE_CAP", "FILE_COUNT_CAP", "TOTAL_BYTE_CAP"]) +def test_rehashed_source_omission_requires_its_actual_predicate(tmp_path, reason): + config = default_policy() + raw = collect(repository(tmp_path, {"safe.py": "pass\n"}), "HEAD", config, AT) + raw["entries"][0].update(status="UNAVAILABLE", reason=reason) + raw["sources"].clear() + with pytest.raises(ValueError, match="unjustified source selection"): + build(raw, config) + + +def test_valid_caps_and_policy_exclusions_still_replay(tmp_path): + repo = repository(tmp_path, {"a.py": "pass\n", "b.py": "pass\n", "vendor/c.py": "pass\n", "README.md": "fixture"}) + for budget, value in (("max_files", 1), ("max_file_bytes", 1), ("max_total_bytes", 6)): + config = default_policy(); config["budget"][budget] = value + if budget == "max_total_bytes": config["budget"]["max_file_bytes"] = value + raw = collect(repo, "HEAD", config, AT) + _, members = build(raw, config) + decoded = verify_content(members) + assert decoded["analysis.json"]["coverage"] == "PARTIAL" + assert proof_scope(decoded)["omitted_blob_sizes"] == "UNPROVEN" + assert any(e["status"] == "OUT_OF_SCOPE" for e in raw["entries"]) + + +def test_omitted_size_claim_does_not_become_independent_blob_proof(tmp_path, capsys): + config = default_policy() + raw = collect(repository(tmp_path, {"safe.py": "pass\n"}), "HEAD", config, AT) + # The tree authenticates the blob id, not this replacement size metadata. + changed = deepcopy(raw) + changed["entries"][0].update(status="UNAVAILABLE", reason="FILE_BYTE_CAP", size=2_000_000) + changed["sources"].clear() + _, members = build(changed, config) + decoded = verify_content(members) + assert decoded["analysis.json"]["coverage"] == "PARTIAL" + assert proof_scope(decoded)["omitted_blob_sizes"] == "UNPROVEN" + root = publish(tmp_path / "bundle", members) + assert main(["code", "verify", "--bundle", str(root)]) == 0 + receipt = json.loads(capsys.readouterr().out) + assert receipt["proof_scope"]["omitted_blob_sizes"] == "UNPROVEN" + assert receipt["proof_scope"]["history_ancestry"] == "UNPROVEN" + + +def test_admitted_size_is_independently_bound_to_original_bytes(tmp_path): + config = default_policy() + raw = collect(repository(tmp_path, {"safe.py": "pass\n"}), "HEAD", config, AT) + raw["entries"][0]["size"] += 1 + with pytest.raises(ValueError, match="source blob binding mismatch"): + build(raw, config) From 7a818043fca9e2660ce1ce802df37b764fdbe231 Mon Sep 17 00:00:00 2001 From: Unisphere Dev Date: Wed, 21 Oct 2026 14:16:21 +0300 Subject: [PATCH 03/11] Reconcile release stabilization and document the first report --- CHANGELOG.md | 11 +++++++++ README.md | 21 ++++++++++++++--- ROADMAP.md | 66 +++++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 94 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 193c949..bd79b63 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,17 @@ contract or a policy always come with a version bump of that contract. Offline Repository Analysis, target E4, issue #72. +- Release-readiness repairs: reject ambiguous/malformed current CI inventory + before it can remove carried failures; preserve immutable revision time; + require exact bounded attempt identities and retain UNKNOWN history. Activity + shares current-record admission and exposes rejected verification inputs. +- Replay native source-selection predicates instead of accepting a recognized + omission reason alone. Operational verification explicitly distinguishes + proved source/tree bytes from unproven omitted sizes and history ancestry; + canonical v1 artifact bytes and existing lineages remain unchanged. +- Reconcile the October 21 release state/sequence and add a tested direct + first-report path; the v0.6.0 installation remains conditional on publication. + - New stdlib-only `code` companion: immutable local Git snapshot, Python 3.12 AST structure, six scoped findings, declared-module import cycles and bounded recent non-merge change hotspots. No source execution or forge dependency. diff --git a/README.md b/README.md index 31ee0a3..980dc80 100644 --- a/README.md +++ b/README.md @@ -211,15 +211,27 @@ For this unreleased checkout: pip install -e . ``` -After the 0.5.0 release tag is published: +After the 0.6.0 release tag is published: ```bash pip install git+https://github.com/drevendev/devostasis@v0.6.0 ``` -Observe one repository (a GitHub token is read from `DEVOSTASIS_GITHUB_TOKEN`, +Produce your first seven-Vital report without a configuration file: + +```bash +devostasis run --repo drevendev/Devostasis --store ./history-local +devostasis verify --bundle history-local/projects/github.com/drevendev/devostasis/latest +``` + +Open `history-local/projects/github.com/drevendev/devostasis/latest/report.md`. +The first successful run is a `BASELINE`; later runs compare with the previous +verified bundle. Unavailable API evidence remains explicit, and planning/debt +without configured registers or mappings remains undeclared/uninstrumented. + +A GitHub token is read from `DEVOSTASIS_GITHUB_TOKEN`, `GITHUB_TOKEN`, `GH_TOKEN` or `gh auth token`; public repositories work -without one, with a low rate limit): +without one, with a low rate limit. For separate observation and evaluation: ```bash devostasis observe --repo owner/name --out observations.json @@ -332,6 +344,9 @@ gauges, accepted band ordering and executable conformance vectors. The pending 0.2.0 adoption adds the accepted Vital repairs and durable Integrity history; 0.3.0 adds the separate Evidence to Action companion and 0.4.0 adds reproducible consumer handoff and accepted receipt/compatibility contracts. +0.5.0 adds bound consumer operations and portable private history; 0.6.0 adds +native offline Python analysis and verified source packets. The cumulative +release is still under review, including inherited history-admission repairs. Its GitLab work collector does not yet implement the core GitLab Vital adapter or the accepted Instruments carrier. diff --git a/ROADMAP.md b/ROADMAP.md index eff266e..dba863e 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -4,7 +4,71 @@ Ordered by what unblocks what, not by what is interesting. Every item names who or what blocks it, so a reader can tell the difference between work not started and work that cannot start. -## Release state and next steps — 2026-10-08 +## Release state and next steps — 2026-10-21 + +The project is at **release stabilization**, with Phase A closed and Phase B +five of seven items implemented. B1 still has 62 named cases without executable +proof, and B7 still needs sustained outside-consumer evidence. The latest +published version is **v0.1.9**; 0.2.0 through 0.6.0 remain unreleased. + +The audit began with a clean working tree. All local and fetched remote heads +are ancestors of upstream `release/0.6.0` at `22a2894`, including the original +maintenance commits and the Dependabot correction merged through #79. Nine +earlier PRs remain open: #36/#37/#50/#53/#57/#59/#64/#69/#75. There is no +unrepresented development branch to recover. Inclusion in the release branch +does not publish its code on `master` or create a release tag. + +Upstream CI for the 0.6 source head `09d497f` passed on Python 3.12/3.13/3.14 +in [run 39030444073](https://github.com/drevendev/Devostasis/actions/runs/39030444073). +The Dependabot merge head `22a2894` also passed +[CI](https://github.com/drevendev/Devostasis/actions/runs/39169319950). +The old `action_required` run is historical evidence, not the current CI state. +These successful checks do not resolve the October 14/16/18 admission reviews +on #57/#75 or approve a later repaired head. + +### Delivery order + +1. **Finish the admission repairs before release.** Current malformed or + duplicate revision records must not erase carried failures or inflate the + sample. Admit timestamps before reconciliation, preserve immutable revision + time, check exact attempt identities without allocation by their declared + maximum, and retain UNKNOWN attempt evidence. Exercise actual four-bundle + evaluator/store chains and current/historical replay. Source omissions must + satisfy path/mode/scope and deterministic budget predicates; omitted blob + sizes remain acquisition claims, explicitly unproven by a tree proof. +2. **Propagate and review every final release head.** Preserve the prepared + sequence: corrected #57 into `release/0.2.0`, then #53, #59, #64, #69 and #75 + into `master`, with their respective tags under CONTRIBUTING.md. Carry the + history repair into descendants, include #79's Dependabot correction in + 0.6, and verify each changed head independently. Reconcile #36/#37/#50 after + their delivered commits land. A green matrix is evidence, not acceptance. +3. **Make the existing product straightforward to try.** The README now starts + with a direct single-repository report and its actual path (#78). Retain + source-install instructions until publication is confirmed. #63 still needs + actual scheduled Dependabot execution; the configuration correction alone + does not complete that operational acceptance. +4. **Complete assurance before increasing reach.** Work the outstanding + timestamp/lineage/store audit families (#35), the remaining exact B1 cases + (#20/#23) and independently proved source-history ancestry/counts (#73). + Keep partial coverage, historical compatibility and evidence limits visible. + +### Proposed next major code increment + +After releasing 0.6, the highest-impact code-only increment is **independently +verifiable history and executable assurance**: bounded commit/parent/tree +proofs for #73, hostile rehashed-history controls, and the missing executable +conformance boundaries for B1. This strengthens the proof objects on which +both work routing and native hotspots depend. It is a proposal, not acceptance +of a new research contract, a new release number, or completion of B1. + +The larger product milestone remains a real outside consumer completing work +from verified scopes with retained results and calendar history. Deployment +and scheduling remain owner-deferred in #71; they are not silently reinstated +by this audit. Once B7 and existing compatibility/review gates are met, extend +reach with the accepted core GitLab adapter and Instrument carrier. The +existing GitLab work collector does not implement that core adapter. + +## Historical 0.6 implementation snapshot — 2026-10-08 **0.6.0 Offline Repository Analysis (E4) is prepared on `zendreven/release-0.6.0`, carrying the pending 0.2–0.5 ancestry.** From 2003a9e8bfbf078f538ea9722d028aa771d1b82d Mon Sep 17 00:00:00 2001 From: Unisphere Dev Date: Wed, 21 Oct 2026 14:16:18 +0300 Subject: [PATCH 04/11] Preserve trusted CI history across inadmissible current observations --- docs/spec/integrity-ci.md | 19 +++++ src/devostasis/activity.py | 7 +- src/devostasis/revision_history.py | 62 ++++++++++++--- src/devostasis/vitals/integrity.py | 31 +++++++- tests/test_ci_history_admission.py | 121 +++++++++++++++++++++++++++++ 5 files changed, 226 insertions(+), 14 deletions(-) create mode 100644 tests/test_ci_history_admission.py diff --git a/docs/spec/integrity-ci.md b/docs/spec/integrity-ci.md index 7b854ad..3873595 100644 --- a/docs/spec/integrity-ci.md +++ b/docs/spec/integrity-ci.md @@ -157,6 +157,25 @@ When the current series is partial or unusable, carried records are kept, so history crosses a bundle whose verification evidence could not be read. A current verdict is never carried (HIST-16). +Current inventory admission validates every row before treating absence as +branch departure. Duplicate current ids are diagnosed as +`REVISION_RECORD_DUPLICATE:`; malformed rows and contradictory +coverage prevent a completeness claim. Ambiguous current records cannot +replace carried facts. Current timestamps are parsed before reconciliation; +a different instant for a known revision yields +`IMMUTABLE_COMMIT_TIME_MISMATCH` and preserves its prior time and attempts. +Equivalent timezone representations name the same instant. + +Attempt completeness requires exactly the observed identities 1..latest, +with work bounded by the observed cardinality. A missing or extra identity +cannot establish completeness. Unknown state vocabulary is malformed input; +the legitimate `UNKNOWN` outcome is retained as incomplete history and never +proves absence of failure. Successive states of one attempt, including +`VERIFY_UNRESOLVED` then `VERIFY_PASS`, remain valid observations. An observed +failure remains absorbing even when the rest of history is incomplete. +These are admission repairs within the existing history lineage and accepted +rules, not new thresholds, external-check semantics or a migration. + **Lineage.** A carried union of `devostasis.ci-history.v2` is read as it is. The revision records a 0.1.x bundle keeps in `ci.revision_verdicts_14d` (`devostasis.ci-history.v1`) are replayed from the attempts they kept, never diff --git a/src/devostasis/activity.py b/src/devostasis/activity.py index 70126a1..b15f5a4 100644 --- a/src/devostasis/activity.py +++ b/src/devostasis/activity.py @@ -14,6 +14,7 @@ from .normalize import CI_REVISIONS, INV_COMMITS, INV_CRS, INV_ISSUES, INV_RELEASES from .observations import ObservationSet from .policy import ACTIVITY +from .revision_history import _series_records BASIS_PREVIOUS = "PREVIOUS_BUNDLE" BASIS_WINDOW = "OBSERVATION_WINDOW_28D" @@ -130,7 +131,11 @@ def build_activity( verification_items: list[dict[str, Any]] = [] verified = failed = unresolved = 0 if ci is not None and ci.has_value: - for rev in ci.value: + admitted, _, problems = _series_records(ci) + coverage_notes.extend(f"VERIFICATION_INPUT_INADMISSIBLE:{problem}" for problem in problems) + if admitted is None: + coverage_notes.append(f"VERIFICATION:{ci.status}:{ci.freshness}") + for rev in admitted or []: if not rev.get("parents") or not _in_interval(rev.get("committed_at"), start, end): continue verified += 1 diff --git a/src/devostasis/revision_history.py b/src/devostasis/revision_history.py index 8c24663..e7c8719 100644 --- a/src/devostasis/revision_history.py +++ b/src/devostasis/revision_history.py @@ -67,6 +67,7 @@ CONTRIBUTION = {FAILURE_OBSERVED: "VERIFY_FAIL", PASS_ONLY_OBSERVED: "VERIFY_PASS", NO_DECISIVE_OBSERVED: None, UNKNOWN_HISTORY: None} ACTIONS_KIND = "github_actions_workflow_run" +ATTEMPT_STATES = {"VERIFY_PASS", "VERIFY_FAIL", "VERIFY_UNRESOLVED", "NON_VERIFY_TERMINAL", "NOT_EXECUTED", "UNKNOWN"} # Where the carried history came from. SOURCE_CARRIED = "CARRIED" @@ -102,6 +103,8 @@ def _attempts(raw: Any, where: str) -> list[dict[str, Any]]: for attempt in raw: if not isinstance(attempt, dict) or not isinstance(attempt.get("state"), str): raise HistoryShapeError(f"{where}: an attempt carries no state") + if attempt["state"] not in ATTEMPT_STATES: + raise HistoryShapeError(f"{where}: unknown attempt state {attempt['state']!r}") number = attempt.get("attempt") if number is not None and (isinstance(number, bool) or not isinstance(number, int) or number < 1): raise HistoryShapeError(f"{where}: attempt number {number!r} is not a positive integer") @@ -183,10 +186,15 @@ def merge_parents(*groups: list[dict[str, Any]]) -> tuple[list[dict[str, Any]], def parent_complete(parent: dict[str, Any]) -> bool: """Every attempt of an Actions run, 1..latest, is in the union. A check suite never is.""" latest = parent.get("latest_attempt") - if parent.get("kind") != ACTIONS_KIND or not isinstance(latest, int) or latest < 1: + if parent.get("kind") != ACTIONS_KIND or type(latest) is not int or latest < 1: return False - numbers = {a["attempt"] for a in parent["attempts"] if isinstance(a.get("attempt"), int)} - return set(range(1, latest + 1)) <= numbers + attempts = parent["attempts"] + if any(type(a.get("attempt")) is not int or not 1 <= a["attempt"] <= latest + or a.get("state") not in ATTEMPT_STATES - {"UNKNOWN"} for a in attempts): + return False + # Bound work by observed evidence, never by an untrusted declared maximum. + # Several successive states of one attempt still represent one identity. + return len({a["attempt"] for a in attempts}) == latest def union_state(parents: list[dict[str, Any]], unresolved: list[str]) -> tuple[str, bool]: @@ -299,11 +307,40 @@ def output(self) -> dict[str, Any]: return {"lineage": LINEAGE, "source": dict(self.source), "records": [encode(record) for record in ordered]} -def _series_records(series: Observation | None) -> tuple[list[dict[str, Any]] | None, bool]: +def _series_records(series: Observation | None) -> tuple[list[dict[str, Any]] | None, bool, list[str]]: """The current revision records and whether they are the whole 14-day inventory.""" if series is None or series.status not in (AVAILABLE, PARTIAL) or series.freshness != FRESH or not isinstance(series.value, list): - return None, False - return [r for r in series.value if isinstance(r, dict) and isinstance(r.get("revision"), str)], series.status == AVAILABLE + return None, False, [] + records, problems, seen, duplicates = [], [], set(), set() + for position, record in enumerate(series.value): + ref = record.get("revision") if isinstance(record, dict) else None + if isinstance(ref, str) and ref: + if ref in seen: + duplicates.add(ref) + seen.add(ref) + try: + if not isinstance(record, dict) or not isinstance(ref, str) or not ref: + raise HistoryShapeError("no immutable revision") + if not isinstance(record.get("committed_at"), str): + raise HistoryShapeError("no committed_at") + timeutil.parse_ts(record["committed_at"]) + if not isinstance(record.get("parents"), list): + raise HistoryShapeError("parents is not a list") + for parent in record["parents"]: + parent_from_current(parent, f"revision {ref}") + if (record.get("history_state") not in CONTRIBUTION + or record.get("historical_contribution") != CONTRIBUTION[record["history_state"]] + or not isinstance(record.get("current_verdict"), (str, type(None)))): + raise HistoryShapeError("inconsistent history contribution or current verdict") + except (HistoryShapeError, ValueError, TypeError) as exc: + problems.append(f"REVISION_RECORD_INCONSISTENT:{ref if isinstance(ref, str) else position}:{exc}") + continue + records.append(record) + problems.extend(f"REVISION_RECORD_DUPLICATE:{ref}" for ref in sorted(duplicates)) + if series.status == AVAILABLE and isinstance(series.coverage, dict) and any(series.coverage.get(key) is False + for key in ("complete", "runs_complete", "attempts_complete", "suites_complete")): + problems.append("REVISION_SERIES_COVERAGE_INCOMPLETE") + return [r for r in records if r["revision"] not in duplicates], series.status == AVAILABLE and not problems, problems def reconcile(obs: ObservationSet) -> Reconciled: @@ -332,7 +369,8 @@ def reconcile(obs: ObservationSet) -> Reconciled: source = {"status": SOURCE_MALFORMED} problems.append(f"REVISION_HISTORY_CARRY_MALFORMED:{exc}") - current, inventory_complete = _series_records(obs.get(REVISIONS)) + current, inventory_complete, current_problems = _series_records(obs.get(REVISIONS)) + problems.extend(current_problems) current_by_rev = {r["revision"]: r for r in current or []} records: dict[str, dict[str, Any]] = {} @@ -345,15 +383,17 @@ def reconcile(obs: ObservationSet) -> Reconciled: for revision, record in current_by_rev.items(): committed_at = record.get("committed_at") - if not isinstance(committed_at, str): - problems.append(f"REVISION_RECORD_INCONSISTENT:{revision}") - continue + prior = records.get(revision) + if prior: + if timeutil.parse_ts(committed_at) != timeutil.parse_ts(prior["committed_at"]): + problems.append(f"IMMUTABLE_COMMIT_TIME_MISMATCH:{revision}") + continue + committed_at = prior["committed_at"] try: visible = [parent_from_current(p, f"revision {revision}") for p in record.get("parents") or []] except HistoryShapeError as exc: problems.append(f"REVISION_RECORD_INCONSISTENT:{revision}:{exc}") continue - prior = records.get(revision) prior_parents = prior["parents"] if prior else [] unresolved = list(prior.get("unresolved") or []) if prior else [] if unresolved and visible and all(parent_complete(p) for p in visible): diff --git a/src/devostasis/vitals/integrity.py b/src/devostasis/vitals/integrity.py index 1430dc2..ba69dd3 100644 --- a/src/devostasis/vitals/integrity.py +++ b/src/devostasis/vitals/integrity.py @@ -194,6 +194,25 @@ def _bearing(history: Reconciled, revisions: list[dict[str, Any]]) -> list[dict[ return [r for r in records if r["parents"] or r.get("unresolved")] +def _unknown_current(revisions: list[dict[str, Any]], diagnostics: list[str]) -> dict[str, Any]: + """Retain the accepted current-verdict diagnostic beside an independent admission/history gap.""" + active = [r for r in revisions if _record_consistent(r) and r.get("parents") + and isinstance(r.get("committed_at"), str)] + if not active: + return {} + latest = _newest(active) + current = latest.get("current_verdict") or VERDICT_UNKNOWN + if current not in VERDICTS: + diagnostics.append(f"CURRENT_VERDICT_UNRECOGNIZED:{current}") + current = VERDICT_UNKNOWN + if current != VERDICT_UNKNOWN: + return {} + diagnostics.append(f"CURRENT_VERDICT_UNKNOWN:{latest['revision']}") + return {"unknown_verdict_rule": UNKNOWN_RULE, "current_revision": { + "revision": latest["revision"], "committed_at": latest["committed_at"], + "current_verdict": current, "history_state": latest["history_state"]}} + + def evaluate(obs: ObservationSet) -> VitalResult: diagnostics: list[str] = [] reconciled = reconcile(obs) @@ -230,7 +249,7 @@ def evaluate(obs: ObservationSet) -> VitalResult: # shape its lineage declares, is a coverage defect, not a pass. diagnostics.extend(f"REVISION_RECORD_INCONSISTENT:{_record_label(r)}" for r in inconsistent) diagnostics.extend(reconciled.problems) - return _unknown(obs, history, diagnostics) + return _unknown(obs, {**history, **_unknown_current(revisions, diagnostics)}, diagnostics) active = [r for r in revisions if r.get("parents")] provenance: dict[str, int] = {} for r in active: @@ -276,8 +295,16 @@ def evaluate(obs: ObservationSet) -> VitalResult: # R52 and PV-HIST-002: history nobody can prove is neither a pass nor a # zero, and T9 puts it above every conservative branch. diagnostics.extend(f"REVISION_HISTORY_UNKNOWN:{r['revision']}" for r in unknown_history) + current_gap = _unknown_current(revisions, diagnostics) + if current_gap: + current_gap["failure_ratio_14d"] = ratio(f, n) if n else None + strength = sample_strength(n) + if strength is not None: + current_gap["sample_strength"] = strength + if strength == SAMPLE_SPARSE: + diagnostics.append(SPARSE_DIAGNOSTIC) return _result( - obs, None, EVAL_UNKNOWN, None, None, {**counts, **history}, diagnostics, + obs, None, EVAL_UNKNOWN, None, None, {**counts, **history, **current_gap}, diagnostics, f"The verification history of {len(unknown_history)} recent revisions cannot be proven complete; {f} of {n} decisive revisions failed, and no band is claimed around the unknown ones.", ) diff --git a/tests/test_ci_history_admission.py b/tests/test_ci_history_admission.py new file mode 100644 index 0000000..c519c3e --- /dev/null +++ b/tests/test_ci_history_admission.py @@ -0,0 +1,121 @@ +"""Release-review regressions at real observation, evaluator and bundle boundaries.""" + +from copy import deepcopy +import json + +import pytest + +from devostasis.bundle import verify_dir +from devostasis.config import single_project +from devostasis.history import FilesystemHistoryStore +from devostasis.observations import ObservationSet +from devostasis.revision_history import ( + CARRIED, LINEAGE, REPLAYABLE_LINEAGE, parent_from_current, union_state, +) +from devostasis.runner import build_from_observations +from devostasis.vitals import integrity +from helpers import add +from test_revision_history import ( + LOCATOR, _build, _integrity, _obs, _passing, _record, _run, _states, +) + + +@pytest.mark.parametrize("conflicting,reverse", [(False, False), (True, False), (True, True)]) +def test_duplicate_current_revisions_never_improve_sample_or_overwrite_failure(tmp_path, conflicting, reverse): + rows = _passing([1, 2, 3]) + duplicate = deepcopy(rows[0]) if not conflicting else _record("p1", 1, [_run(801, [(1, "VERIFY_FAIL")])]) + rows.append(duplicate) + if reverse: + rows.reverse() + obs = ObservationSet.from_dict(_obs("2026-09-08T12:00:00Z", rows).to_dict()) + original = deepcopy(obs.to_dict()) + store = FilesystemHistoryStore(tmp_path) + project = single_project(LOCATOR, config_version="1") + bundle = build_from_observations(project, obs, store) + result = _integrity(bundle) + assert (result["band"], result["evaluation_status"]) == (None, "UNKNOWN") + assert "REVISION_RECORD_DUPLICATE:p1" in result["diagnostics"] + assert obs.to_dict() == original + assert verify_dir(store.commit(bundle)) == [] + saved = ObservationSet.from_dict(json.loads(bundle.members["observations.json"])) + assert build_from_observations(project, saved, FilesystemHistoryStore(tmp_path / "replay")).members == bundle.members + + +@pytest.mark.parametrize("bad", [None, [], {}, {"revision": "r"}, {"revision": "", "committed_at": "2026-09-04T10:00:00Z"}, + {"revision": 7, "committed_at": "2026-09-04T10:00:00Z"}, {"revision": "r", "committed_at": None}, + {"revision": "r", "committed_at": "invalid"}]) +def test_malformed_current_inventory_cannot_prove_carried_failure_left_branch(tmp_path, bad): + store = FilesystemHistoryStore(tmp_path) + _build(store, "2026-09-06T12:00:00Z", [_record("r", 4, [_run(41, [(1, "VERIFY_FAIL")])])]) + bundle, path = _build(store, "2026-09-07T12:00:00Z", _passing([1, 2, 3, 5]) + [bad]) + assert _integrity(bundle)["evaluation_status"] == "UNKNOWN" + assert _states(bundle)["r"] == "FAILURE_OBSERVED" + assert verify_dir(path) == [] + + +@pytest.mark.parametrize("bad_time", ["invalid", "2026-08-01T10:00:00Z", "2026-10-01T10:00:00Z"]) +def test_four_bundles_preserve_failure_after_rejected_immutable_timestamp(tmp_path, bad_time): + store = FilesystemHistoryStore(tmp_path) + passing = _passing([1, 2, 3, 5]) + failed = _record("r", 4, [_run(41, [(1, "VERIFY_FAIL")])]) + _build(store, "2026-09-06T12:00:00Z", passing + [failed]) + corrupted = _record("r", 4, [_run(41, [(2, "VERIFY_PASS")], latest=2)]) + corrupted["committed_at"] = bad_time + second, second_path = _build(store, "2026-09-07T12:00:00Z", passing + [corrupted]) + assert _states(second)["r"] == "FAILURE_OBSERVED" + assert _integrity(second)["evaluation_status"] == "UNKNOWN" + assert verify_dir(second_path) == [] + for day in (8, 9): + bundle, path = _build(store, f"2026-09-{day:02d}T12:00:00Z", passing + [_record("r", 4, [_run(41, [(2, "VERIFY_PASS")], latest=2)])]) + result = _integrity(bundle) + record = next(r for r in result["derived"]["revision_history"]["records"] if r["revision"] == "r") + assert record["committed_at"] == failed["committed_at"] + assert record["history_state"] == "FAILURE_OBSERVED" + assert result["derived"]["failed_count_14d"] == 1 and result["band"] == "FLAKY" + assert verify_dir(path) == [] + + +def test_equivalent_timestamp_and_proven_departure_remain_admissible(tmp_path): + store = FilesystemHistoryStore(tmp_path) + failed = _record("r", 4, [_run(41, [(1, "VERIFY_FAIL")])]) + _build(store, "2026-09-06T12:00:00Z", [failed]) + equivalent = deepcopy(failed); equivalent["committed_at"] = "2026-09-04T12:00:00+02:00" + bundle, _ = _build(store, "2026-09-07T12:00:00Z", [equivalent]) + assert _states(bundle)["r"] == "FAILURE_OBSERVED" + assert not any("IMMUTABLE_COMMIT_TIME_MISMATCH" in d for d in _integrity(bundle)["diagnostics"]) + partial, _ = _build(store, "2026-09-08T12:00:00Z", [], series_status="PARTIAL") + assert _states(partial)["r"] == "FAILURE_OBSERVED" + departed, _ = _build(store, "2026-09-09T12:00:00Z", []) + assert "r" not in _states(departed) + + +@pytest.mark.parametrize("attempts,latest", [([(1, "VERIFY_PASS"), (3, "VERIFY_PASS")], 3), + ([(1, "VERIFY_PASS"), (2, "VERIFY_PASS"), (3, "VERIFY_PASS")], 2), + ([(1, "VERIFY_PASS")], 10**12), ([(1, "UNKNOWN"), (2, "VERIFY_PASS")], 2)]) +def test_incomplete_or_unknown_attempts_never_prove_pass_only(attempts, latest): + parent = parent_from_current(_run(41, attempts, latest=latest), "test") + assert union_state([parent], []) == ("UNKNOWN_HISTORY", False) + + +@pytest.mark.parametrize("lineage", [LINEAGE, REPLAYABLE_LINEAGE]) +def test_unknown_attempts_stay_unknown_in_carried_lineages(lineage): + from devostasis.revision_history import encode, union_record + raw = _record("r", 4, [_run(41, [(1, "UNKNOWN"), (2, "VERIFY_PASS")])]) + records = [raw] if lineage == REPLAYABLE_LINEAGE else [encode(union_record("r", raw["committed_at"], [parent_from_current(raw["parents"][0], "test")], []))] + obs = _obs("2026-09-08T12:00:00Z", [_record("r", 4, [])]) + add(obs, CARRIED, {"lineage": lineage, "records": records}, "record") + assert integrity.evaluate(obs).band is None + assert integrity.evaluate(obs).derived["revision_history"]["records"][0]["history_state"] == "UNKNOWN_HISTORY" + + +def test_unknown_vocabulary_is_rejected_but_legitimate_transitions_and_failures_survive(): + from devostasis.revision_history import HistoryShapeError, merge_parents + with pytest.raises(HistoryShapeError): + parent_from_current(_run(41, [(1, "NOT_A_CI_STATE")]), "test") + pending = parent_from_current(_run(41, [(1, "VERIFY_UNRESOLVED")]), "test") + passed = parent_from_current(_run(41, [(1, "VERIFY_PASS")]), "test") + for first, second in ((pending, passed), (passed, pending)): + merged, conflicts = merge_parents([first], [second]) + assert union_state(merged, conflicts) == ("PASS_ONLY_OBSERVED", True) + failed = parent_from_current(_run(41, [(1, "VERIFY_FAIL"), (3, "UNKNOWN")], latest=3), "test") + assert union_state([failed], []) == ("FAILURE_OBSERVED", False) From 22eb5cb90c9b72181f78ea47670ac69a3ada35d8 Mon Sep 17 00:00:00 2001 From: Unisphere Dev Date: Wed, 21 Oct 2026 14:26:04 +0300 Subject: [PATCH 05/11] Keep current revision metadata bound to reconciled history --- src/devostasis/vitals/integrity.py | 8 ++++---- tests/test_ci_history_admission.py | 8 ++++++++ 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/devostasis/vitals/integrity.py b/src/devostasis/vitals/integrity.py index ba69dd3..75c8c7b 100644 --- a/src/devostasis/vitals/integrity.py +++ b/src/devostasis/vitals/integrity.py @@ -194,7 +194,7 @@ def _bearing(history: Reconciled, revisions: list[dict[str, Any]]) -> list[dict[ return [r for r in records if r["parents"] or r.get("unresolved")] -def _unknown_current(revisions: list[dict[str, Any]], diagnostics: list[str]) -> dict[str, Any]: +def _unknown_current(revisions: list[dict[str, Any]], diagnostics: list[str], history: Reconciled) -> dict[str, Any]: """Retain the accepted current-verdict diagnostic beside an independent admission/history gap.""" active = [r for r in revisions if _record_consistent(r) and r.get("parents") and isinstance(r.get("committed_at"), str)] @@ -210,7 +210,7 @@ def _unknown_current(revisions: list[dict[str, Any]], diagnostics: list[str]) -> diagnostics.append(f"CURRENT_VERDICT_UNKNOWN:{latest['revision']}") return {"unknown_verdict_rule": UNKNOWN_RULE, "current_revision": { "revision": latest["revision"], "committed_at": latest["committed_at"], - "current_verdict": current, "history_state": latest["history_state"]}} + "current_verdict": current, "history_state": history.records.get(latest["revision"], {}).get("history_state", latest["history_state"])}} def evaluate(obs: ObservationSet) -> VitalResult: @@ -249,7 +249,7 @@ def evaluate(obs: ObservationSet) -> VitalResult: # shape its lineage declares, is a coverage defect, not a pass. diagnostics.extend(f"REVISION_RECORD_INCONSISTENT:{_record_label(r)}" for r in inconsistent) diagnostics.extend(reconciled.problems) - return _unknown(obs, {**history, **_unknown_current(revisions, diagnostics)}, diagnostics) + return _unknown(obs, {**history, **_unknown_current(revisions, diagnostics, reconciled)}, diagnostics) active = [r for r in revisions if r.get("parents")] provenance: dict[str, int] = {} for r in active: @@ -295,7 +295,7 @@ def evaluate(obs: ObservationSet) -> VitalResult: # R52 and PV-HIST-002: history nobody can prove is neither a pass nor a # zero, and T9 puts it above every conservative branch. diagnostics.extend(f"REVISION_HISTORY_UNKNOWN:{r['revision']}" for r in unknown_history) - current_gap = _unknown_current(revisions, diagnostics) + current_gap = _unknown_current(revisions, diagnostics, reconciled) if current_gap: current_gap["failure_ratio_14d"] = ratio(f, n) if n else None strength = sample_strength(n) diff --git a/tests/test_ci_history_admission.py b/tests/test_ci_history_admission.py index c519c3e..6f8e8fb 100644 --- a/tests/test_ci_history_admission.py +++ b/tests/test_ci_history_admission.py @@ -119,3 +119,11 @@ def test_unknown_vocabulary_is_rejected_but_legitimate_transitions_and_failures_ assert union_state(merged, conflicts) == ("PASS_ONLY_OBSERVED", True) failed = parent_from_current(_run(41, [(1, "VERIFY_FAIL"), (3, "UNKNOWN")], latest=3), "test") assert union_state([failed], []) == ("FAILURE_OBSERVED", False) + + +def test_current_revision_metadata_matches_its_reconciled_unknown_history(): + from helpers import parent, revision + raw = revision("new", "2026-09-06T10:00:00Z", [parent("p-new", "UNKNOWN")], "UNKNOWN", "NO_DECISIVE_OBSERVED") + result = integrity.evaluate(_obs("2026-09-08T12:00:00Z", _passing([1, 2, 3]) + [raw])) + assert result.derived["current_revision"]["history_state"] == "UNKNOWN_HISTORY" + assert result.derived["unknown_history_count_14d"] == 1 From b849923511ffef24b3b53d0ed01937ae706f60f8 Mon Sep 17 00:00:00 2001 From: Unisphere Dev Date: Wed, 21 Oct 2026 14:26:04 +0300 Subject: [PATCH 06/11] Keep current revision metadata bound to reconciled history --- src/devostasis/vitals/integrity.py | 8 ++++---- tests/test_ci_history_admission.py | 8 ++++++++ 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/devostasis/vitals/integrity.py b/src/devostasis/vitals/integrity.py index ba69dd3..75c8c7b 100644 --- a/src/devostasis/vitals/integrity.py +++ b/src/devostasis/vitals/integrity.py @@ -194,7 +194,7 @@ def _bearing(history: Reconciled, revisions: list[dict[str, Any]]) -> list[dict[ return [r for r in records if r["parents"] or r.get("unresolved")] -def _unknown_current(revisions: list[dict[str, Any]], diagnostics: list[str]) -> dict[str, Any]: +def _unknown_current(revisions: list[dict[str, Any]], diagnostics: list[str], history: Reconciled) -> dict[str, Any]: """Retain the accepted current-verdict diagnostic beside an independent admission/history gap.""" active = [r for r in revisions if _record_consistent(r) and r.get("parents") and isinstance(r.get("committed_at"), str)] @@ -210,7 +210,7 @@ def _unknown_current(revisions: list[dict[str, Any]], diagnostics: list[str]) -> diagnostics.append(f"CURRENT_VERDICT_UNKNOWN:{latest['revision']}") return {"unknown_verdict_rule": UNKNOWN_RULE, "current_revision": { "revision": latest["revision"], "committed_at": latest["committed_at"], - "current_verdict": current, "history_state": latest["history_state"]}} + "current_verdict": current, "history_state": history.records.get(latest["revision"], {}).get("history_state", latest["history_state"])}} def evaluate(obs: ObservationSet) -> VitalResult: @@ -249,7 +249,7 @@ def evaluate(obs: ObservationSet) -> VitalResult: # shape its lineage declares, is a coverage defect, not a pass. diagnostics.extend(f"REVISION_RECORD_INCONSISTENT:{_record_label(r)}" for r in inconsistent) diagnostics.extend(reconciled.problems) - return _unknown(obs, {**history, **_unknown_current(revisions, diagnostics)}, diagnostics) + return _unknown(obs, {**history, **_unknown_current(revisions, diagnostics, reconciled)}, diagnostics) active = [r for r in revisions if r.get("parents")] provenance: dict[str, int] = {} for r in active: @@ -295,7 +295,7 @@ def evaluate(obs: ObservationSet) -> VitalResult: # R52 and PV-HIST-002: history nobody can prove is neither a pass nor a # zero, and T9 puts it above every conservative branch. diagnostics.extend(f"REVISION_HISTORY_UNKNOWN:{r['revision']}" for r in unknown_history) - current_gap = _unknown_current(revisions, diagnostics) + current_gap = _unknown_current(revisions, diagnostics, reconciled) if current_gap: current_gap["failure_ratio_14d"] = ratio(f, n) if n else None strength = sample_strength(n) diff --git a/tests/test_ci_history_admission.py b/tests/test_ci_history_admission.py index c519c3e..6f8e8fb 100644 --- a/tests/test_ci_history_admission.py +++ b/tests/test_ci_history_admission.py @@ -119,3 +119,11 @@ def test_unknown_vocabulary_is_rejected_but_legitimate_transitions_and_failures_ assert union_state(merged, conflicts) == ("PASS_ONLY_OBSERVED", True) failed = parent_from_current(_run(41, [(1, "VERIFY_FAIL"), (3, "UNKNOWN")], latest=3), "test") assert union_state([failed], []) == ("FAILURE_OBSERVED", False) + + +def test_current_revision_metadata_matches_its_reconciled_unknown_history(): + from helpers import parent, revision + raw = revision("new", "2026-09-06T10:00:00Z", [parent("p-new", "UNKNOWN")], "UNKNOWN", "NO_DECISIVE_OBSERVED") + result = integrity.evaluate(_obs("2026-09-08T12:00:00Z", _passing([1, 2, 3]) + [raw])) + assert result.derived["current_revision"]["history_state"] == "UNKNOWN_HISTORY" + assert result.derived["unknown_history_count_14d"] == 1 From 3250ae33513fa50c76d4ebe6aba3a1a696321020 Mon Sep 17 00:00:00 2001 From: Unisphere Dev Date: Wed, 21 Oct 2026 16:16:01 +0300 Subject: [PATCH 07/11] feat(code): authenticate bounded Git history with exact legacy replay --- docs/code-analysis.md | 12 +- docs/spec/code-analysis.md | 73 +++- examples/code-v2/bundle/analysis.json | 1 + examples/code-v2/bundle/findings.sarif | 1 + examples/code-v2/bundle/inputs.json | 1 + examples/code-v2/bundle/manifest.json | 1 + examples/code-v2/bundle/policy.json | 1 + examples/code-v2/bundle/report.md | 52 +++ examples/code-v2/evidence.json | 1 + examples/code-v2/inventory.json | 1 + examples/code-v2/packet.json | 1 + examples/code-v2/policy.json | 1 + examples/code-v2/work-bundle/evidence.json | 1 + examples/code-v2/work-bundle/inventory.json | 1 + examples/code-v2/work-bundle/manifest.json | 1 + examples/code-v2/work-bundle/policy.json | 1 + examples/code-v2/work-bundle/report.md | 15 + examples/code-v2/work-bundle/scope.json | 1 + examples/code-v2/work-policy.json | 1 + schemas/code-history-proof.schema.json | 78 +++++ schemas/code-manifest-v2.schema.json | 108 ++++++ schemas/code-packet-v2.schema.json | 142 ++++++++ schemas/code-policy-v2.schema.json | 184 +++++++++++ scripts/make_code_examples.py | 9 +- src/devostasis/codeanalysis/__init__.py | 16 +- src/devostasis/codeanalysis/bundle.py | 33 +- src/devostasis/codeanalysis/consumer.py | 12 +- src/devostasis/codeanalysis/engine.py | 33 +- src/devostasis/codeanalysis/git.py | 17 +- src/devostasis/codeanalysis/history_proof.py | 331 +++++++++++++++++++ tests/test_code_analysis.py | 2 +- tests/test_code_history_proof.py | 141 ++++++++ tests/test_source_admission.py | 2 +- 33 files changed, 1233 insertions(+), 42 deletions(-) create mode 100644 examples/code-v2/bundle/analysis.json create mode 100644 examples/code-v2/bundle/findings.sarif create mode 100644 examples/code-v2/bundle/inputs.json create mode 100644 examples/code-v2/bundle/manifest.json create mode 100644 examples/code-v2/bundle/policy.json create mode 100644 examples/code-v2/bundle/report.md create mode 100644 examples/code-v2/evidence.json create mode 100644 examples/code-v2/inventory.json create mode 100644 examples/code-v2/packet.json create mode 100644 examples/code-v2/policy.json create mode 100644 examples/code-v2/work-bundle/evidence.json create mode 100644 examples/code-v2/work-bundle/inventory.json create mode 100644 examples/code-v2/work-bundle/manifest.json create mode 100644 examples/code-v2/work-bundle/policy.json create mode 100644 examples/code-v2/work-bundle/report.md create mode 100644 examples/code-v2/work-bundle/scope.json create mode 100644 examples/code-v2/work-policy.json create mode 100644 schemas/code-history-proof.schema.json create mode 100644 schemas/code-manifest-v2.schema.json create mode 100644 schemas/code-packet-v2.schema.json create mode 100644 schemas/code-policy-v2.schema.json create mode 100644 src/devostasis/codeanalysis/history_proof.py create mode 100644 tests/test_code_history_proof.py diff --git a/docs/code-analysis.md b/docs/code-analysis.md index ca29021..9e650a3 100644 --- a/docs/code-analysis.md +++ b/docs/code-analysis.md @@ -36,7 +36,7 @@ metadata and source bytes are included; keep real analysis bundles private when those inputs are private. The public example is entirely synthetic: ```sh -devostasis code verify --bundle examples/code/bundle +devostasis code verify --bundle examples/code-v2/bundle ``` Python coverage is COMPLETE, PARTIAL or NOT_APPLICABLE. Other languages remain @@ -46,8 +46,12 @@ the admitted Python scope, with the recorded static resolution assumptions. It does not prove runtime behavior or the absence of defects in other languages. The local identity derives from the recorded root commit IDs, not a forge URL; shallow or unrelated roots and changed policy make comparisons INCOMPARABLE. -History counts and root traversal are acquisition observations; their complete -ancestry is not independently proved by the source tree proof (issue #73). +New bundles use v2: authenticated raw commit/parent/tree/blob proof lets offline +verification derive roots, ancestry completeness, window selection and edit +counts independently. Counts use the declared MYERS_LF_BYTE_LINES_V1 algorithm, +with no rename heuristics. Missing objects or graph/byte/work caps remain PARTIAL; +partial roots cannot establish repository comparability. Historical v1 bundles +in examples/code still replay exactly and report history ancestry UNPROVEN. Likewise, a tree binds an omitted blob's identity, not its separately recorded size. Verification checks the deterministic selection predicates and marks omitted blob sizes `UNPROVEN` in its operational `proof_scope` receipt. Byte-cap @@ -94,4 +98,4 @@ No external runner, schedule, credentials or deployment is required for the offline commands. B1, B7 and release/review gates remain separate. See the [wire contract](spec/code-analysis.md) and -[synthetic report](../examples/code/bundle/report.md). +[synthetic v2 report](../examples/code-v2/bundle/report.md). diff --git a/docs/spec/code-analysis.md b/docs/spec/code-analysis.md index a10d93d..9da629a 100644 --- a/docs/spec/code-analysis.md +++ b/docs/spec/code-analysis.md @@ -4,7 +4,10 @@ Implementation-owned companion, introduced in 0.6.0. No accepted research Vital rule, ordering or Work Instrument judgement changes. Its identifiers are `devostasis.code-analysis.v1`, `devostasis.code-policy.v1`, `devostasis.code-engine.v1`, `devostasis.code-packet.v1` and -`devostasis.code-comparison.v1`. Case IDs below use the local DEV-CODE family. +`devostasis.code-comparison.v1` for historical artifacts. New collection defaults to +the corresponding `.v2` contracts and `devostasis.git-history-proof.v1`. Exact +policy/manifest/engine dispatch preserves v1 bytes; mixed or unknown tuples fail. +Case IDs below use the local DEV-CODE family. ## Admission and collection @@ -65,7 +68,7 @@ commit proof to 1 MiB, expanded leaves to 100000. Missing/extra/duplicate tree, path or source data is rejected. SHA-1/SHA-256 Git format is explicit. This establishes a commitment to source bytes, not authenticated commit authorship. -History contains `status`, unique `reasons`, `start`, `end`, `commits`. The +Historical v1 history contains `status`, unique `reasons`, `start`, `end`, `commits`. The window is inclusive, ends at observed_at and spans policy days. COMPLETE means the bounded collector exhausted its selected non-merge ancestor walk; PARTIAL retains admitted rows and cap/shallow/path gaps; UNAVAILABLE admits no @@ -74,7 +77,71 @@ Each commit has `revision`, `committed_at`, `changes`; changes contain `path`, `added`, `deleted` (both null for binary changes). Changes sort by path, commits by timestamp/full ID, with no duplicates. Counts, traversal roots and history completeness are recorded acquisition claims, not proved ancestry. -Offline verification replays those observations; proof hardening is #73. +Offline v1 verification replays those observations. Its operational proof scope +continues to report history ancestry UNPROVEN; v2 provides the proof below. + +## Independent v2 history proof + +V2 adds `history_proof` to inputs and boolean `roots_complete` to subject. The +closed proof has `contract`, `commits`, `trees`, `blobs`, `omitted`, `frontier`. +Object maps hold canonical base64 raw objects keyed by their framed Git hashes; +types, hashes, format widths and reachability are checked. Current source proof +objects are reused without duplication. Unused extra objects/omissions fail. +`omitted` maps required absent object IDs to OBJECT_UNAVAILABLE, OBJECT_BYTE_CAP, +OBJECT_COUNT_CAP, PROOF_BYTE_CAP or TIME_CAP. These reasons disclose acquisition +limits; they never establish that an omitted object was truly inaccessible. + +The verifier walks every parent edge from the pinned head, including merge +parents, without pruning by commit timestamps. Commit raw headers bind parents, +tree and committer epoch. Full roots are exactly the reachable parentless +commits. Missing/unvisited edges form the sorted frontier. An empty frontier is +required for `roots_complete=true` and ancestry COMPLETE. Partial repository +identity additionally hashes `unproven_ancestry_anchor=revision`; it cannot +establish a COMPARABLE pair. Full identity retains the object-format/root-set +formula. None of this authenticates a forge identity or commit author. + +V2 history also records `ancestry_status` and +`count_algorithm=MYERS_LF_BYTE_LINES_V1`. All proved non-merge ancestors in the +inclusive window are selected by committer time, newest first up to max_commits, +then emitted in ascending (time, full ID) order. Merge commits remain in the +proof graph. A selected commit compares its complete tree to its sole parent's +tree, or the empty tree for a proved root. There is no rename detection. + +Line counts use the shortest insertion/deletion edit distance over exact byte +lines split only at LF, retaining LF and an unterminated final line. CR is a +byte, not an extra separator. For distance D and old/new lengths N/M, added is +(D+M-N)/2 and deleted is (D+N-M)/2. NUL-containing inputs are BINARY, with both +counts null. Same-blob mode-only changes have zero counts. Git's configurable +numstat/rename heuristics are not this algorithm. A separate dynamic-programming +LCS test oracle checks these counts. + +Every change adds `count_status` and `reason`: VERIFIED, BINARY, UNAVAILABLE or +OUT_OF_SCOPE/POLICY_EXCLUSION. Include/exclude prefixes also govern history; +excluded blob bytes are never acquired for a diff. Missing blobs, unknown tree +presence, unsafe paths and exhausted diff limits cannot become zero edits. +Every commit adds `changes_complete`. Global COMPLETE requires complete ancestry, +no selected-commit truncation and complete in-scope change proof. PARTIAL retains +proved rows plus sorted reasons. History disabled remains NOT_REQUESTED while +bounded ancestry still proves or explicitly limits local identity. + +V2 policy adds max_ancestors (1..10000, default 2000), max_objects (1..50000, +default 10000), max_proof_bytes (1024..67108864, default 33554432), max_blob_bytes +(1..4194304, default 4194304), max_diff_work (1..10000000, default 2000000), and +max_diff_lines (1..200000, default 100000). Additional proof budgets exclude +already admitted current-source objects. Tree expansion and Myers work share +the deterministic work bound; references cap at 100000, frontier at 10000, +individual commits at 1 MiB and trees at 8 MiB. Total canonical bundle remains +128 MiB. No Git process, repository, network, external diff or clock is used by +replay. Missing proof never becomes a COMPLETE claim by rehashing the envelope. + +V2 hotspots add `unproven_changes` and `count_status`; numeric totals contain +only proved numeric edits. Coverage still describes the whole selected window. +Operational `proof_scope` reports ancestry VERIFIED/PARTIAL and counts +VERIFIED/PARTIAL/NOT_REQUESTED. Omitted source blob sizes remain UNPROVEN unless +not applicable: history proof does not universally resolve source omission debt. +The v1 schemas/examples remain frozen; separate `*-v2.schema.json`, +`code-history-proof.schema.json` and `examples/code-v2` publish this lineage. +Cross-engine comparisons are INCOMPARABLE with ANALYSIS_LINEAGE_CHANGED. ## Derived source evidence diff --git a/examples/code-v2/bundle/analysis.json b/examples/code-v2/bundle/analysis.json new file mode 100644 index 0000000..2d73f09 --- /dev/null +++ b/examples/code-v2/bundle/analysis.json @@ -0,0 +1 @@ +{"authority":"STATIC_SOURCE_EVIDENCE; no execution, independent acceptance, health score or merge authority","coverage":"COMPLETE","engine":"devostasis.code-engine.v2","files":[{"functions":[],"lines":null,"module":null,"object_id":"243c57287062ba5fb0d050828cdbcc0b91b39aa0","path":"README.md","reason":"LANGUAGE_UNSUPPORTED","status":"UNSUPPORTED"},{"functions":[],"lines":0,"module":"widget","object_id":"e69de29bb2d1d6434b8b29ae775ad8c2e48c5391","path":"src/widget/__init__.py","reason":null,"status":"ANALYZED"},{"functions":[{"async":false,"complexity":2,"end_line":6,"line":3,"lines":4,"symbol":"choose"}],"lines":6,"module":"widget.app","object_id":"619dd2aa0d86bec82bdc88f2bbfc166e233feeb7","path":"src/widget/app.py","reason":null,"status":"ANALYZED"},{"functions":[{"async":false,"complexity":2,"end_line":7,"line":3,"lines":5,"symbol":"fallback"}],"lines":7,"module":"widget.parser","object_id":"e18e086cd787854f50a4270afa266031f783529f","path":"src/widget/parser.py","reason":null,"status":"ANALYZED"}],"findings":[{"end_line":1,"evidence":{"paths":["src/widget/app.py","src/widget/parser.py"]},"id":"sha256:f360634dd4075ebb04d4c979fe5a321757b8489275c06350a86e6347d99f3fac","level":"warning","line":1,"message":"Static import cycle","path":"src/widget/app.py","related_paths":["src/widget/app.py","src/widget/parser.py"],"rule":"DS-PY-IMPORT-CYCLE","symbol":"cycle:d205d439f837a19cc3f0467afed21a795b586999d0c79f8d3c9fba70482306cc"},{"end_line":6,"evidence":{"complexity":2,"limit":1},"id":"sha256:e61175cd8aee0c21f82268ef4094ea2febe3cf8cbced21d8ea71213776003b36","level":"warning","line":3,"message":"Function decision count exceeds policy","path":"src/widget/app.py","related_paths":[],"rule":"DS-PY-COMPLEXITY","symbol":"choose"},{"end_line":7,"evidence":{"complexity":2,"limit":1},"id":"sha256:b45437a83e217256f4e035e6996d84bee727811654eb04aa532103c937063f5c","level":"warning","line":3,"message":"Function decision count exceeds policy","path":"src/widget/parser.py","related_paths":[],"rule":"DS-PY-COMPLEXITY","symbol":"fallback"},{"end_line":7,"evidence":{"catches":"BaseException"},"id":"sha256:b5b403f6505e4667b581bc3af4381608765b90c45c553da2e63cb5e7a13360b1","level":"warning","line":6,"message":"Bare except also catches process control exceptions","path":"src/widget/parser.py","related_paths":[],"rule":"DS-PY-BARE-EXCEPT","symbol":"fallback.#1"}],"history":{"ancestry_status":"COMPLETE","count_algorithm":"MYERS_LF_BYTE_LINES_V1","end":"2026-10-08T12:00:00Z","reasons":[],"start":"2026-09-10T12:00:00Z","status":"COMPLETE"},"hotspots":[{"added":7,"binary_changes":0,"count_status":"VERIFIED","coverage":"COMPLETE","deleted":0,"path":"src/widget/parser.py","revisions":1,"unproven_changes":0},{"added":6,"binary_changes":0,"count_status":"VERIFIED","coverage":"COMPLETE","deleted":0,"path":"src/widget/app.py","revisions":1,"unproven_changes":0},{"added":1,"binary_changes":0,"count_status":"VERIFIED","coverage":"COMPLETE","deleted":0,"path":"README.md","revisions":1,"unproven_changes":0},{"added":0,"binary_changes":0,"count_status":"VERIFIED","coverage":"COMPLETE","deleted":0,"path":"src/widget/__init__.py","revisions":1,"unproven_changes":0}],"import_cycles":[["src/widget/app.py","src/widget/parser.py"]],"imports":[{"candidates":["src/widget/parser.py"],"from":"src/widget/app.py","line":1,"module":"widget.parser","status":"RESOLVED","to":"src/widget/parser.py"},{"candidates":["src/widget/app.py"],"from":"src/widget/parser.py","line":1,"module":"widget.app","status":"RESOLVED","to":"src/widget/app.py"}],"observed_at":"2026-10-08T12:00:00Z","subject":{"kind":"LOCAL_GIT","object_format":"sha1","repository_id":"sha256:82213dcc32cfbeb5300f0011e499ccfddfaa6bb8dd6006afd8a28bd700cf6ece","revision":"505ec5559236affd51ee6621b0eb54780bcd0e11","roots":["505ec5559236affd51ee6621b0eb54780bcd0e11"],"roots_complete":true,"tree":"a11d47f209c720b4036bab407ed91550e55529d0"}} \ No newline at end of file diff --git a/examples/code-v2/bundle/findings.sarif b/examples/code-v2/bundle/findings.sarif new file mode 100644 index 0000000..ed8a902 --- /dev/null +++ b/examples/code-v2/bundle/findings.sarif @@ -0,0 +1 @@ +{"$schema":"https://json.schemastore.org/sarif-2.1.0.json","runs":[{"properties":{"authority":"STATIC_SOURCE_EVIDENCE; no execution, independent acceptance, health score or merge authority","historyCoverage":"COMPLETE","sourceCoverage":"COMPLETE"},"results":[{"level":"warning","locations":[{"physicalLocation":{"artifactLocation":{"uri":"src/widget/app.py"},"region":{"endLine":1,"startLine":1}}}],"message":{"text":"Static import cycle"},"partialFingerprints":{"devostasisFindingId":"sha256:f360634dd4075ebb04d4c979fe5a321757b8489275c06350a86e6347d99f3fac"},"properties":{"evidence":{"paths":["src/widget/app.py","src/widget/parser.py"]},"relatedPaths":["src/widget/app.py","src/widget/parser.py"],"symbol":"cycle:d205d439f837a19cc3f0467afed21a795b586999d0c79f8d3c9fba70482306cc"},"ruleId":"DS-PY-IMPORT-CYCLE"},{"level":"warning","locations":[{"physicalLocation":{"artifactLocation":{"uri":"src/widget/app.py"},"region":{"endLine":6,"startLine":3}}}],"message":{"text":"Function decision count exceeds policy"},"partialFingerprints":{"devostasisFindingId":"sha256:e61175cd8aee0c21f82268ef4094ea2febe3cf8cbced21d8ea71213776003b36"},"properties":{"evidence":{"complexity":2,"limit":1},"relatedPaths":[],"symbol":"choose"},"ruleId":"DS-PY-COMPLEXITY"},{"level":"warning","locations":[{"physicalLocation":{"artifactLocation":{"uri":"src/widget/parser.py"},"region":{"endLine":7,"startLine":3}}}],"message":{"text":"Function decision count exceeds policy"},"partialFingerprints":{"devostasisFindingId":"sha256:b45437a83e217256f4e035e6996d84bee727811654eb04aa532103c937063f5c"},"properties":{"evidence":{"complexity":2,"limit":1},"relatedPaths":[],"symbol":"fallback"},"ruleId":"DS-PY-COMPLEXITY"},{"level":"warning","locations":[{"physicalLocation":{"artifactLocation":{"uri":"src/widget/parser.py"},"region":{"endLine":7,"startLine":6}}}],"message":{"text":"Bare except also catches process control exceptions"},"partialFingerprints":{"devostasisFindingId":"sha256:b5b403f6505e4667b581bc3af4381608765b90c45c553da2e63cb5e7a13360b1"},"properties":{"evidence":{"catches":"BaseException"},"relatedPaths":[],"symbol":"fallback.#1"},"ruleId":"DS-PY-BARE-EXCEPT"}],"tool":{"driver":{"name":"Devostasis Code Analysis","rules":[{"id":"DS-PY-BARE-EXCEPT","shortDescription":{"text":"Bare except also catches process control exceptions"}},{"id":"DS-PY-COMPLEXITY","shortDescription":{"text":"Function decision count exceeds policy"}},{"id":"DS-PY-FILE-SIZE","shortDescription":{"text":"File line count exceeds policy"}},{"id":"DS-PY-FUNCTION-SIZE","shortDescription":{"text":"Function line span exceeds policy"}},{"id":"DS-PY-IMPORT-CYCLE","shortDescription":{"text":"Static import cycle"}},{"id":"DS-PY-SYNTAX","shortDescription":{"text":"Python syntax cannot be parsed"}}],"version":"devostasis.code-engine.v2"}},"versionControlProvenance":[{"revisionId":"505ec5559236affd51ee6621b0eb54780bcd0e11"}]}],"version":"2.1.0"} \ No newline at end of file diff --git a/examples/code-v2/bundle/inputs.json b/examples/code-v2/bundle/inputs.json new file mode 100644 index 0000000..d11e86e --- /dev/null +++ b/examples/code-v2/bundle/inputs.json @@ -0,0 +1 @@ +{"entries":[{"kind":"blob","mode":"100644","object_id":"243c57287062ba5fb0d050828cdbcc0b91b39aa0","path":"README.md","path_base64":"UkVBRE1FLm1k","reason":"LANGUAGE_UNSUPPORTED","size":26,"status":"UNSUPPORTED"},{"kind":"blob","mode":"100644","object_id":"e69de29bb2d1d6434b8b29ae775ad8c2e48c5391","path":"src/widget/__init__.py","path_base64":"c3JjL3dpZGdldC9fX2luaXRfXy5weQ==","reason":null,"size":0,"status":"ADMITTED"},{"kind":"blob","mode":"100644","object_id":"619dd2aa0d86bec82bdc88f2bbfc166e233feeb7","path":"src/widget/app.py","path_base64":"c3JjL3dpZGdldC9hcHAucHk=","reason":null,"size":85,"status":"ADMITTED"},{"kind":"blob","mode":"100644","object_id":"e18e086cd787854f50a4270afa266031f783529f","path":"src/widget/parser.py","path_base64":"c3JjL3dpZGdldC9wYXJzZXIucHk=","reason":null,"size":90,"status":"ADMITTED"}],"history":{"ancestry_status":"COMPLETE","commits":[{"changes":[{"added":1,"count_status":"VERIFIED","deleted":0,"path":"README.md","reason":null},{"added":0,"count_status":"VERIFIED","deleted":0,"path":"src/widget/__init__.py","reason":null},{"added":6,"count_status":"VERIFIED","deleted":0,"path":"src/widget/app.py","reason":null},{"added":7,"count_status":"VERIFIED","deleted":0,"path":"src/widget/parser.py","reason":null}],"changes_complete":true,"committed_at":"2026-10-08T12:00:00Z","revision":"505ec5559236affd51ee6621b0eb54780bcd0e11"}],"count_algorithm":"MYERS_LF_BYTE_LINES_V1","end":"2026-10-08T12:00:00Z","reasons":[],"start":"2026-09-10T12:00:00Z","status":"COMPLETE"},"history_proof":{"blobs":{"243c57287062ba5fb0d050828cdbcc0b91b39aa0":"U3ludGhldGljIHNvdXJjZSBmaXh0dXJlLgo="},"commits":{},"contract":"devostasis.git-history-proof.v1","frontier":[],"omitted":{},"trees":{}},"observed_at":"2026-10-08T12:00:00Z","proof":{"commit":"dHJlZSBhMTFkNDdmMjA5YzcyMGI0MDM2YmFiNDA3ZWQ5MTU1MGU1NTUyOWQwCmF1dGhvciBTeW50aGV0aWMgRml4dHVyZSA8Zml4dHVyZUBleGFtcGxlLmludmFsaWQ+IDE3OTE0NjA4MDAgKzAwMDAKY29tbWl0dGVyIFN5bnRoZXRpYyBGaXh0dXJlIDxmaXh0dXJlQGV4YW1wbGUuaW52YWxpZD4gMTc5MTQ2MDgwMCArMDAwMAoKU3ludGhldGljIG9mZmxpbmUgYW5hbHlzaXMgZml4dHVyZQo=","trees":{"1c953551c814aa05de7174253135818b33d38f70":"MTAwNjQ0IF9faW5pdF9fLnB5AOad4puy0dZDS4sprnda2MLkjFORMTAwNjQ0IGFwcC5weQBhndKqDYa+yCvciPK7/BZuIz/utzEwMDY0NCBwYXJzZXIucHkA4Y4IbNeHhU9QpCcK+iZgMfeDUp8=","a11d47f209c720b4036bab407ed91550e55529d0":"MTAwNjQ0IFJFQURNRS5tZAAkPFcocGK6X7DQUIKM28wLkbOaoDQwMDAwIHNyYwDT7WOOuuPU6/KOSC3gDnA2Ttck2w==","d3ed638ebae3d4ebf28e482de00e70364ed724db":"NDAwMDAgd2lkZ2V0AByVNVHIFKoF3nF0JTE1gYsz049w"}},"sources":{"src/widget/__init__.py":"","src/widget/app.py":"ZnJvbSAuIGltcG9ydCBwYXJzZXIKCmRlZiBjaG9vc2UodmFsdWUpOgogICAgaWYgdmFsdWU6CiAgICAgICAgcmV0dXJuIDEKICAgIHJldHVybiAwCg==","src/widget/parser.py":"ZnJvbSAuIGltcG9ydCBhcHAKCmRlZiBmYWxsYmFjaygpOgogICAgdHJ5OgogICAgICAgIHJldHVybiAxCiAgICBleGNlcHQ6CiAgICAgICAgcmV0dXJuIDAK"},"subject":{"kind":"LOCAL_GIT","object_format":"sha1","repository_id":"sha256:82213dcc32cfbeb5300f0011e499ccfddfaa6bb8dd6006afd8a28bd700cf6ece","revision":"505ec5559236affd51ee6621b0eb54780bcd0e11","roots":["505ec5559236affd51ee6621b0eb54780bcd0e11"],"roots_complete":true,"tree":"a11d47f209c720b4036bab407ed91550e55529d0"}} \ No newline at end of file diff --git a/examples/code-v2/bundle/manifest.json b/examples/code-v2/bundle/manifest.json new file mode 100644 index 0000000..19db3b0 --- /dev/null +++ b/examples/code-v2/bundle/manifest.json @@ -0,0 +1 @@ +{"bundle_id":"sha256:96a085f7a7d5d6091525e92271f41d7acfa338c1652caf33ae66dcbff6fbc609","contract":"devostasis.code-analysis.v2","engine":"devostasis.code-engine.v2","members":{"analysis.json":"sha256:b05dd89795478f8c967d5f9f2d6a93aa013b5c9075ee265ccb58873883ee3cd9","findings.sarif":"sha256:81af3c53aea50eb741c59f91525b8e453eb88f8016c0c3f1872301f0dd904d9a","inputs.json":"sha256:32b977e80d1b49c861b7aebeb23bddde1f4ba777b19fcb0649b4cf205e247e20","policy.json":"sha256:946751e06423bcc85d80a9c05d884e0d70d6fa504bf67f39444bcb4b3962e0cb","report.md":"sha256:99a2cc68889be28d42bfdfa0eec173c6d69d7ffdf9c96c888cd665e0daa89da7"},"subject":{"kind":"LOCAL_GIT","object_format":"sha1","repository_id":"sha256:82213dcc32cfbeb5300f0011e499ccfddfaa6bb8dd6006afd8a28bd700cf6ece","revision":"505ec5559236affd51ee6621b0eb54780bcd0e11","roots":["505ec5559236affd51ee6621b0eb54780bcd0e11"],"roots_complete":true,"tree":"a11d47f209c720b4036bab407ed91550e55529d0"}} \ No newline at end of file diff --git a/examples/code-v2/bundle/policy.json b/examples/code-v2/bundle/policy.json new file mode 100644 index 0000000..140e3c2 --- /dev/null +++ b/examples/code-v2/bundle/policy.json @@ -0,0 +1 @@ +{"budget":{"max_file_bytes":1048576,"max_files":2000,"max_total_bytes":16777216,"seconds":120},"contract":"devostasis.code-policy.v2","exclude":[".venv","build","dist","vendor","node_modules"],"grammar":"PYTHON_3_12","history":{"days":28,"enabled":true,"max_ancestors":2000,"max_blob_bytes":4194304,"max_commits":500,"max_diff_lines":100000,"max_diff_work":2000000,"max_objects":10000,"max_proof_bytes":33554432},"include":[],"limits":{"complexity":1,"file_lines":600,"function_lines":80},"source_roots":[".","src"],"version":"default-2"} \ No newline at end of file diff --git a/examples/code-v2/bundle/report.md b/examples/code-v2/bundle/report.md new file mode 100644 index 0000000..bd1218a --- /dev/null +++ b/examples/code-v2/bundle/report.md @@ -0,0 +1,52 @@ +# Offline repository analysis + +Revision: `505ec5559236affd51ee6621b0eb54780bcd0e11` + +Python grammar: 3.12. Static evidence only; repository code was not executed. + +Python source coverage: COMPLETE; history: COMPLETE. + +Unsupported and unavailable inputs do not establish an absence of findings. + +| Source status | Files | +| --- | ---: | +| ANALYZED | 3 | +| PARSE_ERROR | 0 | +| UNAVAILABLE | 0 | +| UNSUPPORTED | 1 | +| OUT_OF_SCOPE | 0 | + +## Findings + +| Path | Line | Rule | Symbol | Evidence | +| --- | ---: | --- | --- | --- | +| src/widget/app\.py | 1 | DS-PY-IMPORT-CYCLE | cycle:d205d439f837a19cc3f0467afed21a795b586999d0c79f8d3c9fba70482306cc | \{"paths":\["src/widget/app\.py","src/widget/parser\.py"\]\} | +| src/widget/app\.py | 3 | DS-PY-COMPLEXITY | choose | \{"complexity":2,"limit":1\} | +| src/widget/parser\.py | 3 | DS-PY-COMPLEXITY | fallback | \{"complexity":2,"limit":1\} | +| src/widget/parser\.py | 6 | DS-PY-BARE-EXCEPT | fallback\.<bare-except>\#1 | \{"catches":"BaseException"\} | + +Showing 4 of 4 findings; analysis.json and findings.sarif retain every finding. + +## Source gaps + +| Path | Status | Reason | +| --- | --- | --- | + +Showing 0 of 0 source gaps. + +## Recent change evidence + +Non-merge commits in [2026-09-10T12:00:00Z, 2026-10-08T12:00:00Z]. + +Counts under PARTIAL history are observed counts, not complete totals or productivity. + +| Path | Revisions | Added | Deleted | Coverage | +| --- | ---: | ---: | ---: | --- | +| src/widget/parser\.py | 1 | 7 | 0 | COMPLETE | +| src/widget/app\.py | 1 | 6 | 0 | COMPLETE | +| README\.md | 1 | 1 | 0 | COMPLETE | +| src/widget/\_\_init\_\_\.py | 1 | 0 | 0 | COMPLETE | + +Showing 4 of 4 paths. + +STATIC_SOURCE_EVIDENCE; no execution, independent acceptance, health score or merge authority diff --git a/examples/code-v2/evidence.json b/examples/code-v2/evidence.json new file mode 100644 index 0000000..37ff3cf --- /dev/null +++ b/examples/code-v2/evidence.json @@ -0,0 +1 @@ +{"contract":"devostasis.work.v2","kind":"evidence","sources":[{"observed_at":"2026-10-08T12:00:00Z","producer":"devostasis.code-engine.v2","profile":"sarif.v1","reason":null,"report":{"base64":"eyIkc2NoZW1hIjoiaHR0cHM6Ly9qc29uLnNjaGVtYXN0b3JlLm9yZy9zYXJpZi0yLjEuMC5qc29uIiwicnVucyI6W3sicHJvcGVydGllcyI6eyJhdXRob3JpdHkiOiJTVEFUSUNfU09VUkNFX0VWSURFTkNFOyBubyBleGVjdXRpb24sIGluZGVwZW5kZW50IGFjY2VwdGFuY2UsIGhlYWx0aCBzY29yZSBvciBtZXJnZSBhdXRob3JpdHkiLCJoaXN0b3J5Q292ZXJhZ2UiOiJDT01QTEVURSIsInNvdXJjZUNvdmVyYWdlIjoiQ09NUExFVEUifSwicmVzdWx0cyI6W3sibGV2ZWwiOiJ3YXJuaW5nIiwibG9jYXRpb25zIjpbeyJwaHlzaWNhbExvY2F0aW9uIjp7ImFydGlmYWN0TG9jYXRpb24iOnsidXJpIjoic3JjL3dpZGdldC9hcHAucHkifSwicmVnaW9uIjp7ImVuZExpbmUiOjEsInN0YXJ0TGluZSI6MX19fV0sIm1lc3NhZ2UiOnsidGV4dCI6IlN0YXRpYyBpbXBvcnQgY3ljbGUifSwicGFydGlhbEZpbmdlcnByaW50cyI6eyJkZXZvc3Rhc2lzRmluZGluZ0lkIjoic2hhMjU2OmYzNjA2MzRkZDQwNzVlYmIwNGQ0Yzk3OWZlNWEzMjE3NTdiODQ4OTI3NWMwNjM1MGE4NmU2MzQ3ZDk5ZjNmYWMifSwicHJvcGVydGllcyI6eyJldmlkZW5jZSI6eyJwYXRocyI6WyJzcmMvd2lkZ2V0L2FwcC5weSIsInNyYy93aWRnZXQvcGFyc2VyLnB5Il19LCJyZWxhdGVkUGF0aHMiOlsic3JjL3dpZGdldC9hcHAucHkiLCJzcmMvd2lkZ2V0L3BhcnNlci5weSJdLCJzeW1ib2wiOiJjeWNsZTpkMjA1ZDQzOWY4MzdhMTljYzNmMDQ2N2FmZWQyMWE3OTViNTg2OTk5ZDBjNzlmOGQzYzlmYmE3MDQ4MjMwNmNjIn0sInJ1bGVJZCI6IkRTLVBZLUlNUE9SVC1DWUNMRSJ9LHsibGV2ZWwiOiJ3YXJuaW5nIiwibG9jYXRpb25zIjpbeyJwaHlzaWNhbExvY2F0aW9uIjp7ImFydGlmYWN0TG9jYXRpb24iOnsidXJpIjoic3JjL3dpZGdldC9hcHAucHkifSwicmVnaW9uIjp7ImVuZExpbmUiOjYsInN0YXJ0TGluZSI6M319fV0sIm1lc3NhZ2UiOnsidGV4dCI6IkZ1bmN0aW9uIGRlY2lzaW9uIGNvdW50IGV4Y2VlZHMgcG9saWN5In0sInBhcnRpYWxGaW5nZXJwcmludHMiOnsiZGV2b3N0YXNpc0ZpbmRpbmdJZCI6InNoYTI1NjplNjExNzVjZDhhZWUwYzIxZjgyMjY4ZWY0MDk0ZWEyZmViZTNjZjhjYmNlZDIxZDhlYTcxMjEzNzc2MDAzYjM2In0sInByb3BlcnRpZXMiOnsiZXZpZGVuY2UiOnsiY29tcGxleGl0eSI6MiwibGltaXQiOjF9LCJyZWxhdGVkUGF0aHMiOltdLCJzeW1ib2wiOiJjaG9vc2UifSwicnVsZUlkIjoiRFMtUFktQ09NUExFWElUWSJ9LHsibGV2ZWwiOiJ3YXJuaW5nIiwibG9jYXRpb25zIjpbeyJwaHlzaWNhbExvY2F0aW9uIjp7ImFydGlmYWN0TG9jYXRpb24iOnsidXJpIjoic3JjL3dpZGdldC9wYXJzZXIucHkifSwicmVnaW9uIjp7ImVuZExpbmUiOjcsInN0YXJ0TGluZSI6M319fV0sIm1lc3NhZ2UiOnsidGV4dCI6IkZ1bmN0aW9uIGRlY2lzaW9uIGNvdW50IGV4Y2VlZHMgcG9saWN5In0sInBhcnRpYWxGaW5nZXJwcmludHMiOnsiZGV2b3N0YXNpc0ZpbmRpbmdJZCI6InNoYTI1NjpiNDU0MzdhODNlMjE3MjU2ZjRlMDM1ZTY5OTZkODRiZWU3Mjc4MTE2NTRlYjA0YWE1MzIxMDNjOTM3MDYzZjVjIn0sInByb3BlcnRpZXMiOnsiZXZpZGVuY2UiOnsiY29tcGxleGl0eSI6MiwibGltaXQiOjF9LCJyZWxhdGVkUGF0aHMiOltdLCJzeW1ib2wiOiJmYWxsYmFjayJ9LCJydWxlSWQiOiJEUy1QWS1DT01QTEVYSVRZIn0seyJsZXZlbCI6Indhcm5pbmciLCJsb2NhdGlvbnMiOlt7InBoeXNpY2FsTG9jYXRpb24iOnsiYXJ0aWZhY3RMb2NhdGlvbiI6eyJ1cmkiOiJzcmMvd2lkZ2V0L3BhcnNlci5weSJ9LCJyZWdpb24iOnsiZW5kTGluZSI6Nywic3RhcnRMaW5lIjo2fX19XSwibWVzc2FnZSI6eyJ0ZXh0IjoiQmFyZSBleGNlcHQgYWxzbyBjYXRjaGVzIHByb2Nlc3MgY29udHJvbCBleGNlcHRpb25zIn0sInBhcnRpYWxGaW5nZXJwcmludHMiOnsiZGV2b3N0YXNpc0ZpbmRpbmdJZCI6InNoYTI1NjpiNWI0MDNmNjUwNWU0NjY3YjU4MWJjM2FmNDM4MTYwODc2NWI5MGM0NWM1NTNkYTJlNjNjYjVlN2ExMzM2MGIxIn0sInByb3BlcnRpZXMiOnsiZXZpZGVuY2UiOnsiY2F0Y2hlcyI6IkJhc2VFeGNlcHRpb24ifSwicmVsYXRlZFBhdGhzIjpbXSwic3ltYm9sIjoiZmFsbGJhY2suPGJhcmUtZXhjZXB0PiMxIn0sInJ1bGVJZCI6IkRTLVBZLUJBUkUtRVhDRVBUIn1dLCJ0b29sIjp7ImRyaXZlciI6eyJuYW1lIjoiRGV2b3N0YXNpcyBDb2RlIEFuYWx5c2lzIiwicnVsZXMiOlt7ImlkIjoiRFMtUFktQkFSRS1FWENFUFQiLCJzaG9ydERlc2NyaXB0aW9uIjp7InRleHQiOiJCYXJlIGV4Y2VwdCBhbHNvIGNhdGNoZXMgcHJvY2VzcyBjb250cm9sIGV4Y2VwdGlvbnMifX0seyJpZCI6IkRTLVBZLUNPTVBMRVhJVFkiLCJzaG9ydERlc2NyaXB0aW9uIjp7InRleHQiOiJGdW5jdGlvbiBkZWNpc2lvbiBjb3VudCBleGNlZWRzIHBvbGljeSJ9fSx7ImlkIjoiRFMtUFktRklMRS1TSVpFIiwic2hvcnREZXNjcmlwdGlvbiI6eyJ0ZXh0IjoiRmlsZSBsaW5lIGNvdW50IGV4Y2VlZHMgcG9saWN5In19LHsiaWQiOiJEUy1QWS1GVU5DVElPTi1TSVpFIiwic2hvcnREZXNjcmlwdGlvbiI6eyJ0ZXh0IjoiRnVuY3Rpb24gbGluZSBzcGFuIGV4Y2VlZHMgcG9saWN5In19LHsiaWQiOiJEUy1QWS1JTVBPUlQtQ1lDTEUiLCJzaG9ydERlc2NyaXB0aW9uIjp7InRleHQiOiJTdGF0aWMgaW1wb3J0IGN5Y2xlIn19LHsiaWQiOiJEUy1QWS1TWU5UQVgiLCJzaG9ydERlc2NyaXB0aW9uIjp7InRleHQiOiJQeXRob24gc3ludGF4IGNhbm5vdCBiZSBwYXJzZWQifX1dLCJ2ZXJzaW9uIjoiZGV2b3N0YXNpcy5jb2RlLWVuZ2luZS52MiJ9fSwidmVyc2lvbkNvbnRyb2xQcm92ZW5hbmNlIjpbeyJyZXZpc2lvbklkIjoiNTA1ZWM1NTU5MjM2YWZmZDUxZWU2NjIxYjBlYjU0NzgwYmNkMGUxMSJ9XX1dLCJ2ZXJzaW9uIjoiMi4xLjAifQ==","digest":"sha256:81af3c53aea50eb741c59f91525b8e453eb88f8016c0c3f1872301f0dd904d9a"},"status":"COMPLETE","subject":{"context":"CANONICAL","endpoint":"https://api.github.com","locator":"acme/widget","project_id":"123456","provider":"github","revision":"505ec5559236affd51ee6621b0eb54780bcd0e11","source_binding":{"contract":"devostasis.work-source.v1","kind":"DEFAULT_BRANCH","project":{"endpoint":"https://api.github.com","project_id":"123456","provider":"github"},"ref":"main","revision":"505ec5559236affd51ee6621b0eb54780bcd0e11","source_locator":"acme/widget","source_project":{"endpoint":"https://api.github.com","project_id":"123456","provider":"github"}}},"version":"1"}]} \ No newline at end of file diff --git a/examples/code-v2/inventory.json b/examples/code-v2/inventory.json new file mode 100644 index 0000000..fb5e3ac --- /dev/null +++ b/examples/code-v2/inventory.json @@ -0,0 +1 @@ +{"changes":{"items":[],"reasons":[],"status":"COMPLETE"},"contract":"devostasis.work.v2","issues":{"items":[],"reasons":[],"status":"COMPLETE"},"kind":"inventory","observed_at":"2026-10-08T12:00:00Z","receipts":[],"selection":{"changes":"OPEN_AND_REGISTERED","issues":[]},"subject":{"context":"CANONICAL","endpoint":"https://api.github.com","locator":"acme/widget","project_id":"123456","provider":"github","revision":"505ec5559236affd51ee6621b0eb54780bcd0e11","source_binding":{"contract":"devostasis.work-source.v1","kind":"DEFAULT_BRANCH","project":{"endpoint":"https://api.github.com","project_id":"123456","provider":"github"},"ref":"main","revision":"505ec5559236affd51ee6621b0eb54780bcd0e11","source_locator":"acme/widget","source_project":{"endpoint":"https://api.github.com","project_id":"123456","provider":"github"}}}} \ No newline at end of file diff --git a/examples/code-v2/packet.json b/examples/code-v2/packet.json new file mode 100644 index 0000000..9b1455f --- /dev/null +++ b/examples/code-v2/packet.json @@ -0,0 +1 @@ +{"analysis_bundle_id":"sha256:96a085f7a7d5d6091525e92271f41d7acfa338c1652caf33ae66dcbff6fbc609","authority":"PINNED_LOCAL_SOURCE_ONLY; no live freshness, independent acceptance or execution authority","budget":{"max_bytes":1048576,"max_files":20},"contract":"devostasis.code-packet.v2","files":[{"base64":"ZnJvbSAuIGltcG9ydCBwYXJzZXIKCmRlZiBjaG9vc2UodmFsdWUpOgogICAgaWYgdmFsdWU6CiAgICAgICAgcmV0dXJuIDEKICAgIHJldHVybiAwCg==","object_id":"619dd2aa0d86bec82bdc88f2bbfc166e233feeb7","path":"src/widget/app.py","size":85}],"packet_id":"sha256:0421fe90d4c30abbe1ca9c59e64a4fee7f06079fff195e1f96140a1a29e225c3","subject":{"kind":"LOCAL_GIT","object_format":"sha1","repository_id":"sha256:82213dcc32cfbeb5300f0011e499ccfddfaa6bb8dd6006afd8a28bd700cf6ece","revision":"505ec5559236affd51ee6621b0eb54780bcd0e11","roots":["505ec5559236affd51ee6621b0eb54780bcd0e11"],"roots_complete":true,"tree":"a11d47f209c720b4036bab407ed91550e55529d0"},"total_bytes":85} \ No newline at end of file diff --git a/examples/code-v2/policy.json b/examples/code-v2/policy.json new file mode 100644 index 0000000..140e3c2 --- /dev/null +++ b/examples/code-v2/policy.json @@ -0,0 +1 @@ +{"budget":{"max_file_bytes":1048576,"max_files":2000,"max_total_bytes":16777216,"seconds":120},"contract":"devostasis.code-policy.v2","exclude":[".venv","build","dist","vendor","node_modules"],"grammar":"PYTHON_3_12","history":{"days":28,"enabled":true,"max_ancestors":2000,"max_blob_bytes":4194304,"max_commits":500,"max_diff_lines":100000,"max_diff_work":2000000,"max_objects":10000,"max_proof_bytes":33554432},"include":[],"limits":{"complexity":1,"file_lines":600,"function_lines":80},"source_roots":[".","src"],"version":"default-2"} \ No newline at end of file diff --git a/examples/code-v2/work-bundle/evidence.json b/examples/code-v2/work-bundle/evidence.json new file mode 100644 index 0000000..37ff3cf --- /dev/null +++ b/examples/code-v2/work-bundle/evidence.json @@ -0,0 +1 @@ +{"contract":"devostasis.work.v2","kind":"evidence","sources":[{"observed_at":"2026-10-08T12:00:00Z","producer":"devostasis.code-engine.v2","profile":"sarif.v1","reason":null,"report":{"base64":"eyIkc2NoZW1hIjoiaHR0cHM6Ly9qc29uLnNjaGVtYXN0b3JlLm9yZy9zYXJpZi0yLjEuMC5qc29uIiwicnVucyI6W3sicHJvcGVydGllcyI6eyJhdXRob3JpdHkiOiJTVEFUSUNfU09VUkNFX0VWSURFTkNFOyBubyBleGVjdXRpb24sIGluZGVwZW5kZW50IGFjY2VwdGFuY2UsIGhlYWx0aCBzY29yZSBvciBtZXJnZSBhdXRob3JpdHkiLCJoaXN0b3J5Q292ZXJhZ2UiOiJDT01QTEVURSIsInNvdXJjZUNvdmVyYWdlIjoiQ09NUExFVEUifSwicmVzdWx0cyI6W3sibGV2ZWwiOiJ3YXJuaW5nIiwibG9jYXRpb25zIjpbeyJwaHlzaWNhbExvY2F0aW9uIjp7ImFydGlmYWN0TG9jYXRpb24iOnsidXJpIjoic3JjL3dpZGdldC9hcHAucHkifSwicmVnaW9uIjp7ImVuZExpbmUiOjEsInN0YXJ0TGluZSI6MX19fV0sIm1lc3NhZ2UiOnsidGV4dCI6IlN0YXRpYyBpbXBvcnQgY3ljbGUifSwicGFydGlhbEZpbmdlcnByaW50cyI6eyJkZXZvc3Rhc2lzRmluZGluZ0lkIjoic2hhMjU2OmYzNjA2MzRkZDQwNzVlYmIwNGQ0Yzk3OWZlNWEzMjE3NTdiODQ4OTI3NWMwNjM1MGE4NmU2MzQ3ZDk5ZjNmYWMifSwicHJvcGVydGllcyI6eyJldmlkZW5jZSI6eyJwYXRocyI6WyJzcmMvd2lkZ2V0L2FwcC5weSIsInNyYy93aWRnZXQvcGFyc2VyLnB5Il19LCJyZWxhdGVkUGF0aHMiOlsic3JjL3dpZGdldC9hcHAucHkiLCJzcmMvd2lkZ2V0L3BhcnNlci5weSJdLCJzeW1ib2wiOiJjeWNsZTpkMjA1ZDQzOWY4MzdhMTljYzNmMDQ2N2FmZWQyMWE3OTViNTg2OTk5ZDBjNzlmOGQzYzlmYmE3MDQ4MjMwNmNjIn0sInJ1bGVJZCI6IkRTLVBZLUlNUE9SVC1DWUNMRSJ9LHsibGV2ZWwiOiJ3YXJuaW5nIiwibG9jYXRpb25zIjpbeyJwaHlzaWNhbExvY2F0aW9uIjp7ImFydGlmYWN0TG9jYXRpb24iOnsidXJpIjoic3JjL3dpZGdldC9hcHAucHkifSwicmVnaW9uIjp7ImVuZExpbmUiOjYsInN0YXJ0TGluZSI6M319fV0sIm1lc3NhZ2UiOnsidGV4dCI6IkZ1bmN0aW9uIGRlY2lzaW9uIGNvdW50IGV4Y2VlZHMgcG9saWN5In0sInBhcnRpYWxGaW5nZXJwcmludHMiOnsiZGV2b3N0YXNpc0ZpbmRpbmdJZCI6InNoYTI1NjplNjExNzVjZDhhZWUwYzIxZjgyMjY4ZWY0MDk0ZWEyZmViZTNjZjhjYmNlZDIxZDhlYTcxMjEzNzc2MDAzYjM2In0sInByb3BlcnRpZXMiOnsiZXZpZGVuY2UiOnsiY29tcGxleGl0eSI6MiwibGltaXQiOjF9LCJyZWxhdGVkUGF0aHMiOltdLCJzeW1ib2wiOiJjaG9vc2UifSwicnVsZUlkIjoiRFMtUFktQ09NUExFWElUWSJ9LHsibGV2ZWwiOiJ3YXJuaW5nIiwibG9jYXRpb25zIjpbeyJwaHlzaWNhbExvY2F0aW9uIjp7ImFydGlmYWN0TG9jYXRpb24iOnsidXJpIjoic3JjL3dpZGdldC9wYXJzZXIucHkifSwicmVnaW9uIjp7ImVuZExpbmUiOjcsInN0YXJ0TGluZSI6M319fV0sIm1lc3NhZ2UiOnsidGV4dCI6IkZ1bmN0aW9uIGRlY2lzaW9uIGNvdW50IGV4Y2VlZHMgcG9saWN5In0sInBhcnRpYWxGaW5nZXJwcmludHMiOnsiZGV2b3N0YXNpc0ZpbmRpbmdJZCI6InNoYTI1NjpiNDU0MzdhODNlMjE3MjU2ZjRlMDM1ZTY5OTZkODRiZWU3Mjc4MTE2NTRlYjA0YWE1MzIxMDNjOTM3MDYzZjVjIn0sInByb3BlcnRpZXMiOnsiZXZpZGVuY2UiOnsiY29tcGxleGl0eSI6MiwibGltaXQiOjF9LCJyZWxhdGVkUGF0aHMiOltdLCJzeW1ib2wiOiJmYWxsYmFjayJ9LCJydWxlSWQiOiJEUy1QWS1DT01QTEVYSVRZIn0seyJsZXZlbCI6Indhcm5pbmciLCJsb2NhdGlvbnMiOlt7InBoeXNpY2FsTG9jYXRpb24iOnsiYXJ0aWZhY3RMb2NhdGlvbiI6eyJ1cmkiOiJzcmMvd2lkZ2V0L3BhcnNlci5weSJ9LCJyZWdpb24iOnsiZW5kTGluZSI6Nywic3RhcnRMaW5lIjo2fX19XSwibWVzc2FnZSI6eyJ0ZXh0IjoiQmFyZSBleGNlcHQgYWxzbyBjYXRjaGVzIHByb2Nlc3MgY29udHJvbCBleGNlcHRpb25zIn0sInBhcnRpYWxGaW5nZXJwcmludHMiOnsiZGV2b3N0YXNpc0ZpbmRpbmdJZCI6InNoYTI1NjpiNWI0MDNmNjUwNWU0NjY3YjU4MWJjM2FmNDM4MTYwODc2NWI5MGM0NWM1NTNkYTJlNjNjYjVlN2ExMzM2MGIxIn0sInByb3BlcnRpZXMiOnsiZXZpZGVuY2UiOnsiY2F0Y2hlcyI6IkJhc2VFeGNlcHRpb24ifSwicmVsYXRlZFBhdGhzIjpbXSwic3ltYm9sIjoiZmFsbGJhY2suPGJhcmUtZXhjZXB0PiMxIn0sInJ1bGVJZCI6IkRTLVBZLUJBUkUtRVhDRVBUIn1dLCJ0b29sIjp7ImRyaXZlciI6eyJuYW1lIjoiRGV2b3N0YXNpcyBDb2RlIEFuYWx5c2lzIiwicnVsZXMiOlt7ImlkIjoiRFMtUFktQkFSRS1FWENFUFQiLCJzaG9ydERlc2NyaXB0aW9uIjp7InRleHQiOiJCYXJlIGV4Y2VwdCBhbHNvIGNhdGNoZXMgcHJvY2VzcyBjb250cm9sIGV4Y2VwdGlvbnMifX0seyJpZCI6IkRTLVBZLUNPTVBMRVhJVFkiLCJzaG9ydERlc2NyaXB0aW9uIjp7InRleHQiOiJGdW5jdGlvbiBkZWNpc2lvbiBjb3VudCBleGNlZWRzIHBvbGljeSJ9fSx7ImlkIjoiRFMtUFktRklMRS1TSVpFIiwic2hvcnREZXNjcmlwdGlvbiI6eyJ0ZXh0IjoiRmlsZSBsaW5lIGNvdW50IGV4Y2VlZHMgcG9saWN5In19LHsiaWQiOiJEUy1QWS1GVU5DVElPTi1TSVpFIiwic2hvcnREZXNjcmlwdGlvbiI6eyJ0ZXh0IjoiRnVuY3Rpb24gbGluZSBzcGFuIGV4Y2VlZHMgcG9saWN5In19LHsiaWQiOiJEUy1QWS1JTVBPUlQtQ1lDTEUiLCJzaG9ydERlc2NyaXB0aW9uIjp7InRleHQiOiJTdGF0aWMgaW1wb3J0IGN5Y2xlIn19LHsiaWQiOiJEUy1QWS1TWU5UQVgiLCJzaG9ydERlc2NyaXB0aW9uIjp7InRleHQiOiJQeXRob24gc3ludGF4IGNhbm5vdCBiZSBwYXJzZWQifX1dLCJ2ZXJzaW9uIjoiZGV2b3N0YXNpcy5jb2RlLWVuZ2luZS52MiJ9fSwidmVyc2lvbkNvbnRyb2xQcm92ZW5hbmNlIjpbeyJyZXZpc2lvbklkIjoiNTA1ZWM1NTU5MjM2YWZmZDUxZWU2NjIxYjBlYjU0NzgwYmNkMGUxMSJ9XX1dLCJ2ZXJzaW9uIjoiMi4xLjAifQ==","digest":"sha256:81af3c53aea50eb741c59f91525b8e453eb88f8016c0c3f1872301f0dd904d9a"},"status":"COMPLETE","subject":{"context":"CANONICAL","endpoint":"https://api.github.com","locator":"acme/widget","project_id":"123456","provider":"github","revision":"505ec5559236affd51ee6621b0eb54780bcd0e11","source_binding":{"contract":"devostasis.work-source.v1","kind":"DEFAULT_BRANCH","project":{"endpoint":"https://api.github.com","project_id":"123456","provider":"github"},"ref":"main","revision":"505ec5559236affd51ee6621b0eb54780bcd0e11","source_locator":"acme/widget","source_project":{"endpoint":"https://api.github.com","project_id":"123456","provider":"github"}}},"version":"1"}]} \ No newline at end of file diff --git a/examples/code-v2/work-bundle/inventory.json b/examples/code-v2/work-bundle/inventory.json new file mode 100644 index 0000000..fb5e3ac --- /dev/null +++ b/examples/code-v2/work-bundle/inventory.json @@ -0,0 +1 @@ +{"changes":{"items":[],"reasons":[],"status":"COMPLETE"},"contract":"devostasis.work.v2","issues":{"items":[],"reasons":[],"status":"COMPLETE"},"kind":"inventory","observed_at":"2026-10-08T12:00:00Z","receipts":[],"selection":{"changes":"OPEN_AND_REGISTERED","issues":[]},"subject":{"context":"CANONICAL","endpoint":"https://api.github.com","locator":"acme/widget","project_id":"123456","provider":"github","revision":"505ec5559236affd51ee6621b0eb54780bcd0e11","source_binding":{"contract":"devostasis.work-source.v1","kind":"DEFAULT_BRANCH","project":{"endpoint":"https://api.github.com","project_id":"123456","provider":"github"},"ref":"main","revision":"505ec5559236affd51ee6621b0eb54780bcd0e11","source_locator":"acme/widget","source_project":{"endpoint":"https://api.github.com","project_id":"123456","provider":"github"}}}} \ No newline at end of file diff --git a/examples/code-v2/work-bundle/manifest.json b/examples/code-v2/work-bundle/manifest.json new file mode 100644 index 0000000..5576f55 --- /dev/null +++ b/examples/code-v2/work-bundle/manifest.json @@ -0,0 +1 @@ +{"bundle_id":"sha256:ebd6b9ecd6dbfc8bfa892eeaac2946c197b59955e1314dcffb600d0b20d30c9a","contract":"devostasis.work.v2","engine":"devostasis.work-engine.v2","kind":"bundle","members":{"evidence.json":"sha256:6dd5dafdb0c30195aa78bb67f84b42439e41a4b6f9afd1853f54c76c2531b7b2","inventory.json":"sha256:5090e7b9655fd3d9939f8ba7c919a2659e045d75ce63ea40856ec073dfcce347","policy.json":"sha256:05cc5cc2caca768462c0e31b9da37aad7932663e12a10fee8ef127f3b2a1d683","report.md":"sha256:3111bd131fc868a4c5d9468227339d1bd28e2a15578f31f41fe567701f42fb05","scope.json":"sha256:b4c95d1f9bc3e670804f59061eea16be8508693723d9035835ab27046a57e184"},"vitals_anchor":null} \ No newline at end of file diff --git a/examples/code-v2/work-bundle/policy.json b/examples/code-v2/work-bundle/policy.json new file mode 100644 index 0000000..8b56348 --- /dev/null +++ b/examples/code-v2/work-bundle/policy.json @@ -0,0 +1 @@ +{"actor":"worker","allow_merge_train":false,"contract":"devostasis.work.v2","criteria":[],"dispositions":[],"kind":"policy","minimum_approvals":1,"priorities":{},"queue_order":["review","finish_merge","implement_issue","research","analyze_code"],"read_budget":{"max_bytes":200000,"max_files":20},"requests":[],"required_checks":[],"roles":["all"],"ttl_seconds":3600,"version":"example-1"} \ No newline at end of file diff --git a/examples/code-v2/work-bundle/report.md b/examples/code-v2/work-bundle/report.md new file mode 100644 index 0000000..1804a7d --- /dev/null +++ b/examples/code-v2/work-bundle/report.md @@ -0,0 +1,15 @@ +# Devostasis work scope + +Observed: 2026-10-08T12:00:00Z · expires: 2026-10-08T13:00:00Z + +Read-only proposal. Recheck source state and consumer authorization before execution. + +| Queue | Eligibility | Source | Action | Reasons | +| --- | --- | --- | --- | --- | +| analyze_code | READY | sha256:1b83f3aaa8c87b39bd0abd38babbd75a599fb7398c85f3e28f662ac6d8f7c85d | analyze_code | | +| analyze_code | READY | sha256:021413c4afe15592bb4926c5c54e09e05d7a5f61ac0fc3f4bff2aa084d46ce4f | analyze_code | | +| analyze_code | READY | sha256:cda057b57b75e76615b0b488dc8fba3d1d5b438aab88e0c4007d0e914ed8e890 | analyze_code | | +| analyze_code | READY | sha256:5f5567f8a3ad51e6dc111d6fae9d0351b39fa9d061d00fb694ec7f9b69f81bc4 | analyze_code | | + +Coverage: changes=COMPLETE, issues=COMPLETE +Excluded: 0; evidence gaps: 0. diff --git a/examples/code-v2/work-bundle/scope.json b/examples/code-v2/work-bundle/scope.json new file mode 100644 index 0000000..7cd96ba --- /dev/null +++ b/examples/code-v2/work-bundle/scope.json @@ -0,0 +1 @@ +{"authority":"Read-only proposal. Recheck source state and consumer authorization before execution.","contract":"devostasis.work.v2","coverage":{"changes":"COMPLETE","issues":"COMPLETE"},"excluded":[],"expires_at":"2026-10-08T13:00:00Z","gaps":[],"inputs":{"evidence":"sha256:6dd5dafdb0c30195aa78bb67f84b42439e41a4b6f9afd1853f54c76c2531b7b2","inventory":"sha256:5090e7b9655fd3d9939f8ba7c919a2659e045d75ce63ea40856ec073dfcce347","policy":"sha256:05cc5cc2caca768462c0e31b9da37aad7932663e12a10fee8ef127f3b2a1d683"},"items":[{"acceptance":["Inspect the rule and location; reproduce or disposition the finding, then rerun the producer."],"action":"analyze_code","continuation":false,"criterion":"DS-PY-COMPLEXITY","dependencies":[],"eligibility":"READY","evidence":["sha256:05cc5cc2caca768462c0e31b9da37aad7932663e12a10fee8ef127f3b2a1d683","sha256:5090e7b9655fd3d9939f8ba7c919a2659e045d75ce63ea40856ec073dfcce347","sha256:6dd5dafdb0c30195aa78bb67f84b42439e41a4b6f9afd1853f54c76c2531b7b2","sha256:81af3c53aea50eb741c59f91525b8e453eb88f8016c0c3f1872301f0dd904d9a"],"executor":"worker","fingerprint":"sha256:5a4b2f32f1f8be7cc32c21c55b217732bec9668f51e473426d61ca9acdc89796","id":"sha256:1a80f069dbede63d29605f66c4fb9794baca0b0b419589c66bf24dc00640e633","implementation_owner":null,"merge_gate":null,"priority":{"rank":1000,"rationale":"No consumer business priority declared."},"queue":"analyze_code","read_set":{"budget":{"max_bytes":200000,"max_files":20},"paths":["src/widget/app.py"],"symbols":[],"total_paths":1},"reasons":[],"revision":{"content":"505ec5559236affd51ee6621b0eb54780bcd0e11"},"source":"sha256:1b83f3aaa8c87b39bd0abd38babbd75a599fb7398c85f3e28f662ac6d8f7c85d","source_binding":{"contract":"devostasis.work-source.v1","kind":"DEFAULT_BRANCH","project":{"endpoint":"https://api.github.com","project_id":"123456","provider":"github"},"ref":"main","revision":"505ec5559236affd51ee6621b0eb54780bcd0e11","source_locator":"acme/widget","source_project":{"endpoint":"https://api.github.com","project_id":"123456","provider":"github"}},"subject":{"context":"CANONICAL","endpoint":"https://api.github.com","locator":"acme/widget","project_id":"123456","provider":"github","revision":"505ec5559236affd51ee6621b0eb54780bcd0e11","source_binding":{"contract":"devostasis.work-source.v1","kind":"DEFAULT_BRANCH","project":{"endpoint":"https://api.github.com","project_id":"123456","provider":"github"},"ref":"main","revision":"505ec5559236affd51ee6621b0eb54780bcd0e11","source_locator":"acme/widget","source_project":{"endpoint":"https://api.github.com","project_id":"123456","provider":"github"}}},"title":"Static analysis reported this rule at this location."},{"acceptance":["Inspect the rule and location; reproduce or disposition the finding, then rerun the producer."],"action":"analyze_code","continuation":false,"criterion":"DS-PY-BARE-EXCEPT","dependencies":[],"eligibility":"READY","evidence":["sha256:05cc5cc2caca768462c0e31b9da37aad7932663e12a10fee8ef127f3b2a1d683","sha256:5090e7b9655fd3d9939f8ba7c919a2659e045d75ce63ea40856ec073dfcce347","sha256:6dd5dafdb0c30195aa78bb67f84b42439e41a4b6f9afd1853f54c76c2531b7b2","sha256:81af3c53aea50eb741c59f91525b8e453eb88f8016c0c3f1872301f0dd904d9a"],"executor":"worker","fingerprint":"sha256:907c0de8929c42a350a6883d1da2365a5bbfaf6b8d9a0a2ca9b6a9fd144ddac2","id":"sha256:1f870a65315bc8f6bbe3cc36c842b93b90ee43289c651e9a10c2fd6499a333f3","implementation_owner":null,"merge_gate":null,"priority":{"rank":1000,"rationale":"No consumer business priority declared."},"queue":"analyze_code","read_set":{"budget":{"max_bytes":200000,"max_files":20},"paths":["src/widget/parser.py"],"symbols":[],"total_paths":1},"reasons":[],"revision":{"content":"505ec5559236affd51ee6621b0eb54780bcd0e11"},"source":"sha256:021413c4afe15592bb4926c5c54e09e05d7a5f61ac0fc3f4bff2aa084d46ce4f","source_binding":{"contract":"devostasis.work-source.v1","kind":"DEFAULT_BRANCH","project":{"endpoint":"https://api.github.com","project_id":"123456","provider":"github"},"ref":"main","revision":"505ec5559236affd51ee6621b0eb54780bcd0e11","source_locator":"acme/widget","source_project":{"endpoint":"https://api.github.com","project_id":"123456","provider":"github"}},"subject":{"context":"CANONICAL","endpoint":"https://api.github.com","locator":"acme/widget","project_id":"123456","provider":"github","revision":"505ec5559236affd51ee6621b0eb54780bcd0e11","source_binding":{"contract":"devostasis.work-source.v1","kind":"DEFAULT_BRANCH","project":{"endpoint":"https://api.github.com","project_id":"123456","provider":"github"},"ref":"main","revision":"505ec5559236affd51ee6621b0eb54780bcd0e11","source_locator":"acme/widget","source_project":{"endpoint":"https://api.github.com","project_id":"123456","provider":"github"}}},"title":"Static analysis reported this rule at this location."},{"acceptance":["Inspect the rule and location; reproduce or disposition the finding, then rerun the producer."],"action":"analyze_code","continuation":false,"criterion":"DS-PY-COMPLEXITY","dependencies":[],"eligibility":"READY","evidence":["sha256:05cc5cc2caca768462c0e31b9da37aad7932663e12a10fee8ef127f3b2a1d683","sha256:5090e7b9655fd3d9939f8ba7c919a2659e045d75ce63ea40856ec073dfcce347","sha256:6dd5dafdb0c30195aa78bb67f84b42439e41a4b6f9afd1853f54c76c2531b7b2","sha256:81af3c53aea50eb741c59f91525b8e453eb88f8016c0c3f1872301f0dd904d9a"],"executor":"worker","fingerprint":"sha256:15691e8ddb6f6efc7691344630bb94c27460a54f11097d8f81998e0ca3a805c9","id":"sha256:47d088d2ea2f8b2599670c8907f2f18d9d3d2f50ee729006cfe505c00878450a","implementation_owner":null,"merge_gate":null,"priority":{"rank":1000,"rationale":"No consumer business priority declared."},"queue":"analyze_code","read_set":{"budget":{"max_bytes":200000,"max_files":20},"paths":["src/widget/parser.py"],"symbols":[],"total_paths":1},"reasons":[],"revision":{"content":"505ec5559236affd51ee6621b0eb54780bcd0e11"},"source":"sha256:cda057b57b75e76615b0b488dc8fba3d1d5b438aab88e0c4007d0e914ed8e890","source_binding":{"contract":"devostasis.work-source.v1","kind":"DEFAULT_BRANCH","project":{"endpoint":"https://api.github.com","project_id":"123456","provider":"github"},"ref":"main","revision":"505ec5559236affd51ee6621b0eb54780bcd0e11","source_locator":"acme/widget","source_project":{"endpoint":"https://api.github.com","project_id":"123456","provider":"github"}},"subject":{"context":"CANONICAL","endpoint":"https://api.github.com","locator":"acme/widget","project_id":"123456","provider":"github","revision":"505ec5559236affd51ee6621b0eb54780bcd0e11","source_binding":{"contract":"devostasis.work-source.v1","kind":"DEFAULT_BRANCH","project":{"endpoint":"https://api.github.com","project_id":"123456","provider":"github"},"ref":"main","revision":"505ec5559236affd51ee6621b0eb54780bcd0e11","source_locator":"acme/widget","source_project":{"endpoint":"https://api.github.com","project_id":"123456","provider":"github"}}},"title":"Static analysis reported this rule at this location."},{"acceptance":["Inspect the rule and location; reproduce or disposition the finding, then rerun the producer."],"action":"analyze_code","continuation":false,"criterion":"DS-PY-IMPORT-CYCLE","dependencies":[],"eligibility":"READY","evidence":["sha256:05cc5cc2caca768462c0e31b9da37aad7932663e12a10fee8ef127f3b2a1d683","sha256:5090e7b9655fd3d9939f8ba7c919a2659e045d75ce63ea40856ec073dfcce347","sha256:6dd5dafdb0c30195aa78bb67f84b42439e41a4b6f9afd1853f54c76c2531b7b2","sha256:81af3c53aea50eb741c59f91525b8e453eb88f8016c0c3f1872301f0dd904d9a"],"executor":"worker","fingerprint":"sha256:4cd339c1e44a86642fdb8b7682684e7ce19dc27f2a753beda503aeee80cf587d","id":"sha256:8c82d0679a8170a163c69450af9d44bc0284408ec64c6dae0c83e085a5ae022a","implementation_owner":null,"merge_gate":null,"priority":{"rank":1000,"rationale":"No consumer business priority declared."},"queue":"analyze_code","read_set":{"budget":{"max_bytes":200000,"max_files":20},"paths":["src/widget/app.py"],"symbols":[],"total_paths":1},"reasons":[],"revision":{"content":"505ec5559236affd51ee6621b0eb54780bcd0e11"},"source":"sha256:5f5567f8a3ad51e6dc111d6fae9d0351b39fa9d061d00fb694ec7f9b69f81bc4","source_binding":{"contract":"devostasis.work-source.v1","kind":"DEFAULT_BRANCH","project":{"endpoint":"https://api.github.com","project_id":"123456","provider":"github"},"ref":"main","revision":"505ec5559236affd51ee6621b0eb54780bcd0e11","source_locator":"acme/widget","source_project":{"endpoint":"https://api.github.com","project_id":"123456","provider":"github"}},"subject":{"context":"CANONICAL","endpoint":"https://api.github.com","locator":"acme/widget","project_id":"123456","provider":"github","revision":"505ec5559236affd51ee6621b0eb54780bcd0e11","source_binding":{"contract":"devostasis.work-source.v1","kind":"DEFAULT_BRANCH","project":{"endpoint":"https://api.github.com","project_id":"123456","provider":"github"},"ref":"main","revision":"505ec5559236affd51ee6621b0eb54780bcd0e11","source_locator":"acme/widget","source_project":{"endpoint":"https://api.github.com","project_id":"123456","provider":"github"}}},"title":"Static analysis reported this rule at this location."}],"kind":"scope","observed_at":"2026-10-08T12:00:00Z","planned_at":"2026-10-08T12:00:00Z","policy_version":"example-1","queues":{"analyze_code":["sha256:1a80f069dbede63d29605f66c4fb9794baca0b0b419589c66bf24dc00640e633","sha256:1f870a65315bc8f6bbe3cc36c842b93b90ee43289c651e9a10c2fd6499a333f3","sha256:47d088d2ea2f8b2599670c8907f2f18d9d3d2f50ee729006cfe505c00878450a","sha256:8c82d0679a8170a163c69450af9d44bc0284408ec64c6dae0c83e085a5ae022a"],"finish_merge":[],"implement_issue":[],"research":[],"review":[]},"selection":{"changes":"OPEN_AND_REGISTERED","issues":[]},"subject":{"context":"CANONICAL","endpoint":"https://api.github.com","locator":"acme/widget","project_id":"123456","provider":"github","revision":"505ec5559236affd51ee6621b0eb54780bcd0e11","source_binding":{"contract":"devostasis.work-source.v1","kind":"DEFAULT_BRANCH","project":{"endpoint":"https://api.github.com","project_id":"123456","provider":"github"},"ref":"main","revision":"505ec5559236affd51ee6621b0eb54780bcd0e11","source_locator":"acme/widget","source_project":{"endpoint":"https://api.github.com","project_id":"123456","provider":"github"}}}} \ No newline at end of file diff --git a/examples/code-v2/work-policy.json b/examples/code-v2/work-policy.json new file mode 100644 index 0000000..8b56348 --- /dev/null +++ b/examples/code-v2/work-policy.json @@ -0,0 +1 @@ +{"actor":"worker","allow_merge_train":false,"contract":"devostasis.work.v2","criteria":[],"dispositions":[],"kind":"policy","minimum_approvals":1,"priorities":{},"queue_order":["review","finish_merge","implement_issue","research","analyze_code"],"read_budget":{"max_bytes":200000,"max_files":20},"requests":[],"required_checks":[],"roles":["all"],"ttl_seconds":3600,"version":"example-1"} \ No newline at end of file diff --git a/schemas/code-history-proof.schema.json b/schemas/code-history-proof.schema.json new file mode 100644 index 0000000..ac9e3bb --- /dev/null +++ b/schemas/code-history-proof.schema.json @@ -0,0 +1,78 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/drevendev/Devostasis/schemas/code-history-proof.schema.json", + "description": "Shape of bounded proof v1; executable admission authenticates Git object types/hashes, reachability, frontier, budgets and every derived history claim.", + "type": "object", + "additionalProperties": false, + "required": [ + "contract", + "commits", + "trees", + "blobs", + "omitted", + "frontier" + ], + "properties": { + "contract": { + "const": "devostasis.git-history-proof.v1" + }, + "commits": { + "type": "object", + "maxProperties": 50000, + "propertyNames": { + "pattern": "^[0-9a-f]{40}([0-9a-f]{24})?$" + }, + "additionalProperties": { + "type": "string", + "contentEncoding": "base64" + } + }, + "trees": { + "type": "object", + "maxProperties": 50000, + "propertyNames": { + "pattern": "^[0-9a-f]{40}([0-9a-f]{24})?$" + }, + "additionalProperties": { + "type": "string", + "contentEncoding": "base64" + } + }, + "blobs": { + "type": "object", + "maxProperties": 50000, + "propertyNames": { + "pattern": "^[0-9a-f]{40}([0-9a-f]{24})?$" + }, + "additionalProperties": { + "type": "string", + "contentEncoding": "base64" + } + }, + "omitted": { + "type": "object", + "maxProperties": 100000, + "propertyNames": { + "pattern": "^[0-9a-f]{40}([0-9a-f]{24})?$" + }, + "additionalProperties": { + "enum": [ + "OBJECT_UNAVAILABLE", + "OBJECT_BYTE_CAP", + "OBJECT_COUNT_CAP", + "PROOF_BYTE_CAP", + "TIME_CAP" + ] + } + }, + "frontier": { + "type": "array", + "maxItems": 10000, + "uniqueItems": true, + "items": { + "type": "string", + "pattern": "^[0-9a-f]{40}([0-9a-f]{24})?$" + } + } + } +} diff --git a/schemas/code-manifest-v2.schema.json b/schemas/code-manifest-v2.schema.json new file mode 100644 index 0000000..c621f7a --- /dev/null +++ b/schemas/code-manifest-v2.schema.json @@ -0,0 +1,108 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/drevendev/Devostasis/schemas/code-manifest-v2.schema.json", + "description": "Shape constraints; executable admission additionally enforces canonical bytes, source proofs, unique sorted paths, cross-field limits and exact replay.", + "type": "object", + "additionalProperties": false, + "required": [ + "contract", + "engine", + "subject", + "members", + "bundle_id" + ], + "properties": { + "contract": { + "const": "devostasis.code-analysis.v2" + }, + "engine": { + "const": "devostasis.code-engine.v2" + }, + "subject": { + "type": "object", + "additionalProperties": false, + "required": [ + "kind", + "object_format", + "roots", + "repository_id", + "revision", + "tree", + "roots_complete" + ], + "properties": { + "kind": { + "const": "LOCAL_GIT" + }, + "object_format": { + "enum": [ + "sha1", + "sha256" + ] + }, + "roots": { + "type": "array", + "minItems": 0, + "maxItems": 10000, + "uniqueItems": true, + "items": { + "type": "string", + "pattern": "^[0-9a-f]{40}([0-9a-f]{24})?$" + } + }, + "repository_id": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "revision": { + "type": "string", + "pattern": "^[0-9a-f]{40}([0-9a-f]{24})?$" + }, + "tree": { + "type": "string", + "pattern": "^[0-9a-f]{40}([0-9a-f]{24})?$" + }, + "roots_complete": { + "type": "boolean" + } + } + }, + "members": { + "type": "object", + "additionalProperties": false, + "required": [ + "inputs.json", + "policy.json", + "analysis.json", + "findings.sarif", + "report.md" + ], + "properties": { + "inputs.json": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "policy.json": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "analysis.json": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "findings.sarif": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "report.md": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + } + } + }, + "bundle_id": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + } + } +} diff --git a/schemas/code-packet-v2.schema.json b/schemas/code-packet-v2.schema.json new file mode 100644 index 0000000..f062495 --- /dev/null +++ b/schemas/code-packet-v2.schema.json @@ -0,0 +1,142 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/drevendev/Devostasis/schemas/code-packet-v2.schema.json", + "description": "Shape constraints; executable admission additionally enforces canonical bytes, source proofs, unique sorted paths, cross-field limits and exact replay.", + "type": "object", + "additionalProperties": false, + "required": [ + "contract", + "analysis_bundle_id", + "subject", + "budget", + "files", + "total_bytes", + "authority", + "packet_id" + ], + "properties": { + "contract": { + "const": "devostasis.code-packet.v2" + }, + "analysis_bundle_id": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "subject": { + "type": "object", + "additionalProperties": false, + "required": [ + "kind", + "object_format", + "roots", + "repository_id", + "revision", + "tree", + "roots_complete" + ], + "properties": { + "kind": { + "const": "LOCAL_GIT" + }, + "object_format": { + "enum": [ + "sha1", + "sha256" + ] + }, + "roots": { + "type": "array", + "minItems": 0, + "maxItems": 10000, + "uniqueItems": true, + "items": { + "type": "string", + "pattern": "^[0-9a-f]{40}([0-9a-f]{24})?$" + } + }, + "repository_id": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + }, + "revision": { + "type": "string", + "pattern": "^[0-9a-f]{40}([0-9a-f]{24})?$" + }, + "tree": { + "type": "string", + "pattern": "^[0-9a-f]{40}([0-9a-f]{24})?$" + }, + "roots_complete": { + "type": "boolean" + } + } + }, + "budget": { + "type": "object", + "additionalProperties": false, + "required": [ + "max_files", + "max_bytes" + ], + "properties": { + "max_files": { + "type": "integer", + "minimum": 1, + "maximum": 1000 + }, + "max_bytes": { + "type": "integer", + "minimum": 1, + "maximum": 67108864 + } + } + }, + "files": { + "type": "array", + "minItems": 1, + "maxItems": 1000, + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "path", + "object_id", + "size", + "base64" + ], + "properties": { + "path": { + "type": "string", + "minLength": 1, + "pattern": "^(?!/)(?!.*(?:^|/)\\.\\.(?:/|$))[^\\\\\\u0000-\\u001f]+$" + }, + "object_id": { + "type": "string", + "pattern": "^[0-9a-f]{40}([0-9a-f]{24})?$" + }, + "size": { + "type": "integer", + "minimum": 0, + "maximum": 67108864 + }, + "base64": { + "type": "string", + "contentEncoding": "base64" + } + } + } + }, + "total_bytes": { + "type": "integer", + "minimum": 0, + "maximum": 67108864 + }, + "authority": { + "const": "PINNED_LOCAL_SOURCE_ONLY; no live freshness, independent acceptance or execution authority" + }, + "packet_id": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$" + } + } +} diff --git a/schemas/code-policy-v2.schema.json b/schemas/code-policy-v2.schema.json new file mode 100644 index 0000000..05444b2 --- /dev/null +++ b/schemas/code-policy-v2.schema.json @@ -0,0 +1,184 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/drevendev/Devostasis/schemas/code-policy-v2.schema.json", + "description": "Shape constraints; executable admission additionally enforces canonical bytes, source proofs, unique sorted paths, cross-field limits and exact replay.", + "type": "object", + "additionalProperties": false, + "required": [ + "contract", + "version", + "grammar", + "include", + "exclude", + "source_roots", + "budget", + "limits", + "history" + ], + "properties": { + "contract": { + "const": "devostasis.code-policy.v2" + }, + "version": { + "type": "string", + "minLength": 1 + }, + "grammar": { + "const": "PYTHON_3_12" + }, + "include": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "string", + "minLength": 1, + "pattern": "^(?!/)(?!.*(?:^|/)\\.\\.(?:/|$))[^\\\\\\u0000-\\u001f]+$" + } + }, + "exclude": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "string", + "minLength": 1, + "pattern": "^(?!/)(?!.*(?:^|/)\\.\\.(?:/|$))[^\\\\\\u0000-\\u001f]+$" + } + }, + "source_roots": { + "type": "array", + "minItems": 1, + "maxItems": 100, + "uniqueItems": true, + "items": { + "anyOf": [ + { + "const": "." + }, + { + "type": "string", + "minLength": 1, + "pattern": "^(?!/)(?!.*(?:^|/)\\.\\.(?:/|$))[^\\\\\\u0000-\\u001f]+$" + } + ] + } + }, + "budget": { + "type": "object", + "additionalProperties": false, + "required": [ + "max_files", + "max_file_bytes", + "max_total_bytes", + "seconds" + ], + "properties": { + "max_files": { + "type": "integer", + "minimum": 1, + "maximum": 5000 + }, + "max_file_bytes": { + "type": "integer", + "minimum": 1, + "maximum": 4194304 + }, + "max_total_bytes": { + "type": "integer", + "minimum": 1, + "maximum": 67108864 + }, + "seconds": { + "type": "integer", + "minimum": 1, + "maximum": 3600 + } + } + }, + "limits": { + "type": "object", + "additionalProperties": false, + "required": [ + "complexity", + "function_lines", + "file_lines" + ], + "properties": { + "complexity": { + "type": "integer", + "minimum": 1, + "maximum": 100000 + }, + "function_lines": { + "type": "integer", + "minimum": 1, + "maximum": 100000 + }, + "file_lines": { + "type": "integer", + "minimum": 1, + "maximum": 100000 + } + } + }, + "history": { + "type": "object", + "additionalProperties": false, + "required": [ + "enabled", + "days", + "max_commits", + "max_ancestors", + "max_objects", + "max_proof_bytes", + "max_blob_bytes", + "max_diff_work", + "max_diff_lines" + ], + "properties": { + "enabled": { + "type": "boolean" + }, + "days": { + "type": "integer", + "minimum": 1, + "maximum": 3650 + }, + "max_commits": { + "type": "integer", + "minimum": 1, + "maximum": 10000 + }, + "max_ancestors": { + "type": "integer", + "minimum": 1, + "maximum": 10000 + }, + "max_objects": { + "type": "integer", + "minimum": 1, + "maximum": 50000 + }, + "max_proof_bytes": { + "type": "integer", + "minimum": 1024, + "maximum": 67108864 + }, + "max_blob_bytes": { + "type": "integer", + "minimum": 1, + "maximum": 4194304 + }, + "max_diff_work": { + "type": "integer", + "minimum": 1, + "maximum": 10000000 + }, + "max_diff_lines": { + "type": "integer", + "minimum": 1, + "maximum": 200000 + } + } + } + } +} diff --git a/scripts/make_code_examples.py b/scripts/make_code_examples.py index 609f014..4d17288 100644 --- a/scripts/make_code_examples.py +++ b/scripts/make_code_examples.py @@ -22,8 +22,8 @@ AT = "2026-10-08T12:00:00Z" -def generate(): - config = default_policy() +def generate(*, legacy=True): + config = default_policy(legacy=legacy) config["limits"]["complexity"] = 1 with tempfile.TemporaryDirectory(prefix="devostasis-synthetic-code-") as directory: repo = Path(directory) @@ -63,7 +63,8 @@ def git(*args): if __name__ == "__main__": - destination = ROOT / "examples/code" - for name, data in generate().items(): + legacy = "--v2" not in sys.argv[1:] + destination = ROOT / ("examples/code" if legacy else "examples/code-v2") + for name, data in generate(legacy=legacy).items(): file = destination / name; file.parent.mkdir(parents=True, exist_ok=True); file.write_bytes(data) print(destination) diff --git a/src/devostasis/codeanalysis/__init__.py b/src/devostasis/codeanalysis/__init__.py index 20e070c..153f661 100644 --- a/src/devostasis/codeanalysis/__init__.py +++ b/src/devostasis/codeanalysis/__init__.py @@ -1,7 +1,13 @@ """Offline, revision-bound source evidence; independent of the seven Vitals.""" -CONTRACT = "devostasis.code-analysis.v1" -POLICY = "devostasis.code-policy.v1" -ENGINE = "devostasis.code-engine.v1" -PACKET = "devostasis.code-packet.v1" -COMPARISON = "devostasis.code-comparison.v1" +LEGACY_CONTRACT = "devostasis.code-analysis.v1" +LEGACY_POLICY = "devostasis.code-policy.v1" +LEGACY_ENGINE = "devostasis.code-engine.v1" +LEGACY_PACKET = "devostasis.code-packet.v1" +LEGACY_COMPARISON = "devostasis.code-comparison.v1" +CONTRACT = "devostasis.code-analysis.v2" +POLICY = "devostasis.code-policy.v2" +ENGINE = "devostasis.code-engine.v2" +PACKET = "devostasis.code-packet.v2" +COMPARISON = "devostasis.code-comparison.v2" +HISTORY_PROOF = "devostasis.git-history-proof.v1" diff --git a/src/devostasis/codeanalysis/bundle.py b/src/devostasis/codeanalysis/bundle.py index a291775..3ac0d6c 100644 --- a/src/devostasis/codeanalysis/bundle.py +++ b/src/devostasis/codeanalysis/bundle.py @@ -15,7 +15,7 @@ from ..workscope.bundle import locked from ..workscope.model import fields, instant, number, path, require, revision, strings, timestamp from ..workscope.operations import no_links -from . import CONTRACT, ENGINE +from . import CONTRACT, ENGINE, LEGACY_CONTRACT, LEGACY_ENGINE, POLICY from .engine import RULES, evaluate, policy from .git import object_digest, source_selection @@ -33,17 +33,22 @@ def decode(value, cap): def admit(raw, config): policy(config) - fields(raw, ("subject", "observed_at", "entries", "sources", "history", "proof")) + proven = config["contract"] == POLICY + fields(raw, ("subject", "observed_at", "entries", "sources", "history", "proof", *(["history_proof"] if proven else []))) subject = raw["subject"] - fields(subject, ("kind", "object_format", "roots", "repository_id", "revision", "tree")) + fields(subject, ("kind", "object_format", "roots", "repository_id", "revision", "tree", *(["roots_complete"] if proven else []))) require(subject["kind"] == "LOCAL_GIT" and subject["object_format"] in ("sha1", "sha256"), "unsupported local source identity") width = 40 if subject["object_format"] == "sha1" else 64 for ref in (subject["revision"], subject["tree"]): revision(ref); require(len(ref) == width, "Git object format mismatch") strings(subject["roots"], "root commits") - require(0 < len(subject["roots"]) <= 256 and subject["roots"] == sorted(subject["roots"]), "noncanonical root identity") + require((proven or subject["roots"]) and len(subject["roots"]) <= (10000 if proven else 256) + and subject["roots"] == sorted(subject["roots"]), "noncanonical root identity") for ref in subject["roots"]: revision(ref); require(len(ref) == width, "root format mismatch") - require(subject["repository_id"] == digest({"object_format": subject["object_format"], "roots": subject["roots"]}), "repository identity mismatch") + if proven: + require(type(subject["roots_complete"]) is bool, "root completeness must be boolean") + else: + require(subject["repository_id"] == digest({"object_format": subject["object_format"], "roots": subject["roots"]}), "repository identity mismatch") instant(raw["observed_at"]) fields(raw["proof"], ("commit", "trees")) commit = decode(raw["proof"]["commit"], 1024 * 1024) @@ -108,6 +113,10 @@ def admit(raw, config): require(entry["reason"] in ("UNSAFE_PATH", "SOURCE_LINK_OR_SUBMODULE", "FILE_BYTE_CAP", "FILE_COUNT_CAP", "TOTAL_BYTE_CAP"), "unavailable source reason unknown") require(seen == sorted(set(seen)) and set(raw["sources"]) == admitted, "duplicate, unordered or foreign source") require(len(admitted) <= config["budget"]["max_files"] and source_total <= config["budget"]["max_total_bytes"], "source budget exceeded") + if proven: + from .history_proof import admit_proven + admit_proven(raw, config) + return raw history = raw["history"] fields(history, ("status", "reasons", "start", "end", "commits")); strings(history["reasons"], "history reasons") require(instant(history["end"]) == instant(raw["observed_at"]) and instant(history["start"]) == instant(history["end"]) - timedelta(days=config["history"]["days"]), "history window mismatch") @@ -138,7 +147,7 @@ def escape(value): def sarif(analysis, *, encode_paths=True): return {"version": "2.1.0", "$schema": "https://json.schemastore.org/sarif-2.1.0.json", "runs": [{ - "tool": {"driver": {"name": "Devostasis Code Analysis", "version": ENGINE, + "tool": {"driver": {"name": "Devostasis Code Analysis", "version": analysis["engine"], "rules": [{"id": r, "shortDescription": {"text": m}} for r, m in sorted(RULES.items())]}}, "versionControlProvenance": [{"revisionId": analysis["subject"]["revision"]}], "properties": {"sourceCoverage": analysis["coverage"], "historyCoverage": analysis["history"]["status"], "authority": analysis["authority"]}, @@ -177,7 +186,7 @@ def build(raw, config): admit(raw, config); analysis = evaluate(raw, config) content = {"inputs.json": canonical_bytes(raw), "policy.json": canonical_bytes(config), "analysis.json": canonical_bytes(analysis), "findings.sarif": canonical_bytes(sarif(analysis)), "report.md": render(analysis)} - manifest = {"contract": CONTRACT, "engine": ENGINE, "subject": deepcopy(raw["subject"]), + manifest = {"contract": CONTRACT if "history_proof" in raw else LEGACY_CONTRACT, "engine": analysis["engine"], "subject": deepcopy(raw["subject"]), "members": {name: digest_bytes(data) for name, data in sorted(content.items())}} manifest["bundle_id"] = digest(manifest); content["manifest.json"] = canonical_bytes(manifest) require(sum(map(len, content.values())) <= MAX_BUNDLE_BYTES, "analysis bundle byte cap") @@ -190,7 +199,7 @@ def verify_content(content): require(all(canonical_bytes(value) == content[name] for name, value in decoded.items()), "analysis member is not canonical") manifest = decoded["manifest.json"] fields(manifest, ("contract", "engine", "subject", "members", "bundle_id")) - require(manifest["contract"] == CONTRACT and manifest["engine"] == ENGINE, "unsupported analysis lineage") + require((manifest["contract"], manifest["engine"]) in ((CONTRACT, ENGINE), (LEGACY_CONTRACT, LEGACY_ENGINE)), "unsupported analysis lineage") expected, replay = build(decoded["inputs.json"], decoded["policy.json"]) require(expected == manifest and replay == content, "analysis bundle does not replay") return decoded @@ -215,9 +224,15 @@ def verify(root): def proof_scope(decoded): """Operational verification limits, outside the immutable v1 artifact bytes.""" omitted = any(e["kind"] == "blob" and e["status"] != "ADMITTED" for e in decoded["inputs.json"]["entries"]) - return {"source_tree": "VERIFIED", "admitted_blobs": "VERIFIED", + result = {"source_tree": "VERIFIED", "admitted_blobs": "VERIFIED", "omitted_blob_sizes": "UNPROVEN" if omitted else "NOT_APPLICABLE", "history_ancestry": "UNPROVEN"} + raw = decoded["inputs.json"] + if "history_proof" in raw: + result["history_ancestry"] = "VERIFIED" if raw["subject"]["roots_complete"] else "PARTIAL" + result["history_counts"] = "NOT_REQUESTED" if raw["history"]["status"] == "NOT_REQUESTED" else ( + "VERIFIED" if raw["history"]["status"] == "COMPLETE" else "PARTIAL") + return result def publish(root, content): diff --git a/src/devostasis/codeanalysis/consumer.py b/src/devostasis/codeanalysis/consumer.py index 07ec5d4..898251c 100644 --- a/src/devostasis/codeanalysis/consumer.py +++ b/src/devostasis/codeanalysis/consumer.py @@ -5,16 +5,20 @@ from ..canonical import canonical_bytes, digest from ..workscope.model import fields, instant, number, path, require, strings -from . import COMPARISON, ENGINE, PACKET +from . import COMPARISON, PACKET, CONTRACT, LEGACY_COMPARISON, LEGACY_PACKET from .bundle import decode, sarif def compare(before, after): a, b = before["analysis.json"], after["analysis.json"] reasons = [] + if a["engine"] != b["engine"]: reasons.append("ANALYSIS_LINEAGE_CHANGED") + if (a["subject"].get("roots_complete") is False or b["subject"].get("roots_complete") is False): + reasons.append("LOCAL_REPOSITORY_IDENTITY_UNPROVEN") if a["subject"]["repository_id"] != b["subject"]["repository_id"]: reasons.append("LOCAL_REPOSITORY_IDENTITY_CHANGED") if before["policy.json"] != after["policy.json"]: reasons.append("ANALYSIS_POLICY_CHANGED") - result = {"contract": COMPARISON, "before": before["manifest.json"]["bundle_id"], "after": after["manifest.json"]["bundle_id"], + contract = COMPARISON if any(v["manifest.json"]["contract"] == CONTRACT for v in (before, after)) else LEGACY_COMPARISON + result = {"contract": contract, "before": before["manifest.json"]["bundle_id"], "after": after["manifest.json"]["bundle_id"], "status": "INCOMPARABLE" if reasons else "COMPARABLE", "reasons": reasons, "findings": [], "files": [], "authority": "STATIC_FINDING_TRANSITIONS; no health, productivity, improvement or acceptance judgement"} if reasons: return result @@ -50,7 +54,7 @@ def packet(decoded, paths, max_files=20, max_bytes=1024 * 1024): data = decode(raw["sources"][filename], max_bytes); total += len(data) require(total <= max_bytes, "source packet byte budget") files.append({"path": filename, "object_id": entries[filename]["object_id"], "size": len(data), "base64": raw["sources"][filename]}) - value = {"contract": PACKET, "analysis_bundle_id": decoded["manifest.json"]["bundle_id"], "subject": deepcopy(raw["subject"]), + value = {"contract": PACKET if decoded["manifest.json"]["contract"] == CONTRACT else LEGACY_PACKET, "analysis_bundle_id": decoded["manifest.json"]["bundle_id"], "subject": deepcopy(raw["subject"]), "budget": {"max_files": max_files, "max_bytes": max_bytes}, "files": files, "total_bytes": total, "authority": "PINNED_LOCAL_SOURCE_ONLY; no live freshness, independent acceptance or execution authority"} value["packet_id"] = digest(value) @@ -82,7 +86,7 @@ def work_evidence(decoded, inventory, at=None): # SARIF artifact uses encoded URI references for external consumers. report = canonical_bytes(sarif(analysis, encode_paths=False)) value = empty_evidence() - entry = source("sarif.v1", ENGINE, "1", deepcopy(inventory["subject"]), observed, report) + entry = source("sarif.v1", analysis["engine"], "1", deepcopy(inventory["subject"]), observed, report) if analysis["coverage"] != "COMPLETE": entry.update(status="PARTIAL", reason="CODE_SOURCE_COVERAGE_" + analysis["coverage"]) value["sources"].append(entry) diff --git a/src/devostasis/codeanalysis/engine.py b/src/devostasis/codeanalysis/engine.py index 11ac2b4..dcfb2aa 100644 --- a/src/devostasis/codeanalysis/engine.py +++ b/src/devostasis/codeanalysis/engine.py @@ -9,24 +9,28 @@ from ..canonical import digest from ..workscope.model import fields, number, path, require, strings -from . import ENGINE, POLICY +from . import ENGINE, LEGACY_ENGINE, LEGACY_POLICY, POLICY RULES = {"DS-PY-SYNTAX": "Python syntax cannot be parsed", "DS-PY-COMPLEXITY": "Function decision count exceeds policy", "DS-PY-FUNCTION-SIZE": "Function line span exceeds policy", "DS-PY-FILE-SIZE": "File line count exceeds policy", "DS-PY-BARE-EXCEPT": "Bare except also catches process control exceptions", "DS-PY-IMPORT-CYCLE": "Static import cycle"} -def default_policy(): - return {"contract": POLICY, "version": "default-1", "grammar": "PYTHON_3_12", "include": [], +def default_policy(*, legacy=False): + value = {"contract": LEGACY_POLICY if legacy else POLICY, "version": "default-1" if legacy else "default-2", "grammar": "PYTHON_3_12", "include": [], "exclude": [".venv", "build", "dist", "vendor", "node_modules"], "source_roots": [".", "src"], "budget": {"max_files": 2000, "max_file_bytes": 1024 * 1024, "max_total_bytes": 16 * 1024 * 1024, "seconds": 120}, "limits": {"complexity": 15, "function_lines": 80, "file_lines": 600}, "history": {"enabled": True, "days": 28, "max_commits": 500}} + if not legacy: + value["history"].update(max_ancestors=2000, max_objects=10000, max_proof_bytes=32 * 1024 * 1024, + max_blob_bytes=4 * 1024 * 1024, max_diff_work=2_000_000, max_diff_lines=100000) + return value def policy(value): fields(value, ("contract", "version", "grammar", "include", "exclude", "source_roots", "budget", "limits", "history")) - require(value["contract"] == POLICY and value["grammar"] == "PYTHON_3_12", "unsupported code policy") + require(value["contract"] in (POLICY, LEGACY_POLICY) and value["grammar"] == "PYTHON_3_12", "unsupported code policy") from ..workscope.model import text text(value["version"]) strings(value["include"], "included roots", paths=True); strings(value["exclude"], "excluded roots", paths=True) @@ -40,7 +44,14 @@ def policy(value): require(value["budget"]["max_file_bytes"] <= value["budget"]["max_total_bytes"], "per-file budget exceeds total") fields(value["limits"], ("complexity", "function_lines", "file_lines")) for v in value["limits"].values(): number(v, 1, 100000) - fields(value["history"], ("enabled", "days", "max_commits")) + history_keys = ("enabled", "days", "max_commits") + if value["contract"] == POLICY: + history_keys += ("max_ancestors", "max_objects", "max_proof_bytes", "max_blob_bytes", "max_diff_work", "max_diff_lines") + for key, low, high in (("max_ancestors", 1, 10000), ("max_objects", 1, 50000), + ("max_proof_bytes", 1024, 64 * 1024 * 1024), ("max_blob_bytes", 1, 4 * 1024 * 1024), + ("max_diff_work", 1, 10_000_000), ("max_diff_lines", 1, 200000)): + number(value["history"].get(key), low, high) + fields(value["history"], history_keys) require(type(value["history"]["enabled"]) is bool, "explicit history enablement required") number(value["history"]["days"], 1, 3650); number(value["history"]["max_commits"], 1, 10000) return value @@ -189,21 +200,27 @@ def finding(rule, filename, symbol, line, end, evidence, related=()): for component in components: finding("DS-PY-IMPORT-CYCLE", component[0], "cycle:" + digest(component)[7:], 1, 1, {"paths": component}, component) findings.sort(key=lambda f: (f["path"], f["line"], f["rule"], f["symbol"])) - history = raw["history"]; changes = defaultdict(lambda: {"revisions": set(), "added": 0, "deleted": 0, "binary": 0}) + proven = "history_proof" in raw + history = raw["history"]; changes = defaultdict(lambda: {"revisions": set(), "added": 0, "deleted": 0, "binary": 0, "unproven": 0}) for commit in history["commits"]: for change in commit["changes"]: if change["path"] not in entries or entries[change["path"]]["status"] == "OUT_OF_SCOPE": continue cell = changes[change["path"]]; cell["revisions"].add(commit["revision"]) - if change["added"] is None: cell["binary"] += 1 + if proven and change["count_status"] == "UNAVAILABLE": cell["unproven"] += 1 + elif change["added"] is None: cell["binary"] += 1 else: cell["added"] += change["added"]; cell["deleted"] += change["deleted"] hotspots = [{"path": p, "revisions": len(c["revisions"]), "added": c["added"], "deleted": c["deleted"], "binary_changes": c["binary"], "coverage": history["status"]} for p, c in changes.items()] + if proven: + for hotspot in hotspots: + hotspot["unproven_changes"] = changes[hotspot["path"]]["unproven"] + hotspot["count_status"] = "PARTIAL" if hotspot["unproven_changes"] else "VERIFIED" hotspots.sort(key=lambda h: (-h["revisions"], -(h["added"] + h["deleted"]), h["path"])) unknown = any(r["status"] in ("UNAVAILABLE", "PARSE_ERROR") for r in output) applicable = any(e["status"] == "ADMITTED" or (e["path"] and e["path"].endswith(".py") and e["status"] == "UNAVAILABLE") for e in raw["entries"]) - return {"engine": ENGINE, "subject": deepcopy(raw["subject"]), "observed_at": raw["observed_at"], + return {"engine": ENGINE if proven else LEGACY_ENGINE, "subject": deepcopy(raw["subject"]), "observed_at": raw["observed_at"], "coverage": "PARTIAL" if unknown else "COMPLETE" if applicable else "NOT_APPLICABLE", "files": output, "findings": findings, "imports": imports, "import_cycles": components, "hotspots": hotspots, "history": {k: deepcopy(v) for k, v in history.items() if k != "commits"}, diff --git a/src/devostasis/codeanalysis/git.py b/src/devostasis/codeanalysis/git.py index 1dcb409..6953eb2 100644 --- a/src/devostasis/codeanalysis/git.py +++ b/src/devostasis/codeanalysis/git.py @@ -115,13 +115,24 @@ def resolve(self, ref): def collect(directory, ref, config, at=None): - git = Git(directory, config["budget"]["seconds"]) + from . import LEGACY_POLICY, POLICY + from .engine import policy + policy(config) + if config["contract"] == POLICY: + from .history_proof import collect_proven + return collect_proven(directory, ref, config, at) + require(config["contract"] == LEGACY_POLICY, "unsupported collection policy") + return _collect_v1(directory, ref, config, at) + + +def _collect_v1(directory, ref, config, at=None, *, _git=None, _source_only=False): + git = _git or Git(directory, config["budget"]["seconds"]) commit = git.resolve(ref) tree = git.run(["rev-parse", commit + "^{tree}"]).strip().decode("ascii") commit_epoch = int(git.run(["show", "-s", "--format=%ct", commit]).strip()) observed = timestamp(at or datetime.fromtimestamp(commit_epoch, timezone.utc).isoformat()) # A local identity names the complete root set, never an inferred forge id. - roots_raw = git.run(["rev-list", "--max-parents=0", commit], cap=65536) + roots_raw = (commit + "\n").encode("ascii") if _source_only else git.run(["rev-list", "--max-parents=0", commit], cap=65536) roots = sorted(set(roots_raw.decode("ascii").splitlines())) require(0 < len(roots) <= 256 and all(re.fullmatch(r"[0-9a-f]{40}|[0-9a-f]{64}", r) for r in roots), "local root identity unavailable") rows = git.run(["ls-tree", "-r", "-l", "-z", "--full-tree", tree]) @@ -175,7 +186,7 @@ def collect(directory, ref, config, at=None): start = instant(observed) - timedelta(days=config["history"]["days"]) history = {"status": "NOT_REQUESTED", "reasons": [], "start": timestamp(start.isoformat()), "end": observed, "commits": []} - if config["history"]["enabled"]: + if config["history"]["enabled"] and not _source_only: limit = config["history"]["max_commits"] try: if git.run(["rev-parse", "--is-shallow-repository"]).strip() == b"true": diff --git a/src/devostasis/codeanalysis/history_proof.py b/src/devostasis/codeanalysis/history_proof.py new file mode 100644 index 0000000..f5f0445 --- /dev/null +++ b/src/devostasis/codeanalysis/history_proof.py @@ -0,0 +1,331 @@ +"""Bounded Git object proofs and pure offline ancestry/line-edit admission. + +No Git executable, diff driver, checkout, clock or repository is used by replay. +LF-delimited byte lines use a bounded shortest edit distance, independent of +Git configuration and rename/diff heuristics. Missing proof is explicit. +""" + +import base64 +from datetime import datetime, timedelta, timezone + +from ..canonical import digest +from ..workscope.model import fields, instant, path, require, revision, timestamp +from . import HISTORY_PROOF +from .git import Git, _collect_v1, object_digest + +KINDS = ("commit", "tree", "blob") +OMISSIONS = {"OBJECT_UNAVAILABLE", "OBJECT_BYTE_CAP", "OBJECT_COUNT_CAP", "PROOF_BYTE_CAP", "TIME_CAP"} +ALGORITHM = "MYERS_LF_BYTE_LINES_V1" + + +def encoded(data): + return base64.b64encode(data).decode("ascii") + + +def decoded(value, cap): + require(isinstance(value, str) and len(value) <= (cap + 2) // 3 * 4, "history object encoding cap") + data = base64.b64decode(value, validate=True) + require(len(data) <= cap and encoded(data) == value, "noncanonical history object") + return data + + +def object_ref(value, width): + revision(value) + require(len(value) == width, "history object format mismatch") + return value + + +def commit_headers(data, width): + header, separator, _ = data.partition(b"\n\n") + require(separator, "commit header terminator missing") + lines = header.split(b"\n") + trees = [line[5:].decode("ascii") for line in lines if line.startswith(b"tree ")] + parents = [line[7:].decode("ascii") for line in lines if line.startswith(b"parent ")] + committers = [line[10:] for line in lines if line.startswith(b"committer ")] + require(len(trees) == 1 and len(committers) == 1 and len(parents) <= 10000 + and len(parents) == len(set(parents)), "commit tree/parent/committer shape") + for ref in trees + parents: + object_ref(ref, width) + identity, epoch, zone = committers[0].rsplit(b" ", 2) + require(identity.endswith(b">") and len(zone) == 5 and zone[:1] in (b"+", b"-") + and zone[1:].isdigit(), "commit committer syntax") + require(epoch.lstrip(b"-").isdigit(), "commit epoch syntax") + when = timestamp(datetime.fromtimestamp(int(epoch), timezone.utc).isoformat()) + return {"tree": trees[0], "parents": parents, "committed_at": when} + + +def seed_objects(raw): + fmt = raw["subject"]["object_format"] + objects = {kind: {} for kind in KINDS} + objects["commit"][raw["subject"]["revision"]] = decoded(raw["proof"]["commit"], 1024 * 1024) + objects["tree"] = {oid: decoded(value, 8 * 1024 * 1024) for oid, value in raw["proof"]["trees"].items()} + entries = {e["path"]: e for e in raw["entries"] if e["path"] is not None} + for name, value in raw["sources"].items(): + objects["blob"][entries[name]["object_id"]] = base64.b64decode(value, validate=True) + for kind, rows in objects.items(): + require(all(object_digest(data, fmt, kind) == oid for oid, data in rows.items()), "history seed object mismatch") + return objects + + +class Objects: + """Same bounded lookup interface for acquisition and independent replay.""" + + def __init__(self, raw, config, proof=None, git=None): + self.format = raw["subject"]["object_format"] + self.width = 40 if self.format == "sha1" else 64 + self.limits = config["history"] + self.seed = seed_objects(raw) + self.objects = {kind: dict(rows) for kind, rows in self.seed.items()} + self.extra = {kind: {} for kind in KINDS} + self.omitted, self.used = {}, set() + self.bytes = self.count = 0 + self.git = git + if proof is not None: + fields(proof, ("contract", "commits", "trees", "blobs", "omitted", "frontier")) + require(proof["contract"] == HISTORY_PROOF, "unsupported history proof lineage") + for kind in KINDS: + rows = proof[kind + "s"] + require(isinstance(rows, dict) and len(rows) <= self.limits["max_objects"], "history object count cap") + for oid, value in rows.items(): + object_ref(oid, self.width) + require(oid not in self.objects[kind], "duplicate source/history object") + cap = self.limits["max_blob_bytes"] if kind == "blob" else 1024 * 1024 if kind == "commit" else 8 * 1024 * 1024 + data = decoded(value, cap) + require(object_digest(data, self.format, kind) == oid, "historic object hash mismatch") + self.objects[kind][oid] = self.extra[kind][oid] = data + self.bytes += len(data); self.count += 1 + require(self.count <= self.limits["max_objects"] and self.bytes <= self.limits["max_proof_bytes"], "history proof budget exceeded") + require(self.count <= self.limits["max_objects"] and self.bytes <= self.limits["max_proof_bytes"], "history proof budget exceeded") + require(isinstance(proof["omitted"], dict) and len(proof["omitted"]) <= 100000, "history omission cap") + for oid, reason in proof["omitted"].items(): + object_ref(oid, self.width) + require(reason in OMISSIONS and not any(oid in rows for rows in self.objects.values()), "history omission mismatch") + self.omitted = dict(proof["omitted"]) + + def prefetch(self, kind, refs): + if self.git is None: + return + refs = [oid for oid in sorted(set(refs)) if oid not in self.objects[kind] and oid not in self.omitted] + if not refs: + return + try: + metadata = self.git.run(["cat-file", "--batch-check"], data="".join(r + "\n" for r in refs).encode("ascii"), cap=len(refs) * 160) + lines = metadata.splitlines() + require(len(lines) == len(refs), "historic object header count") + selected, size_total = [], 0 + for oid, line in zip(refs, lines): + parts = line.split() + if parts == [oid.encode("ascii"), b"missing"]: + self.omitted[oid] = "OBJECT_UNAVAILABLE"; continue + require(len(parts) == 3 and parts[0].decode("ascii") == oid and parts[1].decode("ascii") == kind, + "historic object kind mismatch") + size = int(parts[2]) + cap = self.limits["max_blob_bytes"] if kind == "blob" else 1024 * 1024 if kind == "commit" else 8 * 1024 * 1024 + if size > cap: + self.omitted[oid] = "OBJECT_BYTE_CAP" + elif self.count + len(selected) >= self.limits["max_objects"]: + self.omitted[oid] = "OBJECT_COUNT_CAP" + elif self.bytes + size_total + size > self.limits["max_proof_bytes"]: + self.omitted[oid] = "PROOF_BYTE_CAP" + else: + require(size >= 0, "historic object negative size") + selected.append((oid, size)); size_total += size + if selected: + data = self.git.run(["cat-file", "--batch"], data="".join(r + "\n" for r, _ in selected).encode("ascii"), cap=size_total + len(selected) * 160) + offset = 0 + for oid, size in selected: + end = data.index(b"\n", offset) + require(data[offset:end] == f"{oid} {kind} {size}".encode("ascii"), "historic object framing mismatch") + offset = end + 1; value = data[offset:offset + size]; offset += size + require(data[offset:offset + 1] == b"\n" and object_digest(value, self.format, kind) == oid, "historic object bytes mismatch") + offset += 1; self.objects[kind][oid] = self.extra[kind][oid] = value + self.bytes += size; self.count += 1 + require(offset == len(data), "historic object trailing bytes") + except (ValueError, OSError) as exc: + reason = "TIME_CAP" if "TIME_CAP" in str(exc) else "OBJECT_BYTE_CAP" if "BYTE_CAP" in str(exc) else "OBJECT_UNAVAILABLE" + for oid in refs: + if oid not in self.objects[kind]: self.omitted[oid] = reason + + def get(self, kind, oid): + object_ref(oid, self.width) + self.used.add((kind, oid)) + require(len(self.used) <= 100000, "history reference work cap") + self.prefetch(kind, [oid]) + if oid in self.objects[kind]: + return self.objects[kind][oid] + require(oid in self.omitted, "required history object is neither proved nor explicitly unavailable") + return None + + def finish(self): + require(all((kind, oid) in self.used for kind in KINDS for oid in self.extra[kind]), "unreachable or unused history object") + require(set(self.omitted) <= {oid for _, oid in self.used}, "unused history omission") + + def proof(self, frontier): + return {"contract": HISTORY_PROOF, **{kind + "s": {oid: encoded(data) for oid, data in sorted(rows.items())} + for kind, rows in self.extra.items()}, "omitted": dict(sorted(self.omitted.items())), "frontier": sorted(frontier)} + + +def graph(objects, head): + pending, attempted, nodes, absent = {head}, set(), {}, set() + while pending and len(attempted) < objects.limits["max_ancestors"]: + batch = sorted(pending)[:min(64, objects.limits["max_ancestors"] - len(attempted))] + objects.prefetch("commit", batch) + for oid in batch: + pending.remove(oid); attempted.add(oid) + data = objects.get("commit", oid) + if data is None: + absent.add(oid); continue + node = commit_headers(data, objects.width); nodes[oid] = node + pending.update(p for p in node["parents"] if p not in attempted) + require(len(pending) + len(absent) <= 10000, "history frontier cap") + frontier = pending | absent + roots = sorted(oid for oid, node in nodes.items() if not node["parents"]) + require(frontier or roots, "complete ancestry has no root") + return nodes, frontier, roots + + +def tree(objects, root, work): + stack, entries, complete = [(root, b"", frozenset())], {}, True + while stack: + oid, prefix, parents = stack.pop() + require(oid not in parents, "historic tree cycle") + data = objects.get("tree", oid) + if data is None: + complete = False; continue + offset = 0 + while offset < len(data): + work[0] += 1 + if work[0] > objects.limits["max_diff_work"]: + return entries, False + space = data.find(b" ", offset); end = data.find(b"\0", space) + require(space > offset and end > space + 1 and end + 1 + objects.width // 2 <= len(data), "historic tree entry malformed") + mode, name = data[offset:space], data[space + 1:end] + require(b"/" not in name and name not in (b".", b".."), "historic tree name malformed") + child = data[end + 1:end + 1 + objects.width // 2].hex(); offset = end + 1 + objects.width // 2 + filename = prefix + name + if mode == b"40000": + stack.append((child, filename + b"/", parents | {oid})) + else: + require(mode in (b"100644", b"100755", b"120000", b"160000") and filename not in entries, "historic tree mode/duplicate") + entries[filename] = (mode, child) + require(len(entries) <= 100000 and len(stack) <= 10000, "historic tree expansion cap") + return entries, complete + + +def line_edits(before, after, limits, work): + """Shortest insertion/deletion counts over exact LF-terminated byte lines.""" + if b"\0" in before or b"\0" in after: + return None, None, "BINARY", None + line_count = lambda value: value.count(b"\n") + int(bool(value) and not value.endswith(b"\n")) + if line_count(before) + line_count(after) > limits["max_diff_lines"]: + return None, None, "UNAVAILABLE", "DIFF_LINE_CAP" + def lines(value): + parts = value.split(b"\n") + return [p + b"\n" for p in parts[:-1]] + ([parts[-1]] if parts[-1] else []) + a, b = lines(before), lines(after) + n, m = len(a), len(b) + if not n or not m: + return m, n, "VERIFIED", None + frontier = {1: 0} + for distance in range(n + m + 1): + for diagonal in range(-distance, distance + 1, 2): + work[0] += 1 + if work[0] > limits["max_diff_work"]: + return None, None, "UNAVAILABLE", "DIFF_WORK_CAP" + if diagonal == -distance or (diagonal != distance and frontier.get(diagonal - 1, -1) < frontier.get(diagonal + 1, -1)): + x = frontier.get(diagonal + 1, 0) + else: + x = frontier[diagonal - 1] + 1 + y = x - diagonal + while x < n and y < m and a[x] == b[y]: + work[0] += 1 + if work[0] > limits["max_diff_work"]: + return None, None, "UNAVAILABLE", "DIFF_WORK_CAP" + x += 1; y += 1 + frontier[diagonal] = x + if x >= n and y >= m: + return (distance + m - n) // 2, (distance + n - m) // 2, "VERIFIED", None + raise ValueError("line edit traversal failed") + + +def derive_history(raw, config, objects): + head = raw["subject"]["revision"] + nodes, frontier, roots = graph(objects, head) + complete = not frontier + identity = {"object_format": objects.format, "roots": roots} + if not complete: identity["unproven_ancestry_anchor"] = head + subject = {**raw["subject"], "roots": roots, "roots_complete": complete, "repository_id": digest(identity)} + end = instant(raw["observed_at"]); start = end - timedelta(days=config["history"]["days"]) + history = {"status": "NOT_REQUESTED", "reasons": [], "start": timestamp(start.isoformat()), "end": timestamp(end.isoformat()), + "commits": [], "ancestry_status": "COMPLETE" if complete else "PARTIAL", "count_algorithm": ALGORITHM} + if not config["history"]["enabled"]: + return subject, history, frontier + reasons = set() + if frontier: reasons.add("HISTORY_GRAPH_INCOMPLETE") + selected = sorted(((oid, row) for oid, row in nodes.items() if len(row["parents"]) <= 1 + and start <= instant(row["committed_at"]) <= end), key=lambda pair: (pair[1]["committed_at"], pair[0]), reverse=True) + if len(selected) > config["history"]["max_commits"]: reasons.add("HISTORY_COMMIT_CAP") + selected = sorted(selected[:config["history"]["max_commits"]], key=lambda pair: (pair[1]["committed_at"], pair[0])) + tree_cache, work = {}, [0] + def inventory(root): + if root not in tree_cache: tree_cache[root] = tree(objects, root, work) + return tree_cache[root] + for oid, row in selected: + current, current_complete = inventory(row["tree"]) + parent = nodes.get(row["parents"][0]) if row["parents"] else None + previous, previous_complete = inventory(parent["tree"]) if parent else ({}, not row["parents"]) + changes, changes_complete = [], current_complete and previous_complete + for filename in sorted(set(current) | set(previous)): + old, new = previous.get(filename), current.get(filename) + if old == new: continue + try: name = path(filename.decode("utf-8")) + except (ValueError, UnicodeError): + changes_complete = False; reasons.add("HISTORY_PATH_UNAVAILABLE"); continue + included = not config["include"] or any(name == p or name.startswith(p + "/") for p in config["include"]) + excluded = any(name == p or name.startswith(p + "/") for p in config["exclude"]) + if not included or excluded: + changes.append({"path": name, "added": None, "deleted": None, + "count_status": "OUT_OF_SCOPE", "reason": "POLICY_EXCLUSION"}) + continue + sides = [] + for entry in (old, new): + if entry is None: sides.append(b"") + elif entry[0] == b"160000": sides.append(None) + else: sides.append(objects.get("blob", entry[1])) + if (old is None and not previous_complete) or (new is None and not current_complete): + added, deleted, status, reason = None, None, "UNAVAILABLE", "HISTORIC_TREE_UNAVAILABLE" + elif old and new and old[1] == new[1]: + added, deleted, status, reason = 0, 0, "VERIFIED", None + elif any(value is None for value in sides): + added, deleted, status, reason = None, None, "UNAVAILABLE", "HISTORIC_BLOB_UNAVAILABLE" + else: + added, deleted, status, reason = line_edits(*sides, config["history"], work) + if status == "UNAVAILABLE": + changes_complete = False; reasons.add(reason) + changes.append({"path": name, "added": added, "deleted": deleted, "count_status": status, "reason": reason}) + if not changes_complete: reasons.add("HISTORY_CHANGE_PROOF_INCOMPLETE") + history["commits"].append({"revision": oid, "committed_at": row["committed_at"], "changes": changes, + "changes_complete": changes_complete}) + history["reasons"] = sorted(reasons) + history["status"] = "PARTIAL" if reasons else "COMPLETE" + return subject, history, frontier + + +def collect_proven(directory, ref, config, at=None): + git = Git(directory, config["budget"]["seconds"]) + raw = _collect_v1(directory, ref, config, at, _git=git, _source_only=True) + objects = Objects(raw, config, git=git) + subject, history, frontier = derive_history(raw, config, objects) + raw.update(subject=subject, history=history, history_proof=objects.proof(frontier)) + objects.finish() + return raw + + +def admit_proven(raw, config): + objects = Objects(raw, config, proof=raw["history_proof"]) + subject, history, frontier = derive_history(raw, config, objects) + require(raw["subject"] == subject, "ancestry/root identity proof mismatch") + require(raw["history"] == history, "historic counts/completeness do not match object proof") + require(raw["history_proof"]["frontier"] == sorted(frontier), "ancestry frontier mismatch") + objects.finish() + return objects diff --git a/tests/test_code_analysis.py b/tests/test_code_analysis.py index a9e0bb2..f7f8838 100644 --- a/tests/test_code_analysis.py +++ b/tests/test_code_analysis.py @@ -263,7 +263,7 @@ def test_code_shallow_history_retains_explicit_partial_coverage(tmp_path): git(tmp_path, "clone", "--depth=1", repo.as_uri(), str(clone)) result = analyze(clone, at=NEXT)[4]["analysis.json"] assert result["history"]["status"] == "PARTIAL" - assert "SHALLOW_HISTORY" in result["history"]["reasons"] + assert "HISTORY_GRAPH_INCOMPLETE" in result["history"]["reasons"] def test_code_sha256_repository_proofs_replay_with_explicit_object_format(tmp_path): diff --git a/tests/test_code_history_proof.py b/tests/test_code_history_proof.py new file mode 100644 index 0000000..2ab8164 --- /dev/null +++ b/tests/test_code_history_proof.py @@ -0,0 +1,141 @@ +"""Independent object-proof admission rather than trust in collector claims.""" + +from copy import deepcopy +import itertools + +import pytest + +from devostasis.codeanalysis.bundle import admit, build, proof_scope, verify_content +from devostasis.codeanalysis.consumer import compare, packet, verify_packet +from devostasis.codeanalysis.engine import default_policy +from devostasis.codeanalysis.history_proof import line_edits +from test_code_analysis import NEXT, analyze, commit, git, repository + + +@pytest.mark.parametrize("claim", ["root", "repository", "ancestor", "count", "time", "complete", "frontier"]) +def test_rehashed_history_claims_require_authenticated_objects(tmp_path, claim): + repo = repository(tmp_path, {"a.py": "a = 1\n"}) + commit(repo, {"a.py": "a = 2\nb = 3\n"}, NEXT) + raw, config, _, _, _ = analyze(repo, at=NEXT) + raw = deepcopy(raw) + if claim == "root": raw["subject"]["roots"] = ["0" * 40] + if claim == "repository": raw["subject"]["repository_id"] = "sha256:" + "0" * 64 + if claim == "ancestor": raw["history_proof"]["commits"].clear() + if claim == "count": raw["history"]["commits"][-1]["changes"][0]["added"] = 999 + if claim == "time": raw["history"]["commits"][0]["committed_at"] = NEXT + if claim == "complete": raw["history"]["commits"].pop() + if claim == "frontier": raw["history_proof"]["frontier"] = ["0" * 40] + with pytest.raises(ValueError): build(raw, config) + + +def test_offline_replay_has_no_git_repository_or_subprocess_dependency(tmp_path, monkeypatch): + repo = repository(tmp_path, {"a.py": "x = 1\n"}) + commit(repo, {"a.py": "x = 2\n"}, NEXT) + raw, _, _, members, decoded = analyze(repo, at=NEXT) + import subprocess + def forbidden(*args, **kwargs): raise AssertionError("offline verifier invoked subprocess") + monkeypatch.setattr(subprocess, "Popen", forbidden) + assert verify_content(members) == decoded + assert proof_scope(decoded)["history_counts"] == "VERIFIED" + assert len(raw["history"]["commits"]) == 2 + + +def test_graph_walk_does_not_prune_clock_skew_or_merge_parents(tmp_path): + repo = repository(tmp_path, {"a.py": "x = 1\n"}) + root = git(repo, "rev-parse", "HEAD").strip().decode() + git(repo, "checkout", "-b", "side") + side = commit(repo, {"side.py": "s = 1\n"}, NEXT) + git(repo, "checkout", "main") + main = commit(repo, {"main.py": "m = 1\n"}, "2026-08-01T00:00:00Z") + git(repo, "merge", "--no-ff", "side", "-m", "synthetic merge", at=NEXT) + raw, _, _, _, _ = analyze(repo, at=NEXT) + assert raw["subject"]["roots"] == [root] + assert raw["subject"]["roots_complete"] is True + assert {r["revision"] for r in raw["history"]["commits"]} == {root, side} + assert main in raw["history_proof"]["commits"] + + +@pytest.mark.parametrize("limit,value", [("max_ancestors", 1), ("max_objects", 1), ("max_blob_bytes", 1), ("max_diff_work", 1)]) +def test_bounded_proof_never_claims_complete_history(tmp_path, limit, value): + repo = repository(tmp_path, {"a.py": "x = 1\n"}) + commit(repo, {"a.py": "x = 2\n"}, NEXT) + config = default_policy(); config["history"][limit] = value + raw, _, _, _, decoded = analyze(repo, config, at=NEXT) + assert raw["history"]["status"] == "PARTIAL" + assert proof_scope(decoded)["history_counts"] == "PARTIAL" + forged = deepcopy(raw); forged["history"]["status"] = "COMPLETE"; forged["history"]["reasons"] = [] + with pytest.raises(ValueError): admit(forged, config) + if limit == "max_ancestors": + assert raw["subject"]["roots"] == [] and not raw["subject"]["roots_complete"] + assert compare(decoded, decoded)["status"] == "INCOMPARABLE" + assert all(c["count_status"] == "UNAVAILABLE" for c in raw["history"]["commits"][0]["changes"]) + + +def test_wrongly_typed_historic_objects_fail_closed(tmp_path): + raw, config, _, _, _ = analyze(repository(tmp_path)) + raw["history_proof"]["blobs"][raw["subject"]["revision"]] = raw["proof"]["commit"] + with pytest.raises(ValueError, match="hash mismatch"): admit(raw, config) + + +def test_v1_and_v2_replay_separately_and_do_not_compare_across_lineage(tmp_path): + repo = repository(tmp_path) + old = analyze(repo, default_policy(legacy=True))[4] + new = analyze(repo)[4] + assert old["manifest.json"]["contract"] == "devostasis.code-analysis.v1" + assert new["manifest.json"]["contract"] == "devostasis.code-analysis.v2" + assert proof_scope(old)["history_ancestry"] == "UNPROVEN" + assert proof_scope(new)["history_ancestry"] == "VERIFIED" + assert "ANALYSIS_LINEAGE_CHANGED" in compare(old, new)["reasons"] + for decoded in (old, new): + assert verify_packet(packet(decoded, ["src/pkg/a.py"]), decoded) + + +def test_byte_line_algorithm_matches_independent_lcs_oracle(): + limits = default_policy()["history"] + words = [b"", b"a\n", b"b\n", b"a\nb\n", b"b\na\n", b"a\na\n", b"a"] + for before, after in itertools.product(words, repeat=2): + a, b = before.splitlines(keepends=True), after.splitlines(keepends=True) + table = [[0] * (len(b) + 1) for _ in range(len(a) + 1)] + for i in range(len(a)): + for j in range(len(b)): + table[i + 1][j + 1] = table[i][j] + 1 if a[i] == b[j] else max(table[i][j + 1], table[i + 1][j]) + common = table[-1][-1] + assert line_edits(before, after, limits, [0]) == (len(b) - common, len(a) - common, "VERIFIED", None) + assert line_edits(b"a\0", b"b", limits, [0])[2] == "BINARY" + + +def test_historic_proof_does_not_read_policy_excluded_blob_bytes(tmp_path): + from devostasis.codeanalysis.history_proof import decoded + secret = b"excluded fixture bytes, never part of proof\n" + repo = repository(tmp_path, {"private/secret.txt": secret, "a.py": "x = 1\n"}) + commit(repo, {"private/secret.txt": b"other excluded bytes\n", "a.py": "x = 2\n"}, NEXT) + config = default_policy(); config["exclude"].append("private") + raw, _, _, _, _ = analyze(repo, config, at=NEXT) + assert all(decoded(value, 4 * 1024 * 1024) != secret for value in raw["history_proof"]["blobs"].values()) + assert all(c["count_status"] == "OUT_OF_SCOPE" for row in raw["history"]["commits"] for c in row["changes"] if c["path"].startswith("private/")) + + +def test_valid_but_unreachable_historic_object_is_rejected(tmp_path): + from devostasis.codeanalysis.git import object_digest + from devostasis.codeanalysis.history_proof import encoded + raw, config, _, _, _ = analyze(repository(tmp_path)) + extra = b"unreachable fixture" + raw["history_proof"]["blobs"][object_digest(extra, "sha1")] = encoded(extra) + with pytest.raises(ValueError, match="unused history object"): admit(raw, config) + + +def test_v2_frozen_example_replays_and_has_explicit_schemas(): + import json + from pathlib import Path + from devostasis.codeanalysis.bundle import verify + from devostasis.workscope.bundle import verify as verify_work + root = Path(__file__).resolve().parents[1] + decoded = verify(root / "examples/code-v2/bundle") + assert proof_scope(decoded)["history_ancestry"] == "VERIFIED" + verify_packet(json.loads((root / "examples/code-v2/packet.json").read_text()), decoded) + assert any(i["queue"] == "analyze_code" for i in verify_work(root / "examples/code-v2/work-bundle")["scope.json"]["items"]) + for name, value in (("code-manifest", decoded["manifest.json"]), ("code-policy", decoded["policy.json"]), + ("code-packet", json.loads((root / "examples/code-v2/packet.json").read_text()))): + schema = json.loads((root / f"schemas/{name}-v2.schema.json").read_text()) + assert schema["properties"]["contract"]["const"] == value["contract"] + assert set(value) == set(schema["required"]) == set(schema["properties"]) diff --git a/tests/test_source_admission.py b/tests/test_source_admission.py index 21d8e9e..08d073b 100644 --- a/tests/test_source_admission.py +++ b/tests/test_source_admission.py @@ -36,7 +36,7 @@ def test_valid_caps_and_policy_exclusions_still_replay(tmp_path): def test_omitted_size_claim_does_not_become_independent_blob_proof(tmp_path, capsys): - config = default_policy() + config = default_policy(legacy=True) raw = collect(repository(tmp_path, {"safe.py": "pass\n"}), "HEAD", config, AT) # The tree authenticates the blob id, not this replacement size metadata. changed = deepcopy(raw) From 9717a475896f2ad1410a654f32ddaa11402c33d1 Mon Sep 17 00:00:00 2001 From: Unisphere Dev Date: Wed, 21 Oct 2026 16:16:01 +0300 Subject: [PATCH 08/11] feat(conformance): execute native and stateful assurance scenarios --- .github/workflows/ci.yml | 8 + schemas/conformance-bridge.schema.json | 773 +++++++++++++++ scripts/check_conformance_surface.py | 25 + src/devostasis/adapters/gitlab_ci.py | 19 + src/devostasis/history.py | 30 +- src/devostasis/vitals/integrity.py | 26 + tests/fixtures/conformance-surface.json | 1157 +++++++++++++++++++++++ tests/surface_bridge.py | 287 ++++++ tests/test_conformance_surface.py | 67 ++ 9 files changed, 2390 insertions(+), 2 deletions(-) create mode 100644 schemas/conformance-bridge.schema.json create mode 100644 scripts/check_conformance_surface.py create mode 100644 src/devostasis/adapters/gitlab_ci.py create mode 100644 tests/fixtures/conformance-surface.json create mode 100644 tests/surface_bridge.py create mode 100644 tests/test_conformance_surface.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c94bd7f..4276348 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,6 +32,8 @@ jobs: run: python -m pytest -q - name: Conformance vectors through the command line run: devostasis vectors --quiet + - name: Versioned conformance operation bridge + run: python scripts/check_conformance_surface.py - name: Example bundle verifies from its own contents run: devostasis verify --bundle examples/sample-bundle - name: Work companion replay and deterministic handoff @@ -56,6 +58,12 @@ jobs: devostasis work replay --bundle examples/code/work-bundle python scripts/make_code_examples.py git diff --exit-code -- examples/code + devostasis code verify --bundle examples/code-v2/bundle + devostasis code verify-packet --bundle examples/code-v2/bundle --packet examples/code-v2/packet.json + devostasis work verify --bundle examples/code-v2/work-bundle + devostasis work replay --bundle examples/code-v2/work-bundle + python scripts/make_code_examples.py --v2 + git diff --exit-code -- examples/code-v2 - name: Determinism check on the example observations run: | a=$(devostasis build --observations examples/observations.json --store /tmp/store-a --planning milestones --debt-label type:refactor --debt-mapping-version example-1 | head -n 1 | awk '{print $2}') diff --git a/schemas/conformance-bridge.schema.json b/schemas/conformance-bridge.schema.json new file mode 100644 index 0000000..7338562 --- /dev/null +++ b/schemas/conformance-bridge.schema.json @@ -0,0 +1,773 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/drevendev/Devostasis/schemas/conformance-bridge.schema.json", + "description": "Source-checkout executable bridge. Runtime additionally checks unique permanent IDs/variants, exact operation lineage, whole T8 matrix, all input fields and every assertion path.", + "type": "object", + "additionalProperties": false, + "required": [ + "schema", + "source", + "cases" + ], + "properties": { + "schema": { + "const": "devostasis.conformance-bridge.v1" + }, + "source": { + "type": "string", + "minLength": 1 + }, + "cases": { + "type": "array", + "minItems": 1, + "items": { + "oneOf": [ + { + "type": "object", + "additionalProperties": false, + "required": [ + "case", + "scenario", + "given", + "checks" + ], + "properties": { + "case": { + "type": "string", + "minLength": 1 + }, + "scenario": { + "const": "ci-native.v1" + }, + "given": { + "type": "object", + "additionalProperties": false, + "required": [ + "variants" + ], + "properties": { + "variants": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "name", + "provider", + "mapping", + "status", + "conclusion" + ], + "properties": { + "name": { + "type": "string", + "minLength": 1 + }, + "provider": { + "enum": [ + "github", + "gitlab" + ] + }, + "mapping": { + "enum": [ + "devostasis.ci-outcomes.github.v1", + "devostasis.ci-outcomes.gitlab.v1" + ] + }, + "status": { + "type": "string", + "minLength": 1 + }, + "conclusion": { + "type": [ + "string", + "null" + ] + } + } + } + } + } + }, + "checks": { + "type": "array", + "minItems": 1, + "items": { + "oneOf": [ + { + "type": "object", + "additionalProperties": false, + "required": [ + "path", + "equals" + ], + "properties": { + "path": { + "type": "array", + "items": { + "type": [ + "string", + "integer" + ] + } + }, + "equals": {} + } + }, + { + "type": "object", + "additionalProperties": false, + "required": [ + "path", + "not_equals" + ], + "properties": { + "path": { + "type": "array", + "items": { + "type": [ + "string", + "integer" + ] + } + }, + "not_equals": {} + } + }, + { + "type": "object", + "additionalProperties": false, + "required": [ + "path", + "contains" + ], + "properties": { + "path": { + "type": "array", + "items": { + "type": [ + "string", + "integer" + ] + } + }, + "contains": {} + } + }, + { + "type": "object", + "additionalProperties": false, + "required": [ + "path", + "absent" + ], + "properties": { + "path": { + "type": "array", + "items": { + "type": [ + "string", + "integer" + ] + } + }, + "absent": { + "const": true + } + } + } + ] + } + } + } + }, + { + "type": "object", + "additionalProperties": false, + "required": [ + "case", + "scenario", + "given", + "checks" + ], + "properties": { + "case": { + "type": "string", + "minLength": 1 + }, + "scenario": { + "const": "independence.v1" + }, + "given": { + "type": "object", + "additionalProperties": false, + "required": [ + "attempts", + "sample_semantics" + ], + "properties": { + "attempts": { + "type": "array", + "minItems": 1, + "items": { + "type": "string" + } + }, + "sample_semantics": { + "enum": [ + "RAW_RUNS", + "INDEPENDENT_REVISIONS" + ] + } + } + }, + "checks": { + "type": "array", + "minItems": 1, + "items": { + "oneOf": [ + { + "type": "object", + "additionalProperties": false, + "required": [ + "path", + "equals" + ], + "properties": { + "path": { + "type": "array", + "items": { + "type": [ + "string", + "integer" + ] + } + }, + "equals": {} + } + }, + { + "type": "object", + "additionalProperties": false, + "required": [ + "path", + "not_equals" + ], + "properties": { + "path": { + "type": "array", + "items": { + "type": [ + "string", + "integer" + ] + } + }, + "not_equals": {} + } + }, + { + "type": "object", + "additionalProperties": false, + "required": [ + "path", + "contains" + ], + "properties": { + "path": { + "type": "array", + "items": { + "type": [ + "string", + "integer" + ] + } + }, + "contains": {} + } + }, + { + "type": "object", + "additionalProperties": false, + "required": [ + "path", + "absent" + ], + "properties": { + "path": { + "type": "array", + "items": { + "type": [ + "string", + "integer" + ] + } + }, + "absent": { + "const": true + } + } + } + ] + } + } + } + }, + { + "type": "object", + "additionalProperties": false, + "required": [ + "case", + "scenario", + "given", + "checks" + ], + "properties": { + "case": { + "type": "string", + "minLength": 1 + }, + "scenario": { + "const": "multi-vital.v1" + }, + "given": { + "type": "object", + "additionalProperties": false, + "required": [ + "observation_id", + "variants", + "inventories" + ], + "properties": { + "observation_id": { + "type": [ + "string", + "null" + ] + }, + "variants": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "name", + "status", + "freshness" + ], + "properties": { + "name": { + "type": "string", + "minLength": 1 + }, + "status": { + "enum": [ + "AVAILABLE", + "PARTIAL", + "FORBIDDEN", + "UNKNOWN", + "ERROR" + ] + }, + "freshness": { + "enum": [ + "FRESH", + "STALE" + ] + } + } + } + }, + "inventories": { + "type": "object", + "additionalProperties": false, + "required": [ + "git.default_branch.commits_28d", + "forge.change_requests.inventory", + "forge.issues.inventory", + "git.nondefault_branches.inventory" + ], + "properties": { + "git.default_branch.commits_28d": { + "type": "array", + "items": { + "type": "object" + } + }, + "forge.change_requests.inventory": { + "type": "array", + "items": { + "type": "object" + } + }, + "forge.issues.inventory": { + "type": "array", + "items": { + "type": "object" + } + }, + "git.nondefault_branches.inventory": { + "type": "array", + "items": { + "type": "object" + } + } + } + } + } + }, + "checks": { + "type": "array", + "minItems": 1, + "items": { + "oneOf": [ + { + "type": "object", + "additionalProperties": false, + "required": [ + "path", + "equals" + ], + "properties": { + "path": { + "type": "array", + "items": { + "type": [ + "string", + "integer" + ] + } + }, + "equals": {} + } + }, + { + "type": "object", + "additionalProperties": false, + "required": [ + "path", + "not_equals" + ], + "properties": { + "path": { + "type": "array", + "items": { + "type": [ + "string", + "integer" + ] + } + }, + "not_equals": {} + } + }, + { + "type": "object", + "additionalProperties": false, + "required": [ + "path", + "contains" + ], + "properties": { + "path": { + "type": "array", + "items": { + "type": [ + "string", + "integer" + ] + } + }, + "contains": {} + } + }, + { + "type": "object", + "additionalProperties": false, + "required": [ + "path", + "absent" + ], + "properties": { + "path": { + "type": "array", + "items": { + "type": [ + "string", + "integer" + ] + } + }, + "absent": { + "const": true + } + } + } + ] + } + } + } + }, + { + "type": "object", + "additionalProperties": false, + "required": [ + "case", + "scenario", + "given", + "checks" + ], + "properties": { + "case": { + "type": "string", + "minLength": 1 + }, + "scenario": { + "const": "bundle.v1" + }, + "given": { + "type": "object", + "additionalProperties": false, + "required": [ + "operation" + ], + "properties": { + "operation": { + "enum": [ + "canonical", + "member-forbidden", + "receipt", + "public", + "private", + "collision" + ] + } + } + }, + "checks": { + "type": "array", + "minItems": 1, + "items": { + "oneOf": [ + { + "type": "object", + "additionalProperties": false, + "required": [ + "path", + "equals" + ], + "properties": { + "path": { + "type": "array", + "items": { + "type": [ + "string", + "integer" + ] + } + }, + "equals": {} + } + }, + { + "type": "object", + "additionalProperties": false, + "required": [ + "path", + "not_equals" + ], + "properties": { + "path": { + "type": "array", + "items": { + "type": [ + "string", + "integer" + ] + } + }, + "not_equals": {} + } + }, + { + "type": "object", + "additionalProperties": false, + "required": [ + "path", + "contains" + ], + "properties": { + "path": { + "type": "array", + "items": { + "type": [ + "string", + "integer" + ] + } + }, + "contains": {} + } + }, + { + "type": "object", + "additionalProperties": false, + "required": [ + "path", + "absent" + ], + "properties": { + "path": { + "type": "array", + "items": { + "type": [ + "string", + "integer" + ] + } + }, + "absent": { + "const": true + } + } + } + ] + } + } + } + }, + { + "type": "object", + "additionalProperties": false, + "required": [ + "case", + "scenario", + "given", + "checks" + ], + "properties": { + "case": { + "type": "string", + "minLength": 1 + }, + "scenario": { + "const": "history-store.v1" + }, + "given": { + "type": "object", + "additionalProperties": false, + "required": [ + "operation" + ], + "properties": { + "operation": { + "enum": [ + "idempotent", + "failed-commit", + "missing-predecessor", + "forbidden", + "two-writers" + ] + } + } + }, + "checks": { + "type": "array", + "minItems": 1, + "items": { + "oneOf": [ + { + "type": "object", + "additionalProperties": false, + "required": [ + "path", + "equals" + ], + "properties": { + "path": { + "type": "array", + "items": { + "type": [ + "string", + "integer" + ] + } + }, + "equals": {} + } + }, + { + "type": "object", + "additionalProperties": false, + "required": [ + "path", + "not_equals" + ], + "properties": { + "path": { + "type": "array", + "items": { + "type": [ + "string", + "integer" + ] + } + }, + "not_equals": {} + } + }, + { + "type": "object", + "additionalProperties": false, + "required": [ + "path", + "contains" + ], + "properties": { + "path": { + "type": "array", + "items": { + "type": [ + "string", + "integer" + ] + } + }, + "contains": {} + } + }, + { + "type": "object", + "additionalProperties": false, + "required": [ + "path", + "absent" + ], + "properties": { + "path": { + "type": "array", + "items": { + "type": [ + "string", + "integer" + ] + } + }, + "absent": { + "const": true + } + } + } + ] + } + } + } + } + ] + } + } + } +} diff --git a/scripts/check_conformance_surface.py b/scripts/check_conformance_surface.py new file mode 100644 index 0000000..ddd5ccc --- /dev/null +++ b/scripts/check_conformance_surface.py @@ -0,0 +1,25 @@ +"""Run the exact published operation bridge from a source checkout, offline.""" + +from pathlib import Path +import sys +import tempfile + +ROOT = Path(__file__).resolve().parents[1] +sys.path[:0] = [str(ROOT / "src"), str(ROOT / "tests")] +from surface_bridge import CONTRACT, execute, load +from devostasis.vectors import load as load_vectors + + +def main(): + cases = load(ROOT / "tests/fixtures/conformance-surface.json") + old = {v.case for v in load_vectors([ROOT / "tests/vectors"])} + if old.intersection(c["case"] for c in cases): raise ValueError("duplicate permanent case across carriers") + for case in cases: + with tempfile.TemporaryDirectory(prefix="devostasis-conformance-") as directory: + execute(case, Path(directory)) + print(case["case"], "PASS") + print(CONTRACT, len(cases), "passed") + + +if __name__ == "__main__": + main() diff --git a/src/devostasis/adapters/gitlab_ci.py b/src/devostasis/adapters/gitlab_ci.py new file mode 100644 index 0000000..9fa1b21 --- /dev/null +++ b/src/devostasis/adapters/gitlab_ci.py @@ -0,0 +1,19 @@ +"""Offline GitLab native outcome boundary under PV-CI-NORM-001. + +This pure normalizer is not a collector and cannot establish history coverage. +Pipeline ids remain distinct; an in-place retry keeps its pipeline identity. +""" + +from .github_ci import (VERIFY_PASS, VERIFY_FAIL, VERIFY_UNRESOLVED, + NON_VERIFY_TERMINAL, NOT_EXECUTED, UNKNOWN) + +OUTCOME_MAP_VERSION = "devostasis.ci-outcomes.gitlab.v1" +OUTCOME_MAP = {"success": VERIFY_PASS, "failed": VERIFY_FAIL, + "canceled": NON_VERIFY_TERMINAL, "skipped": NOT_EXECUTED, + "manual": NOT_EXECUTED, "scheduled": NOT_EXECUTED} +NONTERMINAL = frozenset({"created", "waiting_for_resource", "preparing", "waiting_for_callback", + "pending", "running", "canceling"}) + + +def normalize_outcome(status): + return VERIFY_UNRESOLVED if status in NONTERMINAL else OUTCOME_MAP.get(status, UNKNOWN) diff --git a/src/devostasis/history.py b/src/devostasis/history.py index 5caf7d0..87529ec 100644 --- a/src/devostasis/history.py +++ b/src/devostasis/history.py @@ -75,6 +75,10 @@ class ImmutabilityError(HistoryStoreError): """Raised when an existing immutable bundle would be overwritten with different content.""" +class HistoryConflictError(HistoryStoreError): + """A publisher must rebuild against the latest committed predecessor.""" + + class IdentityConflictError(HistoryStoreError): """Raised when a locator is claimed by a project that is not the one being written.""" @@ -191,10 +195,12 @@ def _identity_of(index: dict[str, Any] | None) -> str | None: def _read_index_at(self, directory: Path) -> dict[str, Any] | None: path = directory / "index.json" - if not path.exists(): - return None try: index = canonical.load_file(path) + except FileNotFoundError: + return None + except PermissionError as exc: + raise HistoryStoreError(f"HISTORY_STORE_FORBIDDEN: index unreadable at {self._rel(directory)}") from exc except Exception as exc: # noqa: BLE001 raise HistoryStoreError(f"index unreadable at {self._rel(directory)}: {type(exc).__name__}") from exc problems = index_problems(index) @@ -568,6 +574,17 @@ def commit( indexed bundle, which the next commit replaces, and never an index that names a bundle the copy contradicts. """ + from .workscope.bundle import locked + from .workscope.model import ScopeError + + self.root.mkdir(parents=True, exist_ok=True) + try: + with locked(self.root / ".history-store-lock"): + return self._commit_locked(bundle, bands, gauges, top_attention) + except ScopeError as exc: + raise HistoryConflictError(f"HISTORY_WRITE_CONFLICT: {exc}") from exc + + def _commit_locked(self, bundle, bands, gauges, top_attention): from .demand import top_attention as _top problems = self._binding_problems(bundle) @@ -580,6 +597,15 @@ def commit( self._relocate(location) directory = location.directory path = self.put_immutable(bundle, directory) + index = self.read_index(bundle.project_key, directory) + entries = index["bundles"] + if entries and not any(e["bundle_id"] == bundle.bundle_id for e in entries): + tail = entries[-1] + if (bundle.observed_at, bundle.bundle_id) > (tail["observed_at"], tail["bundle_id"]): + if bundle.manifest.get("previous_bundle_id") != tail["bundle_id"]: + # Preserve the immutable candidate; never publish a stale writer + # as canonical latest. The caller retries after rebuilding. + raise HistoryConflictError("HISTORY_WRITE_CONFLICT: predecessor changed; rebuild against latest") bands = bands if bands is not None else bundle.bands() gauges = gauges if gauges is not None else bundle.gauges() if top_attention is None: diff --git a/src/devostasis/vitals/integrity.py b/src/devostasis/vitals/integrity.py index 75c8c7b..72ffaaf 100644 --- a/src/devostasis/vitals/integrity.py +++ b/src/devostasis/vitals/integrity.py @@ -214,6 +214,32 @@ def _unknown_current(revisions: list[dict[str, Any]], diagnostics: list[str], hi def evaluate(obs: ObservationSet) -> VitalResult: + """Exact legacy evaluation, or the explicit independence-provenance extension. + + An absent discriminator preserves historical bytes. A present discriminator + is consumed, versioned and fail closed; retries never create revision samples. + """ + result = _evaluate_revisions(obs) + provenance = obs.get("ci.sample_semantics") + if provenance is None: + return result + result.rule_id = RULE_ID + "+independence-001" + result.inputs += input_meta(obs, ["ci.sample_semantics"]) + result.derived["sample_semantics"] = provenance.value if provenance.good else None + if not provenance.good or provenance.value_type != "enum" or provenance.value not in ("RAW_RUNS", "INDEPENDENT_REVISIONS"): + result.band = result.band_semantics = result.possible_bands = None + result.evaluation_status = EVAL_UNKNOWN + result.diagnostics.append("CI_SAMPLE_INDEPENDENCE_UNKNOWN") + elif provenance.value == "RAW_RUNS": + result.derived["sample_strength"] = "PROVISIONAL_NONINDEPENDENT" + result.diagnostics.append("CI_NONINDEPENDENT_SAMPLE") + if result.evaluation_status == EVAL_AVAILABLE: + result.evaluation_status = EVAL_DEGRADED + result.explanation += " Raw execution provenance cannot establish independent revision samples." + return result + + +def _evaluate_revisions(obs: ObservationSet) -> VitalResult: diagnostics: list[str] = [] reconciled = reconcile(obs) history = {"revision_history": reconciled.output()} diff --git a/tests/fixtures/conformance-surface.json b/tests/fixtures/conformance-surface.json new file mode 100644 index 0000000..b2300da --- /dev/null +++ b/tests/fixtures/conformance-surface.json @@ -0,0 +1,1157 @@ +{ + "schema": "devostasis.conformance-bridge.v1", + "source": "PV-CONFORMANCE-SURFACE-001; PV-REV-CONFORMANCE-SURFACE-001; implementation materialization awaiting independent fixture review", + "cases": [ + { + "case": "R5", + "scenario": "ci-native.v1", + "given": { + "variants": [ + { + "name": "skipped", + "provider": "github", + "mapping": "devostasis.ci-outcomes.github.v1", + "status": "completed", + "conclusion": "skipped" + } + ] + }, + "checks": [ + { + "path": [ + "skipped", + "outcome" + ], + "equals": "NOT_EXECUTED" + }, + { + "path": [ + "skipped", + "revision_verdict" + ], + "equals": "NOT_EXECUTED" + }, + { + "path": [ + "skipped", + "decisive" + ], + "equals": 0 + }, + { + "path": [ + "skipped", + "failed" + ], + "equals": 0 + } + ] + }, + { + "case": "R6", + "scenario": "ci-native.v1", + "given": { + "variants": [ + { + "name": "manual", + "provider": "gitlab", + "mapping": "devostasis.ci-outcomes.gitlab.v1", + "status": "manual", + "conclusion": null + } + ] + }, + "checks": [ + { + "path": [ + "manual", + "outcome" + ], + "equals": "NOT_EXECUTED" + }, + { + "path": [ + "manual", + "revision_verdict" + ], + "equals": "NOT_EXECUTED" + }, + { + "path": [ + "manual", + "decisive" + ], + "equals": 0 + }, + { + "path": [ + "manual", + "failed" + ], + "equals": 0 + } + ] + }, + { + "case": "R7", + "scenario": "ci-native.v1", + "given": { + "variants": [ + { + "name": "timeout", + "provider": "github", + "mapping": "devostasis.ci-outcomes.github.v1", + "status": "completed", + "conclusion": "timed_out" + } + ] + }, + "checks": [ + { + "path": [ + "timeout", + "outcome" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "timeout", + "revision_verdict" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "timeout", + "decisive" + ], + "equals": 1 + }, + { + "path": [ + "timeout", + "failed" + ], + "equals": 1 + } + ] + }, + { + "case": "R8", + "scenario": "ci-native.v1", + "given": { + "variants": [ + { + "name": "cancelled", + "provider": "github", + "mapping": "devostasis.ci-outcomes.github.v1", + "status": "completed", + "conclusion": "cancelled" + } + ] + }, + "checks": [ + { + "path": [ + "cancelled", + "outcome" + ], + "equals": "NON_VERIFY_TERMINAL" + }, + { + "path": [ + "cancelled", + "revision_verdict" + ], + "equals": "NON_VERIFY_TERMINAL" + }, + { + "path": [ + "cancelled", + "decisive" + ], + "equals": 0 + }, + { + "path": [ + "cancelled", + "failed" + ], + "equals": 0 + } + ] + }, + { + "case": "R9", + "scenario": "ci-native.v1", + "given": { + "variants": [ + { + "name": "github", + "provider": "github", + "mapping": "devostasis.ci-outcomes.github.v1", + "status": "completed", + "conclusion": "failure" + }, + { + "name": "gitlab", + "provider": "gitlab", + "mapping": "devostasis.ci-outcomes.gitlab.v1", + "status": "failed", + "conclusion": null + } + ] + }, + "checks": [ + { + "path": [ + "github", + "outcome" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "github", + "revision_verdict" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "github", + "decisive" + ], + "equals": 1 + }, + { + "path": [ + "github", + "failed" + ], + "equals": 1 + }, + { + "path": [ + "gitlab", + "outcome" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "gitlab", + "revision_verdict" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "gitlab", + "decisive" + ], + "equals": 1 + }, + { + "path": [ + "gitlab", + "failed" + ], + "equals": 1 + } + ] + }, + { + "case": "R10", + "scenario": "ci-native.v1", + "given": { + "variants": [ + { + "name": "github", + "provider": "github", + "mapping": "devostasis.ci-outcomes.github.v1", + "status": "completed", + "conclusion": "future-result" + }, + { + "name": "gitlab", + "provider": "gitlab", + "mapping": "devostasis.ci-outcomes.gitlab.v1", + "status": "future-result", + "conclusion": null + } + ] + }, + "checks": [ + { + "path": [ + "github", + "outcome" + ], + "equals": "UNKNOWN" + }, + { + "path": [ + "github", + "revision_verdict" + ], + "equals": "UNKNOWN" + }, + { + "path": [ + "github", + "decisive" + ], + "equals": 0 + }, + { + "path": [ + "github", + "failed" + ], + "equals": 0 + }, + { + "path": [ + "gitlab", + "outcome" + ], + "equals": "UNKNOWN" + }, + { + "path": [ + "gitlab", + "revision_verdict" + ], + "equals": "UNKNOWN" + }, + { + "path": [ + "gitlab", + "decisive" + ], + "equals": 0 + }, + { + "path": [ + "gitlab", + "failed" + ], + "equals": 0 + } + ] + }, + { + "case": "T3", + "scenario": "independence.v1", + "given": { + "attempts": [ + "failure", + "success", + "success", + "success", + "success", + "success", + "success", + "success", + "success", + "success", + "success" + ], + "sample_semantics": "RAW_RUNS" + }, + "checks": [ + { + "path": [ + "canonical_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "result", + "evaluation_status" + ], + "equals": "DEGRADED" + }, + { + "path": [ + "result", + "derived", + "sample_strength" + ], + "equals": "PROVISIONAL_NONINDEPENDENT" + }, + { + "path": [ + "result", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "result", + "derived", + "failed_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "result", + "derived", + "current_revision", + "current_verdict" + ], + "equals": "VERIFY_PASS" + }, + { + "path": [ + "result", + "diagnostics" + ], + "contains": "CI_NONINDEPENDENT_SAMPLE" + }, + { + "path": [ + "result", + "rule_id" + ], + "equals": "integrity.bands.v1+ci-unit-004+hist-002+independence-001" + } + ] + }, + { + "case": "T6", + "scenario": "multi-vital.v1", + "given": { + "inventories": { + "git.default_branch.commits_28d": [ + { + "sha": "r", + "committed_at": "2026-09-04T00:00:00Z" + } + ], + "forge.change_requests.inventory": [ + { + "number": 1, + "state": "OPEN", + "created_at": "2026-09-01T00:00:00Z", + "updated_at": "2026-09-04T00:00:00Z", + "merged_at": null + }, + { + "number": 2, + "state": "CLOSED", + "created_at": "2026-09-02T00:00:00Z", + "updated_at": "2026-09-04T00:00:00Z", + "merged_at": "2026-09-04T00:00:00Z" + } + ], + "forge.issues.inventory": [ + { + "number": 1, + "state": "OPEN", + "created_at": "2026-07-01T00:00:00Z", + "updated_at": "2026-07-01T00:00:00Z" + } + ], + "git.nondefault_branches.inventory": [] + }, + "observation_id": null, + "variants": [ + { + "name": "shared", + "status": "AVAILABLE", + "freshness": "FRESH" + } + ] + }, + "checks": [ + { + "path": [ + "shared", + "vitals", + "pulse", + "dependency_group_ids" + ], + "contains": "FLOW_PULSE_ACTIVITY" + }, + { + "path": [ + "shared", + "vitals", + "flow", + "dependency_group_ids" + ], + "contains": "FLOW_PULSE_ACTIVITY" + }, + { + "path": [ + "shared", + "vitals", + "flow", + "dependency_group_ids" + ], + "contains": "CLUTTER_FLOW_FORGE" + }, + { + "path": [ + "shared", + "vitals", + "clutter", + "dependency_group_ids" + ], + "contains": "CLUTTER_FLOW_FORGE" + }, + { + "path": [ + "shared", + "sources", + "forge.change_requests.open_count", + "derived_from" + ], + "equals": [ + "forge.change_requests.inventory" + ] + }, + { + "path": [ + "shared", + "sources", + "forge.change_requests.updated_count_28d", + "derived_from" + ], + "equals": [ + "forge.change_requests.inventory" + ] + }, + { + "path": [ + "shared", + "sources", + "forge.change_requests.stale_open_count_14d", + "derived_from" + ], + "equals": [ + "forge.change_requests.inventory" + ] + }, + { + "path": [ + "shared", + "snapshot", + "aggregate_health" + ], + "absent": true + }, + { + "path": [ + "shared", + "snapshot", + "score" + ], + "absent": true + }, + { + "path": [ + "shared", + "snapshot", + "aggregate" + ], + "absent": true + }, + { + "path": [ + "shared", + "vitals", + "flow", + "band" + ], + "equals": "MOVING" + }, + { + "path": [ + "shared", + "vitals", + "pulse", + "band" + ], + "equals": "QUIET" + }, + { + "path": [ + "shared", + "vitals", + "clutter", + "band" + ], + "equals": "LIGHT" + } + ] + }, + { + "case": "T8", + "scenario": "multi-vital.v1", + "given": { + "inventories": { + "git.default_branch.commits_28d": [ + { + "sha": "r", + "committed_at": "2026-09-04T00:00:00Z" + } + ], + "forge.change_requests.inventory": [ + { + "number": 1, + "state": "OPEN", + "created_at": "2026-09-01T00:00:00Z", + "updated_at": "2026-09-04T00:00:00Z", + "merged_at": null + }, + { + "number": 2, + "state": "CLOSED", + "created_at": "2026-09-02T00:00:00Z", + "updated_at": "2026-09-04T00:00:00Z", + "merged_at": "2026-09-04T00:00:00Z" + } + ], + "forge.issues.inventory": [ + { + "number": 1, + "state": "OPEN", + "created_at": "2026-07-01T00:00:00Z", + "updated_at": "2026-07-01T00:00:00Z" + } + ], + "git.nondefault_branches.inventory": [] + }, + "observation_id": "forge.change_requests.open_count", + "variants": [ + { + "name": "FORBIDDEN", + "status": "FORBIDDEN", + "freshness": "FRESH" + }, + { + "name": "UNKNOWN", + "status": "UNKNOWN", + "freshness": "FRESH" + }, + { + "name": "ERROR", + "status": "ERROR", + "freshness": "FRESH" + }, + { + "name": "PARTIAL", + "status": "PARTIAL", + "freshness": "FRESH" + }, + { + "name": "STALE", + "status": "AVAILABLE", + "freshness": "STALE" + } + ] + }, + "checks": [ + { + "path": [ + "FORBIDDEN", + "vitals", + "flow", + "evaluation_status" + ], + "equals": "UNKNOWN" + }, + { + "path": [ + "FORBIDDEN", + "vitals", + "flow", + "band" + ], + "equals": null + }, + { + "path": [ + "FORBIDDEN", + "vitals", + "clutter", + "evaluation_status" + ], + "equals": "UNKNOWN" + }, + { + "path": [ + "FORBIDDEN", + "vitals", + "clutter", + "band" + ], + "equals": null + }, + { + "path": [ + "UNKNOWN", + "vitals", + "flow", + "evaluation_status" + ], + "equals": "UNKNOWN" + }, + { + "path": [ + "UNKNOWN", + "vitals", + "flow", + "band" + ], + "equals": null + }, + { + "path": [ + "UNKNOWN", + "vitals", + "clutter", + "evaluation_status" + ], + "equals": "UNKNOWN" + }, + { + "path": [ + "UNKNOWN", + "vitals", + "clutter", + "band" + ], + "equals": null + }, + { + "path": [ + "ERROR", + "vitals", + "flow", + "evaluation_status" + ], + "equals": "UNKNOWN" + }, + { + "path": [ + "ERROR", + "vitals", + "flow", + "band" + ], + "equals": null + }, + { + "path": [ + "ERROR", + "vitals", + "clutter", + "evaluation_status" + ], + "equals": "UNKNOWN" + }, + { + "path": [ + "ERROR", + "vitals", + "clutter", + "band" + ], + "equals": null + }, + { + "path": [ + "PARTIAL", + "vitals", + "flow", + "evaluation_status" + ], + "equals": "UNKNOWN" + }, + { + "path": [ + "PARTIAL", + "vitals", + "flow", + "band" + ], + "equals": null + }, + { + "path": [ + "PARTIAL", + "vitals", + "clutter", + "evaluation_status" + ], + "equals": "UNKNOWN" + }, + { + "path": [ + "PARTIAL", + "vitals", + "clutter", + "band" + ], + "equals": null + }, + { + "path": [ + "STALE", + "vitals", + "flow", + "evaluation_status" + ], + "equals": "UNKNOWN" + }, + { + "path": [ + "STALE", + "vitals", + "flow", + "band" + ], + "equals": null + }, + { + "path": [ + "STALE", + "vitals", + "clutter", + "evaluation_status" + ], + "equals": "UNKNOWN" + }, + { + "path": [ + "STALE", + "vitals", + "clutter", + "band" + ], + "equals": null + } + ] + }, + { + "case": "ART-05", + "scenario": "bundle.v1", + "given": { + "operation": "member-forbidden" + }, + "checks": [ + { + "path": [ + "access" + ], + "equals": "FORBIDDEN" + }, + { + "path": [ + "verified" + ], + "equals": false + } + ] + }, + { + "case": "ART-08", + "scenario": "bundle.v1", + "given": { + "operation": "canonical" + }, + "checks": [ + { + "path": [ + "same_bytes" + ], + "equals": true + }, + { + "path": [ + "same_digest" + ], + "equals": true + }, + { + "path": [ + "scalar_differs" + ], + "equals": true + }, + { + "path": [ + "type_differs" + ], + "equals": true + } + ] + }, + { + "case": "ART-09", + "scenario": "bundle.v1", + "given": { + "operation": "receipt" + }, + "checks": [ + { + "path": [ + "original_verified" + ], + "equals": true + }, + { + "path": [ + "missing_receipt_verified" + ], + "equals": false + }, + { + "path": [ + "references_resolve" + ], + "equals": true + }, + { + "path": [ + "receipt_binding_rejected" + ], + "equals": true + } + ] + }, + { + "case": "ART-10", + "scenario": "bundle.v1", + "given": { + "operation": "public" + }, + "checks": [ + { + "path": [ + "verified" + ], + "equals": true + }, + { + "path": [ + "report_present" + ], + "equals": true + } + ] + }, + { + "case": "ART-11", + "scenario": "bundle.v1", + "given": { + "operation": "private" + }, + "checks": [ + { + "path": [ + "same_id" + ], + "equals": true + }, + { + "path": [ + "same_members" + ], + "equals": true + }, + { + "path": [ + "verified" + ], + "equals": true + } + ] + }, + { + "case": "ART-15", + "scenario": "bundle.v1", + "given": { + "operation": "collision" + }, + "checks": [ + { + "path": [ + "collision" + ], + "equals": true + }, + { + "path": [ + "unchanged" + ], + "equals": true + } + ] + }, + { + "case": "RPT-4", + "scenario": "history-store.v1", + "given": { + "operation": "idempotent" + }, + "checks": [ + { + "path": [ + "same_path" + ], + "equals": true + }, + { + "path": [ + "same_index" + ], + "equals": true + }, + { + "path": [ + "collision" + ], + "equals": true + } + ] + }, + { + "case": "RPT-5", + "scenario": "history-store.v1", + "given": { + "operation": "failed-commit" + }, + "checks": [ + { + "path": [ + "failed" + ], + "equals": true + }, + { + "path": [ + "latest_preserved" + ], + "equals": true + }, + { + "path": [ + "first_verified" + ], + "equals": true + } + ] + }, + { + "case": "RPT-6", + "scenario": "history-store.v1", + "given": { + "operation": "missing-predecessor" + }, + "checks": [ + { + "path": [ + "comparison" + ], + "equals": "HISTORY_GAP" + }, + { + "path": [ + "snapshot_present" + ], + "equals": true + }, + { + "path": [ + "transitions" + ], + "equals": [ + "INCOMPARABLE" + ] + } + ] + }, + { + "case": "RPT-8", + "scenario": "history-store.v1", + "given": { + "operation": "forbidden" + }, + "checks": [ + { + "path": [ + "comparison" + ], + "equals": "HISTORY_GAP" + }, + { + "path": [ + "previous_preserved" + ], + "equals": true + }, + { + "path": [ + "forbidden_explicit" + ], + "equals": true + } + ] + }, + { + "case": "RPT-9", + "scenario": "history-store.v1", + "given": { + "operation": "two-writers" + }, + "checks": [ + { + "path": [ + "conflict" + ], + "equals": true + }, + { + "path": [ + "checkpoint_latest_a" + ], + "equals": true + }, + { + "path": [ + "immutable_candidates_preserved" + ], + "equals": true + }, + { + "path": [ + "retry_uses_a" + ], + "equals": true + }, + { + "path": [ + "latest_retry" + ], + "equals": true + }, + { + "path": [ + "locked_writer_refused" + ], + "equals": true + } + ] + } + ] +} diff --git a/tests/surface_bridge.py b/tests/surface_bridge.py new file mode 100644 index 0000000..2b5a09b --- /dev/null +++ b/tests/surface_bridge.py @@ -0,0 +1,287 @@ +"""Version-pinned bridge to isolated, self-contained conformance scenarios. + +No case is a pointer to a test name: declared inputs execute the operation and +every literal/negative assertion is checked against its returned checkpoints. +""" + +from copy import deepcopy +import json +from pathlib import Path +from unittest.mock import patch + +from devostasis import canonical +from devostasis.adapters import github_ci, gitlab_ci +from devostasis.bundle import verify_dir, verify_members +from devostasis.config import single_project +from devostasis.history import FilesystemHistoryStore, HistoryConflictError, ImmutabilityError +from devostasis.observations import Observation +from devostasis.runner import build_from_observations +from devostasis.vitals import build_snapshot, integrity +from helpers import add, finalize, full_inputs, integrity_inputs, obs_set + +CONTRACT = "devostasis.conformance-bridge.v1" +BUNDLE_LINEAGE = {"schema": "devostasis.manifest.v2", "artifact_contract_version": "devostasis.bundle.v3", + "bundle_identity_contract": "PV-BUNDLE-ID-003", "receipt_identity_contract": "PV-RECEIPT-IDENTITY-003"} + + +def shape(value, keys): + if not isinstance(value, dict) or set(value) != set(keys): + raise ValueError(f"expected fields {sorted(keys)}") + + +def exact_bundle_lineage(value): + if any(value.manifest.get(k) != v for k, v in BUNDLE_LINEAGE.items()): + raise ValueError("bridge does not support this exact canonical bundle lineage") + + +def native(given, root): + shape(given, ("variants",)) + result = {} + for row in given["variants"]: + shape(row, ("name", "provider", "mapping", "status", "conclusion")) + if row["name"] in result: raise ValueError("duplicate variant") + if row["provider"] == "github" and row["mapping"] == github_ci.OUTCOME_MAP_VERSION: + outcome = github_ci.normalize_outcome(row["status"], row["conclusion"]) + elif row["provider"] == "gitlab" and row["mapping"] == gitlab_ci.OUTCOME_MAP_VERSION: + if row["conclusion"] is not None: raise ValueError("GitLab has no separate conclusion") + outcome = gitlab_ci.normalize_outcome(row["status"]) + else: + raise ValueError("unsupported exact provider normalization lineage") + # Feed normalized outcomes into the real revision composition too. + result[row["name"]] = {"outcome": outcome, + "revision_verdict": github_ci.compose_current([outcome]), + "decisive": int(outcome in ("VERIFY_PASS", "VERIFY_FAIL")), + "failed": int(outcome == "VERIFY_FAIL")} + return result + + +def independence(given, root): + shape(given, ("attempts", "sample_semantics")) + states = given["attempts"] + if not isinstance(states, list) or not states: raise ValueError("attempts required") + attempts = [{"run_attempt": n + 1, "status": "completed", "conclusion": value} for n, value in enumerate(states)] + run = {"id": 700, **attempts[-1]} + records = github_ci.build_revision_records([{"sha": "one-immutable-revision", "committed_at": "2026-09-04T00:00:00Z"}], + {"one-immutable-revision": [github_ci.actions_parent(run, attempts[:-1])]}) + obs = obs_set(); integrity_inputs(obs, True, records) + add(obs, "ci.sample_semantics", given["sample_semantics"], "enum") + result = integrity.evaluate(obs).to_dict() + return {"canonical_revision_count": len(records), "result": result} + + +def multivital(given, root): + shape(given, ("observation_id", "variants", "inventories")) + results = {} + for variant in given["variants"]: + shape(variant, ("name", "status", "freshness")) + if variant["name"] in results: raise ValueError("duplicate variant") + from devostasis.normalize import derive + obs = obs_set() + allowed = {"git.default_branch.commits_28d", "forge.change_requests.inventory", + "forge.issues.inventory", "git.nondefault_branches.inventory"} + if set(given["inventories"]) != allowed: raise ValueError("shared inventories must be explicit") + record_keys = {"git.default_branch.commits_28d": {"sha", "committed_at"}, + "forge.change_requests.inventory": {"number", "state", "created_at", "updated_at", "merged_at"}, + "forge.issues.inventory": {"number", "state", "created_at", "updated_at"}, + "git.nondefault_branches.inventory": {"name", "committed_at"}} + for oid, rows in given["inventories"].items(): + if not isinstance(rows, list): raise ValueError("inventory must be a list") + for row in rows: shape(row, record_keys[oid]) + add(obs, oid, deepcopy(rows), "series", coverage={"complete": True, "open_complete": True, "window_complete": True}) + derive(obs, single_project("acme/widget", config_version="1")) + finalize(obs) + if given["observation_id"] is not None: + old = obs.get(given["observation_id"]) + if old is None: raise ValueError("unknown substituted observation") + value = old.value if variant["status"] in ("AVAILABLE", "PARTIAL") else None + obs.replace(Observation(**{**old.to_dict(), "value": value, "status": variant["status"], "freshness": variant["freshness"]})) + finalize(obs) + elif variant != {"name": "shared", "status": "AVAILABLE", "freshness": "FRESH"}: + raise ValueError("shared graph does not substitute evidence") + snapshot = build_snapshot(obs) + results[variant["name"]] = {"snapshot": snapshot, + "vitals": {r["vital_id"]: r for r in snapshot["vitals"]}, + "sources": {item.observation_id: item.evidence_ref for item in obs if item.evidence_ref}} + return results + + +def bundle(given, root): + shape(given, ("operation",)) + store = FilesystemHistoryStore(root) + project = single_project("acme/widget", config_version="1") + value = build_from_observations(project, full_inputs(obs_set()), store) + exact_bundle_lineage(value) + path = store.commit(value) + before = dict(value.members) + op = given["operation"] + if op == "canonical": + a = canonical.loads('{"a":1,"b":[true,null]}') + b = canonical.loads(' { "b" : [true, null], "a" : 1 } ') + return {"same_bytes": canonical.canonical_bytes(a) == canonical.canonical_bytes(b), + "same_digest": canonical.digest(a) == canonical.digest(b), + "scalar_differs": canonical.digest(a) != canonical.digest({"a": 2, "b": [True, None]}), + "type_differs": canonical.digest(a) != canonical.digest({"a": True, "b": [True, None]})} + if op == "member-forbidden": + original = Path.read_bytes + def read(file): + if file == path / "snapshot.json": raise PermissionError("FORBIDDEN") + return original(file) + with patch.object(Path, "read_bytes", read): + try: problems = verify_dir(path) + except PermissionError: return {"access": "FORBIDDEN", "verified": False} + return {"access": "FORBIDDEN", "verified": not problems} + if op == "receipt": + content = dict(value.members); content.pop("source-receipts.json", None) + # Current lineage persists receipt identity in the observation member. + observations = canonical.loads(content["observations.json"].decode()) + receipt = observations["receipt_identity"] + resolved = all(item["observation_id"] in receipt["requested_keys"] + and item["observation_id"] in receipt["per_key"] for item in observations["observations"] + if item["provider"] != "history") + carrier = next(i for i in observations["observations"] if i["observation_id"] == "ci.revision_history_carried") + # The internal carrier resolves through its own identity-bearing lineage, + # not through a provider collection receipt that predates attachment. + resolved = resolved and carrier["status"] == "UNAVAILABLE" and carrier["reason_code"] == "NO_PREVIOUS_BUNDLE" + observations["receipt_identity"] = None + content["observations.json"] = canonical.canonical_bytes(observations) + problems = verify_members(content) + return {"original_verified": verify_members(before) == [], "references_resolve": resolved, + "missing_receipt_verified": problems == [], + "receipt_binding_rejected": any("RECEIPT_COPY_MISMATCH" in p for p in problems)} + if op == "public": + # Verification consumes the persisted members, with all network disabled. + with patch("urllib.request.urlopen", side_effect=AssertionError("network dependency")): + return {"verified": verify_dir(path) == [], "report_present": bool((path / "report.md").read_bytes())} + if op == "private": + (root / "private-calibration.json").write_text('{"untrusted":"private"}', encoding="utf-8") + (root / "SNAP.txt").write_text("convenience", encoding="utf-8") + rebuilt = build_from_observations(project, full_inputs(obs_set()), FilesystemHistoryStore(root / "isolated")) + return {"same_id": rebuilt.bundle_id == value.bundle_id, "same_members": rebuilt.members == before, + "verified": verify_dir(path) == []} + if op == "collision": + value.members["snapshot.json"] += b" " + try: store.put_immutable(value) + except ImmutabilityError: + return {"collision": True, "unchanged": all((path / name).read_bytes() == data for name, data in before.items())} + return {"collision": False, "unchanged": False} + raise ValueError("unsupported bundle operation") + + +def history(given, root): + shape(given, ("operation",)) + store = FilesystemHistoryStore(root) + project = single_project("acme/widget", config_version="1") + first = build_from_observations(project, full_inputs(obs_set()), store) + exact_bundle_lineage(first) + path = store.commit(first) + key = first.project_key + second = build_from_observations(project, full_inputs(obs_set("2026-09-06T12:00:00Z")), store) + op = given["operation"] + if op == "idempotent": + original = store.index_path(key).read_bytes() + store.commit(first) + second_path = store.put_immutable(first) + first.members["snapshot.json"] += b" " + try: store.put_immutable(first) + except ImmutabilityError: collision = True + else: collision = False + return {"same_path": path == second_path, "same_index": original == store.index_path(key).read_bytes(), "collision": collision} + if op == "failed-commit": + with patch.object(store, "put_immutable", side_effect=OSError("incomplete immutable commit")): + try: store.commit(second) + except OSError: failed = True + else: failed = False + return {"failed": failed, "latest_preserved": store.latest(key, "123456").bundle_id == first.bundle_id, + "first_verified": verify_dir(path) == []} + if op == "missing-predecessor": + (path / "snapshot.json").unlink() + current = build_from_observations(project, full_inputs(obs_set("2026-09-06T12:00:00Z")), store) + snapshot = canonical.loads(current.members["snapshot.json"].decode()) + delta = canonical.loads(current.members["delta.json"].decode()) + return {"comparison": current.manifest["comparison_status"], "snapshot_present": len(snapshot["vitals"]) == 7, + "transitions": sorted(set(r["transition_class"] for r in delta["vitals"]))} + if op == "forbidden": + from devostasis import history as history_module + original = canonical.load_file + def read(file, *args, **kwargs): + if file == store.index_path(key): raise PermissionError("FORBIDDEN") + return original(file, *args, **kwargs) + with patch.object(history_module.canonical, "load_file", read): + current = build_from_observations(project, full_inputs(obs_set("2026-09-06T12:00:00Z")), store) + state = store.latest(key, "123456") + return {"comparison": current.manifest["comparison_status"], "forbidden_explicit": any("HISTORY_STORE_FORBIDDEN" in p for p in state.problems), + "previous_preserved": verify_dir(path) == []} + if op == "two-writers": + # Both writers have already read first. Force writer A to commit before + # writer B, then inspect the stale writer and its immutable candidate. + third = build_from_observations(project, full_inputs(obs_set("2026-09-07T12:00:00Z")), store) + from devostasis.workscope.bundle import locked + with locked(root / ".history-store-lock"): + try: store.commit(second) + except HistoryConflictError: locked_writer_refused = True + else: locked_writer_refused = False + a_path = store.commit(second) + try: store.commit(third) + except HistoryConflictError: conflict = True + else: conflict = False + checkpoint = store.latest(key, "123456").bundle_id == second.bundle_id + candidate = store.history_dir(third) + rebuilt = build_from_observations(project, full_inputs(obs_set("2026-09-07T12:00:00Z")), store) + b_path = store.commit(rebuilt) + return {"conflict": conflict, "locked_writer_refused": locked_writer_refused, "checkpoint_latest_a": checkpoint, + "immutable_candidates_preserved": all(verify_dir(p) == [] for p in (path, a_path, candidate, b_path)), + "retry_uses_a": rebuilt.manifest["previous_bundle_id"] == second.bundle_id, + "latest_retry": store.latest(key, "123456").bundle_id == rebuilt.bundle_id} + raise ValueError("unsupported history operation") + + +SCENARIOS = {"ci-native.v1": native, "independence.v1": independence, + "multi-vital.v1": multivital, "bundle.v1": bundle, "history-store.v1": history} + + +def load(path): + document = canonical.loads(Path(path).read_text(encoding="utf-8")) + shape(document, ("schema", "source", "cases")) + if document["schema"] != CONTRACT: raise ValueError("unknown bridge lineage") + if not isinstance(document["source"], str) or not document["source"]: raise ValueError("source required") + seen = set() + for case in document["cases"]: + shape(case, ("case", "scenario", "given", "checks")) + if not isinstance(case["case"], str) or case["case"] in seen: raise ValueError("duplicate or invalid case") + seen.add(case["case"]) + if case["scenario"] not in SCENARIOS: raise ValueError("unknown scenario lineage") + if not isinstance(case["given"], dict) or not isinstance(case["checks"], list) or not case["checks"]: + raise ValueError("scenario input and checks required") + for check in case["checks"]: + if not isinstance(check, dict) or set(check) not in ({"path", "equals"}, {"path", "not_equals"}, {"path", "contains"}, {"path", "absent"}): + raise ValueError("unknown assertion") + if not isinstance(check["path"], list) or not all(type(p) in (str, int) for p in check["path"]): + raise ValueError("invalid assertion path") + if "absent" in check and check["absent"] is not True: raise ValueError("absent must be true") + if case["case"] == "T8": + required = {(s, "FRESH") for s in ("FORBIDDEN", "UNKNOWN", "ERROR", "PARTIAL")} | {("AVAILABLE", "STALE")} + variants = case["given"].get("variants", []) + if len(variants) != 5 or {(v.get("status"), v.get("freshness")) for v in variants} != required: + raise ValueError("T8 requires every independent evidence-state substitution") + return document["cases"] + + +def execute(case, root): + actual = SCENARIOS[case["scenario"]](deepcopy(case["given"]), root) + for check in case["checks"]: + node, exists = actual, True + for part in check["path"]: + try: node = node[part] + except (KeyError, IndexError, TypeError): exists = False; break + where = f"{case['case']}:{check['path']}" + if "absent" in check: + assert not exists, f"{where}: forbidden field exists" + continue + assert exists, f"{where}: unknown checkpoint/assertion path" + if "equals" in check: + assert canonical.canonical_bytes(node) == canonical.canonical_bytes(check["equals"]), f"{where}: expected {check['equals']!r}, got {node!r}" + if "not_equals" in check: + assert canonical.canonical_bytes(node) != canonical.canonical_bytes(check["not_equals"]), f"{where}: forbidden value {node!r}" + if "contains" in check: + assert check["contains"] in node, f"{where}: missing {check['contains']!r}" + return actual diff --git a/tests/test_conformance_surface.py b/tests/test_conformance_surface.py new file mode 100644 index 0000000..b56588f --- /dev/null +++ b/tests/test_conformance_surface.py @@ -0,0 +1,67 @@ +"""Executable capability acceptance and fail-closed bridge regression checks.""" + +from copy import deepcopy +from pathlib import Path + +import pytest + +from devostasis import canonical, vectors +from surface_bridge import execute, load + +CASES = Path(__file__).parent / "fixtures" / "conformance-surface.json" + + +@pytest.mark.parametrize("case", load(CASES), ids=lambda c: c["case"]) +def test_conformance_surface(case, tmp_path): + execute(case, tmp_path) + + +def test_bridge_and_v1_do_not_duplicate_permanent_ids(): + old = {v.case for v in vectors.load([Path(__file__).parent / "vectors"])} + assert not old.intersection(c["case"] for c in load(CASES)) + + +@pytest.mark.parametrize("mutation", ["schema", "scenario", "check", "case", "extra"]) +def test_bridge_unknown_and_duplicate_lineage_never_skips(tmp_path, mutation): + document = canonical.loads(CASES.read_text(encoding="utf-8")) + if mutation == "schema": document["schema"] += "+future" + if mutation == "scenario": document["cases"][0]["scenario"] += "+future" + if mutation == "check": document["cases"][0]["checks"][0]["ignored"] = True + if mutation == "case": document["cases"].append(document["cases"][0]) + if mutation == "extra": document["ignored"] = True + file = tmp_path / "bad.json"; file.write_bytes(canonical.canonical_bytes(document)) + with pytest.raises(ValueError): load(file) + + +def test_bridge_consumes_negative_assertions_and_wrong_expectations(tmp_path): + case = deepcopy(next(c for c in load(CASES) if c["case"] == "ART-08")) + case["checks"][0]["equals"] = False + with pytest.raises(AssertionError, match="expected"): execute(case, tmp_path) + + +def test_unknown_input_and_assertion_paths_cannot_pass(tmp_path): + case = deepcopy(next(c for c in load(CASES) if c["case"] == "R5")) + case["given"]["ignored"] = True + with pytest.raises(ValueError): execute(case, tmp_path) + case["given"].pop("ignored") + case["checks"] = [{"path": ["not-a-checkpoint"], "equals": None}] + with pytest.raises(AssertionError, match="unknown checkpoint"): execute(case, tmp_path) + + +def test_t8_cannot_drop_a_required_substitution(tmp_path): + document = canonical.loads(CASES.read_text(encoding="utf-8")) + case = next(c for c in document["cases"] if c["case"] == "T8") + case["given"]["variants"].pop() + file = tmp_path / "incomplete.json"; file.write_bytes(canonical.canonical_bytes(document)) + with pytest.raises(ValueError, match="every independent"): load(file) + + +@pytest.mark.parametrize("status,freshness,value", [("UNKNOWN", "FRESH", None), ("AVAILABLE", "STALE", "RAW_RUNS"), ("AVAILABLE", "FRESH", "FUTURE")]) +def test_independence_provenance_is_consumed_fail_closed(status, freshness, value): + from helpers import add, obs_set, integrity_inputs, passing_revisions + from devostasis.vitals.integrity import evaluate + obs = obs_set(); integrity_inputs(obs, True, passing_revisions(4)) + add(obs, "ci.sample_semantics", value, "enum", status=status, freshness=freshness) + result = evaluate(obs) + assert result.evaluation_status == "UNKNOWN" and result.band is None + assert "CI_SAMPLE_INDEPENDENCE_UNKNOWN" in result.diagnostics From 364c44fd664307520e3154665f233f2deb858174 Mon Sep 17 00:00:00 2001 From: Unisphere Dev Date: Wed, 21 Oct 2026 16:16:02 +0300 Subject: [PATCH 09/11] docs(release): publish acceptance and review gates through 0.6 --- .devostasis/debt.json | 11 +-- CHANGELOG.md | 21 ++++- README.md | 5 ++ ROADMAP.md | 17 ++-- docs/release-acceptance.md | 133 +++++++++++++++++++++++++++++++ docs/spec/PROVENANCE.md | 9 ++- docs/spec/README.md | 1 + docs/spec/conformance-surface.md | 71 +++++++++++++++++ docs/spec/conformance.md | 65 ++++++++++++--- docs/spec/history-and-reports.md | 12 +++ docs/spec/integrity-ci.md | 11 +++ docs/spec/observations.md | 8 ++ docs/spec/vitals.md | 11 +++ tests/test_spec_drift.py | 8 ++ 14 files changed, 351 insertions(+), 32 deletions(-) create mode 100644 docs/release-acceptance.md create mode 100644 docs/spec/conformance-surface.md diff --git a/.devostasis/debt.json b/.devostasis/debt.json index 78883ee..7c5587e 100644 --- a/.devostasis/debt.json +++ b/.devostasis/debt.json @@ -5,10 +5,11 @@ { "id": "D-10", "title": "Native change hotspots and local roots rely on recorded acquisition rather than complete ancestry proofs", - "state": "open", + "state": "closed", "opened": "2026-10-08", - "updated": "2026-10-08", - "cost": "0.6 source proofs bind pinned commit/tree and admitted blob bytes, but replay of recorded history counts/root traversal is not independent admission of their complete ancestry. A rehashed recorded history can alter hotspots. Counts remain descriptive and separately covered, with no health or acceptance authority. Bounded historic object/parent proof and preserved v1 replay are issue #73." + "updated": "2026-10-21", + "cost": "Implemented in the pending 0.6 assurance increment: code-analysis v2 authenticates bounded commit/parent/tree/blob proofs and independently derives roots, selection, completeness and edit counts offline. Rehashed claims are rejected; caps/shallow/missing objects remain explicit PARTIAL. V1 exact bytes/replay retain their documented acquisition-only limit. Independent PR review and maintainer release remain pending; issue #73.", + "closed": "2026-10-21" }, { "id": "D-7", @@ -48,8 +49,8 @@ "title": "Conformance cases named in the specification have no executable fixture", "state": "open", "opened": "2026-09-05", - "updated": "2026-10-02", - "cost": "docs/spec/conformance.md named T2-T9, R1-R53, ART-05, ART-08-ART-11, ART-15 and RPT-4-RPT-9 with no test behind them, so the public coverage claim is larger than the proof. The vector format and runner exist since 0.1.8, and 0.1.9 transcribed the accepted exact vectors the research process delivered (T2, R1, R2, R3, R4, T4, T5, T7) together with whole accepted families (INT-UNKNOWN, INT-TOTAL, CLU-INCOMPLETE, ACT-COV). The pending 0.2.0 branch holds T9 to its accepted table with a generator, leaving sixty-two named cases without executable proof. Several (T3, T6, T8, the bundle and store cases) need further conformance capabilities or accepted vectors (issues #20, #23)." + "updated": "2026-10-21", + "cost": "0.6 publishes a versioned operation bridge with 20 permanent implementation fixtures: R5-R10 native normalization, T3/T6/T8, ART-05/08/09/10/11/15 and RPT-4/5/6/8/9. Nineteen address the former list of 62; R11-R53 (43 cases) remain without exact materialization. Concrete surface and whole-case exact-fixture independent review remain pending, including downstream R5-R10 fidelity. B1 is open; capability acceptance is not fixture acceptance. T9 cell-key delivery remains separate." }, { "id": "D-2", diff --git a/CHANGELOG.md b/CHANGELOG.md index bd79b63..36fc0a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,20 @@ contract or a policy always come with a version bump of that contract. ## 0.6.0 (unreleased) -Offline Repository Analysis, target E4, issue #72. +Offline Repository Analysis and executable assurance, targets E4/B1, issues #72/#73. + +- Default native analysis v2 carries bounded authenticated commit/parent/tree/blob + history proof; offline replay derives roots, window membership, edit counts and + completeness. Merge ancestry, clock skew, shallow/missing objects and resource + caps remain explicit. V1 bundle/packet/example bytes retain exact replay. +- Publish a versioned source-checkout conformance operation bridge with 20 permanent + implementation fixtures, consumed RAW_RUNS provenance, shared multi-Vital evidence, + all T8 substitutions, canonical bundle and actual HistoryStore checkpoints. +- Serialize canonical store publication and reject stale advancing predecessors + with explicit retry conflict while preserving immutable candidates. Store index + permission denial remains FORBIDDEN/HISTORY_GAP. +- Add per-version 0.2–0.6 acceptance ledger and independent review/release gates; + B1 exact research acceptance and sustained external B7 evidence remain open. - Release-readiness repairs: reject ambiguous/malformed current CI inventory before it can remove carried failures; preserve immutable revision time; @@ -35,9 +48,9 @@ Offline Repository Analysis, target E4, issue #72. GitHub totals while preserving observed partial rows (#70). - Isolate inherited Git context and unrelated credential environment variables from local analysis subprocesses and synthetic diagnostics (#74). -- Independent ancestry/count proof remains documented debt (#73); external - scheduling/adoption is deferred (#71). Existing Vitals, core/work replay, - B1/B7 and upstream release/review gates retain their contracts. +- Independent ancestry/count proof is implemented under the separate v2 lineage + (#73); v1 retains its historical limits. External scheduling/adoption remains + deferred (#71), and B1/B7 and upstream release/review gates remain explicit. ## 0.5.0 (unreleased) diff --git a/README.md b/README.md index 980dc80..6c85ae2 100644 --- a/README.md +++ b/README.md @@ -369,3 +369,8 @@ than patched ad hoc. ## License [MIT](LICENSE). + +The 0.6 release candidate adds independently replayable Git history proof and a +versioned conformance operation bridge. See the [0.2–0.6 acceptance ledger](docs/release-acceptance.md) +for implementation checks, historical compatibility and pending independent +review/release gates. Existing published pins remain unchanged until release. diff --git a/ROADMAP.md b/ROADMAP.md index dba863e..7e8bfab 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -7,8 +7,8 @@ started and work that cannot start. ## Release state and next steps — 2026-10-21 The project is at **release stabilization**, with Phase A closed and Phase B -five of seven items implemented. B1 still has 62 named cases without executable -proof, and B7 still needs sustained outside-consumer evidence. The latest +five of seven items implemented. B1 now has 20 operation-bridge implementation fixtures (19 from the former +62-case gap); R11–R53 and independent whole-case fixture acceptance remain open, and B7 still needs sustained outside-consumer evidence. The latest published version is **v0.1.9**; 0.2.0 through 0.6.0 remain unreleased. The audit began with a clean working tree. All local and fetched remote heads @@ -52,14 +52,15 @@ on #57/#75 or approve a later repaired head. (#20/#23) and independently proved source-history ancestry/counts (#73). Keep partial coverage, historical compatibility and evidence limits visible. -### Proposed next major code increment +### Assurance delivered within the 0.6 release candidate -After releasing 0.6, the highest-impact code-only increment is **independently -verifiable history and executable assurance**: bounded commit/parent/tree +The owner brought **independently verifiable history and executable assurance** +into 0.6 rather than a later release: bounded commit/parent/tree proofs for #73, hostile rehashed-history controls, and the missing executable conformance boundaries for B1. This strengthens the proof objects on which -both work routing and native hotspots depend. It is a proposal, not acceptance -of a new research contract, a new release number, or completion of B1. +both work routing and native hotspots depend. The v2 proof and versioned operation bridge are implemented; concrete-surface, +exact-fixture and release review remain pending. The [acceptance ledger](docs/release-acceptance.md) +tracks every prepared version. This does not complete B1 or publish a release. The larger product milestone remains a real outside consumer completing work from verified scopes with retained results and calendar history. Deployment @@ -88,7 +89,7 @@ completion, without external deployment or independent acceptance claims. Debt found during this increment: contradictory pagination completeness (#70, fixed with regressions), and independently verifiable history/root/count proof -(#73, currently recorded acquisition observations). Protected scheduled consumer +(#73, implemented as v2 in the assurance increment; v1 remains recorded observations). Protected scheduled consumer adoption (#71) is deferred by the owner. Inherited local Git context and excess credential environment forwarding were isolated and regression-tested (#74). Native language expansion and calibrated diff --git a/docs/release-acceptance.md b/docs/release-acceptance.md new file mode 100644 index 0000000..c27ab6d --- /dev/null +++ b/docs/release-acceptance.md @@ -0,0 +1,133 @@ +# Release acceptance through 0.6 + +This is the cumulative acceptance ledger for the prepared 0.2–0.6 increments. +They remain unreleased until maintainer merges/tags under CONTRIBUTING.md. +The latest published tag observed in the October 21 audit was v0.1.9. +Implementation evidence and independent acceptance are separate gates. + +The assurance increment is part of **0.6.0**, not a new release number. It +includes the October 21 readiness repairs and preserves older canonical bundles. +The pull request records final commit/run links; the commands below reproduce +the acceptance from a source checkout without network or private calibration. + +## 0.2.0 — durable core history and incomplete evidence + +- [x] Current malformed/duplicate revision records cannot erase carried failure + or multiply immutable revision samples; timestamps and attempt identities are + admitted before reconciliation. UNKNOWN attempt evidence remains unknown. +- [x] Four-generation store/evaluator chains preserve same-revision failures, + immutable revision time, expiry and exact current/historical replay. +- [x] Horizon, Direction and Debt incomplete-evidence contracts and the accepted + T9 generator execute; unavailable evidence cannot become favorable zero. +- [x] Core bundle verification and example builds remain deterministic. +- [ ] Independent review of the repaired #57 head and associated admission findings. +- [ ] Maintainer resolves the stacked #53 release path and publishes v0.2.0. + +Evidence: `tests/test_ci_history_admission.py`, `tests/test_revision_history.py`, +`tests/conformance/`, core example +verification. The #57 fork matrix passed on Python 3.12/3.13/3.14 at `b849923`. +The broader B1/B7 milestone is still open; this ledger does not silently mark +that historical target achieved. + +## 0.3.0 — Evidence to Action + +- [x] Five deterministic queues, explicit priority/dependencies and bounded + acceptance; observed exact-head evidence gates merge admission. +- [x] SARIF/JUnit/Cobertura/performance evidence retains source/revision/age gaps. +- [x] Bounded handoff, cursors, conditional reads and offline replay execute. +- [x] Missing/forbidden/stale reports cannot satisfy acceptance. +- [ ] Independent release review of #59; maintainer release merge/tag. + +Evidence: `tests/test_workscope.py`, +`tests/test_work_handoff.py`, `examples/work`. The #59 fork matrix passed at +`a41fac6` on Python 3.12/3.13/3.14. + +## 0.4.0 — reproducible consumer handoff + +- [x] Receipt execution mechanics are outside canonical identity; semantic + acquisition distinctions remain identity bearing. +- [x] Exact historical verification precedes compatibility dispatch. Unknown, + mixed, ambiguous or unauthorized nonidentity edges fail closed. +- [x] Mutable branch/change bindings resolve immutable projects and commit + membership before/after source reads; packets bind exact verified bytes. +- [x] Historical work v1 and core lineages retain their own replay paths. +- [ ] Independent review of #64 and receipt/compatibility/consumer boundaries. +- [ ] Maintainer release merge/tag. + +Evidence: `tests/test_receipt_identity.py`, `tests/test_compatibility.py`, +`tests/test_work_handoff.py`, historical examples. The #64 fork matrix passed +at `c98e7ed` on Python 3.12/3.13/3.14. + +## 0.5.0 — Consumer Operations + +- [x] Adopter project/policy/engine bindings reject wrong subjects before + collection or canonical publication; disabled invocations retain negative control. +- [x] Shared request/deadline budgets remain bounded under parallel collection. +- [x] Repeated generations, immutable invocation receipts and calendar-day audit + survive private history export, verification and restore. +- [x] Packet-bound outcomes remain caller reports and grant no independent acceptance. +- [ ] Independent review of #69 and archive/caller/budget boundaries. +- [ ] Maintainer release merge/tag. + +Evidence: `tests/test_work_operations.py`, `examples/work/adoption.json`, +CI archive round trip. The #69 fork matrix passed at `aa5e0e1` on +Python 3.12/3.13/3.14. Actual scheduled outside adoption (#71), collection +qualification (#62), long-term incremental archive scale (#67) and B7 remain +explicit owner/external gates. + +## 0.6.0 — offline analysis and executable assurance + +- [x] Pinned SHA-1/SHA-256 source/tree/blob proofs, Python 3.12 AST/import analysis, + conservative finding comparison, SARIF and bounded verified source packets. +- [x] v2 authenticates reachable raw commits, all parent edges and historical + tree/blob objects; roots, window selection, edit counts and COMPLETE claims + are derived again without Git, a checkout, network or source execution. +- [x] Merge ancestry, clock skew, shallow/missing objects, graph/object/byte/work + caps and unsafe paths retain explicit partial states; incomplete roots cannot + establish comparable repository identity. +- [x] Rehashed root/ancestor/time/count/frontier/complete forgeries, wrong object + kinds and unused proof objects fail. An independent LCS oracle checks the + declared byte-line edit algorithm, including repeated/unterminated lines. +- [x] Legacy `examples/code` regenerates byte for byte. New `examples/code-v2` + and separate schemas replay independently; cross-lineage comparison is refused. +- [x] Twenty permanent conformance IDs execute real native normalization, + consumed RAW_RUNS provenance, one shared multi-Vital graph, all T8 substitutions, + canonical bundle operations and stateful HistoryStore operations. +- [x] HistoryStore publication is locked; a stale advancing writer preserves + candidates, reports conflict and rebuilds from latest. Permission failures + remain explicit HISTORY_GAP. +- [ ] Independent review of Git proof completeness, bounds and algorithm specification. +- [ ] Independent review of the concrete conformance surface and exact fixtures; + capability-level research acceptance alone does not accept this implementation. +- [ ] Independent release/admission review of the final cumulative head, including + the October findings on #75 and the included readiness fixes. +- [ ] Upstream workflow approval where GitHub requires maintainer authorization. +- [ ] Maintainer merges the reviewed feature into release/0.6.0, reconciles the + cumulative #75 release PR, then merges/tags v0.6.0 under CONTRIBUTING.md. + +Reproduce: + +```sh +python -m pytest +devostasis vectors --quiet +python scripts/check_conformance_surface.py +devostasis verify --bundle examples/sample-bundle +python scripts/make_code_examples.py +python scripts/make_code_examples.py --v2 +git diff --exit-code -- examples/code examples/code-v2 +devostasis code verify --bundle examples/code/bundle +devostasis code verify --bundle examples/code-v2/bundle +devostasis code verify-packet --bundle examples/code-v2/bundle --packet examples/code-v2/packet.json +devostasis work verify --bundle examples/code-v2/work-bundle +devostasis work replay --bundle examples/code-v2/work-bundle +python -m pip wheel --no-deps --no-build-isolation . +``` + +CI additionally verifies both work/core examples, deterministic repeated builds +and the operations archive restore on Python 3.12/3.13/3.14. Green checks establish +those executions at the recorded head; they do not complete the unchecked gates. + +B1 retains R11–R53 and independent exact-fixture acceptance. B7 retains sustained +outside-consumer evidence. Legacy v1 history and omitted blob sizes keep their +documented proof limits. No compatibility migration, core GitLab collector, +threshold retuning, aggregate health score or publication is implied by this PR. diff --git a/docs/spec/PROVENANCE.md b/docs/spec/PROVENANCE.md index 33692d3..d4ff501 100644 --- a/docs/spec/PROVENANCE.md +++ b/docs/spec/PROVENANCE.md @@ -8,7 +8,10 @@ normative here. The process is domain-neutral and documented in the | Identifier used by the code | Source unit(s) | Status when implemented | | --- | --- | --- | -| `devostasis.code-analysis.v1`, `devostasis.code-policy.v1`, `devostasis.code-engine.v1`, `devostasis.code-packet.v1`, `devostasis.code-comparison.v1` | implementation-owned E4, issue #72 | local, introduced in 0.6.0; static source evidence without accepted Vital/Instrument judgement; independent history proof is #73 | +| `devostasis.code-analysis.v1`, `devostasis.code-policy.v1`, `devostasis.code-engine.v1`, `devostasis.code-packet.v1`, `devostasis.code-comparison.v1` | implementation-owned E4, issue #72 | local, introduced in 0.6.0; static source evidence without accepted Vital/Instrument judgement; v1 history remains recorded acquisition; v2 supplies independent proof for #73 | +| `devostasis.code-analysis.v2`, `devostasis.code-policy.v2`, `devostasis.code-engine.v2`, `devostasis.code-packet.v2`, `devostasis.code-comparison.v2`, `devostasis.git-history-proof.v1` | implementation-owned #73 assurance increment | local, introduced within 0.6.0; bounded authenticated graph/object proof and independently derived edit counts; separate v1 replay preserved; independent implementation review pending | +| `devostasis.conformance-bridge.v1`, `integrity.bands.v1+ci-unit-004+hist-002+independence-001` | PV-CONFORMANCE-SURFACE-001 / PV-REV-CONFORMANCE-SURFACE-001, accepted capability contract; T3 independence premise | concrete bridge/materialization is implementation-owned and awaits independent review; optional RAW_RUNS provenance is consumed without changing canonical revision identity or default historical rule | +| `devostasis.ci-outcomes.gitlab.v1` | PV-CI-NORM-001 | local offline native status mapping; no core GitLab collector or favorable history completeness is claimed | | `devostasis.work-adoption.v1`, `devostasis.work-invocation.v1`, `devostasis.work-result.v1`, `devostasis.work-history-archive.v1`, `devostasis.work-history-audit.v1` | implementation-owned E3 operational adoption, issues #62/#65 | local, introduced in 0.5.0; separate from immutable work/core canonical members and accepted Instrument/Vital semantics; caller reports grant no independent acceptance or execution authority | | `devostasis.bundle.v3`, `PV-BUNDLE-ID-003`, `PV-RECEIPT-IDENTITY-003`, `devostasis.receipt-identity.v1`, `devostasis.observations.v2`, `devostasis.manifest.v2`, `devostasis.execution-receipt.v1`, `devostasis.render.v5` | cumulative PV-RECEIPT-IDENTITY-001/002/003 | independently accepted by PV-REV-RECEIPT-IDENTITY-003; adopted in 0.4.0 with RECEIPT-ID-01..15,17..21. Literal devostasis schema tokens are local choices; rejected ID-16 is preserved, not reassigned | | `devostasis.contract-compatibility.v1`, `devostasis.compatibility-dispatch-policy.v1` | PV-COMPAT-001/002 | independently accepted by PV-REV-COMPAT-002; adopted in 0.4.0; no accepted production nonidentity edge or migration is declared | @@ -20,7 +23,7 @@ normative here. The process is domain-neutral and documented in the | `flow.bands.v1` | PV-FLOW-EMPTY-QUEUE-001 (PV-CAL-FLOW-001) and PV-FLOW-MERGE-LATENCY-001 (PV-CAL-FLOW-PRECISION-001), both from PV-CAL-002 findings | produced with fixtures; adopted in 0.1.2; the adopted surface was found conformant by the PV-SPEC-001 passes of 2026-09-07 | | `pulse.bands.v1` | PV-PULSE-REQUIRED-LOWER-BOUND-001 (PV-CAL-PULSE-CAP-001, from PV-CAL-003) | produced with fixtures; adopted in 0.1.2; the adopted surface was found conformant by the PV-SPEC-001 passes of 2026-09-07 | | `devostasis.bundle.v2`, `PV-BUNDLE-ID-002`, `PV-EFFECTIVE-CONFIG-001`, `PV-EFFECTIVE-CONFIG-AUTHORITY-001` | PV-ARTIFACT-001..005 (B1 acyclicity, B2 config identity, B3 persisted preimage, B4 stored-config authority); v2 adds gauges.json and demand.json to the identity preimage | PV-ARTIFACT-V1-005 accepted by PV-REV-ARTIFACT-005; ART-23..ART-25 adopted in 0.1.2 | -| history store and reports | PV-REPORT-001 | accepted by PV-REV-REPORT-001 with the non-decisions resolved as documented; RPT-4..RPT-7/RPT-9 remain open implementation work | +| history store and reports | PV-REPORT-001 | accepted by PV-REV-REPORT-001 with the non-decisions resolved as documented; 0.6 adds RPT-4/5/6/8/9 operation fixtures plus publication conflict/permission handling; exact-fixture review remains pending | | `PV-BAND-ORDER-001`, `devostasis.band-order.v1` | PV-BAND-ORDER-001, which supersedes the PV-ORDER-001 research item that asked whether an order could be declared at all | accepted, adoption authorized; adopted in 0.1.8 with cases ORDER-01..15, executable as vectors. Ordering is per Vital only. PV-SPEC-001 reviewed the adoption and found the semantics conformant and the case identifiers reassigned; the repair restored ORDER-01..15 to the meanings the contract's `Required conformance cases` section gives them before 0.1.8 was tagged | | `integrity.bands.v1+ci-unit-004` | PV-REV-INTEGRITY-UNKNOWN-001 (issue #13, REPAIR REQUIRED with INT-UNKNOWN-01..06), PV-REV-TEST-003 (a PARTIAL required series is UNKNOWN), PV-REV-TEST-VECTORS-002 (sample_strength and CI_SPARSE_SAMPLE, issue #21) | accepted; adopted in 0.1.9 as one Integrity rule version. PV-INTEGRITY-TOTALITY-001 (accepted by PV-REV-INT-TOTALITY-001, INT-TOTAL-01..14; 01..06 and 08..13 executable, 07 and 14 need a RAW_RUNS path and a GitLab adapter) is part of the same unreleased version: it replaced the unresolved superset this version first derived mechanically for #12 finding 3, which the review of 2026-09-30 found could omit the band it emitted | | `direction.bands.v2` | PV-REV-DIRECTION-CLOSED-TARGET-001 (calibration finding 9, REPAIR REQUIRED with DIR-CLOSED-01..09), PV-DIRECTION-INCOMPLETE-001 (accepted by PV-REV-DIRECTION-INCOMPLETE-001, DIR-INCOMPLETE-01..16) | accepted; adopted in 0.2.0 as one Direction rule version: state-neutral linkage, LINKED/UNLINKED/UNRESOLVED per change request and the completion-set rule; the open-target count stays derived for presentation only. The acceptance of PV-DIRECTION-INCOMPLETE-001 narrows DIR-CLOSED-04: a positively missing target is unlinked, an unresolvable one unresolved; unreadable linkage evidence no longer fails the project | @@ -32,7 +35,7 @@ normative here. The process is domain-neutral and documented in the | `PV-REV-ACTIVITY-COVERAGE-001` | the judgement on issue #9 | accepted reading 3, runtime unchanged since 0.1.7; ACT-COV-01..05 adopted in 0.1.9 as `activity` vectors | | `clutter.bands.v1`, `PV-CLUTTER-INCOMPLETE-001` | PV-CLUTTER-INCOMPLETE-001 (accepted by PV-REV-CLUTTER-INCOMPLETE-001 with CLU-INCOMPLETE-01..20), PV-ISSUE-026-RECONCILE-001 (the reading of issue #26 under that contract), PV-REV-PR-031-003 (a PARTIAL count proves a floor only with machine-verifiable subset semantics, CLU-PARTIAL-TRUST-01..08) | accepted; adopted in 0.1.9 as one Clutter rule version with the twenty cases executable; the T7 upper-bound path moved under the same rule id; the subset proof (`value_semantics = OBSERVED_SUBSET_COUNT` in a PARTIAL count's coverage) is part of the same rule version, which 0.1.9 introduces and no stored bundle carries yet | | exact vector families T2/R1/R2, R3, R4, T4/T5/T7 | PV-TEST-VECTORS-002/005/004/007, accepted by PV-REV-TEST-VECTORS-002/005/004/007 | transcribed into `tests/vectors/` in 0.1.9 as the accepted expectations; T5 (`PULSE_ISSUE_ONLY_ACTIVITY`) adopted under `pulse.bands.v1` as the accepted vector requires; T7 (`retention_semantics = UNCLASSIFIED` upper bound) adopted under `clutter.bands.v1`, because the Clutter rule had to move for PV-CLUTTER-INCOMPLETE-001 in the same release and the accepted T7 semantics are unchanged | -| `devostasis.vectors.v1` | implementation choice; the research side produced PV-TEST-001 (judged CHANGES_REQUIRED by PV-REV-TEST-001), PV-TEST-002/003 and the final T9 reconciliation PV-TEST-004 (accepted by PV-REV-TEST-004), and delivers exact vectors one family per PV-TEST-VECTORS-00n unit | local: the format and runner are this repository's, so accepted vectors arrive executable rather than needing translation; 0.1.9 adds the `ci` and `activity` kinds and `variants` in answer to the format findings of issues #20 and #23; PV-REV-CONFORMANCE-SURFACE-001 accepted a successor capability contract for the families v1 cannot carry (T3, T6, the bundle and store cases), not yet built here | +| `devostasis.vectors.v1` | implementation choice; the research side produced PV-TEST-001 (judged CHANGES_REQUIRED by PV-REV-TEST-001), PV-TEST-002/003 and the final T9 reconciliation PV-TEST-004 (accepted by PV-REV-TEST-004), and delivers exact vectors one family per PV-TEST-VECTORS-00n unit | local: the format and runner are this repository's, so accepted vectors arrive executable rather than needing translation; 0.1.9 adds the `ci` and `activity` kinds and `variants` in answer to the format findings of issues #20 and #23; PV-REV-CONFORMANCE-SURFACE-001 accepted a successor capability contract for the families v1 cannot carry (T3, T6, the bundle and store cases), implemented as the source-checkout operation bridge in 0.6; concrete-surface and exact-fixture independent review remain pending | | `devostasis.gauge.v1` | implementation-defined normalization submitted for judgement | accepted by PV-REV-GAUGE-001 for presentation and same-Vital ordering; never cross-Vital, never aggregated | | `devostasis.demand.v2` | PV-ROLE-001 (minimal consumer demand interface); v1 defined by the implementation, v2 removes the cross-Vital gauge tie-break required by ROLE-01 | default table, UNRESOLVED semantics and aggregate-free projection accepted by PV-ROLE-001; the v2 repair was found conformant by PV-SPEC-001; predictive validity under calibration in PV-CAL-004, whose measurement the owner reassigned to the implementation side | | `devostasis.fleet.v1` | implementation-defined projection submitted for judgement | accepted by PV-REV-FLEET-001 (FLEET-01..10): `aggregate` and `cross_project_order` stay exactly null, no accepted contract defines a cross-project order | diff --git a/docs/spec/README.md b/docs/spec/README.md index b51a2c8..4186ee7 100644 --- a/docs/spec/README.md +++ b/docs/spec/README.md @@ -17,6 +17,7 @@ conformance tests. | [registers.md](registers.md) | Targets and debt register files, the change-request link marker | | [github-adapter.md](github-adapter.md) | What the GitHub adapter collects, how it maps failures and caps, what it does not collect | | [conformance.md](conformance.md) | Conformance case identifiers and where each is implemented | +| [conformance-surface.md](conformance-surface.md) | Versioned offline native normalization, multi-Vital, canonical bundle and HistoryStore operation bridge | | [vectors.md](vectors.md) | The executable conformance vector format and its runner | | [work-scope.md](work-scope.md) | Separate consumer work-scope v1, five queues, replay, history and handoff | | [work-evidence.md](work-evidence.md) | Optional revision-bound SARIF, JUnit, Cobertura and performance profiles | diff --git a/docs/spec/conformance-surface.md b/docs/spec/conformance-surface.md new file mode 100644 index 0000000..060880b --- /dev/null +++ b/docs/spec/conformance-surface.md @@ -0,0 +1,71 @@ +# Executable operation conformance bridge + +`devostasis.conformance-bridge.v1` is a source-checkout bridge implementing the +capabilities of `devostasis.executable-conformance-surface.v1`, accepted by +PV-REV-CONFORMANCE-SURFACE-001. Capability acceptance does not independently +accept this implementation or its exact fixture materialization. Those reviews +remain pending. Existing `devostasis.vectors.v1` files retain their meaning. + +Run offline, without provider APIs, secrets or private history: + +```sh +python scripts/check_conformance_surface.py +python -m pytest tests/test_conformance_surface.py +``` + +The closed file envelope is `schema`, `source`, `cases`. Each case has exactly +`case`, `scenario`, `given`, `checks`. The published shape is +`schemas/conformance-bridge.schema.json`; execution additionally validates +operation inputs, unique IDs across both carriers and exact recorded lineage. +The bridge and self-contained drivers are in `tests/surface_bridge.py`; +literal inputs and assertions are in `tests/fixtures/conformance-surface.json`. +No arbitrary module, executable, test-name pointer or evaluated code is admitted. + +Each check contains a JSON path array and exactly one of `equals`, `not_equals`, +`contains`, `absent: true`. Equality compares canonical JSON bytes, preserving +scalar types. A positive check of an unknown path fails. Unknown operations, +fields, assertions or lineages fail; none is a passing skip. Every declared +check executes. T8 requires all five independent status/freshness substitutions; +R9 keeps both provider variants under one permanent ID. Failure output names +the permanent case and checkpoint path, including its subordinate variant. + +| Scenario version | Executed boundary | +| --- | --- | +| `ci-native.v1` | GitHub native status/conclusion or GitLab native status through exact `devostasis.ci-outcomes.github.v1` / `devostasis.ci-outcomes.gitlab.v1`, then revision composition; outcome and contribution assertions run before any Vital interpretation | +| `independence.v1` | real Actions attempts → one immutable revision → governed Integrity consuming positive `ci.sample_semantics=RAW_RUNS`; history failure remains sticky and retry count cannot establish independent samples | +| `multi-vital.v1` | one explicit shared commit/change-request/issue/branch inventory graph → normal derivation → seven-Vital snapshot; dependency provenance and absent aggregate assertions inspect the same execution | +| `bundle.v1` | persisted canonical core bundle, canonical byte/digest production, member access failure, receipt binding, public-only verification, private/convenience exclusion, immutable collision | +| `history-store.v1` | real isolated FilesystemHistoryStore, idempotent put, failed commit, missing predecessor, permission-domain failure and deterministic two-writer checkpoints/retry | + +Bundle/store scenarios pin `devostasis.manifest.v2`, `devostasis.bundle.v3`, +`PV-BUNDLE-ID-003` and `PV-RECEIPT-IDENTITY-003`. They do not use a compatibility +registry to verify exact source content. Changing that tuple requires a new +scenario version. Provider maps are explicit fixture discriminators; an unknown +or mixed provider/map pair is refused. + +The GitLab surface is a pure offline outcome normalizer, not the planned core +GitLab collector. It establishes no pipeline-history completeness. R5–R10 here +materialize the native normalization/contribution boundary; independent review +of downstream exact fixture obligations remains separate. The bridge does not +claim all of R11–R53 or INT-TOTAL-14 from those six cases. + +T3 introduces the opt-in rule +`integrity.bands.v1+ci-unit-004+hist-002+independence-001`. Absent provenance +uses the exact historical rule. Positive RAW_RUNS keeps one revision contribution +and reports `sample_strength=PROVISIONAL_NONINDEPENDENT`; an AVAILABLE result is +reduced to DEGRADED. Unknown/stale/unsupported provenance yields UNKNOWN without +a band. No threshold or window changes. The rule boundary is visible to delta. + +The store uses a nonblocking OS publication lock. A stale advancing writer +preserves its immutable candidate and raises `HISTORY_WRITE_CONFLICT`; it must +rebuild from latest before retrying. Identical indexed writes remain idempotent. +The RPT-9 scenario forces both readers to observe one predecessor, refuses an +overlapping lock holder, commits writer A, refuses stale writer B, inspects +latest and all immutable candidates, then rebuilds and commits B. Index access +denial remains `HISTORY_STORE_FORBIDDEN`, hence HISTORY_GAP, never BASELINE. + +Twenty permanent IDs are executable here: R5–R10, T3, T6, T8, +ART-05/08/09/10/11/15 and RPT-4/5/6/8/9. Nineteen reduce the prior list of 62 +unmaterialized cases; RPT-8 additionally gains an operation fixture. The remaining +43 are R11–R53. B1 stays open, as do independent concrete-surface/exact-fixture +acceptance, T9 cell-key delivery, core GitLab and external B7 evidence. diff --git a/docs/spec/conformance.md b/docs/spec/conformance.md index 98e78b8..f03375d 100644 --- a/docs/spec/conformance.md +++ b/docs/spec/conformance.md @@ -547,18 +547,46 @@ requirements without assigning new research case identifiers. | rejected | a case expecting refused evidence passes only when the evaluation raises with that code, and the refusal is its whole expectation | `test_a_rejected_case_passes_only_when_the_evidence_is_refused_with_that_code`, `test_rejected_is_the_whole_expectation_and_names_a_code` | | activity kind | the interval and the coverage notes of the activity member are checked | `test_the_activity_kind_checks_the_interval_and_the_coverage_it_discloses` | -Cases not yet implemented as tests (T3, T6, T8, R5..R53, ART-05, -ART-08..ART-11, ART-15, RPT-4..RPT-6, RPT-9) are listed in the ROADMAP under the -synthetic fixture suite; T9 is implemented by its generator since 0.2.0, and -its cell-key vectors are still the research process's to deliver. The research process delivers them as executable JSON -vectors in the format of [vectors.md](vectors.md), one accepted family per -unit; the ones accepted so far (T2, R1, R2, R3, R4, T4, T5, T7) are in the -corpus above, and so are the twenty cases of `PV-CLUTTER-INCOMPLETE-001`, -transcribed from the accepted contract the way `ORDER-01..15` were. The `ci` kind reaches the normalization boundary R5..R10 are -about, so that family can be materialized against it (issue #20); `variants` -and the dependency-group assertions close the T8 and part of the T6 gap of -issue #23, while T3 and the bundle and store cases still need a surface this -version does not publish. +The successor operation bridge is documented in +[conformance-surface.md](conformance-surface.md). It executes implementation +materializations under the accepted capability contract, with exact-fixture and +concrete-surface independent review still pending. B1 remains open: R11..R53 +still need exact executable materialization; T9 cell-key delivery remains separate. +The existing v1 vector corpus and all permanent identifiers retain their meaning. + +## Versioned operation bridge (0.6 implementation materialization) + +Each citation resolves to one case in the closed bridge carrier. Native R5..R10 +assert normalization and revision contributions; full downstream fixture fidelity +is an independent review obligation, not inferred from these checks. + +| Case | Executed obligation | Proof | +| --- | --- | --- | +| R5 | native GitHub skipped is NOT_EXECUTED, no decisive/failed contribution | `bridge:R5` | +| R6 | native GitLab manual is NOT_EXECUTED, no decisive/failed contribution | `bridge:R6` | +| R7 | native timed_out is VERIFY_FAIL, one failed decisive contribution | `bridge:R7` | +| R8 | native cancelled is non-verification terminal | `bridge:R8` | +| R9 | same-ID GitHub failure/GitLab failed normalization equivalence | `bridge:R9` | +| R10 | future provider values are UNKNOWN without guessed contribution | `bridge:R10` | +| T3 | one immutable revision, failed execution then ten successful RAW_RUNS retries stays DEGRADED / PROVISIONAL_NONINDEPENDENT | `bridge:T3` | +| T6 | shared inventories feed Pulse/Flow/Clutter; shared dependency provenance and no aggregate fields | `bridge:T6` | +| T8 | independent FORBIDDEN, UNKNOWN, ERROR, PARTIAL and STALE numeric substitutions under one ID | `bridge:T8` | +| ART-05 | required member access denial cannot verify | `bridge:ART-05` | +| ART-08 | canonical map/whitespace invariance; material scalar/type differences change digest | `bridge:ART-08` | +| ART-09 | provider observation references resolve in identity-bound receipts; missing receipt binding fails | `bridge:ART-09` | +| ART-10 | persisted public members verify/render without network/private dependencies | `bridge:ART-10` | +| ART-11 | private/SNAP convenience evidence is outside public identity | `bridge:ART-11` | +| ART-15 | same-ID differing canonical bytes collide without overwrite | `bridge:ART-15` | +| RPT-4 | actual identical put is idempotent, differing put collides | `bridge:RPT-4` | +| RPT-5 | incomplete immutable commit preserves prior latest | `bridge:RPT-5` | +| RPT-6 | missing predecessor yields HISTORY_GAP, retains snapshot, invents no transition | `bridge:RPT-6` | +| RPT-8 | store FORBIDDEN remains explicit HISTORY_GAP | `bridge:RPT-8` | +| RPT-9 | deterministic two-reader/write conflict, OS lock, preserved candidates and rebuilt retry | `bridge:RPT-9` | + +Harness proof: `test_bridge_unknown_and_duplicate_lineage_never_skips`, +`test_bridge_consumes_negative_assertions_and_wrong_expectations`, +`test_unknown_input_and_assertion_paths_cannot_pass`, +`test_t8_cannot_drop_a_required_substitution`. ## Companion regression evidence (implementation-owned) @@ -647,3 +675,16 @@ not close B1's missing accepted exact fixtures or B7's sustained calibration. | DEV-PAGES-02 | changed total between pages is partial | `test_work_pagination_changed_total_between_pages_is_partial` | | DEV-PAGES-03 | valid exact full empty and header absent responses are complete | `test_work_pagination_valid_exact_full_empty_and_header_absent_responses_are_complete` | | DEV-PAGES-04 | github result caps and invalid totals are not complete | `test_work_pagination_github_result_caps_and_invalid_totals_are_not_complete` | + +## Independent Git history proof (0.6 implementation-local) + +| Boundary | Proof | +| --- | --- | +| authenticated ancestry/root/count/time/frontier/completeness | `test_rehashed_history_claims_require_authenticated_objects` | +| offline replay without Git/subprocess | `test_offline_replay_has_no_git_repository_or_subprocess_dependency` | +| all merge parents and clock-skew traversal | `test_graph_walk_does_not_prune_clock_skew_or_merge_parents` | +| bounded partial proof never promotes COMPLETE | `test_bounded_proof_never_claims_complete_history` | +| object kind/reachability | `test_wrongly_typed_historic_objects_fail_closed`, `test_valid_but_unreachable_historic_object_is_rejected` | +| v1/v2 replay and comparison boundary | `test_v1_and_v2_replay_separately_and_do_not_compare_across_lineage`, `test_v2_frozen_example_replays_and_has_explicit_schemas` | +| independent edit oracle | `test_byte_line_algorithm_matches_independent_lcs_oracle` | +| exclusion applies before historical blob reads | `test_historic_proof_does_not_read_policy_excluded_blob_bytes` | diff --git a/docs/spec/history-and-reports.md b/docs/spec/history-and-reports.md index ace4508..4d828a2 100644 --- a/docs/spec/history-and-reports.md +++ b/docs/spec/history-and-reports.md @@ -281,3 +281,15 @@ non-monotonic observation boundary; the immutable authority of the comparison; ACT-COV-01..05 as `activity` vectors. RPT-4..RPT-6 and RPT-9, including an executable permission-domain fixture, remain open implementation work named by PV-REV-REPORT-001. + +## Publication conflict and permission checkpoint (0.6) + +Canonical commit uses a nonblocking OS lock across store publication, including +rename, immutable put, index and latest copy. A second publisher gets explicit +HISTORY_WRITE_CONFLICT. Before an advancing non-duplicate candidate reaches the +index, its previous_bundle_id must equal the current indexed tail. A stale writer +keeps its immutable candidate and must rebuild against latest; no silent overwrite +or favorable history replacement occurs. Indexed duplicates remain idempotent. +Failure before immutable commit leaves the prior latest authoritative. Index +PermissionError is HISTORY_STORE_FORBIDDEN, never absence/BASELINE. See the +versioned RPT operation scenarios in [conformance-surface.md](conformance-surface.md). diff --git a/docs/spec/integrity-ci.md b/docs/spec/integrity-ci.md index 3873595..417f0c7 100644 --- a/docs/spec/integrity-ci.md +++ b/docs/spec/integrity-ci.md @@ -217,3 +217,14 @@ verification degrades instead of claiming `CLEAN`; INT-UNKNOWN-01..06; T2, R1 and R2 as vectors ([conformance.md](conformance.md)). The `ci` vector kind ([vectors.md](vectors.md)) executes a case at the normalization boundary this page describes. + +## Offline GitLab normalization and independence (0.6) + +`devostasis.ci-outcomes.gitlab.v1` is a pure native status map: success→VERIFY_PASS, +failed→VERIFY_FAIL, canceled→NON_VERIFY_TERMINAL, skipped/manual/scheduled→NOT_EXECUTED. +Created/waiting_for_resource/preparing/waiting_for_callback/pending/running/canceling +are VERIFY_UNRESOLVED. Every other value is UNKNOWN. This boundary is available +to offline conformance without claiming a core GitLab collector or complete +pipeline retry history. The explicit provider/map pair is checked before use. +The optional governed RAW_RUNS rule and its exact compatibility boundary are in +[vitals.md](vitals.md) and [conformance-surface.md](conformance-surface.md). diff --git a/docs/spec/observations.md b/docs/spec/observations.md index 95f49dc..6c17d0e 100644 --- a/docs/spec/observations.md +++ b/docs/spec/observations.md @@ -156,3 +156,11 @@ receipt is identity-bearing. Enabling a cache therefore never moves a C1 observed zero, C2 tier limitation, C3 permission denial, C4 truncated history, C5 stale cache, C6 connector ambiguity, C7 transient failure. See [conformance.md](conformance.md). + +## Optional sample-independence input (0.6) + +`ci.sample_semantics` is a RAW-OBS-V0 enum, RAW_RUNS or INDEPENDENT_REVISIONS. +The governed Integrity extension consumes its status/freshness/value and adds +it to input metadata. It never creates extra canonical revision records. +Absent input retains historical evaluation. Unknown/stale/unsupported values +fail closed under the versioned rule described in [vitals.md](vitals.md). diff --git a/docs/spec/vitals.md b/docs/spec/vitals.md index b1912c5..be2a478 100644 --- a/docs/spec/vitals.md +++ b/docs/spec/vitals.md @@ -549,3 +549,14 @@ step towards combining them. | Flow congested | 14 days, 10 open, median over 168 h = 604800 s | | Flow gridlocked | 3 open for 30 days with 0 merged, or 10 open with median over 336 h = 1209600 s | | Clutter heavy / cluttered | 25 / 5 stale items, ratio 1/2 / 1/4 with 4 tracked, 20 / 6 stale branches | + +## Optional Integrity independence provenance (0.6) + +`ci.sample_semantics` selects the opt-in rule +`integrity.bands.v1+ci-unit-004+hist-002+independence-001`. It is an enum: +RAW_RUNS or INDEPENDENT_REVISIONS. Absent input preserves the exact historical +rule and bytes. Present unknown/stale/unsupported input yields UNKNOWN without +a band. Positive RAW_RUNS cannot be AVAILABLE: it retains failure-sticky +one-revision arithmetic, reports DEGRADED when a band is otherwise available, +and sample_strength PROVISIONAL_NONINDEPENDENT. Existing UNKNOWN precedence, +thresholds/windows and per-Vital comparison boundaries remain intact. diff --git a/tests/test_spec_drift.py b/tests/test_spec_drift.py index 12d39c1..1f75b07 100644 --- a/tests/test_spec_drift.py +++ b/tests/test_spec_drift.py @@ -16,6 +16,7 @@ CITATION = re.compile(r"`(test_[a-z0-9_]*\*?)`") DEFINITION = re.compile(r"^def (test_[a-z0-9_]+)", re.MULTILINE) VECTOR_CITATION = re.compile(r"`vector:([A-Z0-9-]+)`") +BRIDGE_CITATION = re.compile(r"`bridge:([A-Z0-9-]+)`") def _defined_tests() -> set[str]: @@ -59,6 +60,13 @@ def test_the_specification_index_lists_every_specification_page(): assert not missing, f"docs/spec/README.md does not link: {missing}" +def test_the_conformance_table_and_bridge_name_the_same_cases(): + from surface_bridge import load + corpus = {case["case"] for case in load(ROOT / "tests/fixtures/conformance-surface.json")} + cited = set(BRIDGE_CITATION.findall(CONFORMANCE.read_text(encoding="utf-8"))) + assert cited == corpus + + def test_every_contract_identifier_the_code_declares_is_documented(): """A version the runtime writes into a bundle must be findable in the specification.""" from devostasis import contracts From 354429ff78033a8fbd22f0cd665d82c839a52144 Mon Sep 17 00:00:00 2001 From: Unisphere Dev Date: Wed, 21 Oct 2026 16:19:21 +0300 Subject: [PATCH 10/11] docs(release): use the declared isolated wheel build --- docs/release-acceptance.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/release-acceptance.md b/docs/release-acceptance.md index c27ab6d..43329d1 100644 --- a/docs/release-acceptance.md +++ b/docs/release-acceptance.md @@ -120,7 +120,7 @@ devostasis code verify --bundle examples/code-v2/bundle devostasis code verify-packet --bundle examples/code-v2/bundle --packet examples/code-v2/packet.json devostasis work verify --bundle examples/code-v2/work-bundle devostasis work replay --bundle examples/code-v2/work-bundle -python -m pip wheel --no-deps --no-build-isolation . +python -m pip wheel --no-deps . ``` CI additionally verifies both work/core examples, deterministic repeated builds From a4761daa9fa87026ea106a3d722e7299b3de3b8b Mon Sep 17 00:00:00 2001 From: Unisphere Dev Date: Wed, 21 Oct 2026 17:29:10 +0300 Subject: [PATCH 11/11] feat(ci): complete R11-R53 native and durable history conformance --- .devostasis/debt.json | 5 +- CHANGELOG.md | 8 +- ROADMAP.md | 71 +- docs/release-acceptance.md | 14 +- docs/spec/PROVENANCE.md | 1 + docs/spec/conformance-surface.md | 35 +- docs/spec/conformance.md | 68 +- docs/spec/integrity-ci.md | 66 +- docs/spec/observations.md | 11 + docs/spec/vitals.md | 14 + pyproject.toml | 2 +- schemas/ci-normalization.schema.json | 778 + schemas/conformance-bridge.schema.json | 345 + scripts/check_conformance_surface.py | 4 +- src/devostasis/adapters/ci.py | 319 + src/devostasis/vitals/integrity.py | 13 +- tests/ci_surface_bridge.py | 64 + tests/fixtures/ci-revisions.json | 17569 +++++++++++++++++++++++ tests/surface_bridge.py | 29 +- tests/test_ci_normalization.py | 157 + tests/test_ci_surface_schema.py | 47 + tests/test_conformance_surface.py | 9 +- tests/test_spec_drift.py | 4 +- 23 files changed, 19558 insertions(+), 75 deletions(-) create mode 100644 schemas/ci-normalization.schema.json create mode 100644 src/devostasis/adapters/ci.py create mode 100644 tests/ci_surface_bridge.py create mode 100644 tests/fixtures/ci-revisions.json create mode 100644 tests/test_ci_normalization.py create mode 100644 tests/test_ci_surface_schema.py diff --git a/.devostasis/debt.json b/.devostasis/debt.json index 7c5587e..aa5a005 100644 --- a/.devostasis/debt.json +++ b/.devostasis/debt.json @@ -47,10 +47,11 @@ { "id": "D-1", "title": "Conformance cases named in the specification have no executable fixture", - "state": "open", + "state": "closed", "opened": "2026-09-05", "updated": "2026-10-21", - "cost": "0.6 publishes a versioned operation bridge with 20 permanent implementation fixtures: R5-R10 native normalization, T3/T6/T8, ART-05/08/09/10/11/15 and RPT-4/5/6/8/9. Nineteen address the former list of 62; R11-R53 (43 cases) remain without exact materialization. Concrete surface and whole-case exact-fixture independent review remain pending, including downstream R5-R10 fidelity. B1 is open; capability acceptance is not fixture acceptance. T9 cell-key delivery remains separate." + "cost": "Closed as an implementation-fixture obligation in pending 0.6: the versioned bridge executes 63 permanent cases, materializing the former 62-case gap plus RPT-8. R11-R53 add 43 cases and 84 CI variants with native normalization and real governed/store/bundle lifecycles. B1 remains open for independent concrete-surface/whole-case exact-fixture acceptance and separate T9 cell-key delivery; capability or research expansion acceptance does not accept these local fixtures. No independent review is claimed.", + "closed": "2026-10-21" }, { "id": "D-2", diff --git a/CHANGELOG.md b/CHANGELOG.md index 36fc0a8..4eb9561 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,9 +11,15 @@ Offline Repository Analysis and executable assurance, targets E4/B1, issues #72/ history proof; offline replay derives roots, window membership, edit counts and completeness. Merge ancestry, clock skew, shallow/missing objects and resource caps remain explicit. V1 bundle/packet/example bytes retain exact replay. -- Publish a versioned source-checkout conformance operation bridge with 20 permanent +- Publish a versioned source-checkout conformance operation bridge with 63 permanent implementation fixtures, consumed RAW_RUNS provenance, shared multi-Vital evidence, all T8 substitutions, canonical bundle and actual HistoryStore checkpoints. +- Materialize R11–R53 (43 permanent cases, 84 CI variants) with native parent + identities, explicit rerun proof, exact Actions/suite overlap, unresolved current + composition and failure-sticky store lifecycles. Versioned opt-in normalization + replays raw facts before governed Integrity; altered/partial evidence fails + closed and old observation/bundle replay remains unchanged. Public schemas + expose seven bridge scenarios and the bounded native input contract. - Serialize canonical store publication and reject stale advancing predecessors with explicit retry conflict while preserving immutable candidates. Store index permission denial remains FORBIDDEN/HISTORY_GAP. diff --git a/ROADMAP.md b/ROADMAP.md index 7e8bfab..fcf7620 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -7,8 +7,11 @@ started and work that cannot start. ## Release state and next steps — 2026-10-21 The project is at **release stabilization**, with Phase A closed and Phase B -five of seven items implemented. B1 now has 20 operation-bridge implementation fixtures (19 from the former -62-case gap); R11–R53 and independent whole-case fixture acceptance remain open, and B7 still needs sustained outside-consumer evidence. The latest +five of seven items implemented. B1 now has 63 operation-bridge implementation +fixtures, materializing the former 62-case gap and RPT-8. R11–R53 contribute +43 permanent cases with 84 CI variants. Independent whole-case/concrete-surface +acceptance and separate T9 cell-key delivery remain open; B7 still needs +sustained outside-consumer evidence. The latest published version is **v0.1.9**; 0.2.0 through 0.6.0 remain unreleased. The audit began with a clean working tree. All local and fetched remote heads @@ -145,7 +148,7 @@ start that matrix automatically in the fork. | Release the implemented increments | #57 into upstream `release/0.2.0`, then #53, #59, #64 and #69 into `master`, with each release tag | Maintainer workflow approval, independent review and green checks on each final head; carried maintenance PRs #36/#37/#50 are reconciled when their commits land | | Activate the selected outside consumer | Install a reviewed exact pin, project/policy binding, protected runner, durable writer and regular observation schedule | Actual private deployment receipts and verified stored generations; a recipe or shadow probe alone does not complete B7 | | Qualify useful repeated operation | Complete large-project GitLab collection (#62), recover private fleet observation (#32), and collect independent consumer feedback | Repeated complete inventories, explicit partial/failure recovery, real calendar-day history and one completed bounded task with original acceptance evidence | -| Close the remaining assurance gaps | B1 exact conformance (#20/#23), timestamp/lineage/store audit families (#35), and GitLab exact-head approvals where needed (#58) | Accepted fixtures at the named boundary and explicit capability qualification; 62 named conformance cases remain without executable proof | +| Close the remaining assurance gaps | B1 exact conformance (#20/#23), timestamp/lineage/store audit families (#35), and GitLab exact-head approvals where needed (#58) | 63 permanent operation cases now execute; independent exact-fixture/concrete-surface acceptance and T9 cell-key delivery remain | | Extend reach after the gates | Core GitLab Vital adapter and accepted TestState/Coverage/Deployment/Work Instrument carrier | B7 and compatibility gates, selected adopter needs, preserved historical replay; the existing GitLab work collector is a separate companion | **The highest-impact next major increment is operational adoption and a @@ -435,35 +438,27 @@ share for Direction, and its own attention order is actionable. ## Phase B: make it trustworthy over time -### B1. Executable conformance vectors — the format is built, the vectors are owed - -The specification names **62 conformance cases with no test behind them** -(T3, T6, T8, R5..R53, ART-05, ART-08..ART-11, ART-15, RPT-4..RPT-6, RPT-9). -It named 70 until 0.1.9 adopted the seven exact vectors the research process -has accepted so far (`T2`, `R1`, `R2`, `R3`, `R4`, `T4`, `T5`, `T7`), and 63 -until 0.2.0 held T9 to its accepted table with a generator; the rest are -still the research process's to produce, one accepted family per unit. - -The half that was ours shipped in 0.1.8: `devostasis.vectors.v1`, a runner, a -`devostasis vectors` command and a published schema -([vectors.md](docs/spec/vectors.md)). A case is a JSON document that states -evidence and expected result; the `vital` kind evaluates one Vital over raw -observation envelopes, the `delta` kind compares two snapshots. It fails -closed, so an unknown kind or a malformed vector is a red build rather than a -case that silently did not run, and `ORDER-01..15` are already carried that -way. 0.1.9 added the `ci` kind, which reaches the provider-native -normalization `R5..R10` are about ([#20](https://github.com/drevendev/Devostasis/issues/20)), -the `activity` kind for `ACT-COV-01..05`, and `variants`, the one-identifier -multi-variant shape `T8` and `R9` need -([#23](https://github.com/drevendev/Devostasis/issues/23)). Two more kinds -remain deliberately absent until a case needs them: bundle-level identity -cases (`ART-*`) and store cases (`RPT-4..RPT-6`, `RPT-9`) that need a fixture -store rather than a snapshot pair; `T3` needs a surface that carries -`RAW_RUNS` provenance into Integrity, which no accepted contract defines yet. - -Closing B1 needs the vectors themselves. Until then debt D-1 stays open, and -the specification keeps saying that "conformance" covers about half of what it -names. +### B1. Executable conformance — implementation complete, independent acceptance pending + +The former 62 named cases without executable materialization now run in the +0.6 source-checkout operation bridge, with RPT-8 additionally exercised. The +legacy v1 corpus remains unchanged at 148 CLI vectors. The bridge adds 63 +permanent cases across seven scenario versions; R11–R53 supply 43 cases and +84 CI variants, with literal expectations and real evaluator/store/bundle +execution. See [conformance surface](docs/spec/conformance-surface.md) and the +[case-to-proof table](docs/spec/conformance.md). + +The closed native CI projection admits exact parent identities/overlap/rerun +links, current unresolved precedence, immutable commit ordering and durable +failure history. Raw child fallback and incomplete parent-level histories +remain explicitly degraded/unknown. New observation/rule inputs are opt in; +historical bundles replay under their original semantics. + +D-1's missing-implementation-fixture obligation is closed. B1 itself remains +open for independent review of the concrete surface and whole-case fixture +fidelity, plus separate T9 cell-key delivery. Accepted capability and research +case expansion do not independently accept this implementation. No independent +review or release acceptance is claimed by the implementer. ### B7. First outside repository integrates self-observation — the owner's move @@ -477,8 +472,8 @@ templates and local proofs do not substitute for them. ### Also open from the reporting review -`RPT-4..RPT-6` and `RPT-9` as executable cases, including a permission-domain -fixture for the store. Ours, and covered by B1's format. +`RPT-4..RPT-6` and `RPT-9` now execute real store operations, including a +permission-domain fixture. Their independent exact-fixture review remains open. ### Done @@ -640,14 +635,16 @@ evidence and could make the study look ready while it holds about one day of real signal. The study must count calendar days, not bundles, and manual triggering should stop now that the release chain is quiet. -**Half of the named conformance surface is unproven.** 79 table rows cite a -test; 70 named cases have none. The specification says so and the roadmap +**Historical conformance finding (before the 0.6 materialization).** The audit +then found 79 table rows citing a +test and 70 named cases without one. The specification said so and the roadmap admits it, so nothing is being hidden, but "conformance" currently covers about half of what it names. This is debt D-1 and target B1. Since 0.1.8 the missing half is missing evidence, not missing machinery: a case can be written as a vector and executed, and the fifteen ordering cases are carried that way. Building the runner did not prove one of the 70, and counting it as progress on -D-1 would be counting the tooling as the test. +D-1 would be counting the tooling as the test. The 0.6 bridge now supplies +the implementation fixtures; independent whole-case acceptance remains open. **Provider neutrality is a design intent, not a demonstrated property.** Every provider-neutral contract has exactly one implemented provider. The GitLab diff --git a/docs/release-acceptance.md b/docs/release-acceptance.md index 43329d1..2f2fd9f 100644 --- a/docs/release-acceptance.md +++ b/docs/release-acceptance.md @@ -90,9 +90,17 @@ explicit owner/external gates. declared byte-line edit algorithm, including repeated/unterminated lines. - [x] Legacy `examples/code` regenerates byte for byte. New `examples/code-v2` and separate schemas replay independently; cross-lineage comparison is refused. -- [x] Twenty permanent conformance IDs execute real native normalization, +- [x] Sixty-three permanent conformance IDs execute real native normalization, consumed RAW_RUNS provenance, one shared multi-Vital graph, all T8 substitutions, canonical bundle operations and stateful HistoryStore operations. +- [x] R11–R53 are materialized under their 43 permanent IDs, with 84 CI variants: + native parent identities, exact overlap, proved reruns, unresolved precedence, + commit-time order/window and failure-sticky real-store lifecycles. Raw child + fallback and parent-level history limits remain explicit. +- [x] Governed Integrity recomputes native evidence/provenance/records and rejects + altered bindings; unknown lineage, contradictory identity, broken pagination + and incomplete favorable history fail closed. New behavior is opt in under + a separate rule; old bundle examples and legacy paths retain exact replay. - [x] HistoryStore publication is locked; a stale advancing writer preserves candidates, reports conflict and rebuilds from latest. Permission failures remain explicit HISTORY_GAP. @@ -127,7 +135,9 @@ CI additionally verifies both work/core examples, deterministic repeated builds and the operations archive restore on Python 3.12/3.13/3.14. Green checks establish those executions at the recorded head; they do not complete the unchecked gates. -B1 retains R11–R53 and independent exact-fixture acceptance. B7 retains sustained +The former 62-case implementation gap is materialized; B1 retains independent +concrete-surface/whole-case exact-fixture acceptance and separate T9 cell-key +delivery. B7 retains sustained outside-consumer evidence. Legacy v1 history and omitted blob sizes keep their documented proof limits. No compatibility migration, core GitLab collector, threshold retuning, aggregate health score or publication is implied by this PR. diff --git a/docs/spec/PROVENANCE.md b/docs/spec/PROVENANCE.md index d4ff501..f3f6c6e 100644 --- a/docs/spec/PROVENANCE.md +++ b/docs/spec/PROVENANCE.md @@ -11,6 +11,7 @@ normative here. The process is domain-neutral and documented in the | `devostasis.code-analysis.v1`, `devostasis.code-policy.v1`, `devostasis.code-engine.v1`, `devostasis.code-packet.v1`, `devostasis.code-comparison.v1` | implementation-owned E4, issue #72 | local, introduced in 0.6.0; static source evidence without accepted Vital/Instrument judgement; v1 history remains recorded acquisition; v2 supplies independent proof for #73 | | `devostasis.code-analysis.v2`, `devostasis.code-policy.v2`, `devostasis.code-engine.v2`, `devostasis.code-packet.v2`, `devostasis.code-comparison.v2`, `devostasis.git-history-proof.v1` | implementation-owned #73 assurance increment | local, introduced within 0.6.0; bounded authenticated graph/object proof and independently derived edit counts; separate v1 replay preserved; independent implementation review pending | | `devostasis.conformance-bridge.v1`, `integrity.bands.v1+ci-unit-004+hist-002+independence-001` | PV-CONFORMANCE-SURFACE-001 / PV-REV-CONFORMANCE-SURFACE-001, accepted capability contract; T3 independence premise | concrete bridge/materialization is implementation-owned and awaits independent review; optional RAW_RUNS provenance is consumed without changing canonical revision identity or default historical rule | +| `devostasis.ci-normalization.v1`, `devostasis.ci-rerun-link.v1`, `integrity.bands.v1+ci-unit-004+hist-002+ci-normalization-001` | local closed projection implementing PV-CI-UNIT-004/PV-HIST-002; exact R11–R53 expansion of PV-TEST-002 accepted by [PV-REV-TEST-002](https://docs.google.com/document/d/1zsqtsqW-LldkAqYRcO-a9ztPj8ZTg_duwGDvV2N87Ck/edit) (its remaining changes-required point is T9) | implementation-owned offline input/projection; 43 permanent cases and 84 CI variants implemented in 0.6; independent concrete-surface and exact-fixture fidelity review pending; no claim of provider-signed rerun attestation, core GitLab collection or favorable parent-level history completeness | | `devostasis.ci-outcomes.gitlab.v1` | PV-CI-NORM-001 | local offline native status mapping; no core GitLab collector or favorable history completeness is claimed | | `devostasis.work-adoption.v1`, `devostasis.work-invocation.v1`, `devostasis.work-result.v1`, `devostasis.work-history-archive.v1`, `devostasis.work-history-audit.v1` | implementation-owned E3 operational adoption, issues #62/#65 | local, introduced in 0.5.0; separate from immutable work/core canonical members and accepted Instrument/Vital semantics; caller reports grant no independent acceptance or execution authority | | `devostasis.bundle.v3`, `PV-BUNDLE-ID-003`, `PV-RECEIPT-IDENTITY-003`, `devostasis.receipt-identity.v1`, `devostasis.observations.v2`, `devostasis.manifest.v2`, `devostasis.execution-receipt.v1`, `devostasis.render.v5` | cumulative PV-RECEIPT-IDENTITY-001/002/003 | independently accepted by PV-REV-RECEIPT-IDENTITY-003; adopted in 0.4.0 with RECEIPT-ID-01..15,17..21. Literal devostasis schema tokens are local choices; rejected ID-16 is preserved, not reassigned | diff --git a/docs/spec/conformance-surface.md b/docs/spec/conformance-surface.md index 060880b..5661c6e 100644 --- a/docs/spec/conformance-surface.md +++ b/docs/spec/conformance-surface.md @@ -18,7 +18,10 @@ The closed file envelope is `schema`, `source`, `cases`. Each case has exactly `schemas/conformance-bridge.schema.json`; execution additionally validates operation inputs, unique IDs across both carriers and exact recorded lineage. The bridge and self-contained drivers are in `tests/surface_bridge.py`; -literal inputs and assertions are in `tests/fixtures/conformance-surface.json`. +literal inputs and assertions are in `tests/fixtures/conformance-surface.json` +and `tests/fixtures/ci-revisions.json`. CI revision drivers are in +`tests/ci_surface_bridge.py`; their native input schema is +`schemas/ci-normalization.schema.json`. No arbitrary module, executable, test-name pointer or evaluated code is admitted. Each check contains a JSON path array and exactly one of `equals`, `not_equals`, @@ -35,6 +38,8 @@ the permanent case and checkpoint path, including its subordinate variant. | `independence.v1` | real Actions attempts → one immutable revision → governed Integrity consuming positive `ci.sample_semantics=RAW_RUNS`; history failure remains sticky and retry count cannot establish independent samples | | `multi-vital.v1` | one explicit shared commit/change-request/issue/branch inventory graph → normal derivation → seven-Vital snapshot; dependency provenance and absent aggregate assertions inspect the same execution | | `bundle.v1` | persisted canonical core bundle, canonical byte/digest production, member access failure, receipt binding, public-only verification, private/convenience exclusion, immutable collision | +| `vital-consumers.v1` | actual degraded Vital evaluation, gauge qualification and per-Vital delta over literal observation inputs | +| `ci-revisions.v1` | version-pinned native CI normalization → admitted observations → governed Integrity → real isolated HistoryStore → persisted bundle verification; explicit BASELINE/CONTINUE/GAP operations, literal current/history/coverage assertions | | `history-store.v1` | real isolated FilesystemHistoryStore, idempotent put, failed commit, missing predecessor, permission-domain failure and deterministic two-writer checkpoints/retry | Bundle/store scenarios pin `devostasis.manifest.v2`, `devostasis.bundle.v3`, @@ -43,11 +48,13 @@ registry to verify exact source content. Changing that tuple requires a new scenario version. Provider maps are explicit fixture discriminators; an unknown or mixed provider/map pair is refused. -The GitLab surface is a pure offline outcome normalizer, not the planned core -GitLab collector. It establishes no pipeline-history completeness. R5–R10 here -materialize the native normalization/contribution boundary; independent review -of downstream exact fixture obligations remains separate. The bridge does not -claim all of R11–R53 or INT-TOTAL-14 from those six cases. +The GitLab surface is an offline parent normalizer, separate from the planned +core GitLab collector. It establishes current pipeline identity/state and +retains observed failures, without claiming favorable pipeline-history +completeness. Child-only current verification is explicitly raw/degraded; +canonical parent history remains unknown. Exact Actions/suite overlap requires +check_suite_id, and cross-id reruns require scope-bound linear proof. Neither +names nor app equality deduplicate parents. See [integrity-ci.md](integrity-ci.md). T3 introduces the opt-in rule `integrity.bands.v1+ci-unit-004+hist-002+independence-001`. Absent provenance @@ -64,8 +71,14 @@ overlapping lock holder, commits writer A, refuses stale writer B, inspects latest and all immutable candidates, then rebuilds and commits B. Index access denial remains `HISTORY_STORE_FORBIDDEN`, hence HISTORY_GAP, never BASELINE. -Twenty permanent IDs are executable here: R5–R10, T3, T6, T8, -ART-05/08/09/10/11/15 and RPT-4/5/6/8/9. Nineteen reduce the prior list of 62 -unmaterialized cases; RPT-8 additionally gains an operation fixture. The remaining -43 are R11–R53. B1 stays open, as do independent concrete-surface/exact-fixture -acceptance, T9 cell-key delivery, core GitLab and external B7 evidence. +Sixty-three permanent IDs execute across seven scenario versions: R5–R53, +T3, T6, T8, ART-05/08/09/10/11/15 and RPT-4/5/6/8/9. This materializes the +former 62-case gap and adds an RPT-8 operation fixture. R11–R53 add 43 cases +with 84 subordinate CI variants; each variant executes all lifecycle steps. +Permanent IDs are unique across both bridge files and the legacy vector corpus. +Checks remain active under optimized Python. Expectations are literal research +obligations, not snapshots generated by the implementation under test. + +B1 stays open for independent concrete-surface and whole-case exact-fixture +acceptance and separate T9 cell-key delivery. Core GitLab and external B7 +evidence remain separate. This implementation does not declare those accepted. diff --git a/docs/spec/conformance.md b/docs/spec/conformance.md index f03375d..132f9ab 100644 --- a/docs/spec/conformance.md +++ b/docs/spec/conformance.md @@ -550,8 +550,9 @@ requirements without assigning new research case identifiers. The successor operation bridge is documented in [conformance-surface.md](conformance-surface.md). It executes implementation materializations under the accepted capability contract, with exact-fixture and -concrete-surface independent review still pending. B1 remains open: R11..R53 -still need exact executable materialization; T9 cell-key delivery remains separate. +concrete-surface independent review still pending. R11..R53 now have exact-ID +implementation fixtures below. B1 remains open for independent whole-case +acceptance and separate T9 cell-key delivery. The existing v1 vector corpus and all permanent identifiers retain their meaning. ## Versioned operation bridge (0.6 implementation materialization) @@ -688,3 +689,66 @@ not close B1's missing accepted exact fixtures or B7's sustained calibration. | v1/v2 replay and comparison boundary | `test_v1_and_v2_replay_separately_and_do_not_compare_across_lineage`, `test_v2_frozen_example_replays_and_has_explicit_schemas` | | independent edit oracle | `test_byte_line_algorithm_matches_independent_lcs_oracle` | | exclusion applies before historical blob reads | `test_historic_proof_does_not_read_policy_excluded_blob_bytes` | + +## R11–R53 implementation materialization (0.6) + +The permanent-ID expansion of PV-TEST-002 is accepted by PV-REV-TEST-002; +this local materialization still requires independent fidelity review. Later +PV-CI-UNIT-004/PV-HIST-002 and fail-closed rules govern current/history distinctions. +R17 raw children cannot prove a canonical parent failure or favorable history; +R38 does not grant GitLab attempt-history completeness. Every CI checkpoint +builds, persists and verifies a real bundle. The 43 cases contain 84 CI variants. + +| Case | Executed obligation | Proof | +| --- | --- | --- | +| R11 | degraded possible_bands is a nonauthoritative conservative superset; gauge/delta consume the bound | `bridge:R11` | +| R12 | one failed parent plus five children contributes one failed immutable revision | `bridge:R12` | +| R13 | GitHub suite failure and GitLab failed pipeline compose the same verdict/contribution | `bridge:R13` | +| R14 | canonical parent aggregate governs; children remain provenance without double counting | `bridge:R14` | +| R15 | same parent key collapses, proved rerun chain collapses, unproved distinct ids remain | `bridge:R15` | +| R16 | passing and failing siblings compose one failed revision | `bridge:R16` | +| R17 | child-only fallback declares REVISION_CHILD_AGGREGATE_RAW and degraded coverage; no canonical parent history is invented | `bridge:R17` | +| R18 | latest decisive default-branch revision uses immutable commit time and SHA tie-break, never CI completion time | `bridge:R18` | +| R19 | missing commit time or default-branch association leaves ordering UNKNOWN | `bridge:R19` | +| R20 | enumeration and page boundaries preserve normalized records and governed results | `bridge:R20` | +| R21 | distinct GitLab pipeline ids remain separate parents | `bridge:R21` | +| R22 | same GitLab pipeline id retry has one current parent | `bridge:R22` | +| R23 | distinct GitHub suite ids from one app remain separate parents | `bridge:R23` | +| R24 | only scope-bound linear acyclic rerun proof collapses; cycles/forks/fan-in/unavailable proof preserve parents | `bridge:R24` | +| R25 | parent enumeration and page order are invariant | `bridge:R25` | +| R26 | Actions attempts share one run id and greatest observed attempt governs current state | `bridge:R26` | +| R27 | distinct Actions run ids never merge by descriptive equality | `bridge:R27` | +| R28 | exact workflow check_suite_id deduplicates overlap; unlinked same-app suites remain | `bridge:R28` | +| R29 | passing Actions attempt followed by queued/running retry is currently unresolved | `bridge:R29` | +| R30 | failing Actions attempt followed by queued/running retry is currently unresolved with failure retained | `bridge:R30` | +| R31 | Actions/Checks/linked unresolved surfaces agree; newer disagreement or stale-only evidence fails closed | `bridge:R31` | +| R32 | GitLab same-id retry in progress is unresolved after either prior polarity | `bridge:R32` | +| R33 | greatest Actions attempt in progress governs despite earlier pass/fail attempts | `bridge:R33` | +| R34 | unavailable or contradictory greatest attempt is UNKNOWN, never an older passing attempt | `bridge:R34` | +| R35 | attempt order and page partitions do not change current or history | `bridge:R35` | +| R36 | unresolved plus pass is unresolved, with zero current decisive samples | `bridge:R36` | +| R37 | unresolved plus fail is one current failed revision | `bridge:R37` | +| R38 | GitHub linked and GitLab sibling current verdicts agree without assuming favorable pipeline history completeness | `bridge:R38` | +| R39 | sibling order and page partitions are invariant | `bridge:R39` | +| R40 | unresolved-only evidence is neither PASS nor NO_RECENT_RUNS | `bridge:R40` | +| R41 | unresolved plus canceled and skipped remains unresolved | `bridge:R41` | +| R42 | UNKNOWN dominates failure and unresolved current siblings while historical failure remains | `bridge:R42` | +| R43 | PASS to unresolved to PASS lifecycle keeps one immutable contribution | `bridge:R43` | +| R44 | prior failure survives current pending verification in real durable history | `bridge:R44` | +| R45 | prior pass survives in history while current verification remains unresolved | `bridge:R45` | +| R46 | fail then pass changes current verdict but historical contribution remains failure | `bridge:R46` | +| R47 | PASS to FAIL to PASS keeps absorbing failure in the same revision | `bridge:R47` | +| R48 | Actions/Checks/linked/GitLab prior failure survives pending retry in real stores | `bridge:R48` | +| R49 | historical attempt/sibling order and pagination are invariant | `bridge:R49` | +| R50 | never-decisive unresolved evidence has no fabricated passing historical contribution | `bridge:R50` | +| R51 | 14-day expiry is by immutable commit time, never rerun time | `bridge:R51` | +| R52 | lost retained-failure proof with a broken predecessor becomes UNKNOWN_HISTORY, never favorable history | `bridge:R52` | +| R53 | same-polarity retries keep one immutable contribution and unchanged counts | `bridge:R53` | + +Admission, canonical input, paging and legacy rule boundaries are also checked by +`test_governed_integrity_refuses_forged_native_projections`, +`test_native_input_unknown_shapes_and_ambiguous_bindings_fail_closed`, +`test_page_transport_and_enumeration_do_not_change_canonical_observations`, +`test_partial_inventory_and_partial_parent_cannot_prove_favorable_history`, +`test_normalization_is_opt_in_and_delta_names_the_rule_boundary` and +`test_bridge_wrong_assertion_still_fails_with_optimized_python`. diff --git a/docs/spec/integrity-ci.md b/docs/spec/integrity-ci.md index 417f0c7..e766913 100644 --- a/docs/spec/integrity-ci.md +++ b/docs/spec/integrity-ci.md @@ -32,7 +32,8 @@ is provider-native, never heuristic: - generic GitHub Checks: `check_suite.id`; re-requests keep the same id, so earlier outcomes are not observable (`PARENT_LEVEL_ONLY` provenance). -When Actions runs exist for a revision, check suites created by Actions are +In the historical GitHub collector, when Actions runs exist for a revision, +check suites created by Actions are not collected again (overlap de-duplication). External check apps are only collected when no Actions runs exist at all (see the adapter document). @@ -189,8 +190,10 @@ historical evidence. ## The newest revision The newest in-scope revision speaks for the current state. Its verdict is -read as it was composed: decisive verdicts stand; `VERIFY_UNRESOLVED` makes -the band a conservative superset over the completions the evidence admits; +read as it was composed: decisive verdicts stand; under accepted +PV-INTEGRITY-TOTALITY-001, `VERIFY_UNRESOLVED` permits only an already-true +established historical FAILING predicate to emit DEGRADED/FAILING/EXACT; all +other unresolved regions are UNKNOWN without a band or reachability claim; positively observed `NOT_EXECUTED` and `NON_VERIFY_TERMINAL` fall back to the latest decisive revision and are diagnosed. `UNKNOWN` is none of those: it is the absence of an observation (a `startup_failure`, a conclusion this version @@ -228,3 +231,60 @@ to offline conformance without claiming a core GitLab collector or complete pipeline retry history. The explicit provider/map pair is checked before use. The optional governed RAW_RUNS rule and its exact compatibility boundary are in [vitals.md](vitals.md) and [conformance-surface.md](conformance-surface.md). + +## Versioned native evidence admission (0.6, opt in) + +`devostasis.adapters.ci.observations(evidence, subject)` exposes the offline +`devostasis.ci-normalization.v1` contract. `schemas/ci-normalization.schema.json` +publishes its closed input: project/provider/outcome-map lineage, observation +anchor, configured/inventory coverage, immutable revision metadata, native +parent/child snapshots and explicit rerun links. It admits at most 10,000 rows +per inventory, 1,000 parent pages and 16 MiB canonical input. Native SHA fields +must match revision metadata, parent keys cannot change admitted revisions, +evidence cannot postdate the observation, and unknown fields/lineages fail. +Unknown future outcomes stay UNKNOWN. Contiguous page ordinals are required; +an unfinished last page makes the series PARTIAL. Canonical facts flatten +transport pages, sort and deduplicate identical observations. + +The new path retains distinct provider-native ids: Actions workflow_run.id +with numeric run_attempt; generic check_suite.id; GitLab pipeline.id. For +same-id snapshots, the greatest Actions attempt wins, then observation time; +Checks/GitLab use observation time without inventing numeric attempt history. +Conflicting latest states are UNKNOWN. Only exact Actions check_suite_id +deduplicates a linked suite; Actions governs, suite provenance/failure remains. +A newer disagreeing suite makes current state UNKNOWN. Unlinked same-app +suites and descriptively identical runs remain independent parents. + +`devostasis.ci-rerun-link.v1` requires provider/forge/project/revision scope, +earlier/later native references and AVAILABLE evidence_ref. Only a unique +acyclic linear chain collapses; cycle, fork, fan-in, unavailable or cross-scope +proof preserves all parents and degrades coverage. These links are recorded +acquisition claims, not cryptographic provider attestations. Cross-id histories +retain failures without inventing contiguous attempt completeness. + +Current composition uses UNKNOWN > FAIL > UNRESOLVED > PASS > nonverification +terminal > not executed. Children remain provenance when parents exist. With +only children, latest snapshots per child compose one explicitly degraded +REVISION_CHILD_AGGREGATE_RAW; child failures cannot prove a canonical parent +failed (allowed-failure jobs exist), so history stays UNKNOWN_HISTORY. A +current raw verdict cannot become an AVAILABLE governed result. Partial parent +evidence cannot prove favorable complete history. Known parent failures stay +absorbing across current retries and persisted predecessor unions. + +Default-branch window/order is determined by immutable committed_at, then SHA, +never CI completion/retry time. Off-branch/expired revisions are explicitly +excluded; missing time or branch association leaves ordering UNKNOWN and the +required series PARTIAL. The metadata discloses latest_decisive_default_branch, +verification units, native/retained/aliased parent identities, current attempts, +excluded revisions, diagnostics and coverage. Historical counts remain one +per immutable revision. A broken predecessor cannot erase a proved failure +into favorable unknown history; the real store gap is retained. + +Integrity replays raw native facts and compares all four emitted observation +envelopes, including normalized records and provenance, before evaluation. +Altered project/time/provider/version/digest/records/metadata are refused. This +introduces the optional rule described in [vitals.md](vitals.md); absent these +inputs, the historical collector, evaluator, bundle shapes and bytes replay +unchanged. No core GitLab collection or accepted compatibility migration is +introduced. R11–R53 implementation fixtures exercise this boundary and durable +history; independent whole-case fidelity acceptance remains pending. diff --git a/docs/spec/observations.md b/docs/spec/observations.md index 6c17d0e..0c066aa 100644 --- a/docs/spec/observations.md +++ b/docs/spec/observations.md @@ -164,3 +164,14 @@ The governed Integrity extension consumes its status/freshness/value and adds it to input metadata. It never creates extra canonical revision records. Absent input retains historical evaluation. Unknown/stale/unsupported values fail closed under the versioned rule described in [vitals.md](vitals.md). + +## Optional native CI evidence (0.6) + +`ci.native_evidence` is an AVAILABLE/FRESH record of canonical input under +`devostasis.ci-normalization.v1`; `ci.normalization_provenance` is its derived +AVAILABLE/FRESH record. The helper also emits `ci.configured` and +`ci.revision_verdicts_14d`. Integrity recomputes all four envelopes from native +evidence and checks exact bindings before using them. The project and observation +anchor must agree with the enclosing ObservationSet. See +[integrity-ci.md](integrity-ci.md) and `schemas/ci-normalization.schema.json`. +Absent both new inputs, historical observations retain their existing meaning. diff --git a/docs/spec/vitals.md b/docs/spec/vitals.md index be2a478..3c61072 100644 --- a/docs/spec/vitals.md +++ b/docs/spec/vitals.md @@ -560,3 +560,17 @@ a band. Positive RAW_RUNS cannot be AVAILABLE: it retains failure-sticky one-revision arithmetic, reports DEGRADED when a band is otherwise available, and sample_strength PROVISIONAL_NONINDEPENDENT. Existing UNKNOWN precedence, thresholds/windows and per-Vital comparison boundaries remain intact. + +## Optional Integrity native normalization (0.6) + +`ci.native_evidence` / `ci.normalization_provenance` select +`integrity.bands.v1+ci-unit-004+hist-002+ci-normalization-001`. Both are added to +input metadata; raw facts must reproduce the exact configured, revision-series +and provenance envelopes. Derived ci_normalization and +latest_decisive_default_branch expose the replayed metadata. Incomplete native +coverage reduces an otherwise AVAILABLE evaluation to DEGRADED; required +PARTIAL series/unknown history keep existing UNKNOWN precedence. Thresholds, +14-day window and durable history semantics are unchanged. Absent these inputs, +the exact historical rule remains. Delta treats the new rule as an explicit +per-Vital boundary. If sample_semantics is also present, the combined rule is +`integrity.bands.v1+ci-unit-004+hist-002+ci-normalization-001+independence-001`. diff --git a/pyproject.toml b/pyproject.toml index 956baa1..d48fa79 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,7 +27,7 @@ classifiers = [ dependencies = [] [project.optional-dependencies] -dev = ["pytest>=8"] +dev = ["pytest>=8", "jsonschema>=4.18"] [project.scripts] devostasis = "devostasis.cli:main" diff --git a/schemas/ci-normalization.schema.json b/schemas/ci-normalization.schema.json new file mode 100644 index 0000000..9a89d8f --- /dev/null +++ b/schemas/ci-normalization.schema.json @@ -0,0 +1,778 @@ +{ + "type": "object", + "additionalProperties": false, + "required": [ + "contract", + "project", + "outcome_map", + "observed_at", + "configured", + "coverage", + "revisions", + "parents", + "children", + "rerun_links" + ], + "properties": { + "contract": { + "const": "devostasis.ci-normalization.v1" + }, + "project": { + "$ref": "#/$defs/project" + }, + "outcome_map": { + "enum": [ + "devostasis.ci-outcomes.github.v1", + "devostasis.ci-outcomes.gitlab.v1" + ] + }, + "observed_at": { + "type": "string", + "minLength": 1, + "maxLength": 2048 + }, + "configured": { + "type": [ + "boolean", + "null" + ] + }, + "coverage": { + "enum": [ + "COMPLETE", + "PARTIAL", + "STALE", + "UNAVAILABLE" + ] + }, + "revisions": { + "type": "array", + "items": { + "$ref": "#/$defs/revision" + }, + "maxItems": 10000 + }, + "parents": { + "oneOf": [ + { + "type": "array", + "items": { + "$ref": "#/$defs/parent" + }, + "maxItems": 10000 + }, + { + "type": "object", + "additionalProperties": false, + "required": [ + "pages" + ], + "properties": { + "pages": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "ordinal", + "has_next", + "items" + ], + "properties": { + "ordinal": { + "type": "integer", + "minimum": 1 + }, + "has_next": { + "type": "boolean" + }, + "items": { + "type": "array", + "items": { + "$ref": "#/$defs/parent" + }, + "maxItems": 10000 + } + } + }, + "minItems": 1, + "maxItems": 1000 + } + } + } + ] + }, + "children": { + "type": "array", + "items": { + "$ref": "#/$defs/child" + }, + "maxItems": 10000 + }, + "rerun_links": { + "type": "array", + "items": { + "$ref": "#/$defs/rerun_link" + }, + "maxItems": 10000 + } + }, + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/drevendev/Devostasis/schemas/ci-normalization.schema.json", + "$defs": { + "project": { + "type": "object", + "additionalProperties": false, + "required": [ + "provider", + "forge_instance", + "immutable_project_id", + "default_branch" + ], + "properties": { + "provider": { + "enum": [ + "github", + "gitlab" + ] + }, + "forge_instance": { + "type": "string", + "minLength": 1, + "maxLength": 2048 + }, + "immutable_project_id": { + "type": "string", + "minLength": 1, + "maxLength": 2048 + }, + "default_branch": { + "type": "string", + "minLength": 1, + "maxLength": 2048 + } + } + }, + "reference": { + "type": "object", + "additionalProperties": false, + "required": [ + "surface", + "id" + ], + "properties": { + "surface": { + "enum": [ + "actions", + "checks", + "pipelines" + ] + }, + "id": { + "type": "integer", + "minimum": 1 + } + } + }, + "revision": { + "type": "object", + "additionalProperties": false, + "required": [ + "revision", + "committed_at", + "default_branch" + ], + "properties": { + "revision": { + "type": "string", + "minLength": 1, + "maxLength": 2048 + }, + "committed_at": { + "type": [ + "string", + "null" + ], + "maxLength": 2048 + }, + "default_branch": { + "type": [ + "boolean", + "null" + ] + } + } + }, + "parent": { + "oneOf": [ + { + "type": "object", + "additionalProperties": false, + "required": [ + "surface", + "revision", + "observed_at", + "coverage", + "native" + ], + "properties": { + "surface": { + "const": "actions" + }, + "revision": { + "type": "string", + "minLength": 1, + "maxLength": 2048 + }, + "observed_at": { + "type": "string", + "minLength": 1, + "maxLength": 2048 + }, + "coverage": { + "enum": [ + "COMPLETE", + "PARTIAL", + "STALE", + "UNAVAILABLE" + ] + }, + "native": { + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "head_sha", + "status", + "conclusion", + "run_attempt" + ], + "properties": { + "id": { + "type": "integer", + "minimum": 1 + }, + "head_sha": { + "type": "string", + "minLength": 1, + "maxLength": 2048 + }, + "status": { + "type": [ + "string", + "null" + ], + "maxLength": 2048 + }, + "conclusion": { + "type": [ + "string", + "null" + ], + "maxLength": 2048 + }, + "run_attempt": { + "type": "integer", + "minimum": 1 + }, + "check_suite_id": { + "anyOf": [ + { + "type": "integer", + "minimum": 1 + }, + { + "type": "null" + } + ] + }, + "workflow_id": { + "anyOf": [ + { + "type": "integer", + "minimum": 1 + }, + { + "type": "null" + } + ] + }, + "name": { + "type": [ + "string", + "null" + ], + "maxLength": 2048 + }, + "event": { + "type": [ + "string", + "null" + ], + "maxLength": 2048 + }, + "html_url": { + "type": [ + "string", + "null" + ], + "maxLength": 2048 + }, + "updated_at": { + "type": [ + "string", + "null" + ], + "maxLength": 2048 + }, + "completed_at": { + "type": [ + "string", + "null" + ], + "maxLength": 2048 + } + } + } + } + }, + { + "type": "object", + "additionalProperties": false, + "required": [ + "surface", + "revision", + "observed_at", + "coverage", + "native" + ], + "properties": { + "surface": { + "const": "checks" + }, + "revision": { + "type": "string", + "minLength": 1, + "maxLength": 2048 + }, + "observed_at": { + "type": "string", + "minLength": 1, + "maxLength": 2048 + }, + "coverage": { + "enum": [ + "COMPLETE", + "PARTIAL", + "STALE", + "UNAVAILABLE" + ] + }, + "native": { + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "head_sha", + "status", + "conclusion" + ], + "properties": { + "id": { + "type": "integer", + "minimum": 1 + }, + "head_sha": { + "type": "string", + "minLength": 1, + "maxLength": 2048 + }, + "status": { + "type": [ + "string", + "null" + ], + "maxLength": 2048 + }, + "conclusion": { + "type": [ + "string", + "null" + ], + "maxLength": 2048 + }, + "app": { + "type": "object", + "additionalProperties": false, + "required": [], + "properties": { + "id": { + "type": "integer", + "minimum": 1 + }, + "slug": { + "type": "string", + "minLength": 1, + "maxLength": 2048 + } + } + }, + "url": { + "type": [ + "string", + "null" + ], + "maxLength": 2048 + }, + "updated_at": { + "type": [ + "string", + "null" + ], + "maxLength": 2048 + }, + "completed_at": { + "type": [ + "string", + "null" + ], + "maxLength": 2048 + } + } + } + } + }, + { + "type": "object", + "additionalProperties": false, + "required": [ + "surface", + "revision", + "observed_at", + "coverage", + "native" + ], + "properties": { + "surface": { + "const": "pipelines" + }, + "revision": { + "type": "string", + "minLength": 1, + "maxLength": 2048 + }, + "observed_at": { + "type": "string", + "minLength": 1, + "maxLength": 2048 + }, + "coverage": { + "enum": [ + "COMPLETE", + "PARTIAL", + "STALE", + "UNAVAILABLE" + ] + }, + "native": { + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "sha", + "status" + ], + "properties": { + "id": { + "type": "integer", + "minimum": 1 + }, + "sha": { + "type": "string", + "minLength": 1, + "maxLength": 2048 + }, + "status": { + "type": [ + "string", + "null" + ], + "maxLength": 2048 + }, + "ref": { + "type": [ + "string", + "null" + ], + "maxLength": 2048 + }, + "source": { + "type": [ + "string", + "null" + ], + "maxLength": 2048 + }, + "web_url": { + "type": [ + "string", + "null" + ], + "maxLength": 2048 + }, + "updated_at": { + "type": [ + "string", + "null" + ], + "maxLength": 2048 + }, + "finished_at": { + "type": [ + "string", + "null" + ], + "maxLength": 2048 + } + } + } + } + } + ] + }, + "child": { + "oneOf": [ + { + "type": "object", + "additionalProperties": false, + "required": [ + "surface", + "revision", + "observed_at", + "coverage", + "native", + "parent" + ], + "properties": { + "surface": { + "const": "check-run" + }, + "revision": { + "type": "string", + "minLength": 1, + "maxLength": 2048 + }, + "observed_at": { + "type": "string", + "minLength": 1, + "maxLength": 2048 + }, + "coverage": { + "enum": [ + "COMPLETE", + "PARTIAL", + "STALE", + "UNAVAILABLE" + ] + }, + "native": { + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "head_sha", + "status", + "conclusion" + ], + "properties": { + "id": { + "type": "integer", + "minimum": 1 + }, + "head_sha": { + "type": "string", + "minLength": 1, + "maxLength": 2048 + }, + "status": { + "type": [ + "string", + "null" + ], + "maxLength": 2048 + }, + "conclusion": { + "type": [ + "string", + "null" + ], + "maxLength": 2048 + }, + "name": { + "type": [ + "string", + "null" + ], + "maxLength": 2048 + } + } + }, + "parent": { + "anyOf": [ + { + "$ref": "#/$defs/reference" + }, + { + "type": "null" + } + ] + } + } + }, + { + "type": "object", + "additionalProperties": false, + "required": [ + "surface", + "revision", + "observed_at", + "coverage", + "native", + "parent" + ], + "properties": { + "surface": { + "const": "job" + }, + "revision": { + "type": "string", + "minLength": 1, + "maxLength": 2048 + }, + "observed_at": { + "type": "string", + "minLength": 1, + "maxLength": 2048 + }, + "coverage": { + "enum": [ + "COMPLETE", + "PARTIAL", + "STALE", + "UNAVAILABLE" + ] + }, + "native": { + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "sha", + "status" + ], + "properties": { + "id": { + "type": "integer", + "minimum": 1 + }, + "sha": { + "type": "string", + "minLength": 1, + "maxLength": 2048 + }, + "status": { + "type": [ + "string", + "null" + ], + "maxLength": 2048 + }, + "name": { + "type": [ + "string", + "null" + ], + "maxLength": 2048 + } + } + }, + "parent": { + "anyOf": [ + { + "$ref": "#/$defs/reference" + }, + { + "type": "null" + } + ] + } + } + } + ] + }, + "rerun_link": { + "type": "object", + "additionalProperties": false, + "required": [ + "contract", + "provider", + "forge_instance", + "project_id", + "revision", + "earlier", + "later", + "status", + "evidence_ref" + ], + "properties": { + "contract": { + "const": "devostasis.ci-rerun-link.v1" + }, + "provider": { + "enum": [ + "github", + "gitlab" + ] + }, + "forge_instance": { + "type": "string", + "minLength": 1, + "maxLength": 2048 + }, + "project_id": { + "type": "string", + "minLength": 1, + "maxLength": 2048 + }, + "revision": { + "type": "string", + "minLength": 1, + "maxLength": 2048 + }, + "earlier": { + "$ref": "#/$defs/reference" + }, + "later": { + "$ref": "#/$defs/reference" + }, + "status": { + "enum": [ + "AVAILABLE", + "UNKNOWN", + "UNAVAILABLE", + "FORBIDDEN", + "ERROR" + ] + }, + "evidence_ref": { + "type": [ + "string", + "null" + ], + "maxLength": 2048 + } + } + } + }, + "description": "Bounded provider-native offline acquisition facts. Runtime additionally enforces the 16 MiB bound, timestamps, provider/map/surface and immutable revision bindings, unique revision metadata, contiguous page ordinals and explicit rerun proof scope. Canonical output flattens pages, sorts and deduplicates identical facts. Evidence links are acquisition claims, not provider signatures." +} diff --git a/schemas/conformance-bridge.schema.json b/schemas/conformance-bridge.schema.json index 7338562..5d990b2 100644 --- a/schemas/conformance-bridge.schema.json +++ b/schemas/conformance-bridge.schema.json @@ -765,6 +765,351 @@ } } } + }, + { + "type": "object", + "additionalProperties": false, + "required": [ + "case", + "scenario", + "given", + "checks" + ], + "properties": { + "case": { + "type": "string", + "minLength": 1, + "maxLength": 2048 + }, + "scenario": { + "const": "vital-consumers.v1" + }, + "given": { + "type": "object", + "additionalProperties": false, + "required": [ + "observed_at", + "observations" + ], + "properties": { + "observed_at": { + "type": "string", + "minLength": 1, + "maxLength": 2048 + }, + "observations": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "observation_id", + "value_type", + "status", + "value" + ], + "properties": { + "observation_id": { + "type": "string", + "minLength": 1, + "maxLength": 2048 + }, + "value_type": { + "enum": [ + "count", + "ratio", + "duration", + "boolean", + "enum", + "string", + "set", + "series", + "record" + ] + }, + "status": { + "enum": [ + "AVAILABLE", + "PARTIAL", + "UNAVAILABLE", + "FORBIDDEN", + "UNKNOWN", + "ERROR" + ] + }, + "freshness": { + "enum": [ + "FRESH", + "STALE", + "UNKNOWN" + ] + }, + "value": {}, + "coverage": { + "type": [ + "object", + "null" + ] + } + } + }, + "minItems": 1 + } + } + }, + "checks": { + "type": "array", + "minItems": 1, + "items": { + "oneOf": [ + { + "type": "object", + "additionalProperties": false, + "required": [ + "path", + "equals" + ], + "properties": { + "path": { + "type": "array", + "items": { + "type": [ + "string", + "integer" + ] + } + }, + "equals": {} + } + }, + { + "type": "object", + "additionalProperties": false, + "required": [ + "path", + "not_equals" + ], + "properties": { + "path": { + "type": "array", + "items": { + "type": [ + "string", + "integer" + ] + } + }, + "not_equals": {} + } + }, + { + "type": "object", + "additionalProperties": false, + "required": [ + "path", + "contains" + ], + "properties": { + "path": { + "type": "array", + "items": { + "type": [ + "string", + "integer" + ] + } + }, + "contains": {} + } + }, + { + "type": "object", + "additionalProperties": false, + "required": [ + "path", + "absent" + ], + "properties": { + "path": { + "type": "array", + "items": { + "type": [ + "string", + "integer" + ] + } + }, + "absent": { + "const": true + } + } + } + ] + } + } + } + }, + { + "type": "object", + "additionalProperties": false, + "required": [ + "case", + "scenario", + "given", + "checks" + ], + "properties": { + "case": { + "type": "string", + "minLength": 1, + "maxLength": 2048 + }, + "scenario": { + "const": "ci-revisions.v1" + }, + "given": { + "type": "object", + "additionalProperties": false, + "required": [ + "variants" + ], + "properties": { + "variants": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "name", + "steps" + ], + "properties": { + "name": { + "type": "string", + "minLength": 1, + "maxLength": 2048 + }, + "steps": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "history", + "evidence" + ], + "properties": { + "history": { + "enum": [ + "BASELINE", + "CONTINUE", + "GAP" + ] + }, + "evidence": { + "$ref": "ci-normalization.schema.json" + } + } + }, + "minItems": 1 + } + } + }, + "minItems": 1 + } + } + }, + "checks": { + "type": "array", + "minItems": 1, + "items": { + "oneOf": [ + { + "type": "object", + "additionalProperties": false, + "required": [ + "path", + "equals" + ], + "properties": { + "path": { + "type": "array", + "items": { + "type": [ + "string", + "integer" + ] + } + }, + "equals": {} + } + }, + { + "type": "object", + "additionalProperties": false, + "required": [ + "path", + "not_equals" + ], + "properties": { + "path": { + "type": "array", + "items": { + "type": [ + "string", + "integer" + ] + } + }, + "not_equals": {} + } + }, + { + "type": "object", + "additionalProperties": false, + "required": [ + "path", + "contains" + ], + "properties": { + "path": { + "type": "array", + "items": { + "type": [ + "string", + "integer" + ] + } + }, + "contains": {} + } + }, + { + "type": "object", + "additionalProperties": false, + "required": [ + "path", + "absent" + ], + "properties": { + "path": { + "type": "array", + "items": { + "type": [ + "string", + "integer" + ] + } + }, + "absent": { + "const": true + } + } + } + ] + } + } + } } ] } diff --git a/scripts/check_conformance_surface.py b/scripts/check_conformance_surface.py index ddd5ccc..04e4cfb 100644 --- a/scripts/check_conformance_surface.py +++ b/scripts/check_conformance_surface.py @@ -6,12 +6,12 @@ ROOT = Path(__file__).resolve().parents[1] sys.path[:0] = [str(ROOT / "src"), str(ROOT / "tests")] -from surface_bridge import CONTRACT, execute, load +from surface_bridge import CONTRACT, execute, load_suite from devostasis.vectors import load as load_vectors def main(): - cases = load(ROOT / "tests/fixtures/conformance-surface.json") + cases = load_suite() old = {v.case for v in load_vectors([ROOT / "tests/vectors"])} if old.intersection(c["case"] for c in cases): raise ValueError("duplicate permanent case across carriers") for case in cases: diff --git a/src/devostasis/adapters/ci.py b/src/devostasis/adapters/ci.py new file mode 100644 index 0000000..ac6a841 --- /dev/null +++ b/src/devostasis/adapters/ci.py @@ -0,0 +1,319 @@ +"""Versioned offline CI evidence: native parents, explicit links and child fallback. + +Input is a bounded projection of provider evidence, not a transport or a new +classifier. Old GitHub collection and historical observation bytes are unchanged. +""" + +from copy import deepcopy + +from .. import canonical, timeutil +from ..observations import Observation, ObservationSet +from ..revision_history import current_history +from . import github_ci as gh, gitlab_ci as gl + +CONTRACT = "devostasis.ci-normalization.v1" +LINK_CONTRACT = "devostasis.ci-rerun-link.v1" +NATIVE = "ci.native_evidence" +PROVENANCE = "ci.normalization_provenance" +SURFACES = {"github": {"actions": gh.ACTIONS_KIND, "checks": "github_check_suite"}, + "gitlab": {"pipelines": "gitlab_pipeline"}} +PREFIX = {"actions": "github_actions:workflow_run:", "checks": "github_checks:check_suite:", "pipelines": "gitlab:pipeline:"} +AVAILABILITY = {"COMPLETE", "PARTIAL", "STALE", "UNAVAILABLE"} +NATIVE_KEYS = {"actions": {"id", "head_sha", "run_attempt", "status", "conclusion", "check_suite_id", "name", "event", "workflow_id", "html_url", "updated_at", "completed_at"}, + "checks": {"id", "head_sha", "status", "conclusion", "app", "url", "updated_at", "completed_at"}, + "pipelines": {"id", "sha", "status", "ref", "source", "web_url", "updated_at", "finished_at"}, + "check-run": {"id", "head_sha", "status", "conclusion", "name"}, + "job": {"id", "sha", "status", "name"}} + + +def require(condition, message): + if not condition: raise ValueError(message) + + +def fields(value, required, allowed=None): + require(isinstance(value, dict) and set(required) <= set(value) <= set(allowed or required), "unknown or missing CI evidence fields") + + +def text(value): + require(isinstance(value, str) and 0 < len(value) <= 2048, "CI identity/text unavailable") + return value + + +def identifier(value): + require(type(value) is int and value > 0, "provider id must be a positive integer") + return value + + +def reference(value, provider): + fields(value, ("surface", "id")) + require(value["surface"] in SURFACES[provider], "foreign parent surface") + return PREFIX[value["surface"]] + str(identifier(value["id"])) + + +def evidence(row, provider, *, child=False): + fields(row, ("surface", "revision", "observed_at", "coverage", "native", *(["parent"] if child else []))) + surface, data = row["surface"], row["native"] + require(surface in (("check-run",) if provider == "github" else ("job",)) if child else surface in SURFACES[provider], "foreign provider evidence") + required = {"id", "sha" if provider == "gitlab" else "head_sha", "status"} + if provider == "github": required.add("conclusion") + if surface == "actions": required.add("run_attempt") + fields(data, required, NATIVE_KEYS[surface]); identifier(data["id"]) + text(row["revision"]); timeutil.parse_ts(row["observed_at"]) + require(row["coverage"] in AVAILABILITY, "unknown CI source coverage") + require(data.get("sha" if provider == "gitlab" else "head_sha") == row["revision"], "native revision binding mismatch") + for key in ("status", "conclusion"): + if key in data: require(data[key] is None or isinstance(data[key], str), "untyped native outcome") + if surface == "actions": identifier(data["run_attempt"]) + for key in ("name", "event", "html_url", "url", "ref", "source", "web_url", "updated_at", "completed_at", "finished_at"): + if data.get(key) is not None: text(data[key]) + if data.get("workflow_id") is not None: identifier(data["workflow_id"]) + if data.get("check_suite_id") is not None: identifier(data["check_suite_id"]) + if "app" in data: + fields(data["app"], (), ("id", "slug")) + if "id" in data["app"]: identifier(data["app"]["id"]) + if "slug" in data["app"]: text(data["app"]["slug"]) + if child and row["parent"] is not None: reference(row["parent"], provider) + state = gl.normalize_outcome(data["status"]) if provider == "gitlab" else gh.normalize_outcome(data["status"], data["conclusion"]) + if provider == "github" and data["status"] not in ("completed", "queued", "in_progress", "waiting", "pending", "requested"): + state = "UNKNOWN" + return "UNKNOWN" if row["coverage"] in ("STALE", "UNAVAILABLE") else state + + +def canonical_input(value): + fields(value, ("contract", "project", "outcome_map", "observed_at", "configured", "coverage", "revisions", "parents", "children", "rerun_links")) + require(value["contract"] == CONTRACT, "unsupported CI normalization contract") + project = value["project"] + fields(project, ("provider", "forge_instance", "immutable_project_id", "default_branch")) + require(project["provider"] in SURFACES, "unsupported CI provider") + for item in project.values(): text(item) + expected = gh.OUTCOME_MAP_VERSION if project["provider"] == "github" else gl.OUTCOME_MAP_VERSION + require(value["outcome_map"] == expected, "mixed provider/outcome-map lineage") + timeutil.parse_ts(value["observed_at"]) + require(value["configured"] is None or type(value["configured"]) is bool, "CI configured must be boolean or unknown") + require(value["coverage"] in AVAILABILITY, "unknown CI inventory coverage") + result = deepcopy(value) + if isinstance(result["parents"], dict): + fields(result["parents"], ("pages",)) + pages = result["parents"]["pages"] + require(isinstance(pages, list) and 0 < len(pages) <= 1000, "CI pagination cap") + for page in pages: + fields(page, ("ordinal", "has_next", "items")); identifier(page["ordinal"]) + require(type(page["has_next"]) is bool and isinstance(page["items"], list), "malformed CI page") + pages.sort(key=lambda p: p["ordinal"]) + require([p["ordinal"] for p in pages] == list(range(1, len(pages) + 1)), "missing or duplicate CI page") + require(all(p["has_next"] for p in pages[:-1]), "contradictory CI continuation") + if pages[-1]["has_next"]: result["coverage"] = "PARTIAL" + result["parents"] = [item for p in pages for item in p["items"]] + for key in ("revisions", "parents", "children", "rerun_links"): + rows = result[key] + require(isinstance(rows, list) and len(rows) <= 10000, "CI evidence cardinality cap") + result[key] = [canonical.loads(data.decode()) for data in sorted({canonical.canonical_bytes(row) for row in rows})] + require(len(canonical.canonical_bytes(result)) <= 16 * 1024 * 1024, "CI evidence byte cap") + return result + + +def parent_from_snapshots(rows, provider): + surface = rows[0]["surface"] + if surface == "actions": + suites = {r["native"]["check_suite_id"] for r in rows if r["native"].get("check_suite_id") is not None} + require(len(suites) <= 1, "workflow run changed immutable suite binding") + ordinal = max(r["native"]["run_attempt"] for r in rows) if surface == "actions" else None + latest = [r for r in rows if surface != "actions" or r["native"]["run_attempt"] == ordinal] + instant = max(timeutil.parse_ts(r["observed_at"]) for r in latest) + latest = [r for r in latest if timeutil.parse_ts(r["observed_at"]) == instant] + states = {evidence(r, provider) for r in latest} + current = next(iter(states)) if len(states) == 1 else "UNKNOWN" + row = min(latest, key=canonical.canonical_bytes); data = row["native"] + attempts = [{"attempt": r["native"]["run_attempt"] if surface == "actions" else None, "state": evidence(r, provider)} for r in rows] + attempts = [canonical.loads(b.decode()) for b in sorted({canonical.canonical_bytes(a) for a in attempts})] + if any(r["coverage"] == "PARTIAL" for r in rows): + # A partial parent projection cannot prove that its attempt history is + # complete, even if the visible attempt numbers happen to be contiguous. + attempts.append({"attempt": None, "state": "UNKNOWN"}) + parent = {"parent_id": PREFIX[surface] + str(data["id"]), "kind": SURFACES[provider][surface], + "name": data.get("name") or surface, "event": data.get("event") or data.get("source"), + "current_attempt": ordinal, "current_state": current, "attempts_observed": attempts, + "attempts_complete": False, "history_state": "UNKNOWN_HISTORY", "url": data.get("html_url") or data.get("web_url") or data.get("url")} + state, complete = current_history([parent]); parent.update(history_state=state, attempts_complete=complete) + return parent + + +def collapse_links(parents, links, project, revision, diagnostics): + """Only non-branching, acyclic, scope-bound provider evidence can collapse ids.""" + forward, backward, involved = {}, {}, set() + valid = True + for link in links: + fields(link, ("contract", "provider", "forge_instance", "project_id", "revision", "earlier", "later", "status", "evidence_ref")) + require(link["contract"] == LINK_CONTRACT, "unknown rerun-link lineage") + require(link["provider"] in SURFACES and link["status"] in ("AVAILABLE", "UNKNOWN", "UNAVAILABLE", "FORBIDDEN", "ERROR"), "unknown rerun-link availability/provider") + for key in ("forge_instance", "project_id", "revision"): text(link[key]) + if link["evidence_ref"] is not None: text(link["evidence_ref"]) + a, b = reference(link["earlier"], project["provider"]), reference(link["later"], project["provider"]) + scoped = (link["provider"] == project["provider"] and link["forge_instance"] == project["forge_instance"] + and link["project_id"] == project["immutable_project_id"] and link["revision"] == revision + and a in parents and b in parents and parents[a]["kind"] == parents[b]["kind"]) + if not scoped or link["status"] != "AVAILABLE" or not isinstance(link["evidence_ref"], str) or not link["evidence_ref"]: + valid = False; continue + involved.update((a, b)) + if (a in forward and forward[a] != b) or (b in backward and backward[b] != a) or a == b: valid = False + forward[a] = b; backward[b] = a + roots = sorted(involved - set(backward)) + visited, chains = set(), [] + for root in roots: + chain, cursor = [], root + while cursor not in visited: + visited.add(cursor); chain.append(cursor) + if cursor not in forward: break + cursor = forward[cursor] + else: + valid = False + chains.append(chain) + if visited != involved: valid = False + if not valid: + diagnostics.add("RERUN_LINK_UNPROVEN_OR_AMBIGUOUS") + return parents, {} + output, aliases = dict(parents), {} + for chain in chains: + if len(chain) < 2: continue + # Keep the oldest native key stable; current state comes only from leaf. + parent = {**parents[chain[-1]], "parent_id": chain[0]} + parent["current_attempt"] = None + parent["attempts_observed"] = [{"attempt": None, "state": state} for state in sorted({a["state"] for key in chain for a in parents[key]["attempts_observed"]})] + state, complete = current_history([parent]); parent.update(history_state=state, attempts_complete=complete) + for key in chain: output.pop(key) + output[chain[0]] = parent; aliases[chain[0]] = chain + return output, aliases + + +def normalize(value): + value = canonical_input(value) + project, at = value["project"], timeutil.parse_ts(value["observed_at"]) + provider = project["provider"] + start = timeutil.minus_days(at, 14) + diagnostics, scope, excluded, seen = set(), {}, [], set() + ordering = "KNOWN" + for row in value["revisions"]: + fields(row, ("revision", "committed_at", "default_branch")); ref = text(row["revision"]) + require(ref not in seen, "duplicate immutable revision metadata"); seen.add(ref) + require(row["default_branch"] is None or type(row["default_branch"]) is bool, "untyped default-branch association") + if row["default_branch"] is False: + excluded.append({**row, "reason": "OFF_DEFAULT_BRANCH"}); continue + if row["default_branch"] is None or row["committed_at"] is None: + ordering = "UNKNOWN"; diagnostics.add("REVISION_ORDERING_UNAVAILABLE") + excluded.append({**row, "reason": "ORDERING_UNAVAILABLE"}); continue + committed = timeutil.parse_ts(row["committed_at"]) + if not start <= committed <= at: + excluded.append({**row, "reason": "OUTSIDE_WINDOW"}); continue + scope[ref] = row + grouped, children, bindings = {}, {}, {} + for row in value["parents"]: + evidence(row, provider) + require(timeutil.parse_ts(row["observed_at"]) <= at, "CI evidence is from after the observation boundary") + require(row["revision"] in seen, "parent has no immutable revision metadata") + if row["revision"] in scope: + key = PREFIX[row["surface"]] + str(row["native"]["id"]) + require(key not in bindings or bindings[key] == row["revision"], "native parent identity changed immutable revision") + bindings[key] = row["revision"] + grouped.setdefault(row["revision"], {}).setdefault(key, []).append(row) + for row in value["children"]: + evidence(row, provider, child=True) + require(timeutil.parse_ts(row["observed_at"]) <= at, "child evidence is from after the observation boundary") + require(row["revision"] in seen, "child has no immutable revision metadata") + if row["parent"] is not None: + parent_key = reference(row["parent"], provider) + require(parent_key not in bindings or bindings[parent_key] == row["revision"], "child parent belongs to another revision") + children.setdefault(row["revision"], []).append(row) + links = {} + for row in value["rerun_links"]: + require(isinstance(row, dict) and row.get("revision") in scope, "rerun link has no admitted revision") + links.setdefault(row["revision"], []).append(row) + records, provenance, units = [], {}, {} + for ref, revision in sorted(scope.items(), key=lambda pair: (timeutil.parse_ts(pair[1]["committed_at"]), pair[0])): + rows = grouped.get(ref, {}) + parents = {key: parent_from_snapshots(facts, provider) for key, facts in rows.items()} + retained_suites = [] + if provider == "github": + overlap = {} + for key, facts in rows.items(): + for fact in facts: + suite = fact["native"].get("check_suite_id") if fact["surface"] == "actions" else None + if suite is not None: overlap.setdefault("github_checks:check_suite:" + str(suite), set()).add(key) + for suite, actions in sorted(overlap.items()): + if suite not in parents: continue + if len(actions) != 1: + diagnostics.add("ACTIONS_CHECKS_OVERLAP_AMBIGUOUS"); continue + target = parents[next(iter(actions))]; other = parents.pop(suite) + if any(a["state"] == gh.VERIFY_FAIL for a in other["attempts_observed"]) and not any(a["state"] == gh.VERIFY_FAIL for a in target["attempts_observed"]): + target["attempts_observed"].append({"attempt": None, "state": gh.VERIFY_FAIL}) + # A newer unresolved suite cannot be resurrected as an older pass. + latest_suite = max(timeutil.parse_ts(r["observed_at"]) for r in rows[suite]) + latest_actions = max(timeutil.parse_ts(r["observed_at"]) for r in rows[next(iter(actions))]) + if latest_suite > latest_actions and other["current_state"] != target["current_state"]: + target["current_state"] = "UNKNOWN"; diagnostics.add("LINKED_SURFACES_DISAGREE") + retained_suites.append(suite) + parents, aliases = collapse_links(parents, links.get(ref, []), project, ref, diagnostics) + unit = "PARENT_AGGREGATE" + if not parents and children.get(ref): + # Child snapshots have identity too: earlier terminal states must + # not overrule a newer pending observation of that same child. + by_child = {} + for row in children[ref]: by_child.setdefault(row["native"]["id"], []).append(row) + states = [] + for facts in by_child.values(): + latest = max(timeutil.parse_ts(r["observed_at"]) for r in facts) + current = {evidence(r, provider, child=True) for r in facts if timeutil.parse_ts(r["observed_at"]) == latest} + states.append(next(iter(current)) if len(current) == 1 else "UNKNOWN") + parents = {"revision-child-aggregate:" + ref: {"parent_id": "revision-child-aggregate:" + ref, + "kind": "revision_child_aggregate_raw", "name": "child-only fallback", "event": None, + "current_attempt": None, "current_state": gh.compose_current(states), + # Raw child failures are not proof a canonical parent failed + # (allowed-failure jobs exist). Keep them in native provenance. + "attempts_observed": [{"attempt": None, "state": "UNKNOWN"}], + "attempts_complete": False, "history_state": "UNKNOWN_HISTORY", "url": None}} + unit = "REVISION_CHILD_AGGREGATE_RAW"; diagnostics.add("CHILD_ONLY_NONINDEPENDENT") + ordered = [parents[key] for key in sorted(parents)] + state, complete = current_history(ordered) + current = gh.compose_current([p["current_state"] for p in ordered]) + records.append({"revision": ref, "committed_at": revision["committed_at"], "parents": ordered, + "current_verdict": current, "history_state": state, "historical_contribution": gh.contribution(state), + "history_provenance": gh.ATTEMPT_LEVEL if ordered and all(p["kind"] == gh.ACTIONS_KIND for p in ordered) else gh.PARENT_LEVEL_ONLY if ordered else None, + "history_complete": complete}) + units[ref] = unit + provenance[ref] = {"parent_ids": sorted(parents), "native_parent_ids": sorted(rows), "retained_suites": retained_suites, + "rerun_aliases": aliases, "child_ids": sorted({r["native"]["id"] for r in children.get(ref, [])}), + "current_attempts": {p["parent_id"]: {"ordinal": p["current_attempt"], "terminal": p["current_state"] not in ("UNKNOWN", gh.VERIFY_UNRESOLVED), "state": p["current_state"]} for p in ordered}} + if any(row["coverage"] != "COMPLETE" for row in value["parents"] + value["children"]): diagnostics.add("NATIVE_EVIDENCE_INCOMPLETE") + if any(p["current_state"] == "UNKNOWN" for record in records for p in record["parents"]): diagnostics.add("CURRENT_PARENT_UNKNOWN") + decisive = [r for r in records if r["current_verdict"] in (gh.VERIFY_PASS, gh.VERIFY_FAIL)] + latest = max(decisive, key=lambda r: (timeutil.parse_ts(r["committed_at"]), r["revision"]))["revision"] if decisive else None + meta = {"contract": CONTRACT, "coverage": "DEGRADED" if diagnostics or value["coverage"] != "COMPLETE" else "COMPLETE", + "ordering_status": ordering, "latest_decisive_default_branch": "UNKNOWN" if ordering == "UNKNOWN" else latest, + "verification_units": units, "diagnostics": sorted(diagnostics), "provenance": provenance, "excluded_revisions": excluded} + return {"contract": CONTRACT, "evidence": value, "records": records, "metadata": meta} + + +def observations(value, subject): + normalized = normalize(value); value = normalized["evidence"] + require(all(subject.get(k) == v for k, v in value["project"].items()), "CI project/subject binding mismatch") + obs = ObservationSet(subject=deepcopy(subject), observed_at=value["observed_at"]) + common = {"provider": value["project"]["provider"], "collected_at": obs.observed_at, "adapter_version": CONTRACT, + "source_ref": "native-ci:" + canonical.digest(value)} + obs.add(Observation(observation_id=NATIVE, status="AVAILABLE", value_type="record", value=value, **common)) + obs.add(Observation(observation_id=PROVENANCE, status="AVAILABLE", value_type="record", value=normalized["metadata"], **common)) + configured = value["configured"] + obs.add(Observation(observation_id="ci.configured", status="AVAILABLE" if configured is not None else "UNKNOWN", value_type="boolean", value=configured, **common)) + status = "AVAILABLE" if value["coverage"] == "COMPLETE" and normalized["metadata"]["ordering_status"] == "KNOWN" else "PARTIAL" + obs.add(Observation(observation_id="ci.revision_verdicts_14d", status=status, value_type="series", value=normalized["records"], **common)) + return obs + + +def admitted_metadata(obs): + source, metadata = obs.get(NATIVE), obs.get(PROVENANCE) + require(source is not None and source.good and source.value_type == "record", "native CI evidence unavailable") + expected = observations(source.value, obs.subject) + require(expected.observed_at == obs.observed_at, "native CI observation-time mismatch") + for key in (NATIVE, PROVENANCE, "ci.configured", "ci.revision_verdicts_14d"): + actual, wanted = obs.get(key), expected.get(key) + require(actual is not None and canonical.canonical_bytes(actual.to_dict()) == canonical.canonical_bytes(wanted.to_dict()), + "native CI projection does not replay: " + key) + return metadata.value diff --git a/src/devostasis/vitals/integrity.py b/src/devostasis/vitals/integrity.py index 72ffaaf..da6b6e9 100644 --- a/src/devostasis/vitals/integrity.py +++ b/src/devostasis/vitals/integrity.py @@ -219,11 +219,22 @@ def evaluate(obs: ObservationSet) -> VitalResult: An absent discriminator preserves historical bytes. A present discriminator is consumed, versioned and fail closed; retries never create revision samples. """ + from ..adapters.ci import NATIVE, PROVENANCE, admitted_metadata + native = obs.get(NATIVE) is not None or obs.get(PROVENANCE) is not None + metadata = admitted_metadata(obs) if native else None result = _evaluate_revisions(obs) + if metadata is not None: + result.rule_id += "+ci-normalization-001" + result.inputs += input_meta(obs, [NATIVE, PROVENANCE]) + result.derived["ci_normalization"] = metadata + result.derived["latest_decisive_default_branch"] = metadata["latest_decisive_default_branch"] + if metadata["coverage"] == "DEGRADED" and result.evaluation_status == EVAL_AVAILABLE: + result.evaluation_status = EVAL_DEGRADED + result.diagnostics += metadata["diagnostics"] provenance = obs.get("ci.sample_semantics") if provenance is None: return result - result.rule_id = RULE_ID + "+independence-001" + result.rule_id += "+independence-001" result.inputs += input_meta(obs, ["ci.sample_semantics"]) result.derived["sample_semantics"] = provenance.value if provenance.good else None if not provenance.good or provenance.value_type != "enum" or provenance.value not in ("RAW_RUNS", "INDEPENDENT_REVISIONS"): diff --git a/tests/ci_surface_bridge.py b/tests/ci_surface_bridge.py new file mode 100644 index 0000000..aea9f18 --- /dev/null +++ b/tests/ci_surface_bridge.py @@ -0,0 +1,64 @@ +"""R11–R53 drivers: native projection and real governed/store executions.""" + +from copy import deepcopy + +from devostasis import canonical +from devostasis.adapters import ci +from devostasis.bundle import verify_dir +from devostasis.config import single_project +from devostasis.delta import compare +from devostasis.gauges import gauge_for +from devostasis.history import FilesystemHistoryStore +from devostasis.observations import Observation +from devostasis.runner import build_from_observations +from devostasis.vitals import clutter +from helpers import SUBJECT, finalize, obs_set + + +def consumer(given, root): + ci.fields(given, ("observed_at", "observations")) + obs = obs_set(given["observed_at"]) + for row in given["observations"]: + obs.add(Observation(**row)) + result = clutter.evaluate(obs).to_dict() + previous = {**deepcopy(result), "evaluation_status": "AVAILABLE", "band_semantics": "EXACT", "band": "CLEAN"} + delta = compare({"vitals": [result]}, {"vitals": [previous]}, "COMPARABLE", "synthetic-predecessor") + return {"result": result, "gauge": gauge_for(result), + "delta": next(row for row in delta["vitals"] if row["vital_id"] == "clutter")} + + +def revisions(given, root): + ci.fields(given, ("variants",)) + ci.require(isinstance(given["variants"], list) and given["variants"], "CI variants required") + variants = {} + for variant in given["variants"]: + ci.fields(variant, ("name", "steps")) + name = ci.text(variant["name"]) + ci.require(name not in variants, "duplicate CI variant") + ci.require(isinstance(variant["steps"], list) and variant["steps"], "CI scenario steps required") + store = FilesystemHistoryStore(root / str(len(variants))) + project, checkpoints, previous_path = single_project("acme/widget", config_version="1"), [], None + for step in variant["steps"]: + ci.fields(step, ("history", "evidence")) + ci.require(step["history"] in ("BASELINE", "CONTINUE", "GAP"), "unknown history operation") + ci.require((previous_path is None) == (step["history"] == "BASELINE"), "history baseline must be explicit and first") + if step["history"] == "GAP": + # Retention/provenance loss is an actual broken predecessor, + # never a made-up current snapshot or a fresh BASELINE. + (previous_path / "snapshot.json").unlink() + subject = {**SUBJECT, **step["evidence"]["project"]} + obs = finalize(ci.observations(step["evidence"], subject)) + normalized = ci.normalize(step["evidence"]) + bundle = build_from_observations(project, obs, store) + previous_path = store.commit(bundle) + snapshot = canonical.loads(bundle.members["snapshot.json"].decode()) + result = next(v for v in snapshot["vitals"] if v["vital_id"] == "integrity") + current = {r["revision"]: r for r in normalized["records"]} + history = {r["revision"]: r for r in result["derived"]["revision_history"]["records"]} + checkpoints.append({"normalization": normalized["metadata"], "current": current, "history": history, + "integrity": result, "comparison": bundle.manifest["comparison_status"], "verified": verify_dir(previous_path) == [], + "current_revision_count": len(current), + "current_decisive_count": sum(r["current_verdict"] in ("VERIFY_PASS", "VERIFY_FAIL") for r in current.values()), + "current_failed_count": sum(r["current_verdict"] == "VERIFY_FAIL" for r in current.values())}) + variants[name] = {"steps": checkpoints} + return variants diff --git a/tests/fixtures/ci-revisions.json b/tests/fixtures/ci-revisions.json new file mode 100644 index 0000000..2afbc79 --- /dev/null +++ b/tests/fixtures/ci-revisions.json @@ -0,0 +1,17569 @@ +{ + "schema": "devostasis.conformance-bridge.v1", + "source": "PV-TEST-002 exact R11-R53 records, permanent-ID expansion accepted by PV-REV-TEST-002 (https://docs.google.com/document/d/1zsqtsqW-LldkAqYRcO-a9ztPj8ZTg_duwGDvV2N87Ck/edit); PV-CI-UNIT-004, PV-HIST-002 and later accepted fail-closed rules govern projections. Implementation materialization awaits independent review.", + "cases": [ + { + "case": "R11", + "scenario": "vital-consumers.v1", + "given": { + "observed_at": "2026-09-05T12:00:00Z", + "observations": [ + { + "observation_id": "forge.change_requests.open_count", + "value_type": "count", + "status": "AVAILABLE", + "value": 10 + }, + { + "observation_id": "forge.change_requests.stale_open_count_14d", + "value_type": "count", + "status": "AVAILABLE", + "value": 1 + }, + { + "observation_id": "forge.issues.open_count", + "value_type": "count", + "status": "UNAVAILABLE", + "value": null + }, + { + "observation_id": "forge.issues.stale_open_count_30d", + "value_type": "count", + "status": "UNAVAILABLE", + "value": null + }, + { + "observation_id": "git.nondefault_branches.stale_count_30d", + "value_type": "count", + "status": "AVAILABLE", + "value": 0 + } + ] + }, + "checks": [ + { + "path": [ + "result", + "evaluation_status" + ], + "equals": "DEGRADED" + }, + { + "path": [ + "result", + "band" + ], + "equals": "LIGHT" + }, + { + "path": [ + "result", + "band_semantics" + ], + "equals": "CONSERVATIVE_LOWER_BOUND" + }, + { + "path": [ + "result", + "derived", + "possible_bands_semantics" + ], + "equals": "NON_AUTHORITATIVE_CONSERVATIVE_SUPERSET" + }, + { + "path": [ + "result", + "possible_bands" + ], + "contains": "LIGHT" + }, + { + "path": [ + "result", + "possible_bands" + ], + "contains": "CLUTTERED" + }, + { + "path": [ + "result", + "possible_bands" + ], + "contains": "HEAVY" + }, + { + "path": [ + "gauge", + "qualifier" + ], + "equals": "at_least" + }, + { + "path": [ + "delta", + "transition_class" + ], + "equals": "CHANGED" + }, + { + "path": [ + "result", + "exact_reachable_bands" + ], + "absent": true + } + ] + }, + { + "case": "R12", + "scenario": "ci-revisions.v1", + "given": { + "variants": [ + { + "name": "parent-and-children", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "checks", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 501, + "head_sha": "r", + "status": "completed", + "conclusion": "failure", + "app": { + "id": 42, + "slug": "same-app" + } + } + } + ], + "children": [ + { + "surface": "check-run", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "parent": { + "surface": "checks", + "id": 501 + }, + "native": { + "id": 1, + "head_sha": "r", + "status": "completed", + "conclusion": "success", + "name": "synthetic child" + } + }, + { + "surface": "check-run", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "parent": { + "surface": "checks", + "id": 501 + }, + "native": { + "id": 2, + "head_sha": "r", + "status": "completed", + "conclusion": "success", + "name": "synthetic child" + } + }, + { + "surface": "check-run", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "parent": { + "surface": "checks", + "id": 501 + }, + "native": { + "id": 3, + "head_sha": "r", + "status": "completed", + "conclusion": "success", + "name": "synthetic child" + } + }, + { + "surface": "check-run", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "parent": { + "surface": "checks", + "id": 501 + }, + "native": { + "id": 4, + "head_sha": "r", + "status": "completed", + "conclusion": "success", + "name": "synthetic child" + } + }, + { + "surface": "check-run", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "parent": { + "surface": "checks", + "id": 501 + }, + "native": { + "id": 5, + "head_sha": "r", + "status": "completed", + "conclusion": "failure", + "name": "synthetic child" + } + } + ], + "rerun_links": [] + } + } + ] + } + ] + }, + "checks": [ + { + "path": [ + "parent-and-children", + "steps", + 0, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "parent-and-children", + "steps", + 0, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "parent-and-children", + "steps", + 0, + "history", + "r", + "history_state" + ], + "equals": "FAILURE_OBSERVED" + }, + { + "path": [ + "parent-and-children", + "steps", + 0, + "history", + "r", + "historical_contribution" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "parent-and-children", + "steps", + 0, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "parent-and-children", + "steps", + 0, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "parent-and-children", + "steps", + 0, + "integrity", + "evaluation_status" + ], + "equals": "AVAILABLE" + }, + { + "path": [ + "parent-and-children", + "steps", + 0, + "integrity", + "band" + ], + "equals": "FAILING" + }, + { + "path": [ + "parent-and-children", + "steps", + 0, + "normalization", + "provenance", + "r", + "parent_ids" + ], + "equals": [ + "github_checks:check_suite:501" + ] + }, + { + "path": [ + "parent-and-children", + "steps", + 0, + "normalization", + "provenance", + "r", + "child_ids" + ], + "equals": [ + 1, + 2, + 3, + 4, + 5 + ] + }, + { + "path": [ + "parent-and-children", + "steps", + 0, + "verified" + ], + "equals": true + } + ] + }, + { + "case": "R13", + "scenario": "ci-revisions.v1", + "given": { + "variants": [ + { + "name": "github", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "checks", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 501, + "head_sha": "r", + "status": "completed", + "conclusion": "failure", + "app": { + "id": 42, + "slug": "same-app" + } + } + } + ], + "children": [], + "rerun_links": [] + } + } + ] + }, + { + "name": "gitlab", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "gitlab", + "forge_instance": "gitlab.example", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.gitlab.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "pipelines", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 101, + "sha": "r", + "status": "failed", + "ref": "main", + "source": "push" + } + } + ], + "children": [], + "rerun_links": [] + } + } + ] + } + ] + }, + "checks": [ + { + "path": [ + "github", + "steps", + 0, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "github", + "steps", + 0, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "github", + "steps", + 0, + "history", + "r", + "history_state" + ], + "equals": "FAILURE_OBSERVED" + }, + { + "path": [ + "github", + "steps", + 0, + "history", + "r", + "historical_contribution" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "github", + "steps", + 0, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "github", + "steps", + 0, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "github", + "steps", + 0, + "integrity", + "evaluation_status" + ], + "equals": "AVAILABLE" + }, + { + "path": [ + "github", + "steps", + 0, + "integrity", + "band" + ], + "equals": "FAILING" + }, + { + "path": [ + "gitlab", + "steps", + 0, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "gitlab", + "steps", + 0, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "gitlab", + "steps", + 0, + "history", + "r", + "history_state" + ], + "equals": "FAILURE_OBSERVED" + }, + { + "path": [ + "gitlab", + "steps", + 0, + "history", + "r", + "historical_contribution" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "gitlab", + "steps", + 0, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "gitlab", + "steps", + 0, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "gitlab", + "steps", + 0, + "integrity", + "evaluation_status" + ], + "equals": "AVAILABLE" + }, + { + "path": [ + "gitlab", + "steps", + 0, + "integrity", + "band" + ], + "equals": "FAILING" + }, + { + "path": [ + "github", + "steps", + 0, + "verified" + ], + "equals": true + }, + { + "path": [ + "gitlab", + "steps", + 0, + "verified" + ], + "equals": true + } + ] + }, + { + "case": "R14", + "scenario": "ci-revisions.v1", + "given": { + "variants": [ + { + "name": "aggregate-preferred", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 1, + "status": "completed", + "conclusion": "success", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + } + ], + "children": [ + { + "surface": "check-run", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "parent": { + "surface": "actions", + "id": 700 + }, + "native": { + "id": 1, + "head_sha": "r", + "status": "completed", + "conclusion": "failure", + "name": "synthetic child" + } + } + ], + "rerun_links": [] + } + } + ] + } + ] + }, + "checks": [ + { + "path": [ + "aggregate-preferred", + "steps", + 0, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "aggregate-preferred", + "steps", + 0, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_PASS" + }, + { + "path": [ + "aggregate-preferred", + "steps", + 0, + "history", + "r", + "history_state" + ], + "equals": "PASS_ONLY_OBSERVED" + }, + { + "path": [ + "aggregate-preferred", + "steps", + 0, + "history", + "r", + "historical_contribution" + ], + "equals": "VERIFY_PASS" + }, + { + "path": [ + "aggregate-preferred", + "steps", + 0, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "aggregate-preferred", + "steps", + 0, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 0 + }, + { + "path": [ + "aggregate-preferred", + "steps", + 0, + "integrity", + "evaluation_status" + ], + "equals": "AVAILABLE" + }, + { + "path": [ + "aggregate-preferred", + "steps", + 0, + "integrity", + "band" + ], + "equals": "SPARSE" + }, + { + "path": [ + "aggregate-preferred", + "steps", + 0, + "normalization", + "provenance", + "r", + "parent_ids" + ], + "equals": [ + "github_actions:workflow_run:700" + ] + }, + { + "path": [ + "aggregate-preferred", + "steps", + 0, + "normalization", + "provenance", + "r", + "child_ids" + ], + "equals": [ + 1 + ] + }, + { + "path": [ + "aggregate-preferred", + "steps", + 0, + "verified" + ], + "equals": true + } + ] + }, + { + "case": "R15", + "scenario": "ci-revisions.v1", + "given": { + "variants": [ + { + "name": "same-native-key", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "gitlab", + "forge_instance": "gitlab.example", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.gitlab.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "pipelines", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 101, + "sha": "r", + "status": "failed", + "ref": "main", + "source": "push" + } + }, + { + "surface": "pipelines", + "revision": "r", + "observed_at": "2026-09-05T11:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 101, + "sha": "r", + "status": "success", + "ref": "main", + "source": "push" + } + } + ], + "children": [], + "rerun_links": [] + } + } + ] + }, + { + "name": "explicit-link", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "checks", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 501, + "head_sha": "r", + "status": "completed", + "conclusion": "failure", + "app": { + "id": 42, + "slug": "same-app" + } + } + }, + { + "surface": "checks", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 502, + "head_sha": "r", + "status": "completed", + "conclusion": "success", + "app": { + "id": 42, + "slug": "same-app" + } + } + } + ], + "children": [], + "rerun_links": [ + { + "contract": "devostasis.ci-rerun-link.v1", + "provider": "github", + "forge_instance": "github.com", + "project_id": "123456", + "revision": "r", + "earlier": { + "surface": "checks", + "id": 501 + }, + "later": { + "surface": "checks", + "id": 502 + }, + "status": "AVAILABLE", + "evidence_ref": "provider-native-rerun-relation:501:502" + } + ] + } + } + ] + }, + { + "name": "distinct-unproved", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "gitlab", + "forge_instance": "gitlab.example", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.gitlab.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "pipelines", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 101, + "sha": "r", + "status": "failed", + "ref": "main", + "source": "push" + } + }, + { + "surface": "pipelines", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 102, + "sha": "r", + "status": "success", + "ref": "main", + "source": "push" + } + } + ], + "children": [], + "rerun_links": [] + } + } + ] + } + ] + }, + "checks": [ + { + "path": [ + "same-native-key", + "steps", + 0, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "same-native-key", + "steps", + 0, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_PASS" + }, + { + "path": [ + "same-native-key", + "steps", + 0, + "history", + "r", + "history_state" + ], + "equals": "FAILURE_OBSERVED" + }, + { + "path": [ + "same-native-key", + "steps", + 0, + "history", + "r", + "historical_contribution" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "same-native-key", + "steps", + 0, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "same-native-key", + "steps", + 0, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "same-native-key", + "steps", + 0, + "normalization", + "provenance", + "r", + "parent_ids" + ], + "equals": [ + "gitlab:pipeline:101" + ] + }, + { + "path": [ + "explicit-link", + "steps", + 0, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "explicit-link", + "steps", + 0, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_PASS" + }, + { + "path": [ + "explicit-link", + "steps", + 0, + "history", + "r", + "history_state" + ], + "equals": "FAILURE_OBSERVED" + }, + { + "path": [ + "explicit-link", + "steps", + 0, + "history", + "r", + "historical_contribution" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "explicit-link", + "steps", + 0, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "explicit-link", + "steps", + 0, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "explicit-link", + "steps", + 0, + "normalization", + "provenance", + "r", + "parent_ids" + ], + "equals": [ + "github_checks:check_suite:501" + ] + }, + { + "path": [ + "distinct-unproved", + "steps", + 0, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "distinct-unproved", + "steps", + 0, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "distinct-unproved", + "steps", + 0, + "history", + "r", + "history_state" + ], + "equals": "FAILURE_OBSERVED" + }, + { + "path": [ + "distinct-unproved", + "steps", + 0, + "history", + "r", + "historical_contribution" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "distinct-unproved", + "steps", + 0, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "distinct-unproved", + "steps", + 0, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "distinct-unproved", + "steps", + 0, + "normalization", + "provenance", + "r", + "parent_ids" + ], + "equals": [ + "gitlab:pipeline:101", + "gitlab:pipeline:102" + ] + }, + { + "path": [ + "same-native-key", + "steps", + 0, + "verified" + ], + "equals": true + }, + { + "path": [ + "explicit-link", + "steps", + 0, + "verified" + ], + "equals": true + }, + { + "path": [ + "distinct-unproved", + "steps", + 0, + "verified" + ], + "equals": true + } + ] + }, + { + "case": "R16", + "scenario": "ci-revisions.v1", + "given": { + "variants": [ + { + "name": "siblings", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 1, + "status": "completed", + "conclusion": "success", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + }, + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 701, + "head_sha": "r", + "run_attempt": 1, + "status": "completed", + "conclusion": "failure", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + } + ], + "children": [], + "rerun_links": [] + } + } + ] + } + ] + }, + "checks": [ + { + "path": [ + "siblings", + "steps", + 0, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "siblings", + "steps", + 0, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "siblings", + "steps", + 0, + "history", + "r", + "history_state" + ], + "equals": "FAILURE_OBSERVED" + }, + { + "path": [ + "siblings", + "steps", + 0, + "history", + "r", + "historical_contribution" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "siblings", + "steps", + 0, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "siblings", + "steps", + 0, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "siblings", + "steps", + 0, + "integrity", + "evaluation_status" + ], + "equals": "AVAILABLE" + }, + { + "path": [ + "siblings", + "steps", + 0, + "integrity", + "band" + ], + "equals": "FAILING" + }, + { + "path": [ + "siblings", + "steps", + 0, + "normalization", + "provenance", + "r", + "parent_ids" + ], + "equals": [ + "github_actions:workflow_run:700", + "github_actions:workflow_run:701" + ] + }, + { + "path": [ + "siblings", + "steps", + 0, + "verified" + ], + "equals": true + } + ] + }, + { + "case": "R17", + "scenario": "ci-revisions.v1", + "given": { + "variants": [ + { + "name": "child-only", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [], + "children": [ + { + "surface": "check-run", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "parent": { + "surface": "checks", + "id": 501 + }, + "native": { + "id": 1, + "head_sha": "r", + "status": "completed", + "conclusion": "success", + "name": "synthetic child" + } + }, + { + "surface": "check-run", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "parent": { + "surface": "checks", + "id": 501 + }, + "native": { + "id": 2, + "head_sha": "r", + "status": "completed", + "conclusion": "success", + "name": "synthetic child" + } + }, + { + "surface": "check-run", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "parent": { + "surface": "checks", + "id": 501 + }, + "native": { + "id": 3, + "head_sha": "r", + "status": "completed", + "conclusion": "success", + "name": "synthetic child" + } + }, + { + "surface": "check-run", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "parent": { + "surface": "checks", + "id": 501 + }, + "native": { + "id": 4, + "head_sha": "r", + "status": "completed", + "conclusion": "success", + "name": "synthetic child" + } + }, + { + "surface": "check-run", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "parent": { + "surface": "checks", + "id": 501 + }, + "native": { + "id": 5, + "head_sha": "r", + "status": "completed", + "conclusion": "failure", + "name": "synthetic child" + } + } + ], + "rerun_links": [] + } + } + ] + } + ] + }, + "checks": [ + { + "path": [ + "child-only", + "steps", + 0, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "child-only", + "steps", + 0, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "child-only", + "steps", + 0, + "history", + "r", + "history_state" + ], + "equals": "UNKNOWN_HISTORY" + }, + { + "path": [ + "child-only", + "steps", + 0, + "history", + "r", + "historical_contribution" + ], + "equals": null + }, + { + "path": [ + "child-only", + "steps", + 0, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 0 + }, + { + "path": [ + "child-only", + "steps", + 0, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 0 + }, + { + "path": [ + "child-only", + "steps", + 0, + "integrity", + "evaluation_status" + ], + "equals": "UNKNOWN" + }, + { + "path": [ + "child-only", + "steps", + 0, + "integrity", + "band" + ], + "equals": null + }, + { + "path": [ + "child-only", + "steps", + 0, + "normalization", + "provenance", + "r", + "parent_ids" + ], + "equals": [ + "revision-child-aggregate:r" + ] + }, + { + "path": [ + "child-only", + "steps", + 0, + "normalization", + "coverage" + ], + "equals": "DEGRADED" + }, + { + "path": [ + "child-only", + "steps", + 0, + "normalization", + "verification_units", + "r" + ], + "equals": "REVISION_CHILD_AGGREGATE_RAW" + }, + { + "path": [ + "child-only", + "steps", + 0, + "current_decisive_count" + ], + "equals": 1 + }, + { + "path": [ + "child-only", + "steps", + 0, + "verified" + ], + "equals": true + } + ] + }, + { + "case": "R18", + "scenario": "ci-revisions.v1", + "given": { + "variants": [ + { + "name": "revision-time", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "old", + "committed_at": "2026-09-01T00:00:00Z", + "default_branch": true + }, + { + "revision": "new", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "actions", + "revision": "old", + "observed_at": "2026-09-05T11:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "old", + "run_attempt": 1, + "status": "completed", + "conclusion": "failure", + "name": "same CI name", + "workflow_id": 42, + "event": "push", + "completed_at": "2026-09-05T11:00:00Z" + } + }, + { + "surface": "actions", + "revision": "new", + "observed_at": "2026-09-04T11:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 800, + "head_sha": "new", + "run_attempt": 1, + "status": "completed", + "conclusion": "success", + "name": "same CI name", + "workflow_id": 42, + "event": "push", + "completed_at": "2026-09-04T11:00:00Z" + } + } + ], + "children": [], + "rerun_links": [] + } + } + ] + }, + { + "name": "sha-tie-break", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "aa", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + }, + { + "revision": "zz", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "actions", + "revision": "aa", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "aa", + "run_attempt": 1, + "status": "completed", + "conclusion": "failure", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + }, + { + "surface": "actions", + "revision": "zz", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 800, + "head_sha": "zz", + "run_attempt": 1, + "status": "completed", + "conclusion": "success", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + } + ], + "children": [], + "rerun_links": [] + } + } + ] + } + ] + }, + "checks": [ + { + "path": [ + "revision-time", + "steps", + 0, + "integrity", + "derived", + "latest_decisive_default_branch" + ], + "equals": "new" + }, + { + "path": [ + "revision-time", + "steps", + 0, + "integrity", + "derived", + "current_revision", + "revision" + ], + "equals": "new" + }, + { + "path": [ + "sha-tie-break", + "steps", + 0, + "integrity", + "derived", + "latest_decisive_default_branch" + ], + "equals": "zz" + }, + { + "path": [ + "sha-tie-break", + "steps", + 0, + "integrity", + "derived", + "current_revision", + "revision" + ], + "equals": "zz" + }, + { + "path": [ + "revision-time", + "steps", + 0, + "verified" + ], + "equals": true + }, + { + "path": [ + "sha-tie-break", + "steps", + 0, + "verified" + ], + "equals": true + } + ] + }, + { + "case": "R19", + "scenario": "ci-revisions.v1", + "given": { + "variants": [ + { + "name": "timestamp-unavailable", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": null, + "default_branch": true + } + ], + "parents": [ + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 1, + "status": "completed", + "conclusion": "success", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + } + ], + "children": [], + "rerun_links": [] + } + } + ] + }, + { + "name": "branch-unavailable", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": null + } + ], + "parents": [ + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 1, + "status": "completed", + "conclusion": "success", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + } + ], + "children": [], + "rerun_links": [] + } + } + ] + } + ] + }, + "checks": [ + { + "path": [ + "timestamp-unavailable", + "steps", + 0, + "normalization", + "ordering_status" + ], + "equals": "UNKNOWN" + }, + { + "path": [ + "timestamp-unavailable", + "steps", + 0, + "integrity", + "derived", + "latest_decisive_default_branch" + ], + "equals": "UNKNOWN" + }, + { + "path": [ + "timestamp-unavailable", + "steps", + 0, + "integrity", + "evaluation_status" + ], + "equals": "UNKNOWN" + }, + { + "path": [ + "timestamp-unavailable", + "steps", + 0, + "integrity", + "band" + ], + "equals": null + }, + { + "path": [ + "timestamp-unavailable", + "steps", + 0, + "current_revision_count" + ], + "equals": 0 + }, + { + "path": [ + "branch-unavailable", + "steps", + 0, + "normalization", + "ordering_status" + ], + "equals": "UNKNOWN" + }, + { + "path": [ + "branch-unavailable", + "steps", + 0, + "integrity", + "derived", + "latest_decisive_default_branch" + ], + "equals": "UNKNOWN" + }, + { + "path": [ + "branch-unavailable", + "steps", + 0, + "integrity", + "evaluation_status" + ], + "equals": "UNKNOWN" + }, + { + "path": [ + "branch-unavailable", + "steps", + 0, + "integrity", + "band" + ], + "equals": null + }, + { + "path": [ + "branch-unavailable", + "steps", + 0, + "current_revision_count" + ], + "equals": 0 + }, + { + "path": [ + "timestamp-unavailable", + "steps", + 0, + "verified" + ], + "equals": true + }, + { + "path": [ + "branch-unavailable", + "steps", + 0, + "verified" + ], + "equals": true + } + ] + }, + { + "case": "R20", + "scenario": "ci-revisions.v1", + "given": { + "variants": [ + { + "name": "forward", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "old", + "committed_at": "2026-09-01T00:00:00Z", + "default_branch": true + }, + { + "revision": "new", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "actions", + "revision": "old", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "old", + "run_attempt": 1, + "status": "completed", + "conclusion": "failure", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + }, + { + "surface": "actions", + "revision": "new", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 701, + "head_sha": "new", + "run_attempt": 1, + "status": "completed", + "conclusion": "success", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + } + ], + "children": [], + "rerun_links": [] + } + } + ] + }, + { + "name": "reverse", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "new", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + }, + { + "revision": "old", + "committed_at": "2026-09-01T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "actions", + "revision": "new", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 701, + "head_sha": "new", + "run_attempt": 1, + "status": "completed", + "conclusion": "success", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + }, + { + "surface": "actions", + "revision": "old", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "old", + "run_attempt": 1, + "status": "completed", + "conclusion": "failure", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + } + ], + "children": [], + "rerun_links": [] + } + } + ] + }, + { + "name": "pages", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "old", + "committed_at": "2026-09-01T00:00:00Z", + "default_branch": true + }, + { + "revision": "new", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": { + "pages": [ + { + "ordinal": 1, + "has_next": true, + "items": [ + { + "surface": "actions", + "revision": "old", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "old", + "run_attempt": 1, + "status": "completed", + "conclusion": "failure", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + } + ] + }, + { + "ordinal": 2, + "has_next": false, + "items": [ + { + "surface": "actions", + "revision": "new", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 701, + "head_sha": "new", + "run_attempt": 1, + "status": "completed", + "conclusion": "success", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + } + ] + } + ] + }, + "children": [], + "rerun_links": [] + } + } + ] + } + ] + }, + "checks": [ + { + "path": [ + "forward", + "steps", + 0, + "current_revision_count" + ], + "equals": 2 + }, + { + "path": [ + "forward", + "steps", + 0, + "current", + "new", + "current_verdict" + ], + "equals": "VERIFY_PASS" + }, + { + "path": [ + "forward", + "steps", + 0, + "history", + "new", + "history_state" + ], + "equals": "PASS_ONLY_OBSERVED" + }, + { + "path": [ + "forward", + "steps", + 0, + "history", + "new", + "historical_contribution" + ], + "equals": "VERIFY_PASS" + }, + { + "path": [ + "forward", + "steps", + 0, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 2 + }, + { + "path": [ + "forward", + "steps", + 0, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "forward", + "steps", + 0, + "integrity", + "evaluation_status" + ], + "equals": "AVAILABLE" + }, + { + "path": [ + "forward", + "steps", + 0, + "integrity", + "band" + ], + "equals": "SPARSE_MIXED" + }, + { + "path": [ + "reverse", + "steps", + 0, + "current_revision_count" + ], + "equals": 2 + }, + { + "path": [ + "reverse", + "steps", + 0, + "current", + "new", + "current_verdict" + ], + "equals": "VERIFY_PASS" + }, + { + "path": [ + "reverse", + "steps", + 0, + "history", + "new", + "history_state" + ], + "equals": "PASS_ONLY_OBSERVED" + }, + { + "path": [ + "reverse", + "steps", + 0, + "history", + "new", + "historical_contribution" + ], + "equals": "VERIFY_PASS" + }, + { + "path": [ + "reverse", + "steps", + 0, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 2 + }, + { + "path": [ + "reverse", + "steps", + 0, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "reverse", + "steps", + 0, + "integrity", + "evaluation_status" + ], + "equals": "AVAILABLE" + }, + { + "path": [ + "reverse", + "steps", + 0, + "integrity", + "band" + ], + "equals": "SPARSE_MIXED" + }, + { + "path": [ + "pages", + "steps", + 0, + "current_revision_count" + ], + "equals": 2 + }, + { + "path": [ + "pages", + "steps", + 0, + "current", + "new", + "current_verdict" + ], + "equals": "VERIFY_PASS" + }, + { + "path": [ + "pages", + "steps", + 0, + "history", + "new", + "history_state" + ], + "equals": "PASS_ONLY_OBSERVED" + }, + { + "path": [ + "pages", + "steps", + 0, + "history", + "new", + "historical_contribution" + ], + "equals": "VERIFY_PASS" + }, + { + "path": [ + "pages", + "steps", + 0, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 2 + }, + { + "path": [ + "pages", + "steps", + 0, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "pages", + "steps", + 0, + "integrity", + "evaluation_status" + ], + "equals": "AVAILABLE" + }, + { + "path": [ + "pages", + "steps", + 0, + "integrity", + "band" + ], + "equals": "SPARSE_MIXED" + }, + { + "path": [ + "forward", + "steps", + 0, + "verified" + ], + "equals": true + }, + { + "path": [ + "reverse", + "steps", + 0, + "verified" + ], + "equals": true + }, + { + "path": [ + "pages", + "steps", + 0, + "verified" + ], + "equals": true + } + ] + }, + { + "case": "R21", + "scenario": "ci-revisions.v1", + "given": { + "variants": [ + { + "name": "distinct-pipelines", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "gitlab", + "forge_instance": "gitlab.example", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.gitlab.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "pipelines", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 101, + "sha": "r", + "status": "failed", + "ref": "main", + "source": "push" + } + }, + { + "surface": "pipelines", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 102, + "sha": "r", + "status": "success", + "ref": "main", + "source": "push" + } + } + ], + "children": [], + "rerun_links": [] + } + } + ] + } + ] + }, + "checks": [ + { + "path": [ + "distinct-pipelines", + "steps", + 0, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "distinct-pipelines", + "steps", + 0, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "distinct-pipelines", + "steps", + 0, + "history", + "r", + "history_state" + ], + "equals": "FAILURE_OBSERVED" + }, + { + "path": [ + "distinct-pipelines", + "steps", + 0, + "history", + "r", + "historical_contribution" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "distinct-pipelines", + "steps", + 0, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "distinct-pipelines", + "steps", + 0, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "distinct-pipelines", + "steps", + 0, + "integrity", + "evaluation_status" + ], + "equals": "AVAILABLE" + }, + { + "path": [ + "distinct-pipelines", + "steps", + 0, + "integrity", + "band" + ], + "equals": "FAILING" + }, + { + "path": [ + "distinct-pipelines", + "steps", + 0, + "normalization", + "provenance", + "r", + "parent_ids" + ], + "equals": [ + "gitlab:pipeline:101", + "gitlab:pipeline:102" + ] + }, + { + "path": [ + "distinct-pipelines", + "steps", + 0, + "verified" + ], + "equals": true + } + ] + }, + { + "case": "R22", + "scenario": "ci-revisions.v1", + "given": { + "variants": [ + { + "name": "in-place", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "gitlab", + "forge_instance": "gitlab.example", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.gitlab.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "pipelines", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 101, + "sha": "r", + "status": "failed", + "ref": "main", + "source": "push" + } + }, + { + "surface": "pipelines", + "revision": "r", + "observed_at": "2026-09-05T11:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 101, + "sha": "r", + "status": "success", + "ref": "main", + "source": "push" + } + } + ], + "children": [], + "rerun_links": [] + } + } + ] + } + ] + }, + "checks": [ + { + "path": [ + "in-place", + "steps", + 0, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "in-place", + "steps", + 0, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_PASS" + }, + { + "path": [ + "in-place", + "steps", + 0, + "history", + "r", + "history_state" + ], + "equals": "FAILURE_OBSERVED" + }, + { + "path": [ + "in-place", + "steps", + 0, + "history", + "r", + "historical_contribution" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "in-place", + "steps", + 0, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "in-place", + "steps", + 0, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "in-place", + "steps", + 0, + "integrity", + "evaluation_status" + ], + "equals": "AVAILABLE" + }, + { + "path": [ + "in-place", + "steps", + 0, + "integrity", + "band" + ], + "equals": "SPARSE_MIXED" + }, + { + "path": [ + "in-place", + "steps", + 0, + "normalization", + "provenance", + "r", + "parent_ids" + ], + "equals": [ + "gitlab:pipeline:101" + ] + }, + { + "path": [ + "in-place", + "steps", + 0, + "verified" + ], + "equals": true + } + ] + }, + { + "case": "R23", + "scenario": "ci-revisions.v1", + "given": { + "variants": [ + { + "name": "distinct-suites", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "checks", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 501, + "head_sha": "r", + "status": "completed", + "conclusion": "failure", + "app": { + "id": 42, + "slug": "same-app" + } + } + }, + { + "surface": "checks", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 502, + "head_sha": "r", + "status": "completed", + "conclusion": "success", + "app": { + "id": 42, + "slug": "same-app" + } + } + } + ], + "children": [], + "rerun_links": [] + } + } + ] + } + ] + }, + "checks": [ + { + "path": [ + "distinct-suites", + "steps", + 0, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "distinct-suites", + "steps", + 0, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "distinct-suites", + "steps", + 0, + "history", + "r", + "history_state" + ], + "equals": "FAILURE_OBSERVED" + }, + { + "path": [ + "distinct-suites", + "steps", + 0, + "history", + "r", + "historical_contribution" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "distinct-suites", + "steps", + 0, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "distinct-suites", + "steps", + 0, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "distinct-suites", + "steps", + 0, + "integrity", + "evaluation_status" + ], + "equals": "AVAILABLE" + }, + { + "path": [ + "distinct-suites", + "steps", + 0, + "integrity", + "band" + ], + "equals": "FAILING" + }, + { + "path": [ + "distinct-suites", + "steps", + 0, + "normalization", + "provenance", + "r", + "parent_ids" + ], + "equals": [ + "github_checks:check_suite:501", + "github_checks:check_suite:502" + ] + }, + { + "path": [ + "distinct-suites", + "steps", + 0, + "verified" + ], + "equals": true + } + ] + }, + { + "case": "R24", + "scenario": "ci-revisions.v1", + "given": { + "variants": [ + { + "name": "chain", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "checks", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 501, + "head_sha": "r", + "status": "completed", + "conclusion": "failure", + "app": { + "id": 42, + "slug": "same-app" + } + } + }, + { + "surface": "checks", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 502, + "head_sha": "r", + "status": "completed", + "conclusion": "success", + "app": { + "id": 42, + "slug": "same-app" + } + } + }, + { + "surface": "checks", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 503, + "head_sha": "r", + "status": "completed", + "conclusion": "success", + "app": { + "id": 42, + "slug": "same-app" + } + } + } + ], + "children": [], + "rerun_links": [ + { + "contract": "devostasis.ci-rerun-link.v1", + "provider": "github", + "forge_instance": "github.com", + "project_id": "123456", + "revision": "r", + "earlier": { + "surface": "checks", + "id": 501 + }, + "later": { + "surface": "checks", + "id": 502 + }, + "status": "AVAILABLE", + "evidence_ref": "provider-native-rerun-relation:501:502" + }, + { + "contract": "devostasis.ci-rerun-link.v1", + "provider": "github", + "forge_instance": "github.com", + "project_id": "123456", + "revision": "r", + "earlier": { + "surface": "checks", + "id": 502 + }, + "later": { + "surface": "checks", + "id": 503 + }, + "status": "AVAILABLE", + "evidence_ref": "provider-native-rerun-relation:502:503" + } + ] + } + } + ] + }, + { + "name": "fork", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "checks", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 501, + "head_sha": "r", + "status": "completed", + "conclusion": "failure", + "app": { + "id": 42, + "slug": "same-app" + } + } + }, + { + "surface": "checks", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 502, + "head_sha": "r", + "status": "completed", + "conclusion": "success", + "app": { + "id": 42, + "slug": "same-app" + } + } + }, + { + "surface": "checks", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 503, + "head_sha": "r", + "status": "completed", + "conclusion": "success", + "app": { + "id": 42, + "slug": "same-app" + } + } + } + ], + "children": [], + "rerun_links": [ + { + "contract": "devostasis.ci-rerun-link.v1", + "provider": "github", + "forge_instance": "github.com", + "project_id": "123456", + "revision": "r", + "earlier": { + "surface": "checks", + "id": 501 + }, + "later": { + "surface": "checks", + "id": 502 + }, + "status": "AVAILABLE", + "evidence_ref": "provider-native-rerun-relation:501:502" + }, + { + "contract": "devostasis.ci-rerun-link.v1", + "provider": "github", + "forge_instance": "github.com", + "project_id": "123456", + "revision": "r", + "earlier": { + "surface": "checks", + "id": 501 + }, + "later": { + "surface": "checks", + "id": 503 + }, + "status": "AVAILABLE", + "evidence_ref": "provider-native-rerun-relation:501:503" + } + ] + } + } + ] + }, + { + "name": "cycle", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "checks", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 501, + "head_sha": "r", + "status": "completed", + "conclusion": "failure", + "app": { + "id": 42, + "slug": "same-app" + } + } + }, + { + "surface": "checks", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 502, + "head_sha": "r", + "status": "completed", + "conclusion": "success", + "app": { + "id": 42, + "slug": "same-app" + } + } + } + ], + "children": [], + "rerun_links": [ + { + "contract": "devostasis.ci-rerun-link.v1", + "provider": "github", + "forge_instance": "github.com", + "project_id": "123456", + "revision": "r", + "earlier": { + "surface": "checks", + "id": 501 + }, + "later": { + "surface": "checks", + "id": 502 + }, + "status": "AVAILABLE", + "evidence_ref": "provider-native-rerun-relation:501:502" + }, + { + "contract": "devostasis.ci-rerun-link.v1", + "provider": "github", + "forge_instance": "github.com", + "project_id": "123456", + "revision": "r", + "earlier": { + "surface": "checks", + "id": 502 + }, + "later": { + "surface": "checks", + "id": 501 + }, + "status": "AVAILABLE", + "evidence_ref": "provider-native-rerun-relation:502:501" + } + ] + } + } + ] + }, + { + "name": "fanin", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "checks", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 501, + "head_sha": "r", + "status": "completed", + "conclusion": "failure", + "app": { + "id": 42, + "slug": "same-app" + } + } + }, + { + "surface": "checks", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 502, + "head_sha": "r", + "status": "completed", + "conclusion": "success", + "app": { + "id": 42, + "slug": "same-app" + } + } + }, + { + "surface": "checks", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 503, + "head_sha": "r", + "status": "completed", + "conclusion": "success", + "app": { + "id": 42, + "slug": "same-app" + } + } + } + ], + "children": [], + "rerun_links": [ + { + "contract": "devostasis.ci-rerun-link.v1", + "provider": "github", + "forge_instance": "github.com", + "project_id": "123456", + "revision": "r", + "earlier": { + "surface": "checks", + "id": 501 + }, + "later": { + "surface": "checks", + "id": 503 + }, + "status": "AVAILABLE", + "evidence_ref": "provider-native-rerun-relation:501:503" + }, + { + "contract": "devostasis.ci-rerun-link.v1", + "provider": "github", + "forge_instance": "github.com", + "project_id": "123456", + "revision": "r", + "earlier": { + "surface": "checks", + "id": 502 + }, + "later": { + "surface": "checks", + "id": 503 + }, + "status": "AVAILABLE", + "evidence_ref": "provider-native-rerun-relation:502:503" + } + ] + } + } + ] + }, + { + "name": "unavailable-proof", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "checks", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 501, + "head_sha": "r", + "status": "completed", + "conclusion": "failure", + "app": { + "id": 42, + "slug": "same-app" + } + } + }, + { + "surface": "checks", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 502, + "head_sha": "r", + "status": "completed", + "conclusion": "success", + "app": { + "id": 42, + "slug": "same-app" + } + } + } + ], + "children": [], + "rerun_links": [ + { + "contract": "devostasis.ci-rerun-link.v1", + "provider": "github", + "forge_instance": "github.com", + "project_id": "123456", + "revision": "r", + "earlier": { + "surface": "checks", + "id": 501 + }, + "later": { + "surface": "checks", + "id": 502 + }, + "status": "UNKNOWN", + "evidence_ref": "provider-native-rerun-relation:501:502" + } + ] + } + } + ] + } + ] + }, + "checks": [ + { + "path": [ + "chain", + "steps", + 0, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "chain", + "steps", + 0, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_PASS" + }, + { + "path": [ + "chain", + "steps", + 0, + "history", + "r", + "history_state" + ], + "equals": "FAILURE_OBSERVED" + }, + { + "path": [ + "chain", + "steps", + 0, + "history", + "r", + "historical_contribution" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "chain", + "steps", + 0, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "chain", + "steps", + 0, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "chain", + "steps", + 0, + "integrity", + "evaluation_status" + ], + "equals": "AVAILABLE" + }, + { + "path": [ + "chain", + "steps", + 0, + "integrity", + "band" + ], + "equals": "SPARSE_MIXED" + }, + { + "path": [ + "chain", + "steps", + 0, + "normalization", + "provenance", + "r", + "parent_ids" + ], + "equals": [ + "github_checks:check_suite:501" + ] + }, + { + "path": [ + "fork", + "steps", + 0, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "fork", + "steps", + 0, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "fork", + "steps", + 0, + "history", + "r", + "history_state" + ], + "equals": "FAILURE_OBSERVED" + }, + { + "path": [ + "fork", + "steps", + 0, + "history", + "r", + "historical_contribution" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "fork", + "steps", + 0, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "fork", + "steps", + 0, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "fork", + "steps", + 0, + "integrity", + "evaluation_status" + ], + "equals": "DEGRADED" + }, + { + "path": [ + "fork", + "steps", + 0, + "integrity", + "band" + ], + "equals": "FAILING" + }, + { + "path": [ + "fork", + "steps", + 0, + "normalization", + "provenance", + "r", + "parent_ids" + ], + "equals": [ + "github_checks:check_suite:501", + "github_checks:check_suite:502", + "github_checks:check_suite:503" + ] + }, + { + "path": [ + "fork", + "steps", + 0, + "normalization", + "coverage" + ], + "equals": "DEGRADED" + }, + { + "path": [ + "fork", + "steps", + 0, + "normalization", + "diagnostics" + ], + "contains": "RERUN_LINK_UNPROVEN_OR_AMBIGUOUS" + }, + { + "path": [ + "cycle", + "steps", + 0, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "cycle", + "steps", + 0, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "cycle", + "steps", + 0, + "history", + "r", + "history_state" + ], + "equals": "FAILURE_OBSERVED" + }, + { + "path": [ + "cycle", + "steps", + 0, + "history", + "r", + "historical_contribution" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "cycle", + "steps", + 0, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "cycle", + "steps", + 0, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "cycle", + "steps", + 0, + "integrity", + "evaluation_status" + ], + "equals": "DEGRADED" + }, + { + "path": [ + "cycle", + "steps", + 0, + "integrity", + "band" + ], + "equals": "FAILING" + }, + { + "path": [ + "cycle", + "steps", + 0, + "normalization", + "provenance", + "r", + "parent_ids" + ], + "equals": [ + "github_checks:check_suite:501", + "github_checks:check_suite:502" + ] + }, + { + "path": [ + "cycle", + "steps", + 0, + "normalization", + "coverage" + ], + "equals": "DEGRADED" + }, + { + "path": [ + "cycle", + "steps", + 0, + "normalization", + "diagnostics" + ], + "contains": "RERUN_LINK_UNPROVEN_OR_AMBIGUOUS" + }, + { + "path": [ + "fanin", + "steps", + 0, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "fanin", + "steps", + 0, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "fanin", + "steps", + 0, + "history", + "r", + "history_state" + ], + "equals": "FAILURE_OBSERVED" + }, + { + "path": [ + "fanin", + "steps", + 0, + "history", + "r", + "historical_contribution" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "fanin", + "steps", + 0, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "fanin", + "steps", + 0, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "fanin", + "steps", + 0, + "integrity", + "evaluation_status" + ], + "equals": "DEGRADED" + }, + { + "path": [ + "fanin", + "steps", + 0, + "integrity", + "band" + ], + "equals": "FAILING" + }, + { + "path": [ + "fanin", + "steps", + 0, + "normalization", + "provenance", + "r", + "parent_ids" + ], + "equals": [ + "github_checks:check_suite:501", + "github_checks:check_suite:502", + "github_checks:check_suite:503" + ] + }, + { + "path": [ + "fanin", + "steps", + 0, + "normalization", + "coverage" + ], + "equals": "DEGRADED" + }, + { + "path": [ + "fanin", + "steps", + 0, + "normalization", + "diagnostics" + ], + "contains": "RERUN_LINK_UNPROVEN_OR_AMBIGUOUS" + }, + { + "path": [ + "unavailable-proof", + "steps", + 0, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "unavailable-proof", + "steps", + 0, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "unavailable-proof", + "steps", + 0, + "history", + "r", + "history_state" + ], + "equals": "FAILURE_OBSERVED" + }, + { + "path": [ + "unavailable-proof", + "steps", + 0, + "history", + "r", + "historical_contribution" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "unavailable-proof", + "steps", + 0, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "unavailable-proof", + "steps", + 0, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "unavailable-proof", + "steps", + 0, + "integrity", + "evaluation_status" + ], + "equals": "DEGRADED" + }, + { + "path": [ + "unavailable-proof", + "steps", + 0, + "integrity", + "band" + ], + "equals": "FAILING" + }, + { + "path": [ + "unavailable-proof", + "steps", + 0, + "normalization", + "provenance", + "r", + "parent_ids" + ], + "equals": [ + "github_checks:check_suite:501", + "github_checks:check_suite:502" + ] + }, + { + "path": [ + "unavailable-proof", + "steps", + 0, + "normalization", + "coverage" + ], + "equals": "DEGRADED" + }, + { + "path": [ + "unavailable-proof", + "steps", + 0, + "normalization", + "diagnostics" + ], + "contains": "RERUN_LINK_UNPROVEN_OR_AMBIGUOUS" + }, + { + "path": [ + "chain", + "steps", + 0, + "verified" + ], + "equals": true + }, + { + "path": [ + "fork", + "steps", + 0, + "verified" + ], + "equals": true + }, + { + "path": [ + "cycle", + "steps", + 0, + "verified" + ], + "equals": true + }, + { + "path": [ + "fanin", + "steps", + 0, + "verified" + ], + "equals": true + }, + { + "path": [ + "unavailable-proof", + "steps", + 0, + "verified" + ], + "equals": true + } + ] + }, + { + "case": "R25", + "scenario": "ci-revisions.v1", + "given": { + "variants": [ + { + "name": "github-forward", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "checks", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 501, + "head_sha": "r", + "status": "completed", + "conclusion": "failure", + "app": { + "id": 42, + "slug": "same-app" + } + } + }, + { + "surface": "checks", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 502, + "head_sha": "r", + "status": "completed", + "conclusion": "success", + "app": { + "id": 42, + "slug": "same-app" + } + } + } + ], + "children": [], + "rerun_links": [] + } + } + ] + }, + { + "name": "github-reverse", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "checks", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 502, + "head_sha": "r", + "status": "completed", + "conclusion": "success", + "app": { + "id": 42, + "slug": "same-app" + } + } + }, + { + "surface": "checks", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 501, + "head_sha": "r", + "status": "completed", + "conclusion": "failure", + "app": { + "id": 42, + "slug": "same-app" + } + } + } + ], + "children": [], + "rerun_links": [] + } + } + ] + }, + { + "name": "github-pages", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": { + "pages": [ + { + "ordinal": 1, + "has_next": true, + "items": [ + { + "surface": "checks", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 501, + "head_sha": "r", + "status": "completed", + "conclusion": "failure", + "app": { + "id": 42, + "slug": "same-app" + } + } + } + ] + }, + { + "ordinal": 2, + "has_next": false, + "items": [ + { + "surface": "checks", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 502, + "head_sha": "r", + "status": "completed", + "conclusion": "success", + "app": { + "id": 42, + "slug": "same-app" + } + } + } + ] + } + ] + }, + "children": [], + "rerun_links": [] + } + } + ] + }, + { + "name": "gitlab-forward", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "gitlab", + "forge_instance": "gitlab.example", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.gitlab.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "pipelines", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 101, + "sha": "r", + "status": "failed", + "ref": "main", + "source": "push" + } + }, + { + "surface": "pipelines", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 102, + "sha": "r", + "status": "success", + "ref": "main", + "source": "push" + } + } + ], + "children": [], + "rerun_links": [] + } + } + ] + }, + { + "name": "gitlab-reverse", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "gitlab", + "forge_instance": "gitlab.example", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.gitlab.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "pipelines", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 102, + "sha": "r", + "status": "success", + "ref": "main", + "source": "push" + } + }, + { + "surface": "pipelines", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 101, + "sha": "r", + "status": "failed", + "ref": "main", + "source": "push" + } + } + ], + "children": [], + "rerun_links": [] + } + } + ] + }, + { + "name": "gitlab-pages", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "gitlab", + "forge_instance": "gitlab.example", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.gitlab.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": { + "pages": [ + { + "ordinal": 1, + "has_next": true, + "items": [ + { + "surface": "pipelines", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 101, + "sha": "r", + "status": "failed", + "ref": "main", + "source": "push" + } + } + ] + }, + { + "ordinal": 2, + "has_next": false, + "items": [ + { + "surface": "pipelines", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 102, + "sha": "r", + "status": "success", + "ref": "main", + "source": "push" + } + } + ] + } + ] + }, + "children": [], + "rerun_links": [] + } + } + ] + } + ] + }, + "checks": [ + { + "path": [ + "github-forward", + "steps", + 0, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "github-forward", + "steps", + 0, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "github-forward", + "steps", + 0, + "history", + "r", + "history_state" + ], + "equals": "FAILURE_OBSERVED" + }, + { + "path": [ + "github-forward", + "steps", + 0, + "history", + "r", + "historical_contribution" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "github-forward", + "steps", + 0, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "github-forward", + "steps", + 0, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "github-forward", + "steps", + 0, + "integrity", + "evaluation_status" + ], + "equals": "AVAILABLE" + }, + { + "path": [ + "github-forward", + "steps", + 0, + "integrity", + "band" + ], + "equals": "FAILING" + }, + { + "path": [ + "github-forward", + "steps", + 0, + "normalization", + "provenance", + "r", + "parent_ids" + ], + "equals": [ + "github_checks:check_suite:501", + "github_checks:check_suite:502" + ] + }, + { + "path": [ + "github-reverse", + "steps", + 0, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "github-reverse", + "steps", + 0, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "github-reverse", + "steps", + 0, + "history", + "r", + "history_state" + ], + "equals": "FAILURE_OBSERVED" + }, + { + "path": [ + "github-reverse", + "steps", + 0, + "history", + "r", + "historical_contribution" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "github-reverse", + "steps", + 0, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "github-reverse", + "steps", + 0, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "github-reverse", + "steps", + 0, + "integrity", + "evaluation_status" + ], + "equals": "AVAILABLE" + }, + { + "path": [ + "github-reverse", + "steps", + 0, + "integrity", + "band" + ], + "equals": "FAILING" + }, + { + "path": [ + "github-reverse", + "steps", + 0, + "normalization", + "provenance", + "r", + "parent_ids" + ], + "equals": [ + "github_checks:check_suite:501", + "github_checks:check_suite:502" + ] + }, + { + "path": [ + "github-pages", + "steps", + 0, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "github-pages", + "steps", + 0, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "github-pages", + "steps", + 0, + "history", + "r", + "history_state" + ], + "equals": "FAILURE_OBSERVED" + }, + { + "path": [ + "github-pages", + "steps", + 0, + "history", + "r", + "historical_contribution" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "github-pages", + "steps", + 0, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "github-pages", + "steps", + 0, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "github-pages", + "steps", + 0, + "integrity", + "evaluation_status" + ], + "equals": "AVAILABLE" + }, + { + "path": [ + "github-pages", + "steps", + 0, + "integrity", + "band" + ], + "equals": "FAILING" + }, + { + "path": [ + "github-pages", + "steps", + 0, + "normalization", + "provenance", + "r", + "parent_ids" + ], + "equals": [ + "github_checks:check_suite:501", + "github_checks:check_suite:502" + ] + }, + { + "path": [ + "gitlab-forward", + "steps", + 0, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "gitlab-forward", + "steps", + 0, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "gitlab-forward", + "steps", + 0, + "history", + "r", + "history_state" + ], + "equals": "FAILURE_OBSERVED" + }, + { + "path": [ + "gitlab-forward", + "steps", + 0, + "history", + "r", + "historical_contribution" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "gitlab-forward", + "steps", + 0, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "gitlab-forward", + "steps", + 0, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "gitlab-forward", + "steps", + 0, + "integrity", + "evaluation_status" + ], + "equals": "AVAILABLE" + }, + { + "path": [ + "gitlab-forward", + "steps", + 0, + "integrity", + "band" + ], + "equals": "FAILING" + }, + { + "path": [ + "gitlab-forward", + "steps", + 0, + "normalization", + "provenance", + "r", + "parent_ids" + ], + "equals": [ + "gitlab:pipeline:101", + "gitlab:pipeline:102" + ] + }, + { + "path": [ + "gitlab-reverse", + "steps", + 0, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "gitlab-reverse", + "steps", + 0, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "gitlab-reverse", + "steps", + 0, + "history", + "r", + "history_state" + ], + "equals": "FAILURE_OBSERVED" + }, + { + "path": [ + "gitlab-reverse", + "steps", + 0, + "history", + "r", + "historical_contribution" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "gitlab-reverse", + "steps", + 0, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "gitlab-reverse", + "steps", + 0, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "gitlab-reverse", + "steps", + 0, + "integrity", + "evaluation_status" + ], + "equals": "AVAILABLE" + }, + { + "path": [ + "gitlab-reverse", + "steps", + 0, + "integrity", + "band" + ], + "equals": "FAILING" + }, + { + "path": [ + "gitlab-reverse", + "steps", + 0, + "normalization", + "provenance", + "r", + "parent_ids" + ], + "equals": [ + "gitlab:pipeline:101", + "gitlab:pipeline:102" + ] + }, + { + "path": [ + "gitlab-pages", + "steps", + 0, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "gitlab-pages", + "steps", + 0, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "gitlab-pages", + "steps", + 0, + "history", + "r", + "history_state" + ], + "equals": "FAILURE_OBSERVED" + }, + { + "path": [ + "gitlab-pages", + "steps", + 0, + "history", + "r", + "historical_contribution" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "gitlab-pages", + "steps", + 0, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "gitlab-pages", + "steps", + 0, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "gitlab-pages", + "steps", + 0, + "integrity", + "evaluation_status" + ], + "equals": "AVAILABLE" + }, + { + "path": [ + "gitlab-pages", + "steps", + 0, + "integrity", + "band" + ], + "equals": "FAILING" + }, + { + "path": [ + "gitlab-pages", + "steps", + 0, + "normalization", + "provenance", + "r", + "parent_ids" + ], + "equals": [ + "gitlab:pipeline:101", + "gitlab:pipeline:102" + ] + }, + { + "path": [ + "github-forward", + "steps", + 0, + "verified" + ], + "equals": true + }, + { + "path": [ + "github-reverse", + "steps", + 0, + "verified" + ], + "equals": true + }, + { + "path": [ + "github-pages", + "steps", + 0, + "verified" + ], + "equals": true + }, + { + "path": [ + "gitlab-forward", + "steps", + 0, + "verified" + ], + "equals": true + }, + { + "path": [ + "gitlab-reverse", + "steps", + 0, + "verified" + ], + "equals": true + }, + { + "path": [ + "gitlab-pages", + "steps", + 0, + "verified" + ], + "equals": true + } + ] + }, + { + "case": "R26", + "scenario": "ci-revisions.v1", + "given": { + "variants": [ + { + "name": "attempts", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 1, + "status": "completed", + "conclusion": "failure", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + }, + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T11:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 2, + "status": "completed", + "conclusion": "success", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + } + ], + "children": [], + "rerun_links": [] + } + } + ] + } + ] + }, + "checks": [ + { + "path": [ + "attempts", + "steps", + 0, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "attempts", + "steps", + 0, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_PASS" + }, + { + "path": [ + "attempts", + "steps", + 0, + "history", + "r", + "history_state" + ], + "equals": "FAILURE_OBSERVED" + }, + { + "path": [ + "attempts", + "steps", + 0, + "history", + "r", + "historical_contribution" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "attempts", + "steps", + 0, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "attempts", + "steps", + 0, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "attempts", + "steps", + 0, + "integrity", + "evaluation_status" + ], + "equals": "AVAILABLE" + }, + { + "path": [ + "attempts", + "steps", + 0, + "integrity", + "band" + ], + "equals": "SPARSE_MIXED" + }, + { + "path": [ + "attempts", + "steps", + 0, + "normalization", + "provenance", + "r", + "parent_ids" + ], + "equals": [ + "github_actions:workflow_run:700" + ] + }, + { + "path": [ + "attempts", + "steps", + 0, + "normalization", + "provenance", + "r", + "current_attempts", + "github_actions:workflow_run:700" + ], + "equals": { + "ordinal": 2, + "terminal": true, + "state": "VERIFY_PASS" + } + }, + { + "path": [ + "attempts", + "steps", + 0, + "verified" + ], + "equals": true + } + ] + }, + { + "case": "R27", + "scenario": "ci-revisions.v1", + "given": { + "variants": [ + { + "name": "distinct-runs", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 1, + "status": "completed", + "conclusion": "failure", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + }, + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 701, + "head_sha": "r", + "run_attempt": 1, + "status": "completed", + "conclusion": "success", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + } + ], + "children": [], + "rerun_links": [] + } + } + ] + } + ] + }, + "checks": [ + { + "path": [ + "distinct-runs", + "steps", + 0, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "distinct-runs", + "steps", + 0, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "distinct-runs", + "steps", + 0, + "history", + "r", + "history_state" + ], + "equals": "FAILURE_OBSERVED" + }, + { + "path": [ + "distinct-runs", + "steps", + 0, + "history", + "r", + "historical_contribution" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "distinct-runs", + "steps", + 0, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "distinct-runs", + "steps", + 0, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "distinct-runs", + "steps", + 0, + "integrity", + "evaluation_status" + ], + "equals": "AVAILABLE" + }, + { + "path": [ + "distinct-runs", + "steps", + 0, + "integrity", + "band" + ], + "equals": "FAILING" + }, + { + "path": [ + "distinct-runs", + "steps", + 0, + "normalization", + "provenance", + "r", + "parent_ids" + ], + "equals": [ + "github_actions:workflow_run:700", + "github_actions:workflow_run:701" + ] + }, + { + "path": [ + "distinct-runs", + "steps", + 0, + "verified" + ], + "equals": true + } + ] + }, + { + "case": "R28", + "scenario": "ci-revisions.v1", + "given": { + "variants": [ + { + "name": "actions-first", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 1, + "status": "completed", + "conclusion": "failure", + "name": "same CI name", + "workflow_id": 42, + "event": "push", + "check_suite_id": 501 + } + }, + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T11:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 2, + "status": "completed", + "conclusion": "success", + "name": "same CI name", + "workflow_id": 42, + "event": "push", + "check_suite_id": 501 + } + }, + { + "surface": "checks", + "revision": "r", + "observed_at": "2026-09-05T11:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 501, + "head_sha": "r", + "status": "completed", + "conclusion": "success", + "app": { + "id": 42, + "slug": "same-app" + } + } + } + ], + "children": [], + "rerun_links": [] + } + } + ] + }, + { + "name": "suite-first", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "checks", + "revision": "r", + "observed_at": "2026-09-05T11:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 501, + "head_sha": "r", + "status": "completed", + "conclusion": "success", + "app": { + "id": 42, + "slug": "same-app" + } + } + }, + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T11:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 2, + "status": "completed", + "conclusion": "success", + "name": "same CI name", + "workflow_id": 42, + "event": "push", + "check_suite_id": 501 + } + }, + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 1, + "status": "completed", + "conclusion": "failure", + "name": "same CI name", + "workflow_id": 42, + "event": "push", + "check_suite_id": 501 + } + } + ], + "children": [], + "rerun_links": [] + } + } + ] + }, + { + "name": "unlinked-same-app", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 1, + "status": "completed", + "conclusion": "success", + "name": "same CI name", + "workflow_id": 42, + "event": "push", + "check_suite_id": 501 + } + }, + { + "surface": "checks", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 502, + "head_sha": "r", + "status": "completed", + "conclusion": "failure", + "app": { + "id": 42, + "slug": "same-app" + } + } + } + ], + "children": [], + "rerun_links": [] + } + } + ] + } + ] + }, + "checks": [ + { + "path": [ + "actions-first", + "steps", + 0, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "actions-first", + "steps", + 0, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_PASS" + }, + { + "path": [ + "actions-first", + "steps", + 0, + "history", + "r", + "history_state" + ], + "equals": "FAILURE_OBSERVED" + }, + { + "path": [ + "actions-first", + "steps", + 0, + "history", + "r", + "historical_contribution" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "actions-first", + "steps", + 0, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "actions-first", + "steps", + 0, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "actions-first", + "steps", + 0, + "integrity", + "evaluation_status" + ], + "equals": "AVAILABLE" + }, + { + "path": [ + "actions-first", + "steps", + 0, + "integrity", + "band" + ], + "equals": "SPARSE_MIXED" + }, + { + "path": [ + "actions-first", + "steps", + 0, + "normalization", + "provenance", + "r", + "parent_ids" + ], + "equals": [ + "github_actions:workflow_run:700" + ] + }, + { + "path": [ + "actions-first", + "steps", + 0, + "normalization", + "provenance", + "r", + "retained_suites" + ], + "equals": [ + "github_checks:check_suite:501" + ] + }, + { + "path": [ + "suite-first", + "steps", + 0, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "suite-first", + "steps", + 0, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_PASS" + }, + { + "path": [ + "suite-first", + "steps", + 0, + "history", + "r", + "history_state" + ], + "equals": "FAILURE_OBSERVED" + }, + { + "path": [ + "suite-first", + "steps", + 0, + "history", + "r", + "historical_contribution" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "suite-first", + "steps", + 0, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "suite-first", + "steps", + 0, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "suite-first", + "steps", + 0, + "integrity", + "evaluation_status" + ], + "equals": "AVAILABLE" + }, + { + "path": [ + "suite-first", + "steps", + 0, + "integrity", + "band" + ], + "equals": "SPARSE_MIXED" + }, + { + "path": [ + "suite-first", + "steps", + 0, + "normalization", + "provenance", + "r", + "parent_ids" + ], + "equals": [ + "github_actions:workflow_run:700" + ] + }, + { + "path": [ + "suite-first", + "steps", + 0, + "normalization", + "provenance", + "r", + "retained_suites" + ], + "equals": [ + "github_checks:check_suite:501" + ] + }, + { + "path": [ + "unlinked-same-app", + "steps", + 0, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "unlinked-same-app", + "steps", + 0, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "unlinked-same-app", + "steps", + 0, + "history", + "r", + "history_state" + ], + "equals": "FAILURE_OBSERVED" + }, + { + "path": [ + "unlinked-same-app", + "steps", + 0, + "history", + "r", + "historical_contribution" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "unlinked-same-app", + "steps", + 0, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "unlinked-same-app", + "steps", + 0, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "unlinked-same-app", + "steps", + 0, + "normalization", + "provenance", + "r", + "parent_ids" + ], + "equals": [ + "github_actions:workflow_run:700", + "github_checks:check_suite:502" + ] + }, + { + "path": [ + "actions-first", + "steps", + 0, + "verified" + ], + "equals": true + }, + { + "path": [ + "suite-first", + "steps", + 0, + "verified" + ], + "equals": true + }, + { + "path": [ + "unlinked-same-app", + "steps", + 0, + "verified" + ], + "equals": true + } + ] + }, + { + "case": "R29", + "scenario": "ci-revisions.v1", + "given": { + "variants": [ + { + "name": "queued", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 1, + "status": "completed", + "conclusion": "success", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + }, + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T11:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 2, + "status": "queued", + "conclusion": null, + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + } + ], + "children": [], + "rerun_links": [] + } + } + ] + }, + { + "name": "in_progress", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 1, + "status": "completed", + "conclusion": "success", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + }, + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T11:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 2, + "status": "in_progress", + "conclusion": null, + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + } + ], + "children": [], + "rerun_links": [] + } + } + ] + } + ] + }, + "checks": [ + { + "path": [ + "queued", + "steps", + 0, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "queued", + "steps", + 0, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_UNRESOLVED" + }, + { + "path": [ + "queued", + "steps", + 0, + "history", + "r", + "history_state" + ], + "equals": "PASS_ONLY_OBSERVED" + }, + { + "path": [ + "queued", + "steps", + 0, + "history", + "r", + "historical_contribution" + ], + "equals": "VERIFY_PASS" + }, + { + "path": [ + "queued", + "steps", + 0, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "queued", + "steps", + 0, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 0 + }, + { + "path": [ + "queued", + "steps", + 0, + "integrity", + "evaluation_status" + ], + "equals": "UNKNOWN" + }, + { + "path": [ + "queued", + "steps", + 0, + "integrity", + "band" + ], + "equals": null + }, + { + "path": [ + "queued", + "steps", + 0, + "normalization", + "provenance", + "r", + "current_attempts", + "github_actions:workflow_run:700" + ], + "equals": { + "ordinal": 2, + "terminal": false, + "state": "VERIFY_UNRESOLVED" + } + }, + { + "path": [ + "queued", + "steps", + 0, + "current_decisive_count" + ], + "equals": 0 + }, + { + "path": [ + "queued", + "steps", + 0, + "current_failed_count" + ], + "equals": 0 + }, + { + "path": [ + "in_progress", + "steps", + 0, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "in_progress", + "steps", + 0, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_UNRESOLVED" + }, + { + "path": [ + "in_progress", + "steps", + 0, + "history", + "r", + "history_state" + ], + "equals": "PASS_ONLY_OBSERVED" + }, + { + "path": [ + "in_progress", + "steps", + 0, + "history", + "r", + "historical_contribution" + ], + "equals": "VERIFY_PASS" + }, + { + "path": [ + "in_progress", + "steps", + 0, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "in_progress", + "steps", + 0, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 0 + }, + { + "path": [ + "in_progress", + "steps", + 0, + "integrity", + "evaluation_status" + ], + "equals": "UNKNOWN" + }, + { + "path": [ + "in_progress", + "steps", + 0, + "integrity", + "band" + ], + "equals": null + }, + { + "path": [ + "in_progress", + "steps", + 0, + "normalization", + "provenance", + "r", + "current_attempts", + "github_actions:workflow_run:700" + ], + "equals": { + "ordinal": 2, + "terminal": false, + "state": "VERIFY_UNRESOLVED" + } + }, + { + "path": [ + "in_progress", + "steps", + 0, + "current_decisive_count" + ], + "equals": 0 + }, + { + "path": [ + "in_progress", + "steps", + 0, + "current_failed_count" + ], + "equals": 0 + }, + { + "path": [ + "queued", + "steps", + 0, + "verified" + ], + "equals": true + }, + { + "path": [ + "in_progress", + "steps", + 0, + "verified" + ], + "equals": true + } + ] + }, + { + "case": "R30", + "scenario": "ci-revisions.v1", + "given": { + "variants": [ + { + "name": "queued", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 1, + "status": "completed", + "conclusion": "failure", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + }, + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T11:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 2, + "status": "queued", + "conclusion": null, + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + } + ], + "children": [], + "rerun_links": [] + } + } + ] + }, + { + "name": "in_progress", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 1, + "status": "completed", + "conclusion": "failure", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + }, + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T11:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 2, + "status": "in_progress", + "conclusion": null, + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + } + ], + "children": [], + "rerun_links": [] + } + } + ] + } + ] + }, + "checks": [ + { + "path": [ + "queued", + "steps", + 0, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "queued", + "steps", + 0, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_UNRESOLVED" + }, + { + "path": [ + "queued", + "steps", + 0, + "history", + "r", + "history_state" + ], + "equals": "FAILURE_OBSERVED" + }, + { + "path": [ + "queued", + "steps", + 0, + "history", + "r", + "historical_contribution" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "queued", + "steps", + 0, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "queued", + "steps", + 0, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "queued", + "steps", + 0, + "integrity", + "evaluation_status" + ], + "equals": "UNKNOWN" + }, + { + "path": [ + "queued", + "steps", + 0, + "integrity", + "band" + ], + "equals": null + }, + { + "path": [ + "queued", + "steps", + 0, + "normalization", + "provenance", + "r", + "current_attempts", + "github_actions:workflow_run:700" + ], + "equals": { + "ordinal": 2, + "terminal": false, + "state": "VERIFY_UNRESOLVED" + } + }, + { + "path": [ + "queued", + "steps", + 0, + "current_decisive_count" + ], + "equals": 0 + }, + { + "path": [ + "queued", + "steps", + 0, + "current_failed_count" + ], + "equals": 0 + }, + { + "path": [ + "in_progress", + "steps", + 0, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "in_progress", + "steps", + 0, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_UNRESOLVED" + }, + { + "path": [ + "in_progress", + "steps", + 0, + "history", + "r", + "history_state" + ], + "equals": "FAILURE_OBSERVED" + }, + { + "path": [ + "in_progress", + "steps", + 0, + "history", + "r", + "historical_contribution" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "in_progress", + "steps", + 0, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "in_progress", + "steps", + 0, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "in_progress", + "steps", + 0, + "integrity", + "evaluation_status" + ], + "equals": "UNKNOWN" + }, + { + "path": [ + "in_progress", + "steps", + 0, + "integrity", + "band" + ], + "equals": null + }, + { + "path": [ + "in_progress", + "steps", + 0, + "normalization", + "provenance", + "r", + "current_attempts", + "github_actions:workflow_run:700" + ], + "equals": { + "ordinal": 2, + "terminal": false, + "state": "VERIFY_UNRESOLVED" + } + }, + { + "path": [ + "in_progress", + "steps", + 0, + "current_decisive_count" + ], + "equals": 0 + }, + { + "path": [ + "in_progress", + "steps", + 0, + "current_failed_count" + ], + "equals": 0 + }, + { + "path": [ + "queued", + "steps", + 0, + "verified" + ], + "equals": true + }, + { + "path": [ + "in_progress", + "steps", + 0, + "verified" + ], + "equals": true + } + ] + }, + { + "case": "R31", + "scenario": "ci-revisions.v1", + "given": { + "variants": [ + { + "name": "actions-only", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 1, + "status": "completed", + "conclusion": "success", + "name": "same CI name", + "workflow_id": 42, + "event": "push", + "check_suite_id": 501 + } + }, + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T11:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 2, + "status": "in_progress", + "conclusion": null, + "name": "same CI name", + "workflow_id": 42, + "event": "push", + "check_suite_id": 501 + } + } + ], + "children": [], + "rerun_links": [] + } + } + ] + }, + { + "name": "checks-only", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "checks", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 501, + "head_sha": "r", + "status": "completed", + "conclusion": "success", + "app": { + "id": 42, + "slug": "same-app" + } + } + }, + { + "surface": "checks", + "revision": "r", + "observed_at": "2026-09-05T11:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 501, + "head_sha": "r", + "status": "in_progress", + "conclusion": null, + "app": { + "id": 42, + "slug": "same-app" + } + } + } + ], + "children": [], + "rerun_links": [] + } + } + ] + }, + { + "name": "combined", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 1, + "status": "completed", + "conclusion": "success", + "name": "same CI name", + "workflow_id": 42, + "event": "push", + "check_suite_id": 501 + } + }, + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T11:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 2, + "status": "in_progress", + "conclusion": null, + "name": "same CI name", + "workflow_id": 42, + "event": "push", + "check_suite_id": 501 + } + }, + { + "surface": "checks", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 501, + "head_sha": "r", + "status": "completed", + "conclusion": "success", + "app": { + "id": 42, + "slug": "same-app" + } + } + } + ], + "children": [], + "rerun_links": [] + } + } + ] + }, + { + "name": "combined-reverse", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "checks", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 501, + "head_sha": "r", + "status": "completed", + "conclusion": "success", + "app": { + "id": 42, + "slug": "same-app" + } + } + }, + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T11:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 2, + "status": "in_progress", + "conclusion": null, + "name": "same CI name", + "workflow_id": 42, + "event": "push", + "check_suite_id": 501 + } + }, + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 1, + "status": "completed", + "conclusion": "success", + "name": "same CI name", + "workflow_id": 42, + "event": "push", + "check_suite_id": 501 + } + } + ], + "children": [], + "rerun_links": [] + } + } + ] + }, + { + "name": "newer-surface-disagreement", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 1, + "status": "completed", + "conclusion": "success", + "name": "same CI name", + "workflow_id": 42, + "event": "push", + "check_suite_id": 501 + } + }, + { + "surface": "checks", + "revision": "r", + "observed_at": "2026-09-05T11:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 501, + "head_sha": "r", + "status": "in_progress", + "conclusion": null, + "app": { + "id": 42, + "slug": "same-app" + } + } + } + ], + "children": [], + "rerun_links": [] + } + } + ] + }, + { + "name": "stale-only", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "checks", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "STALE", + "native": { + "id": 501, + "head_sha": "r", + "status": "completed", + "conclusion": "success", + "app": { + "id": 42, + "slug": "same-app" + } + } + } + ], + "children": [], + "rerun_links": [] + } + } + ] + } + ] + }, + "checks": [ + { + "path": [ + "actions-only", + "steps", + 0, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_UNRESOLVED" + }, + { + "path": [ + "actions-only", + "steps", + 0, + "current_decisive_count" + ], + "equals": 0 + }, + { + "path": [ + "actions-only", + "steps", + 0, + "integrity", + "evaluation_status" + ], + "equals": "UNKNOWN" + }, + { + "path": [ + "actions-only", + "steps", + 0, + "integrity", + "band" + ], + "equals": null + }, + { + "path": [ + "checks-only", + "steps", + 0, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_UNRESOLVED" + }, + { + "path": [ + "checks-only", + "steps", + 0, + "current_decisive_count" + ], + "equals": 0 + }, + { + "path": [ + "checks-only", + "steps", + 0, + "integrity", + "evaluation_status" + ], + "equals": "UNKNOWN" + }, + { + "path": [ + "checks-only", + "steps", + 0, + "integrity", + "band" + ], + "equals": null + }, + { + "path": [ + "combined", + "steps", + 0, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_UNRESOLVED" + }, + { + "path": [ + "combined", + "steps", + 0, + "current_decisive_count" + ], + "equals": 0 + }, + { + "path": [ + "combined", + "steps", + 0, + "integrity", + "evaluation_status" + ], + "equals": "UNKNOWN" + }, + { + "path": [ + "combined", + "steps", + 0, + "integrity", + "band" + ], + "equals": null + }, + { + "path": [ + "combined-reverse", + "steps", + 0, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_UNRESOLVED" + }, + { + "path": [ + "combined-reverse", + "steps", + 0, + "current_decisive_count" + ], + "equals": 0 + }, + { + "path": [ + "combined-reverse", + "steps", + 0, + "integrity", + "evaluation_status" + ], + "equals": "UNKNOWN" + }, + { + "path": [ + "combined-reverse", + "steps", + 0, + "integrity", + "band" + ], + "equals": null + }, + { + "path": [ + "newer-surface-disagreement", + "steps", + 0, + "current", + "r", + "current_verdict" + ], + "equals": "UNKNOWN" + }, + { + "path": [ + "newer-surface-disagreement", + "steps", + 0, + "current_decisive_count" + ], + "equals": 0 + }, + { + "path": [ + "newer-surface-disagreement", + "steps", + 0, + "integrity", + "evaluation_status" + ], + "equals": "UNKNOWN" + }, + { + "path": [ + "newer-surface-disagreement", + "steps", + 0, + "integrity", + "band" + ], + "equals": null + }, + { + "path": [ + "stale-only", + "steps", + 0, + "current", + "r", + "current_verdict" + ], + "equals": "UNKNOWN" + }, + { + "path": [ + "stale-only", + "steps", + 0, + "current_decisive_count" + ], + "equals": 0 + }, + { + "path": [ + "stale-only", + "steps", + 0, + "integrity", + "evaluation_status" + ], + "equals": "UNKNOWN" + }, + { + "path": [ + "stale-only", + "steps", + 0, + "integrity", + "band" + ], + "equals": null + }, + { + "path": [ + "actions-only", + "steps", + 0, + "verified" + ], + "equals": true + }, + { + "path": [ + "checks-only", + "steps", + 0, + "verified" + ], + "equals": true + }, + { + "path": [ + "combined", + "steps", + 0, + "verified" + ], + "equals": true + }, + { + "path": [ + "combined-reverse", + "steps", + 0, + "verified" + ], + "equals": true + }, + { + "path": [ + "newer-surface-disagreement", + "steps", + 0, + "verified" + ], + "equals": true + }, + { + "path": [ + "stale-only", + "steps", + 0, + "verified" + ], + "equals": true + } + ] + }, + { + "case": "R32", + "scenario": "ci-revisions.v1", + "given": { + "variants": [ + { + "name": "pass-running", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "gitlab", + "forge_instance": "gitlab.example", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.gitlab.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "pipelines", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 101, + "sha": "r", + "status": "success", + "ref": "main", + "source": "push" + } + }, + { + "surface": "pipelines", + "revision": "r", + "observed_at": "2026-09-05T11:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 101, + "sha": "r", + "status": "running", + "ref": "main", + "source": "push" + } + } + ], + "children": [], + "rerun_links": [] + } + } + ] + }, + { + "name": "fail-pending", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "gitlab", + "forge_instance": "gitlab.example", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.gitlab.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "pipelines", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 101, + "sha": "r", + "status": "failed", + "ref": "main", + "source": "push" + } + }, + { + "surface": "pipelines", + "revision": "r", + "observed_at": "2026-09-05T11:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 101, + "sha": "r", + "status": "pending", + "ref": "main", + "source": "push" + } + } + ], + "children": [], + "rerun_links": [] + } + } + ] + } + ] + }, + "checks": [ + { + "path": [ + "pass-running", + "steps", + 0, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "pass-running", + "steps", + 0, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_UNRESOLVED" + }, + { + "path": [ + "pass-running", + "steps", + 0, + "history", + "r", + "history_state" + ], + "equals": "UNKNOWN_HISTORY" + }, + { + "path": [ + "pass-running", + "steps", + 0, + "history", + "r", + "historical_contribution" + ], + "equals": null + }, + { + "path": [ + "pass-running", + "steps", + 0, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 0 + }, + { + "path": [ + "pass-running", + "steps", + 0, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 0 + }, + { + "path": [ + "pass-running", + "steps", + 0, + "integrity", + "evaluation_status" + ], + "equals": "UNKNOWN" + }, + { + "path": [ + "pass-running", + "steps", + 0, + "integrity", + "band" + ], + "equals": null + }, + { + "path": [ + "pass-running", + "steps", + 0, + "normalization", + "provenance", + "r", + "parent_ids" + ], + "equals": [ + "gitlab:pipeline:101" + ] + }, + { + "path": [ + "pass-running", + "steps", + 0, + "normalization", + "provenance", + "r", + "current_attempts", + "gitlab:pipeline:101" + ], + "equals": { + "ordinal": null, + "terminal": false, + "state": "VERIFY_UNRESOLVED" + } + }, + { + "path": [ + "pass-running", + "steps", + 0, + "current_decisive_count" + ], + "equals": 0 + }, + { + "path": [ + "fail-pending", + "steps", + 0, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "fail-pending", + "steps", + 0, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_UNRESOLVED" + }, + { + "path": [ + "fail-pending", + "steps", + 0, + "history", + "r", + "history_state" + ], + "equals": "FAILURE_OBSERVED" + }, + { + "path": [ + "fail-pending", + "steps", + 0, + "history", + "r", + "historical_contribution" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "fail-pending", + "steps", + 0, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "fail-pending", + "steps", + 0, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "fail-pending", + "steps", + 0, + "integrity", + "evaluation_status" + ], + "equals": "UNKNOWN" + }, + { + "path": [ + "fail-pending", + "steps", + 0, + "integrity", + "band" + ], + "equals": null + }, + { + "path": [ + "fail-pending", + "steps", + 0, + "normalization", + "provenance", + "r", + "parent_ids" + ], + "equals": [ + "gitlab:pipeline:101" + ] + }, + { + "path": [ + "fail-pending", + "steps", + 0, + "normalization", + "provenance", + "r", + "current_attempts", + "gitlab:pipeline:101" + ], + "equals": { + "ordinal": null, + "terminal": false, + "state": "VERIFY_UNRESOLVED" + } + }, + { + "path": [ + "fail-pending", + "steps", + 0, + "current_decisive_count" + ], + "equals": 0 + }, + { + "path": [ + "pass-running", + "steps", + 0, + "verified" + ], + "equals": true + }, + { + "path": [ + "fail-pending", + "steps", + 0, + "verified" + ], + "equals": true + } + ] + }, + { + "case": "R33", + "scenario": "ci-revisions.v1", + "given": { + "variants": [ + { + "name": "pass-fail-running", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 1, + "status": "completed", + "conclusion": "success", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + }, + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 2, + "status": "completed", + "conclusion": "failure", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + }, + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 3, + "status": "in_progress", + "conclusion": null, + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + } + ], + "children": [], + "rerun_links": [] + } + } + ] + }, + { + "name": "fail-pass-running", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 1, + "status": "completed", + "conclusion": "failure", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + }, + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 2, + "status": "completed", + "conclusion": "success", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + }, + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 3, + "status": "in_progress", + "conclusion": null, + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + } + ], + "children": [], + "rerun_links": [] + } + } + ] + } + ] + }, + "checks": [ + { + "path": [ + "pass-fail-running", + "steps", + 0, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "pass-fail-running", + "steps", + 0, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_UNRESOLVED" + }, + { + "path": [ + "pass-fail-running", + "steps", + 0, + "history", + "r", + "history_state" + ], + "equals": "FAILURE_OBSERVED" + }, + { + "path": [ + "pass-fail-running", + "steps", + 0, + "history", + "r", + "historical_contribution" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "pass-fail-running", + "steps", + 0, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "pass-fail-running", + "steps", + 0, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "pass-fail-running", + "steps", + 0, + "integrity", + "evaluation_status" + ], + "equals": "UNKNOWN" + }, + { + "path": [ + "pass-fail-running", + "steps", + 0, + "integrity", + "band" + ], + "equals": null + }, + { + "path": [ + "pass-fail-running", + "steps", + 0, + "normalization", + "provenance", + "r", + "current_attempts", + "github_actions:workflow_run:700" + ], + "equals": { + "ordinal": 3, + "terminal": false, + "state": "VERIFY_UNRESOLVED" + } + }, + { + "path": [ + "fail-pass-running", + "steps", + 0, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "fail-pass-running", + "steps", + 0, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_UNRESOLVED" + }, + { + "path": [ + "fail-pass-running", + "steps", + 0, + "history", + "r", + "history_state" + ], + "equals": "FAILURE_OBSERVED" + }, + { + "path": [ + "fail-pass-running", + "steps", + 0, + "history", + "r", + "historical_contribution" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "fail-pass-running", + "steps", + 0, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "fail-pass-running", + "steps", + 0, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "fail-pass-running", + "steps", + 0, + "integrity", + "evaluation_status" + ], + "equals": "UNKNOWN" + }, + { + "path": [ + "fail-pass-running", + "steps", + 0, + "integrity", + "band" + ], + "equals": null + }, + { + "path": [ + "fail-pass-running", + "steps", + 0, + "normalization", + "provenance", + "r", + "current_attempts", + "github_actions:workflow_run:700" + ], + "equals": { + "ordinal": 3, + "terminal": false, + "state": "VERIFY_UNRESOLVED" + } + }, + { + "path": [ + "pass-fail-running", + "steps", + 0, + "verified" + ], + "equals": true + }, + { + "path": [ + "fail-pass-running", + "steps", + 0, + "verified" + ], + "equals": true + } + ] + }, + { + "case": "R34", + "scenario": "ci-revisions.v1", + "given": { + "variants": [ + { + "name": "latest-unavailable", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 1, + "status": "completed", + "conclusion": "success", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + }, + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 2, + "status": "completed", + "conclusion": "success", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + }, + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "UNAVAILABLE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 3, + "status": null, + "conclusion": null, + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + } + ], + "children": [], + "rerun_links": [] + } + } + ] + }, + { + "name": "latest-contradictory", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 1, + "status": "completed", + "conclusion": "success", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + }, + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 2, + "status": "completed", + "conclusion": "success", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + }, + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 3, + "status": "completed", + "conclusion": "success", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + }, + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 3, + "status": "completed", + "conclusion": "failure", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + } + ], + "children": [], + "rerun_links": [] + } + } + ] + } + ] + }, + "checks": [ + { + "path": [ + "latest-unavailable", + "steps", + 0, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "latest-unavailable", + "steps", + 0, + "current", + "r", + "current_verdict" + ], + "equals": "UNKNOWN" + }, + { + "path": [ + "latest-unavailable", + "steps", + 0, + "history", + "r", + "history_state" + ], + "equals": "UNKNOWN_HISTORY" + }, + { + "path": [ + "latest-unavailable", + "steps", + 0, + "history", + "r", + "historical_contribution" + ], + "equals": null + }, + { + "path": [ + "latest-unavailable", + "steps", + 0, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 0 + }, + { + "path": [ + "latest-unavailable", + "steps", + 0, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 0 + }, + { + "path": [ + "latest-unavailable", + "steps", + 0, + "integrity", + "evaluation_status" + ], + "equals": "UNKNOWN" + }, + { + "path": [ + "latest-unavailable", + "steps", + 0, + "integrity", + "band" + ], + "equals": null + }, + { + "path": [ + "latest-contradictory", + "steps", + 0, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "latest-contradictory", + "steps", + 0, + "current", + "r", + "current_verdict" + ], + "equals": "UNKNOWN" + }, + { + "path": [ + "latest-contradictory", + "steps", + 0, + "history", + "r", + "history_state" + ], + "equals": "FAILURE_OBSERVED" + }, + { + "path": [ + "latest-contradictory", + "steps", + 0, + "history", + "r", + "historical_contribution" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "latest-contradictory", + "steps", + 0, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "latest-contradictory", + "steps", + 0, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "latest-contradictory", + "steps", + 0, + "integrity", + "evaluation_status" + ], + "equals": "UNKNOWN" + }, + { + "path": [ + "latest-contradictory", + "steps", + 0, + "integrity", + "band" + ], + "equals": null + }, + { + "path": [ + "latest-unavailable", + "steps", + 0, + "normalization", + "provenance", + "r", + "current_attempts", + "github_actions:workflow_run:700" + ], + "equals": { + "ordinal": 3, + "terminal": false, + "state": "UNKNOWN" + } + }, + { + "path": [ + "latest-unavailable", + "steps", + 0, + "normalization", + "coverage" + ], + "equals": "DEGRADED" + }, + { + "path": [ + "latest-contradictory", + "steps", + 0, + "normalization", + "provenance", + "r", + "current_attempts", + "github_actions:workflow_run:700" + ], + "equals": { + "ordinal": 3, + "terminal": false, + "state": "UNKNOWN" + } + }, + { + "path": [ + "latest-contradictory", + "steps", + 0, + "normalization", + "coverage" + ], + "equals": "DEGRADED" + }, + { + "path": [ + "latest-unavailable", + "steps", + 0, + "verified" + ], + "equals": true + }, + { + "path": [ + "latest-contradictory", + "steps", + 0, + "verified" + ], + "equals": true + } + ] + }, + { + "case": "R35", + "scenario": "ci-revisions.v1", + "given": { + "variants": [ + { + "name": "forward", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 1, + "status": "completed", + "conclusion": "success", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + }, + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 2, + "status": "completed", + "conclusion": "failure", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + }, + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 3, + "status": "in_progress", + "conclusion": null, + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + } + ], + "children": [], + "rerun_links": [] + } + } + ] + }, + { + "name": "reverse", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 3, + "status": "in_progress", + "conclusion": null, + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + }, + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 2, + "status": "completed", + "conclusion": "failure", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + }, + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 1, + "status": "completed", + "conclusion": "success", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + } + ], + "children": [], + "rerun_links": [] + } + } + ] + }, + { + "name": "pages", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": { + "pages": [ + { + "ordinal": 1, + "has_next": true, + "items": [ + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 1, + "status": "completed", + "conclusion": "success", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + }, + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 2, + "status": "completed", + "conclusion": "failure", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + } + ] + }, + { + "ordinal": 2, + "has_next": false, + "items": [ + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 3, + "status": "in_progress", + "conclusion": null, + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + } + ] + } + ] + }, + "children": [], + "rerun_links": [] + } + } + ] + } + ] + }, + "checks": [ + { + "path": [ + "forward", + "steps", + 0, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "forward", + "steps", + 0, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_UNRESOLVED" + }, + { + "path": [ + "forward", + "steps", + 0, + "history", + "r", + "history_state" + ], + "equals": "FAILURE_OBSERVED" + }, + { + "path": [ + "forward", + "steps", + 0, + "history", + "r", + "historical_contribution" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "forward", + "steps", + 0, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "forward", + "steps", + 0, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "forward", + "steps", + 0, + "integrity", + "evaluation_status" + ], + "equals": "UNKNOWN" + }, + { + "path": [ + "forward", + "steps", + 0, + "integrity", + "band" + ], + "equals": null + }, + { + "path": [ + "forward", + "steps", + 0, + "normalization", + "provenance", + "r", + "current_attempts", + "github_actions:workflow_run:700" + ], + "equals": { + "ordinal": 3, + "terminal": false, + "state": "VERIFY_UNRESOLVED" + } + }, + { + "path": [ + "reverse", + "steps", + 0, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "reverse", + "steps", + 0, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_UNRESOLVED" + }, + { + "path": [ + "reverse", + "steps", + 0, + "history", + "r", + "history_state" + ], + "equals": "FAILURE_OBSERVED" + }, + { + "path": [ + "reverse", + "steps", + 0, + "history", + "r", + "historical_contribution" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "reverse", + "steps", + 0, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "reverse", + "steps", + 0, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "reverse", + "steps", + 0, + "integrity", + "evaluation_status" + ], + "equals": "UNKNOWN" + }, + { + "path": [ + "reverse", + "steps", + 0, + "integrity", + "band" + ], + "equals": null + }, + { + "path": [ + "reverse", + "steps", + 0, + "normalization", + "provenance", + "r", + "current_attempts", + "github_actions:workflow_run:700" + ], + "equals": { + "ordinal": 3, + "terminal": false, + "state": "VERIFY_UNRESOLVED" + } + }, + { + "path": [ + "pages", + "steps", + 0, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "pages", + "steps", + 0, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_UNRESOLVED" + }, + { + "path": [ + "pages", + "steps", + 0, + "history", + "r", + "history_state" + ], + "equals": "FAILURE_OBSERVED" + }, + { + "path": [ + "pages", + "steps", + 0, + "history", + "r", + "historical_contribution" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "pages", + "steps", + 0, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "pages", + "steps", + 0, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "pages", + "steps", + 0, + "integrity", + "evaluation_status" + ], + "equals": "UNKNOWN" + }, + { + "path": [ + "pages", + "steps", + 0, + "integrity", + "band" + ], + "equals": null + }, + { + "path": [ + "pages", + "steps", + 0, + "normalization", + "provenance", + "r", + "current_attempts", + "github_actions:workflow_run:700" + ], + "equals": { + "ordinal": 3, + "terminal": false, + "state": "VERIFY_UNRESOLVED" + } + }, + { + "path": [ + "forward", + "steps", + 0, + "verified" + ], + "equals": true + }, + { + "path": [ + "reverse", + "steps", + 0, + "verified" + ], + "equals": true + }, + { + "path": [ + "pages", + "steps", + 0, + "verified" + ], + "equals": true + } + ] + }, + { + "case": "R36", + "scenario": "ci-revisions.v1", + "given": { + "variants": [ + { + "name": "unresolved-pass", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 1, + "status": "in_progress", + "conclusion": null, + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + }, + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 701, + "head_sha": "r", + "run_attempt": 1, + "status": "completed", + "conclusion": "success", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + } + ], + "children": [], + "rerun_links": [] + } + } + ] + } + ] + }, + "checks": [ + { + "path": [ + "unresolved-pass", + "steps", + 0, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "unresolved-pass", + "steps", + 0, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_UNRESOLVED" + }, + { + "path": [ + "unresolved-pass", + "steps", + 0, + "history", + "r", + "history_state" + ], + "equals": "PASS_ONLY_OBSERVED" + }, + { + "path": [ + "unresolved-pass", + "steps", + 0, + "history", + "r", + "historical_contribution" + ], + "equals": "VERIFY_PASS" + }, + { + "path": [ + "unresolved-pass", + "steps", + 0, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "unresolved-pass", + "steps", + 0, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 0 + }, + { + "path": [ + "unresolved-pass", + "steps", + 0, + "integrity", + "evaluation_status" + ], + "equals": "UNKNOWN" + }, + { + "path": [ + "unresolved-pass", + "steps", + 0, + "integrity", + "band" + ], + "equals": null + }, + { + "path": [ + "unresolved-pass", + "steps", + 0, + "current_decisive_count" + ], + "equals": 0 + }, + { + "path": [ + "unresolved-pass", + "steps", + 0, + "verified" + ], + "equals": true + } + ] + }, + { + "case": "R37", + "scenario": "ci-revisions.v1", + "given": { + "variants": [ + { + "name": "normal", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 1, + "status": "in_progress", + "conclusion": null, + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + }, + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 701, + "head_sha": "r", + "run_attempt": 1, + "status": "completed", + "conclusion": "failure", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + } + ], + "children": [], + "rerun_links": [] + } + } + ] + }, + { + "name": "reverse", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 701, + "head_sha": "r", + "run_attempt": 1, + "status": "completed", + "conclusion": "failure", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + }, + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 1, + "status": "in_progress", + "conclusion": null, + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + } + ], + "children": [], + "rerun_links": [] + } + } + ] + } + ] + }, + "checks": [ + { + "path": [ + "normal", + "steps", + 0, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "normal", + "steps", + 0, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "normal", + "steps", + 0, + "history", + "r", + "history_state" + ], + "equals": "FAILURE_OBSERVED" + }, + { + "path": [ + "normal", + "steps", + 0, + "history", + "r", + "historical_contribution" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "normal", + "steps", + 0, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "normal", + "steps", + 0, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "normal", + "steps", + 0, + "integrity", + "evaluation_status" + ], + "equals": "AVAILABLE" + }, + { + "path": [ + "normal", + "steps", + 0, + "integrity", + "band" + ], + "equals": "FAILING" + }, + { + "path": [ + "reverse", + "steps", + 0, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "reverse", + "steps", + 0, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "reverse", + "steps", + 0, + "history", + "r", + "history_state" + ], + "equals": "FAILURE_OBSERVED" + }, + { + "path": [ + "reverse", + "steps", + 0, + "history", + "r", + "historical_contribution" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "reverse", + "steps", + 0, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "reverse", + "steps", + 0, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "reverse", + "steps", + 0, + "integrity", + "evaluation_status" + ], + "equals": "AVAILABLE" + }, + { + "path": [ + "reverse", + "steps", + 0, + "integrity", + "band" + ], + "equals": "FAILING" + }, + { + "path": [ + "normal", + "steps", + 0, + "verified" + ], + "equals": true + }, + { + "path": [ + "reverse", + "steps", + 0, + "verified" + ], + "equals": true + } + ] + }, + { + "case": "R38", + "scenario": "ci-revisions.v1", + "given": { + "variants": [ + { + "name": "github-linked", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 1, + "status": "completed", + "conclusion": "success", + "name": "same CI name", + "workflow_id": 42, + "event": "push", + "check_suite_id": 501 + } + }, + { + "surface": "checks", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 501, + "head_sha": "r", + "status": "completed", + "conclusion": "success", + "app": { + "id": 42, + "slug": "same-app" + } + } + }, + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 701, + "head_sha": "r", + "run_attempt": 1, + "status": "in_progress", + "conclusion": null, + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + } + ], + "children": [], + "rerun_links": [] + } + } + ] + }, + { + "name": "gitlab", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "gitlab", + "forge_instance": "gitlab.example", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.gitlab.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "pipelines", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 101, + "sha": "r", + "status": "success", + "ref": "main", + "source": "push" + } + }, + { + "surface": "pipelines", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 102, + "sha": "r", + "status": "running", + "ref": "main", + "source": "push" + } + } + ], + "children": [], + "rerun_links": [] + } + } + ] + } + ] + }, + "checks": [ + { + "path": [ + "github-linked", + "steps", + 0, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_UNRESOLVED" + }, + { + "path": [ + "github-linked", + "steps", + 0, + "current_decisive_count" + ], + "equals": 0 + }, + { + "path": [ + "github-linked", + "steps", + 0, + "normalization", + "coverage" + ], + "equals": "COMPLETE" + }, + { + "path": [ + "github-linked", + "steps", + 0, + "integrity", + "evaluation_status" + ], + "equals": "UNKNOWN" + }, + { + "path": [ + "github-linked", + "steps", + 0, + "integrity", + "band" + ], + "equals": null + }, + { + "path": [ + "gitlab", + "steps", + 0, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_UNRESOLVED" + }, + { + "path": [ + "gitlab", + "steps", + 0, + "current_decisive_count" + ], + "equals": 0 + }, + { + "path": [ + "gitlab", + "steps", + 0, + "normalization", + "coverage" + ], + "equals": "COMPLETE" + }, + { + "path": [ + "gitlab", + "steps", + 0, + "integrity", + "evaluation_status" + ], + "equals": "UNKNOWN" + }, + { + "path": [ + "gitlab", + "steps", + 0, + "integrity", + "band" + ], + "equals": null + }, + { + "path": [ + "github-linked", + "steps", + 0, + "verified" + ], + "equals": true + }, + { + "path": [ + "gitlab", + "steps", + 0, + "verified" + ], + "equals": true + } + ] + }, + { + "case": "R39", + "scenario": "ci-revisions.v1", + "given": { + "variants": [ + { + "name": "forward", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 1, + "status": "in_progress", + "conclusion": null, + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + }, + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 701, + "head_sha": "r", + "run_attempt": 1, + "status": "completed", + "conclusion": "success", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + }, + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 702, + "head_sha": "r", + "run_attempt": 1, + "status": "completed", + "conclusion": "cancelled", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + } + ], + "children": [], + "rerun_links": [] + } + } + ] + }, + { + "name": "reverse", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 702, + "head_sha": "r", + "run_attempt": 1, + "status": "completed", + "conclusion": "cancelled", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + }, + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 701, + "head_sha": "r", + "run_attempt": 1, + "status": "completed", + "conclusion": "success", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + }, + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 1, + "status": "in_progress", + "conclusion": null, + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + } + ], + "children": [], + "rerun_links": [] + } + } + ] + }, + { + "name": "shuffle", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 701, + "head_sha": "r", + "run_attempt": 1, + "status": "completed", + "conclusion": "success", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + }, + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 702, + "head_sha": "r", + "run_attempt": 1, + "status": "completed", + "conclusion": "cancelled", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + }, + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 1, + "status": "in_progress", + "conclusion": null, + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + } + ], + "children": [], + "rerun_links": [] + } + } + ] + }, + { + "name": "pages", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": { + "pages": [ + { + "ordinal": 1, + "has_next": true, + "items": [ + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 1, + "status": "in_progress", + "conclusion": null, + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + }, + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 701, + "head_sha": "r", + "run_attempt": 1, + "status": "completed", + "conclusion": "success", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + } + ] + }, + { + "ordinal": 2, + "has_next": false, + "items": [ + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 702, + "head_sha": "r", + "run_attempt": 1, + "status": "completed", + "conclusion": "cancelled", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + } + ] + } + ] + }, + "children": [], + "rerun_links": [] + } + } + ] + } + ] + }, + "checks": [ + { + "path": [ + "forward", + "steps", + 0, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "forward", + "steps", + 0, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_UNRESOLVED" + }, + { + "path": [ + "forward", + "steps", + 0, + "history", + "r", + "history_state" + ], + "equals": "PASS_ONLY_OBSERVED" + }, + { + "path": [ + "forward", + "steps", + 0, + "history", + "r", + "historical_contribution" + ], + "equals": "VERIFY_PASS" + }, + { + "path": [ + "forward", + "steps", + 0, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "forward", + "steps", + 0, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 0 + }, + { + "path": [ + "forward", + "steps", + 0, + "integrity", + "evaluation_status" + ], + "equals": "UNKNOWN" + }, + { + "path": [ + "forward", + "steps", + 0, + "integrity", + "band" + ], + "equals": null + }, + { + "path": [ + "forward", + "steps", + 0, + "current_decisive_count" + ], + "equals": 0 + }, + { + "path": [ + "reverse", + "steps", + 0, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "reverse", + "steps", + 0, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_UNRESOLVED" + }, + { + "path": [ + "reverse", + "steps", + 0, + "history", + "r", + "history_state" + ], + "equals": "PASS_ONLY_OBSERVED" + }, + { + "path": [ + "reverse", + "steps", + 0, + "history", + "r", + "historical_contribution" + ], + "equals": "VERIFY_PASS" + }, + { + "path": [ + "reverse", + "steps", + 0, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "reverse", + "steps", + 0, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 0 + }, + { + "path": [ + "reverse", + "steps", + 0, + "integrity", + "evaluation_status" + ], + "equals": "UNKNOWN" + }, + { + "path": [ + "reverse", + "steps", + 0, + "integrity", + "band" + ], + "equals": null + }, + { + "path": [ + "reverse", + "steps", + 0, + "current_decisive_count" + ], + "equals": 0 + }, + { + "path": [ + "shuffle", + "steps", + 0, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "shuffle", + "steps", + 0, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_UNRESOLVED" + }, + { + "path": [ + "shuffle", + "steps", + 0, + "history", + "r", + "history_state" + ], + "equals": "PASS_ONLY_OBSERVED" + }, + { + "path": [ + "shuffle", + "steps", + 0, + "history", + "r", + "historical_contribution" + ], + "equals": "VERIFY_PASS" + }, + { + "path": [ + "shuffle", + "steps", + 0, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "shuffle", + "steps", + 0, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 0 + }, + { + "path": [ + "shuffle", + "steps", + 0, + "integrity", + "evaluation_status" + ], + "equals": "UNKNOWN" + }, + { + "path": [ + "shuffle", + "steps", + 0, + "integrity", + "band" + ], + "equals": null + }, + { + "path": [ + "shuffle", + "steps", + 0, + "current_decisive_count" + ], + "equals": 0 + }, + { + "path": [ + "pages", + "steps", + 0, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "pages", + "steps", + 0, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_UNRESOLVED" + }, + { + "path": [ + "pages", + "steps", + 0, + "history", + "r", + "history_state" + ], + "equals": "PASS_ONLY_OBSERVED" + }, + { + "path": [ + "pages", + "steps", + 0, + "history", + "r", + "historical_contribution" + ], + "equals": "VERIFY_PASS" + }, + { + "path": [ + "pages", + "steps", + 0, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "pages", + "steps", + 0, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 0 + }, + { + "path": [ + "pages", + "steps", + 0, + "integrity", + "evaluation_status" + ], + "equals": "UNKNOWN" + }, + { + "path": [ + "pages", + "steps", + 0, + "integrity", + "band" + ], + "equals": null + }, + { + "path": [ + "pages", + "steps", + 0, + "current_decisive_count" + ], + "equals": 0 + }, + { + "path": [ + "forward", + "steps", + 0, + "verified" + ], + "equals": true + }, + { + "path": [ + "reverse", + "steps", + 0, + "verified" + ], + "equals": true + }, + { + "path": [ + "shuffle", + "steps", + 0, + "verified" + ], + "equals": true + }, + { + "path": [ + "pages", + "steps", + 0, + "verified" + ], + "equals": true + } + ] + }, + { + "case": "R40", + "scenario": "ci-revisions.v1", + "given": { + "variants": [ + { + "name": "unresolved-only", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 1, + "status": "queued", + "conclusion": null, + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + } + ], + "children": [], + "rerun_links": [] + } + } + ] + } + ] + }, + "checks": [ + { + "path": [ + "unresolved-only", + "steps", + 0, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "unresolved-only", + "steps", + 0, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_UNRESOLVED" + }, + { + "path": [ + "unresolved-only", + "steps", + 0, + "history", + "r", + "history_state" + ], + "equals": "NO_DECISIVE_OBSERVED" + }, + { + "path": [ + "unresolved-only", + "steps", + 0, + "history", + "r", + "historical_contribution" + ], + "equals": null + }, + { + "path": [ + "unresolved-only", + "steps", + 0, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 0 + }, + { + "path": [ + "unresolved-only", + "steps", + 0, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 0 + }, + { + "path": [ + "unresolved-only", + "steps", + 0, + "integrity", + "evaluation_status" + ], + "equals": "UNKNOWN" + }, + { + "path": [ + "unresolved-only", + "steps", + 0, + "integrity", + "band" + ], + "equals": null + }, + { + "path": [ + "unresolved-only", + "steps", + 0, + "current_decisive_count" + ], + "equals": 0 + }, + { + "path": [ + "unresolved-only", + "steps", + 0, + "verified" + ], + "equals": true + } + ] + }, + { + "case": "R41", + "scenario": "ci-revisions.v1", + "given": { + "variants": [ + { + "name": "nondecisive-siblings", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 1, + "status": "in_progress", + "conclusion": null, + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + }, + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 701, + "head_sha": "r", + "run_attempt": 1, + "status": "completed", + "conclusion": "cancelled", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + }, + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 702, + "head_sha": "r", + "run_attempt": 1, + "status": "completed", + "conclusion": "skipped", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + } + ], + "children": [], + "rerun_links": [] + } + } + ] + } + ] + }, + "checks": [ + { + "path": [ + "nondecisive-siblings", + "steps", + 0, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "nondecisive-siblings", + "steps", + 0, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_UNRESOLVED" + }, + { + "path": [ + "nondecisive-siblings", + "steps", + 0, + "history", + "r", + "history_state" + ], + "equals": "NO_DECISIVE_OBSERVED" + }, + { + "path": [ + "nondecisive-siblings", + "steps", + 0, + "history", + "r", + "historical_contribution" + ], + "equals": null + }, + { + "path": [ + "nondecisive-siblings", + "steps", + 0, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 0 + }, + { + "path": [ + "nondecisive-siblings", + "steps", + 0, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 0 + }, + { + "path": [ + "nondecisive-siblings", + "steps", + 0, + "integrity", + "evaluation_status" + ], + "equals": "UNKNOWN" + }, + { + "path": [ + "nondecisive-siblings", + "steps", + 0, + "integrity", + "band" + ], + "equals": null + }, + { + "path": [ + "nondecisive-siblings", + "steps", + 0, + "verified" + ], + "equals": true + } + ] + }, + { + "case": "R42", + "scenario": "ci-revisions.v1", + "given": { + "variants": [ + { + "name": "unknown-fail-unresolved", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 1, + "status": "completed", + "conclusion": "future-value", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + }, + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 701, + "head_sha": "r", + "run_attempt": 1, + "status": "completed", + "conclusion": "failure", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + }, + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 702, + "head_sha": "r", + "run_attempt": 1, + "status": "in_progress", + "conclusion": null, + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + } + ], + "children": [], + "rerun_links": [] + } + } + ] + } + ] + }, + "checks": [ + { + "path": [ + "unknown-fail-unresolved", + "steps", + 0, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "unknown-fail-unresolved", + "steps", + 0, + "current", + "r", + "current_verdict" + ], + "equals": "UNKNOWN" + }, + { + "path": [ + "unknown-fail-unresolved", + "steps", + 0, + "history", + "r", + "history_state" + ], + "equals": "FAILURE_OBSERVED" + }, + { + "path": [ + "unknown-fail-unresolved", + "steps", + 0, + "history", + "r", + "historical_contribution" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "unknown-fail-unresolved", + "steps", + 0, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "unknown-fail-unresolved", + "steps", + 0, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "unknown-fail-unresolved", + "steps", + 0, + "integrity", + "evaluation_status" + ], + "equals": "UNKNOWN" + }, + { + "path": [ + "unknown-fail-unresolved", + "steps", + 0, + "integrity", + "band" + ], + "equals": null + }, + { + "path": [ + "unknown-fail-unresolved", + "steps", + 0, + "verified" + ], + "equals": true + } + ] + }, + { + "case": "R43", + "scenario": "ci-revisions.v1", + "given": { + "variants": [ + { + "name": "lifecycle", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 1, + "status": "completed", + "conclusion": "success", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + } + ], + "children": [], + "rerun_links": [] + } + }, + { + "history": "CONTINUE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-06T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-06T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 2, + "status": "in_progress", + "conclusion": null, + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + } + ], + "children": [], + "rerun_links": [] + } + }, + { + "history": "CONTINUE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-07T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-07T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 3, + "status": "completed", + "conclusion": "success", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + } + ], + "children": [], + "rerun_links": [] + } + } + ] + } + ] + }, + "checks": [ + { + "path": [ + "lifecycle", + "steps", + 0, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "lifecycle", + "steps", + 0, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_PASS" + }, + { + "path": [ + "lifecycle", + "steps", + 0, + "history", + "r", + "history_state" + ], + "equals": "PASS_ONLY_OBSERVED" + }, + { + "path": [ + "lifecycle", + "steps", + 0, + "history", + "r", + "historical_contribution" + ], + "equals": "VERIFY_PASS" + }, + { + "path": [ + "lifecycle", + "steps", + 0, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "lifecycle", + "steps", + 0, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 0 + }, + { + "path": [ + "lifecycle", + "steps", + 0, + "integrity", + "evaluation_status" + ], + "equals": "AVAILABLE" + }, + { + "path": [ + "lifecycle", + "steps", + 0, + "integrity", + "band" + ], + "equals": "SPARSE" + }, + { + "path": [ + "lifecycle", + "steps", + 0, + "current_decisive_count" + ], + "equals": 1 + }, + { + "path": [ + "lifecycle", + "steps", + 1, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "lifecycle", + "steps", + 1, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_UNRESOLVED" + }, + { + "path": [ + "lifecycle", + "steps", + 1, + "history", + "r", + "history_state" + ], + "equals": "PASS_ONLY_OBSERVED" + }, + { + "path": [ + "lifecycle", + "steps", + 1, + "history", + "r", + "historical_contribution" + ], + "equals": "VERIFY_PASS" + }, + { + "path": [ + "lifecycle", + "steps", + 1, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "lifecycle", + "steps", + 1, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 0 + }, + { + "path": [ + "lifecycle", + "steps", + 1, + "integrity", + "evaluation_status" + ], + "equals": "UNKNOWN" + }, + { + "path": [ + "lifecycle", + "steps", + 1, + "integrity", + "band" + ], + "equals": null + }, + { + "path": [ + "lifecycle", + "steps", + 1, + "current_decisive_count" + ], + "equals": 0 + }, + { + "path": [ + "lifecycle", + "steps", + 2, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "lifecycle", + "steps", + 2, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_PASS" + }, + { + "path": [ + "lifecycle", + "steps", + 2, + "history", + "r", + "history_state" + ], + "equals": "PASS_ONLY_OBSERVED" + }, + { + "path": [ + "lifecycle", + "steps", + 2, + "history", + "r", + "historical_contribution" + ], + "equals": "VERIFY_PASS" + }, + { + "path": [ + "lifecycle", + "steps", + 2, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "lifecycle", + "steps", + 2, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 0 + }, + { + "path": [ + "lifecycle", + "steps", + 2, + "integrity", + "evaluation_status" + ], + "equals": "AVAILABLE" + }, + { + "path": [ + "lifecycle", + "steps", + 2, + "integrity", + "band" + ], + "equals": "SPARSE" + }, + { + "path": [ + "lifecycle", + "steps", + 2, + "current_decisive_count" + ], + "equals": 1 + }, + { + "path": [ + "lifecycle", + "steps", + 0, + "verified" + ], + "equals": true + }, + { + "path": [ + "lifecycle", + "steps", + 1, + "verified" + ], + "equals": true + }, + { + "path": [ + "lifecycle", + "steps", + 2, + "verified" + ], + "equals": true + } + ] + }, + { + "case": "R44", + "scenario": "ci-revisions.v1", + "given": { + "variants": [ + { + "name": "lifecycle", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 1, + "status": "completed", + "conclusion": "failure", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + } + ], + "children": [], + "rerun_links": [] + } + }, + { + "history": "CONTINUE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-06T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-06T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 2, + "status": "in_progress", + "conclusion": null, + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + } + ], + "children": [], + "rerun_links": [] + } + } + ] + } + ] + }, + "checks": [ + { + "path": [ + "lifecycle", + "steps", + 0, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "lifecycle", + "steps", + 0, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "lifecycle", + "steps", + 0, + "history", + "r", + "history_state" + ], + "equals": "FAILURE_OBSERVED" + }, + { + "path": [ + "lifecycle", + "steps", + 0, + "history", + "r", + "historical_contribution" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "lifecycle", + "steps", + 0, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "lifecycle", + "steps", + 0, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "lifecycle", + "steps", + 0, + "integrity", + "evaluation_status" + ], + "equals": "AVAILABLE" + }, + { + "path": [ + "lifecycle", + "steps", + 0, + "integrity", + "band" + ], + "equals": "FAILING" + }, + { + "path": [ + "lifecycle", + "steps", + 0, + "current_decisive_count" + ], + "equals": 1 + }, + { + "path": [ + "lifecycle", + "steps", + 1, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "lifecycle", + "steps", + 1, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_UNRESOLVED" + }, + { + "path": [ + "lifecycle", + "steps", + 1, + "history", + "r", + "history_state" + ], + "equals": "FAILURE_OBSERVED" + }, + { + "path": [ + "lifecycle", + "steps", + 1, + "history", + "r", + "historical_contribution" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "lifecycle", + "steps", + 1, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "lifecycle", + "steps", + 1, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "lifecycle", + "steps", + 1, + "integrity", + "evaluation_status" + ], + "equals": "UNKNOWN" + }, + { + "path": [ + "lifecycle", + "steps", + 1, + "integrity", + "band" + ], + "equals": null + }, + { + "path": [ + "lifecycle", + "steps", + 1, + "current_decisive_count" + ], + "equals": 0 + }, + { + "path": [ + "lifecycle", + "steps", + 0, + "verified" + ], + "equals": true + }, + { + "path": [ + "lifecycle", + "steps", + 1, + "verified" + ], + "equals": true + } + ] + }, + { + "case": "R45", + "scenario": "ci-revisions.v1", + "given": { + "variants": [ + { + "name": "lifecycle", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 1, + "status": "completed", + "conclusion": "success", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + } + ], + "children": [], + "rerun_links": [] + } + }, + { + "history": "CONTINUE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-06T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-06T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 2, + "status": "in_progress", + "conclusion": null, + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + } + ], + "children": [], + "rerun_links": [] + } + } + ] + } + ] + }, + "checks": [ + { + "path": [ + "lifecycle", + "steps", + 0, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "lifecycle", + "steps", + 0, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_PASS" + }, + { + "path": [ + "lifecycle", + "steps", + 0, + "history", + "r", + "history_state" + ], + "equals": "PASS_ONLY_OBSERVED" + }, + { + "path": [ + "lifecycle", + "steps", + 0, + "history", + "r", + "historical_contribution" + ], + "equals": "VERIFY_PASS" + }, + { + "path": [ + "lifecycle", + "steps", + 0, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "lifecycle", + "steps", + 0, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 0 + }, + { + "path": [ + "lifecycle", + "steps", + 0, + "integrity", + "evaluation_status" + ], + "equals": "AVAILABLE" + }, + { + "path": [ + "lifecycle", + "steps", + 0, + "integrity", + "band" + ], + "equals": "SPARSE" + }, + { + "path": [ + "lifecycle", + "steps", + 0, + "current_decisive_count" + ], + "equals": 1 + }, + { + "path": [ + "lifecycle", + "steps", + 1, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "lifecycle", + "steps", + 1, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_UNRESOLVED" + }, + { + "path": [ + "lifecycle", + "steps", + 1, + "history", + "r", + "history_state" + ], + "equals": "PASS_ONLY_OBSERVED" + }, + { + "path": [ + "lifecycle", + "steps", + 1, + "history", + "r", + "historical_contribution" + ], + "equals": "VERIFY_PASS" + }, + { + "path": [ + "lifecycle", + "steps", + 1, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "lifecycle", + "steps", + 1, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 0 + }, + { + "path": [ + "lifecycle", + "steps", + 1, + "integrity", + "evaluation_status" + ], + "equals": "UNKNOWN" + }, + { + "path": [ + "lifecycle", + "steps", + 1, + "integrity", + "band" + ], + "equals": null + }, + { + "path": [ + "lifecycle", + "steps", + 1, + "current_decisive_count" + ], + "equals": 0 + }, + { + "path": [ + "lifecycle", + "steps", + 0, + "verified" + ], + "equals": true + }, + { + "path": [ + "lifecycle", + "steps", + 1, + "verified" + ], + "equals": true + } + ] + }, + { + "case": "R46", + "scenario": "ci-revisions.v1", + "given": { + "variants": [ + { + "name": "lifecycle", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 1, + "status": "completed", + "conclusion": "failure", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + } + ], + "children": [], + "rerun_links": [] + } + }, + { + "history": "CONTINUE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-06T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-06T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 2, + "status": "completed", + "conclusion": "success", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + } + ], + "children": [], + "rerun_links": [] + } + }, + { + "history": "CONTINUE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-07T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-07T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 3, + "status": "completed", + "conclusion": "success", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + } + ], + "children": [], + "rerun_links": [] + } + } + ] + } + ] + }, + "checks": [ + { + "path": [ + "lifecycle", + "steps", + 0, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "lifecycle", + "steps", + 0, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "lifecycle", + "steps", + 0, + "history", + "r", + "history_state" + ], + "equals": "FAILURE_OBSERVED" + }, + { + "path": [ + "lifecycle", + "steps", + 0, + "history", + "r", + "historical_contribution" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "lifecycle", + "steps", + 0, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "lifecycle", + "steps", + 0, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "lifecycle", + "steps", + 0, + "integrity", + "evaluation_status" + ], + "equals": "AVAILABLE" + }, + { + "path": [ + "lifecycle", + "steps", + 0, + "integrity", + "band" + ], + "equals": "FAILING" + }, + { + "path": [ + "lifecycle", + "steps", + 0, + "current_decisive_count" + ], + "equals": 1 + }, + { + "path": [ + "lifecycle", + "steps", + 1, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "lifecycle", + "steps", + 1, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_PASS" + }, + { + "path": [ + "lifecycle", + "steps", + 1, + "history", + "r", + "history_state" + ], + "equals": "FAILURE_OBSERVED" + }, + { + "path": [ + "lifecycle", + "steps", + 1, + "history", + "r", + "historical_contribution" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "lifecycle", + "steps", + 1, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "lifecycle", + "steps", + 1, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "lifecycle", + "steps", + 1, + "integrity", + "evaluation_status" + ], + "equals": "AVAILABLE" + }, + { + "path": [ + "lifecycle", + "steps", + 1, + "integrity", + "band" + ], + "equals": "SPARSE_MIXED" + }, + { + "path": [ + "lifecycle", + "steps", + 1, + "current_decisive_count" + ], + "equals": 1 + }, + { + "path": [ + "lifecycle", + "steps", + 2, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "lifecycle", + "steps", + 2, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_PASS" + }, + { + "path": [ + "lifecycle", + "steps", + 2, + "history", + "r", + "history_state" + ], + "equals": "FAILURE_OBSERVED" + }, + { + "path": [ + "lifecycle", + "steps", + 2, + "history", + "r", + "historical_contribution" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "lifecycle", + "steps", + 2, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "lifecycle", + "steps", + 2, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "lifecycle", + "steps", + 2, + "integrity", + "evaluation_status" + ], + "equals": "AVAILABLE" + }, + { + "path": [ + "lifecycle", + "steps", + 2, + "integrity", + "band" + ], + "equals": "SPARSE_MIXED" + }, + { + "path": [ + "lifecycle", + "steps", + 2, + "current_decisive_count" + ], + "equals": 1 + }, + { + "path": [ + "lifecycle", + "steps", + 0, + "verified" + ], + "equals": true + }, + { + "path": [ + "lifecycle", + "steps", + 1, + "verified" + ], + "equals": true + }, + { + "path": [ + "lifecycle", + "steps", + 2, + "verified" + ], + "equals": true + } + ] + }, + { + "case": "R47", + "scenario": "ci-revisions.v1", + "given": { + "variants": [ + { + "name": "lifecycle", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 1, + "status": "completed", + "conclusion": "success", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + } + ], + "children": [], + "rerun_links": [] + } + }, + { + "history": "CONTINUE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-06T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-06T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 2, + "status": "completed", + "conclusion": "failure", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + } + ], + "children": [], + "rerun_links": [] + } + }, + { + "history": "CONTINUE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-07T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-07T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 3, + "status": "completed", + "conclusion": "success", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + } + ], + "children": [], + "rerun_links": [] + } + } + ] + } + ] + }, + "checks": [ + { + "path": [ + "lifecycle", + "steps", + 0, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "lifecycle", + "steps", + 0, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_PASS" + }, + { + "path": [ + "lifecycle", + "steps", + 0, + "history", + "r", + "history_state" + ], + "equals": "PASS_ONLY_OBSERVED" + }, + { + "path": [ + "lifecycle", + "steps", + 0, + "history", + "r", + "historical_contribution" + ], + "equals": "VERIFY_PASS" + }, + { + "path": [ + "lifecycle", + "steps", + 0, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "lifecycle", + "steps", + 0, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 0 + }, + { + "path": [ + "lifecycle", + "steps", + 0, + "integrity", + "evaluation_status" + ], + "equals": "AVAILABLE" + }, + { + "path": [ + "lifecycle", + "steps", + 0, + "integrity", + "band" + ], + "equals": "SPARSE" + }, + { + "path": [ + "lifecycle", + "steps", + 0, + "current_decisive_count" + ], + "equals": 1 + }, + { + "path": [ + "lifecycle", + "steps", + 1, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "lifecycle", + "steps", + 1, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "lifecycle", + "steps", + 1, + "history", + "r", + "history_state" + ], + "equals": "FAILURE_OBSERVED" + }, + { + "path": [ + "lifecycle", + "steps", + 1, + "history", + "r", + "historical_contribution" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "lifecycle", + "steps", + 1, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "lifecycle", + "steps", + 1, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "lifecycle", + "steps", + 1, + "integrity", + "evaluation_status" + ], + "equals": "AVAILABLE" + }, + { + "path": [ + "lifecycle", + "steps", + 1, + "integrity", + "band" + ], + "equals": "FAILING" + }, + { + "path": [ + "lifecycle", + "steps", + 1, + "current_decisive_count" + ], + "equals": 1 + }, + { + "path": [ + "lifecycle", + "steps", + 2, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "lifecycle", + "steps", + 2, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_PASS" + }, + { + "path": [ + "lifecycle", + "steps", + 2, + "history", + "r", + "history_state" + ], + "equals": "FAILURE_OBSERVED" + }, + { + "path": [ + "lifecycle", + "steps", + 2, + "history", + "r", + "historical_contribution" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "lifecycle", + "steps", + 2, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "lifecycle", + "steps", + 2, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "lifecycle", + "steps", + 2, + "integrity", + "evaluation_status" + ], + "equals": "AVAILABLE" + }, + { + "path": [ + "lifecycle", + "steps", + 2, + "integrity", + "band" + ], + "equals": "SPARSE_MIXED" + }, + { + "path": [ + "lifecycle", + "steps", + 2, + "current_decisive_count" + ], + "equals": 1 + }, + { + "path": [ + "lifecycle", + "steps", + 0, + "verified" + ], + "equals": true + }, + { + "path": [ + "lifecycle", + "steps", + 1, + "verified" + ], + "equals": true + }, + { + "path": [ + "lifecycle", + "steps", + 2, + "verified" + ], + "equals": true + } + ] + }, + { + "case": "R48", + "scenario": "ci-revisions.v1", + "given": { + "variants": [ + { + "name": "actions", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 1, + "status": "completed", + "conclusion": "failure", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + } + ], + "children": [], + "rerun_links": [] + } + }, + { + "history": "CONTINUE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-06T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-06T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 2, + "status": "in_progress", + "conclusion": null, + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + } + ], + "children": [], + "rerun_links": [] + } + } + ] + }, + { + "name": "checks", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "checks", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 501, + "head_sha": "r", + "status": "completed", + "conclusion": "failure", + "app": { + "id": 42, + "slug": "same-app" + } + } + } + ], + "children": [], + "rerun_links": [] + } + }, + { + "history": "CONTINUE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-06T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "checks", + "revision": "r", + "observed_at": "2026-09-06T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 501, + "head_sha": "r", + "status": "in_progress", + "conclusion": null, + "app": { + "id": 42, + "slug": "same-app" + } + } + } + ], + "children": [], + "rerun_links": [] + } + } + ] + }, + { + "name": "linked", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 1, + "status": "completed", + "conclusion": "failure", + "name": "same CI name", + "workflow_id": 42, + "event": "push", + "check_suite_id": 501 + } + }, + { + "surface": "checks", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 501, + "head_sha": "r", + "status": "completed", + "conclusion": "failure", + "app": { + "id": 42, + "slug": "same-app" + } + } + } + ], + "children": [], + "rerun_links": [] + } + }, + { + "history": "CONTINUE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-06T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-06T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 2, + "status": "in_progress", + "conclusion": null, + "name": "same CI name", + "workflow_id": 42, + "event": "push", + "check_suite_id": 501 + } + }, + { + "surface": "checks", + "revision": "r", + "observed_at": "2026-09-06T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 501, + "head_sha": "r", + "status": "in_progress", + "conclusion": null, + "app": { + "id": 42, + "slug": "same-app" + } + } + } + ], + "children": [], + "rerun_links": [] + } + } + ] + }, + { + "name": "gitlab", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "gitlab", + "forge_instance": "gitlab.example", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.gitlab.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "pipelines", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 101, + "sha": "r", + "status": "failed", + "ref": "main", + "source": "push" + } + } + ], + "children": [], + "rerun_links": [] + } + }, + { + "history": "CONTINUE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "gitlab", + "forge_instance": "gitlab.example", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.gitlab.v1", + "observed_at": "2026-09-06T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "pipelines", + "revision": "r", + "observed_at": "2026-09-06T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 101, + "sha": "r", + "status": "running", + "ref": "main", + "source": "push" + } + } + ], + "children": [], + "rerun_links": [] + } + } + ] + } + ] + }, + "checks": [ + { + "path": [ + "actions", + "steps", + 0, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "actions", + "steps", + 0, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "actions", + "steps", + 0, + "history", + "r", + "history_state" + ], + "equals": "FAILURE_OBSERVED" + }, + { + "path": [ + "actions", + "steps", + 0, + "history", + "r", + "historical_contribution" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "actions", + "steps", + 0, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "actions", + "steps", + 0, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "actions", + "steps", + 0, + "integrity", + "evaluation_status" + ], + "equals": "AVAILABLE" + }, + { + "path": [ + "actions", + "steps", + 0, + "integrity", + "band" + ], + "equals": "FAILING" + }, + { + "path": [ + "actions", + "steps", + 1, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "actions", + "steps", + 1, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_UNRESOLVED" + }, + { + "path": [ + "actions", + "steps", + 1, + "history", + "r", + "history_state" + ], + "equals": "FAILURE_OBSERVED" + }, + { + "path": [ + "actions", + "steps", + 1, + "history", + "r", + "historical_contribution" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "actions", + "steps", + 1, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "actions", + "steps", + 1, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "actions", + "steps", + 1, + "integrity", + "evaluation_status" + ], + "equals": "UNKNOWN" + }, + { + "path": [ + "actions", + "steps", + 1, + "integrity", + "band" + ], + "equals": null + }, + { + "path": [ + "checks", + "steps", + 0, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "checks", + "steps", + 0, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "checks", + "steps", + 0, + "history", + "r", + "history_state" + ], + "equals": "FAILURE_OBSERVED" + }, + { + "path": [ + "checks", + "steps", + 0, + "history", + "r", + "historical_contribution" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "checks", + "steps", + 0, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "checks", + "steps", + 0, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "checks", + "steps", + 0, + "integrity", + "evaluation_status" + ], + "equals": "AVAILABLE" + }, + { + "path": [ + "checks", + "steps", + 0, + "integrity", + "band" + ], + "equals": "FAILING" + }, + { + "path": [ + "checks", + "steps", + 1, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "checks", + "steps", + 1, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_UNRESOLVED" + }, + { + "path": [ + "checks", + "steps", + 1, + "history", + "r", + "history_state" + ], + "equals": "FAILURE_OBSERVED" + }, + { + "path": [ + "checks", + "steps", + 1, + "history", + "r", + "historical_contribution" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "checks", + "steps", + 1, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "checks", + "steps", + 1, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "checks", + "steps", + 1, + "integrity", + "evaluation_status" + ], + "equals": "UNKNOWN" + }, + { + "path": [ + "checks", + "steps", + 1, + "integrity", + "band" + ], + "equals": null + }, + { + "path": [ + "linked", + "steps", + 0, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "linked", + "steps", + 0, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "linked", + "steps", + 0, + "history", + "r", + "history_state" + ], + "equals": "FAILURE_OBSERVED" + }, + { + "path": [ + "linked", + "steps", + 0, + "history", + "r", + "historical_contribution" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "linked", + "steps", + 0, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "linked", + "steps", + 0, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "linked", + "steps", + 0, + "integrity", + "evaluation_status" + ], + "equals": "AVAILABLE" + }, + { + "path": [ + "linked", + "steps", + 0, + "integrity", + "band" + ], + "equals": "FAILING" + }, + { + "path": [ + "linked", + "steps", + 1, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "linked", + "steps", + 1, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_UNRESOLVED" + }, + { + "path": [ + "linked", + "steps", + 1, + "history", + "r", + "history_state" + ], + "equals": "FAILURE_OBSERVED" + }, + { + "path": [ + "linked", + "steps", + 1, + "history", + "r", + "historical_contribution" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "linked", + "steps", + 1, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "linked", + "steps", + 1, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "linked", + "steps", + 1, + "integrity", + "evaluation_status" + ], + "equals": "UNKNOWN" + }, + { + "path": [ + "linked", + "steps", + 1, + "integrity", + "band" + ], + "equals": null + }, + { + "path": [ + "gitlab", + "steps", + 0, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "gitlab", + "steps", + 0, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "gitlab", + "steps", + 0, + "history", + "r", + "history_state" + ], + "equals": "FAILURE_OBSERVED" + }, + { + "path": [ + "gitlab", + "steps", + 0, + "history", + "r", + "historical_contribution" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "gitlab", + "steps", + 0, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "gitlab", + "steps", + 0, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "gitlab", + "steps", + 0, + "integrity", + "evaluation_status" + ], + "equals": "AVAILABLE" + }, + { + "path": [ + "gitlab", + "steps", + 0, + "integrity", + "band" + ], + "equals": "FAILING" + }, + { + "path": [ + "gitlab", + "steps", + 1, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "gitlab", + "steps", + 1, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_UNRESOLVED" + }, + { + "path": [ + "gitlab", + "steps", + 1, + "history", + "r", + "history_state" + ], + "equals": "FAILURE_OBSERVED" + }, + { + "path": [ + "gitlab", + "steps", + 1, + "history", + "r", + "historical_contribution" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "gitlab", + "steps", + 1, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "gitlab", + "steps", + 1, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "gitlab", + "steps", + 1, + "integrity", + "evaluation_status" + ], + "equals": "UNKNOWN" + }, + { + "path": [ + "gitlab", + "steps", + 1, + "integrity", + "band" + ], + "equals": null + }, + { + "path": [ + "actions", + "steps", + 0, + "verified" + ], + "equals": true + }, + { + "path": [ + "actions", + "steps", + 1, + "verified" + ], + "equals": true + }, + { + "path": [ + "checks", + "steps", + 0, + "verified" + ], + "equals": true + }, + { + "path": [ + "checks", + "steps", + 1, + "verified" + ], + "equals": true + }, + { + "path": [ + "linked", + "steps", + 0, + "verified" + ], + "equals": true + }, + { + "path": [ + "linked", + "steps", + 1, + "verified" + ], + "equals": true + }, + { + "path": [ + "gitlab", + "steps", + 0, + "verified" + ], + "equals": true + }, + { + "path": [ + "gitlab", + "steps", + 1, + "verified" + ], + "equals": true + } + ] + }, + { + "case": "R49", + "scenario": "ci-revisions.v1", + "given": { + "variants": [ + { + "name": "forward", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 1, + "status": "completed", + "conclusion": "failure", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + } + ], + "children": [], + "rerun_links": [] + } + }, + { + "history": "CONTINUE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-06T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 1, + "status": "completed", + "conclusion": "failure", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + }, + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-06T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 2, + "status": "completed", + "conclusion": "success", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + }, + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-06T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 701, + "head_sha": "r", + "run_attempt": 1, + "status": "completed", + "conclusion": "success", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + } + ], + "children": [], + "rerun_links": [] + } + } + ] + }, + { + "name": "reverse", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 1, + "status": "completed", + "conclusion": "failure", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + } + ], + "children": [], + "rerun_links": [] + } + }, + { + "history": "CONTINUE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-06T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-06T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 701, + "head_sha": "r", + "run_attempt": 1, + "status": "completed", + "conclusion": "success", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + }, + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-06T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 2, + "status": "completed", + "conclusion": "success", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + }, + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 1, + "status": "completed", + "conclusion": "failure", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + } + ], + "children": [], + "rerun_links": [] + } + } + ] + }, + { + "name": "shuffle", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 1, + "status": "completed", + "conclusion": "failure", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + } + ], + "children": [], + "rerun_links": [] + } + }, + { + "history": "CONTINUE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-06T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-06T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 2, + "status": "completed", + "conclusion": "success", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + }, + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 1, + "status": "completed", + "conclusion": "failure", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + }, + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-06T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 701, + "head_sha": "r", + "run_attempt": 1, + "status": "completed", + "conclusion": "success", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + } + ], + "children": [], + "rerun_links": [] + } + } + ] + }, + { + "name": "pages", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 1, + "status": "completed", + "conclusion": "failure", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + } + ], + "children": [], + "rerun_links": [] + } + }, + { + "history": "CONTINUE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-06T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": { + "pages": [ + { + "ordinal": 1, + "has_next": true, + "items": [ + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 1, + "status": "completed", + "conclusion": "failure", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + }, + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-06T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 2, + "status": "completed", + "conclusion": "success", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + } + ] + }, + { + "ordinal": 2, + "has_next": false, + "items": [ + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-06T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 701, + "head_sha": "r", + "run_attempt": 1, + "status": "completed", + "conclusion": "success", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + } + ] + } + ] + }, + "children": [], + "rerun_links": [] + } + } + ] + } + ] + }, + "checks": [ + { + "path": [ + "forward", + "steps", + 0, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "forward", + "steps", + 0, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "forward", + "steps", + 0, + "history", + "r", + "history_state" + ], + "equals": "FAILURE_OBSERVED" + }, + { + "path": [ + "forward", + "steps", + 0, + "history", + "r", + "historical_contribution" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "forward", + "steps", + 0, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "forward", + "steps", + 0, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "forward", + "steps", + 0, + "integrity", + "evaluation_status" + ], + "equals": "AVAILABLE" + }, + { + "path": [ + "forward", + "steps", + 0, + "integrity", + "band" + ], + "equals": "FAILING" + }, + { + "path": [ + "forward", + "steps", + 1, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "forward", + "steps", + 1, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_PASS" + }, + { + "path": [ + "forward", + "steps", + 1, + "history", + "r", + "history_state" + ], + "equals": "FAILURE_OBSERVED" + }, + { + "path": [ + "forward", + "steps", + 1, + "history", + "r", + "historical_contribution" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "forward", + "steps", + 1, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "forward", + "steps", + 1, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "forward", + "steps", + 1, + "integrity", + "evaluation_status" + ], + "equals": "AVAILABLE" + }, + { + "path": [ + "forward", + "steps", + 1, + "integrity", + "band" + ], + "equals": "SPARSE_MIXED" + }, + { + "path": [ + "reverse", + "steps", + 0, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "reverse", + "steps", + 0, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "reverse", + "steps", + 0, + "history", + "r", + "history_state" + ], + "equals": "FAILURE_OBSERVED" + }, + { + "path": [ + "reverse", + "steps", + 0, + "history", + "r", + "historical_contribution" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "reverse", + "steps", + 0, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "reverse", + "steps", + 0, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "reverse", + "steps", + 0, + "integrity", + "evaluation_status" + ], + "equals": "AVAILABLE" + }, + { + "path": [ + "reverse", + "steps", + 0, + "integrity", + "band" + ], + "equals": "FAILING" + }, + { + "path": [ + "reverse", + "steps", + 1, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "reverse", + "steps", + 1, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_PASS" + }, + { + "path": [ + "reverse", + "steps", + 1, + "history", + "r", + "history_state" + ], + "equals": "FAILURE_OBSERVED" + }, + { + "path": [ + "reverse", + "steps", + 1, + "history", + "r", + "historical_contribution" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "reverse", + "steps", + 1, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "reverse", + "steps", + 1, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "reverse", + "steps", + 1, + "integrity", + "evaluation_status" + ], + "equals": "AVAILABLE" + }, + { + "path": [ + "reverse", + "steps", + 1, + "integrity", + "band" + ], + "equals": "SPARSE_MIXED" + }, + { + "path": [ + "shuffle", + "steps", + 0, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "shuffle", + "steps", + 0, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "shuffle", + "steps", + 0, + "history", + "r", + "history_state" + ], + "equals": "FAILURE_OBSERVED" + }, + { + "path": [ + "shuffle", + "steps", + 0, + "history", + "r", + "historical_contribution" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "shuffle", + "steps", + 0, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "shuffle", + "steps", + 0, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "shuffle", + "steps", + 0, + "integrity", + "evaluation_status" + ], + "equals": "AVAILABLE" + }, + { + "path": [ + "shuffle", + "steps", + 0, + "integrity", + "band" + ], + "equals": "FAILING" + }, + { + "path": [ + "shuffle", + "steps", + 1, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "shuffle", + "steps", + 1, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_PASS" + }, + { + "path": [ + "shuffle", + "steps", + 1, + "history", + "r", + "history_state" + ], + "equals": "FAILURE_OBSERVED" + }, + { + "path": [ + "shuffle", + "steps", + 1, + "history", + "r", + "historical_contribution" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "shuffle", + "steps", + 1, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "shuffle", + "steps", + 1, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "shuffle", + "steps", + 1, + "integrity", + "evaluation_status" + ], + "equals": "AVAILABLE" + }, + { + "path": [ + "shuffle", + "steps", + 1, + "integrity", + "band" + ], + "equals": "SPARSE_MIXED" + }, + { + "path": [ + "pages", + "steps", + 0, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "pages", + "steps", + 0, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "pages", + "steps", + 0, + "history", + "r", + "history_state" + ], + "equals": "FAILURE_OBSERVED" + }, + { + "path": [ + "pages", + "steps", + 0, + "history", + "r", + "historical_contribution" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "pages", + "steps", + 0, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "pages", + "steps", + 0, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "pages", + "steps", + 0, + "integrity", + "evaluation_status" + ], + "equals": "AVAILABLE" + }, + { + "path": [ + "pages", + "steps", + 0, + "integrity", + "band" + ], + "equals": "FAILING" + }, + { + "path": [ + "pages", + "steps", + 1, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "pages", + "steps", + 1, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_PASS" + }, + { + "path": [ + "pages", + "steps", + 1, + "history", + "r", + "history_state" + ], + "equals": "FAILURE_OBSERVED" + }, + { + "path": [ + "pages", + "steps", + 1, + "history", + "r", + "historical_contribution" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "pages", + "steps", + 1, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "pages", + "steps", + 1, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "pages", + "steps", + 1, + "integrity", + "evaluation_status" + ], + "equals": "AVAILABLE" + }, + { + "path": [ + "pages", + "steps", + 1, + "integrity", + "band" + ], + "equals": "SPARSE_MIXED" + }, + { + "path": [ + "forward", + "steps", + 0, + "verified" + ], + "equals": true + }, + { + "path": [ + "forward", + "steps", + 1, + "verified" + ], + "equals": true + }, + { + "path": [ + "reverse", + "steps", + 0, + "verified" + ], + "equals": true + }, + { + "path": [ + "reverse", + "steps", + 1, + "verified" + ], + "equals": true + }, + { + "path": [ + "shuffle", + "steps", + 0, + "verified" + ], + "equals": true + }, + { + "path": [ + "shuffle", + "steps", + 1, + "verified" + ], + "equals": true + }, + { + "path": [ + "pages", + "steps", + 0, + "verified" + ], + "equals": true + }, + { + "path": [ + "pages", + "steps", + 1, + "verified" + ], + "equals": true + } + ] + }, + { + "case": "R50", + "scenario": "ci-revisions.v1", + "given": { + "variants": [ + { + "name": "no-decisive-history", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 1, + "status": "in_progress", + "conclusion": null, + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + } + ], + "children": [], + "rerun_links": [] + } + } + ] + } + ] + }, + "checks": [ + { + "path": [ + "no-decisive-history", + "steps", + 0, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "no-decisive-history", + "steps", + 0, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_UNRESOLVED" + }, + { + "path": [ + "no-decisive-history", + "steps", + 0, + "history", + "r", + "history_state" + ], + "equals": "NO_DECISIVE_OBSERVED" + }, + { + "path": [ + "no-decisive-history", + "steps", + 0, + "history", + "r", + "historical_contribution" + ], + "equals": null + }, + { + "path": [ + "no-decisive-history", + "steps", + 0, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 0 + }, + { + "path": [ + "no-decisive-history", + "steps", + 0, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 0 + }, + { + "path": [ + "no-decisive-history", + "steps", + 0, + "integrity", + "evaluation_status" + ], + "equals": "UNKNOWN" + }, + { + "path": [ + "no-decisive-history", + "steps", + 0, + "integrity", + "band" + ], + "equals": null + }, + { + "path": [ + "no-decisive-history", + "steps", + 0, + "current_decisive_count" + ], + "equals": 0 + }, + { + "path": [ + "no-decisive-history", + "steps", + 0, + "verified" + ], + "equals": true + } + ] + }, + { + "case": "R51", + "scenario": "ci-revisions.v1", + "given": { + "variants": [ + { + "name": "window", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-08-29T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 1, + "status": "completed", + "conclusion": "failure", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + } + ], + "children": [], + "rerun_links": [] + } + }, + { + "history": "CONTINUE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-06T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-08-29T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-06T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 2, + "status": "in_progress", + "conclusion": null, + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + } + ], + "children": [], + "rerun_links": [] + } + }, + { + "history": "CONTINUE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-13T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-08-29T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-13T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 2, + "status": "in_progress", + "conclusion": null, + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + } + ], + "children": [], + "rerun_links": [] + } + } + ] + } + ] + }, + "checks": [ + { + "path": [ + "window", + "steps", + 0, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "window", + "steps", + 0, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "window", + "steps", + 0, + "history", + "r", + "history_state" + ], + "equals": "FAILURE_OBSERVED" + }, + { + "path": [ + "window", + "steps", + 0, + "history", + "r", + "historical_contribution" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "window", + "steps", + 0, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "window", + "steps", + 0, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "window", + "steps", + 0, + "integrity", + "evaluation_status" + ], + "equals": "AVAILABLE" + }, + { + "path": [ + "window", + "steps", + 0, + "integrity", + "band" + ], + "equals": "FAILING" + }, + { + "path": [ + "window", + "steps", + 1, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "window", + "steps", + 1, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_UNRESOLVED" + }, + { + "path": [ + "window", + "steps", + 1, + "history", + "r", + "history_state" + ], + "equals": "FAILURE_OBSERVED" + }, + { + "path": [ + "window", + "steps", + 1, + "history", + "r", + "historical_contribution" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "window", + "steps", + 1, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "window", + "steps", + 1, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "window", + "steps", + 1, + "integrity", + "evaluation_status" + ], + "equals": "UNKNOWN" + }, + { + "path": [ + "window", + "steps", + 1, + "integrity", + "band" + ], + "equals": null + }, + { + "path": [ + "window", + "steps", + 2, + "current" + ], + "equals": {} + }, + { + "path": [ + "window", + "steps", + 2, + "history" + ], + "equals": {} + }, + { + "path": [ + "window", + "steps", + 2, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 0 + }, + { + "path": [ + "window", + "steps", + 2, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 0 + }, + { + "path": [ + "window", + "steps", + 2, + "normalization", + "excluded_revisions" + ], + "equals": [ + { + "revision": "r", + "committed_at": "2026-08-29T00:00:00Z", + "default_branch": true, + "reason": "OUTSIDE_WINDOW" + } + ] + }, + { + "path": [ + "window", + "steps", + 0, + "verified" + ], + "equals": true + }, + { + "path": [ + "window", + "steps", + 1, + "verified" + ], + "equals": true + }, + { + "path": [ + "window", + "steps", + 2, + "verified" + ], + "equals": true + } + ] + }, + { + "case": "R52", + "scenario": "ci-revisions.v1", + "given": { + "variants": [ + { + "name": "lost-predecessor", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "checks", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 501, + "head_sha": "r", + "status": "completed", + "conclusion": "failure", + "app": { + "id": 42, + "slug": "same-app" + } + } + } + ], + "children": [], + "rerun_links": [] + } + }, + { + "history": "GAP", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-06T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "checks", + "revision": "r", + "observed_at": "2026-09-06T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 501, + "head_sha": "r", + "status": "completed", + "conclusion": "success", + "app": { + "id": 42, + "slug": "same-app" + } + } + } + ], + "children": [], + "rerun_links": [] + } + } + ] + } + ] + }, + "checks": [ + { + "path": [ + "lost-predecessor", + "steps", + 0, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "lost-predecessor", + "steps", + 0, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "lost-predecessor", + "steps", + 0, + "history", + "r", + "history_state" + ], + "equals": "FAILURE_OBSERVED" + }, + { + "path": [ + "lost-predecessor", + "steps", + 0, + "history", + "r", + "historical_contribution" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "lost-predecessor", + "steps", + 0, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "lost-predecessor", + "steps", + 0, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "lost-predecessor", + "steps", + 0, + "integrity", + "evaluation_status" + ], + "equals": "AVAILABLE" + }, + { + "path": [ + "lost-predecessor", + "steps", + 0, + "integrity", + "band" + ], + "equals": "FAILING" + }, + { + "path": [ + "lost-predecessor", + "steps", + 1, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "lost-predecessor", + "steps", + 1, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_PASS" + }, + { + "path": [ + "lost-predecessor", + "steps", + 1, + "history", + "r", + "history_state" + ], + "equals": "UNKNOWN_HISTORY" + }, + { + "path": [ + "lost-predecessor", + "steps", + 1, + "history", + "r", + "historical_contribution" + ], + "equals": null + }, + { + "path": [ + "lost-predecessor", + "steps", + 1, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 0 + }, + { + "path": [ + "lost-predecessor", + "steps", + 1, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 0 + }, + { + "path": [ + "lost-predecessor", + "steps", + 1, + "integrity", + "evaluation_status" + ], + "equals": "UNKNOWN" + }, + { + "path": [ + "lost-predecessor", + "steps", + 1, + "integrity", + "band" + ], + "equals": null + }, + { + "path": [ + "lost-predecessor", + "steps", + 1, + "comparison" + ], + "equals": "HISTORY_GAP" + }, + { + "path": [ + "lost-predecessor", + "steps", + 1, + "integrity", + "derived", + "revision_history", + "source", + "status" + ], + "equals": "HISTORY_GAP" + }, + { + "path": [ + "lost-predecessor", + "steps", + 0, + "verified" + ], + "equals": true + }, + { + "path": [ + "lost-predecessor", + "steps", + 1, + "verified" + ], + "equals": true + } + ] + }, + { + "case": "R53", + "scenario": "ci-revisions.v1", + "given": { + "variants": [ + { + "name": "pass-pass", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 1, + "status": "completed", + "conclusion": "success", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + } + ], + "children": [], + "rerun_links": [] + } + }, + { + "history": "CONTINUE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-06T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-06T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 2, + "status": "completed", + "conclusion": "success", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + } + ], + "children": [], + "rerun_links": [] + } + } + ] + }, + { + "name": "fail-fail", + "steps": [ + { + "history": "BASELINE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-05T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-05T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 1, + "status": "completed", + "conclusion": "failure", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + } + ], + "children": [], + "rerun_links": [] + } + }, + { + "history": "CONTINUE", + "evidence": { + "contract": "devostasis.ci-normalization.v1", + "project": { + "provider": "github", + "forge_instance": "github.com", + "immutable_project_id": "123456", + "default_branch": "master" + }, + "outcome_map": "devostasis.ci-outcomes.github.v1", + "observed_at": "2026-09-06T12:00:00Z", + "configured": true, + "coverage": "COMPLETE", + "revisions": [ + { + "revision": "r", + "committed_at": "2026-09-04T00:00:00Z", + "default_branch": true + } + ], + "parents": [ + { + "surface": "actions", + "revision": "r", + "observed_at": "2026-09-06T10:00:00Z", + "coverage": "COMPLETE", + "native": { + "id": 700, + "head_sha": "r", + "run_attempt": 2, + "status": "completed", + "conclusion": "failure", + "name": "same CI name", + "workflow_id": 42, + "event": "push" + } + } + ], + "children": [], + "rerun_links": [] + } + } + ] + } + ] + }, + "checks": [ + { + "path": [ + "pass-pass", + "steps", + 0, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "pass-pass", + "steps", + 0, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_PASS" + }, + { + "path": [ + "pass-pass", + "steps", + 0, + "history", + "r", + "history_state" + ], + "equals": "PASS_ONLY_OBSERVED" + }, + { + "path": [ + "pass-pass", + "steps", + 0, + "history", + "r", + "historical_contribution" + ], + "equals": "VERIFY_PASS" + }, + { + "path": [ + "pass-pass", + "steps", + 0, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "pass-pass", + "steps", + 0, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 0 + }, + { + "path": [ + "pass-pass", + "steps", + 0, + "integrity", + "evaluation_status" + ], + "equals": "AVAILABLE" + }, + { + "path": [ + "pass-pass", + "steps", + 0, + "integrity", + "band" + ], + "equals": "SPARSE" + }, + { + "path": [ + "pass-pass", + "steps", + 1, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "pass-pass", + "steps", + 1, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_PASS" + }, + { + "path": [ + "pass-pass", + "steps", + 1, + "history", + "r", + "history_state" + ], + "equals": "PASS_ONLY_OBSERVED" + }, + { + "path": [ + "pass-pass", + "steps", + 1, + "history", + "r", + "historical_contribution" + ], + "equals": "VERIFY_PASS" + }, + { + "path": [ + "pass-pass", + "steps", + 1, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "pass-pass", + "steps", + 1, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 0 + }, + { + "path": [ + "pass-pass", + "steps", + 1, + "integrity", + "evaluation_status" + ], + "equals": "AVAILABLE" + }, + { + "path": [ + "pass-pass", + "steps", + 1, + "integrity", + "band" + ], + "equals": "SPARSE" + }, + { + "path": [ + "fail-fail", + "steps", + 0, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "fail-fail", + "steps", + 0, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "fail-fail", + "steps", + 0, + "history", + "r", + "history_state" + ], + "equals": "FAILURE_OBSERVED" + }, + { + "path": [ + "fail-fail", + "steps", + 0, + "history", + "r", + "historical_contribution" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "fail-fail", + "steps", + 0, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "fail-fail", + "steps", + 0, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "fail-fail", + "steps", + 0, + "integrity", + "evaluation_status" + ], + "equals": "AVAILABLE" + }, + { + "path": [ + "fail-fail", + "steps", + 0, + "integrity", + "band" + ], + "equals": "FAILING" + }, + { + "path": [ + "fail-fail", + "steps", + 1, + "current_revision_count" + ], + "equals": 1 + }, + { + "path": [ + "fail-fail", + "steps", + 1, + "current", + "r", + "current_verdict" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "fail-fail", + "steps", + 1, + "history", + "r", + "history_state" + ], + "equals": "FAILURE_OBSERVED" + }, + { + "path": [ + "fail-fail", + "steps", + 1, + "history", + "r", + "historical_contribution" + ], + "equals": "VERIFY_FAIL" + }, + { + "path": [ + "fail-fail", + "steps", + 1, + "integrity", + "derived", + "decisive_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "fail-fail", + "steps", + 1, + "integrity", + "derived", + "failed_count_14d" + ], + "equals": 1 + }, + { + "path": [ + "fail-fail", + "steps", + 1, + "integrity", + "evaluation_status" + ], + "equals": "AVAILABLE" + }, + { + "path": [ + "fail-fail", + "steps", + 1, + "integrity", + "band" + ], + "equals": "FAILING" + }, + { + "path": [ + "pass-pass", + "steps", + 0, + "verified" + ], + "equals": true + }, + { + "path": [ + "pass-pass", + "steps", + 1, + "verified" + ], + "equals": true + }, + { + "path": [ + "fail-fail", + "steps", + 0, + "verified" + ], + "equals": true + }, + { + "path": [ + "fail-fail", + "steps", + 1, + "verified" + ], + "equals": true + } + ] + } + ] +} diff --git a/tests/surface_bridge.py b/tests/surface_bridge.py index 2b5a09b..7f2cdad 100644 --- a/tests/surface_bridge.py +++ b/tests/surface_bridge.py @@ -18,6 +18,7 @@ from devostasis.runner import build_from_observations from devostasis.vitals import build_snapshot, integrity from helpers import add, finalize, full_inputs, integrity_inputs, obs_set +from ci_surface_bridge import consumer as vital_consumers, revisions as ci_revisions CONTRACT = "devostasis.conformance-bridge.v1" BUNDLE_LINEAGE = {"schema": "devostasis.manifest.v2", "artifact_contract_version": "devostasis.bundle.v3", @@ -236,7 +237,8 @@ def read(file, *args, **kwargs): SCENARIOS = {"ci-native.v1": native, "independence.v1": independence, - "multi-vital.v1": multivital, "bundle.v1": bundle, "history-store.v1": history} + "multi-vital.v1": multivital, "bundle.v1": bundle, "history-store.v1": history, + "vital-consumers.v1": vital_consumers, "ci-revisions.v1": ci_revisions} def load(path): @@ -244,10 +246,12 @@ def load(path): shape(document, ("schema", "source", "cases")) if document["schema"] != CONTRACT: raise ValueError("unknown bridge lineage") if not isinstance(document["source"], str) or not document["source"]: raise ValueError("source required") + if not isinstance(document["cases"], list) or not document["cases"]: + raise ValueError("nonempty case inventory required") seen = set() for case in document["cases"]: shape(case, ("case", "scenario", "given", "checks")) - if not isinstance(case["case"], str) or case["case"] in seen: raise ValueError("duplicate or invalid case") + if not isinstance(case["case"], str) or not case["case"] or case["case"] in seen: raise ValueError("duplicate or invalid case") seen.add(case["case"]) if case["scenario"] not in SCENARIOS: raise ValueError("unknown scenario lineage") if not isinstance(case["given"], dict) or not isinstance(case["checks"], list) or not case["checks"]: @@ -275,13 +279,24 @@ def execute(case, root): except (KeyError, IndexError, TypeError): exists = False; break where = f"{case['case']}:{check['path']}" if "absent" in check: - assert not exists, f"{where}: forbidden field exists" + if exists: raise AssertionError(f"{where}: forbidden field exists") continue - assert exists, f"{where}: unknown checkpoint/assertion path" + if not exists: raise AssertionError(f"{where}: unknown checkpoint/assertion path") if "equals" in check: - assert canonical.canonical_bytes(node) == canonical.canonical_bytes(check["equals"]), f"{where}: expected {check['equals']!r}, got {node!r}" + if canonical.canonical_bytes(node) != canonical.canonical_bytes(check["equals"]): + raise AssertionError(f"{where}: expected {check['equals']!r}, got {node!r}") if "not_equals" in check: - assert canonical.canonical_bytes(node) != canonical.canonical_bytes(check["not_equals"]), f"{where}: forbidden value {node!r}" + if canonical.canonical_bytes(node) == canonical.canonical_bytes(check["not_equals"]): + raise AssertionError(f"{where}: forbidden value {node!r}") if "contains" in check: - assert check["contains"] in node, f"{where}: missing {check['contains']!r}" + if check["contains"] not in node: raise AssertionError(f"{where}: missing {check['contains']!r}") return actual + + +def load_suite(paths=None): + paths = paths if paths is not None else [Path(__file__).parent / "fixtures" / name + for name in ("conformance-surface.json", "ci-revisions.json")] + cases = [case for path in paths for case in load(path)] + ids = [case["case"] for case in cases] + if len(ids) != len(set(ids)): raise ValueError("duplicate permanent case across bridge carriers") + return cases diff --git a/tests/test_ci_normalization.py b/tests/test_ci_normalization.py new file mode 100644 index 0000000..245aaf6 --- /dev/null +++ b/tests/test_ci_normalization.py @@ -0,0 +1,157 @@ +"""Admission attacks and compatibility at the opt-in native CI boundary.""" + +from copy import deepcopy +from dataclasses import replace +from pathlib import Path +import subprocess +import sys + +import pytest + +from devostasis import canonical +from devostasis.adapters import ci +from devostasis.delta import compare +from devostasis.vitals import integrity +from helpers import SUBJECT, obs_set, integrity_inputs, passing_revisions +from surface_bridge import load, load_suite + +FIXTURE = Path(__file__).parent / "fixtures" / "ci-revisions.json" + + +def evidence(): + case = next(c for c in load(FIXTURE) if c["case"] == "R26") + return deepcopy(case["given"]["variants"][0]["steps"][0]["evidence"]) + + +def observed(value=None): + value = evidence() if value is None else value + return ci.observations(value, {**SUBJECT, **value["project"]}) + + +@pytest.mark.parametrize("key,field,value", [ + (ci.NATIVE, "provider", "gitlab"), + (ci.NATIVE, "adapter_version", "future"), + (ci.NATIVE, "source_ref", "native-ci:forged"), + (ci.PROVENANCE, "collected_at", "2026-09-01T00:00:00Z"), + (ci.PROVENANCE, "value", {}), + ("ci.revision_verdicts_14d", "value", []), + ("ci.revision_verdicts_14d", "freshness", "STALE"), + ("ci.configured", "value", False), +]) +def test_governed_integrity_refuses_forged_native_projections(key, field, value): + obs = observed() + obs.replace(replace(obs.get(key), **{field: value})) + with pytest.raises(ValueError, match="does not replay"): + integrity.evaluate(obs) + + +@pytest.mark.parametrize("change", ["contract", "mapping", "provider", "extra", "native-extra", "sha", "id", "attempt", "name", "future", "parent-binding", "revision-binding", "missing-page", "duplicate-page", "continuation"]) +def test_native_input_unknown_shapes_and_ambiguous_bindings_fail_closed(change): + value = evidence() + first = value["parents"][0] + if change == "contract": value["contract"] += "+future" + elif change == "mapping": value["outcome_map"] = "devostasis.ci-outcomes.gitlab.v1" + elif change == "provider": value["project"]["provider"] = "future" + elif change == "extra": value["ignored"] = True + elif change == "native-extra": first["native"]["ignored"] = True + elif change == "sha": first["native"]["head_sha"] = "foreign" + elif change == "id": first["native"]["id"] = True + elif change == "attempt": first["native"]["run_attempt"] = 0 + elif change == "name": first["native"]["name"] = 42 + elif change == "future": first["observed_at"] = "2099-01-01T00:00:00Z" + elif change in ("parent-binding", "revision-binding"): + value["revisions"].append({**value["revisions"][0], "revision": "foreign"}) + row = deepcopy(first); row["revision"] = row["native"]["head_sha"] = "foreign" + if change == "parent-binding": value["parents"].append(row) + else: value["revisions"].append({**value["revisions"][0], "committed_at": "2026-09-01T00:00:00Z"}) + else: + page = {"ordinal": 1, "has_next": False, "items": value["parents"]} + value["parents"] = {"pages": [page]} + if change == "missing-page": page["ordinal"] = 2 + elif change == "duplicate-page": value["parents"]["pages"].append(deepcopy(page)) + else: value["parents"]["pages"].append({"ordinal": 2, "has_next": False, "items": []}) + with pytest.raises(ValueError): ci.normalize(value) + + +def test_subject_and_observation_anchor_are_bound_to_native_evidence(): + value = evidence() + with pytest.raises(ValueError, match="project/subject"): + ci.observations(value, {**SUBJECT, "immutable_project_id": "foreign"}) + obs = observed(value); obs.observed_at = "2026-09-04T00:00:00Z" + with pytest.raises(ValueError, match="observation-time"): + integrity.evaluate(obs) + + +def test_page_transport_and_enumeration_do_not_change_canonical_observations(): + value = evidence(); before = deepcopy(value) + alternate = deepcopy(value) + alternate["parents"] = {"pages": [ + {"ordinal": 2, "has_next": False, "items": [value["parents"][0]]}, + {"ordinal": 1, "has_next": True, "items": list(reversed(value["parents"]))}, + ]} + a, b = observed(value), observed(alternate) + assert canonical.canonical_bytes(a.to_dict()) == canonical.canonical_bytes(b.to_dict()) + assert integrity.evaluate(a).to_dict() == integrity.evaluate(b).to_dict() + assert value == before + + +def test_partial_inventory_and_partial_parent_cannot_prove_favorable_history(): + value = evidence() + # One observed passing attempt would be complete without the partial flag. + row = deepcopy(value["parents"][-1]); row["native"]["run_attempt"] = 1 + row["native"].update(status="completed", conclusion="success") + value["parents"] = [row] + value["parents"][0]["coverage"] = "PARTIAL" + normalized = ci.normalize(value) + assert normalized["records"][0]["history_state"] == "UNKNOWN_HISTORY" + assert integrity.evaluate(observed(value)).evaluation_status == "UNKNOWN" + row["coverage"] = "COMPLETE" + value["parents"] = {"pages": [{"ordinal": 1, "has_next": True, "items": [row]}]} + obs = observed(value) + assert obs.get("ci.revision_verdicts_14d").status == "PARTIAL" + assert integrity.evaluate(obs).evaluation_status == "UNKNOWN" + + +def test_unknown_native_outcome_is_unknown_and_child_current_uses_latest_snapshot(): + value = evidence() + for row in value["parents"]: row["native"]["status"] = "future" + assert ci.normalize(value)["records"][0]["current_verdict"] == "UNKNOWN" + case = next(c for c in load(FIXTURE) if c["case"] == "R17") + value = deepcopy(case["given"]["variants"][0]["steps"][0]["evidence"]) + old = value["children"][0]; value["children"] = [old] + old["observed_at"] = "2026-09-04T00:00:00Z" + newer = deepcopy(old); newer["observed_at"] = value["observed_at"] + newer["native"].update(status="in_progress", conclusion=None) + value["children"].append(newer) + result = ci.normalize(value) + assert result["records"][0]["current_verdict"] == "VERIFY_UNRESOLVED" + assert result["records"][0]["history_state"] == "UNKNOWN_HISTORY" + + +def test_normalization_is_opt_in_and_delta_names_the_rule_boundary(): + old = obs_set(); integrity_inputs(old, True, passing_revisions(4)) + previous = integrity.evaluate(old).to_dict() + assert previous["rule_id"] == integrity.RULE_ID + assert "ci_normalization" not in previous["derived"] + current = integrity.evaluate(observed()).to_dict() + assert current["rule_id"] == integrity.RULE_ID + "+ci-normalization-001" + delta = compare({"vitals": [current]}, {"vitals": [previous]}, "COMPARABLE", "previous") + row = next(r for r in delta["vitals"] if r["vital_id"] == "integrity") + assert row["transition_class"] == "INCOMPARABLE" + assert row["reason_codes"][0].startswith("RULE_VERSION_BOUNDARY:") + + +def test_all_r11_r53_have_one_permanent_case_and_required_matrix_variants(): + cases = load(FIXTURE) + assert {c["case"] for c in cases} == {f"R{i}" for i in range(11, 54)} + assert len(cases) == 43 + assert sum(len(c["given"]["variants"]) for c in cases if c["scenario"] == "ci-revisions.v1") == 84 + # Cross-carrier duplication must never become two competing meanings. + with pytest.raises(ValueError, match="across bridge carriers"): + load_suite([FIXTURE, FIXTURE]) + + +def test_bridge_wrong_assertion_still_fails_with_optimized_python(tmp_path): + code = "from surface_bridge import execute; from pathlib import Path; execute({'case':'negative','scenario':'ci-native.v1','given':{'variants':[{'name':'x','provider':'github','mapping':'devostasis.ci-outcomes.github.v1','status':'completed','conclusion':'success'}]},'checks':[{'path':[],'equals':None}]}, Path('.'))" + result = subprocess.run([sys.executable, "-O", "-c", code], cwd=Path(__file__).parent, capture_output=True, text=True) + assert result.returncode != 0 and "AssertionError" in result.stderr diff --git a/tests/test_ci_surface_schema.py b/tests/test_ci_surface_schema.py new file mode 100644 index 0000000..cff4134 --- /dev/null +++ b/tests/test_ci_surface_schema.py @@ -0,0 +1,47 @@ +"""Published closed schemas are checked by a real Draft 2020-12 validator.""" + +from copy import deepcopy +from pathlib import Path + +from jsonschema import Draft202012Validator, ValidationError +from referencing import Registry, Resource +import pytest + +from devostasis import canonical + +ROOT = Path(__file__).resolve().parents[1] +SCHEMAS = ROOT / "schemas" +FILES = ("ci-normalization.schema.json", "conformance-bridge.schema.json") + + +def validator(name): + resources = [canonical.load_file(SCHEMAS / file) for file in FILES] + registry = Registry().with_resources((r["$id"], Resource.from_contents(r)) for r in resources) + return Draft202012Validator(canonical.load_file(SCHEMAS / name), registry=registry) + + +@pytest.mark.parametrize("name", FILES) +def test_ci_surface_schemas_are_valid_draft_2020_12(name): + Draft202012Validator.check_schema(canonical.load_file(SCHEMAS / name)) + + +@pytest.mark.parametrize("name", ["conformance-surface.json", "ci-revisions.json"]) +def test_published_bridge_schema_accepts_every_literal_case(name): + document = canonical.load_file(ROOT / "tests" / "fixtures" / name) + validator("conformance-bridge.schema.json").validate(document) + + +@pytest.mark.parametrize("change", ["lineage", "scenario", "step", "native", "extra", "check"]) +def test_published_bridge_schema_refuses_unknown_native_and_operation_fields(change): + document = canonical.load_file(ROOT / "tests/fixtures/ci-revisions.json") + case = next(c for c in document["cases"] if c["case"] == "R26") + document["cases"] = [deepcopy(case)]; case = document["cases"][0] + step = case["given"]["variants"][0]["steps"][0] + if change == "lineage": step["evidence"]["contract"] += "+future" + elif change == "scenario": case["scenario"] += "+future" + elif change == "step": step["history"] = "SYNTHETIC_PREDECESSOR" + elif change == "native": step["evidence"]["parents"][0]["native"]["ignored"] = True + elif change == "extra": case["given"]["ignored"] = True + else: case["checks"][0]["ignored"] = True + with pytest.raises(ValidationError): + validator("conformance-bridge.schema.json").validate(document) diff --git a/tests/test_conformance_surface.py b/tests/test_conformance_surface.py index b56588f..65b4767 100644 --- a/tests/test_conformance_surface.py +++ b/tests/test_conformance_surface.py @@ -6,22 +6,22 @@ import pytest from devostasis import canonical, vectors -from surface_bridge import execute, load +from surface_bridge import execute, load, load_suite CASES = Path(__file__).parent / "fixtures" / "conformance-surface.json" -@pytest.mark.parametrize("case", load(CASES), ids=lambda c: c["case"]) +@pytest.mark.parametrize("case", load_suite(), ids=lambda c: c["case"]) def test_conformance_surface(case, tmp_path): execute(case, tmp_path) def test_bridge_and_v1_do_not_duplicate_permanent_ids(): old = {v.case for v in vectors.load([Path(__file__).parent / "vectors"])} - assert not old.intersection(c["case"] for c in load(CASES)) + assert not old.intersection(c["case"] for c in load_suite()) -@pytest.mark.parametrize("mutation", ["schema", "scenario", "check", "case", "extra"]) +@pytest.mark.parametrize("mutation", ["schema", "scenario", "check", "case", "extra", "empty"]) def test_bridge_unknown_and_duplicate_lineage_never_skips(tmp_path, mutation): document = canonical.loads(CASES.read_text(encoding="utf-8")) if mutation == "schema": document["schema"] += "+future" @@ -29,6 +29,7 @@ def test_bridge_unknown_and_duplicate_lineage_never_skips(tmp_path, mutation): if mutation == "check": document["cases"][0]["checks"][0]["ignored"] = True if mutation == "case": document["cases"].append(document["cases"][0]) if mutation == "extra": document["ignored"] = True + if mutation == "empty": document["cases"] = [] file = tmp_path / "bad.json"; file.write_bytes(canonical.canonical_bytes(document)) with pytest.raises(ValueError): load(file) diff --git a/tests/test_spec_drift.py b/tests/test_spec_drift.py index 1f75b07..bbe191d 100644 --- a/tests/test_spec_drift.py +++ b/tests/test_spec_drift.py @@ -61,8 +61,8 @@ def test_the_specification_index_lists_every_specification_page(): def test_the_conformance_table_and_bridge_name_the_same_cases(): - from surface_bridge import load - corpus = {case["case"] for case in load(ROOT / "tests/fixtures/conformance-surface.json")} + from surface_bridge import load_suite + corpus = {case["case"] for case in load_suite()} cited = set(BRIDGE_CITATION.findall(CONFORMANCE.read_text(encoding="utf-8"))) assert cited == corpus