Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions crates/forge-schemas/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ pub mod canonical;
pub mod experiment;
pub mod handoff;
pub mod ledger;
pub mod persona;
pub mod plan;
pub mod telemetry;
pub mod verification;
pub mod workflow;
Expand Down
157 changes: 157 additions & 0 deletions crates/forge-schemas/src/persona.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
//! Persona de squad como **conteúdo** (`persona.v1`, Fase 2 BuildToValue).
//!
//! Uma persona é um item de galeria: identidade + modelos mentais (referências
//! canônicas da área — "Clean Architecture" para um arquiteto, "Bluebook" para
//! um paralegal), princípios, escada de autonomia (DESCRITIVA — rótulo
//! consultável, não um loop automático; ADR 0021), gatilhos de ativação e
//! contratos de comunicação (handoff). "Squads são conteúdo, não código": o
//! admin publica uma persona e ela aparece na galeria — nenhuma recompilação.
//!
//! O contrato canônico vive em `schemas/json/persona.v1.schema.json`; este tipo
//! deve permanecer compatível (teste em `tests/schema_fixtures.rs`).

use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

/// Nível de autonomia DESCRITIVO (rótulo). Não dispara promoção/rebaixamento
/// automático — é metadado que o humano consulta/sobrepõe (ADR 0021).
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub enum AutonomyLabel {
L1,
L2,
L3,
L4,
L5,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct MentalModel {
/// Referência canônica da área (livro/autor/norma).
pub reference: String,
/// Quando aplicar essa referência.
pub apply_when: String,
}

/// Severidade de um princípio quando violado.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum PrincipleSeverity {
Low,
Medium,
High,
Critical,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct CorePrinciple {
pub id: String,
pub description: String,
/// Como validar (ex.: "manual", "static_analysis", "code_review").
pub validation: String,
pub severity: PrincipleSeverity,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct Autonomy {
pub level: AutonomyLabel,
#[serde(default)]
pub can_decide_alone: Vec<String>,
#[serde(default)]
pub requires_approval: Vec<String>,
/// Se o papel pode vetar uma entrega (ex.: um Auditor/Segurança).
pub can_veto: bool,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct ActivationTriggers {
#[serde(default)]
pub semantic_patterns: Vec<String>,
#[serde(default)]
pub context_keywords: Vec<String>,
/// Limiar de confiança [0,1] para a persona "acender" numa tarefa.
pub confidence_threshold: f64,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct Communication {
#[serde(default)]
pub receives_from: Vec<String>,
#[serde(default)]
pub delivers_to: Vec<String>,
/// Descrição do contrato de handoff (o que entrega e com que critério).
pub handoff_contract: String,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct Persona {
pub id: String,
pub display_name: String,
/// Domínio/profissão (ex.: "editorial", "juridico", "musica", "software").
pub domain: String,
#[serde(default)]
pub mental_models: Vec<MentalModel>,
#[serde(default)]
pub core_principles: Vec<CorePrinciple>,
pub autonomy: Autonomy,
pub activation_triggers: ActivationTriggers,
pub communication: Communication,
/// Formatos exportáveis que esta persona entrega (DOCX, MusicXML, PDF…).
#[serde(default)]
pub delivery_formats: Vec<String>,
}

impl Persona {
/// Checagem semântica além do schema: o limiar de confiança tem que estar
/// em [0,1] (um limiar fora disso nunca acenderia ou sempre acenderia — um
/// erro silencioso de galeria). Erro claro em vez de persona quebrada.
pub fn validate(&self) -> Result<(), String> {
let t = self.activation_triggers.confidence_threshold;
if !(0.0..=1.0).contains(&t) {
return Err(format!(
"confidence_threshold fora de [0,1]: {t} (persona {})",
self.id
));
}
Ok(())
}
}

#[cfg(test)]
mod tests {
use super::*;

fn persona_min() -> Persona {
Persona {
id: "revisor-de-estilo".into(),
display_name: "Revisor de estilo".into(),
domain: "editorial".into(),
mental_models: vec![],
core_principles: vec![],
autonomy: Autonomy {
level: AutonomyLabel::L3,
can_decide_alone: vec![],
requires_approval: vec![],
can_veto: false,
},
activation_triggers: ActivationTriggers {
semantic_patterns: vec![],
context_keywords: vec![],
confidence_threshold: 0.6,
},
communication: Communication {
receives_from: vec![],
delivers_to: vec![],
handoff_contract: "artigo.md + notas".into(),
},
delivery_formats: vec![],
}
}

#[test]
fn valida_limiar_de_confianca() {
assert!(persona_min().validate().is_ok());
let mut p = persona_min();
p.activation_triggers.confidence_threshold = 1.5;
assert!(p.validate().is_err());
}
}
158 changes: 158 additions & 0 deletions crates/forge-schemas/src/plan.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
//! Manifesto de plano/entrega da esteira (`plan.v1`, Fase 2 BuildToValue).
//!
//! O plano é o "ticket de trabalho" declarativo que a esteira executa: fases →
//! entregas (arquivos exportáveis: DOCX/XLSX/PDF/MusicXML) → quality gates por
//! fase → critérios de sucesso → orçamento → rollback. É a ponte entre o grafo
//! de squad (`squad.workflow.v1`, a fiação) e os artefatos exportáveis (o
//! produto). Contrato canônico em `schemas/json/plan.v1.schema.json`.

use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema)]
pub struct Prerequisites {
#[serde(default)]
pub contracts: Vec<String>,
#[serde(default)]
pub approvals: Vec<String>,
#[serde(default)]
pub dependencies: Vec<String>,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct PlanPhase {
/// Ordem na esteira (1-based, única e sequencial — ver `Plan::validate`).
pub order: u32,
/// Papel/persona responsável pela fase.
pub primary_role: String,
#[serde(default)]
pub support_roles: Vec<String>,
/// Artefatos exportáveis produzidos nesta fase (ex.: "pauta.md",
/// "partitura.musicxml").
#[serde(default)]
pub deliverables: Vec<String>,
/// Se a fase abre um gate humano (o membro humano aprova antes de seguir).
pub approval_required: bool,
/// Confiança estimada [0,1] da fase.
pub estimated_confidence: f64,
/// Gates de qualidade da fase (ex.: "revisao:pass", "test-coverage:85%").
#[serde(default)]
pub quality_gates: Vec<String>,
}

#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema)]
pub struct SuccessCriteria {
#[serde(default)]
pub functional: Vec<String>,
#[serde(default)]
pub non_functional: Vec<String>,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct Budget {
/// Custo estimado (na moeda/telemetria da plataforma).
pub estimated_cost: f64,
/// Teto de chamadas de LLM para o plano.
pub max_llm_calls: u32,
}

#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema)]
pub struct RollbackStrategy {
/// Se um kill-switch aborta a esteira (Fase 3 amarra ao `operational_status`).
pub kill_switch: bool,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct Plan {
#[serde(default)]
pub prerequisites: Prerequisites,
pub execution_sequence: Vec<PlanPhase>,
#[serde(default)]
pub success_criteria: SuccessCriteria,
pub budget: Budget,
#[serde(default)]
pub rollback_strategy: RollbackStrategy,
}

impl Plan {
/// Checagens semânticas além do schema:
/// - a sequência não é vazia (um plano sem fases não produz entrega);
/// - `order` é 1..=N única e sequencial (esteira sem buracos/duplicatas);
/// - cada `estimated_confidence` está em [0,1].
///
/// Erro claro (um 422) em vez de um plano silenciosamente quebrado.
pub fn validate(&self) -> Result<(), String> {
if self.execution_sequence.is_empty() {
return Err("execution_sequence vazia — um plano precisa de ao menos uma fase".into());
}
let mut orders: Vec<u32> = self.execution_sequence.iter().map(|p| p.order).collect();
orders.sort_unstable();
for (i, order) in orders.iter().enumerate() {
let expected = (i as u32) + 1;
if *order != expected {
return Err(format!(
"orders da esteira devem ser 1..={} únicas e sequenciais; achei {order} onde esperava {expected}",
self.execution_sequence.len()
));
}
}
for phase in &self.execution_sequence {
if !(0.0..=1.0).contains(&phase.estimated_confidence) {
return Err(format!(
"estimated_confidence fora de [0,1] na fase {}: {}",
phase.order, phase.estimated_confidence
));
}
}
Ok(())
}
}

#[cfg(test)]
mod tests {
use super::*;

fn phase(order: u32) -> PlanPhase {
PlanPhase {
order,
primary_role: "pauteiro".into(),
support_roles: vec![],
deliverables: vec!["pauta.md".into()],
approval_required: true,
estimated_confidence: 0.8,
quality_gates: vec!["revisao:pass".into()],
}
}

fn plan_of(orders: &[u32]) -> Plan {
Plan {
prerequisites: Prerequisites::default(),
execution_sequence: orders.iter().map(|o| phase(*o)).collect(),
success_criteria: SuccessCriteria::default(),
budget: Budget {
estimated_cost: 0.0,
max_llm_calls: 20,
},
rollback_strategy: RollbackStrategy { kill_switch: true },
}
}

#[test]
fn aceita_esteira_sequencial() {
assert!(plan_of(&[1, 2, 3]).validate().is_ok());
}

#[test]
fn rejeita_esteira_vazia_e_com_buraco() {
assert!(plan_of(&[]).validate().is_err());
assert!(plan_of(&[1, 3]).validate().is_err());
assert!(plan_of(&[1, 1]).validate().is_err());
}

#[test]
fn rejeita_confianca_fora_de_intervalo() {
let mut p = plan_of(&[1]);
p.execution_sequence[0].estimated_confidence = 2.0;
assert!(p.validate().is_err());
}
}
Loading
Loading