| 1 | //! Work Graph — the single authoritative work ledger for a session. |
| 2 | //! |
| 3 | //! One graph carries objectives, plan steps, operations, evidence, blockers, |
| 4 | //! and approvals. **Invariant: one graph writes every projection; projections |
| 5 | //! never write each other.** Plan and todo views, work-surface rows, and the |
| 6 | //! inspector all derive from [`WorkGraphSnapshot`] through pure functions. |
| 7 | //! |
| 8 | //! Why a graph instead of parallel trackers: flat status lists let an agent |
| 9 | //! mark work "done" by assertion, lose dependency structure, and cannot say |
| 10 | //! what evidence backed a completion. Here completion and verification are |
| 11 | //! distinct states, `Verified` is unreachable without a satisfying evidence |
| 12 | //! path (V4, fail-closed), dependencies are first-class edges (acyclic, V1), |
| 13 | //! and liveness truth stays with the owning subsystems — the graph records |
| 14 | //! observations, it never invents them. |
| 15 | //! |
| 16 | //! This slice is the core only: model, changes, pure reducer, validation. |
| 17 | //! Session persistence, legacy import, UI projections, and liveness adapters |
| 18 | //! land in later slices; nothing in the app or engine calls this yet. |
| 19 | // Staged cutover: later slices wire persistence, UI, and liveness; until |
| 20 | // then the public surface (including re-exports) has no external callers. |
| 21 | #![allow(dead_code)] |
| 22 | #![allow(unused_imports)] |
| 23 | |
| 24 | mod compat; |
| 25 | mod digest; |
| 26 | mod events; |
| 27 | mod ids; |
| 28 | mod liveness; |
| 29 | mod migration; |
| 30 | mod model; |
| 31 | mod reducer; |
| 32 | mod runtime; |
| 33 | mod validate; |
| 34 | |
| 35 | #[cfg(test)] |
| 36 | mod tests; |
| 37 | |
| 38 | pub use compat::{PlanProjection, TodoProjection, project_plan, project_todos}; |
| 39 | pub use digest::{format_operation_digest, format_operation_digest_parts}; |
| 40 | pub use events::{ |
| 41 | ApprovalRef, CancelOutcome, ChangeCtx, ChangeReceipt, ObservationSummary, OperationObservation, |
| 42 | OwnerState, ProposedNodeUpdate, WorkGraphChange, WorkGraphProposal, WorkNodePatch, |
| 43 | }; |
| 44 | pub use ids::{BindingId, ChangeId, ProposalId, WorkEdgeId, WorkNodeId}; |
| 45 | pub use liveness::{ |
| 46 | OperationIntent, OperationOwnerSnapshot, fleet_task_owner_snapshot, lane_owner_snapshot, |
| 47 | task_owner_snapshot, |
| 48 | }; |
| 49 | pub use migration::import_legacy; |
| 50 | pub(crate) use model::constrained_effective_reasoning_for_route; |
| 51 | pub use model::{ |
| 52 | ACTIVITY_CAP, AcceptanceRequirement, BoundedSet, BoundedVec, CompatPlanMetadata, |
| 53 | CompatProjectionState, CompatTodoBinding, EdgeKind, EvidenceKind, EvidenceKindTag, EvidenceRef, |
| 54 | EvidenceRefError, HISTORY_CAP, IdempotencyKey, NodeKind, NodeState, OperationBinding, |
| 55 | Provenance, ReasoningEffortTier, SCHEMA_VERSION, SEEN_KEYS_CAP, Ts, WorkActivityEvent, |
| 56 | WorkEdge, WorkGraphSnapshot, WorkNode, external_identity_is_well_formed, |
| 57 | }; |
| 58 | pub use reducer::apply; |
| 59 | pub(crate) use runtime::{ACTIVE_OPERATION_SUMMARY_END, ACTIVE_OPERATION_SUMMARY_START}; |
| 60 | pub use runtime::{SharedWorkRuntime, WorkRuntime, WorkRuntimeSnapshot, new_shared_work_runtime}; |
| 61 | pub use validate::{ValidationCode, ValidationReport, Violation, validate}; |
| 62 | |
| 63 | /// Convenience wrapper owning the current snapshot. [`WorkGraph::apply`] |
| 64 | /// commits the reducer's result on success and leaves the snapshot untouched |
| 65 | /// on rejection — the fail-closed contract, packaged. |
| 66 | #[derive(Debug, Clone, PartialEq)] |
| 67 | pub struct WorkGraph { |
| 68 | snapshot: WorkGraphSnapshot, |
| 69 | } |
| 70 | |
| 71 | impl WorkGraph { |
| 72 | #[must_use] |
| 73 | pub fn new() -> Self { |
| 74 | Self { |
| 75 | snapshot: WorkGraphSnapshot::new(), |
| 76 | } |
| 77 | } |
| 78 | |
| 79 | #[must_use] |
| 80 | pub fn from_snapshot(snapshot: WorkGraphSnapshot) -> Self { |
| 81 | Self { snapshot } |
| 82 | } |
| 83 | |
| 84 | #[must_use] |
| 85 | pub fn snapshot(&self) -> &WorkGraphSnapshot { |
| 86 | &self.snapshot |
| 87 | } |
| 88 | |
| 89 | #[must_use] |
| 90 | pub fn into_snapshot(self) -> WorkGraphSnapshot { |
| 91 | self.snapshot |
| 92 | } |
| 93 | |
| 94 | /// Apply one change through the reducer. On `Ok` the new snapshot is |
| 95 | /// committed; on `Err` the held snapshot is unchanged. |
| 96 | pub fn apply( |
| 97 | &mut self, |
| 98 | change: WorkGraphChange, |
| 99 | ctx: ChangeCtx, |
| 100 | ) -> Result<ChangeReceipt, ValidationReport> { |
| 101 | let (next, receipt) = reducer::apply(&self.snapshot, change, ctx)?; |
| 102 | self.snapshot = next; |
| 103 | Ok(receipt) |
| 104 | } |
| 105 | } |
| 106 | |
| 107 | impl Default for WorkGraph { |
| 108 | fn default() -> Self { |
| 109 | Self::new() |
| 110 | } |
| 111 | } |
| 112 |