tandem-plan-compiler 0.4.31

Mission and plan compiler boundary for Tandem
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
#[cfg(test)]
mod tests {
    use super::*;
    use serde_json::json;

    #[test]
    fn plan_package_roundtrips_preview_shape() {
        let package = PlanPackage {
            plan_id: "plan_0f6e8c".to_string(),
            plan_revision: 3,
            lifecycle_state: PlanLifecycleState::Preview,
            owner: PlanOwner {
                owner_id: "user".to_string(),
                scope: "workspace".to_string(),
                audience: "internal".to_string(),
            },
            mission: MissionDefinition {
                goal: "Operationalize a user goal".to_string(),
                summary: Some("Turn one mission into a governed multi-routine plan".to_string()),
                domain: Some("mixed".to_string()),
            },
            success_criteria: SuccessCriteria {
                required_artifacts: vec!["founder_brief.md".to_string()],
                minimum_viable_completion: Some("At least one usable routine graph".to_string()),
                minimum_output: None,
                freshness_window_hours: None,
            },
            budget_policy: Some(BudgetPolicy {
                max_cost_per_run_usd: Some(4.0),
                max_daily_cost_usd: Some(20.0),
                max_weekly_cost_usd: Some(60.0),
                token_ceiling_per_run: Some(40_000),
                cheap_model_preferred_for: vec!["search".to_string()],
                strong_model_reserved_for: vec!["final synthesis".to_string()],
            }),
            budget_enforcement: Some(BudgetEnforcement {
                cost_tracking_unit: Some(CostTrackingUnit {
                    method: Some("token_count × model_rate_per_token".to_string()),
                    recorded_fields: vec!["tokens_in".to_string(), "tokens_out".to_string()],
                    tracking_scope: vec!["step".to_string(), "plan_run".to_string()],
                }),
                soft_warning_threshold: Some(0.8),
                hard_limit_behavior: Some("pause_before_step".to_string()),
                partial_result_preservation: Some(true),
                daily_and_weekly_enforcement: None,
            }),
            approval_policy: Some(ApprovalMatrix {
                public_posts: Some(ApprovalMode::ApprovalRequired),
                internal_reports: Some(ApprovalMode::AutoApproved),
                ..ApprovalMatrix::default()
            }),
            inter_routine_policy: Some(InterRoutinePolicy {
                communication_model: CommunicationModel::ArtifactOnly,
                shared_memory_access: false,
                shared_memory_justification: None,
                peer_visibility: PeerVisibility::DeclaredOutputsOnly,
                artifact_handoff_validation: true,
            }),
            trigger_policy: Some(TriggerPolicy {
                supported: vec![TriggerKind::Scheduled, TriggerKind::Manual],
            }),
            output_roots: Some(OutputRoots {
                plan: Some("knowledge/workflows/plan/".to_string()),
                history: Some("knowledge/workflows/run-history/".to_string()),
                proof: Some("knowledge/workflows/proof/".to_string()),
                drafts: Some("knowledge/workflows/drafts/".to_string()),
            }),
            precedence_log: vec![PrecedenceLogEntry {
                path: "budget_policy.max_cost_per_run_usd".to_string(),
                compiler_default: Some(json!(2.0)),
                user_override: Some(json!(4.0)),
                approved_plan_state: None,
                resolved_value: Some(json!(4.0)),
                source_tier: PrecedenceSourceTier::UserOverride,
                conflict_detected: true,
                resolution_rule: "approved_plan_state > user_override > compiler_default"
                    .to_string(),
                resolved_at: Some("2026-03-27T09:12:00Z".to_string()),
            }],
            plan_diff: Some(PlanDiff {
                from_revision: 2,
                to_revision: 3,
                changed_fields: vec![PlanDiffChangedField {
                    path: "routine_graph[0].trigger.schedule".to_string(),
                    change_type: PlanDiffChangeType::Update,
                    old_value: Some(json!("0 9 * * *")),
                    new_value: Some(json!("0 10 * * *")),
                    requires_revalidation: true,
                    requires_reapproval: false,
                    breaking: false,
                }],
                summary: PlanDiffSummary {
                    changed_count: 1,
                    breaking_count: 0,
                    revalidation_required: true,
                    reapproval_required: false,
                },
            }),
            manual_trigger_record: Some(ManualTriggerRecord {
                trigger_id: "mt_01HZY".to_string(),
                plan_id: "plan_0f6e8c".to_string(),
                plan_revision: 3,
                routine_id: "founder_brief_daily".to_string(),
                triggered_by: "user_123".to_string(),
                trigger_source: ManualTriggerSource::Calendar,
                dry_run: true,
                approval_policy_snapshot: Some(ApprovalMatrix {
                    internal_reports: Some(ApprovalMode::AutoApproved),
                    public_posts: Some(ApprovalMode::ApprovalRequired),
                    ..ApprovalMatrix::default()
                }),
                connector_binding_snapshot: vec![ConnectorBinding {
                    capability: "gmail".to_string(),
                    binding_type: "oauth_integration".to_string(),
                    binding_id: "gmail-prod".to_string(),
                    allowlist_pattern: Some("gmail.send".to_string()),
                    status: "mapped".to_string(),
                }],
                triggered_at: "2026-03-27T09:15:00Z".to_string(),
                run_id: Some("run_abc123".to_string()),
                outcome: Some("paused_after_validation".to_string()),
                artifacts_produced: vec!["founder_brief_draft.md".to_string()],
                notes: Some("Dry-run from calendar entry".to_string()),
            }),
            validation_state: Some(PlanValidationState {
                required_connectors_mapped: Some(false),
                directories_writable: Some(true),
                schedules_valid: Some(true),
                models_resolved: Some(true),
                dependencies_resolvable: Some(true),
                approvals_complete: Some(true),
                degraded_modes_acknowledged: Some(false),
                data_scopes_valid: Some(true),
                audit_scopes_valid: Some(true),
                mission_context_scopes_valid: Some(true),
                inter_routine_policy_complete: Some(true),
                credential_envelopes_valid: Some(true),
                compartmentalized_activation_ready: Some(true),
                context_objects_valid: Some(true),
                success_criteria_evaluation: None,
            }),
            overlap_policy: Some(OverlapPolicy {
                exact_identity: Some(OverlapIdentity {
                    hash_version: Some(1),
                    canonical_hash: Some("abc123".to_string()),
                    normalized_fields: vec!["goal".to_string(), "outputs".to_string()],
                }),
                semantic_identity: Some(SemanticIdentity {
                    similarity_model: Some("text-embedding-3-large".to_string()),
                    semantic_signature: Some("vec-ref".to_string()),
                    similarity_threshold: Some(0.8),
                }),
                overlap_log: vec![OverlapLogEntry {
                    matched_plan_id: "plan_old".to_string(),
                    matched_plan_revision: 2,
                    match_layer: "semantic".to_string(),
                    similarity_score: Some(0.92),
                    decision: "fork".to_string(),
                    decided_by: "user_confirmed".to_string(),
                    decided_at: "2026-03-27T10:00:00Z".to_string(),
                }],
            }),
            routine_graph: vec![RoutinePackage {
                routine_id: "founder_brief_daily".to_string(),
                semantic_kind: RoutineSemanticKind::Reporting,
                trigger: TriggerDefinition {
                    trigger_type: TriggerKind::Scheduled,
                    schedule: Some("0 10 * * *".to_string()),
                    timezone: Some("UTC".to_string()),
                },
                dependencies: vec![RoutineDependency {
                    dependency_type: "routine".to_string(),
                    routine_id: "market_pain_daily".to_string(),
                    mode: DependencyMode::Hard,
                }],
                dependency_resolution: DependencyResolution {
                    strategy: DependencyResolutionStrategy::TopologicalSequential,
                    partial_failure_mode: PartialFailureMode::PauseDownstreamOnly,
                    reentry_point: ReentryPoint::FailedStep,
                    mid_routine_connector_failure: MidRoutineConnectorFailureMode::SurfaceAndPause,
                },
                connector_resolution: RoutineConnectorResolution {
                    states: vec!["unresolved".to_string(), "bound".to_string()],
                    binding_options: vec!["mcp_server".to_string(), "native_feature".to_string()],
                },
                data_scope: DataScope {
                    readable_paths: vec!["mission.goal".to_string()],
                    writable_paths: vec![
                        "knowledge/workflows/drafts/founder_brief_daily/**".to_string()
                    ],
                    denied_paths: vec!["credentials/**".to_string()],
                    cross_routine_visibility: CrossRoutineVisibility::DeclaredOutputsOnly,
                    mission_context_scope: MissionContextScope::GoalAndDependencies,
                    mission_context_justification: None,
                },
                audit_scope: AuditScope {
                    run_history_visibility: RunHistoryVisibility::PlanOwner,
                    named_audit_roles: Vec::new(),
                    intermediate_artifact_visibility: IntermediateArtifactVisibility::RoutineOnly,
                    final_artifact_visibility: FinalArtifactVisibility::DeclaredConsumers,
                },
                success_criteria: SuccessCriteria {
                    required_artifacts: vec!["founder_brief.md".to_string()],
                    minimum_viable_completion: None,
                    minimum_output: Some("one usable brief draft".to_string()),
                    freshness_window_hours: Some(24),
                },
                steps: vec![StepPackage {
                    step_id: "draft_brief".to_string(),
                    label: "Draft brief".to_string(),
                    kind: "reporting".to_string(),
                    action: "synthesize the daily findings into a founder brief".to_string(),
                    inputs: vec!["clustered_themes".to_string()],
                    outputs: vec!["founder_brief_draft".to_string()],
                    dependencies: vec!["market_pain_daily".to_string()],
                    context_reads: vec![
                        "ctx:founder_brief_daily:mission.goal".to_string(),
                        "ctx:founder_brief_daily:workspace.environment".to_string(),
                    ],
                    context_writes: vec![
                        "ctx:founder_brief_daily:draft_brief:founder_brief_draft.md".to_string(),
                    ],
                    connector_requirements: vec![ConnectorRequirement {
                        capability: "gmail".to_string(),
                        required: true,
                    }],
                    model_policy: StepModelPolicy {
                        primary: Some(StepModelSelection {
                            tier: ModelTier::Mid,
                        }),
                    },
                    approval_policy: ApprovalMode::DraftOnly,
                    success_criteria: SuccessCriteria {
                        minimum_output: Some("one usable brief draft".to_string()),
                        ..SuccessCriteria::default()
                    },
                    failure_policy: StepFailurePolicy {
                        on_model_failure: Some("retry_once_then_pause".to_string()),
                        ..StepFailurePolicy::default()
                    },
                    retry_policy: StepRetryPolicy {
                        max_attempts: Some(2),
                    },
                    artifacts: vec!["founder_brief_draft.md".to_string()],
                    provenance: Some(StepProvenance {
                        plan_id: Some("plan_0f6e8c".to_string()),
                        routine_id: Some("founder_brief_daily".to_string()),
                        step_id: Some("draft_brief".to_string()),
                        cost_provenance: None,
                    }),
                    notes: None,
                }],
            }],
            connector_intents: vec![ConnectorIntent {
                capability: "gmail".to_string(),
                why: "Deliver founder brief".to_string(),
                required: true,
                degraded_mode_allowed: false,
            }],
            connector_bindings: vec![ConnectorBinding {
                capability: "gmail".to_string(),
                binding_type: "oauth_integration".to_string(),
                binding_id: "gmail-prod".to_string(),
                allowlist_pattern: Some("gmail.send".to_string()),
                status: "mapped".to_string(),
            }],
            connector_binding_resolution: None,
            model_routing_resolution: None,
            credential_envelopes: vec![CredentialEnvelope {
                routine_id: "founder_brief_daily".to_string(),
                entitled_connectors: vec![CredentialBindingRef {
                    capability: "gmail".to_string(),
                    binding_id: "gmail-prod".to_string(),
                }],
                denied_connectors: Vec::new(),
                envelope_issued_at: None,
                envelope_expires_at: None,
                issuing_authority: Some("engine".to_string()),
            }],
            context_objects: vec![ContextObject {
                context_object_id: "ctx:founder_brief_daily:draft_brief:founder_brief_draft.md"
                    .to_string(),
                name: "Draft brief handoff".to_string(),
                kind: "step_output_handoff".to_string(),
                scope: ContextObjectScope::Handoff,
                owner_routine_id: "founder_brief_daily".to_string(),
                producer_step_id: Some("draft_brief".to_string()),
                declared_consumers: vec!["founder_brief_daily".to_string()],
                artifact_ref: Some("founder_brief_draft.md".to_string()),
                data_scope_refs: vec![
                    "knowledge/workflows/drafts/founder_brief_daily/**".to_string()
                ],
                freshness_window_hours: Some(24),
                validation_status: ContextValidationStatus::Pending,
                provenance: ContextObjectProvenance {
                    plan_id: "plan_0f6e8c".to_string(),
                    routine_id: "founder_brief_daily".to_string(),
                    step_id: Some("draft_brief".to_string()),
                },
                summary: Some("one usable brief draft".to_string()),
            }],
            metadata: Some(serde_json::json!({
                "source": "preview",
                "schema_version": 1
            })),
        };

        let json = serde_json::to_value(&package).expect("serialize plan package");
        let roundtrip: PlanPackage =
            serde_json::from_value(json).expect("deserialize plan package");

        assert_eq!(roundtrip, package);
        assert_eq!(roundtrip.routine_graph.len(), 1);
        assert_eq!(roundtrip.routine_graph[0].steps.len(), 1);
    }

    #[test]
    fn compile_workflow_plan_preview_package_projects_workflow_plan_json() {
        let plan = crate::contracts::WorkflowPlanJson {
            plan_id: "plan_preview".to_string(),
            planner_version: "v1".to_string(),
            plan_source: "test".to_string(),
            original_prompt: "Research the market and draft a summary.".to_string(),
            normalized_prompt: "research the market and draft a summary".to_string(),
            confidence: "medium".to_string(),
            title: "Research market".to_string(),
            description: Some("Preview plan".to_string()),
            schedule: crate::contracts::default_fallback_schedule_json(),
            execution_target: "automation_v2".to_string(),
            workspace_root: "/repo".to_string(),
            steps: vec![WorkflowPlanStep {
                step_id: "research_sources".to_string(),
                kind: "research".to_string(),
                objective: "Collect source material".to_string(),
                depends_on: Vec::new(),
                agent_role: "worker".to_string(),
                input_refs: vec![json!({"from_step_id":"seed","alias":"seed_input"})],
                output_contract: Some(json!({
                    "kind": "brief",
                    "enforcement": {
                        "required_tools": ["websearch"]
                    }
                })),
                metadata: Some(json!({
                    "builder": {
                        "web_research_expected": true
                    }
                })),
            }],
            requires_integrations: vec!["gmail".to_string()],
            allowed_mcp_servers: vec!["github".to_string()],
            operator_preferences: None,
            save_options: json!({"can_export_pack": true}),
        };

        let package = compile_workflow_plan_preview_package(&plan, Some("user"));

        assert_eq!(package.plan_id, "plan_preview");
        assert_eq!(package.lifecycle_state, PlanLifecycleState::Preview);
        assert_eq!(package.owner.owner_id, "user");
        assert_eq!(package.routine_graph.len(), 1);
        assert_eq!(package.routine_graph[0].steps.len(), 1);
        assert_eq!(
            package.routine_graph[0].steps[0].context_reads,
            vec![
                "ctx:plan_preview_routine:mission.goal".to_string(),
                "ctx:plan_preview_routine:workspace.environment".to_string(),
            ]
        );
        assert_eq!(
            package.routine_graph[0].steps[0].context_writes,
            vec!["ctx:plan_preview_routine:research_sources:research_sources.artifact".to_string()]
        );
        assert_eq!(
            package.routine_graph[0].steps[0].connector_requirements[0].capability,
            "websearch"
        );
        assert_eq!(package.connector_intents[0].capability, "gmail");
        assert_eq!(
            package.connector_binding_resolution.as_ref().map(|report| (
                report.mapped_count,
                report.unresolved_required_count,
                report.entries.len()
            )),
            Some((0, 1, 1))
        );
        assert_eq!(
            package
                .output_roots
                .as_ref()
                .and_then(|roots| roots.plan.as_deref()),
            Some("/repo/knowledge/workflows/plan/")
        );
        assert_eq!(
            package.routine_graph[0].trigger.trigger_type,
            TriggerKind::Manual
        );
        assert!(package.routine_graph[0].trigger.schedule.is_none());
        assert!(package.routine_graph[0].trigger.timezone.is_none());
        assert_eq!(
            package
                .budget_policy
                .as_ref()
                .and_then(|policy| policy.max_cost_per_run_usd),
            Some(4.0)
        );
        assert_eq!(
            package
                .budget_enforcement
                .as_ref()
                .and_then(|enforcement| enforcement.soft_warning_threshold),
            Some(0.8)
        );
        assert_eq!(
            package
                .overlap_policy
                .as_ref()
                .and_then(|policy| policy.exact_identity.as_ref())
                .and_then(|identity| identity.hash_version),
            Some(1)
        );
        assert_eq!(
            package
                .overlap_policy
                .as_ref()
                .and_then(|policy| policy.semantic_identity.as_ref())
                .and_then(|identity| identity.similarity_threshold),
            Some(0.85)
        );
        assert_eq!(package.credential_envelopes.len(), 1);
        assert_eq!(
            package.credential_envelopes[0].routine_id,
            package.routine_graph[0].routine_id
        );
        assert!(package.precedence_log.is_empty());
        assert!(package.plan_diff.is_none());
        assert!(package.manual_trigger_record.is_none());
        assert_eq!(package.credential_envelopes[0].entitled_connectors.len(), 0);
        assert_eq!(package.context_objects.len(), 3);
        assert_eq!(package.context_objects[0].kind, "mission_goal");
        assert_eq!(package.context_objects[1].kind, "workspace_environment");
        assert_eq!(
            package.context_objects[2].producer_step_id.as_deref(),
            Some("research_sources")
        );
        assert_eq!(
            package.routine_graph[0].data_scope.writable_paths[0],
            "/repo/knowledge/workflows/plan/plan_preview_routine/**"
        );
        assert_eq!(
            package
                .validation_state
                .as_ref()
                .and_then(|state| state.credential_envelopes_valid),
            Some(true)
        );
    }

    #[test]
    fn with_manual_trigger_record_captures_plan_snapshots() {
        let plan = compile_workflow_plan_preview_package(
            &crate::contracts::WorkflowPlanJson {
                plan_id: "plan_manual_trigger".to_string(),
                planner_version: "v1".to_string(),
                plan_source: "test".to_string(),
                original_prompt: "Draft a brief".to_string(),
                normalized_prompt: "draft a brief".to_string(),
                confidence: "medium".to_string(),
                title: "Draft a brief".to_string(),
                description: Some("Preview plan".to_string()),
                schedule: crate::contracts::default_fallback_schedule_json(),
                execution_target: "automation_v2".to_string(),
                workspace_root: "/repo".to_string(),
                steps: vec![WorkflowPlanStep {
                    step_id: "draft_brief".to_string(),
                    kind: "analysis".to_string(),
                    objective: "Draft a brief".to_string(),
                    depends_on: Vec::new(),
                    agent_role: "writer".to_string(),
                    input_refs: Vec::new(),
                    output_contract: Some(json!({"kind": "report_markdown"})),
                    metadata: None,
                }],
                requires_integrations: vec!["github".to_string()],
                allowed_mcp_servers: vec!["github".to_string()],
                operator_preferences: None,
                save_options: json!({}),
            },
            Some("control-panel"),
        );
        let updated = with_manual_trigger_record(
            &plan,
            "manual-trigger-run_123",
            "control-panel",
            ManualTriggerSource::Api,
            true,
            "2026-03-28T10:15:00Z",
            Some("run_123"),
            Some("queued"),
            vec!["artifact.md".to_string()],
            Some("Triggered from test"),
        )
        .expect("manual trigger record");

        let record = updated
            .manual_trigger_record
            .as_ref()
            .expect("manual trigger record");
        assert_eq!(record.trigger_id, "manual-trigger-run_123");
        assert_eq!(record.plan_id, updated.plan_id);
        assert_eq!(record.plan_revision, updated.plan_revision);
        assert_eq!(record.routine_id, updated.routine_graph[0].routine_id);
        assert_eq!(record.triggered_by, "control-panel");
        assert_eq!(record.trigger_source, ManualTriggerSource::Api);
        assert!(record.dry_run);
        assert_eq!(record.run_id.as_deref(), Some("run_123"));
        assert_eq!(record.outcome.as_deref(), Some("queued"));
        assert_eq!(record.artifacts_produced, vec!["artifact.md".to_string()]);
        assert_eq!(record.notes.as_deref(), Some("Triggered from test"));
        assert_eq!(
            record.connector_binding_snapshot,
            updated.connector_bindings
        );
        assert_eq!(record.approval_policy_snapshot, updated.approval_policy);
    }

    #[test]
    fn lifecycle_transition_table_matches_spec() {
        assert!(can_transition_plan_lifecycle(
            PlanLifecycleState::Preview,
            PlanLifecycleState::AwaitingApproval
        ));
        assert!(can_transition_plan_lifecycle(
            PlanLifecycleState::Approved,
            PlanLifecycleState::Applied
        ));
        assert!(can_transition_plan_lifecycle(
            PlanLifecycleState::Archived,
            PlanLifecycleState::Draft
        ));
        assert!(!can_transition_plan_lifecycle(
            PlanLifecycleState::Draft,
            PlanLifecycleState::Active
        ));
        assert!(!can_transition_plan_lifecycle(
            PlanLifecycleState::Archived,
            PlanLifecycleState::Applied
        ));
    }
}