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 @@ -1007,6 +1007,42 @@ mod tests {
assert!(serde_json::from_value::<RuntimeMapEdge>(wrong_target).is_err());
}

#[test]
fn version_four_cron_evidence_requires_its_closed_slot_and_canonical_edge() {
let valid = serde_json::json!({
"version": 4,
"id": "cron_evidence_schedule_opaque",
"provider": "cron",
"kind": "cron_schedule_declaration",
"assertionKind": "declared",
"summary": "cron declared a scheduled job",
"subjectRef": "scheduled_job_opaque",
"collectedAt": 42,
"providerRevision": "opaque-cron-revision",
"providerSlot": "cron",
"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!("systemd")),
("assertionKind", serde_json::json!("observed")),
("kind", serde_json::json!("systemd_requires")),
] {
let mut malformed = valid.clone();
malformed[field] = invalid;
assert!(serde_json::from_value::<RuntimeEvidenceRef>(malformed).is_err());
}
let edge = serde_json::json!({
"source": "scheduled_job_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
34 changes: 32 additions & 2 deletions crates/dockermap-core/src/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,9 @@ pub enum RuntimeMode {
pub enum ProviderSlot {
NetworkInfrastructure,
HostScoped,
/// Cron has an independent collector lifecycle. It must not inherit
/// host-node, listener, PM2, or tmux freshness.
Cron,
/// systemd has an independent collector lifecycle. It must not inherit
/// freshness from the broader host-scoped observation slot.
Systemd,
Expand Down Expand Up @@ -829,6 +832,7 @@ pub enum RuntimeEvidenceProvider {
Docker,
Systemd,
Npm,
Cron,
}

/// Evidence assertion semantics are deliberately closed. A declaration says
Expand Down Expand Up @@ -866,6 +870,8 @@ pub enum RuntimeEvidenceKind {
/// A package.json dependency declaration. This is not proof that the
/// package was installed, resolved, executed, or is safe.
NpmPackageManifestDependency,
/// A parsed cron declaration. This does not claim the command ran.
CronScheduleDeclaration,
}

/// A compact, versioned reference to the bounded fact supporting a runtime
Expand All @@ -876,7 +882,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 = 3))]
#[schemars(range(min = 1, max = 4))]
pub version: u8,
#[schemars(length(min = 1, max = 259))]
pub id: String,
Expand Down Expand Up @@ -957,6 +963,15 @@ impl RuntimeEvidenceRef {
| RuntimeEvidenceFreshness::Stale
| RuntimeEvidenceFreshness::TimedOut,
Some(ProviderSlot::ProjectNpm),
) | (
4,
RuntimeEvidenceProvider::Cron,
RuntimeEvidenceKind::CronScheduleDeclaration,
RuntimeEvidenceAssertionKind::Declared,
RuntimeEvidenceFreshness::Fresh
| RuntimeEvidenceFreshness::Stale
| RuntimeEvidenceFreshness::TimedOut,
Some(ProviderSlot::Cron),
)
)
}
Expand Down Expand Up @@ -1151,6 +1166,21 @@ impl RuntimeMapEdge {
&& self.target.starts_with("npm_package_")
&& self.source != self.target
}
(
4,
RuntimeEvidenceProvider::Cron,
RuntimeEvidenceKind::CronScheduleDeclaration,
RuntimeEvidenceAssertionKind::Declared,
RuntimeEvidenceFreshness::Fresh
| RuntimeEvidenceFreshness::Stale
| RuntimeEvidenceFreshness::TimedOut,
Some(ProviderSlot::Cron),
) => {
self.relationship == RuntimeRelationshipKind::RunsOn
&& self.source.starts_with("scheduled_job_")
&& self.target == "host_local"
&& self.source != self.target
}
(
2,
RuntimeEvidenceProvider::Systemd,
Expand Down Expand Up @@ -1292,7 +1322,7 @@ pub struct RuntimeMap {
#[schemars(length(min = 1))]
pub model_revision: String,
#[serde(rename = "providerStates")]
#[schemars(length(min = 6, max = 6))]
#[schemars(length(min = 7, max = 7))]
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
27 changes: 25 additions & 2 deletions crates/dockermap-core/src/schema_baseline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,11 +162,34 @@ mod tests {
.expect("provider state property exists");
assert_eq!(
states.get("minItems").and_then(|value| value.as_u64()),
Some(6)
Some(7)
);
assert_eq!(
states.get("maxItems").and_then(|value| value.as_u64()),
Some(6)
Some(7)
);
}

#[test]
fn runtime_evidence_schema_admits_the_closed_version_four_cron_shape() {
let schema = DAEMON_SCHEMA_NAMES
.iter()
.zip(daemon_schema_documents())
.find_map(|(name, schema)| (*name == "RuntimeMap").then_some(schema))
.expect("runtime map schema exists");
let evidence = schema
.pointer("/$defs/RuntimeEvidenceRef")
.expect("runtime evidence definition exists");
assert_eq!(
evidence
.pointer("/properties/version/maximum")
.and_then(|value| value.as_u64()),
Some(4),
"generated schema must not reject the newest closed evidence version"
);
assert!(
evidence.pointer("/properties/provider/$ref").is_some(),
"provider stays a closed generated enum"
);
}

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 @@ -393,7 +393,8 @@ fn docker_runtime_evidence(
RuntimeEvidenceKind::SystemdRequires
| RuntimeEvidenceKind::SystemdWants
| RuntimeEvidenceKind::SystemdPartOf
| RuntimeEvidenceKind::NpmPackageManifestDependency => {
| RuntimeEvidenceKind::NpmPackageManifestDependency
| RuntimeEvidenceKind::CronScheduleDeclaration => {
unreachable!("Docker evidence helper only accepts Docker evidence kinds")
}
};
Expand All @@ -412,7 +413,8 @@ fn docker_runtime_evidence(
RuntimeEvidenceKind::SystemdRequires
| RuntimeEvidenceKind::SystemdWants
| RuntimeEvidenceKind::SystemdPartOf
| RuntimeEvidenceKind::NpmPackageManifestDependency => {
| RuntimeEvidenceKind::NpmPackageManifestDependency
| RuntimeEvidenceKind::CronScheduleDeclaration => {
unreachable!("Docker evidence helper only accepts Docker evidence kinds")
}
};
Expand Down
Loading
Loading