pub struct LogLine {
pub who: String,
pub did: Verb,
pub this: Payload,
pub when: u64,
pub confirmed_by: Option<String>,
pub if_ok: Outcome,
pub if_doubt: Escalation,
pub if_not: FailureHandling,
pub status: Status,
}Expand description
O “átomo” LogLine — 9-field tuple rígido.
Representa uma ação verificável com lifecycle determinístico e invariants obrigatórios.
Conforme Paper I §3, cada LogLine deve ter exatamente 9 campos e seguir o lifecycle
DRAFT → PENDING → COMMITTED | GHOST.
§Exemplo
use logline_core::*;
let line = LogLine::builder()
.who("did:ubl:alice")
.did(Verb::Approve)
.this(Payload::Text("purchase:123".into()))
.when(1_735_671_234)
.if_ok(Outcome { label: "approved".into(), effects: vec!["emit_receipt".into()] })
.if_doubt(Escalation { label: "manual_review".into(), route_to: "auditor".into() })
.if_not(FailureHandling { label: "rejected".into(), action: "notify".into() })
.build_draft()?;Fields§
§who: StringIdentidade do agente que executa a ação (DID futuro, ex: did:ubl:...).
did: VerbVerbo canônico ou custom (Paper I: validar contra ALLOWED_ACTIONS via VerbRegistry).
this: PayloadCarga útil mínima/typed (Paper I: JSON estrito validado por schema do verbo).
when: u64Unix timestamp em nanosegundos (interno). Na serialização canônica JSON✯Atomic será ISO8601.
confirmed_by: Option<String>Identidade que confirma a ação. Paper I: obrigatório para ações de Risk Level 3+ (L3+).
if_ok: OutcomeConsequência positiva obrigatória (Paper I: invariante obrigatório).
if_doubt: EscalationVia de dúvida obrigatória (Paper I: invariante obrigatório).
if_not: FailureHandlingFallback/erro obrigatório (Paper I: invariante obrigatório).
status: StatusEstado do lifecycle rígido: DRAFT → PENDING → COMMITTED | GHOST.
Implementations§
Source§impl LogLine
impl LogLine
Sourcepub fn builder() -> LogLineBuilder
pub fn builder() -> LogLineBuilder
Cria um novo builder para construir um LogLine passo a passo.
§Exemplo
use logline_core::*;
let builder = LogLine::builder()
.who("did:ubl:alice")
.did(Verb::Transfer);Sourcepub fn verify_invariants(&self) -> Result<(), LogLineError>
pub fn verify_invariants(&self) -> Result<(), LogLineError>
Verifica invariants do 9-tuple (Paper I §3).
Valida que todos os campos obrigatórios estão presentes e não vazios:
whonão pode ser vaziowhendeve ser > 0if_ok,if_doubt,if_notdevem estar presentes e não vazios
§Erros
Retorna LogLineError::MissingField ou LogLineError::MissingInvariant
se algum invariant não for satisfeito.
§Exemplo
use logline_core::*;
let line = LogLine::builder()
.who("did:ubl:alice")
.did(Verb::Approve)
.when(1_735_671_234)
.if_ok(Outcome { label: "ok".into(), effects: vec![] })
.if_doubt(Escalation { label: "doubt".into(), route_to: "auditor".into() })
.if_not(FailureHandling { label: "not".into(), action: "notify".into() })
.build_draft()?;
assert!(line.verify_invariants().is_ok());Sourcepub fn sign(self, signer: &dyn Signer) -> Result<Self, LogLineError>
pub fn sign(self, signer: &dyn Signer) -> Result<Self, LogLineError>
Assina o LogLine (DRAFT ou PENDING). Paper I: “nada acontece sem estar assinado”.
A assinatura é calculada sobre os bytes determinísticos retornados por
to_signable_bytes(). Retorna self para method chaining.
§Erros
LogLineError::InvalidTransitionse o status não forDraftouPendingLogLineError::Signingse a assinatura falhar
§Exemplo
use logline_core::*;
struct NoopSigner;
impl Signer for NoopSigner {
fn sign(&self, _msg: &[u8]) -> Result<Signature, SignError> {
Ok(Signature { alg: "none".into(), bytes: vec![] })
}
}
let signer = NoopSigner;
let line = LogLine::builder()
.who("did:ubl:alice")
.did(Verb::Approve)
.when(1_735_671_234)
.if_ok(Outcome { label: "ok".into(), effects: vec![] })
.if_doubt(Escalation { label: "doubt".into(), route_to: "auditor".into() })
.if_not(FailureHandling { label: "not".into(), action: "notify".into() })
.build_draft()?
.sign(&signer)?;Sourcepub fn freeze(self) -> Result<Self, LogLineError>
pub fn freeze(self) -> Result<Self, LogLineError>
Congela o DRAFT em PENDING (pronto para sign/commit/ghost).
Valida os invariants antes de fazer a transição. Após freeze(), o LogLine
está pronto para ser assinado e commitado, ou abandonado como Ghost.
§Erros
LogLineError::InvalidTransitionse o status não forDraftLogLineError::MissingFieldouLogLineError::MissingInvariantse os invariants falharem
§Exemplo
use logline_core::*;
let line = LogLine::builder()
.who("did:ubl:alice")
.did(Verb::Approve)
.when(1_735_671_234)
.if_ok(Outcome { label: "ok".into(), effects: vec![] })
.if_doubt(Escalation { label: "doubt".into(), route_to: "auditor".into() })
.if_not(FailureHandling { label: "not".into(), action: "notify".into() })
.build_draft()?
.freeze()?;
assert_eq!(line.status, Status::Pending);Sourcepub fn freeze_with_registry(
self,
registry: &dyn VerbRegistry,
) -> Result<Self, LogLineError>
pub fn freeze_with_registry( self, registry: &dyn VerbRegistry, ) -> Result<Self, LogLineError>
Congela com validação de verbo contra ALLOWED_ACTIONS (Paper I: verbo deve estar no registro).
Equivalente a freeze(), mas valida primeiro se o verbo (did) está permitido
no sistema através do VerbRegistry. Útil para implementar políticas de segurança
onde apenas verbos específicos são permitidos.
§Erros
LogLineError::MissingField("did (unknown verb)")se o verbo não estiver no registro- Erros de
freeze()se os invariants falharem
§Exemplo
use logline_core::*;
struct SimpleRegistry;
impl VerbRegistry for SimpleRegistry {
fn is_allowed(&self, verb: &Verb) -> bool {
matches!(verb, Verb::Transfer | Verb::Approve)
}
}
let registry = SimpleRegistry;
let line = LogLine::builder()
.who("did:ubl:alice")
.did(Verb::Approve)
.when(1_735_671_234)
.if_ok(Outcome { label: "ok".into(), effects: vec![] })
.if_doubt(Escalation { label: "doubt".into(), route_to: "auditor".into() })
.if_not(FailureHandling { label: "not".into(), action: "notify".into() })
.build_draft()?
.freeze_with_registry(®istry)?;Sourcepub fn commit(self, signer: &dyn Signer) -> Result<Self, LogLineError>
pub fn commit(self, signer: &dyn Signer) -> Result<Self, LogLineError>
Commit final (PENDING → COMMITTED). Paper I: requer assinatura obrigatória.
Transiciona o LogLine de Pending para Committed, assinando os bytes determinísticos.
Uma vez Committed, o LogLine não pode mais ser modificado ou abandonado.
§Erros
LogLineError::InvalidTransitionse o status não forPendingLogLineError::Signingse a assinatura falhar
§Exemplo
use logline_core::*;
struct NoopSigner;
impl Signer for NoopSigner {
fn sign(&self, _msg: &[u8]) -> Result<Signature, SignError> {
Ok(Signature { alg: "none".into(), bytes: vec![] })
}
}
let signer = NoopSigner;
let line = LogLine::builder()
.who("did:ubl:alice")
.did(Verb::Approve)
.when(1_735_671_234)
.if_ok(Outcome { label: "ok".into(), effects: vec![] })
.if_doubt(Escalation { label: "doubt".into(), route_to: "auditor".into() })
.if_not(FailureHandling { label: "not".into(), action: "notify".into() })
.build_draft()?
.freeze()?
.commit(&signer)?;
assert_eq!(line.status, Status::Committed);Sourcepub fn abandon(
self,
reason: Option<String>,
) -> Result<GhostRecord, LogLineError>
pub fn abandon( self, reason: Option<String>, ) -> Result<GhostRecord, LogLineError>
Abandona intenção: DRAFT/PENDING → GHOST (forensics).
Versão sem assinatura (compatibilidade). Para versão assinada, use abandon_signed().
Cria um GhostRecord que preserva o LogLine original para análise forense.
§Erros
LogLineError::AlreadyCommittedse o LogLine já estiverCommitted
§Exemplo
use logline_core::*;
let line = LogLine::builder()
.who("did:ubl:alice")
.did(Verb::Deploy)
.when(1_735_671_234)
.if_ok(Outcome { label: "ok".into(), effects: vec![] })
.if_doubt(Escalation { label: "doubt".into(), route_to: "qa".into() })
.if_not(FailureHandling { label: "not".into(), action: "rollback".into() })
.build_draft()?;
let ghost = line.abandon(Some("user_cancelled".into()))?;
assert_eq!(ghost.status, Status::Ghost);Sourcepub fn abandon_signed(
self,
signer: &dyn Signer,
reason: Option<String>,
) -> Result<GhostRecord, LogLineError>
pub fn abandon_signed( self, signer: &dyn Signer, reason: Option<String>, ) -> Result<GhostRecord, LogLineError>
Abandona intenção assinada: DRAFT/PENDING → GHOST (forensics).
Paper I: attempt já nasce assinado, então o abandon também deve ser assinado. Versão recomendada que assina o LogLine antes de criar o GhostRecord.
§Erros
LogLineError::AlreadyCommittedse o LogLine já estiverCommittedLogLineError::Signingse a assinatura falhar
§Exemplo
use logline_core::*;
struct NoopSigner;
impl Signer for NoopSigner {
fn sign(&self, _msg: &[u8]) -> Result<Signature, SignError> {
Ok(Signature { alg: "none".into(), bytes: vec![] })
}
}
let signer = NoopSigner;
let line = LogLine::builder()
.who("did:ubl:alice")
.did(Verb::Deploy)
.when(1_735_671_234)
.if_ok(Outcome { label: "ok".into(), effects: vec![] })
.if_doubt(Escalation { label: "doubt".into(), route_to: "qa".into() })
.if_not(FailureHandling { label: "not".into(), action: "rollback".into() })
.build_draft()?;
let ghost = line.abandon_signed(&signer, Some("timeout".into()))?;
assert_eq!(ghost.status, Status::Ghost);Sourcepub fn to_signable_bytes(&self) -> Vec<u8> ⓘ
pub fn to_signable_bytes(&self) -> Vec<u8> ⓘ
Bytes determinísticos “suficientes” para v0.1 (placeholder).
Gera uma representação determinística dos campos principais do LogLine
para assinatura. Em versões futuras, isso será substituído por bytes canônicos
JSON✯Atomic (via json_atomic).
Formato atual: who|verb|when|status|confirmed_by|this.kind
§Exemplo
use logline_core::*;
let line = LogLine::builder()
.who("did:ubl:alice")
.did(Verb::Approve)
.when(1_735_671_234)
.if_ok(Outcome { label: "ok".into(), effects: vec![] })
.if_doubt(Escalation { label: "doubt".into(), route_to: "auditor".into() })
.if_not(FailureHandling { label: "not".into(), action: "notify".into() })
.build_draft()?;
let bytes = line.to_signable_bytes();
assert!(!bytes.is_empty());