- 1067
Evidence::Cited => Some(86_400), - 1068
Evidence::Verified | Evidence::Audited => Some(3_600), - 1069
}, - 1070
max_turns: None, - 1071
acts: BTreeSet::from([reading.act]), - 1072
stop: crate::StopProfile::default(), - 1073
} - 1074
} - 1075
- 1076
/// The baseline contract with the engagement's own stop rule and the - 1077
/// primary act of every part. A reading never sets `max_turns`: the turn - 1078
/// budget is the operator's, whatever the request looked like. - 1079
pub fn from_intent(objective: impl Into<String>, intent: &crate::Intent) -> Self { - 1080
let mut spec = Self::from_reading( - 1081
objective, - 1082
&intent.reading, - 1083
intent.provenance.resolver_version, - 1084
); - 1085
spec.stop = intent.engagement.posture.stop; - 1086
spec.acts - 1087
.extend(intent.strands.iter().map(|strand| strand.reading.act)); - 1088
spec - 1089
} - 1090
- 1091
/// Whether this outcome requires execution or file modifications. - 1092
/// - 1093
/// True when an act genuinely needs one (`Act::requires_execution`: - 1094
/// `Modify`/`Operate`/`Govern`/`Verify`), or when the request names a - 1095
/// file deliverable outright (`expects_saved_file`) — the only case - 1096
/// where authoring content also demands a receipt, since `Act` alone has - 1097
/// no view of the request text. - 1098
pub fn requires_execution(&self) -> bool { - 1099
self.expects_saved_file() || self.acts.iter().any(|act| act.requires_execution()) - 1100
} - 1101
- 1102
/// Whether this outcome requires inspection, search, or enumeration. - 1103
pub fn requires_inspection(&self) -> bool { - 1104
self.acts.iter().any(|act| act.requires_inspection()) - 1105
} - 1106
- 1107
/// Whether this outcome requires real tool execution or evidence receipts. - 1108
pub fn requires_tool(&self) -> bool { - 1109
self.expects_saved_file() - 1110
|| self.acts.iter().any(|act| act.requires_tool()) - 1111
|| self - 1112
.requirements - 1113
.iter() - 1114
.any(|r| r.kind == RequirementKind::Evidence) - 1115
} - 1116
- 1117
/// The act to name in logs and nudges: the most demanding one. - 1118
pub fn deliverable_act(&self) -> Option<&str> { - 1119
self.acts - 1120
.iter() - 1121
.max_by_key(|act| { - 1122
( - 1123
act.is_effectful(), - 1124
act.requires_execution(), - 1125
act.requires_inspection(), - 1126
) - 1127
}) - 1128
.map(|act| act.as_str()) - 1129
} - 1130
- 1131
/// Merge an extension-provided requirement without allowing it to alter - 1132
/// authority. Invalid declarations are rejected at the contract boundary. - 1133
pub fn merge_declared_requirement( - 1134
&mut self, - 1135
id: impl Into<String>, - 1136
kind: &str, - 1137
description: impl Into<String>, - 1138
importance: &str, - 1139
target: Option<String>, - 1140
) -> Result<(), String> { - 1141
let kind = match kind { - 1142
"deliverable" => RequirementKind::Deliverable, - 1143
"evidence" => RequirementKind::Evidence, - 1144
"constraint" => RequirementKind::Constraint, - 1145
"integrity" => RequirementKind::Integrity, - 1146
other => return Err(format!("unsupported outcome requirement kind: {other}")), - 1147
}; - 1148
let importance = match importance { - 1149
"" | "prefer" => RequirementImportance::Prefer, - 1150
"must" => RequirementImportance::Must, - 1151
other => { - 1152
return Err(format!( - 1153
"unsupported outcome requirement importance: {other}" - 1154
)); - 1155
} - 1156
}; - 1157
let id = id.into(); - 1158
let description = description.into(); - 1159
if id.trim().is_empty() || description.trim().is_empty() { - 1160
return Err("outcome requirement id and description are required".into()); - 1161
} - 1162
if self - 1163
.requirements - 1164
.iter() - 1165
.any(|requirement| requirement.id == id) - 1166
{ - 1167
return Err(format!("duplicate outcome requirement id: {id}")); - 1168
} - 1169
self.requirements.push(OutcomeRequirement { - 1170
id, - 1171
kind, - 1172
description, - 1173
origin: RequirementOrigin::Inferred, - 1174
importance, - 1175
target, - 1176
}); - 1177
Ok(()) - 1178
} - 1179
} - 1180
- 1181
#[cfg(test)] - 1182
mod tests { - 1183
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)] - 1184
use super::*; - 1185
use crate::{Act, Reading}; - 1186
- 1187
#[test] - 1188
fn named_saved_file_requires_evidence_but_an_inline_plan_does_not() { - 1189
let authoring = Reading { - 1190
act: Act::Author, - 1191
..Reading::general() - 1192
}; - 1193
let file = OutcomeSpec::from_reading( - 1194
"Create an invitation in this workspace as invitation.html", - 1195
&authoring, - 1196
1, - 1197
); - 1198
assert!(file.expects_saved_file()); - 1199
let plan = OutcomeSpec::from_reading("Create a two-day lunch plan", &authoring, 1); - 1200
assert!(!plan.expects_saved_file()); - 1201
let inspection = OutcomeSpec::from_reading("Explain README.md", &Reading::general(), 1); - 1202
assert!(!inspection.expects_saved_file()); - 1203
// Naming a file inside a question is not asking for one. - 1204
let explanation = - 1205
OutcomeSpec::from_reading("explain how to write a README.md", &Reading::general(), 1); - 1206
assert!(!explanation.expects_saved_file()); - 1207
assert!(!explanation.requires_execution()); - 1208
} - 1209
- 1210
#[test] - 1211
fn baseline_contract_preserves_objective_and_adds_only_inferred_requirements() { - 1212
let reading = Reading { - 1213
act: Act::Answer, - 1214
evidence: Evidence::Cited, - 1215
..Reading::general() - 1216
}; - 1217
let spec = OutcomeSpec::from_reading("current events in India", &reading, 1); - 1218
assert_eq!(spec.objective, "current events in India"); - 1219
assert!(spec.requirements.iter().any(|requirement| { - 1220
requirement.kind == RequirementKind::Evidence - 1221
&& requirement.origin == RequirementOrigin::Inferred - 1222
&& requirement.importance == RequirementImportance::Must - 1223
})); - 1224
assert!( - 1225
spec.requirements - 1226
.iter() - 1227
.all(|requirement| { requirement.origin == RequirementOrigin::Inferred }) - 1228
); - 1229
} - 1230
- 1231
#[test] - 1232
fn produced_output_is_not_called_complete() { - 1233
assert_eq!( - 1234
evaluate_response(Some("answer"), false, false), - 1235
OutcomeStatus::Produced - 1236
); - 1237
} - 1238
- 1239
#[test] - 1240
fn refusal_cannot_satisfy_a_deliverable_requirement() { - 1241
let mut spec = OutcomeSpec::from_reading("create a report", &Reading::general(), 1); - 1242
assert!( - 1243
spec.merge_declared_requirement( - 1244
"report", - 1245
"deliverable", - 1246
"create report.md", - 1247
"must", - 1248
None - 1249
) - 1250
.is_ok() - 1251
); - 1252
let evaluations = evaluate_requirements(&spec, Some("I cannot do that.")); - 1253
assert_eq!(evaluations[1].status, RequirementStatus::Unknown); - 1254
assert_eq!( - 1255
evaluate_completion(OutcomeStatus::Produced, &evaluations, &spec), - 1256
CompletionVerdict::Unknown - 1257
); - 1258
} - 1259
- 1260
#[test] - 1261
fn cited_output_remains_unknown_until_source_support_is_checked() { - 1262
let reading = Reading { - 1263
evidence: Evidence::Cited, - 1264
..Reading::general() - 1265
}; - 1266
let spec = OutcomeSpec::from_reading("research", &reading, 1); - 1267
let evaluations = evaluate_requirements(&spec, Some("claim https://example.com")); - 1268
assert_eq!(evaluations[0].status, RequirementStatus::Met); - 1269
assert_eq!(evaluations[1].status, RequirementStatus::Unknown); - 1270
} - 1271
- 1272
#[test] - 1273
fn retrieval_receipt_is_not_mistaken_for_supported_evidence() { - 1274
let reading = Reading { - 1275
evidence: Evidence::Cited, - 1276
..Reading::general() - 1277
}; - 1278
let spec = OutcomeSpec::from_reading("research", &reading, 1); - 1279
let evaluations = - 1280
evaluate_requirements_with_evidence(&spec, Some("claim https://example.com"), true); - 1281
assert_eq!(evaluations[1].status, RequirementStatus::Unknown); - 1282
assert!(evaluations[1].reason.contains("receipt")); - 1283
} - 1284
- 1285
#[test] - 1286
fn missing_and_unlinked_evidence_are_distinct_failures() { - 1287
let reading = Reading { - 1288
evidence: Evidence::Cited, - 1289
..Reading::general() - 1290
}; - 1291
let spec = OutcomeSpec::from_reading("research", &reading, 1); - 1292
let missing = evaluate_requirements(&spec, Some("plain answer")); - 1293
assert_eq!(missing[1].status, RequirementStatus::Unmet); - 1294
let unlinked = evaluate_requirements(&spec, Some("answer https://example.com")); - 1295
assert_eq!(unlinked[1].status, RequirementStatus::Unknown); - 1296
assert!(unlinked[1].reason.contains("support and freshness")); - 1297
} - 1298
- 1299
#[test] - 1300
fn stale_evidence_is_unknown_and_explains_why() { - 1301
let reading = Reading { - 1302
evidence: Evidence::Cited, - 1303
..Reading::general() - 1304
}; - 1305
let spec = OutcomeSpec::from_reading("current events", &reading, 1); - 1306
let evaluations = evaluate_requirements_with_state( - 1307
&spec, - 1308
Some("answer https://example.com"), - 1309
EvidenceState::Stale, - 1310
); - 1311
assert_eq!(evaluations[1].status, RequirementStatus::Unknown); - 1312
assert!(evaluations[1].reason.contains("stale")); - 1313
} - 1314
- 1315
#[test] - 1316
#[allow(clippy::expect_used)] - 1317
fn evidence_receipt_keeps_observation_and_source_times_distinct() { - 1318
let observed = chrono::DateTime::parse_from_rfc3339("2026-01-02T00:00:00Z") - 1319
.expect("timestamp") - 1320
.with_timezone(&chrono::Utc); - 1321
let published = chrono::DateTime::parse_from_rfc3339("2025-12-31T00:00:00Z") - 1322
.expect("timestamp") - 1323
.with_timezone(&chrono::Utc); - 1324
let receipt = EvidenceReceipt { - 1325
id: "r1".into(), - 1326
kind: "article".into(), - 1327
producer: "web".into(), - 1328
observed_at: observed, - 1329
source_published_at: Some(published), - 1330
effective_at: None, - 1331
state: EvidenceState::Fresh, - 1332
}; - 1333
assert_eq!(receipt.freshness_timestamp(), published); - 1334
assert_eq!(receipt.observed_at, observed); - 1335
assert_eq!( - 1336
evidence_state_from_receipt(observed, &receipt, chrono::Duration::days(1)), - 1337
EvidenceState::Stale - 1338
); - 1339
} - 1340
- 1341
#[test] - 1342
fn evidence_freshness_is_deterministic_and_domain_configurable() { - 1343
let now = chrono::Utc::now(); - 1344
assert_eq!( - 1345
evidence_state_from_age( - 1346
now, - 1347
now - chrono::Duration::hours(1), - 1348
chrono::Duration::hours(2) - 1349
), - 1350
EvidenceState::Fresh - 1351
); - 1352
assert_eq!( - 1353
evidence_state_from_age( - 1354
now, - 1355
now - chrono::Duration::hours(3), - 1356
chrono::Duration::hours(2) - 1357
), - 1358
EvidenceState::Stale - 1359
); - 1360
assert_eq!( - 1361
evidence_state_from_age( - 1362
now, - 1363
now + chrono::Duration::minutes(1), - 1364
chrono::Duration::hours(2) - 1365
), - 1366
EvidenceState::Fresh - 1367
); - 1368
} - 1369
- 1370
#[test] - 1371
fn extension_requirements_are_data_driven_and_narrowing_only() { - 1372
let mut spec = OutcomeSpec::from_reading("make a plan", &Reading::general(), 1); - 1373
let result = spec.merge_declared_requirement( - 1374
"plan-structure", - 1375
"constraint", - 1376
"include assumptions and next steps", - 1377
"must", - 1378
Some("primary".into()), - 1379
); - 1380
assert!(result.is_ok()); - 1381
assert_eq!( - 1382
spec.requirements.last().map(|item| item.origin), - 1383
Some(RequirementOrigin::Inferred) - 1384
); - 1385
assert!( - 1386
spec.merge_declared_requirement("", "constraint", "x", "must", None) - 1387
.is_err() - 1388
); - 1389
assert!( - 1390
spec.merge_declared_requirement("bad", "grant", "x", "must", None) - 1391
.is_err() - 1392
); - 1393
} - 1394
- 1395
#[test] - 1396
fn completion_verdict_never_confuses_output_with_satisfaction() { - 1397
let reading = Reading { - 1398
evidence: Evidence::Cited, - 1399
..Reading::general() - 1400
}; - 1401
let spec = OutcomeSpec::from_reading("research", &reading, 1); - 1402
let evaluations = evaluate_requirements(&spec, Some("answer https://example.com")); - 1403
assert_eq!( - 1404
evaluate_completion(OutcomeStatus::Produced, &evaluations, &spec), - 1405
CompletionVerdict::Unknown - 1406
); - 1407
} - 1408
- 1409
#[test] - 1410
fn completion_verdict_handles_partial_failed_and_cancelled_work() { - 1411
let reading = Reading { - 1412
evidence: Evidence::Cited, - 1413
..Reading::general() - 1414
}; - 1415
let spec = OutcomeSpec::from_reading("research", &reading, 1); - 1416
let partial = vec![ - 1417
RequirementEvaluation { - 1418
requirement_id: "deliverable-1".into(), - 1419
status: RequirementStatus::Met, - 1420
reason: "content exists".into(), - 1421
}, - 1422
RequirementEvaluation { - 1423
requirement_id: "evidence-1".into(), - 1424
status: RequirementStatus::Unmet, - 1425
reason: "no linked evidence".into(), - 1426
}, - 1427
]; - 1428
assert_eq!( - 1429
evaluate_completion(OutcomeStatus::Produced, &partial, &spec), - 1430
CompletionVerdict::Partial - 1431
); - 1432
assert_eq!( - 1433
evaluate_completion(OutcomeStatus::Failed, &[], &spec), - 1434
CompletionVerdict::Failed - 1435
); - 1436
assert_eq!( - 1437
evaluate_completion(OutcomeStatus::Cancelled, &[], &spec), - 1438
CompletionVerdict::Cancelled - 1439
); - 1440
} - 1441
- 1442
#[test] - 1443
fn review_state_is_deterministic() { - 1444
assert_eq!( - 1445
human_review_state(CompletionVerdict::Complete), - 1446
"not_required" - 1447
); - 1448
assert_eq!( - 1449
human_review_state(CompletionVerdict::Unknown), - 1450
"recommended" - 1451
); - 1452
assert_eq!( - 1453
human_review_state(CompletionVerdict::Failed), - 1454
"required_for_recovery" - 1455
); - 1456
} - 1457
- 1458
#[test] - 1459
fn only_explicit_commands_are_control() { - 1460
assert_eq!(parse_command("/status"), Some(Command::Status)); - 1461
assert_eq!(parse_command("/stop"), Some(Command::Cancel)); - 1462
assert_eq!(parse_command("Stop!"), Some(Command::Cancel)); - 1463
assert_eq!(parse_command("pause"), Some(Command::Pause)); - 1464
assert_eq!( - 1465
parse_command("/replan around the new constraint"), - 1466
Some(Command::Replan { - 1467
text: "around the new constraint".into() - 1468
}) - 1469
); - 1470
assert_eq!( - 1471
parse_command("/goal replace ship the index only"), - 1472
Some(Command::GoalReplace { - 1473
text: "ship the index only".into() - 1474
}) - 1475
); - 1476
assert_eq!( - 1477
parse_command("/approve gate-7"), - 1478
Some(Command::Approve { - 1479
gate_id: "gate-7".into() - 1480
}) - 1481
); - 1482
// Natural language is steering, whatever word it starts with. - 1483
for text in [ - 1484
"stop using semicolons in the output", - 1485
"pause the music service before deploying", - 1486
"also include a CSV", - 1487
"status of the migration please", - 1488
"replace the deprecated API call", - 1489
"use a shorter answer", - 1490
] { - 1491
assert_eq!(parse_command(text), None, "{text}"); - 1492
} - 1493
// A slash command without its argument is not a command either. - 1494
assert_eq!(parse_command("/replan"), None); - 1495
assert_eq!(parse_command("/goal replace"), None); - 1496
} - 1497
- 1498
fn intervention(kind: InterventionKind, source: ControlSource) -> InterventionRequest { - 1499
InterventionRequest { - 1500
request_id: "i-1".into(), - 1501
kind, - 1502
text: String::new(), - 1503
source, - 1504
target_revision: Some(1), - 1505
target_session_id: Some("child".into()), - 1506
target_parent_session_id: Some("parent".into()), - 1507
} - 1508
} - 1509
- 1510
fn human() -> ControlSource { - 1511
ControlSource::Human { - 1512
surface: "desktop".into(), - 1513
principal: None, - 1514
} - 1515
} - 1516
- 1517
fn agent(session_id: &str) -> ControlSource { - 1518
ControlSource::Agent { - 1519
session_id: session_id.into(), - 1520
parent_session_id: None, - 1521
} - 1522
} - 1523
- 1524
fn system() -> ControlSource { - 1525
ControlSource::System { - 1526
origin: "webhook".into(), - 1527
} - 1528
} - 1529
- 1530
/// The authority matrix: humans may do anything, agents only their own - 1531
/// children, systems only observe. - 1532
#[test] - 1533
fn intervention_authority_comes_from_the_source() { - 1534
use InterventionDecision as D; - 1535
use InterventionKind as K; - 1536
let decide = |kind: K, source: ControlSource| { - 1537
evaluate_intervention(intervention(kind, source)).decision - 1538
}; - 1539
- 1540
assert_eq!(decide(K::Cancel, human()), D::Accepted); - 1541
assert_eq!(decide(K::Replan, human()), D::Queued); - 1542
assert_eq!(decide(K::Approve, human()), D::Accepted); - 1543
assert!(evaluate_intervention(intervention(K::Replan, human())).creates_revision); - 1544
- 1545
// An agent controlling a child it dispatched. - 1546
assert_eq!(decide(K::Cancel, agent("parent")), D::Accepted); - 1547
// An agent trying to control the session it lives in — or one that - 1548
// is not its child — is refused. - 1549
assert_eq!(decide(K::Cancel, agent("child")), D::Rejected); - 1550
assert_eq!(decide(K::Cancel, agent("sibling")), D::Rejected); - 1551
assert_eq!(decide(K::Pause, agent("stranger")), D::Rejected); - 1552
assert_eq!(decide(K::Resume, agent("stranger")), D::Rejected); - 1553
// Without the target's parentage there is nothing to prove, so the - 1554
// answer is no. - 1555
let mut unknown_parent = intervention(K::Cancel, agent("parent")); - 1556
unknown_parent.target_parent_session_id = None; - 1557
assert_eq!(evaluate_intervention(unknown_parent).decision, D::Rejected); - 1558
assert_eq!(decide(K::Replan, agent("parent")), D::RequiresHuman); - 1559
assert_eq!(decide(K::Approve, agent("parent")), D::Rejected); - 1560
- 1561
assert_eq!(decide(K::Status, system()), D::Accepted); - 1562
assert_eq!(decide(K::Resume, system()), D::Accepted); - 1563
assert_eq!(decide(K::Cancel, system()), D::Rejected); - 1564
assert_eq!(decide(K::Steer, system()), D::Rejected); - 1565
assert_eq!(decide(K::Approve, system()), D::Rejected); - 1566
} - 1567
- 1568
#[test] - 1569
fn domain_verification_oracles_validate_tables_matrices_citations() { - 1570
// Tabular markdown: well-formed - 1571
let valid_table = "| Col A | Col B |\n| --- | --- |\n| Val 1 | Val 2 |\n| Val 3 | Val 4 |"; - 1572
assert!(verify_tabular_data(valid_table).unwrap().is_ok()); - 1573
- 1574
// Tabular markdown: ragged (col count mismatch) - 1575
let ragged_table = "| Col A | Col B |\n| --- | --- |\n| Val 1 |\n| Val 3 | Val 4 |"; - 1576
assert!(verify_tabular_data(ragged_table).unwrap().is_err()); - 1577
- 1578
// Tabular vak block - 1579
let valid_block = "```vak-table\n{\"columns\": [\"A\", \"B\"], \"rows\": [[\"1\", \"2\"], [\"3\", \"4\"]]}\n```"; - 1580
assert!(verify_tabular_data(valid_block).unwrap().is_ok()); - 1581
- 1582
let invalid_block = - 1583
"```vak-table\n{\"columns\": [\"A\", \"B\"], \"rows\": [[\"1\"], [\"3\", \"4\"]]}\n```"; - 1584
assert!(verify_tabular_data(invalid_block).unwrap().is_err()); - 1585
- 1586
// Decision matrix vak block - 1587
let valid_matrix = "```vak-decision\n{\"options\": [{\"label\": \"Option A\"}, {\"label\": \"Option B\"}]}\n```"; - 1588
assert!(verify_decision_matrix(valid_matrix).unwrap().is_ok()); - 1589
- 1590
let single_option_matrix = - 1591
"```vak-decision\n{\"options\": [{\"label\": \"Option A\"}]}\n```"; - 1592
assert!( - 1593
verify_decision_matrix(single_option_matrix) - 1594
.unwrap() - 1595
.is_err() - 1596
); - 1597
- 1598
// Claim citations - 1599
let valid_citations = - 1600
"According to study[^1] and report[^2].\n\n[^1]: Reference one\n[^2]: Reference two"; - 1601
assert!(verify_claim_citations(valid_citations).unwrap().is_ok()); - 1602
- 1603
let unlinked_citations = "According to study[^1] and missing[^3].\n\n[^1]: Reference one"; - 1604
assert!(verify_claim_citations(unlinked_citations).unwrap().is_err()); - 1605
- 1606
// RequirementKind::Integrity evaluation - 1607
let mut spec = OutcomeSpec::from_reading("produce analysis", &Reading::general(), 1); - 1608
spec.merge_declared_requirement( - 1609
"integ-1", - 1610
"integrity", - 1611
"verify data integrity", - 1612
"must", - 1613
None, - 1614
) - 1615
.unwrap(); - 1616
- 1617
let good_eval = - 1618
evaluate_requirements_with_state(&spec, Some(valid_table), EvidenceState::None); - 1619
let integ_eval = good_eval - 1620
.iter() - 1621
.find(|e| e.requirement_id == "integ-1") - 1622
.unwrap(); - 1623
assert_eq!(integ_eval.status, RequirementStatus::Met); - 1624
- 1625
let bad_eval = - 1626
evaluate_requirements_with_state(&spec, Some(ragged_table), EvidenceState::None); - 1627
let bad_integ = bad_eval - 1628
.iter() - 1629
.find(|e| e.requirement_id == "integ-1") - 1630
.unwrap(); - 1631
assert_eq!(bad_integ.status, RequirementStatus::Unmet); - 1632
} - 1633
} - 1634
Indexing the workspace…
Vakyartha documentation is discovering safe artifacts, anchors, and source references.