Skip to content
Merged
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
81 changes: 64 additions & 17 deletions src/interface/ffi/src/main.zig
Original file line number Diff line number Diff line change
Expand Up @@ -246,10 +246,11 @@ export fn verisimiser_enable_dimension(
return .invalid_param;
};

// TODO: actually enable the dimension in the overlay index

clearError();
return .ok;
// The overlay index that persists per-entity dimension state is not yet
// wired into the FFI. Fail loudly rather than report a dimension as enabled
// when it is not (soundness: no silent success).
setError("octad dimension overlay not yet wired into the FFI");
return .sidecar_unavailable;
}

/// Get the active dimension bitmask for an entity.
Expand Down Expand Up @@ -296,10 +297,11 @@ export fn verisimiser_record_provenance(
return .invalid_param;
};

// TODO: compute SHA-256 hash, chain from previous, write to sidecar

clearError();
return .ok;
// The SHA-256 hash-chain sidecar append is not yet wired into the FFI.
// Fail loudly rather than report a provenance event as recorded when no
// entry was written (soundness: no phantom audit trail).
setError("provenance sidecar not yet wired into the FFI");
return .sidecar_unavailable;
}

/// Verify the integrity of an entity's provenance hash chain.
Expand All @@ -319,11 +321,12 @@ export fn verisimiser_verify_provenance(

_ = entity_id;

// TODO: walk the hash chain, verify each link
// Return .chain_corrupted if any link fails verification

clearError();
return .ok;
// The hash-chain store is not yet wired into the FFI, so integrity cannot
// be confirmed. Return an error rather than .ok — reporting "verified" for
// an unchecked (possibly tampered) chain would be the worst kind of
// soundness hole.
setError("provenance sidecar not yet wired into the FFI; cannot verify integrity");
return .sidecar_unavailable;
}

/// Get the length of an entity's provenance chain.
Expand Down Expand Up @@ -364,10 +367,11 @@ export fn verisimiser_record_version(
_ = snapshot_ptr;
_ = snapshot_len;

// TODO: store snapshot in temporal sidecar with current timestamp

clearError();
return .ok;
// The temporal sidecar that stores version snapshots is not yet wired into
// the FFI. Fail loudly rather than report a version as recorded when no
// snapshot was stored (soundness: no phantom history).
setError("temporal sidecar not yet wired into the FFI");
return .sidecar_unavailable;
}

/// Query entity state at a specific point in time.
Expand Down Expand Up @@ -614,3 +618,46 @@ test "enable dimension with invalid dimension" {
const result = verisimiser_enable_dimension(handle, 42, 99);
try std.testing.expectEqual(Result.invalid_param, result);
}

// Soundness: the persistence-backed octad operations are not yet wired into
// the FFI, so they must fail loudly rather than report a false success.

test "verify provenance does not falsely report verified" {
const handle = verisimiser_init() orelse return error.InitFailed;
defer verisimiser_free(handle);

// A valid handle + entity must NOT return .ok while verification is unwired:
// claiming a chain is verified without checking it would be unsound.
const result = verisimiser_verify_provenance(handle, 42);
try std.testing.expect(result != Result.ok);
try std.testing.expectEqual(Result.sidecar_unavailable, result);
}

test "record provenance does not falsely report recorded" {
const handle = verisimiser_init() orelse return error.InitFailed;
defer verisimiser_free(handle);

const result = verisimiser_record_provenance(handle, 42, 0, 0);
try std.testing.expect(result != Result.ok);
try std.testing.expectEqual(Result.sidecar_unavailable, result);
}

test "record version does not falsely report stored" {
const handle = verisimiser_init() orelse return error.InitFailed;
defer verisimiser_free(handle);

const result = verisimiser_record_version(handle, 42, 0, 0);
try std.testing.expect(result != Result.ok);
try std.testing.expectEqual(Result.sidecar_unavailable, result);
}

test "enable dimension does not falsely report enabled" {
const handle = verisimiser_init() orelse return error.InitFailed;
defer verisimiser_free(handle);

// Dimension 2 (provenance) is a valid enum value; the call must still fail
// loudly because the overlay index is not wired in.
const result = verisimiser_enable_dimension(handle, 42, 2);
try std.testing.expect(result != Result.ok);
try std.testing.expectEqual(Result.sidecar_unavailable, result);
}
57 changes: 57 additions & 0 deletions src/manifest/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,43 @@ mod validate_manifest_tests {
assert!(report.failed_count() == 0);
}

/// A manifest that sets both `[database].backend` and the legacy
/// `target-db` to conflicting values must fail validation up front, not
/// silently pass and blow up later at generate time (V-L2-E1).
#[test]
fn conflicting_backend_fails_validation() {
let dir = tempfile::tempdir().expect("tempdir");
let path = dir.path().join("verisimiser.toml");
let sidecar_path = dir.path().join("sidecar.db");
let body = format!(
"[project]\n\
name = \"test\"\n\
[database]\n\
backend = \"sqlite\"\n\
target-db = \"postgresql\"\n\
[sidecar]\n\
storage = \"sqlite\"\n\
path = \"{}\"\n",
sidecar_path.display().to_string().replace('\\', "/")
);
std::fs::write(&path, body).expect("write");

let report = validate_manifest(path.to_str().unwrap());
assert!(
!report.passed,
"conflicting backend/target-db must fail validation; checks: {:?}",
report.checks
);
assert!(
report
.checks
.iter()
.any(|c| c.name == "backend-unambiguous" && !c.passed),
"expected a failed 'backend-unambiguous' check; checks: {:?}",
report.checks
);
}

/// A schema-source pointing at a missing file must fail
/// `schema-source-exists`.
#[test]
Expand Down Expand Up @@ -960,6 +997,26 @@ pub fn validate_manifest(path: &str) -> ValidationReport {
},
},
);

// 5. Backend selection is unambiguous. `effective_backend()` rejects a
// manifest that sets both [database].backend and the legacy
// [database].target-db to conflicting values (V-L2-E1). Validation must
// exercise it, otherwise a latent conflict passes `validate` only to
// fail later at generate time.
let backend_check = ValidationCheck {
name: "backend-unambiguous".to_string(),
description: "[database].backend and legacy target-db do not conflict".to_string(),
passed: true,
detail: None,
};
checks.push(match m.database.effective_backend() {
Ok(_) => backend_check,
Err(e) => ValidationCheck {
passed: false,
detail: Some(e.to_string()),
..backend_check
},
});
}

let passed = checks.iter().all(|c| c.passed);
Expand Down
Loading