field_ios

In-the-field app for Radroots on iOS
git clone https://radroots.dev/git/field_ios.git
Log | Files | Refs | README | LICENSE

commit c54d9855bf17af91b1d1040baa5465c45e5b907d
parent a813eec2cb7e5ce8f0b869bb582c130b717c6e9f
Author: triesap <tyson@radroots.org>
Date:   Thu, 10 Sep 2026 11:02:43 +0000

calendar: Qualify current shared wire fixtures

- Pin exact current calendar kinds, tags and source-zone fixture provenance
- Check strict signed admission and bounded exclusive UTC day coverage
- Prove real FFI publication, fresh SQLite admission and offline reopen
- Qualify exact generated consumers and complete standalone checks

Diffstat:
MTeraFFI/provenance.json | 52++++++++++++++++++++++++++--------------------------
MTeraFFI/source.lock | 4++--
MTeraFFI/source/aarch64-apple-darwin.json | 38++++++++++++++++++++++++++++----------
MTeraFFI/source/aarch64-apple-ios-sim.json | 38++++++++++++++++++++++++++++----------
MTeraFFI/source/aarch64-apple-ios.json | 38++++++++++++++++++++++++++++----------
Acore/crates/tera_core/src/runtime/product_surface/calendar_wire_tests.rs | 255+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcore/crates/tera_core/src/runtime/product_surface/today.rs | 4++++
Acore/crates/tera_ffi/tests/local_mvp/calendar_wire_runtime.rs | 190+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcore/crates/tera_ffi/tests/local_mvp_real_io.rs | 3+++
Acore/fixtures/calendar_wire.v1.json | 37+++++++++++++++++++++++++++++++++++++
Mrelease/provenance.json | 4++--
Mtest-fixtures/legacy-identifiers.v1.json | 16++++++++++++++++
12 files changed, 619 insertions(+), 60 deletions(-)

diff --git a/TeraFFI/provenance.json b/TeraFFI/provenance.json @@ -22,9 +22,9 @@ "sha256": "3ed9b7ece2f86e5b5d1ddc6ecbb97deb49c46ca2ea91a930eaec1b580232345c" }, { - "bytes": 73868864, + "bytes": 73868728, "path": "TeraFFI.xcframework/ios-arm64-simulator/libtera_ffi.a", - "sha256": "ed47907bc5ae7835596bf73ab17dad734a82989170c7ed34e491c8465646e528" + "sha256": "417da7d44cb3b3e69441673918d22f1aad54ab60c2b57d3f8335999f2ae98967" }, { "bytes": 71509, @@ -37,9 +37,9 @@ "sha256": "3ed9b7ece2f86e5b5d1ddc6ecbb97deb49c46ca2ea91a930eaec1b580232345c" }, { - "bytes": 73923032, + "bytes": 73922992, "path": "TeraFFI.xcframework/ios-arm64/libtera_ffi.a", - "sha256": "58d96142e77201061116064e83515aec0ae4ccfcd2e8974b11de4ea3345c43e1" + "sha256": "bf6ae1c6d05e1306e715ac40bba50290ceec17ab0dedaf30c8ee491e4f3ada5b" }, { "bytes": 42544, @@ -79,32 +79,32 @@ { "bytes": 20499440, "path": "native/aarch64-apple-darwin/libtera_ffi.dylib", - "sha256": "79f6d1340e4be49e059be5800b247ceaa01307151d188c2739a38e689346abf8" + "sha256": "40beed9f054eefec8f87ce75bd3157d74dfdbebbb0b90572ed324bf1aebd49d5" }, { - "bytes": 73868864, + "bytes": 73868728, "path": "native/aarch64-apple-ios-sim/libtera_ffi.a", - "sha256": "ed47907bc5ae7835596bf73ab17dad734a82989170c7ed34e491c8465646e528" + "sha256": "417da7d44cb3b3e69441673918d22f1aad54ab60c2b57d3f8335999f2ae98967" }, { - "bytes": 73923032, + "bytes": 73922992, "path": "native/aarch64-apple-ios/libtera_ffi.a", - "sha256": "58d96142e77201061116064e83515aec0ae4ccfcd2e8974b11de4ea3345c43e1" + "sha256": "bf6ae1c6d05e1306e715ac40bba50290ceec17ab0dedaf30c8ee491e4f3ada5b" }, { - "bytes": 71296, + "bytes": 72121, "path": "source/aarch64-apple-darwin.json", - "sha256": "42c76082f5a01d21c94d59001b442e04d6ccb4fb428536aaad06988d6b23e938" + "sha256": "7a240ff8cdd83a3969f9efa8c94fe87aa702a38670a9cfadc5ceb43134983445" }, { - "bytes": 71140, + "bytes": 71965, "path": "source/aarch64-apple-ios-sim.json", - "sha256": "b72fdce869c7f84db6e627939c3fbaee53077279f68b31eefd0f788d041ae841" + "sha256": "717f17889c8f844d0d1d8536e2a0ddf00c4a7e778b33ac55b225ead2a63b4b73" }, { - "bytes": 71136, + "bytes": 71961, "path": "source/aarch64-apple-ios.json", - "sha256": "ffa229ef3fd7e745364d59c5284a68111d0be1c7981a2b465f2b4d225d833fc0" + "sha256": "980c41d31200d854315d597cf3ee01b1592ba39f8badf9bf3651895bd784246b" } ], "language": "swift", @@ -112,7 +112,7 @@ "schema": "radroots.artifact-manifest.v2", "source": { "repository": "https://github.com/radrootslabs/tera", - "tree": "d66c73094f8b59dc2e016029720be10ebc5ada91" + "tree": "2cd97ed5ace482fe1d1836445bc74cd39f4107bc" }, "source_records": { "aarch64-apple-darwin": "source/aarch64-apple-darwin.json", @@ -139,9 +139,9 @@ "sha256": "3ed9b7ece2f86e5b5d1ddc6ecbb97deb49c46ca2ea91a930eaec1b580232345c" }, { - "bytes": 73868864, + "bytes": 73868728, "path": "Tera/Frameworks/TeraFFI.xcframework/ios-arm64-simulator/libtera_ffi.a", - "sha256": "ed47907bc5ae7835596bf73ab17dad734a82989170c7ed34e491c8465646e528" + "sha256": "417da7d44cb3b3e69441673918d22f1aad54ab60c2b57d3f8335999f2ae98967" }, { "bytes": 71509, @@ -154,9 +154,9 @@ "sha256": "3ed9b7ece2f86e5b5d1ddc6ecbb97deb49c46ca2ea91a930eaec1b580232345c" }, { - "bytes": 73923032, + "bytes": 73922992, "path": "Tera/Frameworks/TeraFFI.xcframework/ios-arm64/libtera_ffi.a", - "sha256": "58d96142e77201061116064e83515aec0ae4ccfcd2e8974b11de4ea3345c43e1" + "sha256": "bf6ae1c6d05e1306e715ac40bba50290ceec17ab0dedaf30c8ee491e4f3ada5b" }, { "bytes": 521598, @@ -174,19 +174,19 @@ "sha256": "e2e025ee9a29182970f52f53593796afd154cdfb09aba197474577103dcb4ad6" }, { - "bytes": 71296, + "bytes": 72121, "path": "TeraFFI/source/aarch64-apple-darwin.json", - "sha256": "42c76082f5a01d21c94d59001b442e04d6ccb4fb428536aaad06988d6b23e938" + "sha256": "7a240ff8cdd83a3969f9efa8c94fe87aa702a38670a9cfadc5ceb43134983445" }, { - "bytes": 71140, + "bytes": 71965, "path": "TeraFFI/source/aarch64-apple-ios-sim.json", - "sha256": "b72fdce869c7f84db6e627939c3fbaee53077279f68b31eefd0f788d041ae841" + "sha256": "717f17889c8f844d0d1d8536e2a0ddf00c4a7e778b33ac55b225ead2a63b4b73" }, { - "bytes": 71136, + "bytes": 71961, "path": "TeraFFI/source/aarch64-apple-ios.json", - "sha256": "ffa229ef3fd7e745364d59c5284a68111d0be1c7981a2b465f2b4d225d833fc0" + "sha256": "980c41d31200d854315d597cf3ee01b1592ba39f8badf9bf3651895bd784246b" } ], "schema": "tera.installed-native-artifacts.v1" diff --git a/TeraFFI/source.lock b/TeraFFI/source.lock @@ -1,7 +1,7 @@ schema = "tera.installed-source.v1" repository = "https://github.com/radrootslabs/tera" -source_tree = "d66c73094f8b59dc2e016029720be10ebc5ada91" -manifest_sha256 = "33eda17ee164b8ec3d38df3586d4336249ad1ed13a65a88467e75888bb7347be" +source_tree = "2cd97ed5ace482fe1d1836445bc74cd39f4107bc" +manifest_sha256 = "1cc7fd2b6773c7d407e06d715d12bb7923b2e8055e473fdecf251f77c06d332c" source_date_epoch = 1787871027 [foundation] diff --git a/TeraFFI/source/aarch64-apple-darwin.json b/TeraFFI/source/aarch64-apple-darwin.json @@ -668,6 +668,12 @@ "mode": "100644", "sha256": "79fd90c240c67137232e6278e69f87a2159b6e078eb99123823a4fc11ce8fcc8" }, + "core/crates/tera_core/src/runtime/product_surface/calendar_wire_tests.rs": { + "bytes": 9425, + "git_blob": "da908dfacbeb6fff82bc618ba4d5e7ec9aa9e6a9", + "mode": "100644", + "sha256": "3ec65d1cb2d94740396137d03920b212afb74692dba11729e4ce5bb3c5a2a789" + }, "core/crates/tera_core/src/runtime/product_surface/context.rs": { "bytes": 14211, "git_blob": "92498807101c638cec675e0b49fde1f4ceed17a2", @@ -747,10 +753,10 @@ "sha256": "76e3bddfc237dac63293d264d68991b489585136fe377e39dbba72aa3a1181f4" }, "core/crates/tera_core/src/runtime/product_surface/today.rs": { - "bytes": 140839, - "git_blob": "2ac582261824b8127a5b12e1effccbc56e3e4e42", + "bytes": 140913, + "git_blob": "cdbd64d61b33c2e35b4adcbe41834125286caa78", "mode": "100644", - "sha256": "ed24b32ba9c956af9723c251eb25fa7a726ea19844392c770b5363352c9fdb62" + "sha256": "4ef107bd6e4dd7870fd262dea9efebcc19ed7e6b262a9baa34ebdf8894d2720c" }, "core/crates/tera_core/src/runtime/product_surface/today_admission_tests.rs": { "bytes": 14718, @@ -1046,11 +1052,17 @@ "mode": "100644", "sha256": "4cb6aa1ff8b006d8a8710e807c9965d47fdd3e72eca67b28a5d909696574f161" }, + "core/crates/tera_ffi/tests/local_mvp/calendar_wire_runtime.rs": { + "bytes": 6876, + "git_blob": "2ba8bf12c3449ac8b73604c5199badf041feac6b", + "mode": "100644", + "sha256": "7b86747a88a0406f5aac3a8dd753bdafb5e955ac51caf614bd1dee2e90beb50b" + }, "core/crates/tera_ffi/tests/local_mvp_real_io.rs": { - "bytes": 35407, - "git_blob": "a7a8836031c0b7586567c8fe96549642c4535a5e", + "bytes": 35482, + "git_blob": "75d2647bbc8403934af2bb0c26bcbe2e795b8562", "mode": "100644", - "sha256": "e5d2972e13fb73b38d4dd9690e66af0cc36a6545e79523102c1d4040703c7de4" + "sha256": "bec40620792cf8f79e7fc7aa6f8027236b559500262ba5cc285008f89af9096e" }, "core/crates/tera_ffi/tests/logging_error.rs": { "bytes": 357, @@ -1124,6 +1136,12 @@ "mode": "100644", "sha256": "a09d6144bec7938c48781a7648ce40ebac62a47fd5a093477727c1bdf746ed16" }, + "core/fixtures/calendar_wire.v1.json": { + "bytes": 1962, + "git_blob": "69b0644dc24cf31bfd2a45d77f8211184a7da4c2", + "mode": "100644", + "sha256": "7377d9ebcbb29633e3edc14b1a09b2c92ca8f1f8c21e5ecdb0299d6719bcf1ab" + }, "core/provenance/tera_bindgen/LICENSE-APACHE": { "bytes": 10831, "git_blob": "9f90a7c2df28eba05d53e5c6f9ac246c018c6bee", @@ -1533,13 +1551,13 @@ "sha256": "b052a73a824e8f8b26af2646a4758f13655e04de9551a8271890d3cf2b63209d" }, "test-fixtures/legacy-identifiers.v1.json": { - "bytes": 120272, - "git_blob": "c79579634b4af362ddb7e84ac0a89644f2a980cf", + "bytes": 120816, + "git_blob": "6cd6bc7b0beb7172b994ee33f8b9f5a499c3fc9b", "mode": "100644", - "sha256": "28e4400177809875fee084c8e3546c7ba28c9d7b268fb60186a3bc005e5c296a" + "sha256": "f344ae262369369516e9a9923addb2a9cacdf6e5f017024f459e5fa93a78c47d" } }, "policy": "staged_inputs", - "tree": "d66c73094f8b59dc2e016029720be10ebc5ada91" + "tree": "2cd97ed5ace482fe1d1836445bc74cd39f4107bc" } } diff --git a/TeraFFI/source/aarch64-apple-ios-sim.json b/TeraFFI/source/aarch64-apple-ios-sim.json @@ -664,6 +664,12 @@ "mode": "100644", "sha256": "79fd90c240c67137232e6278e69f87a2159b6e078eb99123823a4fc11ce8fcc8" }, + "core/crates/tera_core/src/runtime/product_surface/calendar_wire_tests.rs": { + "bytes": 9425, + "git_blob": "da908dfacbeb6fff82bc618ba4d5e7ec9aa9e6a9", + "mode": "100644", + "sha256": "3ec65d1cb2d94740396137d03920b212afb74692dba11729e4ce5bb3c5a2a789" + }, "core/crates/tera_core/src/runtime/product_surface/context.rs": { "bytes": 14211, "git_blob": "92498807101c638cec675e0b49fde1f4ceed17a2", @@ -743,10 +749,10 @@ "sha256": "76e3bddfc237dac63293d264d68991b489585136fe377e39dbba72aa3a1181f4" }, "core/crates/tera_core/src/runtime/product_surface/today.rs": { - "bytes": 140839, - "git_blob": "2ac582261824b8127a5b12e1effccbc56e3e4e42", + "bytes": 140913, + "git_blob": "cdbd64d61b33c2e35b4adcbe41834125286caa78", "mode": "100644", - "sha256": "ed24b32ba9c956af9723c251eb25fa7a726ea19844392c770b5363352c9fdb62" + "sha256": "4ef107bd6e4dd7870fd262dea9efebcc19ed7e6b262a9baa34ebdf8894d2720c" }, "core/crates/tera_core/src/runtime/product_surface/today_admission_tests.rs": { "bytes": 14718, @@ -1042,11 +1048,17 @@ "mode": "100644", "sha256": "4cb6aa1ff8b006d8a8710e807c9965d47fdd3e72eca67b28a5d909696574f161" }, + "core/crates/tera_ffi/tests/local_mvp/calendar_wire_runtime.rs": { + "bytes": 6876, + "git_blob": "2ba8bf12c3449ac8b73604c5199badf041feac6b", + "mode": "100644", + "sha256": "7b86747a88a0406f5aac3a8dd753bdafb5e955ac51caf614bd1dee2e90beb50b" + }, "core/crates/tera_ffi/tests/local_mvp_real_io.rs": { - "bytes": 35407, - "git_blob": "a7a8836031c0b7586567c8fe96549642c4535a5e", + "bytes": 35482, + "git_blob": "75d2647bbc8403934af2bb0c26bcbe2e795b8562", "mode": "100644", - "sha256": "e5d2972e13fb73b38d4dd9690e66af0cc36a6545e79523102c1d4040703c7de4" + "sha256": "bec40620792cf8f79e7fc7aa6f8027236b559500262ba5cc285008f89af9096e" }, "core/crates/tera_ffi/tests/logging_error.rs": { "bytes": 357, @@ -1120,6 +1132,12 @@ "mode": "100644", "sha256": "a09d6144bec7938c48781a7648ce40ebac62a47fd5a093477727c1bdf746ed16" }, + "core/fixtures/calendar_wire.v1.json": { + "bytes": 1962, + "git_blob": "69b0644dc24cf31bfd2a45d77f8211184a7da4c2", + "mode": "100644", + "sha256": "7377d9ebcbb29633e3edc14b1a09b2c92ca8f1f8c21e5ecdb0299d6719bcf1ab" + }, "core/provenance/tera_bindgen/LICENSE-APACHE": { "bytes": 10831, "git_blob": "9f90a7c2df28eba05d53e5c6f9ac246c018c6bee", @@ -1529,13 +1547,13 @@ "sha256": "b052a73a824e8f8b26af2646a4758f13655e04de9551a8271890d3cf2b63209d" }, "test-fixtures/legacy-identifiers.v1.json": { - "bytes": 120272, - "git_blob": "c79579634b4af362ddb7e84ac0a89644f2a980cf", + "bytes": 120816, + "git_blob": "6cd6bc7b0beb7172b994ee33f8b9f5a499c3fc9b", "mode": "100644", - "sha256": "28e4400177809875fee084c8e3546c7ba28c9d7b268fb60186a3bc005e5c296a" + "sha256": "f344ae262369369516e9a9923addb2a9cacdf6e5f017024f459e5fa93a78c47d" } }, "policy": "staged_inputs", - "tree": "d66c73094f8b59dc2e016029720be10ebc5ada91" + "tree": "2cd97ed5ace482fe1d1836445bc74cd39f4107bc" } } diff --git a/TeraFFI/source/aarch64-apple-ios.json b/TeraFFI/source/aarch64-apple-ios.json @@ -664,6 +664,12 @@ "mode": "100644", "sha256": "79fd90c240c67137232e6278e69f87a2159b6e078eb99123823a4fc11ce8fcc8" }, + "core/crates/tera_core/src/runtime/product_surface/calendar_wire_tests.rs": { + "bytes": 9425, + "git_blob": "da908dfacbeb6fff82bc618ba4d5e7ec9aa9e6a9", + "mode": "100644", + "sha256": "3ec65d1cb2d94740396137d03920b212afb74692dba11729e4ce5bb3c5a2a789" + }, "core/crates/tera_core/src/runtime/product_surface/context.rs": { "bytes": 14211, "git_blob": "92498807101c638cec675e0b49fde1f4ceed17a2", @@ -743,10 +749,10 @@ "sha256": "76e3bddfc237dac63293d264d68991b489585136fe377e39dbba72aa3a1181f4" }, "core/crates/tera_core/src/runtime/product_surface/today.rs": { - "bytes": 140839, - "git_blob": "2ac582261824b8127a5b12e1effccbc56e3e4e42", + "bytes": 140913, + "git_blob": "cdbd64d61b33c2e35b4adcbe41834125286caa78", "mode": "100644", - "sha256": "ed24b32ba9c956af9723c251eb25fa7a726ea19844392c770b5363352c9fdb62" + "sha256": "4ef107bd6e4dd7870fd262dea9efebcc19ed7e6b262a9baa34ebdf8894d2720c" }, "core/crates/tera_core/src/runtime/product_surface/today_admission_tests.rs": { "bytes": 14718, @@ -1042,11 +1048,17 @@ "mode": "100644", "sha256": "4cb6aa1ff8b006d8a8710e807c9965d47fdd3e72eca67b28a5d909696574f161" }, + "core/crates/tera_ffi/tests/local_mvp/calendar_wire_runtime.rs": { + "bytes": 6876, + "git_blob": "2ba8bf12c3449ac8b73604c5199badf041feac6b", + "mode": "100644", + "sha256": "7b86747a88a0406f5aac3a8dd753bdafb5e955ac51caf614bd1dee2e90beb50b" + }, "core/crates/tera_ffi/tests/local_mvp_real_io.rs": { - "bytes": 35407, - "git_blob": "a7a8836031c0b7586567c8fe96549642c4535a5e", + "bytes": 35482, + "git_blob": "75d2647bbc8403934af2bb0c26bcbe2e795b8562", "mode": "100644", - "sha256": "e5d2972e13fb73b38d4dd9690e66af0cc36a6545e79523102c1d4040703c7de4" + "sha256": "bec40620792cf8f79e7fc7aa6f8027236b559500262ba5cc285008f89af9096e" }, "core/crates/tera_ffi/tests/logging_error.rs": { "bytes": 357, @@ -1120,6 +1132,12 @@ "mode": "100644", "sha256": "a09d6144bec7938c48781a7648ce40ebac62a47fd5a093477727c1bdf746ed16" }, + "core/fixtures/calendar_wire.v1.json": { + "bytes": 1962, + "git_blob": "69b0644dc24cf31bfd2a45d77f8211184a7da4c2", + "mode": "100644", + "sha256": "7377d9ebcbb29633e3edc14b1a09b2c92ca8f1f8c21e5ecdb0299d6719bcf1ab" + }, "core/provenance/tera_bindgen/LICENSE-APACHE": { "bytes": 10831, "git_blob": "9f90a7c2df28eba05d53e5c6f9ac246c018c6bee", @@ -1529,13 +1547,13 @@ "sha256": "b052a73a824e8f8b26af2646a4758f13655e04de9551a8271890d3cf2b63209d" }, "test-fixtures/legacy-identifiers.v1.json": { - "bytes": 120272, - "git_blob": "c79579634b4af362ddb7e84ac0a89644f2a980cf", + "bytes": 120816, + "git_blob": "6cd6bc7b0beb7172b994ee33f8b9f5a499c3fc9b", "mode": "100644", - "sha256": "28e4400177809875fee084c8e3546c7ba28c9d7b268fb60186a3bc005e5c296a" + "sha256": "f344ae262369369516e9a9923addb2a9cacdf6e5f017024f459e5fa93a78c47d" } }, "policy": "staged_inputs", - "tree": "d66c73094f8b59dc2e016029720be10ebc5ada91" + "tree": "2cd97ed5ace482fe1d1836445bc74cd39f4107bc" } } diff --git a/core/crates/tera_core/src/runtime/product_surface/calendar_wire_tests.rs b/core/crates/tera_core/src/runtime/product_surface/calendar_wire_tests.rs @@ -0,0 +1,255 @@ +//! Consumer conformance against the exact shared owner selected by Cargo.lock. +use radroots_event::calendar::{ + AuthoredCalendarDateEvent, AuthoredCalendarTimeEvent, CalendarDate, +}; +use radroots_event_codec::{ + admission::admit_verified_event, + decode::calendar::{ + admit_radroots_calendar_date_event, admit_radroots_calendar_time_event, + parse_nip52_calendar_date_event, parse_nip52_calendar_time_event, + }, + encode::calendar::time_to_wire_parts, + verify::verify_nip01_event, +}; +use serde_json::Value; + +use super::tests::{keys, signed_owned}; +use crate::runtime::product_surface::{CreateEvent, Phase1AddCommand}; + +fn fixtures() -> Value { + serde_json::from_str(include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/../../fixtures/calendar_wire.v1.json" + ))) + .expect("standalone calendar fixtures") +} + +fn tags(fixture: &Value) -> Vec<Vec<String>> { + serde_json::from_value(fixture["tags"].clone()).unwrap() +} + +fn field<'a>(fixture: &'a Value, name: &str) -> &'a str { + fixture["tags"] + .as_array() + .unwrap() + .iter() + .find(|tag| tag[0] == name) + .unwrap()[1] + .as_str() + .unwrap() +} + +#[test] +fn authored_calendar_plans_match_current_wire_and_signature_admission() { + let suite = fixtures(); + assert_eq!(suite["schema_version"], 1); + assert_eq!( + suite["provenance"]["registry_version"], + radroots_event::contract::RegistryVersion::CURRENT.get() + ); + assert_eq!(suite["events"].as_array().unwrap().len(), 3); + for fixture in suite["events"].as_array().unwrap() { + let content = fixture["content"].as_str().unwrap(); + let event = if fixture["kind"] == 31_922 { + CreateEvent::date( + AuthoredCalendarDateEvent::new( + field(fixture, "d"), + field(fixture, "title"), + CalendarDate::parse(field(fixture, "start")).unwrap(), + ) + .unwrap() + .with_end(CalendarDate::parse(field(fixture, "end")).unwrap()) + .unwrap() + .with_description(content) + .unwrap() + .with_locations(vec![field(fixture, "location").to_owned()]) + .unwrap(), + ) + } else { + let mut event = AuthoredCalendarTimeEvent::new( + field(fixture, "d"), + field(fixture, "title"), + field(fixture, "start").parse().unwrap(), + ) + .unwrap() + .with_end(field(fixture, "end").parse().unwrap()) + .unwrap() + .with_start_tzid(field(fixture, "start_tzid")) + .unwrap() + .with_description(content) + .unwrap() + .with_locations(vec![field(fixture, "location").to_owned()]) + .unwrap(); + if let Some(zone) = fixture["timing"]["time"]["end_tzid"].as_str() { + event = event.with_end_tzid(zone).unwrap(); + } + CreateEvent::time(event) + }; + let plan = Phase1AddCommand::CreateEvent(event) + .authored_plan(1_786_000_000, keys().public_key().to_string()) + .unwrap(); + assert_eq!( + u64::from(plan.body().kind()), + fixture["kind"].as_u64().unwrap() + ); + assert_eq!(plan.body().tags(), tags(fixture)); + assert_eq!(plan.body().content(), content); + let signed = signed_owned( + plan.body().kind(), + plan.body().tags().to_vec(), + content, + 1_786_000_000, + ); + let admitted = + admit_verified_event(verify_nip01_event(signed.envelope().clone()).unwrap()).unwrap(); + assert_eq!( + admitted.contract_id(), + fixture["contract_id"].as_str().unwrap() + ); + assert_eq!(admitted.event().tags_as_vec(), tags(fixture)); + assert_eq!( + admitted.event(), + signed.envelope(), + "admission preserves signed source" + ); + } +} + +fn reject(kind: u32, tags: Vec<Vec<String>>) { + let signed = signed_owned(kind, tags, "calendar conformance", 1_786_000_000); + let verified = + verify_nip01_event(signed.envelope().clone()).expect("valid signed malformed profile"); + assert!( + admit_verified_event(verified).is_err(), + "malformed calendar profile admitted" + ); +} + +fn replace(tags: &mut [Vec<String>], name: &str, value: &str) { + tags.iter_mut().find(|tag| tag[0] == name).unwrap()[1] = value.to_owned(); +} + +#[test] +fn signed_calendar_admission_rejects_noncanonical_cardinality_and_fields() { + for fixture in fixtures()["events"].as_array().unwrap() { + let kind = fixture["kind"].as_u64().unwrap() as u32; + for name in ["d", "title", "start"] { + let mut missing = tags(fixture); + missing.retain(|tag| tag[0] != name); + reject(kind, missing); + } + // Each scalar in these fixtures is singular, exactly two elements. + for scalar in tags(fixture).iter().filter(|tag| tag[0] != "D") { + let mut duplicate = tags(fixture); + duplicate.push(scalar.clone()); + // location is intentionally repeatable in the shared contract. + if scalar[0] != "location" { + reject(kind, duplicate); + } + let mut arity = tags(fixture); + arity + .iter_mut() + .find(|tag| tag[0] == scalar[0]) + .unwrap() + .push("extra".into()); + reject(kind, arity); + } + let mut equal_end = tags(fixture); + replace(&mut equal_end, "end", field(fixture, "start")); + reject(kind, equal_end); + if kind == 31_922 { + let mut forbidden = tags(fixture); + forbidden.push(vec!["D".into(), "20833".into()]); + reject(kind, forbidden); + let mut invalid = tags(fixture); + replace(&mut invalid, "start", "2026-02-29"); + reject(kind, invalid); + } else { + for value in ["01800000000", "-1", "18446744073709551616"] { + let mut invalid = tags(fixture); + replace(&mut invalid, "start", value); + reject(kind, invalid); + } + for value in ["america/vancouver", "Mars/Olympus", "UTC+03"] { + let mut invalid = tags(fixture); + replace(&mut invalid, "start_tzid", value); + reject(kind, invalid); + } + let mut missing = tags(fixture); + missing.retain(|tag| tag[0] != "D"); + reject(kind, missing); + let mut duplicate = tags(fixture); + duplicate.push(vec!["D".into(), "20833".into()]); + reject(kind, duplicate); + } + } +} + +#[test] +fn baseline_observation_does_not_weaken_strict_calendar_day_coverage() { + let suite = fixtures(); + let fixture = &suite["events"][1]; + for values in [ + vec!["20833"], + vec!["20834", "20833"], + vec!["20833", "20833", "20834"], + vec!["020833", "20834"], + ] { + let mut partial = tags(fixture); + partial.retain(|tag| tag[0] != "D"); + partial.extend(values.into_iter().map(|day| vec!["D".into(), day.into()])); + let parsed = parse_nip52_calendar_time_event(31_923, &partial, "") + .expect("bounded in-range observed days"); + assert!(admit_radroots_calendar_time_event(parsed).is_err()); + reject(31_923, partial); + } + let mut absent = tags(fixture); + absent.retain(|tag| tag[0] != "D"); + assert!(parse_nip52_calendar_time_event(31_923, &absent, "").is_err()); + let mut date = tags(&suite["events"][0]); + date.push(vec!["D".into(), "20833".into()]); + let parsed = parse_nip52_calendar_date_event(31_922, &date, "").unwrap(); + assert!(admit_radroots_calendar_date_event(parsed).is_err()); +} + +#[test] +fn timed_encoder_keeps_exclusive_midnight_and_full_width_bounded_days() { + for (start, end, expected) in [ + (1_800_000_000, None, vec!["20833".to_owned()]), + (1_800_000_000, Some(1_800_057_600), vec!["20833".to_owned()]), + ( + 1_800_000_000, + Some(1_800_057_601), + vec!["20833".to_owned(), "20834".to_owned()], + ), + (u64::MAX, None, vec![(u64::MAX / 86_400).to_string()]), + ( + 0, + Some(366 * 86_400), + (0..366).map(|day| day.to_string()).collect(), + ), + ] { + let mut event = AuthoredCalendarTimeEvent::new("boundary", "Boundary", start).unwrap(); + if let Some(end) = end { + event = event.with_end(end).unwrap(); + } + let wire = time_to_wire_parts(&event).unwrap(); + assert_eq!(wire.kind, 31_923); + assert_eq!( + wire.tags + .iter() + .filter(|tag| tag[0] == "D") + .map(|tag| tag[1].clone()) + .collect::<Vec<_>>(), + expected + ); + let signed = signed_owned(wire.kind, wire.tags, &wire.content, 1_786_000_000); + admit_verified_event(verify_nip01_event(signed.envelope().clone()).unwrap()).unwrap(); + } + assert!( + AuthoredCalendarTimeEvent::new("boundary", "Boundary", 0) + .unwrap() + .with_end(367 * 86_400) + .is_err() + ); +} diff --git a/core/crates/tera_core/src/runtime/product_surface/today.rs b/core/crates/tera_core/src/runtime/product_surface/today.rs @@ -62,6 +62,10 @@ const PROJECTION_CONTENT_DOMAIN: &[u8] = b"radroots.today-content-generation.v2\ const PROJECTION_DOCUMENT_KEY_DOMAIN: &[u8] = b"radroots.today-document-key.v1\0"; const SNAPSHOT_ID_DOMAIN: &[u8] = b"tera.today-snapshot-id.v3\0"; +#[cfg(test)] +#[path = "calendar_wire_tests.rs"] +mod calendar_wire_tests; + #[path = "today_calendar_migration.rs"] mod calendar_migration; diff --git a/core/crates/tera_ffi/tests/local_mvp/calendar_wire_runtime.rs b/core/crates/tera_ffi/tests/local_mvp/calendar_wire_runtime.rs @@ -0,0 +1,190 @@ +//! Real FFI authoring and fresh SQLite admission of the pinned calendar wire. +use super::*; +use nostr::Filter; +use serde_json::Value; +use tera_ffi::{FfiCalendarTiming, FfiCivilDate}; + +fn fixtures() -> Value { + serde_json::from_str(include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/../../fixtures/calendar_wire.v1.json" + ))) + .unwrap() +} + +fn field<'a>(fixture: &'a Value, name: &str) -> &'a str { + fixture["tags"] + .as_array() + .unwrap() + .iter() + .find(|tag| tag[0] == name) + .unwrap()[1] + .as_str() + .unwrap() +} + +fn input(fixture: &Value) -> FfiAddDraftInput { + let mut input = add_input( + FfiAddCommandType::CreateEvent, + fixture["content"].as_str().unwrap(), + None, + ); + input.identifier = Some(field(fixture, "d").into()); + input.title = Some(field(fixture, "title").into()); + input.location = Some(field(fixture, "location").into()); + if fixture["kind"] == 31_922 { + input.event_timing = Some(FfiEventTimingKind::AllDay); + input.event_start_date = Some(field(fixture, "start").into()); + input.event_end_date = Some(field(fixture, "end").into()); + // An inactive editor zone must never become a civil wire timestamp/tag. + input.event_timezone = Some("Pacific/Auckland".into()); + } else { + input.event_timing = Some(FfiEventTimingKind::Timed); + input.event_start_unix_s = Some(field(fixture, "start").parse().unwrap()); + input.event_end_unix_s = Some(field(fixture, "end").parse().unwrap()); + input.event_timezone = Some(field(fixture, "start_tzid").into()); + } + input +} + +fn expected_timing(fixture: &Value) -> FfiCalendarTiming { + if fixture["kind"] == 31_922 { + FfiCalendarTiming::DateBased { + start: FfiCivilDate { + year: 2026, + month: 9, + day: 5, + }, + end_exclusive: Some(FfiCivilDate { + year: 2026, + month: 9, + day: 7, + }), + } + } else { + let timing = &fixture["timing"]["time"]; + FfiCalendarTiming::TimeBased { + start_unix_s: timing["start"].as_u64().unwrap(), + end_exclusive_unix_s: timing["end_exclusive"].as_u64(), + start_tzid: timing["start_tzid"].as_str().map(str::to_owned), + end_tzid: timing["end_tzid"].as_str().map(str::to_owned), + } + } +} + +#[tokio::test] +async fn calendar_wire_survives_ffi_publication_fresh_admission_and_reopen() { + let suite = fixtures(); + let fixtures = suite["events"].as_array().unwrap(); + assert_eq!(fixtures.len(), 3); + let relay = MockRelay::run().await.unwrap(); + let relay_url = relay.url().await.to_string(); + let context = local_network(&relay_url); + let publisher_root = tempfile::tempdir().unwrap(); + support::prepare(publisher_root.path()); + let publisher = runtime_with_signer(publisher_root.path()).await; + configure_simulator( + &publisher, + &relay_url, + &format!("http://127.0.0.1:{}", unused_loopback_port().await), + ); + for (index, fixture) in fixtures[..2].iter().enumerate() { + let id = draft_id(81 + index as u8); + let saved = publisher + .phase1_save_draft( + id.clone(), + input(fixture), + AUTHORED_AT, + None, + 1_800_000_000_000 + index as u64, + ) + .await + .unwrap(); + let queued = queue( + &publisher, + &id, + saved.revision, + &relay_url, + false, + 1_800_000_000_100 + index as u64, + ) + .await; + advance_complete(&publisher, &id, queued.revision).await; + } + // The creator has one optional start zone. The independent receive path + // must also preserve the shared contract's separate optional end zone. + let client = Client::new(Keys::parse(FIXTURE_SECRET).unwrap()); + client.add_relay(&relay_url).await.unwrap(); + client.connect().await; + client.wait_for_connection(Duration::from_secs(2)).await; + let external = &fixtures[2]; + let wire_tags: Vec<Vec<String>> = serde_json::from_value(external["tags"].clone()).unwrap(); + client + .send_event_builder( + EventBuilder::new(Kind::from(31_923), external["content"].as_str().unwrap()) + .tags(wire_tags.into_iter().map(|tag| Tag::parse(tag).unwrap())) + .custom_created_at(Timestamp::from_secs(AUTHORED_AT)), + ) + .await + .unwrap(); + let events = client + .fetch_events( + Filter::new().kinds([Kind::from(31_922), Kind::from(31_923)]), + Duration::from_secs(5), + ) + .await + .unwrap(); + assert_eq!(events.len(), 3); + for fixture in fixtures { + let event = events + .iter() + .find(|event| event.content == fixture["content"].as_str().unwrap()) + .unwrap(); + event.verify().expect("published wire signature and id"); + assert_eq!( + u64::from(event.kind.as_u16()), + fixture["kind"].as_u64().unwrap() + ); + assert_eq!(serde_json::to_value(&event.tags).unwrap(), fixture["tags"]); + } + let reader_root = tempfile::tempdir().unwrap(); + support::prepare(reader_root.path()); + let reader = runtime_with_signer(reader_root.path()).await; + reader + .configure_simulator_relays(vec![relay_url.clone()]) + .unwrap(); + let sync = reader + .phase1_sync_today(context.clone(), AS_OF, FfiTodayProjectionUpdate::Rebuild) + .await + .unwrap(); + assert_eq!(sync.relay_state, FfiTodayRelaySyncState::Complete); + assert_eq!(sync.events_admitted, 3); + let cards = collect_pages(&reader, &context, 1, AS_OF).await; + assert_eq!(cards.len(), 3); + for fixture in fixtures { + let event = events + .iter() + .find(|event| event.content == fixture["content"].as_str().unwrap()) + .unwrap(); + let card = cards + .iter() + .find(|card| card.source_event_id == event.id.to_hex()) + .unwrap(); + assert_eq!(card.card_type, FfiTodayCardType::Event); + assert_eq!(card.calendar_timing, Some(expected_timing(fixture))); + } + reader.shutdown().await.unwrap(); + client.shutdown().await; + publisher.shutdown().await.unwrap(); + relay.shutdown(); + let reopened = runtime_with_signer(reader_root.path()).await; + reopened + .configure_simulator_relays(vec![relay_url]) + .unwrap(); + let cached = collect_pages(&reopened, &context, 1, AS_OF).await; + assert_eq!( + cached, cards, + "offline reopen preserves exact source and typed timing" + ); + reopened.shutdown().await.unwrap(); +} diff --git a/core/crates/tera_ffi/tests/local_mvp_real_io.rs b/core/crates/tera_ffi/tests/local_mvp_real_io.rs @@ -23,6 +23,9 @@ use tokio::net::TcpListener; #[allow(dead_code)] mod support; +#[path = "local_mvp/calendar_wire_runtime.rs"] +mod calendar_wire_runtime; + const AUTHORED_AT: u64 = 1_786_000_000; const AS_OF: u64 = 1_786_200_000; const FIXTURE_SECRET: &str = "0000000000000000000000000000000000000000000000000000000000000001"; diff --git a/core/fixtures/calendar_wire.v1.json b/core/fixtures/calendar_wire.v1.json @@ -0,0 +1,37 @@ +{ + "schema_version": 1, + "provenance": { + "repository": "radrootslabs/lib", + "reviewed_revision": "13ac1f2135199702a07ba6aa14ab22101f17fcb0", + "registry_version": 7, + "encoder": "crates/event_codec/src/calendar/encode.rs", + "admission": "crates/event/src/contract/registry_v7.rs", + "scope": "Current pinned Radroots calendar profiles; reviewed revision is provenance, not an instruction to upgrade dependencies." + }, + "events": [ + { + "id": "date_range", + "contract_id": "radroots.calendar.date_event.v1", + "kind": 31922, + "content": "A two-day local harvest gathering.", + "tags": [["d", "harvest-days"], ["title", "Harvest days"], ["start", "2026-09-05"], ["end", "2026-09-07"], ["location", "Fixture field"]], + "timing": {"date": {"start": "2026-09-05", "end_exclusive": "2026-09-07"}} + }, + { + "id": "time_range", + "contract_id": "radroots.calendar.time_event.v1", + "kind": 31923, + "content": "A timed harvest gathering crossing UTC midnight.", + "tags": [["d", "harvest-time"], ["title", "Harvest time"], ["start", "1800000000"], ["end", "1800086400"], ["D", "20833"], ["D", "20834"], ["start_tzid", "America/Vancouver"], ["location", "Fixture field"]], + "timing": {"time": {"start": 1800000000, "end_exclusive": 1800086400, "start_tzid": "America/Vancouver", "end_tzid": null}} + }, + { + "id": "time_both_zones", + "contract_id": "radroots.calendar.time_event.v1", + "kind": 31923, + "content": "An inbound timed event with independent source zones.", + "tags": [["d", "harvest-source-zones"], ["title", "Source zones"], ["start", "1800000000"], ["end", "1800057600"], ["D", "20833"], ["start_tzid", "America/Vancouver"], ["end_tzid", "Pacific/Auckland"], ["location", "Fixture field"]], + "timing": {"time": {"start": 1800000000, "end_exclusive": 1800057600, "start_tzid": "America/Vancouver", "end_tzid": "Pacific/Auckland"}} + } + ] +} diff --git a/release/provenance.json b/release/provenance.json @@ -2,7 +2,7 @@ "artifacts": { "app_api_sha256": "020924097c0d7efc33128cb8fd3d3b2026d95f57c44da71880e585aff80f070b", "ffi_api_sha256": "e2e025ee9a29182970f52f53593796afd154cdfb09aba197474577103dcb4ad6", - "ffi_provenance_sha256": "33eda17ee164b8ec3d38df3586d4336249ad1ed13a65a88467e75888bb7347be", + "ffi_provenance_sha256": "1cc7fd2b6773c7d407e06d715d12bb7923b2e8055e473fdecf251f77c06d332c", "info_plist_sha256": "15ef08b1cdd1096cfb9eeaf5be5bf8f814807a7ca9350bbbb47860fa72ec13ef", "privacy_manifest_sha256": "a331d51864743ebe4e00dd22360b4a538b6b3ac26a6b3eb54094e60a36959a12", "sbom_sha256": "236eea371d7a6f95e2c632fe1832abe0cd8f08c4c9b43fcf840accc3e5b674d0", @@ -22,7 +22,7 @@ "lib_revision": "13ac1f2135199702a07ba6aa14ab22101f17fcb0", "source_date_epoch": 1787871027, "swift_package_lock_sha256": "94ae067a374726cdaf6b4ca0a5e44663c57fcdc5334060c5ffef5e79cfbf04c0", - "tera_ffi_source_tree": "d66c73094f8b59dc2e016029720be10ebc5ada91", + "tera_ffi_source_tree": "2cd97ed5ace482fe1d1836445bc74cd39f4107bc", "xcode_package_lock_sha256": "c7f41934ea25f7a287bdc4f3a6ecabbf09a3a0bdd0f5a3e58183f355ca814096" }, "version": "0.1.0-alpha" diff --git a/test-fixtures/legacy-identifiers.v1.json b/test-fixtures/legacy-identifiers.v1.json @@ -1741,6 +1741,10 @@ { "path": "core/crates/tera_core/src/runtime/product_surface/calendar_timing.rs", "count": 2 + }, + { + "path": "core/crates/tera_core/src/runtime/product_surface/calendar_wire_tests.rs", + "count": 2 } ] }, @@ -1751,6 +1755,10 @@ { "path": "core/crates/tera_core/src/runtime/product_surface/calendar_timing.rs", "count": 2 + }, + { + "path": "core/crates/tera_core/src/runtime/product_surface/calendar_wire_tests.rs", + "count": 2 } ] }, @@ -4045,6 +4053,10 @@ "count": 1 }, { + "path": "core/crates/tera_core/src/runtime/product_surface/calendar_wire_tests.rs", + "count": 2 + }, + { "path": "core/crates/tera_core/src/runtime/product_surface/identity.rs", "count": 1 }, @@ -4143,6 +4155,10 @@ "count": 1 }, { + "path": "core/crates/tera_core/src/runtime/product_surface/calendar_wire_tests.rs", + "count": 1 + }, + { "path": "core/crates/tera_core/src/runtime/product_surface/outbox.rs", "count": 2 },