- 1310
infer_semantic_type(&serde_json::json!({"left": {}, "right": {}})), - 1311
Some("comparison") - 1312
); - 1313
assert_eq!( - 1314
infer_semantic_type(&serde_json::json!({"tasks": []})), - 1315
Some("checklist") - 1316
); - 1317
assert_eq!( - 1318
infer_semantic_type(&serde_json::json!({"events": []})), - 1319
Some("schedule") - 1320
); - 1321
assert_eq!( - 1322
infer_semantic_type(&serde_json::json!({"lessons": []})), - 1323
Some("lesson") - 1324
); - 1325
assert_eq!( - 1326
infer_semantic_type(&serde_json::json!({"income": [], "expenses": []})), - 1327
Some("budget") - 1328
); - 1329
assert_eq!( - 1330
infer_semantic_type(&serde_json::json!({"decision": "keep"})), - 1331
Some("decision") - 1332
); - 1333
} - 1334
- 1335
#[test] - 1336
fn path_validator_rejects_expressions_and_parent_traversal() { - 1337
for path in ["$.title + evil", "$..title", "$.title.foo()", "$.items[-1]"] { - 1338
assert!( - 1339
validate_binding(&Binding { - 1340
path: path.into(), - 1341
required: false, - 1342
empty: EmptyValue::Omit - 1343
}) - 1344
.is_err() - 1345
); - 1346
} - 1347
} - 1348
- 1349
#[test] - 1350
fn hostile_specs_fail_closed_at_parse_boundary() { - 1351
let mut oversized = spec(); - 1352
oversized.root.props.insert( - 1353
"text".into(), - 1354
SpecValue::Text { - 1355
value: "x".repeat(MAX_TEXT + 1), - 1356
}, - 1357
); - 1358
assert!(matches!( - 1359
validate_spec(&oversized), - 1360
Err(PresentationError::Limit(message)) if message.contains("text") - 1361
)); - 1362
- 1363
let mut deep = spec(); - 1364
let mut node = SpecNode { - 1365
primitive: Primitive::Group, - 1366
props: BTreeMap::new(), - 1367
children: Vec::new(), - 1368
each: None, - 1369
item: None, - 1370
}; - 1371
for _ in 0..=MAX_DEPTH { - 1372
node = SpecNode { - 1373
primitive: Primitive::Group, - 1374
props: BTreeMap::new(), - 1375
children: vec![node], - 1376
each: None, - 1377
item: None, - 1378
}; - 1379
} - 1380
deep.root = node; - 1381
assert!(matches!( - 1382
validate_spec(&deep), - 1383
Err(PresentationError::Limit(message)) if message.contains("depth") - 1384
)); - 1385
} - 1386
- 1387
#[test] - 1388
fn hostile_binding_corpus_never_escapes_the_small_path_language() { - 1389
// Keep this deterministic and dependency-free: the corpus exercises - 1390
// the same boundary that an untrusted pack or model-authored JSON - 1391
// reaches, without relying on a particular fuzzing runner. - 1392
let valid = ["$", "$.title", "$.items[0].label", "$.a_b-c[12]"]; - 1393
for path in valid { - 1394
assert!( - 1395
validate_binding(&Binding { - 1396
path: path.into(), - 1397
required: false, - 1398
empty: EmptyValue::Omit, - 1399
}) - 1400
.is_ok(), - 1401
"valid path rejected: {path}" - 1402
); - 1403
} - 1404
let hostile = [ - 1405
"", - 1406
"title", - 1407
"$..title", - 1408
"$.title + 1", - 1409
"$.title.foo()", - 1410
"$.items[-1]", - 1411
"$.items[abc]", - 1412
"$.items[0].", - 1413
"$.items[]", - 1414
"$.items[0]/../../secret", - 1415
"$['title']", - 1416
&format!("$.{}", "x".repeat(513)), - 1417
]; - 1418
for path in hostile { - 1419
assert!( - 1420
validate_binding(&Binding { - 1421
path: path.into(), - 1422
required: false, - 1423
empty: EmptyValue::Omit, - 1424
}) - 1425
.is_err(), - 1426
"hostile path accepted: {path}" - 1427
); - 1428
} - 1429
} - 1430
- 1431
#[test] - 1432
fn hostile_payloads_keep_the_exact_fallback_and_do_not_become_actions() { - 1433
let mut presentation = spec(); - 1434
presentation.root.props.insert( - 1435
"text".into(), - 1436
SpecValue::Binding(Binding { - 1437
path: "$.title".into(), - 1438
required: true, - 1439
empty: EmptyValue::Omit, - 1440
}), - 1441
); - 1442
let fallback = "Keep this answer unchanged\n\nApprove manually."; - 1443
let payloads = [ - 1444
serde_json::json!({"title": "javascript:alert(1)"}), - 1445
serde_json::json!({"title": "https://example.invalid/?next=javascript:alert(1)"}), - 1446
serde_json::json!({"title": "<script>document.body.innerHTML='x'</script>"}), - 1447
serde_json::json!({"title": {"action": "approve", "args": "--unsafe"}}), - 1448
serde_json::json!({"title": ["\\u0000", "\n", "\r"]}), - 1449
serde_json::json!({"other": "missing required binding"}), - 1450
]; - 1451
for payload in payloads { - 1452
let compiled = compile( - 1453
&presentation, - 1454
&CompileInput { - 1455
semantic_type: "trip".into(), - 1456
payload, - 1457
fallback_text: fallback.into(), - 1458
}, - 1459
); - 1460
assert!( - 1461
matches!(compiled, CompiledPresentation::Fallback { text, .. } if text == fallback), - 1462
"hostile payload unexpectedly changed fallback" - 1463
); - 1464
} - 1465
} - 1466
- 1467
#[test] - 1468
fn generated_specs_hit_limits_without_panicking() { - 1469
for width in [MAX_NODES - 1, MAX_NODES, MAX_NODES + 1] { - 1470
let mut candidate = spec(); - 1471
candidate.root.children = (0..width) - 1472
.map(|index| SpecNode { - 1473
primitive: Primitive::Text, - 1474
props: BTreeMap::from([( - 1475
format!("p{index}"), - 1476
SpecValue::Text { value: "x".into() }, - 1477
)]), - 1478
children: Vec::new(), - 1479
each: None, - 1480
item: None, - 1481
}) - 1482
.collect(); - 1483
let result = validate_spec(&candidate); - 1484
// The root itself counts toward the node budget. - 1485
if width >= MAX_NODES { - 1486
assert!(matches!(result, Err(PresentationError::Limit(_)))); - 1487
} - 1488
} - 1489
} - 1490
- 1491
#[test] - 1492
fn tampered_pack_and_cross_scope_activation_are_denied() { - 1493
let mut library = PresentationLibrary::default(); - 1494
let original = spec(); - 1495
let mut tampered = original.clone(); - 1496
tampered.id = "tampered".into(); - 1497
assert!(matches!( - 1498
library.register(StoredPresentation { - 1499
spec: tampered, - 1500
digest: digest(&original).expect("digest"), - 1501
origin: PresentationOrigin { - 1502
scope: LibraryScope::Workspace, - 1503
owner: "workspace-a".into(), - 1504
plugin_id: Some("untrusted-pack".into()), - 1505
generation: Some("1".into()), - 1506
}, - 1507
enabled: false, - 1508
}), - 1509
Err(PresentationError::DigestMismatch) - 1510
)); - 1511
- 1512
let stored = StoredPresentation { - 1513
digest: digest(&original).expect("digest"), - 1514
spec: original, - 1515
origin: PresentationOrigin { - 1516
scope: LibraryScope::Workspace, - 1517
owner: "workspace-a".into(), - 1518
plugin_id: Some("pack-a".into()), - 1519
generation: Some("1".into()), - 1520
}, - 1521
enabled: false, - 1522
}; - 1523
library.register(stored).expect("register"); - 1524
assert!(matches!( - 1525
library.activate("test.timeline", 1, LibraryScope::Workspace, "workspace-b"), - 1526
Err(PresentationError::ActivationDenied) - 1527
)); - 1528
assert!(library.activations().is_empty()); - 1529
} - 1530
- 1531
#[test] - 1532
fn library_registers_activates_and_selects_by_scope() { - 1533
let spec = spec(); - 1534
let stored = StoredPresentation { - 1535
digest: digest(&spec).expect("digest"), - 1536
spec, - 1537
origin: PresentationOrigin { - 1538
scope: LibraryScope::Workspace, - 1539
owner: "ws-1".into(), - 1540
plugin_id: None, - 1541
generation: None, - 1542
}, - 1543
enabled: false, - 1544
}; - 1545
let mut library = PresentationLibrary::default(); - 1546
library.register(stored).expect("register"); - 1547
library - 1548
.activate("test.timeline", 1, LibraryScope::Workspace, "ws-1") - 1549
.expect("activate"); - 1550
assert!( - 1551
library - 1552
.select("trip", LibraryScope::Workspace, "ws-1") - 1553
.is_some() - 1554
); - 1555
library.deactivate("test.timeline", LibraryScope::Workspace, "ws-1"); - 1556
assert!( - 1557
library - 1558
.select("trip", LibraryScope::Workspace, "ws-1") - 1559
.is_none() - 1560
); - 1561
} - 1562
- 1563
#[test] - 1564
fn preferred_selection_uses_user_intent_before_workspace_intent() { - 1565
let mut workspace_spec = spec(); - 1566
workspace_spec.id = "workspace.layout".into(); - 1567
let mut user_spec = spec(); - 1568
user_spec.id = "user.layout".into(); - 1569
let mut library = PresentationLibrary::default(); - 1570
library - 1571
.register(StoredPresentation { - 1572
digest: digest(&workspace_spec).expect("digest"), - 1573
spec: workspace_spec, - 1574
origin: PresentationOrigin { - 1575
scope: LibraryScope::Workspace, - 1576
owner: "ws".into(), - 1577
plugin_id: None, - 1578
generation: None, - 1579
}, - 1580
enabled: false, - 1581
}) - 1582
.expect("workspace"); - 1583
library - 1584
.register(StoredPresentation { - 1585
digest: digest(&user_spec).expect("digest"), - 1586
spec: user_spec, - 1587
origin: PresentationOrigin { - 1588
scope: LibraryScope::User, - 1589
owner: "user".into(), - 1590
plugin_id: None, - 1591
generation: None, - 1592
}, - 1593
enabled: false, - 1594
}) - 1595
.expect("user"); - 1596
library - 1597
.activate("workspace.layout", 1, LibraryScope::Workspace, "ws") - 1598
.expect("workspace activation"); - 1599
assert_eq!( - 1600
library - 1601
.select_preferred("trip", "user", "ws") - 1602
.map(|s| s.spec.id.as_str()), - 1603
Some("workspace.layout") - 1604
); - 1605
library - 1606
.activate("user.layout", 1, LibraryScope::User, "user") - 1607
.expect("user activation"); - 1608
assert_eq!( - 1609
library - 1610
.select_preferred("trip", "user", "ws") - 1611
.map(|s| s.spec.id.as_str()), - 1612
Some("user.layout") - 1613
); - 1614
} - 1615
- 1616
#[test] - 1617
fn built_in_seed_can_be_explicitly_activated_for_a_workspace() { - 1618
let seed = seeds::built_in_seed_pack() - 1619
.into_iter() - 1620
.next() - 1621
.expect("seed pack is non-empty"); - 1622
let id = seed.spec.id.clone(); - 1623
let revision = seed.spec.revision; - 1624
let accepts = seed.spec.accepts[0].clone(); - 1625
let mut library = PresentationLibrary::default(); - 1626
library.register(seed).expect("register seed"); - 1627
library - 1628
.activate(&id, revision, LibraryScope::Workspace, "a-workspace") - 1629
.expect("activate built-in"); - 1630
assert!( - 1631
library - 1632
.select(&accepts, LibraryScope::Workspace, "a-workspace") - 1633
.is_some() - 1634
); - 1635
} - 1636
- 1637
#[test] - 1638
fn built_in_seed_can_be_explicitly_activated_for_user_scope() { - 1639
let seed = seeds::built_in_seed_pack() - 1640
.into_iter() - 1641
.next() - 1642
.expect("seed pack is non-empty"); - 1643
let id = seed.spec.id.clone(); - 1644
let revision = seed.spec.revision; - 1645
let accepts = seed.spec.accepts[0].clone(); - 1646
let mut library = PresentationLibrary::default(); - 1647
library.register(seed).expect("register seed"); - 1648
library - 1649
.activate(&id, revision, LibraryScope::User, "user") - 1650
.expect("activate built-in for user"); - 1651
assert!( - 1652
library - 1653
.select(&accepts, LibraryScope::User, "user") - 1654
.is_some() - 1655
); - 1656
} - 1657
- 1658
#[test] - 1659
fn library_scope_json_matches_client_contract() { - 1660
assert_eq!( - 1661
serde_json::to_string(&LibraryScope::User).unwrap(), - 1662
"\"user\"" - 1663
); - 1664
assert_eq!( - 1665
serde_json::from_str::<LibraryScope>("\"workspace\"").unwrap(), - 1666
LibraryScope::Workspace - 1667
); - 1668
assert_eq!( - 1669
serde_json::from_str::<LibraryScope>("\"Workspace\"").unwrap(), - 1670
LibraryScope::Workspace - 1671
); - 1672
} - 1673
- 1674
#[test] - 1675
fn reset_restores_original_revision_without_deleting_history() { - 1676
let mut original = spec(); - 1677
original.id = "reset.card".into(); - 1678
let mut revision = original.clone(); - 1679
revision.revision = 2; - 1680
let mut library = PresentationLibrary::default(); - 1681
for candidate in [original, revision] { - 1682
library - 1683
.register(StoredPresentation { - 1684
digest: digest(&candidate).expect("digest"), - 1685
spec: candidate, - 1686
origin: PresentationOrigin { - 1687
scope: LibraryScope::Workspace, - 1688
owner: "ws".into(), - 1689
plugin_id: None, - 1690
generation: None, - 1691
}, - 1692
enabled: false, - 1693
}) - 1694
.expect("register"); - 1695
} - 1696
library - 1697
.activate("reset.card", 2, LibraryScope::Workspace, "ws") - 1698
.expect("activate revision"); - 1699
assert!(library.reset("reset.card", LibraryScope::Workspace, "ws")); - 1700
assert_eq!( - 1701
library - 1702
.select("trip", LibraryScope::Workspace, "ws") - 1703
.map(|entry| entry.spec.revision), - 1704
Some(1) - 1705
); - 1706
assert!(library.get("reset.card", 2).is_some()); - 1707
} - 1708
- 1709
#[test] - 1710
fn library_rejects_digest_mismatch() { - 1711
let spec = spec(); - 1712
let result = PresentationLibrary::default().register(StoredPresentation { - 1713
digest: "bad".into(), - 1714
spec, - 1715
origin: PresentationOrigin { - 1716
scope: LibraryScope::User, - 1717
owner: "user-1".into(), - 1718
plugin_id: None, - 1719
generation: None, - 1720
}, - 1721
enabled: true, - 1722
}); - 1723
assert!(matches!(result, Err(PresentationError::DigestMismatch))); - 1724
} - 1725
- 1726
#[test] - 1727
fn revision_is_immutable_and_runtime_numbered() { - 1728
let mut proposed = spec(); - 1729
proposed.revision = 99; - 1730
let revision = propose_revision( - 1731
PresentationRevisionRequest { - 1732
base_id: "test.timeline".into(), - 1733
base_revision: 1, - 1734
feedback: "make it shorter".into(), - 1735
attempt: 1, - 1736
}, - 1737
proposed, - 1738
) - 1739
.expect("revision"); - 1740
assert_eq!(revision.proposed.revision, 2); - 1741
assert_eq!(revision.digest, digest(&revision.proposed).expect("digest")); - 1742
} - 1743
- 1744
#[test] - 1745
fn revision_rejects_identity_and_retry_budget() { - 1746
let mut proposed = spec(); - 1747
proposed.id = "other".into(); - 1748
let result = propose_revision( - 1749
PresentationRevisionRequest { - 1750
base_id: "test.timeline".into(), - 1751
base_revision: 1, - 1752
feedback: "change".into(), - 1753
attempt: 1, - 1754
}, - 1755
proposed, - 1756
); - 1757
assert!(matches!( - 1758
result, - 1759
Err(PresentationError::RevisionIdentityChanged) - 1760
)); - 1761
let result = propose_revision( - 1762
PresentationRevisionRequest { - 1763
base_id: "test.timeline".into(), - 1764
base_revision: 1, - 1765
feedback: "change".into(), - 1766
attempt: 3, - 1767
}, - 1768
spec(), - 1769
); - 1770
assert!(matches!( - 1771
result, - 1772
Err(PresentationError::RevisionBudgetExceeded) - 1773
)); - 1774
} - 1775
- 1776
#[test] - 1777
fn revoking_plugin_removes_definitions_and_activations() { - 1778
let spec = spec(); - 1779
let mut library = PresentationLibrary::default(); - 1780
library - 1781
.register(StoredPresentation { - 1782
digest: digest(&spec).expect("digest"), - 1783
spec, - 1784
origin: PresentationOrigin { - 1785
scope: LibraryScope::Workspace, - 1786
owner: "workspace".into(), - 1787
plugin_id: Some("pack.demo".into()), - 1788
generation: Some("1.0.0".into()), - 1789
}, - 1790
enabled: false, - 1791
}) - 1792
.expect("register"); - 1793
library - 1794
.activate("test.timeline", 1, LibraryScope::Workspace, "workspace") - 1795
.expect("activate"); - 1796
assert_eq!(library.revoke_plugin("pack.demo"), 1); - 1797
assert!(library.activations().is_empty()); - 1798
assert!(library.get("test.timeline", 1).is_none()); - 1799
} - 1800
} - 1801
Indexing the workspace…
Vakyartha documentation is discovering safe artifacts, anchors, and source references.