1use std::path::PathBuf;
4
5use phoxal_model::component::capability::MotorCommand;
6use phoxal_model::identity::{CapabilityRef, ComponentInstanceId};
7use phoxal_model::{AssetId, Clock};
8use phoxal_runtime_contract::identity::{ParticipantArtifactId, ParticipantId};
9use phoxal_runtime_contract::metadata::ParticipantKind;
10use phoxal_runtime_contract::metadata::ParticipantRequirement;
11use phoxal_runtime_contract::version::FrameworkVersion;
12
13use crate::{BundlePath, BundlePathError, DigestError, ParticipantClock, Sha256Digest};
14
15#[derive(Clone, Debug, thiserror::Error)]
17pub enum DocumentError {
18 #[error(
19 "runtime graph has {count} participants; limit is {}",
20 crate::MAX_RUNTIME_PARTICIPANTS
21 )]
22 TooManyParticipants { count: usize },
23 #[error(
24 "artifact '{artifact}' was built from framework {actual}, but this runtime already selected {expected}"
25 )]
26 MixedFramework {
27 artifact: ParticipantArtifactId,
28 expected: FrameworkVersion,
29 actual: FrameworkVersion,
30 },
31 #[error("duplicate participant id '{id}'")]
32 DuplicateParticipant { id: ParticipantId },
33 #[error("duplicate participant binary path '{path}'")]
34 DuplicateBinary { path: BundlePath },
35 #[error("participant '{participant}' references unknown artifact '{artifact}'")]
36 UnknownArtifact {
37 participant: ParticipantId,
38 artifact: ParticipantArtifactId,
39 },
40 #[error("artifact '{artifact}' contract names '{contract}'")]
41 ArtifactContractMismatch {
42 artifact: ParticipantArtifactId,
43 contract: ParticipantArtifactId,
44 },
45 #[error("artifact '{artifact}' path is outside bin/: {path}")]
46 ArtifactOutsideBin {
47 artifact: ParticipantArtifactId,
48 path: BundlePath,
49 },
50 #[error("brain artifact id is '{actual}', expected 'brain'")]
51 BrainArtifactId { actual: ParticipantArtifactId },
52 #[error("brain artifact path is '{actual}', expected 'bin/brain'")]
53 BrainArtifactPath { actual: BundlePath },
54 #[error("artifact '{artifact}' is not selected by any runtime participant")]
55 UnusedArtifact { artifact: ParticipantArtifactId },
56 #[error("participant '{participant}' names unknown component instance '{component_instance}'")]
57 UnknownComponent {
58 participant: ParticipantId,
59 component_instance: ComponentInstanceId,
60 },
61 #[error("driver participant '{participant}' has no component binding")]
62 MissingDriverComponent { participant: ParticipantId },
63 #[error(
64 "{kind:?} participant '{participant}' cannot bind component instance '{component_instance}'"
65 )]
66 UnexpectedComponent {
67 participant: ParticipantId,
68 kind: ParticipantKind,
69 component_instance: ComponentInstanceId,
70 },
71 #[error("runtime graph has no mandatory 'brain' participant")]
72 MissingBrain,
73 #[error("brain artifact is mounted as participant '{actual}', expected 'brain'")]
74 BrainIdMismatch { actual: ParticipantId },
75 #[error("runtime graph contains more than one brain participant")]
76 DuplicateBrain,
77 #[error("simulated runtime graph has no simulator world authority")]
78 MissingSimulator,
79 #[error("simulated runtime graph contains more than one simulator world authority")]
80 DuplicateSimulator,
81 #[error(
82 "participant '{participant}' clock {participant_clock:?} conflicts with robot clock {robot:?}"
83 )]
84 ClockMismatch {
85 participant: ParticipantId,
86 robot: Clock,
87 participant_clock: ParticipantClock,
88 },
89 #[error(
90 "{kind:?} participant '{participant}' clock {participant_clock:?} is incompatible with robot clock {robot:?}"
91 )]
92 ExecutionModeMismatch {
93 participant: ParticipantId,
94 kind: ParticipantKind,
95 robot: Clock,
96 participant_clock: ParticipantClock,
97 },
98 #[error(
99 "artifact '{artifact}' requires {requirement:?}, but the robot uses {actual} kinematics"
100 )]
101 RequirementKinematicsMismatch {
102 artifact: ParticipantArtifactId,
103 requirement: ParticipantRequirement,
104 actual: phoxal_model::robot::KinematicKind,
105 },
106 #[error("artifact '{artifact}' requires at least one {side} actuator")]
107 RequirementActuatorListEmpty {
108 artifact: ParticipantArtifactId,
109 side: &'static str,
110 },
111 #[error("artifact '{artifact}' requirement could not resolve actuator '{actuator}': {error}")]
112 RequirementActuatorInvalid {
113 artifact: ParticipantArtifactId,
114 actuator: CapabilityRef,
115 error: String,
116 },
117 #[error(
118 "artifact '{artifact}' actuator '{actuator}' is configured for {actual:?}, but the binary requires {expected:?}"
119 )]
120 RequirementMotorModeMismatch {
121 artifact: ParticipantArtifactId,
122 actuator: CapabilityRef,
123 expected: MotorCommand,
124 actual: MotorCommand,
125 },
126 #[error("artifact '{artifact}' has an invalid config schema: {error}")]
127 InvalidConfigSchema {
128 artifact: ParticipantArtifactId,
129 error: String,
130 },
131 #[error("participant '{participant}' config does not match its binary schema: {error}")]
132 InvalidConfig {
133 participant: ParticipantId,
134 error: String,
135 },
136 #[error("asset id '{id}' is persisted at the wrong path '{path}'", id = id.as_str())]
137 AssetPathMismatch { id: AssetId, path: BundlePath },
138 #[error("asset path is outside assets/: {path}")]
139 AssetOutsideAssets { path: BundlePath },
140 #[error("duplicate asset id '{id}'", id = id.as_str())]
141 DuplicateAssetId { id: AssetId },
142 #[error("duplicate asset path '{path}'")]
143 DuplicateAssetPath { path: BundlePath },
144 #[error("router config is outside assets/: {path}")]
145 RouterOutsideAssets { path: BundlePath },
146 #[error("router config is not present in the asset index: {path}")]
147 RouterMissingAsset { path: BundlePath },
148 #[error("supplied asset bytes do not match the persisted asset index")]
149 AssetIndexMismatch,
150 #[error("supplied executable sources do not match the persisted artifact set")]
151 BinaryIndexMismatch,
152 #[error("bundle path is not valid: {0}")]
153 Path(#[from] BundlePathError),
154}
155
156#[derive(Clone, Debug, thiserror::Error)]
158pub enum SelectionError {
159 #[error("runtime bundle has no participant '{requested}'")]
160 Unknown { requested: ParticipantId },
161 #[error("participant '{participant}' references missing artifact '{artifact}'")]
162 MissingArtifact {
163 participant: ParticipantId,
164 artifact: ParticipantArtifactId,
165 },
166}
167
168#[derive(Debug, thiserror::Error)]
170pub enum BundleError {
171 #[error("failed to resolve bundle root {}: {source}", path.display())]
172 Root {
173 path: PathBuf,
174 #[source]
175 source: std::io::Error,
176 },
177 #[error("bundle root is not a directory: {0}")]
178 NotDirectory(PathBuf),
179 #[error("bundle target already exists: {0}")]
180 TargetExists(PathBuf),
181 #[error("failed to read runtime document {}: {source}", path.display())]
182 ReadDocument {
183 path: PathBuf,
184 #[source]
185 source: std::io::Error,
186 },
187 #[error("runtime document is not valid JSON: {0}")]
188 DocumentJson(#[from] serde_json::Error),
189 #[error(transparent)]
190 Document(#[from] DocumentError),
191 #[error("bundle contains unsupported filesystem entry {path}")]
192 UnsupportedEntry { path: PathBuf },
193 #[error("bundle executable is not executable: {path}")]
194 NotExecutable { path: PathBuf },
195 #[error("bundle contains unexpected file {path}")]
196 UnexpectedFile { path: PathBuf },
197 #[error("bundle contains unindexed directory {path}")]
198 UnindexedDirectory { path: PathBuf },
199 #[error("bundle is missing required file {path}")]
200 MissingFile { path: PathBuf },
201 #[error("failed to read bundle file {path}: {source}", path = path.display())]
202 ReadFile {
203 path: PathBuf,
204 #[source]
205 source: std::io::Error,
206 },
207 #[error("bundle file {path} changed after indexing: expected {expected}, found {actual}")]
208 Integrity {
209 path: PathBuf,
210 expected: Sha256Digest,
211 actual: Sha256Digest,
212 },
213 #[error("bundle file {path} has size {actual}, expected {expected}")]
214 Size {
215 path: PathBuf,
216 expected: u64,
217 actual: u64,
218 },
219 #[error("asset '{id}' is not declared by runtime.json", id = id.as_str())]
220 UndeclaredAsset { id: AssetId },
221 #[error(transparent)]
222 Path(#[from] BundlePathError),
223 #[error(transparent)]
224 Digest(#[from] DigestError),
225 #[error(transparent)]
226 Selection(#[from] SelectionError),
227}