Skip to content

Commit 866bd1a

Browse files
ZhiXiao-LinRoyLin
andauthored
feat: type permanent log discontinuities (#3)
Co-authored-by: RoyLin <roylin@RoyLindeMacBook-Pro.local>
1 parent 010588f commit 866bd1a

4 files changed

Lines changed: 49 additions & 3 deletions

File tree

README.md

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ their owning applications.
5050
- **Durable Local State**: Persist owner-only records atomically under
5151
cross-process locks without following symbolic-link state boundaries
5252
- **Logs and Exec**: Expose generation-bound log and exec surfaces only when a
53-
provider reports the corresponding capability
53+
provider reports the corresponding capability, with typed cursor-loss and
54+
source-disconnect discontinuities
5455
- **Capability-Driven Conformance**: Always run complete Base and Recovery
5556
profiles, automatically activate advertised optional profiles, and reject
5657
missing fixtures, incomplete evidence, or provider inventory leaks
@@ -108,7 +109,7 @@ The `RuntimeClient` contract exposes:
108109
| `inspect` | Return the latest observation or a generation-aware absence |
109110
| `stop` | Stop the active generation without deleting durable identity |
110111
| `remove` | Remove the provider resource and persist an absence tombstone |
111-
| `logs` | Read strictly ordered, cursor-addressed log chunks |
112+
| `logs` | Read strictly ordered, cursor-addressed log chunks or return a typed permanent discontinuity |
112113
| `exec` | Execute a bounded command against the exact active generation |
113114

114115
Each mutating request carries its own request ID and optional absolute deadline.
@@ -122,6 +123,14 @@ optional caller deadline. `RuntimeDriver::exec` receives that effective
122123
absolute deadline in `deadline_at_ms`, and every pending replay receives the
123124
same value, so retrying cannot restart or extend the execution window.
124125

126+
Log transport or provider availability failures remain retryable errors. A
127+
provider that can prove the requested cursor was lost, or that the durable unit
128+
survives after its log source disappeared, instead returns
129+
`RuntimeError::LogDiscontinuity` with the exact unit, generation, requested
130+
cursor, and typed reason. Callers can durably project that boundary before
131+
resuming from the earliest currently available record without parsing error
132+
text.
133+
125134
## Capabilities
126135

127136
Providers report supported unit classes, artifact media types, isolation

src/contract/mod.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ pub use observation::{
1717
pub use process::{RuntimeProcessSpec, SecretReference, SecretTarget};
1818
pub use protocol::{
1919
RuntimeActionRequest, RuntimeApplyRequest, RuntimeExecRequest, RuntimeExecResult,
20-
RuntimeLogChunk, RuntimeLogQuery, RuntimeLogStream, RuntimeRemoval,
20+
RuntimeLogChunk, RuntimeLogDiscontinuityReason, RuntimeLogQuery, RuntimeLogStream,
21+
RuntimeRemoval,
2122
};
2223
pub use resource::{IsolationLevel, ResourceLimits};
2324
pub use unit::{

src/contract/protocol.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,13 @@ pub enum RuntimeLogStream {
103103
Stderr,
104104
}
105105

106+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
107+
#[serde(rename_all = "snake_case")]
108+
pub enum RuntimeLogDiscontinuityReason {
109+
CursorLost,
110+
SourceDisconnected,
111+
}
112+
106113
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
107114
#[serde(deny_unknown_fields)]
108115
pub struct RuntimeLogQuery {
@@ -249,3 +256,22 @@ fn canonical_digest<T: Serialize>(value: &T, valid: Result<(), String>) -> Resul
249256
.map_err(|error| format!("could not encode Runtime request: {error}"))?;
250257
Ok(format!("sha256:{:x}", Sha256::digest(bytes)))
251258
}
259+
260+
#[cfg(test)]
261+
mod tests {
262+
use super::*;
263+
264+
#[test]
265+
fn log_discontinuity_reasons_have_stable_wire_names() {
266+
assert_eq!(
267+
serde_json::to_string(&RuntimeLogDiscontinuityReason::CursorLost)
268+
.expect("serialize cursor loss"),
269+
"\"cursor_lost\""
270+
);
271+
assert_eq!(
272+
serde_json::to_string(&RuntimeLogDiscontinuityReason::SourceDisconnected)
273+
.expect("serialize source disconnect"),
274+
"\"source_disconnected\""
275+
);
276+
}
277+
}

src/error.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::contract::RuntimeLogDiscontinuityReason;
12
use thiserror::Error;
23

34
pub type RuntimeResult<T> = Result<T, RuntimeError>;
@@ -30,6 +31,15 @@ pub enum RuntimeError {
3031
ProviderUnavailable(String),
3132
#[error("Runtime transport failed: {0}")]
3233
Transport(String),
34+
#[error(
35+
"Runtime log source for unit {unit_id:?} generation {generation} is discontinuous: {reason:?}"
36+
)]
37+
LogDiscontinuity {
38+
unit_id: String,
39+
generation: u64,
40+
cursor: Option<String>,
41+
reason: RuntimeLogDiscontinuityReason,
42+
},
3343
#[error("Runtime protocol failed: {0}")]
3444
Protocol(String),
3545
}

0 commit comments

Comments
 (0)