1#![forbid(unsafe_code)]
19
20pub mod access;
21pub mod build_key;
22pub mod catalog;
23pub mod diff;
24pub mod generate;
25mod generate_tables;
26pub mod manifest;
27pub mod privacy;
28pub mod provenance;
29
30pub use build_key::{BUILD_PROFILE, BuildKeyError, BuildKeyInputs, build_key, canonical_inputs};
31pub use catalog::{
32 Catalog, CatalogError, CatalogModule, ModuleRelease, ModuleSet, PinnedRelease, ReleaseReview,
33 ResolveError, Tier, builtin, is_exact_version, is_sha256_digest,
34};
35pub use generate::{GeneratedFile, GeneratedVenture, HarnessSource, generate};
36pub use manifest::{ManifestError, ModuleRef, VentureManifest};
37pub use access::{Access, AccessMap, LEVELS};
40pub use cratefield_tables::Schema;
41pub use diff::{Change as DeclarationChange, Move, diff as declaration_diff};
42pub use privacy::{Disposition, KINDS, TablePrivacy, TablePrivacyMap};
43pub use provenance::{
44 BuildEnvViolation, BuildEnvironmentAttestation, PROVENANCE_SCHEMA, Provenance, ProvenanceError,
45 ProvenanceFile, ResourceLimits, file_digest, is_placeholder_digest,
46};
47
48#[cfg(test)]
49mod tests {
50 use super::*;
51
52 fn sample_json() -> &'static str {
53 r#"{
54 "name": "acme-signups",
55 "host": "acme.factory0.dev",
56 "cors_origins": ["https://acme.example"],
57 "modules": ["waitlist", { "slug": "email-signup" }],
58 "config": { "brand": "Acme" },
59 "seed_sql": "INSERT INTO waitlist_product (slug) VALUES ('launch');"
60 }"#
61 }
62
63 fn with_tables() -> &'static str {
65 r#"{
66 "name": "acme-signups",
67 "host": "acme.factory0.dev",
68 "cors_origins": ["https://acme.example"],
69 "modules": ["waitlist"],
70 "tables": {
71 "note": {
72 "primary_key": "id",
73 "fields": [
74 { "name": "id", "kind": "uuid", "required": true },
75 { "name": "author", "kind": "text" },
76 { "name": "body", "kind": "text", "max_len": 400 }
77 ]
78 }
79 },
80 "table_privacy": {
81 "note": {
82 "holds": "personal",
83 "subject": "author",
84 "kind": "content",
85 "disposition": "erase",
86 "description": "The notes you wrote, and when."
87 }
88 },
89 "table_access": { "note": "owner" }
90 }"#
91 }
92
93 #[test]
94 fn a_manifest_without_tables_still_reads_and_writes_unchanged() {
95 let manifest = VentureManifest::from_json_str(sample_json()).expect("parses");
99 assert!(manifest.tables.is_empty());
100 manifest.validate().expect("valid");
101 let written = serde_json::to_string(&manifest).expect("writes");
102 assert!(!written.contains("tables"), "{written}");
103 }
104
105 #[test]
106 fn a_declared_table_is_carried_and_checked() {
107 let manifest = VentureManifest::from_json_str(with_tables()).expect("parses");
108 manifest.validate().expect("valid");
109 assert_eq!(manifest.tables.tables.len(), 1);
110 assert_eq!(manifest.tables.tables[0].name, "note");
111 let written = serde_json::to_string(&manifest).expect("writes");
114 let again = VentureManifest::from_json_str(&written).expect("reads what it wrote");
115 assert_eq!(manifest, again);
116 }
117
118 #[test]
119 fn a_declaration_that_is_not_legal_fails_the_manifest_not_something_later() {
120 let broken = with_tables().replace(r#""primary_key": "id""#, r#""primary_key": "nope""#);
124 let manifest = VentureManifest::from_json_str(&broken).expect("parses");
125 let error = manifest.validate().expect_err("not legal");
126 let text = error.to_string();
127 assert!(text.contains("[tables]"), "{text}");
128 assert!(text.contains("nope"), "{text}");
129 }
130
131 #[test]
132 fn a_declared_table_becomes_a_module_the_venture_composes() {
133 let manifest = VentureManifest::from_json_str(with_tables()).expect("parses");
137 let set = manifest.resolve(&catalog::builtin()).expect("resolves");
138 let venture = generate::generate(&manifest, &set, &generate::HarnessSource::default())
139 .expect("generates");
140
141 let file = |name: &str| {
142 venture
143 .files
144 .iter()
145 .find(|f| f.path == name)
146 .map(|f| f.contents.as_str())
147 .unwrap_or_default()
148 };
149
150 let tables = file("src/tables.rs");
151 assert!(!tables.is_empty(), "no src/tables.rs was emitted");
152 assert!(
153 tables.contains("CREATE TABLE IF NOT EXISTS note"),
154 "{tables}"
155 );
156 assert!(tables.contains("&[\"note\"]"), "{tables}");
159 assert!(tables.contains("table: \"note\""), "{tables}");
160 assert!(tables.contains("subject: \"author\""), "{tables}");
161 assert!(tables.contains("DataKind::Content"), "{tables}");
162 assert!(tables.contains("Disposition::Erase"), "{tables}");
163
164 assert!(
167 tables.contains("&[Port::Db, Port::Auth]"),
168 "an owner table generated a module that needs no verifier: {tables}"
169 );
170
171 assert!(
176 tables.contains("cratefield::tables_api::router(Arc::new("),
177 "the generated module creates the tables and serves nothing: {tables}"
178 );
179 assert!(
180 tables.contains("cratefield::tables_api::Tables {"),
181 "{tables}"
182 );
183 assert!(tables.contains("const DECLARED: &str = r#"), "{tables}");
184 let validate = {
192 let from = tables
193 .find("fn validate_config")
194 .expect("the module validates its config");
195 let rest = &tables[from..];
196 let to = rest.find("fn router").expect("router follows it");
197 &rest[..to]
198 };
199 assert!(
200 validate.contains("cratefield::tables_api::parse(DECLARED)"),
201 "the declaration is never read while composing: {validate}"
202 );
203
204 assert!(
207 tables.contains("fn surface(&self) -> Surface"),
208 "the declared tables are served and unpublished: {tables}"
209 );
210 assert!(
211 tables.contains("cratefield::tables_api::surface(&tables)"),
212 "{tables}"
213 );
214
215 let cargo = file("Cargo.toml");
219 assert!(cargo.contains("\"tables-api\","), "{cargo}");
220
221 let lib = file("src/lib.rs");
222 assert!(lib.contains("pub mod tables;"), "{lib}");
223 assert!(
224 lib.contains(".module(crate::tables::DeclaredTables::new())"),
225 "{lib}"
226 );
227 }
228
229 #[test]
230 fn a_generated_venture_can_be_tested() {
231 let manifest = VentureManifest::from_json_str(with_tables()).expect("parses");
236 let set = manifest.resolve(&catalog::builtin()).expect("resolves");
237 let venture = generate::generate(&manifest, &set, &generate::HarnessSource::default())
238 .expect("generates");
239 let cargo = venture
240 .files
241 .iter()
242 .find(|f| f.path == "Cargo.toml")
243 .map(|f| f.contents.as_str())
244 .unwrap_or_default();
245
246 let dev = cargo
247 .split_once("[dev-dependencies]")
248 .unwrap_or_else(|| {
249 panic!("a venture with no dev-dependencies cannot be tested: {cargo}")
250 })
251 .1;
252 assert!(
253 dev.contains("\"testing\""),
254 "the conformance kit is what a venture tests through: {dev}"
255 );
256 assert!(dev.contains("pollster"), "{dev}");
259 }
260
261 #[test]
262 fn a_venture_with_a_non_public_table_wires_a_verifier() {
263 let manifest = VentureManifest::from_json_str(with_tables()).expect("parses");
267 let set = manifest.resolve(&catalog::builtin()).expect("resolves");
268 let venture = generate::generate(&manifest, &set, &generate::HarnessSource::default())
269 .expect("generates");
270 let lib = venture
271 .files
272 .iter()
273 .find(|f| f.path == "src/lib.rs")
274 .map(|f| f.contents.as_str())
275 .unwrap_or_default();
276 assert!(
277 lib.contains(".auth_from_env()"),
278 "an owner table generated a venture that cannot boot: {lib}"
279 );
280 }
281
282 #[test]
283 fn a_venture_whose_tables_are_all_public_does_not_wire_a_verifier() {
284 let manifest = VentureManifest::from_json_str(
286 &with_tables().replace(r#""note": "owner""#, r#""note": "public-read""#),
287 )
288 .expect("parses");
289 let set = manifest.resolve(&catalog::builtin()).expect("resolves");
290 let venture = generate::generate(&manifest, &set, &generate::HarnessSource::default())
291 .expect("generates");
292 let lib = venture
293 .files
294 .iter()
295 .find(|f| f.path == "src/lib.rs")
296 .map(|f| f.contents.as_str())
297 .unwrap_or_default();
298 assert!(!lib.contains("auth_from_env"), "{lib}");
299 }
300
301 #[test]
302 fn a_venture_whose_tables_are_all_public_does_not_demand_a_verifier() {
303 let tables = generated_tables_rs(
309 &with_tables().replace(r#""note": "owner""#, r#""note": "public-read""#),
310 );
311 assert!(tables.contains("&[Port::Db]"), "{tables}");
312 assert!(!tables.contains("Port::Auth"), "{tables}");
313 }
314
315 fn generated_tables_rs(json: &str) -> String {
317 let manifest = VentureManifest::from_json_str(json).expect("the fixture parses");
318 let set = manifest.resolve(&catalog::builtin()).expect("resolves");
319 let venture = generate::generate(&manifest, &set, &generate::HarnessSource::default())
320 .expect("generates");
321 venture
322 .files
323 .iter()
324 .find(|f| f.path == "src/tables.rs")
325 .map(|f| f.contents.clone())
326 .unwrap_or_default()
327 }
328
329 #[test]
330 fn one_table_that_is_not_public_is_enough_to_need_a_verifier() {
331 let two_tables = with_tables()
334 .replace(
335 r#""note": {
336 "primary_key": "id","#,
337 r#""tier": {
338 "primary_key": "id",
339 "fields": [ { "name": "id", "kind": "uuid", "required": true } ]
340 },
341 "note": {
342 "primary_key": "id","#,
343 )
344 .replace(
345 r#""table_privacy": {"#,
346 r#""table_privacy": {
347 "tier": { "holds": "nothing", "reason": "Plan tiers; nobody is in them." },"#,
348 )
349 .replace(
350 r#""table_access": { "note": "owner" }"#,
351 r#""table_access": { "note": "owner", "tier": "public-read" }"#,
352 );
353 let tables = generated_tables_rs(&two_tables);
354 assert!(
355 tables.contains("\"tier\""),
356 "the fixture has both: {tables}"
357 );
358 assert!(tables.contains("Port::Auth"), "{tables}");
359 }
360
361 #[test]
362 fn a_venture_that_lists_no_cors_origins_still_generates_valid_rust() {
363 let legal = VentureManifest::from_json_str(sample_json()).expect("parses");
379 let set = legal.resolve(&catalog::builtin()).expect("resolves");
380 let mut without = legal.clone();
381 without.cors_origins.clear();
382 assert!(
383 without.validate().is_err(),
384 "an empty origin list has to be a manifest error, or `fz build` \
385 emits a venture that panics on its first request"
386 );
387
388 let venture = generate::generate(&without, &set, &generate::HarnessSource::default())
389 .expect("generates");
390 let lib = venture
391 .files
392 .iter()
393 .find(|f| f.path == "src/lib.rs")
394 .map(|f| f.contents.as_str())
395 .unwrap_or_default();
396 assert!(!lib.contains("cors_origins"), "{lib}");
397
398 let manifest = VentureManifest::from_json_str(sample_json()).expect("parses");
400 let set = manifest.resolve(&catalog::builtin()).expect("resolves");
401 let venture = generate::generate(&manifest, &set, &generate::HarnessSource::default())
402 .expect("generates");
403 let lib = venture
404 .files
405 .iter()
406 .find(|f| f.path == "src/lib.rs")
407 .map(|f| f.contents.as_str())
408 .unwrap_or_default();
409 assert!(
410 lib.contains(".cors_origins([\"https://acme.example\"])"),
411 "{lib}"
412 );
413 }
414
415 #[test]
416 fn a_declared_table_must_say_who_may_reach_it() {
417 let base: serde_json::Value =
421 serde_json::from_str(with_tables()).expect("the fixture is JSON");
422 let mut object = base.as_object().expect("an object").clone();
423 object.remove("table_access");
424 let error = VentureManifest::from_json_str(&serde_json::Value::Object(object).to_string())
425 .expect("parses")
426 .validate()
427 .expect_err("must say");
428 let text = error.to_string();
429 assert!(text.contains("does not say who may reach it"), "{text}");
430 assert!(text.contains("public-read"), "the four are listed: {text}");
431 }
432
433 #[test]
434 fn owner_needs_the_table_to_say_whose_each_row_is() {
435 let base: serde_json::Value =
440 serde_json::from_str(with_tables()).expect("the fixture is JSON");
441 let mut object = base.as_object().expect("an object").clone();
442 object.insert(
443 "table_privacy".to_owned(),
444 serde_json::json!({ "note": { "holds": "nothing", "reason": "Reference data." } }),
445 );
446 let error = VentureManifest::from_json_str(&serde_json::Value::Object(object).to_string())
447 .expect("parses")
448 .validate()
449 .expect_err("nothing to match a caller against");
450 let text = error.to_string();
451 assert!(
452 text.contains("no column to match a caller against"),
453 "{text}"
454 );
455
456 for level in ["public-read", "tenant-members", "admin"] {
458 let mut object = base.as_object().expect("an object").clone();
459 object.insert(
460 "table_privacy".to_owned(),
461 serde_json::json!({ "note": { "holds": "nothing", "reason": "Reference data." } }),
462 );
463 object.insert(
464 "table_access".to_owned(),
465 serde_json::json!({ "note": level }),
466 );
467 VentureManifest::from_json_str(&serde_json::Value::Object(object).to_string())
468 .expect("parses")
469 .validate()
470 .unwrap_or_else(|err| panic!("{level} should be legal here: {err}"));
471 }
472 }
473
474 #[test]
475 fn access_for_a_table_that_is_not_declared_is_refused() {
476 let base: serde_json::Value =
477 serde_json::from_str(with_tables()).expect("the fixture is JSON");
478 let mut object = base.as_object().expect("an object").clone();
479 object.insert(
480 "table_access".to_owned(),
481 serde_json::json!({ "note": "owner", "ghost": "admin" }),
482 );
483 let error = VentureManifest::from_json_str(&serde_json::Value::Object(object).to_string())
484 .expect("parses")
485 .validate()
486 .expect_err("not a declared table");
487 assert!(
488 error.to_string().contains("not a declared table"),
489 "{error}"
490 );
491 }
492
493 #[test]
494 fn a_venture_with_no_declared_tables_gets_no_tables_module() {
495 let manifest = VentureManifest::from_json_str(sample_json()).expect("parses");
496 let set = manifest.resolve(&catalog::builtin()).expect("resolves");
497 let venture = generate::generate(&manifest, &set, &generate::HarnessSource::default())
498 .expect("generates");
499 let paths: Vec<&str> = venture.files.iter().map(|f| f.path.as_str()).collect();
500 assert!(!paths.contains(&"src/tables.rs"), "{paths:?}");
501 let lib = venture
502 .files
503 .iter()
504 .find(|f| f.path == "src/lib.rs")
505 .map(|f| f.contents.as_str())
506 .unwrap_or_default();
507 assert!(!lib.contains("mod tables"), "{lib}");
508 }
509
510 fn without_privacy() -> String {
512 let full: serde_json::Value =
513 serde_json::from_str(with_tables()).expect("the fixture is JSON");
514 let mut object = full.as_object().expect("an object").clone();
515 object.remove("table_privacy");
516 serde_json::Value::Object(object).to_string()
517 }
518
519 #[test]
520 fn a_declared_table_must_say_what_it_holds() {
521 let manifest = VentureManifest::from_json_str(&without_privacy()).expect("parses");
526 let error = manifest.validate().expect_err("must say");
527 let text = error.to_string();
528 assert!(text.contains("does not say what it holds"), "{text}");
529 assert!(text.contains("tables.note.privacy"), "{text}");
530 }
531
532 #[test]
533 fn saying_a_table_holds_nothing_is_one_line_and_needs_the_reason() {
534 let base: serde_json::Value =
535 serde_json::from_str(&without_privacy()).expect("the fixture is JSON");
536 let with = |privacy: serde_json::Value| {
537 let mut object = base.as_object().expect("an object").clone();
538 object.insert(
539 "table_privacy".to_owned(),
540 serde_json::json!({ "note": privacy }),
541 );
542 object.insert(
546 "table_access".to_owned(),
547 serde_json::json!({ "note": "public-read" }),
548 );
549 VentureManifest::from_json_str(&serde_json::Value::Object(object).to_string())
550 .expect("parses")
551 };
552
553 with(serde_json::json!({
554 "holds": "nothing",
555 "reason": "One row per plan tier; nobody is named in it."
556 }))
557 .validate()
558 .expect("a reason is all it takes");
559
560 let error = with(serde_json::json!({ "holds": "nothing", "reason": " " }))
563 .validate()
564 .expect_err("an empty reason is not a reason");
565 assert!(error.to_string().contains("needs a reason"), "{error}");
566 }
567
568 #[test]
569 fn a_declaration_must_describe_columns_the_table_actually_has() {
570 let base: serde_json::Value =
573 serde_json::from_str(&without_privacy()).expect("the fixture is JSON");
574 let check = |privacy: serde_json::Value| -> String {
575 let mut object = base.as_object().expect("an object").clone();
576 object.insert(
577 "table_privacy".to_owned(),
578 serde_json::json!({ "note": privacy }),
579 );
580 object.insert(
581 "table_access".to_owned(),
582 serde_json::json!({ "note": "public-read" }),
583 );
584 VentureManifest::from_json_str(&serde_json::Value::Object(object).to_string())
585 .expect("parses")
586 .validate()
587 .expect_err("not valid")
588 .to_string()
589 };
590
591 let text = check(serde_json::json!({
592 "holds": "personal", "subject": "nobody", "kind": "content",
593 "disposition": "erase", "description": "x"
594 }));
595 assert!(text.contains("`nobody` is not a field"), "{text}");
596
597 let text = check(serde_json::json!({
598 "holds": "personal", "subject": "author", "kind": "invented",
599 "disposition": "erase", "description": "x"
600 }));
601 assert!(text.contains("is not a data kind"), "{text}");
602
603 let text = check(serde_json::json!({
605 "holds": "personal", "subject": "author", "kind": "content",
606 "disposition": "erase", "description": " "
607 }));
608 assert!(text.contains("must not be empty"), "{text}");
609 }
610
611 #[test]
612 fn a_field_that_becomes_an_identifier_cannot_carry_a_quote() {
613 let base: serde_json::Value =
624 serde_json::from_str(sample_json()).expect("the fixture is JSON");
625 let with = |field: &str, value: serde_json::Value| {
626 let mut object = base.as_object().expect("an object").clone();
627 object.insert(field.to_owned(), value);
628 VentureManifest::from_json_str(&serde_json::Value::Object(object).to_string())
629 .expect("parses")
630 .validate()
631 };
632
633 for (field, value) in [
634 (
635 "name",
636 serde_json::json!("acme\", evil(); cratefield::Venture::new(\"x"),
637 ),
638 ("host", serde_json::json!("acme.test\", evil(), \"x")),
639 (
640 "public_url",
641 serde_json::json!("https://acme.test\", evil(), \"x"),
642 ),
643 (
644 "cors_origins",
645 serde_json::json!(["https://acme.test\", evil(), \"x"]),
646 ),
647 ] {
648 let error = with(field, value).expect_err(field).to_string();
649 assert!(error.contains(field), "{error}");
650 assert!(!error.contains("evil()"), "{error}");
653 }
654
655 with("name", serde_json::json!("acme-signups_2")).expect("an ordinary name");
658 with("host", serde_json::json!("acme.factory0.dev:8787")).expect("a host with a port");
659 with("public_url", serde_json::json!("https://acme.example")).expect("an ordinary url");
660 with(
661 "cors_origins",
662 serde_json::json!(["https://acme.example", "http://localhost:5173"]),
663 )
664 .expect("ordinary origins");
665 }
666
667 #[test]
668 fn an_origin_with_a_path_is_not_an_origin() {
669 let base: serde_json::Value =
673 serde_json::from_str(sample_json()).expect("the fixture is JSON");
674 let mut object = base.as_object().expect("an object").clone();
675 object.insert(
676 "cors_origins".to_owned(),
677 serde_json::json!(["https://acme.example/app"]),
678 );
679 let error = VentureManifest::from_json_str(&serde_json::Value::Object(object).to_string())
680 .expect("parses")
681 .validate()
682 .expect_err("an origin has no path")
683 .to_string();
684 assert!(error.contains("cors_origins"), "{error}");
685 }
686
687 #[test]
688 fn browser_extension_origins_are_origins() {
689 let with_origins = |origins: serde_json::Value| {
693 let base: serde_json::Value =
694 serde_json::from_str(sample_json()).expect("the fixture is JSON");
695 let mut object = base.as_object().expect("an object").clone();
696 object.insert("cors_origins".to_owned(), origins);
697 VentureManifest::from_json_str(&serde_json::Value::Object(object).to_string())
698 .expect("parses")
699 .validate()
700 };
701 with_origins(serde_json::json!([
702 "chrome-extension://abcdefghijklmnopabcdefghijklmnop",
703 "moz-extension://5de6e0f6-2b1a-4f6e-9c3d-0a1b2c3d4e5f",
704 "safari-web-extension://5DE6E0F6-2B1A-4F6E-9C3D-0A1B2C3D4E5F",
705 ]))
706 .expect("extension origins are origins");
707
708 for bad in [
711 "chrome-extension://abcdefghijklmnopabcdefghijklmno",
712 "chrome-extension://qbcdefghijklmnopabcdefghijklmnop",
713 "moz-extension://5DE6E0F6-2B1A-4F6E-9C3D-0A1B2C3D4E5F",
714 "chrome-extension://",
715 "chrome-extension://abcdefghijklmnopabcdefghijklmnop/",
716 "foo-bar://x",
717 "null",
718 ] {
719 let error = with_origins(serde_json::json!([bad]))
720 .expect_err(bad)
721 .to_string();
722 assert!(error.contains("cors_origins"), "{bad}: {error}");
723 }
724 }
725
726 #[test]
727 fn a_subject_column_must_be_able_to_hold_a_caller_id() {
728 let declare = |kind: &str| -> Result<(), ManifestError> {
737 let json = format!(
738 r#"{{
739 "name": "acme", "host": "acme.factory0.dev",
740 "cors_origins": ["https://acme.example"], "modules": [],
741 "tables": {{ "note": {{ "primary_key": "id", "fields": [
742 {{ "name": "id", "kind": "uuid", "required": true }},
743 {{ "name": "author", "kind": "{kind}" }}
744 ] }} }},
745 "table_privacy": {{ "note": {{
746 "holds": "personal", "subject": "author", "kind": "content",
747 "disposition": "erase", "description": "Notes."
748 }} }},
749 "table_access": {{ "note": "owner" }}
750 }}"#
751 );
752 VentureManifest::from_json_str(&json)
753 .expect("parses")
754 .validate()
755 };
756
757 let text = declare("integer")
758 .expect_err("cannot hold an id")
759 .to_string();
760 assert!(text.contains("cannot hold a caller's id"), "{text}");
761 assert!(text.contains("`text` or `uuid`"), "{text}");
762
763 declare("uuid").expect("a uuid holds an id");
767 declare("text").expect("so does text");
768 }
769
770 #[test]
771 fn published_prose_is_plain_text() {
772 let base: serde_json::Value =
785 serde_json::from_str(&without_privacy()).expect("the fixture is JSON");
786 let check = |privacy: serde_json::Value| -> Result<(), ManifestError> {
787 let mut object = base.as_object().expect("an object").clone();
788 object.insert(
789 "table_privacy".to_owned(),
790 serde_json::json!({ "note": privacy }),
791 );
792 object.insert(
793 "table_access".to_owned(),
794 serde_json::json!({ "note": "public-read" }),
795 );
796 VentureManifest::from_json_str(&serde_json::Value::Object(object).to_string())
797 .expect("parses")
798 .validate()
799 };
800 let personal = |description: &str| {
801 serde_json::json!({
802 "holds": "personal", "subject": "author", "kind": "content",
803 "disposition": "erase", "description": description
804 })
805 };
806
807 let text = check(personal("Line one\rLine two."))
808 .expect_err("a bare CR")
809 .to_string();
810 assert!(text.contains("U+000D"), "{text}");
811
812 let text = check(personal("Your notes\u{202e}.txt"))
813 .expect_err("a direction override")
814 .to_string();
815 assert!(text.contains("U+202E"), "{text}");
816 assert!(text.contains("direction"), "{text}");
817
818 let text = check(serde_json::json!({ "holds": "nothing", "reason": "Tiers\u{0}." }))
820 .expect_err("a NUL")
821 .to_string();
822 assert!(text.contains("U+0000"), "{text}");
823
824 check(personal(
828 "The notes you wrote — “first” and ‘second’.\n\tWith a tab.",
829 ))
830 .expect("prose is allowed to have shape");
831 }
832
833 #[test]
834 fn anonymise_names_columns_a_database_can_actually_overwrite() {
835 let base: serde_json::Value =
840 serde_json::from_str(&without_privacy()).expect("the fixture is JSON");
841 let check = |columns: serde_json::Value| -> String {
842 let mut object = base.as_object().expect("an object").clone();
843 object.insert(
844 "table_privacy".to_owned(),
845 serde_json::json!({ "note": {
846 "holds": "personal", "subject": "author", "kind": "content",
847 "disposition": { "anonymise": columns },
848 "description": "x"
849 }}),
850 );
851 VentureManifest::from_json_str(&serde_json::Value::Object(object).to_string())
852 .expect("parses")
853 .validate()
854 .map_or_else(|err| err.to_string(), |()| String::new())
855 };
856
857 assert!(
858 check(serde_json::json!(["body"])).is_empty(),
859 "an optional column is fine"
860 );
861 assert!(
862 check(serde_json::json!(["id"])).contains("required and has no default"),
863 "a required column with no default was accepted"
864 );
865 assert!(
866 check(serde_json::json!([])).contains("erases nothing"),
867 "an empty anonymise list was accepted"
868 );
869 assert!(
870 check(serde_json::json!(["ghost"])).contains("not a field"),
871 "a column that does not exist was accepted"
872 );
873 }
874
875 #[test]
876 fn privacy_for_a_table_that_is_not_declared_is_refused() {
877 let base: serde_json::Value =
878 serde_json::from_str(with_tables()).expect("the fixture is JSON");
879 let mut object = base.as_object().expect("an object").clone();
880 let mut privacy = object["table_privacy"].as_object().expect("map").clone();
881 privacy.insert(
882 "ghost".to_owned(),
883 serde_json::json!({ "holds": "nothing", "reason": "x" }),
884 );
885 object.insert(
886 "table_privacy".to_owned(),
887 serde_json::Value::Object(privacy),
888 );
889 let error = VentureManifest::from_json_str(&serde_json::Value::Object(object).to_string())
890 .expect("parses")
891 .validate()
892 .expect_err("not a declared table");
893 assert!(
894 error.to_string().contains("not a declared table"),
895 "{error}"
896 );
897 }
898
899 #[test]
900 fn a_manifest_parses_both_module_ref_forms() {
901 let manifest = VentureManifest::from_json_str(sample_json()).expect("parses");
902 assert_eq!(manifest.name, "acme-signups");
903 assert_eq!(manifest.module_slugs(), vec!["waitlist", "email-signup"]);
904 }
905
906 #[test]
907 fn a_manifest_round_trips_through_json() {
908 let manifest = VentureManifest::from_json_str(sample_json()).expect("parses");
909 let json = manifest.to_json_string().expect("serializes");
910 let back = VentureManifest::from_json_str(&json).expect("re-parses");
911 assert_eq!(manifest, back);
912 }
913
914 #[test]
915 fn resolution_pulls_the_closure_and_keys_order_independently() {
916 let manifest = VentureManifest::from_json_str(sample_json()).expect("parses");
917 let set = manifest.resolve(&builtin()).expect("resolves");
918 assert!(set.contains("waitlist"));
919 assert!(set.contains("email-signup"));
920 assert_eq!(set.content_key(), "email-signup+waitlist");
923 }
924
925 #[test]
926 fn an_unknown_module_is_refused_with_a_clear_error() {
927 let json = r#"{ "name": "x", "host": "x.dev", "cors_origins": ["https://x.dev"], "modules": ["ghost"] }"#;
928 let manifest = VentureManifest::from_json_str(json).expect("parses");
929 let err = manifest.resolve(&builtin()).expect_err("unknown module");
930 assert!(matches!(
931 err,
932 ManifestError::Resolve(ResolveError::UnknownModule(ref s)) if s == "ghost"
933 ));
934 }
935
936 #[test]
937 fn an_empty_name_is_refused() {
938 let json = r#"{ "name": "", "host": "x.dev", "modules": [] }"#;
939 let manifest = VentureManifest::from_json_str(json).expect("parses");
940 assert_eq!(
941 manifest.validate(),
942 Err(ManifestError::MissingField("name"))
943 );
944 }
945
946 #[test]
947 fn a_duplicate_module_is_refused() {
948 let json = r#"{ "name": "x", "host": "x.dev", "cors_origins": ["https://x.dev"], "modules": ["cms", "cms"] }"#;
949 let manifest = VentureManifest::from_json_str(json).expect("parses");
950 assert_eq!(
951 manifest.validate(),
952 Err(ManifestError::DuplicateModule("cms".to_owned()))
953 );
954 }
955
956 #[test]
957 fn generation_is_deterministic() {
958 let manifest = VentureManifest::from_json_str(sample_json()).expect("parses");
959 let set = manifest.resolve(&builtin()).expect("resolves");
960 let a = generate(&manifest, &set, &HarnessSource::default()).expect("generates");
961 let b = generate(&manifest, &set, &HarnessSource::default()).expect("generates");
962 assert_eq!(a, b, "the same manifest must generate byte-identical files");
963 assert_eq!(a.composition_hash, b.composition_hash);
964 }
965
966 #[test]
967 fn generation_emits_the_expected_files_and_wiring() {
968 let manifest = VentureManifest::from_json_str(sample_json()).expect("parses");
969 let set = manifest.resolve(&builtin()).expect("resolves");
970 let venture = generate(&manifest, &set, &HarnessSource::default()).expect("generates");
971
972 let paths: Vec<&str> = venture.files.iter().map(|f| f.path.as_str()).collect();
973 assert!(paths.contains(&"Cargo.toml"));
974 assert!(paths.contains(&"src/lib.rs"));
975 assert!(paths.contains(&"src/fz_main.rs"));
976 assert!(paths.contains(&"wrangler.toml"));
977 assert!(paths.contains(&"seed.sql"), "seed_sql present -> seed.sql");
978
979 let file = |name: &str| {
980 venture
981 .files
982 .iter()
983 .find(|f| f.path == name)
984 .map(|f| f.contents.as_str())
985 .unwrap_or_default()
986 };
987
988 let cargo = file("Cargo.toml");
989 assert!(cargo.contains("\"resend\""), "{cargo}");
991 assert!(cargo.contains("\"email-signup\""));
992 assert!(cargo.contains("\"waitlist\""));
993 assert!(cargo.contains("crate-type = [\"cdylib\", \"rlib\"]"));
994
995 let lib = file("src/lib.rs");
996 assert!(lib.contains("use cratefield::waitlist::Waitlist;"));
997 assert!(lib.contains("use cratefield::email_signup::EmailSignup;"));
998 assert!(lib.contains(".module(Waitlist::new())"));
999 assert!(lib.contains(".module(EmailSignup::new())"));
1000 assert!(lib.contains("default_templates()"));
1001 assert!(lib.contains("cratefield::resend::Resend::new("));
1004 assert!(lib.contains("cratefield::cloudflare::WorkersClock"));
1005 assert!(lib.contains("cratefield::Venture::new(\"acme-signups\", \"acme.factory0.dev\")"));
1006
1007 let fz = file("src/fz_main.rs");
1008 assert!(fz.contains("cratefield_cli::main_for(acme_signups::harness);"));
1009
1010 let wrangler = file("wrangler.toml");
1011 assert!(wrangler.contains("binding = \"DB\""));
1012 assert!(wrangler.contains("[vars]"));
1013 assert!(wrangler.contains("brand = \"Acme\""));
1014 }
1015
1016 #[test]
1017 fn a_cms_only_venture_needs_no_mailer() {
1018 let json = r#"{ "name": "docs-site", "host": "docs.dev", "cors_origins": ["https://docs.dev"], "modules": ["cms"] }"#;
1019 let manifest = VentureManifest::from_json_str(json).expect("parses");
1020 let set = manifest.resolve(&builtin()).expect("resolves");
1021 let venture = generate(&manifest, &set, &HarnessSource::default()).expect("generates");
1022 let cargo = &venture.files[0].contents;
1023 assert!(
1024 !cargo.contains("\"resend\""),
1025 "cms needs no mailer: {cargo}"
1026 );
1027 }
1028
1029 fn generated_for(modules: &[&str]) -> (String, String) {
1031 let json = serde_json::json!({
1032 "name": "every-module",
1033 "host": "every.dev",
1034 "cors_origins": ["https://every.dev"],
1035 "modules": modules,
1036 });
1037 let manifest = VentureManifest::from_json_str(&json.to_string()).expect("parses");
1038 let set = manifest.resolve(&builtin()).expect("resolves");
1039 let venture = generate(&manifest, &set, &HarnessSource::default()).expect("generates");
1040 let file = |name: &str| {
1041 venture
1042 .files
1043 .iter()
1044 .find(|f| f.path == name)
1045 .map(|f| f.contents.clone())
1046 .unwrap_or_default()
1047 };
1048 (file("Cargo.toml"), file("src/lib.rs"))
1049 }
1050
1051 #[test]
1052 fn every_builtin_module_generates() {
1053 let catalog = builtin();
1057 let slugs: Vec<&str> = catalog.modules.iter().map(|m| m.slug.as_str()).collect();
1058 for slug in &slugs {
1059 let (_, lib) = generated_for(&[*slug]);
1060 let codegen = generate::REGISTRY
1061 .iter()
1062 .find(|m| m.slug == *slug)
1063 .expect("in the registry");
1064 let module = format!(".module({}::new())", codegen.type_name);
1065 assert!(lib.contains(&module), "{slug}: {lib}");
1066 }
1067 let (_, lib) = generated_for(&slugs);
1068 for codegen in generate::REGISTRY {
1069 let module = format!(".module({}::new())", codegen.type_name);
1070 assert!(lib.contains(&module), "{lib}");
1071 }
1072 }
1073
1074 #[test]
1075 fn a_module_that_needs_push_gets_it_from_the_environment() {
1076 let (cargo, lib) = generated_for(&["notifications"]);
1077 assert!(cargo.contains("\"push-wiring\""), "{cargo}");
1078 assert!(lib.contains(".push_from_env()"), "{lib}");
1079
1080 let (cargo, lib) = generated_for(&["cms"]);
1081 assert!(!cargo.contains("push-wiring"), "{cargo}");
1082 assert!(!lib.contains("push_from_env"), "{lib}");
1083 }
1084
1085 #[test]
1086 fn a_manifest_with_privacy_resolves_against_the_builtin_catalog() {
1087 let json = r#"{ "name": "x", "host": "x.dev", "cors_origins": ["https://x.dev"], "modules": ["waitlist", "privacy"] }"#;
1090 let manifest = VentureManifest::from_json_str(json).expect("parses");
1091 let set = manifest.resolve(&builtin()).expect("resolves");
1092 assert_eq!(set.content_key(), "privacy+waitlist");
1093 }
1094
1095 #[test]
1096 fn a_path_source_emits_path_deps_for_offline_builds() {
1097 let manifest = VentureManifest::from_json_str(sample_json()).expect("parses");
1098 let set = manifest.resolve(&builtin()).expect("resolves");
1099 let venture = generate(
1100 &manifest,
1101 &set,
1102 &HarnessSource::Path("/opt/harness".to_owned()),
1103 )
1104 .expect("generates");
1105 let cargo = &venture.files[0].contents;
1106 assert!(
1107 cargo.contains("path = \"/opt/harness/crates/facade\""),
1108 "{cargo}"
1109 );
1110 }
1111
1112 #[test]
1113 fn the_builtin_catalog_is_coherent() {
1114 builtin()
1115 .validate()
1116 .expect("the built-in catalog must be valid");
1117 }
1118
1119 fn single_entry(slug: &str, releases: Vec<ModuleRelease>) -> Catalog {
1122 Catalog {
1123 modules: vec![CatalogModule {
1124 slug: slug.to_owned(),
1125 name: slug.to_owned(),
1126 summary: String::new(),
1127 tier: Tier::Optional,
1128 depends_on: vec![],
1129 releases,
1130 }],
1131 }
1132 }
1133
1134 fn approved_pin(version: &str) -> ModuleRelease {
1135 ModuleRelease {
1136 version: version.to_owned(),
1137 digest: format!("sha256:{}", "a".repeat(64)),
1138 review: ReleaseReview::Approved {
1139 reviewer: "test-reviewer".to_owned(),
1140 reviewed_at: "2026-01-01T00:00:00Z".to_owned(),
1141 },
1142 }
1143 }
1144
1145 #[test]
1146 fn the_builtin_catalog_resolves_with_reviewed_pins() {
1147 let set = builtin()
1148 .resolve(&["waitlist"])
1149 .expect("the shipped catalog resolves");
1150 assert_eq!(set.releases().len(), set.slugs().len());
1151 let pin = set.release("waitlist").expect("waitlist is pinned");
1152 assert_eq!(pin.version, "0.1.1");
1153 assert!(is_placeholder_digest(&pin.digest));
1156 }
1157
1158 #[test]
1159 fn an_entry_with_no_pinned_release_is_refused() {
1160 let catalog = single_entry("blog", vec![]);
1161 assert_eq!(
1162 catalog.resolve(&["blog"]),
1163 Err(ResolveError::Unpinned("blog".to_owned()))
1164 );
1165 }
1166
1167 #[test]
1168 fn revocation_refuses_a_selection_that_resolved_minutes_earlier() {
1169 let mut catalog = single_entry("blog", vec![approved_pin("1.0.0")]);
1170 catalog
1171 .resolve(&["blog"])
1172 .expect("approved before the advisory");
1173 catalog.modules[0].releases = vec![ModuleRelease {
1174 version: "1.0.0".to_owned(),
1175 digest: format!("sha256:{}", "a".repeat(64)),
1176 review: ReleaseReview::Revoked {
1177 by: "sec-oncall".to_owned(),
1178 revoked_at: "2026-09-08T00:00:00Z".to_owned(),
1179 reason: "dependency poisoned".to_owned(),
1180 },
1181 }];
1182 let err = catalog
1183 .resolve(&["blog"])
1184 .expect_err("revoked after the advisory");
1185 assert!(matches!(
1186 &err,
1187 ResolveError::Revoked { slug, version, .. }
1188 if slug == "blog" && version == "1.0.0"
1189 ));
1190 assert!(err.to_string().contains("dependency poisoned"));
1191 }
1192
1193 #[test]
1194 fn the_manifest_propagates_a_release_refusal() {
1195 let json = r#"{ "name": "x", "host": "x.dev", "cors_origins": ["https://x.dev"], "modules": ["blog"] }"#;
1196 let manifest = VentureManifest::from_json_str(json).expect("parses");
1197 let catalog = single_entry(
1198 "blog",
1199 vec![ModuleRelease {
1200 version: "2.0.0".to_owned(),
1201 digest: format!("sha256:{}", "a".repeat(64)),
1202 review: ReleaseReview::Pending,
1203 }],
1204 );
1205 let err = manifest
1206 .resolve(&catalog)
1207 .expect_err("pending is not approved");
1208 assert!(matches!(
1209 err,
1210 ManifestError::Resolve(ResolveError::Unreviewed { ref slug, ref version })
1211 if slug == "blog" && version == "2.0.0"
1212 ));
1213 }
1214
1215 fn artifact() -> GeneratedVenture {
1218 let files = vec![
1219 GeneratedFile {
1220 path: "Cargo.toml".to_owned(),
1221 contents: "[package]\nname = \"acme\"\n".to_owned(),
1222 },
1223 GeneratedFile {
1224 path: "src/lib.rs".to_owned(),
1225 contents: "// venture\n".to_owned(),
1226 },
1227 ];
1228 let composition_hash = generate::composition_hash_of(&files);
1229 GeneratedVenture {
1230 name: "acme-signups".to_owned(),
1231 modules: vec!["waitlist".to_owned()],
1232 content_key: "waitlist".to_owned(),
1233 composition_hash,
1234 files,
1235 }
1236 }
1237
1238 fn reviewed_set() -> ModuleSet {
1239 single_entry("waitlist", vec![approved_pin("1.0.0")])
1240 .resolve(&["waitlist"])
1241 .expect("resolves")
1242 }
1243
1244 fn good_env() -> BuildEnvironmentAttestation {
1245 BuildEnvironmentAttestation {
1246 sandbox_digest: format!("sha256:{}", "b".repeat(64)),
1247 disposable: true,
1248 workspace_scoped: true,
1249 network_egress: "registry-allowlist".to_owned(),
1250 deploy_credentials_withheld: true,
1251 production_key_absent: true,
1252 limits: ResourceLimits {
1253 wall_clock: "600s".to_owned(),
1254 memory: "4Gi".to_owned(),
1255 disk: "10Gi".to_owned(),
1256 },
1257 }
1258 }
1259
1260 #[test]
1261 fn provenance_round_trips_and_verifies_its_artifact() {
1262 let artifact = artifact();
1263 let record = Provenance::record(
1264 &artifact,
1265 &reviewed_set(),
1266 "test-builder",
1267 Some("2026-09-09T00:00:00Z"),
1268 None,
1269 )
1270 .expect("a local record always writes");
1271 assert_eq!(record.attestation, None);
1272 assert_eq!(record.releases.len(), 1);
1273 let back = Provenance::from_json(&record.to_json()).expect("round-trips");
1274 assert_eq!(back, record);
1275 back.verify_against(&artifact)
1276 .expect("the artifact verifies");
1277 }
1278
1279 #[test]
1280 fn a_tampered_file_fails_verification() {
1281 let artifact = artifact();
1282 let record =
1283 Provenance::record(&artifact, &reviewed_set(), "b", None, None).expect("records");
1284 let mut tampered = artifact.clone();
1285 tampered.files[1].contents = "// evil\n".to_owned();
1286 let err = record
1287 .verify_against(&tampered)
1288 .expect_err("digest must catch the edit");
1289 assert!(matches!(
1290 err,
1291 ProvenanceError::FileDigestMismatch { ref path, .. } if path == "src/lib.rs"
1292 ));
1293 }
1294
1295 #[test]
1296 fn files_swapped_in_or_out_fail_verification() {
1297 let artifact = artifact();
1298 let record =
1299 Provenance::record(&artifact, &reviewed_set(), "b", None, None).expect("records");
1300 let mut extra = artifact.clone();
1301 extra.files.push(GeneratedFile {
1302 path: "src/backdoor.rs".to_owned(),
1303 contents: String::new(),
1304 });
1305 assert!(matches!(
1306 record.verify_against(&extra),
1307 Err(ProvenanceError::FileListMismatch { .. })
1308 ));
1309 }
1310
1311 #[test]
1312 fn a_fabricated_composition_hash_fails_verification() {
1313 let artifact = artifact();
1314 let record =
1315 Provenance::record(&artifact, &reviewed_set(), "b", None, None).expect("records");
1316 let mut drifted = artifact.clone();
1317 drifted.composition_hash = "deadbeef".to_owned();
1318 assert!(matches!(
1319 record.verify_against(&drifted),
1320 Err(ProvenanceError::CompositionMismatch { .. })
1321 ));
1322 }
1323
1324 #[test]
1325 fn provenance_never_claims_a_different_venture_or_set() {
1326 let artifact = artifact();
1327 let record =
1328 Provenance::record(&artifact, &reviewed_set(), "b", None, None).expect("records");
1329 let other = GeneratedVenture {
1330 name: "someone-else".to_owned(),
1331 ..artifact.clone()
1332 };
1333 assert!(matches!(
1334 record.verify_against(&other),
1335 Err(ProvenanceError::VentureMismatch { .. })
1336 ));
1337 let other = GeneratedVenture {
1338 content_key: "cms+waitlist".to_owned(),
1339 ..artifact
1340 };
1341 assert!(matches!(
1342 record.verify_against(&other),
1343 Err(ProvenanceError::ModuleSetMismatch { .. })
1344 ));
1345 }
1346
1347 #[test]
1348 fn a_record_with_an_unknown_schema_is_rejected() {
1349 let artifact = artifact();
1350 let json = Provenance::record(&artifact, &reviewed_set(), "b", None, None)
1351 .expect("records")
1352 .to_json()
1353 .replace(PROVENANCE_SCHEMA, "fz-provenance/99");
1354 assert!(matches!(
1355 Provenance::from_json(&json),
1356 Err(ProvenanceError::BadRecord(_))
1357 ));
1358 }
1359
1360 #[test]
1361 fn an_attestation_that_fails_the_contract_is_refused_at_record_time() {
1362 let mut env = good_env();
1363 env.network_egress = "open".to_owned();
1364 env.deploy_credentials_withheld = false;
1365 let violations = env.check();
1366 assert_eq!(violations.len(), 2, "{violations:?}");
1367 assert!(matches!(
1368 Provenance::record(&artifact(), &reviewed_set(), "b", None, Some(env)),
1369 Err(ProvenanceError::Environment(BuildEnvViolation(ref v))) if v.len() == 2
1370 ));
1371 assert!(good_env().check().is_empty(), "the fixture is sound");
1372 }
1373
1374 #[test]
1375 fn a_sanctioned_build_refuses_placeholder_pins() {
1376 let set = builtin().resolve(&["waitlist"]).expect("resolves");
1380 assert!(matches!(
1381 Provenance::record(&artifact(), &set, "fleet-a", None, Some(good_env())),
1382 Err(ProvenanceError::UnstampedPin(ref slug)) if slug == "waitlist"
1383 ));
1384 }
1385
1386 #[test]
1387 fn a_sanctioned_record_carries_a_stable_attestation_digest() {
1388 let artifact = artifact();
1389 let record = Provenance::record(
1390 &artifact,
1391 &reviewed_set(),
1392 "fleet-a",
1393 None,
1394 Some(good_env()),
1395 )
1396 .expect("a clean attestation records");
1397 record.verify_against(&artifact).expect("verifies");
1398 let env = record.attestation.as_ref().expect("attested");
1399 assert_eq!(env.attestation_digest(), good_env().attestation_digest());
1400 let back = BuildEnvironmentAttestation::from_json(&env.to_json()).expect("round-trips");
1401 assert_eq!(back.attestation_digest(), env.attestation_digest());
1402 let json = record.to_json();
1403 assert!(json.contains("\"built-at\": null"), "no clock, no lie");
1404 assert_eq!(Provenance::from_json(&json).expect("parses"), record);
1405 }
1406
1407 #[test]
1408 fn file_digests_are_content_bound_sha256_addresses() {
1409 let a = file_digest("hello");
1410 assert!(is_sha256_digest(&a));
1411 assert_ne!(a, file_digest("hellp"));
1412 assert!(!is_placeholder_digest(&a));
1413 assert!(is_placeholder_digest(&format!("sha256:{}", "0".repeat(64))));
1414 }
1415}