Skip to main content

ossctl_core/protocol/
reconcile.rs

1//! Public wire DTO for `ossctl release verify` — the read-only reconcile report
2//! (ADR-0002 §1, ADR-0003 state table).
3//!
4//! `release verify` reads a journaled run and, for each *published* target,
5//! reconciles the recorded [`PublishReceipt`](crate::protocol::journal::PublishReceipt)
6//! against what the registry currently holds — never mutating the repo, the
7//! journal, or the registry. The result is this report, emitted under the
8//! canonical `{schema_version, data, warnings}` envelope, so it carries no
9//! document version of its own ([`crate::SCHEMA_VERSION`] versions the envelope).
10//!
11//! ## The `Unknown` discipline
12//!
13//! Every per-target outcome is a [`VerifyOutcome`]; a reconcile that *could not
14//! be performed* (a registry outage, an unresolvable package, a distribution
15//! target not observable through [`RegistryQuery`](crate::ports::RegistryQuery))
16//! is [`VerifyOutcome::Unknown`], **never** [`VerifyOutcome::Missing`] — the same
17//! tri-state presence discipline `ossctl audit` uses. An outage must never be
18//! read as "the release did not land".
19
20use serde::Serialize;
21
22use crate::protocol::journal::RunStatus;
23use crate::protocol::release::VerifyOutcome;
24
25/// The read-only reconcile report for one journaled run — the body of
26/// `ossctl release verify`'s success envelope.
27///
28/// Reconciles the run's *published* targets (those with a durable receipt)
29/// against current registry state. A run that was interrupted before publishing
30/// a declared target simply has no receipt to reconcile for it; that is surfaced
31/// as an envelope warning by the CLI, not as a false [`VerifyOutcome::Missing`].
32#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
33pub struct ReconcileReport {
34    /// The run this report reconciles.
35    pub run_id: String,
36    /// The sealed plan id the run executes (echoed from the journal).
37    pub plan_id: String,
38    /// The run's derived status at read time (`in_progress`/`completed`/
39    /// `abandoned`) — context for the reconcile, which is a point-in-time snapshot
40    /// of a possibly-live run.
41    pub run_status: RunStatus,
42    /// The high-water event sequence this report was reconciled against — the
43    /// snapshot's provenance. For a live run, two reconciles taken at different
44    /// `journal_seq` may legitimately differ; this pins which log prefix was seen.
45    pub journal_seq: u64,
46    /// One entry per published target, in stable target-id order.
47    pub targets: Vec<TargetReconcile>,
48    /// Rollup counts across [`Self::targets`].
49    pub summary: ReconcileSummary,
50}
51
52/// One published target reconciled against the registry.
53#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
54pub struct TargetReconcile {
55    /// The journaled target id (the `published` map key, e.g. `"cargo"`).
56    pub target: String,
57    /// The ecosystem the receipt was published to (`rust`, `node`, …), verbatim
58    /// from the receipt.
59    pub ecosystem: String,
60    /// The published package/crate name, when the receipt recorded one.
61    pub package: Option<String>,
62    /// The version the receipt claims landed — the value reconciled remotely.
63    pub version: String,
64    /// How the receipt reconciles against current registry state.
65    pub outcome: VerifyOutcome,
66    /// A human-readable reason, present for every non-`matches` outcome (why it is
67    /// `missing`/`conflicts`, or why the reconcile was `unknown`). Omitted for a
68    /// clean `matches`.
69    #[serde(skip_serializing_if = "Option::is_none")]
70    pub detail: Option<String>,
71}
72
73/// Rollup counts across all reconciled targets — the four [`VerifyOutcome`]
74/// classes plus the total, so a caller branches on `conflicts`/`missing` without
75/// re-tallying [`ReconcileReport::targets`].
76#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
77pub struct ReconcileSummary {
78    /// Total targets reconciled (`= matches + conflicts + missing + unknown`).
79    pub reconciled: usize,
80    /// Targets whose receipt matches registry state.
81    pub matches: usize,
82    /// Targets present remotely but with a differing digest. Reachable only when
83    /// the registry exposes a remote digest to compare against the receipt's; the
84    /// current [`RegistryQuery`](crate::ports::RegistryQuery) port lists versions
85    /// only, so in production a digest-level conflict is not yet observable (a
86    /// present version resolves to `matches`, never a false `conflicts`).
87    pub conflicts: usize,
88    /// Targets the registry does not report as published.
89    pub missing: usize,
90    /// Targets the reconcile could not be performed for.
91    pub unknown: usize,
92}