diff --git a/Makefile b/Makefile index 4f641ca..7f40132 100644 --- a/Makefile +++ b/Makefile @@ -107,8 +107,8 @@ test: rust-preflight $(CARGO) test $(CARGO_LOCKED) -p solstone-linux check-observer-contract: rust-preflight - @echo "Observer contract bundle: 8.0.0" - @echo "Observer contract manifest SHA-256: 9b3bcd6b7f8a83adb9007e32501af44403cb93cfb8d80f256b6a7b5b9f93057e" + @echo "Observer contract bundle: 9.0.0" + @echo "Observer contract manifest SHA-256: 93b2a5a1604f1ba6fad30624c00cac98ea3d04a80cb1718886cf665c16f58834" @inventory=$$(CARGO_NET_OFFLINE=true $(CARGO) test $(CARGO_LOCKED) -p solstone-linux observer_contract_tests:: -- --list); \ printf '%s\n' "$$inventory"; \ printf '%s\n' "$$inventory" | grep -Fx 'observer_contract_tests::observer_contract_conformance: test' >/dev/null || { echo "error: observer contract test inventory mismatch" >&2; exit 1; }; \ diff --git a/contracts/observer-client-import.json b/contracts/observer-client-import.json index bd647e9..b83db54 100644 --- a/contracts/observer-client-import.json +++ b/contracts/observer-client-import.json @@ -1,8 +1,8 @@ { "authority_repository": "https://github.com/solpbc/solstone-journal", - "authority_commit": "766021cd44d4a0a7ce471d2affb461bf3ce0fc39", - "bundle_version": "8.0.0", + "authority_commit": "b819bd840765a77322f4fc69f92e593e8c59b8ca", + "bundle_version": "9.0.0", "manifest_path": "manifest.json", - "manifest_sha256": "9b3bcd6b7f8a83adb9007e32501af44403cb93cfb8d80f256b6a7b5b9f93057e", + "manifest_sha256": "93b2a5a1604f1ba6fad30624c00cac98ea3d04a80cb1718886cf665c16f58834", "vendored_root": "vendor/observer-client-contract" } diff --git a/crates/solstone-linux/src/observer_contract_tests.rs b/crates/solstone-linux/src/observer_contract_tests.rs index 27840de..068abb0 100644 --- a/crates/solstone-linux/src/observer_contract_tests.rs +++ b/crates/solstone-linux/src/observer_contract_tests.rs @@ -4,10 +4,8 @@ use crate::{ config::Config, private_link::{PrivateLinkOwner, start_registered_private_link_for_test}, - private_link_test_peer::{PeerRequest, PrivateLinkPeer}, - sync::{contract_segment_proven_held, contract_sha256_file}, - sync_health::ErrorType, - upload::{ListingEntry, UploadClient, contract_parse_listing}, + private_link_test_peer::PrivateLinkPeer, + upload::UploadClient, }; use serde_json::{Value, json}; use sha2::{Digest, Sha256}; @@ -19,111 +17,23 @@ use std::{ }; use tempfile::TempDir; -const MANIFEST_SHA256: &str = "9b3bcd6b7f8a83adb9007e32501af44403cb93cfb8d80f256b6a7b5b9f93057e"; -const AUTHORITY_COMMIT: &str = "766021cd44d4a0a7ce471d2affb461bf3ce0fc39"; - -const FULL_FIXTURES: &[&str] = &[ - "declared.observer.ingestSegments.custody_unknown_rejected", - "declared.observer.ingestSegments.envelope_total_mismatch", - "declared.observer.ingestUpload.status_unknown_rejected", - "example.callosum.rootEvents.response.200.text-event-stream.default", - "example.link.pair.request.body.application-json.default", - "example.link.pair.response.200.application-json.default", - "example.observer.callosumStream.response.200.text-event-stream.default", - "example.observer.ingestEvent.request.body.application-json.default", - "example.observer.ingestEvent.response.200.application-json.default", - "example.observer.ingestSegments.response.200.application-json.legacy", - "example.observer.ingestSegments.response.200.application-json.v2", - "example.observer.ingestUpload.request.body.multipart-form-data.default", - "example.observer.ingestUpload.response.200.application-json.duplicate", - "example.observer.ingestUpload.response.200.application-json.normal", - "example.observer.register.request.body.application-json.default", - "example.observer.register.response.200.application-json.default", - "recorded.auth.bearer.segments", - "recorded.auth.handle.segments", - "recorded.ingestUpload.collision", - "recorded.ingestUpload.conflict", - "recorded.ingestUpload.duplicate", - "recorded.ingestUpload.failed", - "recorded.ingestUpload.ok", - "recorded.segments.custody_statuses", - "recorded.segments.legacy.absent_header", - "recorded.segments.legacy.unparseable_header", - "recorded.segments.submitted_name_omitted", - "recorded.segments.v2.envelope", - "recorded.sse.observer.data", - "recorded.sse.observer.error", - "recorded.sse.observer.heartbeat", - "recorded.sse.root.data_unknown_event", - "recorded.sse.root.heartbeat", -]; +const MANIFEST_SHA256: &str = "93b2a5a1604f1ba6fad30624c00cac98ea3d04a80cb1718886cf665c16f58834"; +const AUTHORITY_COMMIT: &str = "b819bd840765a77322f4fc69f92e593e8c59b8ca"; const LINUX_FIXTURES: &[&str] = &[ - "declared.observer.ingestSegments.custody_unknown_rejected", - "declared.observer.ingestSegments.envelope_total_mismatch", - "declared.observer.ingestUpload.status_unknown_rejected", - "example.observer.ingestEvent.request.body.application-json.default", - "example.observer.ingestEvent.response.200.application-json.default", - "example.observer.ingestSegments.response.200.application-json.legacy", - "example.observer.ingestSegments.response.200.application-json.v2", - "example.observer.ingestUpload.request.body.multipart-form-data.default", - "example.observer.ingestUpload.response.200.application-json.duplicate", - "example.observer.ingestUpload.response.200.application-json.normal", - "example.observer.register.request.body.application-json.default", - "example.observer.register.response.200.application-json.default", - "recorded.auth.bearer.segments", - "recorded.auth.handle.segments", - "recorded.ingestUpload.collision", - "recorded.ingestUpload.conflict", - "recorded.ingestUpload.duplicate", - "recorded.ingestUpload.failed", - "recorded.ingestUpload.ok", - "recorded.segments.custody_statuses", - "recorded.segments.legacy.absent_header", - "recorded.segments.legacy.unparseable_header", - "recorded.segments.submitted_name_omitted", - "recorded.segments.v2.envelope", -]; - -const FULL_VECTORS: &[&str] = &[ - "callosum.rootEvents.sse.data_unknown_event", - "callosum.rootEvents.sse.heartbeat", - "observer.auth.bearer", - "observer.auth.handle", - "observer.callosumStream.sse.data", - "observer.callosumStream.sse.error", - "observer.callosumStream.sse.heartbeat", - "observer.ingestSegments.custody_statuses", - "observer.ingestSegments.custody_unknown_rejected", - "observer.ingestSegments.envelope_total_mismatch", - "observer.ingestSegments.legacy_array.absent_header", - "observer.ingestSegments.legacy_array.unparseable_header", - "observer.ingestSegments.submitted_name_fallback", - "observer.ingestSegments.v2_envelope", - "observer.ingestUpload.status.collision", - "observer.ingestUpload.status.conflict", - "observer.ingestUpload.status.duplicate", - "observer.ingestUpload.status.failed", - "observer.ingestUpload.status.ok", - "observer.ingestUpload.status_unknown_rejected", + "declared.observer.ingestUpload.status.collision", + "declared.observer.ingestUpload.status.conflict", + "declared.observer.ingestUpload.status.duplicate", + "declared.observer.ingestUpload.status.failed", + "declared.observer.ingestUpload.status.ok", ]; const LINUX_VECTORS: &[&str] = &[ - "observer.auth.bearer", - "observer.auth.handle", - "observer.ingestSegments.custody_statuses", - "observer.ingestSegments.custody_unknown_rejected", - "observer.ingestSegments.envelope_total_mismatch", - "observer.ingestSegments.legacy_array.absent_header", - "observer.ingestSegments.legacy_array.unparseable_header", - "observer.ingestSegments.submitted_name_fallback", - "observer.ingestSegments.v2_envelope", "observer.ingestUpload.status.collision", "observer.ingestUpload.status.conflict", "observer.ingestUpload.status.duplicate", "observer.ingestUpload.status.failed", "observer.ingestUpload.status.ok", - "observer.ingestUpload.status_unknown_rejected", ]; fn workspace_root() -> PathBuf { @@ -279,8 +189,11 @@ fn verify_bundle(root: &Path, expected_manifest_digest: &str) -> Result BTreeMap { - let body: Value = serde_json::from_slice(&fs::read(path).unwrap()).unwrap(); +fn load_document(path: &Path) -> Value { + serde_json::from_slice(&fs::read(path).unwrap()).unwrap() +} + +fn load_index(body: &Value, key: &str) -> BTreeMap { let mut indexed = BTreeMap::new(); for item in body[key].as_array().unwrap() { let id = item["id"].as_str().unwrap().to_owned(); @@ -296,24 +209,37 @@ fn set(values: &[&str]) -> BTreeSet { values.iter().map(|value| (*value).to_owned()).collect() } -fn record(executed: &mut BTreeSet, id: &str, passed: bool) { - assert!(passed, "production-path assertion failed for {id}"); +fn record(executed: &mut BTreeSet, id: &str) { assert!( executed.insert(id.to_owned()), "duplicate coverage for {id}" ); } +fn vocabulary<'a>(manifest: &'a Value, id: &str) -> &'a Value { + manifest["vocabularies"] + .as_array() + .unwrap() + .iter() + .find(|vocabulary| vocabulary["id"] == id) + .unwrap_or_else(|| panic!("missing vocabulary {id}")) +} + fn assert_identities( manifest: &Value, fixtures: &BTreeMap, vectors: &BTreeMap, + fixture_document: &Value, + vector_document: &Value, + consumer_audit: &Value, ) { - assert_eq!(manifest["bundle_semver"], "8.0.0"); + assert_eq!(manifest["bundle_semver"], "9.0.0"); assert_eq!(manifest["openapi_document_version"], "1.0.0"); + assert_eq!(manifest["observer_protocol_version"], 3); + assert_eq!(manifest["supported_response_variants"], json!([3])); assert_eq!( manifest["generator_identity"], - "solstone.convey.contract.observer_bundle.v1" + "solstone.repository_contracts.observer_client_contract_bundle.v1" ); assert_eq!( manifest["schema_dialect_uri"], @@ -323,53 +249,109 @@ fn assert_identities( manifest["bundle_schema_identity"], "solstone.observer-client-contract-bundle.schema.v1" ); - assert_eq!(manifest["observer_protocol_version"], 2); - assert_eq!(manifest["supported_response_variants"], json!([1, 2])); assert_eq!( - manifest["consumer_identifiers"], + manifest["operation_ids"], json!([ - "solstone-android", - "solstone-browser", - "solstone-linux", - "solstone-macos", - "solstone-swift", - "solstone-tmux", - "solstone-windows" + "observer.ingestUpload", + "observer.ingestManifest", + "observer.ingestManifestDay", + "observer.ingestSegments" ]) ); - let targets: BTreeSet<_> = manifest["windows_linux_rollout_targets"] + assert_eq!( + manifest["consumer_identifiers"], + json!(["solstone-browser", "solstone-linux", "solstone-windows"]) + ); + assert_eq!( + manifest["component_closure"], + json!(["Error", "SegmentFile", "SegmentItem", "SegmentsEnvelope"]) + ); + assert_eq!( + fixture_document["schema"], + "solstone.observer-client-contract-fixtures.v2" + ); + assert_eq!( + vector_document["schema"], + "solstone.observer-client-contract-vectors.v2" + ); + assert_eq!( + consumer_audit["schema"], + "solstone.observer-client-contract-consumer-audit.v2" + ); + + assert_eq!( + vocabulary(manifest, "SegmentFile.status"), + &json!({ + "classification":"closed", + "id":"SegmentFile.status", + "source_pointer":"/components/schemas/SegmentFile/properties/status", + "unknown_value_behavior":"reject", + "values":["present","missing","processed"] + }) + ); + assert_eq!( + vocabulary(manifest, "observer.ingestUpload.status"), + &json!({ + "classification":"closed", + "id":"observer.ingestUpload.status", + "source_pointers":[ + "/paths/~1app~1devices~1ingest/post/responses/200/content/application~1json/schema/properties/status", + "/paths/~1app~1devices~1ingest/post/responses/409" + ], + "unknown_value_behavior":"reject", + "values":["ok","duplicate","collision","conflict","failed"] + }) + ); + + let linux_target = manifest["windows_linux_rollout_targets"] .as_array() .unwrap() .iter() - .map(|item| item["consumer_identifier"].as_str().unwrap()) - .collect(); + .find(|target| target["consumer_identifier"] == "solstone-linux") + .unwrap(); assert_eq!( - targets, - BTreeSet::from(["solstone-linux", "solstone-windows"]) + linux_target["adoption_blocker_ids"], + json!(["solstone-linux-legacy-v2-unmigrated"]) ); + let linux_revision = manifest["audited_consumer_revisions"] + .as_array() + .unwrap() + .iter() + .find(|revision| revision["consumer_identifier"] == "solstone-linux") + .unwrap(); assert_eq!( - manifest["operation_ids"], - json!([ - "callosum.rootEvents", - "link.pair", - "observer.callosumStream", - "observer.ingestEvent", - "observer.ingestSegments", - "observer.ingestUpload", - "observer.register" - ]) + linux_revision["revision"], + "1c679db1ce6f9a65db70c5aae0ca2fad677416ef" + ); + let linux_audit = consumer_audit["audited_commits"] + .as_array() + .unwrap() + .iter() + .find(|commit| commit["consumer"] == "solstone-linux") + .unwrap(); + assert_eq!( + linux_audit["commit"], + "1c679db1ce6f9a65db70c5aae0ca2fad677416ef" ); + assert_eq!( fixtures.keys().cloned().collect::>(), - set(FULL_FIXTURES) + set(LINUX_FIXTURES) ); assert_eq!( vectors.keys().cloned().collect::>(), - set(FULL_VECTORS) + set(LINUX_VECTORS) ); for vector in vectors.values() { assert!(fixtures.contains_key(vector["fixture_id"].as_str().unwrap())); } + assert_eq!( + fixtures["declared.observer.ingestUpload.status.failed"]["schema_validation"], + json!({ + "note":"vocabulary-only status value; the full Error payload requires error, reason_code, and detail, and the authority enumerates no HTTP 500 reason code", + "valid":false + }) + ); } fn verify_provenance(root: &Path) -> Result<(), String> { @@ -378,7 +360,7 @@ fn verify_provenance(root: &Path) -> Result<(), String> { let expected = json!({ "authority_repository":"https://github.com/solpbc/solstone-journal", "authority_commit":AUTHORITY_COMMIT, - "bundle_version":"8.0.0", + "bundle_version":"9.0.0", "manifest_path":"manifest.json", "manifest_sha256":MANIFEST_SHA256, "vendored_root":"vendor/observer-client-contract" @@ -389,6 +371,107 @@ fn verify_provenance(root: &Path) -> Result<(), String> { Ok(()) } +fn assert_protocol_three_parameter(operation: &Value) { + let parameter = operation["parameters"] + .as_array() + .unwrap() + .iter() + .find(|parameter| parameter["name"] == "X-Solstone-Protocol-Version") + .expect("required protocol parameter"); + assert_eq!(parameter["in"], "header"); + assert_eq!(parameter["required"], true); + assert_eq!(parameter["schema"], json!({"const":3,"type":"integer"})); + assert!( + parameter["description"] + .as_str() + .is_some_and(|description| { + description.contains( + "Identity is the linked-device mTLS certificate, not a request header.", + ) + }) + ); +} + +fn assert_read_projection(projection: &Value) { + let paths = &projection["paths"]; + let manifest = &paths["/app/devices/ingest/manifest"]["get"]; + let day_manifest = &paths["/app/devices/ingest/manifest/{day}"]["get"]; + let segments = &paths["/app/devices/ingest/segments/{day}"]["get"]; + for (route, operation, id) in [ + ( + "/app/devices/ingest/manifest", + manifest, + "observer.ingestManifest", + ), + ( + "/app/devices/ingest/manifest/{day}", + day_manifest, + "observer.ingestManifestDay", + ), + ( + "/app/devices/ingest/segments/{day}", + segments, + "observer.ingestSegments", + ), + ] { + let methods: Vec<_> = paths[route] + .as_object() + .unwrap() + .keys() + .map(String::as_str) + .collect(); + assert_eq!(methods, ["get"]); + assert_eq!(operation["operationId"], id); + assert_protocol_three_parameter(operation); + } + assert_eq!( + manifest["responses"]["200"]["content"]["application/json"]["schema"]["required"], + json!(["days"]) + ); + let day_schema = &day_manifest["responses"]["200"]["content"]["application/json"]["schema"]; + assert_eq!( + day_schema["required"], + json!(["version", "day", "segments"]) + ); + assert_eq!(day_schema["properties"]["version"]["type"], "integer"); + assert!(day_schema["properties"]["version"].get("const").is_none()); + assert!(day_schema["properties"]["version"].get("enum").is_none()); + assert_eq!( + segments["responses"]["200"]["content"]["application/json"]["schema"], + json!({"$ref":"#/components/schemas/SegmentsEnvelope"}) + ); + + let schemas = &projection["components"]["schemas"]; + assert_eq!( + schemas["SegmentsEnvelope"]["required"], + json!(["items", "total", "protocol_version"]) + ); + assert_eq!( + schemas["SegmentsEnvelope"]["properties"]["items"]["items"], + json!({"$ref":"#/components/schemas/SegmentItem"}) + ); + assert_eq!( + schemas["SegmentItem"]["required"], + json!(["key", "observed", "files"]) + ); + assert_eq!( + schemas["SegmentItem"]["properties"]["files"]["items"], + json!({"$ref":"#/components/schemas/SegmentFile"}) + ); + assert_eq!( + schemas["SegmentFile"]["required"], + json!(["name", "size", "sha256", "status"]) + ); + assert_eq!( + schemas["SegmentFile"]["properties"]["size"]["type"], + "integer" + ); + assert_eq!( + schemas["SegmentFile"]["properties"]["status"]["enum"], + json!(["present", "missing", "processed"]) + ); +} + fn config(temp: &TempDir) -> Config { Config { stream: "desktop".into(), @@ -441,450 +524,65 @@ impl LinkedHarness { } } -fn header<'a>(request: &'a PeerRequest, name: &str) -> Option<&'a str> { - request - .headers - .iter() - .find(|(candidate, _)| candidate.eq_ignore_ascii_case(name)) - .map(|(_, value)| value.as_str()) -} - -async fn assert_upload_contract( - fixtures: &BTreeMap, - vectors: &BTreeMap, - executed_fixtures: &mut BTreeSet, - executed_vectors: &mut BTreeSet, -) { - let cases = [ - ( - "recorded.ingestUpload.ok", - "observer.ingestUpload.status.ok", - ), - ( - "recorded.ingestUpload.collision", - "observer.ingestUpload.status.collision", - ), - ( - "recorded.ingestUpload.duplicate", - "observer.ingestUpload.status.duplicate", - ), - ( - "recorded.ingestUpload.conflict", - "observer.ingestUpload.status.conflict", - ), - ( - "recorded.ingestUpload.failed", - "observer.ingestUpload.status.failed", - ), - ( - "declared.observer.ingestUpload.status_unknown_rejected", - "observer.ingestUpload.status_unknown_rejected", - ), - ]; - for (fixture_id, vector_id) in cases { - let fixture = &fixtures[fixture_id]; - let vector = &vectors[vector_id]; - assert_eq!(vector["fixture_id"], fixture_id); - let status = vector["observed_status"] - .as_u64() - .or_else(|| fixture["provenance"]["status"].as_u64()) - .expect("authority observed HTTP status") as u16; - let temp = tempfile::tempdir().unwrap(); - let media = temp.path().join("audio.flac"); - fs::write(&media, b"audio").unwrap(); - let harness = LinkedHarness::start(&temp).await; - harness - .peer - .enqueue_response(status, fixture["payload"].to_string()); - let result = harness - .client - .upload_segment("20260618", "143022_300", &[media]) - .await; - let accepted = vector["decision"]["accepted"].as_bool().unwrap_or_else(|| { - assert_eq!(vector["decision"]["unknown_value_behavior"], "reject"); - false - }); - assert_eq!(result.success, accepted); - if accepted { - let duplicate = vector["decision"]["stored_key_source"] == "existing_segment"; - assert_eq!(result.duplicate, duplicate); - let source = vector["decision"]["stored_key_source"].as_str().unwrap(); - assert_eq!( - result.stored_key.as_deref(), - fixture["payload"][source].as_str() - ); - } else if status == 200 { - assert_eq!(result.error_type, Some(ErrorType::Incompatible)); - assert_eq!(harness.peer.requests().len(), 1); - } else { - assert!(!result.success && result.stored_key.is_none()); - } - record(executed_fixtures, fixture_id, true); - record(executed_vectors, vector_id, true); - harness.finish().await; - } - for fixture_id in [ - "example.observer.ingestUpload.response.200.application-json.normal", - "example.observer.ingestUpload.response.200.application-json.duplicate", - ] { - let temp = tempfile::tempdir().unwrap(); - let media = temp.path().join("audio.flac"); - fs::write(&media, b"audio").unwrap(); - let harness = LinkedHarness::start(&temp).await; - harness - .peer - .enqueue_response(200, fixtures[fixture_id]["payload"].to_string()); - assert!( - harness - .client - .upload_segment("20260618", "143022_300", &[media]) - .await - .success - ); - record(executed_fixtures, fixture_id, true); - harness.finish().await; - } - let fixture_id = "example.observer.ingestUpload.request.body.multipart-form-data.default"; +async fn upload_fixture_result(fixture: &Value) -> crate::upload::UploadResult { + let status = fixture["provenance"]["http_status"] + .as_u64() + .expect("fixture HTTP status") as u16; + let payload = fixture["payload"].to_string(); let temp = tempfile::tempdir().unwrap(); - let fixture_files = fixtures[fixture_id]["payload"]["files"] - .as_array() - .expect("multipart fixture files"); - let media: Vec<_> = fixture_files - .iter() - .map(|name| { - let path = temp.path().join(name.as_str().expect("fixture filename")); - fs::write(&path, format!("fixture bytes for {}", path.display())).unwrap(); - path - }) - .collect(); + let media = temp.path().join("audio.flac"); + fs::write(&media, b"audio").unwrap(); let harness = LinkedHarness::start(&temp).await; - harness.peer.enqueue_response( - 200, - json!({"status":"ok","segment":"143022_300"}).to_string(), - ); - harness + harness.peer.enqueue_response(status, payload); + let result = harness .client - .upload_segment( - fixtures[fixture_id]["payload"]["day"].as_str().unwrap(), - fixtures[fixture_id]["payload"]["segment"].as_str().unwrap(), - &media, - ) + .upload_segment("20260618", "143022_300", &[media]) .await; - let requests = harness.peer.requests(); - let request = &requests[0]; - let body = String::from_utf8_lossy(&request.body); - assert_eq!(request.method, "POST"); - assert_eq!(request.path, "/app/devices/ingest"); - assert!(header(request, "authorization").is_some_and(|value| value.starts_with("Bearer "))); - assert_eq!(body.matches("name=\"day\"").count(), 1); - assert_eq!(body.matches("name=\"segment\"").count(), 1); - assert_eq!(body.matches("name=\"files\"").count(), fixture_files.len()); - assert!(body.contains("\r\n\r\n20260618\r\n")); - assert!(body.contains("\r\n\r\n143022_300\r\n")); - for name in fixture_files { - let name = name.as_str().unwrap(); - assert!(body.contains(&format!("name=\"files\"; filename=\"{name}\""))); - let content_type = if name.ends_with(".flac") { - "audio/flac" - } else { - "application/octet-stream" - }; - assert!(body.contains(&format!( - "filename=\"{name}\"\r\nContent-Type: {content_type}" - ))); - } - for forbidden in ["name=\"host\"", "name=\"meta\"", "name=\"platform\""] { - assert!(!body.contains(forbidden)); - } - record(executed_fixtures, fixture_id, true); harness.finish().await; + result +} + +fn assert_upload_success_matches_decision(success: bool, decision: &Value) { + assert_eq!(success, decision["accepted"].as_bool().unwrap()); } -async fn assert_listing_contract( +async fn assert_upload_contract( fixtures: &BTreeMap, vectors: &BTreeMap, executed_fixtures: &mut BTreeSet, executed_vectors: &mut BTreeSet, ) { - let cases = [ - ( - "example.observer.ingestSegments.response.200.application-json.legacy", - None, - ), - ( - "example.observer.ingestSegments.response.200.application-json.v2", - None, - ), - ( - "recorded.segments.legacy.absent_header", - Some("observer.ingestSegments.legacy_array.absent_header"), - ), - ( - "recorded.segments.legacy.unparseable_header", - Some("observer.ingestSegments.legacy_array.unparseable_header"), - ), - ( - "recorded.segments.v2.envelope", - Some("observer.ingestSegments.v2_envelope"), - ), - ( - "declared.observer.ingestSegments.envelope_total_mismatch", - Some("observer.ingestSegments.envelope_total_mismatch"), - ), - ( - "recorded.auth.bearer.segments", - Some("observer.auth.bearer"), - ), - ( - "recorded.auth.handle.segments", - Some("observer.auth.handle"), - ), - ]; - for (fixture_id, vector_id) in cases { - let payload = &fixtures[fixture_id]["payload"]; - let (legacy, truncated) = if let Some(vector_id) = vector_id { - let vector = &vectors[vector_id]; - assert_eq!(vector["fixture_id"], fixture_id); - let decision = &vector["decision"]; - let legacy = decision["response_variant"] - .as_str() - .map_or_else(|| payload.is_array(), |variant| variant == "legacy_array"); - let truncated = decision["valid"].as_bool().map_or_else( - || { - payload["total"].as_u64().is_some_and(|total| { - total != payload["items"].as_array().expect("listing items").len() as u64 - }) - }, - |valid| !valid, - ); - (legacy, truncated) - } else { - ( - payload.is_array(), - payload["total"].as_u64().is_some_and(|total| { - total != payload["items"].as_array().expect("listing items").len() as u64 - }), - ) - }; - let temp = tempfile::tempdir().unwrap(); - let harness = LinkedHarness::start(&temp).await; - let response_headers = vector_id - .and_then(|id| vectors[id]["decision"]["header"].as_str()) - .map_or_else( - || vec![("x-solstone-protocol-version".to_owned(), "2".to_owned())], - |header| match header { - "absent" => Vec::new(), - "unparseable" => vec![( - "x-solstone-protocol-version".to_owned(), - "unparseable".to_owned(), - )], - value => vec![("x-solstone-protocol-version".to_owned(), value.to_owned())], - }, - ); - harness - .peer - .enqueue_response_with_headers(200, response_headers, payload.to_string()); - let result = harness.client.get_server_segments("20260618").await; + for fixture_id in LINUX_FIXTURES { + let fixture = &fixtures[*fixture_id]; + let status = fixture["payload"]["status"].as_str().unwrap(); + let vector_id = format!("observer.ingestUpload.status.{status}"); + let vector = &vectors[&vector_id]; + let decision = &vector["decision"]; + assert_eq!(vector["fixture_id"], *fixture_id); + assert_eq!(fixture["kind"], "declared"); assert_eq!( - (result.legacy, result.truncated), - (legacy, truncated), - "{fixture_id}" + fixture["provenance"]["vocabulary"], + "observer.ingestUpload.status" ); - let requests = harness.peer.requests(); - let request = &requests[0]; - assert_eq!(request.method, "GET"); - assert_eq!(request.path, "/app/devices/ingest/segments/20260618"); - assert_eq!(header(request, "x-solstone-protocol-version"), Some("2")); - let authorization = header(request, "authorization"); - assert!(authorization.is_some_and(|value| value.starts_with("Bearer "))); - record(executed_fixtures, fixture_id, true); - if let Some(vector_id) = vector_id { - let decision = &vectors[vector_id]["decision"]; - if vector_id.starts_with("observer.auth.") { - assert!( - decision["accepted"] - .as_bool() - .expect("authority auth accepted") - ); - let form = decision["auth_form"].as_str().expect("authority auth form"); - assert!(matches!( - form, - "authorization_bearer" | "x_solstone_observer" - )); - assert!( - vectors["observer.auth.bearer"]["decision"]["accepted"] - .as_bool() - .expect("authority bearer accepted") - ); - assert_eq!( - vectors["observer.auth.bearer"]["decision"]["auth_form"], - "authorization_bearer" - ); - } - record(executed_vectors, vector_id, true); - } - harness.finish().await; - } - - let custody_cases = [ - ( - "recorded.segments.custody_statuses", - "observer.ingestSegments.custody_statuses", - ), - ( - "declared.observer.ingestSegments.custody_unknown_rejected", - "observer.ingestSegments.custody_unknown_rejected", - ), - ( - "recorded.segments.submitted_name_omitted", - "observer.ingestSegments.submitted_name_fallback", - ), - ]; - for (fixture_id, vector_id) in custody_cases { - let query = contract_parse_listing(fixtures[fixture_id]["payload"].clone(), 200); - let entries = query.segments.expect("fixture listing entries"); - let vector = &vectors[vector_id]; - assert_eq!(vector["fixture_id"], fixture_id); - let decision = &vector["decision"]; - let temp = tempfile::tempdir().unwrap(); - let mut asserted = false; - for entry in entries { - for (index, remote) in entry - .files - .expect("fixture listing files") - .into_iter() - .enumerate() - { - let segment = temp.path().join(format!("segment-{index}")); - fs::create_dir(&segment).unwrap(); - let filename = remote.name.as_deref().expect("fixture remote name"); - let local = segment.join(filename); - fs::write(&local, format!("fixture custody bytes {filename}")).unwrap(); - let mut matching = remote.clone(); - matching.sha256 = Some(contract_sha256_file(&local).unwrap()); - let candidate = ListingEntry { - key: entry.key.clone(), - original_key: entry.original_key.clone(), - files: Some(vec![matching.clone()]), - }; - let status = matching.status.as_deref().expect("fixture custody status"); - let expected = if decision["fallback"] == "name" { - true - } else if let Some(holding) = decision["holding_by_status"][status].as_str() { - holding == "held" - } else { - assert_eq!(decision["unknown_status"], "reject"); - false - }; - assert_eq!(contract_segment_proven_held(&segment, &candidate), expected); - asserted = true; - - if decision["fallback"] == "name" { - assert!( - !decision["submitted_name_present"] - .as_bool() - .expect("authority submitted_name_present") - ); - assert!(contract_segment_proven_held(&segment, &candidate)); - matching.submitted_name = Some("different.flac".to_owned()); - let precedence = ListingEntry { - files: Some(vec![matching.clone()]), - ..candidate.clone() - }; - assert!(!contract_segment_proven_held(&segment, &precedence)); - matching.submitted_name = matching.name.clone(); - let precedence = ListingEntry { - files: Some(vec![matching]), - ..candidate - }; - assert!(contract_segment_proven_held(&segment, &precedence)); - } - } + assert_eq!( + fixture["provenance"]["http_status"], + decision["http_status"] + ); + assert_eq!(decision["kind"], "ingest_status"); + assert_eq!(decision["status"], status); + assert_eq!(vector["pointers"], json!(["/status"])); + let result = upload_fixture_result(fixture).await; + assert_upload_success_matches_decision(result.success, decision); + if decision["accepted"] == true { + assert_eq!(result.duplicate, status == "duplicate"); + } else { + assert!(!result.success); } - record(executed_fixtures, fixture_id, asserted); - record(executed_vectors, vector_id, asserted); + record(executed_fixtures, fixture_id); + record(executed_vectors, &vector_id); } } -async fn assert_event_and_register( - fixtures: &BTreeMap, - executed: &mut BTreeSet, -) { - let temp = tempfile::tempdir().unwrap(); - let event_id = "example.observer.ingestEvent.request.body.application-json.default"; - let harness = LinkedHarness::start(&temp).await; - harness.peer.enqueue_response( - 200, - fixtures["example.observer.ingestEvent.response.200.application-json.default"]["payload"] - .to_string(), - ); - let payload = fixtures[event_id]["payload"].as_object().unwrap(); - let mut fields = payload.clone(); - let tract = fields.remove("tract").unwrap(); - let event = fields.remove("event").unwrap(); - assert!( - harness - .client - .relay_event(tract.as_str().unwrap(), event.as_str().unwrap(), fields) - .await - ); - let requests = harness.peer.requests(); - let request = &requests[0]; - assert_eq!( - (request.method.as_str(), request.path.as_str()), - ("POST", "/app/devices/ingest/event") - ); - assert_eq!(header(request, "content-type"), Some("application/json")); - assert_eq!( - request.body, - br#"{"event":"status","state":"recording","tract":"observe"}"# - ); - assert_eq!( - serde_json::from_slice::(&request.body).unwrap(), - fixtures[event_id]["payload"] - ); - record(executed, event_id, true); - record( - executed, - "example.observer.ingestEvent.response.200.application-json.default", - true, - ); - harness.finish().await; - let register_request = "example.observer.register.request.body.application-json.default"; - let register_response = "example.observer.register.response.200.application-json.default"; - let peer = PrivateLinkPeer::start().await; - peer.enqueue_response(200, fixtures[register_response]["payload"].to_string()); - let label = fixtures[register_request]["payload"]["label"] - .as_str() - .unwrap(); - let (_state, owner) = start_registered_private_link_for_test( - peer.credential(), - label, - "K", - "/app/devices/ingest", - ) - .await; - assert!(matches!( - owner - .register_for_test(&fixtures[register_request]["payload"]) - .await, - crate::private_link::LinkOutcome::Success { .. } - )); - let requests = peer.requests(); - let request = &requests[0]; - let body: Value = serde_json::from_slice(&request.body).unwrap(); - assert_eq!( - (request.method.as_str(), request.path.as_str()), - ("POST", "/app/devices/register") - ); - assert_eq!(body, fixtures[register_request]["payload"]); - assert!(header(request, "authorization").is_none()); - record(executed, register_request, true); - record(executed, register_response, true); - owner.shutdown().await.unwrap(); - peer.shutdown().await; -} - fn copy_tree(source: &Path, destination: &Path) { fs::create_dir_all(destination).unwrap(); for entry in fs::read_dir(source).unwrap() { @@ -898,12 +596,16 @@ fn copy_tree(source: &Path, destination: &Path) { } } +#[allow(clippy::too_many_arguments)] fn assert_mutations( bundle: &Path, provenance: &Path, manifest: &Value, fixtures: &BTreeMap, vectors: &BTreeMap, + fixture_document: &Value, + vector_document: &Value, + consumer_audit: &Value, ) { let temp = tempfile::tempdir().unwrap(); let copy = temp.path().join("bundle"); @@ -974,30 +676,67 @@ fn assert_mutations( .unwrap() .pop(); assert!( - std::panic::catch_unwind(|| assert_identities(&missing_operation, fixtures, vectors)) - .is_err() + std::panic::catch_unwind(|| { + assert_identities( + &missing_operation, + fixtures, + vectors, + fixture_document, + vector_document, + consumer_audit, + ) + }) + .is_err() ); for (field, value) in [ ("bundle_semver", json!("9.9.9")), - ("observer_protocol_version", json!(1)), + ("observer_protocol_version", json!(2)), ] { let mut mutated = manifest.clone(); mutated[field] = value; assert!( - std::panic::catch_unwind(|| assert_identities(&mutated, fixtures, vectors)).is_err() + std::panic::catch_unwind(|| { + assert_identities( + &mutated, + fixtures, + vectors, + fixture_document, + vector_document, + consumer_audit, + ) + }) + .is_err() ); } let mut missing_fixture = fixtures.clone(); missing_fixture.pop_first(); assert!( - std::panic::catch_unwind(|| assert_identities(manifest, &missing_fixture, vectors)) - .is_err() + std::panic::catch_unwind(|| { + assert_identities( + manifest, + &missing_fixture, + vectors, + fixture_document, + vector_document, + consumer_audit, + ) + }) + .is_err() ); let mut missing_vector = vectors.clone(); missing_vector.pop_first(); assert!( - std::panic::catch_unwind(|| assert_identities(manifest, fixtures, &missing_vector)) - .is_err() + std::panic::catch_unwind(|| { + assert_identities( + manifest, + fixtures, + &missing_vector, + fixture_document, + vector_document, + consumer_audit, + ) + }) + .is_err() ); } @@ -1005,31 +744,16 @@ async fn assert_production_contradiction_mutation( fixtures: &BTreeMap, vectors: &BTreeMap, ) { - let fixture_id = "recorded.ingestUpload.ok"; - let vector_id = "observer.ingestUpload.status.ok"; - let mut payload = fixtures[fixture_id]["payload"].clone(); - payload["status"] = json!("duplicate"); - payload["existing_segment"] = json!("mutated-existing-segment"); - let status = vectors[vector_id]["observed_status"] - .as_u64() - .expect("authority observed status") as u16; - let temp = tempfile::tempdir().unwrap(); - let media = temp.path().join("audio.flac"); - fs::write(&media, b"audio").unwrap(); - let harness = LinkedHarness::start(&temp).await; - harness.peer.enqueue_response(status, payload.to_string()); - let result = harness - .client - .upload_segment("20260618", "143022_300", &[media]) - .await; - let decision = &vectors[vector_id]["decision"]; - let expected_duplicate = decision["stored_key_source"] == "existing_segment"; - let expected_key = fixtures[fixture_id]["payload"][decision["stored_key_source"] - .as_str() - .expect("stored key source")] - .as_str(); - assert!(result.duplicate != expected_duplicate || result.stored_key.as_deref() != expected_key); - harness.finish().await; + let fixture = &fixtures["declared.observer.ingestUpload.status.ok"]; + let mut mutated_vector = vectors["observer.ingestUpload.status.ok"].clone(); + mutated_vector["decision"]["accepted"] = json!(false); + let result = upload_fixture_result(fixture).await; + assert!( + std::panic::catch_unwind(|| { + assert_upload_success_matches_decision(result.success, &mutated_vector["decision"]) + }) + .is_err() + ); } #[tokio::test] @@ -1038,11 +762,23 @@ async fn observer_contract_conformance() { let bundle = root.join("vendor/observer-client-contract"); let manifest = verify_bundle(&bundle, MANIFEST_SHA256).unwrap(); verify_provenance(&root.join("contracts/observer-client-import.json")).unwrap(); - let fixtures = load_index(&bundle.join("fixtures/wire-behavior.json"), "fixtures"); - let vectors = load_index(&bundle.join("vectors.json"), "vectors"); - assert_identities(&manifest, &fixtures, &vectors); - assert_eq!(set(LINUX_FIXTURES).len(), 24); - assert_eq!(set(LINUX_VECTORS).len(), 15); + let projection = load_document(&bundle.join("projection.openapi.json")); + let fixture_document = load_document(&bundle.join("fixtures/wire-behavior.json")); + let vector_document = load_document(&bundle.join("vectors.json")); + let consumer_audit = load_document(&bundle.join("consumer-audit.json")); + let fixtures = load_index(&fixture_document, "fixtures"); + let vectors = load_index(&vector_document, "vectors"); + assert_identities( + &manifest, + &fixtures, + &vectors, + &fixture_document, + &vector_document, + &consumer_audit, + ); + assert_read_projection(&projection); + assert_eq!(set(LINUX_FIXTURES).len(), 5); + assert_eq!(set(LINUX_VECTORS).len(), 5); let mut executed_fixtures = BTreeSet::new(); let mut executed_vectors = BTreeSet::new(); assert_upload_contract( @@ -1052,20 +788,15 @@ async fn observer_contract_conformance() { &mut executed_vectors, ) .await; - assert_listing_contract( - &fixtures, - &vectors, - &mut executed_fixtures, - &mut executed_vectors, - ) - .await; - assert_event_and_register(&fixtures, &mut executed_fixtures).await; assert_mutations( &bundle, &root.join("contracts/observer-client-import.json"), &manifest, &fixtures, &vectors, + &fixture_document, + &vector_document, + &consumer_audit, ); assert_production_contradiction_mutation(&fixtures, &vectors).await; assert_eq!(executed_fixtures, set(LINUX_FIXTURES)); diff --git a/crates/solstone-linux/src/toolchain_policy_tests.rs b/crates/solstone-linux/src/toolchain_policy_tests.rs index 6a760ff..87d09e8 100644 --- a/crates/solstone-linux/src/toolchain_policy_tests.rs +++ b/crates/solstone-linux/src/toolchain_policy_tests.rs @@ -761,7 +761,7 @@ fn observer_contract_gate_is_locked_offline_and_named() { assert!(target.contains("CARGO_NET_OFFLINE=true")); assert!(target.contains("$(CARGO_LOCKED) -p solstone-linux")); assert!(target.contains("observer_contract_tests::observer_contract_conformance")); - assert!(target.contains("8.0.0")); - assert!(target.contains("9b3bcd6b7f8a83adb9007e32501af44403cb93cfb8d80f256b6a7b5b9f93057e")); + assert!(target.contains("9.0.0")); + assert!(target.contains("93b2a5a1604f1ba6fad30624c00cac98ea3d04a80cb1718886cf665c16f58834")); assert!(makefile.contains("ci: rust-preflight check-cargo-deny check-observer-contract")); } diff --git a/vendor/observer-client-contract/consumer-audit.json b/vendor/observer-client-contract/consumer-audit.json index a80a272..9e35422 100644 --- a/vendor/observer-client-contract/consumer-audit.json +++ b/vendor/observer-client-contract/consumer-audit.json @@ -1,125 +1,104 @@ { "audited_commits": [ { - "commit": "19c972c4fea775176cea6421ac8b87f3bb20ab42", - "consumer": "solstone-windows" + "commit": "998c1095cd8f766dd188bece5ad6527444f8dfac", + "consumer": "solstone-browser" }, { "commit": "1c679db1ce6f9a65db70c5aae0ca2fad677416ef", "consumer": "solstone-linux" }, { - "commit": "998c1095cd8f766dd188bece5ad6527444f8dfac", - "consumer": "solstone-browser" + "commit": "19c972c4fea775176cea6421ac8b87f3bb20ab42", + "consumer": "solstone-windows" } ], "direct_paths": [ { - "classification": "bundled", + "classification": "legacy_v2_unmigrated", "consumer": "solstone-browser", - "path": "/app/observer/register", - "rationale": "Projected as observer.register for browser observer enrollment.", + "legacy_surface": "observer_v2_register", + "rationale": "Pinned revision calls a legacy v2 observer capability and is not verified against the linked-device v3 ingest surface.", "revision": "998c1095cd8f766dd188bece5ad6527444f8dfac", "source_files": [ "extension/journal.js" ] }, { - "classification": "bundled", + "classification": "legacy_v2_unmigrated", "consumer": "solstone-browser", - "path": "/app/observer/ingest", - "rationale": "Projected as observer.ingestUpload for browser upload.", + "legacy_surface": "observer_ingest_v2_upload", + "rationale": "Pinned revision calls a legacy v2 observer capability and is not verified against the linked-device v3 ingest surface.", "revision": "998c1095cd8f766dd188bece5ad6527444f8dfac", "source_files": [ "extension/journal.js" ] }, { - "classification": "bundled", + "classification": "legacy_v2_unmigrated", "consumer": "solstone-browser", - "path": "/app/observer/ingest/event", - "rationale": "Projected as observer.ingestEvent for browser event relay.", + "legacy_surface": "observer_ingest_v2_event", + "rationale": "Pinned revision calls a legacy v2 observer capability and is not verified against the linked-device v3 ingest surface.", "revision": "998c1095cd8f766dd188bece5ad6527444f8dfac", "source_files": [ "extension/journal.js" ] }, { - "classification": "bundled", + "classification": "legacy_v2_unmigrated", "consumer": "solstone-browser", - "path": "/app/observer/ingest/segments/{day}", - "rationale": "Projected as observer.ingestSegments; browser reconcile consumes the legacy v1 array variant.", + "legacy_surface": "observer_ingest_v2_segments", + "rationale": "Pinned revision calls a legacy v2 observer capability and is not verified against the linked-device v3 ingest surface.", "revision": "998c1095cd8f766dd188bece5ad6527444f8dfac", "source_files": [ "extension/journal.js" ] }, { - "classification": "relay_session_control_excluded_from_journal_projection", - "consumer": "solstone-browser", - "path": "/enroll/device", - "rationale": "Relay enrollment/session-control path, not journal-owned API.", - "revision": "998c1095cd8f766dd188bece5ad6527444f8dfac", - "source_files": [ - "extension/journal.js" - ] - }, - { - "classification": "bundled", + "classification": "legacy_v2_unmigrated", "consumer": "solstone-linux", - "path": "/app/observer/register", - "rationale": "Projected as observer.register for Linux observer enrollment.", + "legacy_surface": "observer_v2_register", + "rationale": "Pinned revision calls a legacy v2 observer capability and is not verified against the linked-device v3 ingest surface.", "revision": "1c679db1ce6f9a65db70c5aae0ca2fad677416ef", "source_files": [ "crates/solstone-linux/src/upload.rs" ] }, { - "classification": "bundled", + "classification": "legacy_v2_unmigrated", "consumer": "solstone-linux", - "path": "/app/observer/ingest", - "rationale": "Projected as observer.ingestUpload for Linux segment upload.", + "legacy_surface": "observer_ingest_v2_upload", + "rationale": "Pinned revision calls a legacy v2 observer capability and is not verified against the linked-device v3 ingest surface.", "revision": "1c679db1ce6f9a65db70c5aae0ca2fad677416ef", "source_files": [ "crates/solstone-linux/src/upload.rs" ] }, { - "classification": "bundled", + "classification": "legacy_v2_unmigrated", "consumer": "solstone-linux", - "path": "/app/observer/ingest/event", - "rationale": "Projected as observer.ingestEvent for Linux event relay.", + "legacy_surface": "observer_ingest_v2_event", + "rationale": "Pinned revision calls a legacy v2 observer capability and is not verified against the linked-device v3 ingest surface.", "revision": "1c679db1ce6f9a65db70c5aae0ca2fad677416ef", "source_files": [ "crates/solstone-linux/src/upload.rs" ] }, { - "classification": "bundled", + "classification": "legacy_v2_unmigrated", "consumer": "solstone-linux", - "path": "/app/observer/ingest/segments/{day}", - "rationale": "Projected as observer.ingestSegments for Linux reconciliation.", + "legacy_surface": "observer_ingest_v2_segments", + "rationale": "Pinned revision calls a legacy v2 observer capability and is not verified against the linked-device v3 ingest surface.", "revision": "1c679db1ce6f9a65db70c5aae0ca2fad677416ef", "source_files": [ "crates/solstone-linux/src/upload.rs" ] }, { - "classification": "bundled", - "consumer": "solstone-windows", - "path": "/app/network/pair", - "rationale": "Projected as link.pair for Windows pairing.", - "revision": "19c972c4fea775176cea6421ac8b87f3bb20ab42", - "source_files": [ - "crates/observer-pl/src/lib.rs", - "crates/observer-pl/src/wire.rs" - ] - }, - { - "classification": "bundled", + "classification": "legacy_v2_unmigrated", "consumer": "solstone-windows", - "path": "/app/observer/register", - "rationale": "Projected as observer.register for Windows observer enrollment.", + "legacy_surface": "observer_v2_register", + "rationale": "Pinned revision calls a legacy v2 observer capability and is not verified against the linked-device v3 ingest surface.", "revision": "19c972c4fea775176cea6421ac8b87f3bb20ab42", "source_files": [ "crates/observer-pl/src/lib.rs", @@ -127,10 +106,10 @@ ] }, { - "classification": "bundled", + "classification": "legacy_v2_unmigrated", "consumer": "solstone-windows", - "path": "/app/observer/ingest", - "rationale": "Projected as observer.ingestUpload for Windows segment upload.", + "legacy_surface": "observer_ingest_v2_upload", + "rationale": "Pinned revision calls a legacy v2 observer capability and is not verified against the linked-device v3 ingest surface.", "revision": "19c972c4fea775176cea6421ac8b87f3bb20ab42", "source_files": [ "crates/observer-pl/src/lib.rs", @@ -138,10 +117,10 @@ ] }, { - "classification": "bundled", + "classification": "legacy_v2_unmigrated", "consumer": "solstone-windows", - "path": "/app/observer/ingest/event", - "rationale": "Projected as observer.ingestEvent for Windows event relay.", + "legacy_surface": "observer_ingest_v2_event", + "rationale": "Pinned revision calls a legacy v2 observer capability and is not verified against the linked-device v3 ingest surface.", "revision": "19c972c4fea775176cea6421ac8b87f3bb20ab42", "source_files": [ "crates/observer-pl/src/lib.rs", @@ -149,28 +128,18 @@ ] }, { - "classification": "bundled", + "classification": "legacy_v2_unmigrated", "consumer": "solstone-windows", - "path": "/app/observer/ingest/segments/{day}", - "rationale": "Projected as observer.ingestSegments for Windows reconciliation.", + "legacy_surface": "observer_ingest_v2_segments", + "rationale": "Pinned revision calls a legacy v2 observer capability and is not verified against the linked-device v3 ingest surface.", "revision": "19c972c4fea775176cea6421ac8b87f3bb20ab42", "source_files": [ "crates/observer-pl/src/lib.rs", "crates/observer-pl/src/wire.rs" ] - }, - { - "classification": "bundled", - "consumer": "solstone-windows", - "path": "/sse/events", - "rationale": "Projected as callosum.rootEvents for Windows journal bridge SSE.", - "revision": "19c972c4fea775176cea6421ac8b87f3bb20ab42", - "source_files": [ - "crates/pl-transport-win/src/journal_bridge.rs" - ] } ], - "schema": "solstone.observer-client-consumer-audit.v1", + "schema": "solstone.observer-client-contract-consumer-audit.v2", "searched_files": [ { "consumer": "solstone-browser", @@ -195,12 +164,6 @@ "path": "crates/observer-pl/src/wire.rs", "revision": "19c972c4fea775176cea6421ac8b87f3bb20ab42", "role": "production" - }, - { - "consumer": "solstone-windows", - "path": "crates/pl-transport-win/src/journal_bridge.rs", - "revision": "19c972c4fea775176cea6421ac8b87f3bb20ab42", - "role": "production" } ], "settings_drift_findings": [] diff --git a/vendor/observer-client-contract/fixtures/wire-behavior.json b/vendor/observer-client-contract/fixtures/wire-behavior.json index 3b0d7fe..a464359 100644 --- a/vendor/observer-client-contract/fixtures/wire-behavior.json +++ b/vendor/observer-client-contract/fixtures/wire-behavior.json @@ -1,736 +1,79 @@ { "fixtures": [ { - "id": "declared.observer.ingestSegments.custody_unknown_rejected", - "kind": "declared-negative", - "payload": { - "items": [ - { - "files": [ - { - "name": "audio.flac", - "sha256": "0000000000000000000000000000000000000000000000000000000000000000", - "size": 1, - "status": "unknown" - } - ], - "key": "120000_300", - "observed": false - } - ], - "protocol_version": 2, - "total": 1 - }, - "provenance": { - "direction": "response", - "media_type": "application/json", - "named_variant": "custody_unknown", - "operation_id": "observer.ingestSegments", - "status": 200 - }, - "schema_validation": { - "validates": false - } - }, - { - "id": "declared.observer.ingestSegments.envelope_total_mismatch", - "kind": "declared-negative", - "payload": { - "items": [], - "protocol_version": 2, - "total": 1 - }, - "provenance": { - "direction": "response", - "media_type": "application/json", - "named_variant": "envelope_total_mismatch", - "operation_id": "observer.ingestSegments", - "status": 200 - }, - "schema_validation": { - "validates": true - } - }, - { - "id": "declared.observer.ingestUpload.status_unknown_rejected", - "kind": "declared-negative", - "payload": { - "status": "unknown" - }, - "provenance": { - "direction": "response", - "media_type": "application/json", - "named_variant": "status_unknown", - "operation_id": "observer.ingestUpload", - "status": 200 - }, - "schema_validation": { - "validates": false - } - }, - { - "id": "example.callosum.rootEvents.response.200.text-event-stream.default", - "kind": "openapi-example", - "payload": { - "event": "status", - "tract": "observe", - "ts": 1781803200000 - }, - "provenance": { - "direction": "response", - "media_type": "text/event-stream", - "named_variant": "default", - "operation_id": "callosum.rootEvents", - "status": 200 - }, - "schema_validation": { - "validates": true - } - }, - { - "id": "example.link.pair.request.body.application-json.default", - "kind": "openapi-example", - "payload": { - "csr": "-----BEGIN CERTIFICATE REQUEST-----\n...\n-----END CERTIFICATE REQUEST-----\n", - "device_label": "Rae's iPhone", - "nonce": "5f0d8c8b9f1e48b0a5f80b98f3d5e9b0", - "sender_instance_id": "ios-01" - }, - "provenance": { - "direction": "request", - "media_type": "application/json", - "named_variant": "default", - "operation_id": "link.pair", - "status": null - }, - "schema_validation": { - "validates": true - } - }, - { - "id": "example.link.pair.response.200.application-json.default", - "kind": "openapi-example", - "payload": { - "ca_chain": [ - "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----\n" - ], - "client_cert": "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----\n", - "fingerprint": "sha256:abc123", - "home_attestation": "eyJhbGciOi...", - "home_label": "home", - "instance_id": "4d1f3d57-4f39-4930-b8f8-5e6f2a84d51a", - "local_endpoints": [ - { - "ip": "192.168.1.10", - "port": 7657, - "scope": "lan" - } - ] - }, - "provenance": { - "direction": "response", - "media_type": "application/json", - "named_variant": "default", - "operation_id": "link.pair", - "status": 200 - }, - "schema_validation": { - "validates": true - } - }, - { - "id": "example.observer.callosumStream.response.200.text-event-stream.default", - "kind": "openapi-example", - "payload": { - "day": "20260618", - "event": "observing", - "segment": "143022_300", - "tract": "observe", - "ts": 1781803200000 - }, - "provenance": { - "direction": "response", - "media_type": "text/event-stream", - "named_variant": "default", - "operation_id": "observer.callosumStream", - "status": 200 - }, - "schema_validation": { - "validates": true - } - }, - { - "id": "example.observer.ingestEvent.request.body.application-json.default", - "kind": "openapi-example", - "payload": { - "event": "status", - "state": "recording", - "tract": "observe" - }, - "provenance": { - "direction": "request", - "media_type": "application/json", - "named_variant": "default", - "operation_id": "observer.ingestEvent", - "status": null - }, - "schema_validation": { - "validates": true - } - }, - { - "id": "example.observer.ingestEvent.response.200.application-json.default", - "kind": "openapi-example", + "id": "declared.observer.ingestUpload.status.ok", + "kind": "declared", "payload": { "status": "ok" }, "provenance": { - "direction": "response", - "media_type": "application/json", - "named_variant": "default", - "operation_id": "observer.ingestEvent", - "status": 200 - }, - "schema_validation": { - "validates": true - } - }, - { - "id": "example.observer.ingestSegments.response.200.application-json.legacy", - "kind": "openapi-example", - "payload": [ - { - "files": [ - { - "name": "screen.png", - "sha256": "5f70bf18a086007016bb522ec180fd0b", - "size": 2048, - "status": "present" - } - ], - "key": "143022_300", - "observed": true - } - ], - "provenance": { - "direction": "response", - "media_type": "application/json", - "named_variant": "legacy", - "operation_id": "observer.ingestSegments", - "status": 200 - }, - "schema_validation": { - "validates": true - } - }, - { - "id": "example.observer.ingestSegments.response.200.application-json.v2", - "kind": "openapi-example", - "payload": { - "items": [ - { - "files": [ - { - "name": "screen.png", - "sha256": "5f70bf18a086007016bb522ec180fd0b", - "size": 2048, - "status": "present" - } - ], - "key": "143022_300", - "observed": true - } - ], - "protocol_version": 2, - "total": 1 - }, - "provenance": { - "direction": "response", - "media_type": "application/json", - "named_variant": "v2", - "operation_id": "observer.ingestSegments", - "status": 200 - }, - "schema_validation": { - "validates": true - } - }, - { - "id": "example.observer.ingestUpload.request.body.multipart-form-data.default", - "kind": "openapi-example", - "payload": { - "day": "20260618", - "files": [ - "screen.png", - "audio.flac" - ], - "host": "archon", - "meta": "{\"facet\":\"work\"}", - "platform": "linux", - "segment": "143022_300" - }, - "provenance": { - "direction": "request", - "media_type": "multipart/form-data", - "named_variant": "default", - "operation_id": "observer.ingestUpload", - "status": null + "http_status": 200, + "vocabulary": "observer.ingestUpload.status" }, "schema_validation": { - "validates": true + "valid": true } }, { - "id": "example.observer.ingestUpload.response.200.application-json.duplicate", - "kind": "openapi-example", + "id": "declared.observer.ingestUpload.status.duplicate", + "kind": "declared", "payload": { - "existing_segment": "143022_300", - "message": "All files already received", "status": "duplicate" }, "provenance": { - "direction": "response", - "media_type": "application/json", - "named_variant": "duplicate", - "operation_id": "observer.ingestUpload", - "status": 200 - }, - "schema_validation": { - "validates": true - } - }, - { - "id": "example.observer.ingestUpload.response.200.application-json.normal", - "kind": "openapi-example", - "payload": { - "bytes": 524288, - "files": [ - "screen.png", - "audio.flac" - ], - "segment": "143022_300", - "status": "ok" - }, - "provenance": { - "direction": "response", - "media_type": "application/json", - "named_variant": "normal", - "operation_id": "observer.ingestUpload", - "status": 200 + "http_status": 200, + "vocabulary": "observer.ingestUpload.status" }, "schema_validation": { - "validates": true + "valid": true } }, { - "id": "example.observer.register.request.body.application-json.default", - "kind": "openapi-example", + "id": "declared.observer.ingestUpload.status.collision", + "kind": "declared", "payload": { - "hostname": "archon", - "label": "Archon desktop", - "platform": "linux", - "stream_type": "desktop", - "version": "1.4.0" - }, - "provenance": { - "direction": "request", - "media_type": "application/json", - "named_variant": "default", - "operation_id": "observer.register", - "status": null - }, - "schema_validation": { - "validates": true - } - }, - { - "id": "example.observer.register.response.200.application-json.default", - "kind": "openapi-example", - "payload": { - "ingest_url": "/app/observer/ingest", - "key": "x7J7k2observerHandle", - "name": "archon", - "prefix": "x7J7k2ob", - "protocol_version": 2 - }, - "provenance": { - "direction": "response", - "media_type": "application/json", - "named_variant": "default", - "operation_id": "observer.register", - "status": 200 - }, - "schema_validation": { - "validates": true - } - }, - { - "id": "recorded.auth.bearer.segments", - "kind": "recorded-response", - "payload": { - "items": [], - "protocol_version": 2, - "total": 0 - }, - "provenance": { - "direction": "response", - "media_type": "application/json", - "named_variant": "bearer", - "operation_id": "observer.ingestSegments", - "status": 200 - }, - "schema_validation": { - "validates": true - } - }, - { - "id": "recorded.auth.handle.segments", - "kind": "recorded-response", - "payload": { - "items": [], - "protocol_version": 2, - "total": 0 - }, - "provenance": { - "direction": "response", - "media_type": "application/json", - "named_variant": "observer_handle", - "operation_id": "observer.ingestSegments", - "status": 200 - }, - "schema_validation": { - "validates": true - } - }, - { - "id": "recorded.ingestUpload.collision", - "kind": "recorded-response", - "payload": { - "bytes": 24, - "files": [ - "audio.flac", - "screen.mp4" - ], - "segment": "120000_301", - "segment_original": "120000_300", "status": "collision" }, "provenance": { - "direction": "response", - "media_type": "application/json", - "named_variant": "collision", - "operation_id": "observer.ingestUpload", - "status": 200 + "http_status": 200, + "vocabulary": "observer.ingestUpload.status" }, "schema_validation": { - "validates": true + "valid": true } }, { - "id": "recorded.ingestUpload.conflict", - "kind": "recorded-response", + "id": "declared.observer.ingestUpload.status.conflict", + "kind": "declared", "payload": { - "conflicting_files": [ - "notes.txt" - ], - "detail": "Conflicting sidecar metadata for existing segment", - "error": "I couldn't bring in those observer sidecars because they conflict with files already held.", - "existing_segment": "120000_300", - "reason_code": "ingest_sidecar_conflict", + "detail": "held sidecar bytes conflict", + "error": "Ingest request failed", + "reason_code": "content_conflict", "status": "conflict" }, "provenance": { - "direction": "response", - "media_type": "application/json", - "named_variant": "conflict", - "operation_id": "observer.ingestUpload", - "status": 409 - }, - "schema_validation": { - "validates": true - } - }, - { - "id": "recorded.ingestUpload.duplicate", - "kind": "recorded-response", - "payload": { - "existing_segment": "120000_300", - "message": "All files already received", - "status": "duplicate" - }, - "provenance": { - "direction": "response", - "media_type": "application/json", - "named_variant": "duplicate", - "operation_id": "observer.ingestUpload", - "status": 200 + "http_status": 409, + "vocabulary": "observer.ingestUpload.status" }, "schema_validation": { - "validates": true + "valid": true } }, { - "id": "recorded.ingestUpload.failed", - "kind": "recorded-response", + "id": "declared.observer.ingestUpload.status.failed", + "kind": "declared", "payload": { - "detail": "Uploaded file did not match the journal contract", - "error": "I couldn't use those observer files because they don't match the journal contract.", - "failed_path": "20250107/observer/failed/120000_300/1700000000000", - "invalid_files": [ - "audio.jsonl:2: 'text' is a required property" - ], - "reason_code": "ingest_contract_invalid", "status": "failed" }, "provenance": { - "direction": "response", - "media_type": "application/json", - "named_variant": "failed", - "operation_id": "observer.ingestUpload", - "status": 422 - }, - "schema_validation": { - "validates": true - } - }, - { - "id": "recorded.ingestUpload.ok", - "kind": "recorded-response", - "payload": { - "bytes": 8, - "files": [ - "audio.flac" - ], - "segment": "120000_300", - "status": "ok" - }, - "provenance": { - "direction": "response", - "media_type": "application/json", - "named_variant": "ok", - "operation_id": "observer.ingestUpload", - "status": 200 - }, - "schema_validation": { - "validates": true - } - }, - { - "id": "recorded.segments.custody_statuses", - "kind": "recorded-response", - "payload": { - "items": [ - { - "files": [ - { - "name": "audio.flac", - "sha256": "93a6beeb378a929f07f54d587e0d5fb2f33a121269aa31bf6ab87818b701d255", - "size": 13, - "status": "processed" - }, - { - "name": "screen.mp4", - "sha256": "9e7a7350d9be4ef540e1cf03da8db61f4da22f31fc27f56bbc5bd3f88bd00e8a", - "size": 14, - "status": "missing" - }, - { - "name": "notes.txt", - "sha256": "459e9e209d70522fb19bd418ba31d79562181e7bca54e85186b4c89f8d90c789", - "size": 13, - "status": "present" - } - ], - "key": "120000_300", - "observed": false - } - ], - "protocol_version": 2, - "total": 1 - }, - "provenance": { - "direction": "response", - "media_type": "application/json", - "named_variant": "custody_statuses", - "operation_id": "observer.ingestSegments", - "status": 200 - }, - "schema_validation": { - "validates": true - } - }, - { - "id": "recorded.segments.legacy.absent_header", - "kind": "recorded-response", - "payload": [], - "provenance": { - "direction": "response", - "media_type": "application/json", - "named_variant": "legacy_array_absent_header", - "operation_id": "observer.ingestSegments", - "status": 200 - }, - "schema_validation": { - "validates": true - } - }, - { - "id": "recorded.segments.legacy.unparseable_header", - "kind": "recorded-response", - "payload": [], - "provenance": { - "direction": "response", - "media_type": "application/json", - "named_variant": "legacy_array_unparseable_header", - "operation_id": "observer.ingestSegments", - "status": 200 - }, - "schema_validation": { - "validates": true - } - }, - { - "id": "recorded.segments.submitted_name_omitted", - "kind": "recorded-response", - "payload": { - "items": [ - { - "files": [ - { - "name": "audio.flac", - "sha256": "b4fae6d15a821dfcf135b6b44c6ee85f8250521a83ab757a36613a4327cf3075", - "size": 15, - "status": "present" - } - ], - "key": "120000_300", - "observed": false - } - ], - "protocol_version": 2, - "total": 1 - }, - "provenance": { - "direction": "response", - "media_type": "application/json", - "named_variant": "submitted_name_omitted", - "operation_id": "observer.ingestSegments", - "status": 200 - }, - "schema_validation": { - "validates": true - } - }, - { - "id": "recorded.segments.v2.envelope", - "kind": "recorded-response", - "payload": { - "items": [], - "protocol_version": 2, - "total": 0 - }, - "provenance": { - "direction": "response", - "media_type": "application/json", - "named_variant": "v2_envelope", - "operation_id": "observer.ingestSegments", - "status": 200 - }, - "schema_validation": { - "validates": true - } - }, - { - "id": "recorded.sse.observer.data", - "kind": "recorded-sse-frame", - "payload": { - "event": "status", - "extra": "value", - "tract": "observe", - "ts": 0 - }, - "provenance": { - "direction": "response", - "media_type": "text/event-stream", - "named_variant": "data", - "operation_id": "observer.callosumStream", - "status": 200 - }, - "schema_validation": { - "validates": true - } - }, - { - "id": "recorded.sse.observer.error", - "kind": "recorded-sse-frame", - "payload": { - "detail": "Observer revoked", - "error": "I couldn't use that paired device because it was revoked.", - "reason_code": "pl_revoked" - }, - "provenance": { - "direction": "response", - "media_type": "text/event-stream", - "named_variant": "error", - "operation_id": "observer.callosumStream", - "status": 200 - }, - "schema_validation": { - "validates": true - } - }, - { - "id": "recorded.sse.observer.heartbeat", - "kind": "recorded-sse-frame", - "payload": ": heartbeat\n\n", - "provenance": { - "direction": "response", - "media_type": "text/event-stream", - "named_variant": "heartbeat", - "operation_id": "observer.callosumStream", - "status": 200 - }, - "schema_validation": { - "reason": "SSE heartbeat comments are not JSON schema payloads", - "validates": null - } - }, - { - "id": "recorded.sse.root.data_unknown_event", - "kind": "recorded-sse-frame", - "payload": { - "event": "unknown", - "extra": "value", - "tract": "future", - "ts": 0 - }, - "provenance": { - "direction": "response", - "media_type": "text/event-stream", - "named_variant": "data_unknown_event", - "operation_id": "callosum.rootEvents", - "status": 200 - }, - "schema_validation": { - "validates": true - } - }, - { - "id": "recorded.sse.root.heartbeat", - "kind": "recorded-sse-frame", - "payload": ": heartbeat\n\n", - "provenance": { - "direction": "response", - "media_type": "text/event-stream", - "named_variant": "heartbeat", - "operation_id": "callosum.rootEvents", - "status": 200 + "http_status": 500, + "vocabulary": "observer.ingestUpload.status" }, "schema_validation": { - "reason": "SSE heartbeat comments are not JSON schema payloads", - "validates": null + "note": "vocabulary-only status value; the full Error payload requires error, reason_code, and detail, and the authority enumerates no HTTP 500 reason code", + "valid": false } } ], - "schema": "solstone.observer-client-contract-fixtures.v1" + "schema": "solstone.observer-client-contract-fixtures.v2" } diff --git a/vendor/observer-client-contract/manifest.json b/vendor/observer-client-contract/manifest.json index 432f1b7..b03dd57 100644 --- a/vendor/observer-client-contract/manifest.json +++ b/vendor/observer-client-contract/manifest.json @@ -14,133 +14,59 @@ } ], "bundle_schema_identity": "solstone.observer-client-contract-bundle.schema.v1", - "bundle_semver": "8.0.0", + "bundle_semver": "9.0.0", "component_closure": [ - "CallosumEvent", "Error", "SegmentFile", "SegmentItem", "SegmentsEnvelope" ], "consumer_identifiers": [ - "solstone-android", "solstone-browser", "solstone-linux", - "solstone-macos", - "solstone-swift", - "solstone-tmux", "solstone-windows" ], "files": [ { "path": "consumer-audit.json", - "sha256": "3f9938b38994ea2a3a19ea154152e5dc8b3f24843e53eb1f14b6fb33d58e6ac9" + "sha256": "522a1d0417086a85d755e058b9043e519665c63eace50b146bab1f1157ff0cdf" }, { "path": "fixtures/wire-behavior.json", - "sha256": "b9ed72f3c3de4a890978079fcf286ddad212c6ac99bd32d3e080afdd5a787dc4" + "sha256": "65917fe91620f517d9988664e24b559722acbd6fb798bc518d1e092ef8f8771c" }, { "path": "projection.openapi.json", - "sha256": "46eb05e6889f9176446fb315585a0e15a57600aead9ed1aaf948a93eee596c35" + "sha256": "7780c76380ae59504c069e388042401b050c60caf9027e1cf22a4cce0c19103e" }, { "path": "vectors.json", - "sha256": "a4cb9712bf5a6679a2fefc6b94f1618dfbd8e7f227364103d70095a8dd6bce1f" + "sha256": "fd99f21f225573cd2aead2217e9716237e7b4f66bd36a225d0e7019ded4a222b" } ], - "generator_identity": "solstone.convey.contract.observer_bundle.v1", + "generator_identity": "solstone.repository_contracts.observer_client_contract_bundle.v1", "generator_inputs": [ { - "id": "bundle.projection_builder", - "path": "solstone/convey/contract/observer_bundle.py", - "role": "projection_builder", - "sha256": "908e77f6eabfd1dbe951dcc9e0b523d3db2e8563c1a61c3a5dbfce8162fa671b" - }, - { - "id": "bundle.recording", - "path": "solstone/convey/contract/observer_bundle_recording.py", - "role": "fixture_vector_builder", - "sha256": "37b44cf50b8e8fd791be330d86282f2fa59a93367925e9abd6d06683deaa78a3" - }, - { - "id": "extension.observer_sse_error_frame", - "path": "solstone/apps/observer/contract.py", - "role": "code_adjacent_extension", - "sha256": "acfca9d0843a4c53b9db11c36d03c3a340d62f149fa50f6a3c934007c2a736f2" - }, - { - "id": "fragment.link", - "path": "solstone/apps/network/contract.py", - "role": "code_adjacent_fragment", - "sha256": "e8be782b5e8333dfd796c5812bd5d61a645d9c8d09f25bd3d1893fc3ac0cd5ab" - }, - { - "id": "fragment.observer", - "path": "solstone/apps/observer/contract.py", - "role": "code_adjacent_fragment", - "sha256": "acfca9d0843a4c53b9db11c36d03c3a340d62f149fa50f6a3c934007c2a736f2" - }, - { - "id": "fragment.root", - "path": "solstone/convey/root_contract.py", - "role": "code_adjacent_fragment", - "sha256": "26e7fe79cf78aa0a3c09e5074ce51136ccfb86b7aa0e33a16c18c69c746e99aa" - }, - { - "id": "openapi.assembler", - "path": "solstone/convey/contract/assemble.py", + "id": "openapi.convey_clients", + "path": "docs/openapi/convey-clients.json", "role": "openapi_source", - "sha256": "03f547c2d14d282f4ecf1c30ae754b241adbcf6ace0aae3298bd3ea27f9624a6" - }, - { - "id": "producer.observer_routes", - "path": "solstone/apps/observer/routes.py", - "role": "producer", - "sha256": "c3685b7fba0fc039c3a6a4ad9047dd87598574f47f3bbd086715a33f50861fd8" - }, - { - "id": "producer.observer_utils", - "path": "solstone/apps/observer/utils.py", - "role": "producer", - "sha256": "937aa9669c5b95bbc3c400a337f68e52bd0b783ed6579898ba75efb657b27e77" - }, - { - "id": "producer.protocol", - "path": "solstone/observe/protocol.py", - "role": "producer", - "sha256": "21feb3b5e23eb9df37544bdecec8e96ba0ec56ae74f73a8f0962d2dcc522af9e" - }, - { - "id": "producer.root_sse", - "path": "solstone/convey/root.py", - "role": "producer", - "sha256": "7286e8836eec5c957f137e8173e314deabda0f71f453fa27a34432a33cd4128f" - }, - { - "id": "reason_codes", - "path": "solstone/convey/reasons.py", - "role": "vocabulary_source", - "sha256": "3ca67b324d3993d8fe0eedb05dcb0f88716deaff80b5a68611dae3ebc37b7a13" + "sha256": "434d103d7b2accc8d6244f9886884c6c05ce59096bd0b8ee9bfbc67d564f6563" } ], - "observer_protocol_version": 2, + "observer_protocol_version": 3, "openapi_document_version": "1.0.0", "openapi_spec_version": "3.1.0", "operation_ids": [ - "callosum.rootEvents", - "link.pair", - "observer.callosumStream", - "observer.ingestEvent", - "observer.ingestSegments", "observer.ingestUpload", - "observer.register" + "observer.ingestManifest", + "observer.ingestManifestDay", + "observer.ingestSegments" ], "projection_path": "projection.openapi.json", "schema_dialect_uri": "https://json-schema.org/draft/2020-12/schema", + "scope_rationale": "This ingest-triad bundle projects only the four Rust-served linked-device devices/ingest operations. Pairing and root SSE are live but out of scope; deferred legacy observer operations are not projected.", "supported_response_variants": [ - 1, - 2 + 3 ], "vocabularies": [ { @@ -154,392 +80,13 @@ "processed" ] }, - { - "absent_or_unparseable": 1, - "classification": "extensible_integer", - "current": 2, - "id": "X-Solstone-Protocol-Version", - "source_pointer": "/paths/~1app~1observer~1ingest~1segments~1{day}/get/responses/200", - "supported_response_variants": [ - 1, - 2 - ], - "unknown_value_behavior": "version_greater_or_equal_current_uses_v2" - }, - { - "classification": "closed", - "id": "callosum.rootEvents.responses.403.reason_code", - "method": "GET", - "path": "/sse/events", - "source_pointer": "/paths/~1sse~1events/get/responses/403", - "unknown_value_behavior": "reject", - "values": [ - "pl_revoked" - ] - }, - { - "classification": "closed", - "id": "callosum.rootEvents.sse_frames", - "source_pointer": "/paths/~1sse~1events/get/responses/200", - "unknown_value_behavior": "reject", - "values": [ - "data", - "heartbeat" - ] - }, - { - "classification": "extensible", - "id": "callosum.tract_event", - "known_registry": { - "activity": [ - "live", - "recorded" - ], - "cortex": [ - "request", - "start", - "thinking", - "tool_start", - "tool_end", - "finish", - "error", - "talent_updated", - "info", - "status", - "cancel", - "dry_run", - "progress", - "text_delta", - "tool_budget_exhausted", - "budget_escalation" - ], - "importer": [ - "started", - "status", - "completed", - "error", - "file_imported", - "enrichment_ready" - ], - "link": [ - "pair_complete", - "last_seen", - "stream_reset" - ], - "logs": [ - "exec", - "line", - "exit" - ], - "navigate": [ - "request" - ], - "notification": [ - "*" - ], - "observe": [ - "status", - "observing", - "detected", - "described", - "transcribed", - "observed", - "memory_throttle_started", - "memory_throttle_completed" - ], - "storage": [ - "warning" - ], - "supervisor": [ - "started", - "stopped", - "restarting", - "status", - "queue", - "scheduled", - "provider_runtime", - "request", - "restart", - "drain", - "skipped", - "sync_conflict" - ], - "support": [ - "proactive_suggestion" - ], - "think": [ - "started", - "status", - "group_started", - "group_completed", - "talent_started", - "talent_completed", - "completed", - "segments_started", - "segments_completed", - "memory_throttle_started", - "memory_throttle_completed", - "daily_complete" - ], - "work": [ - "talent_queued", - "talent_spawned", - "talent_finished", - "talent_errored", - "result", - "reflection_ready", - "support_draft", - "support_submit_claim" - ] - }, - "source_pointer": "/", - "unknown_value_behavior": "preserve" - }, - { - "classification": "closed", - "id": "callosum.work.event", - "source_pointer": "/x-callosum-registry/work", - "unknown_value_behavior": "reject", - "values": [ - "talent_queued", - "talent_spawned", - "talent_finished", - "talent_errored", - "result", - "reflection_ready", - "support_draft", - "support_submit_claim" - ] - }, - { - "classification": "closed", - "id": "link.pair.responses.400.reason_code", - "method": "POST", - "path": "/app/network/pair", - "source_pointer": "/paths/~1app~1network~1pair/post/responses/400", - "unknown_value_behavior": "reject", - "values": [ - "missing_required_field", - "pairing_key_invalid", - "pairing_request_invalid" - ] - }, - { - "classification": "closed", - "id": "link.pair.responses.403.reason_code", - "method": "POST", - "path": "/app/network/pair", - "source_pointer": "/paths/~1app~1network~1pair/post/responses/403", - "unknown_value_behavior": "reject", - "values": [ - "pl_revoked" - ] - }, - { - "classification": "closed", - "id": "link.pair.responses.410.reason_code", - "method": "POST", - "path": "/app/network/pair", - "source_pointer": "/paths/~1app~1network~1pair/post/responses/410", - "unknown_value_behavior": "reject", - "values": [ - "operation_no_longer_available" - ] - }, - { - "classification": "closed", - "id": "observer.callosumStream.responses.200.sse_error.reason_code", - "method": "GET", - "path": "/app/observer/callosum", - "source_pointer": "/paths/~1app~1observer~1callosum/get/responses/200/x-sse-error-frame", - "unknown_value_behavior": "reject", - "values": [ - "auth_required", - "feature_unavailable", - "pl_revoked" - ] - }, - { - "classification": "closed", - "id": "observer.callosumStream.responses.401.reason_code", - "method": "GET", - "path": "/app/observer/callosum", - "source_pointer": "/paths/~1app~1observer~1callosum/get/responses/401", - "unknown_value_behavior": "reject", - "values": [ - "auth_key_invalid", - "auth_required", - "feature_unavailable", - "pl_revoked" - ] - }, - { - "classification": "closed", - "id": "observer.callosumStream.sse_frames", - "source_pointer": "/paths/~1app~1observer~1callosum/get/responses/200", - "unknown_value_behavior": "reject", - "values": [ - "data", - "error", - "heartbeat" - ] - }, - { - "classification": "closed", - "id": "observer.ingestEvent.responses.400.reason_code", - "method": "POST", - "path": "/app/observer/ingest/event", - "source_pointer": "/paths/~1app~1observer~1ingest~1event/post/responses/400", - "unknown_value_behavior": "reject", - "values": [ - "missing_required_field" - ] - }, - { - "classification": "closed", - "id": "observer.ingestEvent.responses.401.reason_code", - "method": "POST", - "path": "/app/observer/ingest/event", - "source_pointer": "/paths/~1app~1observer~1ingest~1event/post/responses/401", - "unknown_value_behavior": "reject", - "values": [ - "auth_key_invalid", - "auth_required" - ] - }, - { - "classification": "closed", - "id": "observer.ingestEvent.responses.403.reason_code", - "method": "POST", - "path": "/app/observer/ingest/event", - "source_pointer": "/paths/~1app~1observer~1ingest~1event/post/responses/403", - "unknown_value_behavior": "reject", - "values": [ - "feature_unavailable", - "pl_revoked" - ] - }, - { - "classification": "closed", - "id": "observer.ingestSegments.responses.400.reason_code", - "method": "GET", - "path": "/app/observer/ingest/segments/{day}", - "source_pointer": "/paths/~1app~1observer~1ingest~1segments~1{day}/get/responses/400", - "unknown_value_behavior": "reject", - "values": [ - "invalid_day" - ] - }, - { - "classification": "closed", - "id": "observer.ingestSegments.responses.401.reason_code", - "method": "GET", - "path": "/app/observer/ingest/segments/{day}", - "source_pointer": "/paths/~1app~1observer~1ingest~1segments~1{day}/get/responses/401", - "unknown_value_behavior": "reject", - "values": [ - "auth_key_invalid", - "auth_required" - ] - }, - { - "classification": "closed", - "id": "observer.ingestSegments.responses.403.reason_code", - "method": "GET", - "path": "/app/observer/ingest/segments/{day}", - "source_pointer": "/paths/~1app~1observer~1ingest~1segments~1{day}/get/responses/403", - "unknown_value_behavior": "reject", - "values": [ - "feature_unavailable", - "pl_revoked" - ] - }, - { - "classification": "closed", - "id": "observer.ingestUpload.responses.400.reason_code", - "method": "POST", - "path": "/app/observer/ingest", - "source_pointer": "/paths/~1app~1observer~1ingest/post/responses/400", - "unknown_value_behavior": "reject", - "values": [ - "ingest_no_files", - "invalid_day", - "invalid_segment_or_stream", - "missing_required_field" - ] - }, - { - "classification": "closed", - "id": "observer.ingestUpload.responses.401.reason_code", - "method": "POST", - "path": "/app/observer/ingest", - "source_pointer": "/paths/~1app~1observer~1ingest/post/responses/401", - "unknown_value_behavior": "reject", - "values": [ - "auth_key_invalid", - "auth_required" - ] - }, - { - "classification": "closed", - "id": "observer.ingestUpload.responses.403.reason_code", - "method": "POST", - "path": "/app/observer/ingest", - "source_pointer": "/paths/~1app~1observer~1ingest/post/responses/403", - "unknown_value_behavior": "reject", - "values": [ - "feature_unavailable", - "pl_revoked" - ] - }, - { - "classification": "closed", - "id": "observer.ingestUpload.responses.409.reason_code", - "method": "POST", - "path": "/app/observer/ingest", - "source_pointer": "/paths/~1app~1observer~1ingest/post/responses/409", - "unknown_value_behavior": "reject", - "values": [ - "ingest_sidecar_conflict" - ] - }, - { - "classification": "closed", - "id": "observer.ingestUpload.responses.422.reason_code", - "method": "POST", - "path": "/app/observer/ingest", - "source_pointer": "/paths/~1app~1observer~1ingest/post/responses/422", - "unknown_value_behavior": "reject", - "values": [ - "ingest_contract_invalid" - ] - }, - { - "classification": "closed", - "id": "observer.ingestUpload.responses.500.reason_code", - "method": "POST", - "path": "/app/observer/ingest", - "source_pointer": "/paths/~1app~1observer~1ingest/post/responses/500", - "unknown_value_behavior": "reject", - "values": [ - "ingest_storage_failed" - ] - }, - { - "classification": "closed", - "id": "observer.ingestUpload.responses.507.reason_code", - "method": "POST", - "path": "/app/observer/ingest", - "source_pointer": "/paths/~1app~1observer~1ingest/post/responses/507", - "unknown_value_behavior": "reject", - "values": [ - "ingest_storage_failed" - ] - }, { "classification": "closed", "id": "observer.ingestUpload.status", - "source_pointer": "/paths/~1app~1observer~1ingest/post/responses/200/content/application~1json/schema/properties/status", + "source_pointers": [ + "/paths/~1app~1devices~1ingest/post/responses/200/content/application~1json/schema/properties/status", + "/paths/~1app~1devices~1ingest/post/responses/409" + ], "unknown_value_behavior": "reject", "values": [ "ok", @@ -548,58 +95,19 @@ "conflict", "failed" ] - }, - { - "classification": "closed", - "id": "observer.register.responses.400.reason_code", - "method": "POST", - "path": "/app/observer/register", - "source_pointer": "/paths/~1app~1observer~1register/post/responses/400", - "unknown_value_behavior": "reject", - "values": [ - "invalid_segment_or_stream", - "missing_required_field" - ] - }, - { - "classification": "closed", - "id": "observer.register.responses.403.reason_code", - "method": "POST", - "path": "/app/observer/register", - "source_pointer": "/paths/~1app~1observer~1register/post/responses/403", - "unknown_value_behavior": "reject", - "values": [ - "local_request_only" - ] - }, - { - "classification": "closed", - "id": "observer.register.responses.500.reason_code", - "method": "POST", - "path": "/app/observer/register", - "source_pointer": "/paths/~1app~1observer~1register/post/responses/500", - "unknown_value_behavior": "reject", - "values": [ - "settings_operation_failed" - ] - }, - { - "classification": "closed", - "id": "observer.status.ok", - "source_pointer": "/paths/~1app~1observer~1ingest~1event/post/responses/200/content/application~1json/schema/properties/status", - "unknown_value_behavior": "reject", - "values": [ - "ok" - ] } ], "windows_linux_rollout_targets": [ { - "adoption_blocker_ids": [], + "adoption_blocker_ids": [ + "solstone-linux-legacy-v2-unmigrated" + ], "consumer_identifier": "solstone-linux" }, { - "adoption_blocker_ids": [], + "adoption_blocker_ids": [ + "solstone-windows-legacy-v2-unmigrated" + ], "consumer_identifier": "solstone-windows" } ] diff --git a/vendor/observer-client-contract/projection.openapi.json b/vendor/observer-client-contract/projection.openapi.json index f18cd58..47681e4 100644 --- a/vendor/observer-client-contract/projection.openapi.json +++ b/vendor/observer-client-contract/projection.openapi.json @@ -1,26 +1,6 @@ { "components": { "schemas": { - "CallosumEvent": { - "additionalProperties": true, - "properties": { - "event": { - "type": "string" - }, - "tract": { - "type": "string" - }, - "ts": { - "type": "integer" - } - }, - "required": [ - "tract", - "event", - "ts" - ], - "type": "object" - }, "Error": { "additionalProperties": true, "properties": { @@ -47,10 +27,12 @@ "backup_operation_failed", "backup_unavailable", "config_busy", + "content_conflict", "convey_busy", "convey_operation_failed", "corrupt_config", "cross_origin_blocked", + "day_invalid", "edge_index_unavailable", "entity_alias_conflict", "entity_already_exists", @@ -58,9 +40,11 @@ "entity_busy", "entity_not_found", "entity_operation_failed", + "envelope_invalid", "facet_already_exists", "facet_not_found", "feature_unavailable", + "field_missing", "file_not_found", "file_read_failed", "health_report_failed", @@ -91,9 +75,12 @@ "invalid_state", "journal_source_problem", "ledger_item_not_found", + "linked_device_required", "local_request_only", "missing_request_body", "missing_required_field", + "multipart_malformed", + "multipart_part_too_large", "observer_restart_failed", "operation_erased", "operation_in_progress", @@ -106,6 +93,10 @@ "pl_revoked", "plaud_validation_unavailable", "principal_entity_protected", + "protocol_version_future", + "protocol_version_legacy", + "protocol_version_malformed", + "protocol_version_required", "provider_key_missing", "push_request_invalid", "raw_media_not_available", @@ -115,6 +106,7 @@ "reprocess_past_only", "reprocess_unreachable", "search_failed", + "segment_invalid", "service_busy", "service_operation_failed", "settings_operation_failed", @@ -242,322 +234,38 @@ } }, "info": { - "description": "Generated OpenAPI projection for observer-owned client contract surfaces. Regenerate with make openapi.", - "title": "Solstone Observer Client Contract Projection", - "version": "1.0.0", + "description": "Generated from convey-clients.json. Covers only the four Rust-served linked-device devices/ingest operations.", + "title": "Linked-device v3 ingest client contract", + "version": "9.0.0", "x-generated": true, - "x-generated-by": "solstone.convey.contract.observer_bundle.v1" + "x-generated-by": "solstone-core-repository-contracts" }, "openapi": "3.1.0", "paths": { - "/app/network/pair": { - "post": { - "description": "Accept a client CSR plus nonce, then return a signed certificate and home attestation.", - "operationId": "link.pair", - "parameters": [ - { - "description": "Pairing nonce; the body nonce can be used instead.", - "in": "query", - "name": "token", - "required": false, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "example": { - "csr": "-----BEGIN CERTIFICATE REQUEST-----\n...\n-----END CERTIFICATE REQUEST-----\n", - "device_label": "Rae's iPhone", - "nonce": "5f0d8c8b9f1e48b0a5f80b98f3d5e9b0", - "sender_instance_id": "ios-01" - }, - "schema": { - "additionalProperties": true, - "properties": { - "csr": { - "type": "string" - }, - "device_label": { - "type": "string" - }, - "nonce": { - "type": "string" - }, - "sender_instance_id": { - "type": "string" - } - }, - "required": [ - "csr" - ], - "type": "object" - } - } - }, - "required": true - }, - "responses": { - "200": { - "content": { - "application/json": { - "example": { - "ca_chain": [ - "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----\n" - ], - "client_cert": "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----\n", - "fingerprint": "sha256:abc123", - "home_attestation": "eyJhbGciOi...", - "home_label": "home", - "instance_id": "4d1f3d57-4f39-4930-b8f8-5e6f2a84d51a", - "local_endpoints": [ - { - "ip": "192.168.1.10", - "port": 7657, - "scope": "lan" - } - ] - }, - "schema": { - "additionalProperties": true, - "properties": { - "ca_chain": { - "items": { - "type": "string" - }, - "type": "array" - }, - "client_cert": { - "type": "string" - }, - "fingerprint": { - "type": "string" - }, - "home_attestation": { - "type": "string" - }, - "home_label": { - "type": "string" - }, - "instance_id": { - "type": "string" - }, - "local_endpoints": { - "items": { - "additionalProperties": true, - "properties": { - "ip": { - "type": "string" - }, - "port": { - "type": "integer" - }, - "scope": { - "type": "string" - } - }, - "required": [ - "ip", - "port", - "scope" - ], - "type": "object" - }, - "type": "array" - } - }, - "required": [ - "client_cert", - "ca_chain", - "instance_id", - "home_label", - "home_attestation", - "fingerprint" - ], - "type": "object" - } - } - }, - "description": "Signed link material for the paired client." - }, - "400": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - }, - "description": "Pair request rejected by handler validation.", - "x-reason-codes": [ - "missing_required_field", - "pairing_key_invalid", - "pairing_request_invalid" - ] - }, - "403": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - }, - "description": "Access gate rejected a revoked paired-link identity.", - "x-reason-codes": [ - "pl_revoked" - ] - }, - "410": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - }, - "description": "Nonce expired or was already used.", - "x-reason-codes": [ - "operation_no_longer_available" - ] - } - }, - "summary": "Complete link pairing", - "tags": [ - "link" - ] - } - }, - "/app/observer/callosum": { - "get": { - "description": "Open an observer-authenticated Server-Sent Events feed. Frames: data `data: {json}\\n\\n`; heartbeat `: heartbeat\\n\\n`; error `event: error\\ndata: {Error}\\n\\n`.", - "operationId": "observer.callosumStream", - "parameters": [ - { - "description": "Observer handle. Preferred over Authorization when present.", - "in": "header", - "name": "X-Solstone-Observer", - "required": false, - "schema": { - "type": "string" - } - }, - { - "description": "Bearer observer handle.", - "in": "header", - "name": "Authorization", - "required": false, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "content": { - "text/event-stream": { - "example": { - "day": "20260618", - "event": "observing", - "segment": "143022_300", - "tract": "observe", - "ts": 1781803200000 - }, - "schema": { - "$ref": "#/components/schemas/CallosumEvent" - } - } - }, - "description": "Callosum event stream. Data frames carry CallosumEvent JSON; heartbeat frames are comments; error frames carry Error JSON.", - "x-sse-error-frame": { - "description": "In-stream `event: error` frames emit only these. auth_key_invalid is excluded here because key validity is established at stream open (routes.py:259-261); mid-stream re-checks only cover observer missing/revoked/disabled (routes.py:281-292).", - "schema": { - "$ref": "#/components/schemas/Error" - }, - "x-reason-codes": [ - "auth_required", - "feature_unavailable", - "pl_revoked" - ] - }, - "x-sse-frame-kinds": { - "classification": "closed", - "id": "observer.callosumStream.sse_frames", - "unknown_value_behavior": "reject", - "values": [ - "data", - "error", - "heartbeat" - ] - } - }, - "401": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - }, - "description": "Stream-open observer auth failure; codes include 401 and 403 outcomes.", - "x-reason-codes": [ - "auth_key_invalid", - "auth_required", - "feature_unavailable", - "pl_revoked" - ] - } - }, - "summary": "Stream Callosum events", - "tags": [ - "observer" - ] - } - }, - "/app/observer/ingest": { + "/app/devices/ingest": { "post": { - "description": "Upload one capture segment as multipart form data and trigger local observe processing. Media parts must use a registry container format \u2014 audio: flac (audio/flac), opus (audio/opus), ogg (audio/ogg), m4a (audio/mp4), mp3 (audio/mpeg), wav (audio/wav); video: webm (video/webm), mp4 (video/mp4), mov (video/quicktime); image: png (image/png), jpg (image/jpeg), jpeg (image/jpeg), heic (image/heic), heif (image/heif), gif (image/gif), webp (image/webp), tiff (image/tiff). Headerless raw streams are not accepted as media containers; raw PCM must be WAV-wrapped before upload. Video segment timestamps are boundary-relative real capture offsets (seconds from the segment start), never synthetic frame indices. Duplicate identity is derived from the segment directory, with terminal processing proof holding absent legacy raw media and a journal-authored ingest manifest adding hash-strength when present, not from the observer history log. `collision` means conflicting content at the requested key (same stored filename, different bytes); an occupied but non-conflicting key joins that segment. Reserved names such as `stream.json` and `ingest.json` are never written from client bytes and never appear in segment listings as held. Every `duplicate` appends a corroborating history record so `/ingest/segments/` can prove the held files by filename and sha.", + "description": "Upload one capture segment from a linked device as multipart form data. Identity is the linked-device mTLS certificate. The body has one JSON `envelope` part plus repeated `files` parts. Envelope grammar is `core/crates/solstone-core/src/contract/schemas/protocol.schema.json` (required `day`, `segment`, `files`; optional `source`, `meta`; `stream` and `observer` are forbidden). Each multipart part is at most 64 MiB; the total request is at most 128 MiB.", "operationId": "observer.ingestUpload", "parameters": [ { - "description": "Observer handle. Preferred over Authorization when present.", - "in": "header", - "name": "X-Solstone-Observer", - "required": false, - "schema": { - "type": "string" - } - }, - { - "description": "Bearer observer handle.", + "description": "Linked-device ingest protocol version. Must be 3. Values less than 3 are rejected with HTTP 426 Upgrade Required; values greater than 3 are rejected as unsupported. Identity is the linked-device mTLS certificate, not a request header.", "in": "header", - "name": "Authorization", - "required": false, + "name": "X-Solstone-Protocol-Version", + "required": true, "schema": { - "type": "string" + "const": 3, + "type": "integer" } } ], "requestBody": { "content": { "multipart/form-data": { - "example": { - "day": "20260618", - "files": [ - "screen.png", - "audio.flac" - ], - "host": "archon", - "meta": "{\"facet\":\"work\"}", - "platform": "linux", - "segment": "143022_300" - }, "schema": { - "additionalProperties": true, "properties": { - "day": { + "envelope": { + "description": "JSON object. Grammar: core/crates/solstone-core/src/contract/schemas/protocol.schema.json", + "format": "json", "type": "string" }, "files": { @@ -566,23 +274,10 @@ "type": "string" }, "type": "array" - }, - "host": { - "type": "string" - }, - "meta": { - "type": "string" - }, - "platform": { - "type": "string" - }, - "segment": { - "type": "string" } }, "required": [ - "segment", - "day", + "envelope", "files" ], "type": "object" @@ -599,7 +294,7 @@ "duplicate": { "summary": "Duplicate segment", "value": { - "existing_segment": "143022_300", + "existing_segment": "143000_1", "message": "All files already received", "status": "duplicate" } @@ -609,10 +304,9 @@ "value": { "bytes": 524288, "files": [ - "screen.png", - "audio.flac" + "audio.m4a" ], - "segment": "143022_300", + "segment": "143000_1", "status": "ok" } } @@ -626,6 +320,9 @@ "existing_segment": { "type": "string" }, + "file_descriptors": { + "type": "array" + }, "files": { "items": { "type": "string" @@ -635,6 +332,10 @@ "message": { "type": "string" }, + "meta": { + "additionalProperties": true, + "type": "object" + }, "segment": { "type": "string" }, @@ -646,15 +347,9 @@ "ok", "duplicate", "collision", - "conflict", "failed" ], - "type": "string", - "x-vocabulary": { - "classification": "closed", - "id": "observer.ingestUpload.status", - "unknown_value_behavior": "reject" - } + "type": "string" } }, "required": [ @@ -664,7 +359,7 @@ } } }, - "description": "Upload accepted, collision-adjusted, or duplicate. On `duplicate`, `existing_segment` is the authoritative stored segment key the client must adopt (do not re-upload); the server also appends a history record so the segment listing corroborates the duplicate. On `collision`, the requested key held conflicting content (same stored filename, different bytes), and the returned `segment` is the authoritative remapped key the client must adopt for subsequent references. An occupied key without conflicting content is not a collision; the upload joins that segment." + "description": "Upload accepted, collision-adjusted, or duplicate." }, "400": { "content": { @@ -676,24 +371,14 @@ }, "description": "Upload request failed validation.", "x-reason-codes": [ - "ingest_no_files", - "invalid_day", - "invalid_segment_or_stream", - "missing_required_field" - ] - }, - "401": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - }, - "description": "Observer handle missing or invalid.", - "x-reason-codes": [ - "auth_key_invalid", - "auth_required" + "protocol_version_required", + "protocol_version_malformed", + "field_missing", + "envelope_invalid", + "day_invalid", + "segment_invalid", + "multipart_malformed", + "multipart_part_too_large" ] }, "403": { @@ -704,10 +389,9 @@ } } }, - "description": "Observer is disabled or revoked.", + "description": "A linked-device identity is required.", "x-reason-codes": [ - "feature_unavailable", - "pl_revoked" + "linked_device_required" ] }, "409": { @@ -718,12 +402,12 @@ } } }, - "description": "Uploaded sidecar metadata conflicts with an existing segment. The journal already holds different bytes under the named filename; strip the conflicting file and re-send. The response names `conflicting_files` and the held `existing_segment`.", + "description": "Held sidecar bytes conflict.", "x-reason-codes": [ - "ingest_sidecar_conflict" + "content_conflict" ] }, - "422": { + "413": { "content": { "application/json": { "schema": { @@ -731,12 +415,9 @@ } } }, - "description": "Uploaded contract-covered file failed journal contract validation.", - "x-reason-codes": [ - "ingest_contract_invalid" - ] + "description": "Request exceeds the 128 MiB total-body limit." }, - "500": { + "426": { "content": { "application/json": { "schema": { @@ -744,12 +425,13 @@ } } }, - "description": "Uploaded file could not be stored.", + "description": "Protocol version is not 3.", "x-reason-codes": [ - "ingest_storage_failed" + "protocol_version_legacy", + "protocol_version_future" ] }, - "507": { + "500": { "content": { "application/json": { "schema": { @@ -757,100 +439,75 @@ } } }, - "description": "No segment slot was available after collision handling.", - "x-reason-codes": [ - "ingest_storage_failed" - ] + "description": "Journal write or notification failed." } }, - "summary": "Upload observer segment files", + "summary": "Upload one capture segment from a linked device", "tags": [ "observer" ] } }, - "/app/observer/ingest/event": { - "post": { - "description": "Relay an observer-originated event onto the local Callosum bus.", - "operationId": "observer.ingestEvent", + "/app/devices/ingest/manifest": { + "get": { + "description": "Return days with uploaded linked-device segment history.", + "operationId": "observer.ingestManifest", "parameters": [ { - "description": "Observer handle. Preferred over Authorization when present.", + "description": "Linked-device ingest protocol version. Must be 3. Values less than 3 are rejected with HTTP 426 Upgrade Required; values greater than 3 are rejected as unsupported. Identity is the linked-device mTLS certificate, not a request header.", "in": "header", - "name": "X-Solstone-Observer", - "required": false, + "name": "X-Solstone-Protocol-Version", + "required": true, "schema": { - "type": "string" + "const": 3, + "type": "integer" } }, { - "description": "Bearer observer handle.", - "in": "header", - "name": "Authorization", + "description": "Optional device-chosen sub-stream discriminator.", + "in": "query", + "name": "source", "required": false, "schema": { "type": "string" } } ], - "requestBody": { - "content": { - "application/json": { - "example": { - "event": "status", - "state": "recording", - "tract": "observe" - }, - "schema": { - "additionalProperties": true, - "properties": { - "event": { - "type": "string" - }, - "tract": { - "type": "string" - } - }, - "required": [ - "tract", - "event" - ], - "type": "object" - } - } - }, - "required": true - }, "responses": { "200": { "content": { "application/json": { "example": { - "status": "ok" + "days": { + "20260815": { + "segments": 3 + } + } }, "schema": { "additionalProperties": true, "properties": { - "status": { - "enum": [ - "ok" - ], - "type": "string", - "x-vocabulary": { - "classification": "closed", - "id": "observer.status.ok", - "unknown_value_behavior": "reject" - } + "days": { + "additionalProperties": { + "additionalProperties": true, + "properties": { + "segments": { + "type": "integer" + } + }, + "type": "object" + }, + "type": "object" } }, "required": [ - "status" + "days" ], "type": "object" } } }, - "description": "Event relayed." + "description": "Available manifest days keyed by YYYYMMDD." }, "400": { "content": { @@ -860,12 +517,13 @@ } } }, - "description": "Tract or event was missing.", + "description": "Protocol version missing or malformed.", "x-reason-codes": [ - "missing_required_field" + "protocol_version_required", + "protocol_version_malformed" ] }, - "401": { + "403": { "content": { "application/json": { "schema": { @@ -873,13 +531,12 @@ } } }, - "description": "Observer handle missing or invalid.", + "description": "A linked-device identity is required.", "x-reason-codes": [ - "auth_key_invalid", - "auth_required" + "linked_device_required" ] }, - "403": { + "426": { "content": { "application/json": { "schema": { @@ -887,40 +544,32 @@ } } }, - "description": "Observer is disabled or revoked.", + "description": "Protocol version is not 3.", "x-reason-codes": [ - "feature_unavailable", - "pl_revoked" + "protocol_version_legacy", + "protocol_version_future" ] } }, - "summary": "Relay observer event", + "summary": "List linked-device ingest manifest days", "tags": [ "observer" ] } }, - "/app/observer/ingest/segments/{day}": { + "/app/devices/ingest/manifest/{day}": { "get": { - "description": "Return segment upload history for one day. Protocol version 2 and newer receive a collection envelope; older or absent protocol headers receive a legacy bare array. Per-file `status` is `present` (file at its recorded path), `processed` (the recorded raw media is absent because journal processing terminally consumed it, proven by a validated `solstone.processing.v1` record in the same-stem sidecar at the recorded segment path), or `missing` (not found). A local file is proven held by the journal only when its listing entry is `present` or `processed` AND the entry `sha256` matches the local file; a `missing` status \u2014 or any local file the listing does not positively account for \u2014 is needs-upload, and the client must never delete a local copy on that basis. Confirm-before-delete matches every upload-eligible local file to a listing entry per (filename, sha256) \u2014 the listing filename is `submitted_name` when present, else `name` \u2014 and deletes only on a proven-held match. Re-uploading the same bytes for a `processed` entry returns `duplicate` and creates no new segment directory. A peer that does not recognize `processed` degrades safely: it treats the file as not-held, re-uploads, and the server answers `duplicate`. `submitted_name` appears only when the stored filename differs from the submitted one; segment-level `original_key` carries the client's originally requested segment key when a collision remapped it.", - "operationId": "observer.ingestSegments", + "description": "Return file names, sizes, sha256 hashes, and custody status for every segment on one day.", + "operationId": "observer.ingestManifestDay", "parameters": [ { - "description": "Observer handle. Preferred over Authorization when present.", - "in": "header", - "name": "X-Solstone-Observer", - "required": false, - "schema": { - "type": "string" - } - }, - { - "description": "Bearer observer handle.", + "description": "Linked-device ingest protocol version. Must be 3. Values less than 3 are rejected with HTTP 426 Upgrade Required; values greater than 3 are rejected as unsupported. Identity is the linked-device mTLS certificate, not a request header.", "in": "header", - "name": "Authorization", - "required": false, + "name": "X-Solstone-Protocol-Version", + "required": true, "schema": { - "type": "string" + "const": 3, + "type": "integer" } }, { @@ -933,96 +582,73 @@ } }, { - "description": "Fallback stream for legacy observer history rows.", + "description": "Optional device-chosen sub-stream discriminator.", "in": "query", - "name": "stream", + "name": "source", "required": false, "schema": { "type": "string" } - }, - { - "description": ">= current protocol version returns the collection envelope; lower or absent returns the legacy bare array.", - "in": "header", - "name": "X-Solstone-Protocol-Version", - "required": false, - "schema": { - "type": "integer" - } } ], "responses": { "200": { "content": { "application/json": { - "examples": { - "legacy": { - "summary": "Legacy bare array", - "value": [ - { - "files": [ - { - "name": "screen.png", - "sha256": "5f70bf18a086007016bb522ec180fd0b", - "size": 2048, - "status": "present" - } - ], - "key": "143022_300", - "observed": true - } - ] - }, - "v2": { - "summary": "Protocol v2 envelope", - "value": { - "items": [ + "example": { + "day": "20260815", + "segments": { + "143000": { + "files": [ { - "files": [ - { - "name": "screen.png", - "sha256": "5f70bf18a086007016bb522ec180fd0b", - "size": 2048, - "status": "present" - } - ], - "key": "143022_300", - "observed": true + "name": "audio.m4a", + "sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "size": 4096, + "status": "present" } - ], - "protocol_version": 2, - "total": 1 + ] } - } + }, + "version": 1 }, "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/SegmentsEnvelope" + "additionalProperties": true, + "properties": { + "day": { + "type": "string" }, - { - "items": { - "$ref": "#/components/schemas/SegmentItem" + "segments": { + "additionalProperties": { + "additionalProperties": true, + "properties": { + "files": { + "items": { + "$ref": "#/components/schemas/SegmentFile" + }, + "type": "array" + } + }, + "required": [ + "files" + ], + "type": "object" }, - "type": "array" + "type": "object" + }, + "version": { + "type": "integer" } - ] + }, + "required": [ + "version", + "day", + "segments" + ], + "type": "object" } } }, - "description": "Segment verification response.", - "x-vocabularies": { - "X-Solstone-Protocol-Version": { - "absent_or_unparseable": 1, - "classification": "extensible_integer", - "current": 2, - "supported_response_variants": [ - 1, - 2 - ], - "unknown_value_behavior": "version_greater_or_equal_current_uses_v2" - } - } + "description": "Day transfer manifest." }, "400": { "content": { @@ -1032,12 +658,14 @@ } } }, - "description": "Day path parameter was invalid.", + "description": "Day path parameter or protocol version was invalid.", "x-reason-codes": [ - "invalid_day" + "day_invalid", + "protocol_version_required", + "protocol_version_malformed" ] }, - "401": { + "403": { "content": { "application/json": { "schema": { @@ -1045,13 +673,12 @@ } } }, - "description": "Observer handle missing or invalid.", + "description": "A linked-device identity is required.", "x-reason-codes": [ - "auth_key_invalid", - "auth_required" + "linked_device_required" ] }, - "403": { + "426": { "content": { "application/json": { "schema": { @@ -1059,106 +686,81 @@ } } }, - "description": "Observer is disabled or revoked.", + "description": "Protocol version is not 3.", "x-reason-codes": [ - "feature_unavailable", - "pl_revoked" + "protocol_version_legacy", + "protocol_version_future" ] } }, - "summary": "List uploaded observer segments", + "summary": "Read linked-device transfer manifest for a day", "tags": [ "observer" ] } }, - "/app/observer/register": { - "post": { - "description": "Register a trusted local or paired-link observer and lock its stream identity.", - "operationId": "observer.register", - "requestBody": { - "content": { - "application/json": { - "example": { - "hostname": "archon", - "label": "Archon desktop", - "platform": "linux", - "stream_type": "desktop", - "version": "1.4.0" - }, - "schema": { - "additionalProperties": true, - "properties": { - "hostname": { - "type": "string" - }, - "label": { - "type": "string" - }, - "platform": { - "type": "string" - }, - "stream_type": { - "type": "string" - }, - "version": { - "type": "string" - } - }, - "required": [ - "platform", - "hostname", - "stream_type", - "version" - ], - "type": "object" - } + "/app/devices/ingest/segments/{day}": { + "get": { + "description": "Return segment upload history for one day as a protocol-version-3 collection envelope. Per-file `status` is `present`, `processed`, or `missing`. `submitted_name` appears only when the stored filename differs from the submitted one; segment-level `original_key` carries the client's originally requested segment key when a collision remapped it.", + "operationId": "observer.ingestSegments", + "parameters": [ + { + "description": "Linked-device ingest protocol version. Must be 3. Values less than 3 are rejected with HTTP 426 Upgrade Required; values greater than 3 are rejected as unsupported. Identity is the linked-device mTLS certificate, not a request header.", + "in": "header", + "name": "X-Solstone-Protocol-Version", + "required": true, + "schema": { + "const": 3, + "type": "integer" } }, - "required": true - }, + { + "description": "YYYYMMDD day.", + "in": "path", + "name": "day", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Optional device-chosen sub-stream discriminator.", + "in": "query", + "name": "source", + "required": false, + "schema": { + "type": "string" + } + } + ], "responses": { "200": { "content": { "application/json": { "example": { - "ingest_url": "/app/observer/ingest", - "key": "x7J7k2observerHandle", - "name": "archon", - "prefix": "x7J7k2ob", - "protocol_version": 2 - }, - "schema": { - "additionalProperties": true, - "properties": { - "ingest_url": { - "type": "string" - }, - "key": { - "type": "string" - }, - "name": { - "type": "string" - }, - "prefix": { - "type": "string" - }, - "protocol_version": { - "type": "integer" + "items": [ + { + "files": [ + { + "name": "audio.m4a", + "sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "size": 4096, + "status": "present" + } + ], + "key": "143000", + "observed": true } - }, - "required": [ - "key", - "prefix", - "name", - "ingest_url", - "protocol_version" ], - "type": "object" + "protocol_version": 3, + "total": 1 + }, + "schema": { + "$ref": "#/components/schemas/SegmentsEnvelope" } } }, - "description": "Observer registration descriptor." + "description": "Segment verification response." }, "400": { "content": { @@ -1168,10 +770,11 @@ } } }, - "description": "Register request failed validation.", + "description": "Day path parameter or protocol version was invalid.", "x-reason-codes": [ - "invalid_segment_or_stream", - "missing_required_field" + "day_invalid", + "protocol_version_required", + "protocol_version_malformed" ] }, "403": { @@ -1182,12 +785,12 @@ } } }, - "description": "Register request did not satisfy local/device admission or targeted a stream already bound to another device.", + "description": "A linked-device identity is required.", "x-reason-codes": [ - "local_request_only" + "linked_device_required" ] }, - "500": { + "426": { "content": { "application/json": { "schema": { @@ -1195,291 +798,46 @@ } } }, - "description": "Observer record could not be saved.", + "description": "Protocol version is not 3.", "x-reason-codes": [ - "settings_operation_failed" + "protocol_version_legacy", + "protocol_version_future" ] } }, - "summary": "Register observer", + "summary": "List uploaded linked-device segments", "tags": [ "observer" ] } - }, - "/sse/events": { - "get": { - "description": "Open the root Server-Sent Events feed for Callosum events. Frames: heartbeat comments `: heartbeat\\n\\n`; data frames `data: {json}\\n\\n` carrying CallosumEvent JSON for all tracts. This stream emits no `event: error` frames.", - "operationId": "callosum.rootEvents", - "responses": { - "200": { - "content": { - "text/event-stream": { - "example": { - "tract": "observe", - "event": "status", - "ts": 1781803200000 - }, - "schema": { - "$ref": "#/components/schemas/CallosumEvent" - } - } - }, - "description": "Callosum event stream. Heartbeat frames are comments `: heartbeat\\n\\n`; data frames are `data: {json}\\n\\n` and carry CallosumEvent JSON for all tracts.", - "x-sse-frame-kinds": { - "classification": "closed", - "id": "callosum.rootEvents.sse_frames", - "unknown_value_behavior": "reject", - "values": [ - "data", - "heartbeat" - ] - } - }, - "403": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - }, - "description": "Access gate rejected a revoked paired-link identity before stream open.", - "x-reason-codes": [ - "pl_revoked" - ] - } - }, - "summary": "Stream root Callosum events", - "tags": [ - "callosum" - ] - } } }, - "x-callosum-registry": { - "activity": [ - "live", - "recorded" - ], - "cortex": [ - "request", - "start", - "thinking", - "tool_start", - "tool_end", - "finish", - "error", - "talent_updated", - "info", - "status", - "cancel", - "dry_run", - "progress", - "text_delta", - "tool_budget_exhausted", - "budget_escalation" - ], - "importer": [ - "started", - "status", - "completed", - "error", - "file_imported", - "enrichment_ready" - ], - "link": [ - "pair_complete", - "last_seen", - "stream_reset" - ], - "logs": [ - "exec", - "line", - "exit" - ], - "navigate": [ - "request" - ], - "notification": [ - "*" - ], - "observe": [ - "status", - "observing", - "detected", - "described", - "transcribed", - "observed", - "memory_throttle_started", - "memory_throttle_completed" - ], - "storage": [ - "warning" - ], - "supervisor": [ - "started", - "stopped", - "restarting", - "status", - "queue", - "scheduled", - "provider_runtime", - "request", - "restart", - "drain", - "skipped", - "sync_conflict" - ], - "support": [ - "proactive_suggestion" - ], - "think": [ - "started", - "status", - "group_started", - "group_completed", - "talent_started", - "talent_completed", - "completed", - "segments_started", - "segments_completed", - "memory_throttle_started", - "memory_throttle_completed", - "daily_complete" - ], - "work": [ - "talent_queued", - "talent_spawned", - "talent_finished", - "talent_errored", - "result", - "reflection_ready", - "support_draft", - "support_submit_claim" - ] - }, "x-vocabularies": { - "callosum.tract_event": { - "classification": "extensible", - "known_registry": { - "activity": [ - "live", - "recorded" - ], - "cortex": [ - "request", - "start", - "thinking", - "tool_start", - "tool_end", - "finish", - "error", - "talent_updated", - "info", - "status", - "cancel", - "dry_run", - "progress", - "text_delta", - "tool_budget_exhausted", - "budget_escalation" - ], - "importer": [ - "started", - "status", - "completed", - "error", - "file_imported", - "enrichment_ready" - ], - "link": [ - "pair_complete", - "last_seen", - "stream_reset" - ], - "logs": [ - "exec", - "line", - "exit" - ], - "navigate": [ - "request" - ], - "notification": [ - "*" - ], - "observe": [ - "status", - "observing", - "detected", - "described", - "transcribed", - "observed", - "memory_throttle_started", - "memory_throttle_completed" - ], - "storage": [ - "warning" - ], - "supervisor": [ - "started", - "stopped", - "restarting", - "status", - "queue", - "scheduled", - "provider_runtime", - "request", - "restart", - "drain", - "skipped", - "sync_conflict" - ], - "support": [ - "proactive_suggestion" - ], - "think": [ - "started", - "status", - "group_started", - "group_completed", - "talent_started", - "talent_completed", - "completed", - "segments_started", - "segments_completed", - "memory_throttle_started", - "memory_throttle_completed", - "daily_complete" - ], - "work": [ - "talent_queued", - "talent_spawned", - "talent_finished", - "talent_errored", - "result", - "reflection_ready", - "support_draft", - "support_submit_claim" - ] - }, - "unknown_value_behavior": "preserve" + "SegmentFile.status": { + "classification": "closed", + "id": "SegmentFile.status", + "source_pointer": "/components/schemas/SegmentFile/properties/status", + "unknown_value_behavior": "reject", + "values": [ + "present", + "missing", + "processed" + ] }, - "callosum.work.event": { + "observer.ingestUpload.status": { "classification": "closed", - "id": "callosum.work.event", - "source_pointer": "/x-callosum-registry/work", + "id": "observer.ingestUpload.status", + "source_pointers": [ + "/paths/~1app~1devices~1ingest/post/responses/200/content/application~1json/schema/properties/status", + "/paths/~1app~1devices~1ingest/post/responses/409" + ], "unknown_value_behavior": "reject", "values": [ - "talent_queued", - "talent_spawned", - "talent_finished", - "talent_errored", - "result", - "reflection_ready", - "support_draft", - "support_submit_claim" + "ok", + "duplicate", + "collision", + "conflict", + "failed" ] } } diff --git a/vendor/observer-client-contract/vectors.json b/vendor/observer-client-contract/vectors.json index 0a32ae2..b00b3da 100644 --- a/vendor/observer-client-contract/vectors.json +++ b/vendor/observer-client-contract/vectors.json @@ -1,430 +1,72 @@ { - "schema": "solstone.observer-client-contract-vectors.v1", - "tmp_token": "$TMP", + "schema": "solstone.observer-client-contract-vectors.v2", "vectors": [ - { - "decision": { - "action": "pass_through", - "frame_kind": "data", - "kind": "sse_frame", - "unknown_event_behavior": "preserve" - }, - "fixture_id": "recorded.sse.root.data_unknown_event", - "frame_kind": "data", - "id": "callosum.rootEvents.sse.data_unknown_event", - "kind": "recorded", - "pointer_hashes": { - "/event": "53331ee8a3d89d5e956086b9c9ffaa709efef795a17c43bbc9666bb622947f8e", - "/tract": "7d75b7beeeecfafd1fdf1c465813c01f0c794acf8e1b60a65b74a8675ad64215" - }, - "pointers": [ - "/tract", - "/event" - ] - }, - { - "decision": { - "action": "ignore_keepalive", - "frame_kind": "heartbeat", - "kind": "sse_frame" - }, - "fixture_id": "recorded.sse.root.heartbeat", - "frame_kind": "heartbeat", - "id": "callosum.rootEvents.sse.heartbeat", - "kind": "recorded", - "pointer_hashes": { - "": "d9526400820634cc9e4cd9ecc2b579ed41723071a82af69f696eda332bc17a89" - }, - "pointers": [ - "" - ] - }, { "decision": { "accepted": true, - "auth_form": "authorization_bearer", - "kind": "auth_header_form", - "precedence": "x_solstone_observer_preferred_when_both_present" - }, - "fixture_id": "recorded.auth.bearer.segments", - "id": "observer.auth.bearer", - "kind": "recorded", - "observed_status": 200, - "pointer_hashes": { - "/items": "37517e5f3dc66819f61f5a7bb8ace1921282415f10551d2defa5c3eb0985b570", - "/protocol_version": "53c234e5e8472b6ac51c1ae1cab3fe06fad053beb8ebfd8977b010655bfdd3c3", - "/total": "9a271f2a916b0b6ee6cecb2426f0b3206ef074578be55d9bc94f6f3fe3ab86aa" - }, - "pointers": [ - "/items", - "/total", - "/protocol_version" - ] - }, - { - "decision": { - "accepted": true, - "auth_form": "x_solstone_observer", - "kind": "auth_header_form", - "precedence": "x_solstone_observer_preferred_when_both_present" - }, - "fixture_id": "recorded.auth.handle.segments", - "id": "observer.auth.handle", - "kind": "recorded", - "observed_status": 200, - "pointer_hashes": { - "/items": "37517e5f3dc66819f61f5a7bb8ace1921282415f10551d2defa5c3eb0985b570", - "/protocol_version": "53c234e5e8472b6ac51c1ae1cab3fe06fad053beb8ebfd8977b010655bfdd3c3", - "/total": "9a271f2a916b0b6ee6cecb2426f0b3206ef074578be55d9bc94f6f3fe3ab86aa" - }, - "pointers": [ - "/items", - "/total", - "/protocol_version" - ] - }, - { - "decision": { - "action": "dispatch_callosum_event", - "frame_kind": "data", - "kind": "sse_frame", - "unknown_event_behavior": "preserve" - }, - "fixture_id": "recorded.sse.observer.data", - "frame_kind": "data", - "id": "observer.callosumStream.sse.data", - "kind": "recorded", - "pointer_hashes": { - "/event": "bedca5f5c3450db9f398aa7a86904a16e6fa794bdb256baadca050d80d4e7534", - "/tract": "137c165f7fe3ec3dfb24fffbefea32ad3186836233e02ebe226870cc6c1bd470" - }, - "pointers": [ - "/tract", - "/event" - ] - }, - { - "decision": { - "action": "surface_error_and_close", - "frame_kind": "error", - "kind": "sse_frame", - "reason_code": "pl_revoked" - }, - "fixture_id": "recorded.sse.observer.error", - "frame_kind": "error", - "id": "observer.callosumStream.sse.error", - "kind": "recorded", - "pointer_hashes": { - "/reason_code": "dcc3c59606299c038b842d61569ff85c6b57b9da41032257dc4e57a016972018" - }, - "pointers": [ - "/reason_code" - ] - }, - { - "decision": { - "action": "ignore_keepalive", - "frame_kind": "heartbeat", - "kind": "sse_frame" - }, - "fixture_id": "recorded.sse.observer.heartbeat", - "frame_kind": "heartbeat", - "id": "observer.callosumStream.sse.heartbeat", - "kind": "recorded", - "pointer_hashes": { - "": "d9526400820634cc9e4cd9ecc2b579ed41723071a82af69f696eda332bc17a89" - }, - "pointers": [ - "" - ] - }, - { - "decision": { - "holding_by_status": { - "missing": "not_held", - "present": "held", - "processed": "held" - }, - "kind": "custody_status", - "unknown_status": "reject" - }, - "fixture_id": "recorded.segments.custody_statuses", - "id": "observer.ingestSegments.custody_statuses", - "kind": "recorded", - "observed_status": 200, - "pointer_hashes": { - "/items/0/files/0/status": "16412508f5322b160fca89f86f8cd0f11d71deeb6dc114bd2fbfec57731d95e3", - "/items/0/files/1/status": "2781cbb6c3c0ce779a2e503364db12cb9f92bad3c740951e24e919e58b9e1186", - "/items/0/files/2/status": "87f7439876e4033adb5735dd9e8a4031ef2be2239574b1a83398efca11784151" - }, - "pointers": [ - "/items/0/files/0/status", - "/items/0/files/1/status", - "/items/0/files/2/status" - ] - }, - { - "decision": { - "kind": "custody_unknown", - "status": "unknown", - "unknown_status": "reject" + "http_status": 200, + "kind": "ingest_status", + "status": "ok" }, - "fixture_id": "declared.observer.ingestSegments.custody_unknown_rejected", - "id": "observer.ingestSegments.custody_unknown_rejected", + "fixture_id": "declared.observer.ingestUpload.status.ok", + "id": "observer.ingestUpload.status.ok", "kind": "declared", - "pointer_hashes": { - "/items/0/files/0/status": "53331ee8a3d89d5e956086b9c9ffaa709efef795a17c43bbc9666bb622947f8e" - }, "pointers": [ - "/items/0/files/0/status" + "/status" ] }, { "decision": { - "expected": "total_equals_items_length", - "kind": "envelope_integrity", - "valid": false + "accepted": true, + "http_status": 200, + "kind": "ingest_status", + "status": "duplicate" }, - "fixture_id": "declared.observer.ingestSegments.envelope_total_mismatch", - "id": "observer.ingestSegments.envelope_total_mismatch", + "fixture_id": "declared.observer.ingestUpload.status.duplicate", + "id": "observer.ingestUpload.status.duplicate", "kind": "declared", - "pointer_hashes": { - "/total": "4355a46b19d348dc2f57c046f8ef63d4538ebb936000f3c9ee954a27460dd865" - }, "pointers": [ - "/total" - ] - }, - { - "decision": { - "absent_or_unparseable_uses": 1, - "header": "absent", - "kind": "protocol_variant", - "parsed_version": 1, - "response_variant": "legacy_array" - }, - "fixture_id": "recorded.segments.legacy.absent_header", - "id": "observer.ingestSegments.legacy_array.absent_header", - "kind": "recorded", - "observed_status": 200, - "pointer_hashes": { - "": "37517e5f3dc66819f61f5a7bb8ace1921282415f10551d2defa5c3eb0985b570" - }, - "pointers": [ - "" - ] - }, - { - "decision": { - "absent_or_unparseable_uses": 1, - "header": "unparseable", - "kind": "protocol_variant", - "parsed_version": 1, - "response_variant": "legacy_array" - }, - "fixture_id": "recorded.segments.legacy.unparseable_header", - "id": "observer.ingestSegments.legacy_array.unparseable_header", - "kind": "recorded", - "observed_status": 200, - "pointer_hashes": { - "": "37517e5f3dc66819f61f5a7bb8ace1921282415f10551d2defa5c3eb0985b570" - }, - "pointers": [ - "" - ] - }, - { - "decision": { - "fallback": "name", - "kind": "submitted_name_fallback", - "submitted_name_present": false - }, - "fixture_id": "recorded.segments.submitted_name_omitted", - "id": "observer.ingestSegments.submitted_name_fallback", - "kind": "recorded", - "observed_status": 200, - "pointer_hashes": { - "/items/0/files/0/name": "7773c30a843bf694ca3b00e7ba5ead99e6042ba78dadfe75431c0138f5b78287" - }, - "pointers": [ - "/items/0/files/0/name" - ] - }, - { - "decision": { - "current_protocol_version": 2, - "header": "2", - "kind": "protocol_variant", - "parsed_version": 2, - "response_variant": "v2_envelope" - }, - "fixture_id": "recorded.segments.v2.envelope", - "id": "observer.ingestSegments.v2_envelope", - "kind": "recorded", - "observed_status": 200, - "pointer_hashes": { - "/items": "37517e5f3dc66819f61f5a7bb8ace1921282415f10551d2defa5c3eb0985b570", - "/protocol_version": "53c234e5e8472b6ac51c1ae1cab3fe06fad053beb8ebfd8977b010655bfdd3c3", - "/total": "9a271f2a916b0b6ee6cecb2426f0b3206ef074578be55d9bc94f6f3fe3ab86aa" - }, - "pointers": [ - "/items", - "/total", - "/protocol_version" + "/status" ] }, { "decision": { "accepted": true, - "client_action": "adopt_remapped_segment", "http_status": 200, "kind": "ingest_status", - "original_key_source": "segment_original", - "status": "collision", - "stored_key_precedence": [ - "segment", - "segment_original" - ], - "stored_key_source": "segment" + "status": "collision" }, - "fixture_id": "recorded.ingestUpload.collision", + "fixture_id": "declared.observer.ingestUpload.status.collision", "id": "observer.ingestUpload.status.collision", - "kind": "recorded", - "observed_status": 200, - "pointer_hashes": { - "/segment": "fbd10688be6ee217abeacd21b42137e77be6a7445d4d7d30bf8c2e8b5f3453d9", - "/segment_original": "a7c937bcf529de76a07597c2f37951b1b775ae759c0446eb7335960f15c86a1b", - "/status": "130b3c5ac14097b6f596f0470a7a985e0582b8fca45e668fc0040590c306b305" - }, + "kind": "declared", "pointers": [ - "/status", - "/segment", - "/segment_original" + "/status" ] }, { "decision": { "accepted": false, - "client_action": "preserve_local_and_surface_conflict", "http_status": 409, "kind": "ingest_status", - "status": "conflict", - "stored_key_precedence": [ - "existing_segment" - ], - "stored_key_source": "existing_segment" + "status": "conflict" }, - "fixture_id": "recorded.ingestUpload.conflict", + "fixture_id": "declared.observer.ingestUpload.status.conflict", "id": "observer.ingestUpload.status.conflict", - "kind": "recorded", - "observed_status": 409, - "pointer_hashes": { - "/conflicting_files": "41e76925274ddd172048699de40403b63a622b77ae075614db1f0039ba91af0b", - "/existing_segment": "a7c937bcf529de76a07597c2f37951b1b775ae759c0446eb7335960f15c86a1b", - "/reason_code": "c9cf4aec1407ba979c729691832dc66ddbfcebc87b3dd0b030de3ff8b2f6500f", - "/status": "2ebfc71bdbe3659bffaa77f7dd86fad4bf90586c93ad995d676dc1e5d4165b0b" - }, - "pointers": [ - "/status", - "/reason_code", - "/conflicting_files", - "/existing_segment" - ] - }, - { - "decision": { - "accepted": true, - "client_action": "adopt_existing_segment_without_reupload", - "http_status": 200, - "kind": "ingest_status", - "status": "duplicate", - "stored_key_precedence": [ - "existing_segment" - ], - "stored_key_source": "existing_segment" - }, - "fixture_id": "recorded.ingestUpload.duplicate", - "id": "observer.ingestUpload.status.duplicate", - "kind": "recorded", - "observed_status": 200, - "pointer_hashes": { - "/existing_segment": "a7c937bcf529de76a07597c2f37951b1b775ae759c0446eb7335960f15c86a1b", - "/message": "0e36bda88535e7cb2adc89ff06e730fbb87ac70f227a20a0bc279798060f2194", - "/status": "d949ce0c1c1a0ca6a9b37ca25e0fbd04bf25f9788767975e54ad48b8d36cc335" - }, + "kind": "declared", "pointers": [ - "/status", - "/existing_segment", - "/message" + "/status" ] }, { "decision": { "accepted": false, - "client_action": "preserve_local_and_surface_failure", - "http_status": 422, + "http_status": 500, "kind": "ingest_status", - "status": "failed", - "stored_key_precedence": [], - "stored_key_source": null + "status": "failed" }, - "fixture_id": "recorded.ingestUpload.failed", + "fixture_id": "declared.observer.ingestUpload.status.failed", "id": "observer.ingestUpload.status.failed", - "kind": "recorded", - "observed_status": 422, - "pointer_hashes": { - "/failed_path": "d876240f5ba88940fb6ff0ba0142ff98deac01464429e87783648aabbdda1078", - "/reason_code": "9ad60b11f8bc9e9f5f55d955b9e5a00be65d59b15fd9f93cc25ecd5950d5bb3f", - "/status": "e9c6c7eab63b97ace1ed584f168beaba80eb77c85e4aef9d014e7242bef77f8e" - }, - "pointers": [ - "/status", - "/reason_code", - "/failed_path" - ] - }, - { - "decision": { - "accepted": true, - "client_action": "adopt_segment", - "http_status": 200, - "kind": "ingest_status", - "status": "ok", - "stored_key_precedence": [ - "segment" - ], - "stored_key_source": "segment" - }, - "fixture_id": "recorded.ingestUpload.ok", - "id": "observer.ingestUpload.status.ok", - "kind": "recorded", - "observed_status": 200, - "pointer_hashes": { - "/bytes": "aa67a169b0bba217aa0aa88a65346920c84c42447c36ba5f7ea65f422c1fe5d8", - "/files": "6959e999efaf728120be19b4161634a371611fe883b12033e02d97fd3f97641b", - "/segment": "a7c937bcf529de76a07597c2f37951b1b775ae759c0446eb7335960f15c86a1b", - "/status": "24a279376551117f31ed9d92797023d0f89b376a6392801c7d6626e4cb7877e5" - }, - "pointers": [ - "/status", - "/segment", - "/files", - "/bytes" - ] - }, - { - "decision": { - "kind": "closed_vocabulary_unknown", - "status": "unknown", - "unknown_value_behavior": "reject", - "vocabulary": "observer.ingestUpload.status" - }, - "fixture_id": "declared.observer.ingestUpload.status_unknown_rejected", - "id": "observer.ingestUpload.status_unknown_rejected", "kind": "declared", - "pointer_hashes": { - "/status": "53331ee8a3d89d5e956086b9c9ffaa709efef795a17c43bbc9666bb622947f8e" - }, "pointers": [ "/status" ]