- 3001
supports_tables: true, - 3002
supports_code_blocks: true, - 3003
supports_links: true, - 3004
supports_actions: false, - 3005
template: None, - 3006
posture: DeliveryPosture::default(), - 3007
}, - 3008
skill_registry: None, - 3009
}; - 3010
let packet = render(&job).expect("render"); - 3011
assert!(!packet.diagnostics.is_empty()); - 3012
})); - 3013
- 3014
scenarios.push(tc("no_diagnostic_with_support", || { - 3015
let job = DeliveryJob { - 3016
job_id: "action-s".into(), - 3017
target: "test:one".into(), - 3018
kind: DeliveryKind::Approval, - 3019
content: DeliveryContent::Approval(ApprovalPayload { - 3020
request_id: "r".into(), - 3021
title: "t".into(), - 3022
detail: "d".into(), - 3023
expires_at: None, - 3024
actions: vec![DeliveryAction { - 3025
id: "approve".into(), - 3026
label: "Yes".into(), - 3027
verb: "approve".into(), - 3028
data: BTreeMap::new(), - 3029
}], - 3030
}), - 3031
profile: DeliveryProfile { - 3032
surface: "test".into(), - 3033
markup: Markup::Plain, - 3034
max_chars: None, - 3035
supports_tables: true, - 3036
supports_code_blocks: true, - 3037
supports_links: true, - 3038
supports_actions: true, - 3039
template: None, - 3040
posture: DeliveryPosture::default(), - 3041
}, - 3042
skill_registry: None, - 3043
}; - 3044
let packet = render(&job).expect("render"); - 3045
assert!(packet.diagnostics.is_empty()); - 3046
})); - 3047
- 3048
scenarios.push(tc("json_payload_structured", || { - 3049
let job = answer_job( - 3050
"# T\n\nBody", - 3051
Markup::Json, - 3052
None, - 3053
DeliveryKind::Assistant, - 3054
"test", - 3055
); - 3056
let packet = render(&job).expect("render"); - 3057
assert!(matches!(packet.payload, DeliveryPayload::Structured(_))); - 3058
})); - 3059
- 3060
scenarios.push(tc("plain_payload_text", || { - 3061
let job = answer_job( - 3062
"# T\n\nBody", - 3063
Markup::Plain, - 3064
None, - 3065
DeliveryKind::Assistant, - 3066
"test", - 3067
); - 3068
let packet = render(&job).expect("render"); - 3069
assert!(matches!(packet.payload, DeliveryPayload::Text(_))); - 3070
})); - 3071
- 3072
scenarios.push(tc("presentation_some_for_answer", || { - 3073
let job = answer_job( - 3074
"# T\n\nBody", - 3075
Markup::Plain, - 3076
None, - 3077
DeliveryKind::Assistant, - 3078
"test", - 3079
); - 3080
let packet = render(&job).expect("render"); - 3081
assert!(packet.presentation.is_some()); - 3082
let timeline = packet.presentation.unwrap(); - 3083
assert_eq!(timeline.schema_version, PRESENTATION_SCHEMA_VERSION); - 3084
assert_eq!(timeline.items.len(), 1); - 3085
})); - 3086
- 3087
scenarios.push(tc("presentation_none_for_approval", || { - 3088
let job = approval_job(Markup::Plain, "test"); - 3089
let packet = render(&job).expect("render"); - 3090
assert!(packet.presentation.is_none()); - 3091
})); - 3092
- 3093
scenarios.push(tc("presentation_none_for_progress", || { - 3094
let job = progress_job(Markup::Plain, "test"); - 3095
let packet = render(&job).expect("render"); - 3096
assert!(packet.presentation.is_none()); - 3097
})); - 3098
- 3099
scenarios.push(tc("presentation_none_for_text", || { - 3100
let job = text_job("text", Markup::Plain, "test"); - 3101
let packet = render(&job).expect("render"); - 3102
assert!(packet.presentation.is_none()); - 3103
})); - 3104
- 3105
scenarios.push(tc("presentation_none_for_toolresult", || { - 3106
let job = tool_result_job(Markup::Plain, "test", false); - 3107
let packet = render(&job).expect("render"); - 3108
assert!(packet.presentation.is_none()); - 3109
})); - 3110
- 3111
// Outcome status from metadata - 3112
scenarios.push(tc("outcome_succeeded_complete", || { - 3113
let src = "# Result\n\nDone."; - 3114
let mut answer = answer_draft(src); - 3115
answer - 3116
.metadata - 3117
.insert("outcome_completion".into(), "complete".into()); - 3118
let job = DeliveryJob { - 3119
job_id: "o1".into(), - 3120
target: "t".into(), - 3121
kind: DeliveryKind::Assistant, - 3122
content: DeliveryContent::Answer(answer), - 3123
profile: plain_profile("test"), - 3124
skill_registry: None, - 3125
}; - 3126
let packet = render(&job).expect("render"); - 3127
let timeline = packet.presentation.unwrap(); - 3128
assert_eq!(timeline.items[0].status, OutputStatus::Succeeded); - 3129
})); - 3130
- 3131
scenarios.push(tc("outcome_partial_when_incomplete", || { - 3132
let src = "# Result\n\nDone."; - 3133
let mut answer = answer_draft(src); - 3134
answer - 3135
.metadata - 3136
.insert("outcome_completion".into(), "partial".into()); - 3137
let job = DeliveryJob { - 3138
job_id: "o2".into(), - 3139
target: "t".into(), - 3140
kind: DeliveryKind::Assistant, - 3141
content: DeliveryContent::Answer(answer), - 3142
profile: plain_profile("test"), - 3143
skill_registry: None, - 3144
}; - 3145
let packet = render(&job).expect("render"); - 3146
let timeline = packet.presentation.unwrap(); - 3147
assert_eq!(timeline.items[0].status, OutputStatus::Partial); - 3148
assert!(!timeline.diagnostics.is_empty()); - 3149
})); - 3150
- 3151
scenarios.push(tc("outcome_partial_for_human_review", || { - 3152
let src = "# Result\n\nDone."; - 3153
let mut answer = answer_draft(src); - 3154
answer - 3155
.metadata - 3156
.insert("outcome_human_review".into(), "needed".into()); - 3157
let job = DeliveryJob { - 3158
job_id: "o3".into(), - 3159
target: "t".into(), - 3160
kind: DeliveryKind::Assistant, - 3161
content: DeliveryContent::Answer(answer), - 3162
profile: plain_profile("test"), - 3163
skill_registry: None, - 3164
}; - 3165
let packet = render(&job).expect("render"); - 3166
let timeline = packet.presentation.unwrap(); - 3167
assert_eq!(timeline.items[0].status, OutputStatus::Partial); - 3168
})); - 3169
- 3170
// channel_projection joins results - 3171
scenarios.push(tc("channel_projection_multi_results", || { - 3172
let src = "# Combined"; - 3173
let answer = AnswerDraft { - 3174
schema_version: DELIVERY_SCHEMA_VERSION, - 3175
source_markdown: src.into(), - 3176
blocks: answer_blocks(src), - 3177
document: PresentationDocument::default(), - 3178
metadata: BTreeMap::new(), - 3179
results: vec![ - 3180
AnswerResult { - 3181
id: "r1".into(), - 3182
source_markdown: "First.".into(), - 3183
outcome: None, - 3184
}, - 3185
AnswerResult { - 3186
id: "r2".into(), - 3187
source_markdown: "Second.".into(), - 3188
outcome: None, - 3189
}, - 3190
], - 3191
}; - 3192
let job = DeliveryJob { - 3193
job_id: "cp".into(), - 3194
target: "t".into(), - 3195
kind: DeliveryKind::Assistant, - 3196
content: DeliveryContent::Answer(answer), - 3197
profile: plain_profile("test"), - 3198
skill_registry: None, - 3199
}; - 3200
let packet = render(&job).expect("render"); - 3201
assert!(packet.fallback_markdown.contains("First.")); - 3202
assert!(packet.fallback_markdown.contains("Second.")); - 3203
})); - 3204
- 3205
scenarios.push(tc("channel_projection_single_result", || { - 3206
let src = "# Single"; - 3207
let answer = AnswerDraft { - 3208
schema_version: DELIVERY_SCHEMA_VERSION, - 3209
source_markdown: src.into(), - 3210
blocks: answer_blocks(src), - 3211
document: PresentationDocument::default(), - 3212
metadata: BTreeMap::new(), - 3213
results: vec![AnswerResult { - 3214
id: "r1".into(), - 3215
source_markdown: "Only.".into(), - 3216
outcome: None, - 3217
}], - 3218
}; - 3219
let job = DeliveryJob { - 3220
job_id: "cp2".into(), - 3221
target: "t".into(), - 3222
kind: DeliveryKind::Assistant, - 3223
content: DeliveryContent::Answer(answer), - 3224
profile: plain_profile("test"), - 3225
skill_registry: None, - 3226
}; - 3227
let packet = render(&job).expect("render"); - 3228
assert_eq!(packet.fallback_markdown, "Only."); - 3229
})); - 3230
- 3231
run("packet_structure", scenarios); - 3232
} - 3233
- 3234
// ════════════════════════════════════════════════════════════════════ - 3235
// 17. SERIALIZATION ROUNDTRIP - 3236
// ════════════════════════════════════════════════════════════════════ - 3237
- 3238
#[test] - 3239
fn audit_serialization_roundtrip() { - 3240
let mut scenarios: Vec<Scenario> = vec![]; - 3241
- 3242
let source = "# Title\n\nBody text.\n\n```rust\nlet x = 1;\n```"; - 3243
- 3244
for (i, markup) in [ - 3245
Markup::Plain, - 3246
Markup::Markdown, - 3247
Markup::TelegramHtml, - 3248
Markup::SlackMrkdwn, - 3249
Markup::DiscordMarkdown, - 3250
Markup::Json, - 3251
] - 3252
.iter() - 3253
.enumerate() - 3254
{ - 3255
let m = *markup; - 3256
scenarios.push(tc(&format!("serde_packet_{i}"), move || { - 3257
let job = answer_job(source, m, Some(1000), DeliveryKind::Assistant, "test"); - 3258
let packet = render(&job).expect("render"); - 3259
let bytes = serde_json::to_vec(&packet).expect("serialize"); - 3260
let deserialized: DeliveryPacket = serde_json::from_slice(&bytes).expect("deserialize"); - 3261
assert_eq!(deserialized.job_id, packet.job_id); - 3262
assert_eq!(deserialized.fallback_markdown, packet.fallback_markdown); - 3263
assert_eq!(deserialized.coverage, packet.coverage); - 3264
})); - 3265
} - 3266
- 3267
scenarios.push(tc("serde_job_roundtrip", || { - 3268
let job = answer_job( - 3269
source, - 3270
Markup::Markdown, - 3271
None, - 3272
DeliveryKind::Assistant, - 3273
"test", - 3274
); - 3275
let bytes = serde_json::to_vec(&job).expect("serialize"); - 3276
let deserialized: DeliveryJob = serde_json::from_slice(&bytes).expect("deserialize"); - 3277
assert_eq!(deserialized.job_id, job.job_id); - 3278
assert_eq!(deserialized.kind, job.kind); - 3279
assert_eq!(deserialized.profile.surface, job.profile.surface); - 3280
})); - 3281
- 3282
for (i, markup) in [Markup::Plain, Markup::Markdown, Markup::Json] - 3283
.iter() - 3284
.enumerate() - 3285
{ - 3286
let m = *markup; - 3287
scenarios.push(tc(&format!("serde_profile_{i}"), move || { - 3288
let p = DeliveryProfile { - 3289
surface: "telegram".into(), - 3290
markup: m, - 3291
max_chars: Some(4000), - 3292
supports_tables: true, - 3293
supports_code_blocks: true, - 3294
supports_links: true, - 3295
supports_actions: false, - 3296
template: None, - 3297
posture: DeliveryPosture::default(), - 3298
}; - 3299
let bytes = serde_json::to_vec(&p).expect("serialize"); - 3300
let deserialized: DeliveryProfile = - 3301
serde_json::from_slice(&bytes).expect("deserialize"); - 3302
assert_eq!(deserialized.markup, p.markup); - 3303
assert_eq!(deserialized.max_chars, p.max_chars); - 3304
assert_eq!(deserialized.surface, p.surface); - 3305
})); - 3306
} - 3307
- 3308
for (i, cadence) in [Cadence::Live, Cadence::OnCompletion, Cadence::Digest] - 3309
.iter() - 3310
.enumerate() - 3311
{ - 3312
let c = *cadence; - 3313
scenarios.push(tc(&format!("serde_cadence_{i}"), move || { - 3314
let bytes = serde_json::to_vec(&c).expect("serialize"); - 3315
let d: Cadence = serde_json::from_slice(&bytes).expect("deserialize"); - 3316
assert_eq!(d, c); - 3317
})); - 3318
} - 3319
- 3320
for (i, urgency) in [Urgency::Interrupt, Urgency::Notify, Urgency::Quiet] - 3321
.iter() - 3322
.enumerate() - 3323
{ - 3324
let u = *urgency; - 3325
scenarios.push(tc(&format!("serde_urgency_{i}"), move || { - 3326
let bytes = serde_json::to_vec(&u).expect("serialize"); - 3327
let d: Urgency = serde_json::from_slice(&bytes).expect("deserialize"); - 3328
assert_eq!(d, u); - 3329
})); - 3330
} - 3331
- 3332
for (i, kind) in [ - 3333
DeliveryKind::Assistant, - 3334
DeliveryKind::Approval, - 3335
DeliveryKind::Progress, - 3336
DeliveryKind::ToolResult, - 3337
DeliveryKind::Alert, - 3338
DeliveryKind::User, - 3339
DeliveryKind::System, - 3340
DeliveryKind::Internal, - 3341
] - 3342
.iter() - 3343
.enumerate() - 3344
{ - 3345
let k = *kind; - 3346
scenarios.push(tc(&format!("serde_kind_{i}"), move || { - 3347
let bytes = serde_json::to_vec(&k).expect("serialize"); - 3348
let d: DeliveryKind = serde_json::from_slice(&bytes).expect("deserialize"); - 3349
assert_eq!(d, k); - 3350
})); - 3351
} - 3352
- 3353
for (i, content) in [ - 3354
DeliveryContent::Text { - 3355
markdown: "hello".into(), - 3356
}, - 3357
DeliveryContent::Approval(ApprovalPayload { - 3358
request_id: "r".into(), - 3359
title: "t".into(), - 3360
detail: "d".into(), - 3361
expires_at: None, - 3362
actions: vec![], - 3363
}), - 3364
DeliveryContent::Progress(ProgressPayload { - 3365
label: "l".into(), - 3366
state: "s".into(), - 3367
percent: Some(50), - 3368
}), - 3369
DeliveryContent::ToolResult(ToolResultPayload { - 3370
tool: "t".into(), - 3371
output: "o".into(), - 3372
is_error: false, - 3373
}), - 3374
] - 3375
.iter() - 3376
.enumerate() - 3377
{ - 3378
let c = content.clone(); - 3379
scenarios.push(tc(&format!("serde_content_{i}"), move || { - 3380
let bytes = serde_json::to_vec(&c).expect("serialize"); - 3381
let d: DeliveryContent = serde_json::from_slice(&bytes).expect("deserialize"); - 3382
assert_eq!(d, c); - 3383
})); - 3384
} - 3385
- 3386
scenarios.push(tc("serde_template_roundtrip", || { - 3387
let template = TemplateSpec { - 3388
id: "test".into(), - 3389
revision: 1, - 3390
origin: TemplateOrigin::User, - 3391
activation: TemplateActivation::Active, - 3392
nodes: vec![ - 3393
TemplateNode::Literal { - 3394
text: "Hello".into(), - 3395
}, - 3396
TemplateNode::Slot { - 3397
slot: TemplateSlot::Body, - 3398
}, - 3399
], - 3400
}; - 3401
let bytes = serde_json::to_vec(&template).expect("serialize"); - 3402
let deserialized: TemplateSpec = serde_json::from_slice(&bytes).expect("deserialize"); - 3403
assert_eq!(deserialized.id, template.id); - 3404
assert_eq!(deserialized.revision, template.revision); - 3405
assert_eq!(deserialized.nodes.len(), template.nodes.len()); - 3406
})); - 3407
- 3408
scenarios.push(tc("serde_profile_roundtrip", || { - 3409
let p = DeliveryProfile { - 3410
surface: "tg".into(), - 3411
markup: Markup::TelegramHtml, - 3412
max_chars: Some(4000), - 3413
supports_tables: false, - 3414
supports_code_blocks: true, - 3415
supports_links: true, - 3416
supports_actions: true, - 3417
template: Some(TemplateSpec { - 3418
id: "t".into(), - 3419
revision: 1, - 3420
origin: TemplateOrigin::User, - 3421
activation: TemplateActivation::Active, - 3422
nodes: vec![TemplateNode::Literal { text: "x".into() }], - 3423
}), - 3424
posture: DeliveryPosture { - 3425
cadence: Cadence::Digest, - 3426
urgency: Urgency::Quiet, - 3427
}, - 3428
}; - 3429
let bytes = serde_json::to_vec(&p).expect("serialize"); - 3430
let d: DeliveryProfile = serde_json::from_slice(&bytes).expect("deserialize"); - 3431
assert_eq!(d.surface, p.surface); - 3432
assert_eq!(d.markup, p.markup); - 3433
assert_eq!(d.max_chars, p.max_chars); - 3434
assert!(d.template.is_some()); - 3435
})); - 3436
- 3437
run("serialization_roundtrip", scenarios); - 3438
} - 3439
- 3440
// ════════════════════════════════════════════════════════════════════ - 3441
// 18. CONTENT KIND × MARKUP MATRIX - 3442
// ════════════════════════════════════════════════════════════════════ - 3443
- 3444
#[test] - 3445
fn audit_content_kind_markup_matrix() { - 3446
let mut scenarios: Vec<Scenario> = vec![]; - 3447
- 3448
let external_kinds = [ - 3449
DeliveryKind::Assistant, - 3450
DeliveryKind::TaskSummary, - 3451
DeliveryKind::Alert, - 3452
DeliveryKind::User, - 3453
DeliveryKind::Approval, - 3454
DeliveryKind::Progress, - 3455
DeliveryKind::ToolResult, - 3456
]; - 3457
- 3458
let markups: [(Markup, &str); 6] = [ - 3459
(Markup::Plain, "plain"), - 3460
(Markup::Markdown, "md"), - 3461
(Markup::TelegramHtml, "tg"), - 3462
(Markup::SlackMrkdwn, "slack"), - 3463
(Markup::DiscordMarkdown, "discord"), - 3464
(Markup::Json, "json"), - 3465
]; - 3466
- 3467
for kind in &external_kinds { - 3468
for (markup, label) in markups { - 3469
let k = *kind; - 3470
let m = markup; - 3471
let l = label; - 3472
scenarios.push(tc(&format!("matrix_{k:?}_{l}"), move || { - 3473
let content = match k { - 3474
DeliveryKind::Approval => DeliveryContent::Approval(ApprovalPayload { - 3475
request_id: "r".into(), - 3476
title: "t".into(), - 3477
detail: "d".into(), - 3478
expires_at: None, - 3479
actions: vec![], - 3480
}), - 3481
DeliveryKind::Progress => DeliveryContent::Progress(ProgressPayload { - 3482
label: "l".into(), - 3483
state: "s".into(), - 3484
percent: None, - 3485
}), - 3486
DeliveryKind::ToolResult => DeliveryContent::ToolResult(ToolResultPayload { - 3487
tool: "bash".into(), - 3488
output: "ok".into(), - 3489
is_error: false, - 3490
}), - 3491
_ => DeliveryContent::Answer(answer_draft("source")), - 3492
}; - 3493
let job = DeliveryJob { - 3494
job_id: "mx".into(), - 3495
target: "t".into(), - 3496
kind: k, - 3497
content, - 3498
profile: DeliveryProfile::plain("test"), - 3499
skill_registry: None, - 3500
}; - 3501
let job = DeliveryJob { - 3502
profile: DeliveryProfile { - 3503
surface: "test".into(), - 3504
markup: m, - 3505
max_chars: None, - 3506
supports_tables: true, - 3507
supports_code_blocks: true, - 3508
supports_links: true, - 3509
supports_actions: false, - 3510
template: None, - 3511
posture: DeliveryPosture::default(), - 3512
}, - 3513
..job - 3514
}; - 3515
let result = render(&job); - 3516
match k { - 3517
DeliveryKind::Approval | DeliveryKind::Progress | DeliveryKind::ToolResult => { - 3518
// These don't accept Answer content, so they might error - 3519
// Actually Text content is accepted by all - 3520
// But they specifically need matching content - 3521
// Approval→Approval, Progress→Progress, ToolResult→ToolResult - 3522
// Since we provide matching content, they should succeed - 3523
assert!(result.is_ok(), "{k:?} + {l}: {result:?}"); - 3524
} - 3525
_ => { - 3526
// Assistant/TaskSummary/Alert/User can accept Answer - 3527
assert!(result.is_ok(), "{k:?} + {l}: {result:?}"); - 3528
} - 3529
} - 3530
})); - 3531
} - 3532
} - 3533
- 3534
run("content_kind_markup_matrix", scenarios); - 3535
} - 3536
Indexing the workspace…
Vakyartha documentation is discovering safe artifacts, anchors, and source references.