- 1
//! `vak intent` and `vak commit`. - 2
//! - 3
//! `intent explain` is the load-bearing one. Every later phase of this work — - 4
//! capability slicing, approval posture, route demand — changes behaviour on - 5
//! the strength of a reading, and a reading nobody can inspect is a reading - 6
//! nobody can debug. This prints the whole decision: each signal with the - 7
//! weight it carried, the axes it produced, and precisely what the engagement - 8
//! narrows relative to doing nothing. - 9
//! - 10
//! It costs nothing and dispatches nothing, so it is safe to run against any - 11
//! prompt before committing to it. - 12
- 13
use vak_commit::{CommitmentLedger, Verdict}; - 14
use vak_core::Core; - 15
use vak_intent::{Act, Declared, Evidence, Horizon, Limits, Stakes, Surface as IntentSurface}; - 16
- 17
use crate::cli::{CommitAction, IntentAction}; - 18
- 19
pub(crate) fn run_intent(cwd: std::path::PathBuf, action: IntentAction) -> i32 { - 20
let core = match Core::new(cwd) { - 21
Ok(core) => core.with_surface(vak_core::Surface::Cli), - 22
Err(error) => { - 23
eprintln!("error: {error}"); - 24
return 2; - 25
} - 26
}; - 27
dispatch_intent(&core, action) - 28
} - 29
- 30
fn dispatch_intent(core: &Core, action: IntentAction) -> i32 { - 31
match action { - 32
IntentAction::Show => show_policy(core), - 33
IntentAction::Explain { - 34
prompt, - 35
surface, - 36
act, - 37
horizon, - 38
stakes, - 39
evidence, - 40
json, - 41
} => explain(core, &prompt, surface, act, horizon, stakes, evidence, json), - 42
} - 43
} - 44
- 45
fn show_policy(core: &Core) -> i32 { - 46
let config = core.config(); - 47
println!("intent kernel"); - 48
println!(" enabled {}", config.intent.enabled); - 49
println!( - 50
" accept confidence {:.2}", - 51
config.intent.accept_confidence - 52
); - 53
println!( - 54
" provisional {:.2}", - 55
config.intent.provisional_confidence - 56
); - 57
println!( - 58
" capability slicing {}", - 59
config.intent.slice_capabilities - 60
); - 61
println!(" approval posture {}", config.intent.posture); - 62
println!(" escalation {}", config.intent.escalate); - 63
if config.intent.escalate == "cloud" { - 64
println!( - 65
" classification cap ${:.4} per dispatch", - 66
config.intent.max_classify_usd - 67
); - 68
println!( - 69
" classification model {}", - 70
config - 71
.intent - 72
.classify_model - 73
.as_deref() - 74
.unwrap_or("(cheapest leg on the frozen ladder)") - 75
); - 76
} - 77
println!(" autonomy {}", config.intent.autonomy); - 78
// Did we read it right? The misread ledger's per-cell accuracy, so the - 79
// loop closes on a person rather than on nothing. - 80
let ledger = vak_core::misread::MisreadLedger::new(&core.sessions_home()); - 81
let cells = ledger.accuracy(); - 82
let observed: u64 = cells - 83
.iter() - 84
.filter(|cell| cell.resolver_version == vak_intent::RESOLVER_VERSION) - 85
.map(|cell| cell.observations()) - 86
.sum(); - 87
println!( - 88
" readings observed {observed} (resolver v{})", - 89
vak_intent::RESOLVER_VERSION - 90
); - 91
let weak = vak_core::misread::weak_cells(&ledger, 5); - 92
if weak.is_empty() { - 93
println!( - 94
" weak cells none (nothing contradicted, fewer than 5 observations, \ - 95
or accuracy ≥ 0.75)" - 96
); - 97
} else { - 98
for cell in weak { - 99
let wanted = cell - 100
.wanted - 101
.iter() - 102
.take(3) - 103
.map(|(name, count)| format!("{name}×{count}")) - 104
.collect::<Vec<_>>() - 105
.join(", "); - 106
println!( - 107
" weak cell {}/{} accuracy {:.2} over {} — model asked for: {}", - 108
cell.act, - 109
cell.stakes, - 110
cell.accuracy(), - 111
cell.observations(), - 112
if wanted.is_empty() { - 113
"-".to_string() - 114
} else { - 115
wanted - 116
} - 117
); - 118
} - 119
} - 120
println!(); - 121
println!("commitments"); - 122
println!(" enabled {}", config.commitment.enabled); - 123
println!(" stall limit {}", config.commitment.stall_limit); - 124
match config.commitment.lifetime_budget_usd { - 125
Some(budget) => println!(" lifetime budget ${budget:.2}"), - 126
None => println!(" lifetime budget (none)"), - 127
} - 128
match config.commitment.default_ttl_days { - 129
Some(days) => println!(" default TTL {days} day(s)"), - 130
None => println!(" default TTL (none)"), - 131
} - 132
0 - 133
} - 134
- 135
#[allow(clippy::too_many_arguments)] - 136
fn explain( - 137
core: &Core, - 138
prompt: &str, - 139
surface: Option<String>, - 140
act: Option<String>, - 141
horizon: Option<String>, - 142
stakes: Option<String>, - 143
evidence: Option<String>, - 144
json: bool, - 145
) -> i32 { - 146
let mut declared = Declared::default(); - 147
// An unparseable override is an error rather than a silent fallback: the - 148
// whole point of this command is to answer "what would happen", and - 149
// quietly ignoring half the question makes the answer wrong. - 150
macro_rules! parse_override { - 151
($value:expr, $parser:path, $name:literal, $target:expr) => { - 152
if let Some(raw) = $value { - 153
match $parser(&raw) { - 154
Some(parsed) => $target = Some(parsed), - 155
None => { - 156
eprintln!("error: unknown {} '{}'", $name, raw); - 157
return 2; - 158
} - 159
} - 160
} - 161
}; - 162
} - 163
parse_override!(act, Act::parse, "act", declared.act); - 164
parse_override!(horizon, Horizon::parse, "horizon", declared.horizon); - 165
parse_override!(stakes, Stakes::parse, "stakes", declared.stakes); - 166
parse_override!(evidence, Evidence::parse, "evidence", declared.evidence); - 167
- 168
let core_surface = match surface.as_deref() { - 169
None => core.surface().clone(), - 170
Some(raw) => match IntentSurface::parse(raw) { - 171
Some(IntentSurface::Cli) => vak_core::Surface::Cli, - 172
Some(IntentSurface::Desktop) => vak_core::Surface::Desktop, - 173
Some(IntentSurface::Server) => vak_core::Surface::Server, - 174
Some(IntentSurface::Chat) => vak_core::Surface::Chat { - 175
channel: "chat".into(), - 176
}, - 177
Some(IntentSurface::Cron | IntentSurface::Heartbeat) => vak_core::Surface::Background, - 178
Some(IntentSurface::Worker) => vak_core::Surface::Worker, - 179
None => { - 180
eprintln!("error: unknown surface '{raw}'"); - 181
return 2; - 182
} - 183
}, - 184
}; - 185
- 186
let authority = core.turn_authority_for(&core_surface); - 187
let resolution = vak_core::intent::resolve_turn( - 188
prompt, - 189
"", - 190
&core_surface, - 191
&[], - 192
vak_core::intent::workspace_facts(core.cwd()), - 193
vak_intent::HistoryFacts::default(), - 194
&declared, - 195
&authority, - 196
&vak_core::intent::resolver_config(core.config()), - 197
); - 198
let escalation = match &resolution { - 199
vak_intent::Resolution::Escalate { reason, .. } => Some(reason.clone()), - 200
vak_intent::Resolution::Settled(_) => None, - 201
}; - 202
let intent = resolution.intent(); - 203
- 204
if json { - 205
let value = serde_json::json!({ - 206
"reading": intent.reading, - 207
"strands": intent.strands, - 208
"engagement": intent.engagement, - 209
"provenance": intent.provenance, - 210
"narrows": intent.engagement.limits.diff_from(&Limits::unrestricted()), - 211
"escalation_recommended": escalation, - 212
"model_visible": intent.model_visible(), - 213
}); - 214
println!( - 215
"{}", - 216
serde_json::to_string_pretty(&value).unwrap_or_else(|_| "{}".into()) - 217
); - 218
return 0; - 219
} - 220
- 221
if intent.strands.len() > 1 { - 222
println!("parts ({})", intent.strands.len()); - 223
for (index, strand) in intent.strands.iter().enumerate() { - 224
let relation = match &strand.relation { - 225
vak_intent::StrandRelation::Independent => String::new(), - 226
vak_intent::StrandRelation::Sequential { after } => { - 227
format!(", after {after}") - 228
} - 229
vak_intent::StrandRelation::Dependent { on } => format!(", uses {on}"), - 230
}; - 231
let lineage = match &strand.lineage { - 232
vak_intent::Lineage::New => String::new(), - 233
vak_intent::Lineage::Continues { thread_id } => { - 234
format!(", continues {thread_id}") - 235
} - 236
vak_intent::Lineage::Corrects { thread_id } => { - 237
format!(", corrects {thread_id}") - 238
} - 239
vak_intent::Lineage::Replaces { thread_id } => { - 240
format!(", replaces {thread_id}") - 241
} - 242
}; - 243
println!( - 244
" {}. [{}] {}/{}/{}/{} {:.0}%{relation}{lineage} “{}”", - 245
index + 1, - 246
strand.strand_id, - 247
strand.reading.act.as_str(), - 248
strand.reading.horizon.as_str(), - 249
strand.reading.stakes.as_str(), - 250
strand.reading.evidence.as_str(), - 251
strand.reading.confidence * 100.0, - 252
strand.text - 253
); - 254
} - 255
println!(); - 256
} - 257
- 258
let reading = &intent.reading; - 259
println!("reading (composite)"); - 260
println!(" act {}", reading.act.as_str()); - 261
println!(" horizon {}", reading.horizon.as_str()); - 262
println!(" stakes {}", reading.stakes.as_str()); - 263
println!(" evidence {}", reading.evidence.as_str()); - 264
println!(" clarity {}", reading.clarity.as_str()); - 265
println!(" attendance {}", reading.attendance.as_str()); - 266
if !reading.domains.is_empty() { - 267
println!( - 268
" domains {}", - 269
reading - 270
.domains - 271
.iter() - 272
.cloned() - 273
.collect::<Vec<_>>() - 274
.join(", ") - 275
); - 276
} - 277
println!( - 278
" confidence {:.2} overall (act {:.2}, horizon {:.2}, stakes {:.2}, evidence {:.2})", - 279
reading.confidence, - 280
reading.axis_confidence.act, - 281
reading.axis_confidence.horizon, - 282
reading.axis_confidence.stakes, - 283
reading.axis_confidence.evidence - 284
); - 285
- 286
println!(); - 287
println!( - 288
"resolved by {} ({})", - 289
intent.provenance.tier.as_str(), - 290
if intent.provenance.reproducible { - 291
"reproducible from this ledger" - 292
} else { - 293
"NOT reproducible — a model decided it" - 294
} - 295
); - 296
if let Some(model) = &intent.provenance.model { - 297
println!(" model {model}"); - 298
} - 299
if let Some(note) = &intent.provenance.escalation_note { - 300
println!(" note {note}"); - 301
} - 302
if let Some(reason) = &escalation { - 303
println!(" escalation recommended: {reason}"); - 304
} - 305
- 306
println!(); - 307
println!("signals"); - 308
if intent.provenance.signals.is_empty() { - 309
println!(" (none — nothing in this request matched the lexicon)"); - 310
} - 311
for signal in &intent.provenance.signals { - 312
println!( - 313
" {:<11} {:>5.2} {}", - 314
signal.kind.as_str(), - 315
signal.weight, - 316
signal.detail - 317
); - 318
} - 319
- 320
println!(); - 321
println!("engagement (relative to doing nothing)"); - 322
let narrows = intent.engagement.limits.diff_from(&Limits::unrestricted()); - 323
if narrows.is_empty() { - 324
println!(" nothing narrowed — this is the general engagement"); - 325
} - 326
for line in &narrows { - 327
println!(" - {line}"); - 328
} - 329
- 330
let posture = &intent.engagement.posture; - 331
println!(); - 332
println!("posture"); - 333
println!( - 334
" work mode {}", - 335
if posture.managed { "managed" } else { "direct" } - 336
); - 337
println!(" commitment {}", posture.open_commitment); - 338
println!(" checkpoint {}", posture.checkpoint_before_effect); - 339
println!(" human loop {}", posture.hil.as_str()); - 340
println!(" clarify {}", posture.clarify.as_str()); - 341
println!(" stop when {}", posture.stop.as_str()); - 342
println!(" context {}", posture.context.as_str()); - 343
println!( - 344
" deliver {} / {} / {}", - 345
posture.delivery.shape.as_str(), - 346
posture.delivery.cadence.as_str(), - 347
posture.delivery.urgency.as_str() - 348
); - 349
println!( - 350
" demand reasoning={} evidence={} structured={}", - 351
posture.demand.reasoning_required, - 352
posture.demand.evidence_required, - 353
posture.demand.structured_output - 354
); - 355
- 356
if let Some(note) = intent.model_visible() { - 357
println!(); - 358
println!("the model will additionally be told"); - 359
for line in note.lines() { - 360
println!(" {line}"); - 361
} - 362
} - 363
0 - 364
} - 365
- 366
// ------------------------------------------------------------- commit --- - 367
- 368
pub(crate) fn run_commit(cwd: std::path::PathBuf, action: CommitAction) -> i32 { - 369
let core = match Core::new(cwd) { - 370
Ok(core) => core, - 371
Err(error) => { - 372
eprintln!("error: {error}"); - 373
return 2; - 374
} - 375
}; - 376
dispatch_commit(&core, action) - 377
} - 378
- 379
fn dispatch_commit(core: &Core, action: CommitAction) -> i32 { - 380
let ledger = CommitmentLedger::new(&core.sessions_home()); - 381
match action { - 382
CommitAction::List { all, json } => list(&ledger, all, json), - 383
CommitAction::Show { id, json } => show(&ledger, &id, json), - 384
CommitAction::Close { id, verdict, note } => close(&ledger, &id, &verdict, ¬e), - 385
CommitAction::Supersede { id, by, reason } => supersede(&ledger, &id, &by, &reason), - 386
CommitAction::Attest { - 387
id, - 388
criterion, - 389
note, - 390
} => attest(&ledger, &id, &criterion, ¬e), - 391
} - 392
} - 393
- 394
fn list(ledger: &CommitmentLedger, all: bool, json: bool) -> i32 { - 395
let commitments = if all { ledger.all() } else { ledger.open() }; - 396
if json { - 397
println!( - 398
"{}", - 399
serde_json::to_string_pretty(&commitments).unwrap_or_else(|_| "[]".into()) - 400
); - 401
return 0; - 402
} - 403
if commitments.is_empty() { - 404
println!("no commitments"); - 405
return 0; - 406
} - 407
// Ordered by the same scheduler the runtime uses, so this listing answers - 408
// "what happens next" rather than merely "what exists". - 409
let ranked = vak_commit::rank(&commitments, &vak_commit::SchedulerContext::default()); - 410
for priority in ranked { - 411
let Some(commitment) = commitments - 412
.iter() - 413
.find(|c| c.commitment_id == priority.commitment_id) - 414
else { - 415
continue; - 416
}; - 417
println!( - 418
"{} {}", - 419
&commitment.commitment_id[..8.min(commitment.commitment_id.len())], - 420
commitment.summary() - 421
); - 422
println!(" {}", priority.explain()); - 423
} - 424
0 - 425
} - 426
- 427
fn show(ledger: &CommitmentLedger, id: &str, json: bool) -> i32 { - 428
let Some(commitment) = resolve_id(ledger, id) else { - 429
eprintln!("error: no commitment matching '{id}'"); - 430
return 2; - 431
}; - 432
if json { - 433
println!( - 434
"{}", - 435
serde_json::to_string_pretty(&commitment).unwrap_or_else(|_| "{}".into()) - 436
); - 437
return 0; - 438
} - 439
println!("{}", commitment.spec.objective); - 440
println!(" id {}", commitment.commitment_id); - 441
println!(" phase {}", commitment.phase.as_str()); - 442
println!(" opened {}", commitment.opened_at.to_rfc3339()); - 443
println!( - 444
" requires {} evidence to close fulfilled", - 445
commitment.spec.min_satisfaction.as_str() - 446
); - 447
println!( - 448
" achieved {} evidence", - 449
commitment.achieved_strength().as_str() - 450
); - 451
println!(" spend ${:.4}", commitment.spend_usd); - 452
if let Some(suspension) = &commitment.suspension { - 453
println!(" waiting {}", suspension.describe()); - 454
} - 455
if let Some(blocker) = &commitment.blocker { - 456
println!(" blocked {blocker}"); - 457
} - 458
if commitment.is_stalled() { - 459
println!( - 460
" stalled {} consecutive episodes made no progress", - 461
commitment.consecutive_stalls - 462
); - 463
} - 464
if !commitment.drift.is_empty() { - 465
println!(" drift {}", commitment.drift.join("; ")); - 466
} - 467
if !commitment.criteria.is_empty() { - 468
println!(" criteria"); - 469
for criterion in &commitment.criteria { - 470
let mark = if criterion.passed() { "PASS" } else { "open" }; - 471
println!( - 472
" [{mark}] {} ({})", - 473
criterion.statement, - 474
criterion - 475
.strength - 476
.map(|s| s.as_str()) - 477
.unwrap_or("not evaluated") - 478
); - 479
} - 480
} - 481
if !commitment.episodes.is_empty() { - 482
println!(" episodes"); - 483
for episode in &commitment.episodes { - 484
println!( - 485
" {} {} ${:.4}", - 486
episode.session_id, - 487
episode - 488
.advancement - 489
.as_ref() - 490
.map(|a| a.as_str()) - 491
.unwrap_or("running"), - 492
episode.spend_usd - 493
); - 494
} - 495
} - 496
if let Some(closure) = &commitment.closure { - 497
println!( - 498
" closed {} ({} evidence) — {}", - 499
closure.verdict.as_str(), - 500
closure.strength.as_str(), - 501
closure.note - 502
); - 503
} - 504
0 - 505
} - 506
- 507
fn close(ledger: &CommitmentLedger, id: &str, verdict: &str, note: &str) -> i32 { - 508
let Some(commitment) = resolve_id(ledger, id) else { - 509
eprintln!("error: no commitment matching '{id}'"); - 510
return 2; - 511
}; - 512
let verdict = match verdict { - 513
"fulfilled" => Verdict::Fulfilled, - 514
"partial" => Verdict::Partial, - 515
"failed" => Verdict::Failed, - 516
"abandoned" => Verdict::Abandoned, - 517
"expired" => Verdict::Expired, - 518
"unknown" => Verdict::Unknown, - 519
other => { - 520
eprintln!( - 521
"error: unknown verdict '{other}' \ - 522
(fulfilled, partial, failed, abandoned, expired, unknown)" - 523
); - 524
return 2; - 525
} - 526
}; - 527
let strength = commitment.achieved_strength(); - 528
match ledger.append(&vak_commit::Event::new( - 529
&commitment.commitment_id, - 530
vak_commit::EventKind::Closed { - 531
verdict, - 532
strength, - 533
evidence: Vec::new(), - 534
note: note.to_string(), - 535
}, - 536
)) { - 537
Ok(()) => { - 538
println!( - 539
"closed {} as {}", - 540
&commitment.commitment_id[..8.min(commitment.commitment_id.len())], - 541
verdict.as_str() - 542
); - 543
0 - 544
} - 545
Err(error) => { - 546
// The refusal explains itself: this is where the closure invariant - 547
// becomes visible to a person rather than staying an internal rule. - 548
eprintln!("error: {error}"); - 549
2 - 550
} - 551
} - 552
} - 553
- 554
fn supersede(ledger: &CommitmentLedger, id: &str, by: &str, reason: &str) -> i32 { - 555
let (Some(old), Some(new)) = (resolve_id(ledger, id), resolve_id(ledger, by)) else { - 556
eprintln!("error: both the superseded and the replacing commitment must exist"); - 557
return 2; - 558
}; - 559
match ledger.append(&vak_commit::Event::new( - 560
&old.commitment_id, - 561
vak_commit::EventKind::Superseded { - 562
by: new.commitment_id.clone(), - 563
reason: reason.to_string(), - 564
}, - 565
)) { - 566
Ok(()) => { - 567
println!("superseded by {}", new.commitment_id); - 568
0 - 569
} - 570
Err(error) => { - 571
eprintln!("error: {error}"); - 572
2 - 573
} - 574
} - 575
} - 576
- 577
fn attest(ledger: &CommitmentLedger, id: &str, criterion: &str, note: &str) -> i32 { - 578
let Some(commitment) = resolve_id(ledger, id) else { - 579
eprintln!("error: no commitment matching '{id}'"); - 580
return 2; - 581
}; - 582
if !commitment - 583
.criteria - 584
.iter() - 585
.any(|c| c.criterion_id == criterion) - 586
{ - 587
eprintln!("error: commitment has no criterion '{criterion}'"); - 588
return 2; - 589
} - 590
let by = std::env::var("USER").unwrap_or_else(|_| "operator".into()); - 591
let evaluation = vak_commit::Evaluation::attested(criterion, &by, note); - 592
match ledger.append(&vak_commit::Event::new( - 593
&commitment.commitment_id, - 594
vak_commit::EventKind::CriterionEvaluated { - 595
criterion_id: evaluation.criterion_id.clone(), - 596
result: evaluation.result.clone(), - 597
strength: evaluation.strength, - 598
}, - 599
)) { - 600
Ok(()) => { - 601
println!("attested '{criterion}' as {by}"); - 602
0 - 603
} - 604
Err(error) => { - 605
eprintln!("error: {error}"); - 606
2 - 607
} - 608
} - 609
} - 610
- 611
/// Resolve a full id or an unambiguous prefix. - 612
fn resolve_id(ledger: &CommitmentLedger, id: &str) -> Option<vak_commit::Commitment> { - 613
let all = ledger.all(); - 614
if let Some(exact) = all.iter().find(|c| c.commitment_id == id) { - 615
return Some(exact.clone()); - 616
} - 617
let mut matches = all - 618
.iter() - 619
.filter(|c| c.commitment_id.starts_with(id)) - 620
.cloned(); - 621
let first = matches.next()?; - 622
// An ambiguous prefix resolves to nothing rather than to whichever row - 623
// happened to sort first. - 624
if matches.next().is_some() { - 625
return None; - 626
} - 627
Some(first) - 628
} - 629
- 630
// -------------------------------------------------------------- grants --- - 631
- 632
/// Delegate authority to one commitment. - 633
/// - 634
/// An envelope is **pre-authorization within existing authority**, never a - 635
/// grant of new authority: its permission ceiling can only lower the mode - 636
/// already in force, and irreversible work still reaches a human whatever was - 637
/// delegated. What it buys is silence on the ordinary case — a `delegated` - 638
/// agent stops asking about the things you already said yes to, inside the - 639
/// boundary you drew. - 640
#[allow(clippy::too_many_arguments)] - 641
pub(crate) fn run_grant( - 642
cwd: std::path::PathBuf, - 643
id: String, - 644
paths: Vec<String>, - 645
tools: Vec<String>, - 646
spend_usd: Option<f64>, - 647
hours: Option<i64>, - 648
permission: String, - 649
on_silence: String, - 650
after_hours: u32, - 651
) -> i32 { - 652
let core = match Core::new(cwd) { - 653
Ok(core) => core, - 654
Err(error) => { - 655
eprintln!("error: {error}"); - 656
return 2; - 657
} - 658
}; - 659
let ledger = CommitmentLedger::new(&core.sessions_home()); - 660
let Some(commitment) = resolve_id(&ledger, &id) else { - 661
eprintln!("error: no commitment matching '{id}'"); - 662
return 2; - 663
}; - 664
let Some(ceiling) = vak_intent::PermissionCeiling::parse(&permission) else { - 665
eprintln!( - 666
"error: unknown permission '{permission}' \ - 667
(read-only, workspace-write, full-access)" - 668
); - 669
return 2; - 670
}; - 671
let escalation = match on_silence.as_str() { - 672
"wait" => vak_intent::Escalation::WaitIndefinitely, - 673
"assume" => vak_intent::Escalation::AssumeConservative { after_hours }, - 674
"abandon" => vak_intent::Escalation::AbandonAfter { after_hours }, - 675
other => { - 676
eprintln!("error: unknown --on-silence '{other}' (wait, assume, abandon)"); - 677
return 2; - 678
} - 679
}; - 680
// A limit that is not a finite, non-negative amount is no limit: `NaN` - 681
// compares false against every spend and would silently remove it. - 682
if spend_usd.is_some_and(|cap| !cap.is_finite() || cap < 0.0) { - 683
eprintln!("error: --spend-usd must be a finite amount of zero or more"); - 684
return 2; - 685
} - 686
// Assuming a default for an irreversible action because nobody replied is - 687
// the exact autonomy this system exists to prevent, so the refusal is - 688
// enforced rather than documented. - 689
if !escalation.permitted_for(commitment.spec.reading.stakes) { - 690
eprintln!( - 691
"error: --on-silence assume is not available for {} work; \ - 692
a default nobody confirmed cannot stand in for consent here", - 693
commitment.spec.reading.stakes.as_str() - 694
); - 695
return 2; - 696
} - 697
- 698
let envelope = vak_intent::Envelope { - 699
envelope_id: uuid::Uuid::now_v7().to_string(), - 700
granted_by: std::env::var("USER").unwrap_or_else(|_| "operator".into()), - 701
granted_at: chrono::Utc::now(), - 702
expires_at: hours.map(|h| chrono::Utc::now() + chrono::Duration::hours(h)), - 703
spend_limit_usd: spend_usd, - 704
path_scope: paths, - 705
tool_scope: tools, - 706
permission_ceiling: ceiling, - 707
escalation, - 708
revoked_at: None, - 709
}; - 710
let envelope_id = envelope.envelope_id.clone(); - 711
match ledger.append(&vak_commit::Event::new( - 712
&commitment.commitment_id, - 713
vak_commit::EventKind::EnvelopeGranted { - 714
envelope: Box::new(envelope), - 715
}, - 716
)) { - 717
Ok(()) => { - 718
println!("granted {envelope_id} on {}", commitment.spec.objective); - 719
println!( - 720
" this narrows, it does not widen: the permission mode is capped at \ - 721
{permission} and irreversible steps still ask." - 722
); - 723
0 - 724
} - 725
Err(error) => { - 726
eprintln!("error: {error}"); - 727
2 - 728
} - 729
} - 730
} - 731
- 732
/// Withdraw a grant. - 733
/// - 734
/// Revocation takes effect on read rather than being remembered: a revoked - 735
/// envelope narrows nothing further and grants nothing at all, so an in-flight - 736
/// run loses the delegation at its next authority check (`AGENTS.md` - 737
/// invariant 11). - 738
pub(crate) fn run_revoke(cwd: std::path::PathBuf, id: String) -> i32 { - 739
let core = match Core::new(cwd) { - 740
Ok(core) => core, - 741
Err(error) => { - 742
eprintln!("error: {error}"); - 743
return 2; - 744
} - 745
}; - 746
let ledger = CommitmentLedger::new(&core.sessions_home()); - 747
let Some(commitment) = resolve_id(&ledger, &id) else { - 748
eprintln!("error: no commitment matching '{id}'"); - 749
return 2; - 750
}; - 751
let Some(envelope) = commitment.envelope.as_ref() else { - 752
eprintln!("error: that commitment has no grant to revoke"); - 753
return 2; - 754
}; - 755
match ledger.append(&vak_commit::Event::new( - 756
&commitment.commitment_id, - 757
vak_commit::EventKind::EnvelopeRevoked { - 758
envelope_id: envelope.envelope_id.clone(), - 759
by: std::env::var("USER").unwrap_or_else(|_| "operator".into()), - 760
}, - 761
)) { - 762
Ok(()) => { - 763
println!("revoked {}", envelope.envelope_id); - 764
0 - 765
} - 766
Err(error) => { - 767
eprintln!("error: {error}"); - 768
2 - 769
} - 770
} - 771
} - 772
Indexing the workspace…
Vakyartha documentation is discovering safe artifacts, anchors, and source references.