Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions crates/dockermap-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1043,6 +1043,42 @@ mod tests {
assert!(serde_json::from_value::<RuntimeMapEdge>(wrong_target).is_err());
}

#[test]
fn version_five_tmux_evidence_requires_its_closed_slot_and_canonical_edge() {
let valid = serde_json::json!({
"version": 5,
"id": "tmux_evidence_session_listing_opaque",
"provider": "tmux",
"kind": "tmux_session_listing",
"assertionKind": "observed",
"summary": "tmux listed a local session",
"subjectRef": "tmux_session_opaque",
"collectedAt": 42,
"providerRevision": "opaque-tmux-revision",
"providerSlot": "tmux",
"freshness": "stale"
});
assert!(serde_json::from_value::<RuntimeEvidenceRef>(valid.clone()).is_ok());
for (field, invalid) in [
("providerSlot", serde_json::json!("host_scoped")),
("provider", serde_json::json!("cron")),
("assertionKind", serde_json::json!("declared")),
("kind", serde_json::json!("cron_schedule_declaration")),
] {
let mut malformed = valid.clone();
malformed[field] = invalid;
assert!(serde_json::from_value::<RuntimeEvidenceRef>(malformed).is_err());
}
let edge = serde_json::json!({
"source": "tmux_session_opaque", "target": "host_local", "relationship": "runs_on",
"metadata": {}, "evidenceRefs": [valid]
});
assert!(serde_json::from_value::<RuntimeMapEdge>(edge.clone()).is_ok());
let mut wrong_target = edge;
wrong_target["target"] = serde_json::json!("host_other");
assert!(serde_json::from_value::<RuntimeMapEdge>(wrong_target).is_err());
}

#[test]
fn version_one_evidence_cannot_attest_a_different_runtime_edge() {
let snapshot = mock_snapshot();
Expand Down
43 changes: 37 additions & 6 deletions crates/dockermap-core/src/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,8 +153,11 @@ pub enum RuntimeMode {
pub enum ProviderSlot {
NetworkInfrastructure,
HostScoped,
/// Tmux has an independent collector lifecycle. It must not inherit
/// host-node, listener, or PM2 freshness.
Tmux,
/// Cron has an independent collector lifecycle. It must not inherit
/// host-node, listener, PM2, or tmux freshness.
/// host-node, listener, or PM2 freshness.
Cron,
/// systemd has an independent collector lifecycle. It must not inherit
/// freshness from the broader host-scoped observation slot.
Expand Down Expand Up @@ -823,16 +826,17 @@ pub struct RuntimeMapNode {
pub package: Option<RuntimePackageEntity>,
}

/// Evidence providers are deliberately closed. Version two adds systemd only
/// after it received its own scheduler slot; it cannot inherit a broader host
/// collection's freshness or revision.
/// Evidence providers are deliberately closed. Every host provider enters only
/// after it receives its own scheduler slot, so it cannot inherit a broader
/// host collection's freshness or revision.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum RuntimeEvidenceProvider {
Docker,
Systemd,
Npm,
Cron,
Tmux,
}

/// Evidence assertion semantics are deliberately closed. A declaration says
Expand Down Expand Up @@ -872,6 +876,9 @@ pub enum RuntimeEvidenceKind {
NpmPackageManifestDependency,
/// A parsed cron declaration. This does not claim the command ran.
CronScheduleDeclaration,
/// A fixed tmux session listing. This does not claim that the session is
/// attached, active, executing work, or reachable.
TmuxSessionListing,
}

/// A compact, versioned reference to the bounded fact supporting a runtime
Expand All @@ -882,7 +889,7 @@ pub enum RuntimeEvidenceKind {
pub struct RuntimeEvidenceRef {
/// Version of this closed evidence representation, not a provider API
/// version. It lets future additions remain explicit and reviewable.
#[schemars(range(min = 1, max = 4))]
#[schemars(range(min = 1, max = 5))]
pub version: u8,
#[schemars(length(min = 1, max = 259))]
pub id: String,
Expand Down Expand Up @@ -972,6 +979,15 @@ impl RuntimeEvidenceRef {
| RuntimeEvidenceFreshness::Stale
| RuntimeEvidenceFreshness::TimedOut,
Some(ProviderSlot::Cron),
) | (
5,
RuntimeEvidenceProvider::Tmux,
RuntimeEvidenceKind::TmuxSessionListing,
RuntimeEvidenceAssertionKind::Observed,
RuntimeEvidenceFreshness::Fresh
| RuntimeEvidenceFreshness::Stale
| RuntimeEvidenceFreshness::TimedOut,
Some(ProviderSlot::Tmux),
)
)
}
Expand Down Expand Up @@ -1181,6 +1197,21 @@ impl RuntimeMapEdge {
&& self.target == "host_local"
&& self.source != self.target
}
(
5,
RuntimeEvidenceProvider::Tmux,
RuntimeEvidenceKind::TmuxSessionListing,
RuntimeEvidenceAssertionKind::Observed,
RuntimeEvidenceFreshness::Fresh
| RuntimeEvidenceFreshness::Stale
| RuntimeEvidenceFreshness::TimedOut,
Some(ProviderSlot::Tmux),
) => {
self.relationship == RuntimeRelationshipKind::RunsOn
&& self.source.starts_with("tmux_session_")
&& self.target == "host_local"
&& self.source != self.target
}
(
2,
RuntimeEvidenceProvider::Systemd,
Expand Down Expand Up @@ -1322,7 +1353,7 @@ pub struct RuntimeMap {
#[schemars(length(min = 1))]
pub model_revision: String,
#[serde(rename = "providerStates")]
#[schemars(length(min = 7, max = 7))]
#[schemars(length(min = 8, max = 8))]
pub provider_states: Vec<ProviderState>,
/// ACTUAL source of these bytes: "docker" or "mock" (#85 A3). Stamped by
/// the daemon route layer from the cache's runtime mode.
Expand Down
8 changes: 4 additions & 4 deletions crates/dockermap-core/src/schema_baseline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,16 +162,16 @@ mod tests {
.expect("provider state property exists");
assert_eq!(
states.get("minItems").and_then(|value| value.as_u64()),
Some(7)
Some(8)
);
assert_eq!(
states.get("maxItems").and_then(|value| value.as_u64()),
Some(7)
Some(8)
);
}

#[test]
fn runtime_evidence_schema_admits_the_closed_version_four_cron_shape() {
fn runtime_evidence_schema_admits_the_closed_version_five_tmux_shape() {
let schema = DAEMON_SCHEMA_NAMES
.iter()
.zip(daemon_schema_documents())
Expand All @@ -184,7 +184,7 @@ mod tests {
evidence
.pointer("/properties/version/maximum")
.and_then(|value| value.as_u64()),
Some(4),
Some(5),
"generated schema must not reject the newest closed evidence version"
);
assert!(
Expand Down
6 changes: 4 additions & 2 deletions crates/dockermap-core/src/snapshot_runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,8 @@ fn docker_runtime_evidence(
| RuntimeEvidenceKind::SystemdWants
| RuntimeEvidenceKind::SystemdPartOf
| RuntimeEvidenceKind::NpmPackageManifestDependency
| RuntimeEvidenceKind::CronScheduleDeclaration => {
| RuntimeEvidenceKind::CronScheduleDeclaration
| RuntimeEvidenceKind::TmuxSessionListing => {
unreachable!("Docker evidence helper only accepts Docker evidence kinds")
}
};
Expand All @@ -414,7 +415,8 @@ fn docker_runtime_evidence(
| RuntimeEvidenceKind::SystemdWants
| RuntimeEvidenceKind::SystemdPartOf
| RuntimeEvidenceKind::NpmPackageManifestDependency
| RuntimeEvidenceKind::CronScheduleDeclaration => {
| RuntimeEvidenceKind::CronScheduleDeclaration
| RuntimeEvidenceKind::TmuxSessionListing => {
unreachable!("Docker evidence helper only accepts Docker evidence kinds")
}
};
Expand Down
Loading
Loading