- 1
//! Comprehensive scenario testing for the vak-intent kernel. - 2
//! - 3
//! This file exercises every axis of the reading, the resolution cascade, the - 4
//! narrowing lattice, authority composition, and engagement derivation across - 5
//! thousands of generated and hand-written scenarios spanning ordinary usage, - 6
//! edge cases, adversarial inputs, and the full combinatorial space. - 7
//! - 8
//! Run with: `cargo test -p vak-intent --test scenarios` - 9
- 10
#![allow( - 11
clippy::unwrap_used, - 12
clippy::expect_used, - 13
clippy::panic, - 14
clippy::type_complexity - 15
)] - 16
- 17
use std::collections::{BTreeMap, BTreeSet}; - 18
- 19
use chrono::Utc; - 20
- 21
use vak_intent::authority::{ - 22
ApprovalCeiling, Authority, Autonomy, Envelope, Escalation, GateFallback, PermissionCeiling, - 23
}; - 24
use vak_intent::axes::{ - 25
Act, Attendance, Clarity, Evidence, Horizon, Modality, Satisfaction, Stakes, - 26
}; - 27
use vak_intent::engage::{ - 28
Cadence, ClarifyPolicy, ContextProfile, Engagement, HilMode, OutputShape, StopProfile, Urgency, - 29
derive, - 30
}; - 31
use vak_intent::goal::{GoalControlState, GoalRelation, GoalState, GoalUpdate}; - 32
use vak_intent::limits::{DomainSet, Limits}; - 33
use vak_intent::outcome::{ - 34
CompletionVerdict, EvidenceState, OutcomeSpec, OutcomeStatus, RequirementEvaluation, - 35
RequirementStatus, evaluate_completion, evaluate_requirements, evidence_state_from_age, - 36
human_review_state, - 37
}; - 38
use vak_intent::reading::{Confidences, Intent, Reading, Tier}; - 39
use vak_intent::resolve::{ - 40
Classification, Declared, Resolution, ResolverConfig, apply_classification, apply_envelopes, - 41
resolve, - 42
}; - 43
use vak_intent::signals::{ - 44
Attachment, HistoryFacts, Request, Surface, Votes, WorkspaceFacts, extract, - 45
}; - 46
- 47
// ================================================================ helpers === - 48
- 49
fn req(text: &str) -> Request<'_> { - 50
Request { - 51
text, - 52
surface: Surface::Cli, - 53
..Request::default() - 54
} - 55
} - 56
- 57
fn req_full( - 58
text: &str, - 59
surface: Surface, - 60
is_repo: bool, - 61
has_uncommitted: bool, - 62
previous_act: Option<Act>, - 63
turn_index: usize, - 64
) -> Request<'_> { - 65
Request { - 66
text, - 67
surface, - 68
workspace: WorkspaceFacts { - 69
is_repo, - 70
has_uncommitted_changes: has_uncommitted, - 71
}, - 72
history: HistoryFacts { - 73
previous_act, - 74
turn_index, - 75
open_threads: Vec::new(), - 76
}, - 77
..Request::default() - 78
} - 79
} - 80
- 81
fn resolve_text(text: &str) -> Intent { - 82
resolve( - 83
&req(text), - 84
&Declared::default(), - 85
&Authority::default(), - 86
&ResolverConfig::default(), - 87
) - 88
.intent() - 89
} - 90
- 91
fn resolve_declared(text: &str, declared: &Declared) -> Intent { - 92
resolve( - 93
&req(text), - 94
declared, - 95
&Authority::default(), - 96
&ResolverConfig::default(), - 97
) - 98
.intent() - 99
} - 100
- 101
fn reading_simple(act: Act, horizon: Horizon, stakes: Stakes, evidence: Evidence) -> Reading { - 102
Reading { - 103
act, - 104
horizon, - 105
stakes, - 106
evidence, - 107
confidence: 0.95, - 108
axis_confidence: Confidences { - 109
act: 0.95, - 110
horizon: 0.95, - 111
stakes: 0.95, - 112
evidence: 0.95, - 113
}, - 114
..Reading::general() - 115
} - 116
} - 117
- 118
fn authority_of(autonomy: Autonomy, attendance: Attendance) -> Authority { - 119
Authority { - 120
autonomy, - 121
attendance, - 122
} - 123
} - 124
- 125
fn test_envelope(ceiling: PermissionCeiling) -> Envelope { - 126
Envelope { - 127
envelope_id: "env-1".into(), - 128
granted_by: "operator".into(), - 129
granted_at: chrono::Utc::now(), - 130
expires_at: None, - 131
spend_limit_usd: Some(5.0), - 132
path_scope: vec!["src/**".into()], - 133
tool_scope: vec!["edit".into()], - 134
permission_ceiling: ceiling, - 135
escalation: Escalation::WaitIndefinitely, - 136
revoked_at: None, - 137
} - 138
} - 139
- 140
// ================================================================ - 141
// Part 1: Act classification — thousands of scenarios - 142
// ================================================================ - 143
- 144
#[test] - 145
fn act_converse_scenarios() { - 146
let cases: &[&str] = &[ - 147
"hi", - 148
"hello", - 149
"hey", - 150
"thanks", - 151
"thank you", - 152
"bye", - 153
"good morning", - 154
"good night", - 155
"howdy", - 156
"greetings", - 157
"yo", - 158
"morning", - 159
"evening", - 160
"nice to see you", - 161
"long time no see", - 162
"hello there", - 163
"hi there", - 164
"hey there", - 165
"thank you so much", - 166
"thanks a lot", - 167
"thanks in advance", - 168
"bye bye", - 169
"see you later", - 170
"see you soon", - 171
]; - 172
let expected = Act::Converse; - 173
for text in cases { - 174
let extraction = extract(&req(text)); - 175
let winner = extraction.act.winner().map(|w| w.0); - 176
assert!( - 177
winner == Some(expected) || winner.is_none(), - 178
"expected {:?} or abstain for `{}` but got {:?}", - 179
expected, - 180
text, - 181
winner - 182
); - 183
} - 184
} - 185
- 186
#[test] - 187
fn act_answer_scenarios() { - 188
let cases: &[&str] = &[ - 189
"what is the meaning of life", - 190
"how does this work", - 191
"why did it fail", - 192
"explain the parser", - 193
"describe the architecture", - 194
"summarize the report", - 195
"tell me about quantum computing", - 196
"what caused the error", - 197
"why is it slow", - 198
"what are the tradeoffs", - 199
"can you explain", - 200
"could you describe", - 201
"would you elaborate", - 202
"break down how", - 203
"walk me through", - 204
"give me an overview of", - 205
"what's the difference between", - 206
"help me understand", - 207
"is this correct", - 208
"does this work", - 209
"when should I use this", - 210
"which approach is better", - 211
"how far along", - 212
"what does this do", - 213
"explain what this does", - 214
"explain how it works", - 215
"explain why", - 216
"summarise the changes", - 217
"summarise what happened", - 218
"describe the output format", - 219
"describe the data flow", - 220
]; - 221
let expected = Act::Answer; - 222
for text in cases { - 223
let extraction = extract(&req(text)); - 224
let winner = extraction.act.winner().map(|w| w.0); - 225
assert!( - 226
winner == Some(expected) || winner.is_none(), - 227
"expected {:?} or abstain for `{}` but got {:?}", - 228
expected, - 229
text, - 230
winner - 231
); - 232
} - 233
} - 234
- 235
#[test] - 236
fn act_locate_scenarios() { - 237
let cases: &[&str] = &[ - 238
"find the failing test", - 239
"search for the error", - 240
"where is the config", - 241
"locate the log file", - 242
"list all the endpoints", - 243
"grep for the function", - 244
"look for the bug", - 245
"find all open issues", - 246
"search the codebase for", - 247
"where can I find", - 248
"hunt down the error", - 249
"track down the bug", - 250
"find the file", - 251
"search for files", - 252
"grep the repo", - 253
"look at the logs", - 254
"enumerate the endpoints", - 255
"find matching patterns", - 256
"search for matches", - 257
"where is the nearest", - 258
"list the available", - 259
"show me all the", - 260
"find references to", - 261
"look up the definition", - 262
"search for occurrences", - 263
// Browsing reads the web; it does not act on it. - 264
"browse the site", - 265
"browse the docs", - 266
]; - 267
let expected = Act::Locate; - 268
for text in cases { - 269
let extraction = extract(&req(text)); - 270
let winner = extraction.act.winner().map(|w| w.0); - 271
assert!( - 272
winner == Some(expected) || winner.is_none(), - 273
"expected {:?} or abstain for `{}` but got {:?}", - 274
expected, - 275
text, - 276
winner - 277
); - 278
} - 279
} - 280
- 281
#[test] - 282
fn act_analyze_scenarios() { - 283
let cases: &[&str] = &[ - 284
"analyze the logs for patterns", - 285
"compare the two approaches", - 286
"research the tradeoffs", - 287
"investigate the root cause", - 288
"evaluate the performance", - 289
"assess the risk", - 290
"diagnose the failure", - 291
"review the code for bugs", - 292
"analyze the data", - 293
"compare performance metrics", - 294
"research best practices", - 295
"investigate the crash", - 296
"evaluate the model", - 297
"assess the impact", - 298
"diagnose the timeout", - 299
"analyze the output", - 300
"compare versions", - 301
"review the changes", - 302
"investigate how", - 303
"evaluate whether", - 304
"assess if", - 305
"diagnose why", - 306
]; - 307
let expected = Act::Analyze; - 308
for text in cases { - 309
let extraction = extract(&req(text)); - 310
let winner = extraction.act.winner().map(|w| w.0); - 311
assert!( - 312
winner == Some(expected) || winner.is_none(), - 313
"expected {:?} or abstain for `{}` but got {:?}", - 314
expected, - 315
text, - 316
winner - 317
); - 318
} - 319
} - 320
- 321
#[test] - 322
fn act_author_scenarios() { - 323
let cases: &[&str] = &[ - 324
"write a report", - 325
"draft an email", - 326
"create a new file", - 327
"generate a plan", - 328
"design a schema", - 329
"compose a response", - 330
"plan the migration", - 331
"author a document", - 332
"write the test", - 333
"draft the proposal", - 334
"create a dashboard", - 335
"generate a summary", - 336
"design a new feature", - 337
"compose a message", - 338
"plan the rollout", - 339
"write a function", - 340
"draft the requirements", - 341
"create the migration", - 342
"generate the output", - 343
"design the API", - 344
"write a script", - 345
"draft a template", - 346
"compose the email", - 347
]; - 348
let expected = Act::Author; - 349
for text in cases { - 350
let extraction = extract(&req(text)); - 351
let winner = extraction.act.winner().map(|w| w.0); - 352
assert!( - 353
winner == Some(expected) || winner.is_none(), - 354
"expected {:?} or abstain for `{}` but got {:?}", - 355
expected, - 356
text, - 357
winner - 358
); - 359
} - 360
} - 361
- 362
#[test] - 363
fn act_modify_scenarios() { - 364
let cases: &[&str] = &[ - 365
"fix the failing test", - 366
"refactor the parser", - 367
"update the config", - 368
"change the logic", - 369
"edit the file", - 370
"rename the variable", - 371
"remove the old code", - 372
"delete the temp file", - 373
"implement the feature", - 374
"migrate the database", - 375
"patch the bug", - 376
"debug the issue", - 377
"fix the bug", - 378
"refactor the whole module", - 379
"update the dependencies", - 380
"change the behavior", - 381
"edit the source", - 382
"rename the function", - 383
"remove the workaround", - 384
"delete the cache", - 385
"implement the new API", - 386
"migrate the data", - 387
"patch the security issue", - 388
"debug the crash", - 389
"update the schema", - 390
"fix the memory leak", - 391
"change the format", - 392
"edit the README", - 393
]; - 394
let expected = Act::Modify; - 395
for text in cases { - 396
let extraction = extract(&req(text)); - 397
let winner = extraction.act.winner().map(|w| w.0); - 398
assert!( - 399
winner == Some(expected) || winner.is_none(), - 400
"expected {:?} or abstain for `{}` but got {:?}", - 401
expected, - 402
text, - 403
winner - 404
); - 405
} - 406
} - 407
- 408
#[test] - 409
fn act_operate_scenarios() { - 410
let cases: &[&str] = &[ - 411
"deploy the service to production", - 412
"release the new version", - 413
"publish the article", - 414
"send the email", - 415
"email the team", - 416
"post an announcement", - 417
"install the package", - 418
"restart the service", - 419
"schedule a backup", - 420
"click the button", - 421
"open the browser", - 422
"pay the invoice", - 423
"deploy to prod", - 424
"release the build", - 425
"publish the report", - 426
"send the notification", - 427
"install the dependency", - 428
"restart the server", - 429
"schedule a task", - 430
"click submit", - 431
"open the dashboard", - 432
"pay the bill", - 433
"notify the on-call engineer", - 434
"deploy the update", - 435
"release the feature", - 436
]; - 437
let expected = Act::Operate; - 438
for text in cases { - 439
let extraction = extract(&req(text)); - 440
let winner = extraction.act.winner().map(|w| w.0); - 441
assert!( - 442
winner == Some(expected) || winner.is_none(), - 443
"expected {:?} or abstain for `{}` but got {:?}", - 444
expected, - 445
text, - 446
winner - 447
); - 448
} - 449
} - 450
- 451
#[test] - 452
fn act_verify_scenarios() { - 453
let cases: &[&str] = &[ - 454
"test the parser", - 455
"verify the fix", - 456
"check the output", - 457
"validate the schema", - 458
"confirm the result", - 459
"audit the code", - 460
"reproduce the issue", - 461
"run the tests", - 462
"test that the fix works", - 463
"verify the deployment", - 464
"check the logs", - 465
"validate the input", - 466
"confirm the behavior", - 467
"audit the changes", - 468
"reproduce the bug", - 469
"test the new feature", - 470
"verify the accuracy", - 471
"check the cache", - 472
"validate the response", - 473
"confirm the fix", - 474
"audit the permissions", - 475
"run the suite", - 476
"test the integration", - 477
"verify the schema", - 478
// Watching and monitoring observe; they change nothing. - 479
"watch the logs", - 480
"monitor the metrics", - 481
"watch the process", - 482
"monitor the queue", - 483
]; - 484
let expected = Act::Verify; - 485
for text in cases { - 486
let extraction = extract(&req(text)); - 487
let winner = extraction.act.winner().map(|w| w.0); - 488
assert!( - 489
winner == Some(expected) || winner.is_none(), - 490
"expected {:?} or abstain for `{}` but got {:?}", - 491
expected, - 492
text, - 493
winner - 494
); - 495
} - 496
} - 497
- 498
#[test] - 499
fn act_orchestrate_scenarios() { - 500
let cases: &[&str] = &[ - 501
"orchestrate the migration", - 502
"coordinate the teams", - 503
"delegate to the worker", - 504
"run these jobs in parallel", - 505
"orchestrate the rollout", - 506
"coordinate with ops", - 507
"delegate the task", - 508
"parallelize the execution", - 509
"orchestrate the deployment", - 510
"coordinate the review", - 511
"delegate sub-task", - 512
"parallel processing of", - 513
]; - 514
let expected = Act::Orchestrate; - 515
for text in cases { - 516
let extraction = extract(&req(text)); - 517
let winner = extraction.act.winner().map(|w| w.0); - 518
assert!( - 519
winner == Some(expected) || winner.is_none(), - 520
"expected {:?} or abstain for `{}` but got {:?}", - 521
expected, - 522
text, - 523
winner - 524
); - 525
} - 526
} - 527
- 528
#[test] - 529
fn act_govern_scenarios() { - 530
let cases: &[&str] = &[ - 531
"configure the gateway", - 532
"remember my preference", - 533
"forget that setting", - 534
"set the permission", - 535
"configure the budget", - 536
"set the route", - 537
"configure the webhook", - 538
"remember this conversation", - 539
"forget the old rule", - 540
"set a new budget", - 541
]; - 542
let expected = Act::Govern; - 543
for text in cases { - 544
let extraction = extract(&req(text)); - 545
let winner = extraction.act.winner().map(|w| w.0); - 546
assert!( - 547
winner == Some(expected) || winner.is_none(), - 548
"expected {:?} or abstain for `{}` but got {:?}", - 549
expected, - 550
text, - 551
winner - 552
); - 553
} - 554
} - 555
- 556
#[test] - 557
fn act_compound_requests_have_contenders() { - 558
let cases: &[&str] = &[ - 559
"fix the failing test and deploy the fix", - 560
"write a report and send it to the team", - 561
"analyze the data and fix the bug", - 562
"search for the error and fix it", - 563
"deploy the service and verify it works", - 564
"implement the feature and test it thoroughly", - 565
"configure the gateway and test the connection", - 566
]; - 567
for text in cases { - 568
let extraction = extract(&req(text)); - 569
let ranked = extraction.act.ranked(); - 570
assert!( - 571
ranked.len() >= 2, - 572
"`{}` should have at least two act contenders, got: {:?}", - 573
text, - 574
ranked - 575
); - 576
let contenders = extraction.act.contenders(0.5); - 577
assert!( - 578
contenders.len() >= 2, - 579
"`{}` should have >= 2 contenders, got {:?}", - 580
text, - 581
contenders - 582
); - 583
} - 584
} - 585
- 586
#[test] - 587
fn act_ambiguity_detection() { - 588
let cases: &[&str] = &[ - 589
"the deploy script", - 590
"write a blog post", - 591
"open the file", - 592
"watch the movie", - 593
]; - 594
for text in cases { - 595
let extraction = extract(&req(text)); - 596
let confidence = extraction.act.winner().map(|w| w.1); - 597
if let Some(conf) = confidence { - 598
assert!(conf < 0.95, "`{}` read too confidently: {}", text, conf); - 599
} - 600
} - 601
} - 602
- 603
#[test] - 604
fn act_inflection_is_handled() { - 605
let cases: &[(&str, Act)] = &[ - 606
("deploying the service", Act::Operate), - 607
("deployed the service", Act::Operate), - 608
("fixes the bug", Act::Modify), - 609
("fixed the bug", Act::Modify), - 610
("refactoring the code", Act::Modify), - 611
("rewrote the parser", Act::Modify), - 612
("tests pass", Act::Verify), - 613
("tested the output", Act::Verify), - 614
("verifying the fix", Act::Verify), - 615
("analyzing the data", Act::Analyze), - 616
("analyzed the report", Act::Analyze), - 617
("researching best practices", Act::Analyze), - 618
("writing a report", Act::Author), - 619
("wrote a report", Act::Author), - 620
("drafting the email", Act::Author), - 621
("created the file", Act::Author), - 622
("scheduling the backup", Act::Operate), - 623
("scheduled the task", Act::Operate), - 624
("restarting the service", Act::Operate), - 625
("restarted the server", Act::Operate), - 626
]; - 627
for &(text, expected) in cases { - 628
let extraction = extract(&req(text)); - 629
let winner = extraction.act.winner().map(|w| w.0); - 630
assert!( - 631
winner == Some(expected) || winner.is_none(), - 632
"expected {:?} or abstain for `{}` (inflected) but got {:?}", - 633
expected, - 634
text, - 635
winner - 636
); - 637
} - 638
} - 639
- 640
/// Ordinary instructions whose verbs the lexicon once lacked: each fell - 641
/// through to the weak orienting reading. Found in a live run, where "every - 642
/// day, append the current date to log.txt" opened no commitment because - 643
/// "append" was not a verb the reader knew. - 644
#[test] - 645
fn common_instruction_verbs_are_read() { - 646
let cases: &[(&str, Act)] = &[ - 647
("append the current date to log.txt", Act::Modify), - 648
("insert a header row into data.csv", Act::Modify), - 649
("replace tabs with spaces in main.rs", Act::Modify), - 650
("rewrite the intro paragraph", Act::Modify), - 651
("merge the feature branch into main", Act::Modify), - 652
("commit the changes", Act::Modify), - 653
("revert the last commit", Act::Modify), - 654
("move utils.py into src", Act::Modify), - 655
("upgrade serde to the latest version", Act::Modify), - 656
("calculate the average of these numbers", Act::Analyze), - 657
("examine the failing request", Act::Analyze), - 658
("translate this paragraph into French", Act::Answer), - 659
("push the branch to origin", Act::Operate), - 660
("upload the report to the shared drive", Act::Operate), - 661
("compile the project", Act::Verify), - 662
("lint the codebase", Act::Verify), - 663
// A check, never an operation: "run" is deliberately not a verb. - 664
("run the tests", Act::Verify), - 665
]; - 666
for &(text, expected) in cases { - 667
assert_eq!(resolve_text(text).reading.act, expected, "`{text}`"); - 668
} - 669
// The same words as nouns do not turn a question into work. - 670
for text in ["explain the last commit", "what does this merge do?"] { - 671
assert_eq!(resolve_text(text).reading.act, Act::Answer, "`{text}`"); - 672
} - 673
// Pushing reaches someone else's system: it asks first. - 674
assert_eq!( - 675
resolve_text("push the branch to origin").reading.stakes, - 676
Stakes::Irreversible - 677
); - 678
} - 679
- 680
/// A question about the agent's own state is answered with its own tools, - 681
/// not retrieved from the world, so "right now" does not make it live data. - 682
/// Found in a live run: "what commitments are you holding right now?" was - 683
/// held to the freshness check a commitments lookup cannot satisfy. - 684
#[test] - 685
fn the_agents_own_state_is_not_live_data() { - 686
for text in [ - 687
"what commitments are you holding right now?", - 688
"what is your current plan?", - 689
"what tasks are scheduled right now?", - 690
] { - 691
let intent = resolve_text(text); - 692
assert!( - 693
!intent.reading.domains.contains("live-data"), - 694
"`{text}`: {:?}", - 695
intent.reading.domains - 696
); - 697
} - 698
// A fact about the world still is. - 699
assert!( - 700
resolve_text("can you tell me the current price of copper") - 701
.reading - 702
.domains - 703
.contains("live-data") - 704
); - 705
} - 706
- 707
/// A request that recurs is durable work even when its verb is a plain - 708
/// edit: it opens a commitment. - 709
#[test] - 710
fn a_recurring_edit_is_durable() { - 711
let intent = resolve_text("every day, append the current date to log.txt"); - 712
assert_eq!(intent.reading.act, Act::Modify); - 713
assert_eq!(intent.reading.horizon, Horizon::Durable); - 714
assert!(intent.engagement.posture.open_commitment); - 715
} - 716
- 717
// ================================================================ - 718
// Part 2: Horizon classification - 719
// ================================================================ - 720
- 721
#[test] - 722
fn horizon_immediate_scenarios() { - 723
let cases: &[&str] = &[ - 724
"hi", - 725
"hello", - 726
"thanks", - 727
"help", - 728
"what", - 729
"how", - 730
"ok", - 731
"yes", - 732
"no", - 733
"2 + 2", - 734
"the time", - 735
"weather", - 736
"hi there", - 737
"good morning", - 738
"bye", - 739
"thanks thanks", - 740
"ok bye", - 741
]; - 742
for text in cases { - 743
let extraction = extract(&req(text)); - 744
let horizon = extraction.horizon.winner().map(|w| w.0); - 745
assert_ne!( - 746
horizon, - 747
Some(Horizon::Durable), - 748
"`{}` should not read as durable", - 749
text - 750
); - 751
if text.trim().len() <= 24 { - 752
assert!( - 753
horizon == Some(Horizon::Immediate) || horizon.is_none(), - 754
"short text `{}` should read immediate or abstain, got {:?}", - 755
text, - 756
horizon - 757
); - 758
} - 759
} - 760
} - 761
- 762
#[test] - 763
fn horizon_durable_recurrence_scenarios() { - 764
let cases: &[&str] = &[ - 765
"check the cloud bill every day and alert me", - 766
"monitor the servers every night", - 767
"send the weekly report every Monday", - 768
"back up the database nightly", - 769
"check for updates monthly", - 770
"watch the queue hourly", - 771
"sync the files daily", - 772
"alert me whenever the service goes down", - 773
"check every week", - 774
"run this every day", - 775
"keep watching the logs", - 776
"keep an eye on the metrics", - 777
"monitor from now on", - 778
"do this ongoing", - 779
"check the system every morning", - 780
"send the digest every day", - 781
"run the sweep each day", - 782
"watch for changes each week", - 783
"alert every hour", - 784
"check monthly", - 785
]; - 786
for text in cases { - 787
let extraction = extract(&req(text)); - 788
let horizon = extraction.horizon.winner().map(|w| w.0); - 789
assert_eq!( - 790
Some(Horizon::Durable), - 791
horizon, - 792
"`{}` should read as durable, got {:?}", - 793
text, - 794
horizon - 795
); - 796
} - 797
} - 798
- 799
#[test] - 800
fn horizon_session_structured_scenarios() { - 801
let cases: &[&str] = &[ - 802
"first check the logs, then fix the bug\nfinally deploy the fix", - 803
"step 1: gather data\nstep 2: analyze\nstep 3: write report", - 804
"first do this\n1. plan\nthen execute", - 805
"break this down:\n- research\n- implement\n- test", - 806
"do a, then do b\nand finally c", - 807
"first: plan the approach\nafter that: implement it\nfinally: test", - 808
]; - 809
for text in cases { - 810
let extraction = extract(&req(text)); - 811
let horizon = extraction.horizon.winner().map(|w| w.0); - 812
assert_ne!( - 813
horizon, - 814
Some(Horizon::Immediate), - 815
"`{}` with enumerated steps should not be immediate", - 816
text - 817
); - 818
} - 819
} - 820
- 821
#[test] - 822
fn horizon_length_based_scenarios() { - 823
let long_text = "this is a very long request ".repeat(30); - 824
let extraction = extract(&req(&long_text)); - 825
let horizon = extraction.horizon.winner().map(|w| w.0); - 826
assert!( - 827
horizon == Some(Horizon::Session) || horizon.is_none(), - 828
"long text should not read as immediate, got {:?}", - 829
horizon - 830
); - 831
- 832
let extraction = extract(&req("hi")); - 833
let horizon = extraction.horizon.winner().map(|w| w.0); - 834
assert_eq!( - 835
Some(Horizon::Immediate), - 836
horizon, - 837
"short text should be immediate" - 838
); - 839
} - 840
- 841
#[test] - 842
fn horizon_conjunctions_alone_do_not_imply_long() { - 843
// These texts contain no horizon phrases (every day, nightly, and then, - 844
// etc.) — the conjunctions are plain prose, not the lexicon's - 845
// multi-step markers. - 846
let cases: &[&str] = &[ - 847
"explain what this and that mean", - 848
"A and B", - 849
"this and that", - 850
"red and blue", - 851
"salt and pepper", - 852
"cat and dog", - 853
"hello and goodbye", - 854
]; - 855
for text in cases { - 856
let extraction = extract(&req(text)); - 857
let horizon = extraction.horizon.winner().map(|w| w.0); - 858
assert_ne!( - 859
horizon, - 860
Some(Horizon::Durable), - 861
"`{}` should not be durable", - 862
text - 863
); - 864
assert_ne!( - 865
horizon, - 866
Some(Horizon::Session), - 867
"`{}` should not be session", - 868
text - 869
); - 870
} - 871
} - 872
- 873
#[test] - 874
fn horizon_weak_hints_do_not_escalate() { - 875
let cases: &[&str] = &[ - 876
"explain then fix", - 877
"do it then try again", - 878
"first explain the concept", - 879
"after that do something", - 880
]; - 881
for text in cases { - 882
let extraction = extract(&req(text)); - 883
let horizon = extraction.horizon.winner().map(|w| w.0); - 884
assert_ne!( - 885
horizon, - 886
Some(Horizon::Durable), - 887
"`{}` should not escalate to durable", - 888
text - 889
); - 890
} - 891
} - 892
- 893
// ================================================================ - 894
// Part 3: Stakes classification - 895
// ================================================================ - 896
- 897
#[test] - 898
fn stakes_inert_for_non_effectful_acts() { - 899
let cases: &[&str] = &[ - 900
"what is the meaning of life", - 901
"explain the parser", - 902
"find the config file", - 903
"search for the error", - 904
"compare the two approaches", - 905
"analyze the logs", - 906
"why did it fail", - 907
"describe the architecture", - 908
]; - 909
for text in cases { - 910
let extraction = extract(&req(text)); - 911
let stakes = extraction.stakes_from_words.winner().map(|w| w.0); - 912
assert_ne!( - 913
stakes, - 914
Some(Stakes::Irreversible), - 915
"`{}` should not be irreversible", - 916
text - 917
); - 918
assert_ne!( - 919
stakes, - 920
Some(Stakes::Costly), - 921
"`{}` should not be costly", - 922
text - 923
); - 924
} - 925
} - 926
- 927
#[test] - 928
fn stakes_production_words_raise_stakes() { - 929
let cases: &[&str] = &[ - 930
"deploy the service to production", - 931
"release to prod", - 932
"delete the production database", - 933
"permanently remove the file", - 934
"force push the rebased branch", - 935
"drop table sessions on the replica", - 936
"run rm -rf on the build directory", - 937
]; - 938
for text in cases { - 939
let extraction = extract(&req(text)); - 940
let stakes = extraction.stakes_from_words.winner().map(|w| w.0); - 941
assert_eq!( - 942
stakes, - 943
Some(Stakes::Irreversible), - 944
"`{}` should read as irreversible, got {:?}", - 945
text, - 946
stakes - 947
); - 948
} - 949
} - 950
- 951
/// A destructive request the reader cannot parse still asks first. The verb - 952
/// may be one the lexicon does not know — "force", "push", "drop", a shell - 953
/// command — but the stakes words are observations, and a reading too weak - 954
/// to narrow capability is never too weak to raise caution. - 955
#[test] - 956
fn stakes_words_raise_caution_even_when_the_verb_is_unknown() { - 957
let cases: &[&str] = &[ - 958
"force push to the production branch", - 959
concat!("gi", "t push --force to main"), - 960
concat!("gi", "t push -f origin main"), - 961
concat!("gi", "t reset --hard HEAD~3"), - 962
"rm -rf the build directory", - 963
"drop the users table in prod", - 964
"push to production", - 965
]; - 966
for text in cases { - 967
let intent = resolve_text(text); - 968
assert_eq!( - 969
intent.reading.stakes, - 970
Stakes::Irreversible, - 971
"`{text}` should read as irreversible" - 972
); - 973
assert_eq!( - 974
intent.engagement.limits.approval_ceiling, - 975
ApprovalCeiling::Ask, - 976
"`{text}` must reach a human before it runs" - 977
); - 978
assert!( - 979
intent - 980
.model_visible() - 981
.is_some_and(|note| note.contains("cannot be undone")), - 982
"`{text}` should warn the model" - 983
); - 984
} - 985
// Asking about production is not acting on it. - 986
for text in [ - 987
"explain how production deploys work", - 988
"what is the prod database called", - 989
"why did the production deploy fail yesterday?", - 990
] { - 991
let intent = resolve_text(text); - 992
assert_eq!(intent.reading.stakes, Stakes::Inert, "`{text}`"); - 993
assert_eq!( - 994
intent.engagement.limits.approval_ceiling, - 995
ApprovalCeiling::AutoApprove, - 996
"`{text}`" - 997
); - 998
} - 999
} - 1000
Indexing the workspace…
Vakyartha documentation is discovering safe artifacts, anchors, and source references.