Paper I — The LogLine Protocol
The Atomic Unit of Verifiable Action
Normative keywords per RFC 2119/8174 (MUST/SHOULD/MAY) apply.
The Story
March 2024. A major bank. $2.3 million gone in 47 minutes.
The forensic team spent six weeks reconstructing what happened. The logs showed API calls. The logs showed timestamps. The logs showed nothing useful about why any of it was authorized.
The attacker had compromised an AI assistant. The assistant had legitimate access. Every request looked normal—individually. The pattern that would have revealed the attack was invisible because the logs recorded what happened, not what was intended.
The attacker left no fingerprints because there was nowhere to leave them.
Now imagine a different architecture.
Before the first transfer, the AI assistant would have been required to sign this:
{
"who": "did:logline:agent:assistant-7x3k",
"did": "transfer",
"this": {
"from": "account:operating",
"to": "account:external:9f8a2c",
"amount": 47500,
"currency": "USD"
},
"when": "2024-03-14T14:23:07.847Z",
"confirmed_by": null,
"if_ok": "emit:transfer.completed",
"if_doubt": "escalate:treasury.human",
"if_not": "emit:transfer.denied",
"status": "pending"
}
This LogLine would hit the policy engine. The policy would check:
- Agent trajectory score: 0.23 (new agent, low trust)
- Transfer limit at this trajectory: $5,000
- Requested amount: $47,500
Decision: REQUIRE — human confirmation needed.
The LogLine becomes a Ghost. It persists. Signed. Timestamped. Evidence that the attack was attempted.
One Ghost is an anomaly. Forty-seven Ghosts in 47 minutes from the same agent? That's an alarm.
The attack fails because every attempt is a confession.
This is the LogLine Protocol.
I. The Inversion
Since 1945, every computing system has followed the same axiom:
Code runs → Log writes
Execution precedes registration. This gap between action and evidence is the root vulnerability of computation. In this gap:
- Logs are forged
- Logs are deleted
- Logs prove nothing about authorization
- Logs provide no cryptographic binding
The LogLine Protocol inverts this relationship.
Log writes → Code runs
Nothing happens unless it is first structured, signed, and committed as a LogLine.
The log is not a record of execution.
The log is the prerequisite for execution.
// logline-core/src/runtime.rs
// This is real code. Install it: cargo install logline-cli
use logline_core::{LogLine, Ledger, PolicyEngine, Decision};
pub struct Runtime {
ledger: Ledger,
policy: PolicyEngine,
}
impl Runtime {
/// Execute an intent. The order is non-negotiable:
/// 1. Create LogLine
/// 2. Evaluate policy
/// 3. Commit to ledger
/// 4. THEN (and only then) execute
pub fn execute(&mut self, intent: Intent) -> Result {
// Step 1: Create the LogLine (the intent becomes structured)
let logline = LogLine::from_intent(&intent)?;
// Step 2: Evaluate policy BEFORE any execution
let decision = self.policy.evaluate(&logline)?;
// Step 3: Commit to ledger (this happens regardless of decision)
let committed = self.ledger.append(logline, &decision)?;
// Step 4: Execute only if ALLOW
match decision {
Decision::Allow => {
let effect = self.execute_effect(&committed)?;
Ok(Receipt::new(committed, effect))
}
Decision::Require { signers } => {
// LogLine persists, waiting for consent
Err(ExecutionError::ConsentRequired {
logline_cid: committed.cid(),
required_signers: signers,
})
}
Decision::Deny { reason } => {
// LogLine persists as Ghost
Err(ExecutionError::Denied {
ghost_cid: committed.cid(),
reason,
})
}
}
}
}
The critical insight: the ledger append happens before the decision branch. Whether allowed or denied, the intent is recorded. The execution happens only after.
II. The 9-Field Tuple
Every action in a LogLine system is preceded by this structure:
┌─────────────────────────────────────────────────────────────────┐
│ THE LOGLINE TUPLE │
├─────────────────────────────────────────────────────────────────┤
│ │
│ who The actor │
│ DID (did:method:id), Ed25519-bound │
│ │
│ did The verb │
│ Canonical action from ALLOWED_ACTIONS registry │
│ │
│ this The payload │
│ Typed JSON, validated against verb schema │
│ │
│ when The timestamp │
│ ISO8601 UTC, nanosecond precision │
│ │
│ confirmed_by The consent │
│ DID of approver (required for L3+ actions) │
│ │
│ if_ok Success commitment │
│ What happens when the action succeeds │
│ │
│ if_doubt Uncertainty protocol │
│ What happens on timeout or ambiguity │
│ │
│ if_not Failure commitment │
│ What happens when the action fails │
│ │
│ status Lifecycle state │
│ DRAFT → PENDING → COMMITTED | GHOST │
│ │
└─────────────────────────────────────────────────────────────────┘
This structure is non-negotiable. Its rigidity is its security.
The Pact
These nine fields are not just a data structure. They are a contract.
who → I identify myself
did → I declare my intention
this → I specify the terms
when → I mark the moment
confirmed_by → I accept the witness
if_ok → I commit to success
if_doubt → I commit to uncertainty
if_not → I commit to failure
status → I accept the verdict
Before you act, you sign the pact.
There is no "let me try and see what happens." There is no action without commitment. The `if_ok`, `if_doubt`, and `if_not` fields are especially powerful—you cannot request anything without declaring what happens in every scenario.
This is why the system works. This is why disputes collapse. This is why trust is computable.
The format is the contract. The contract is the foundation.
// logline-core/src/tuple.rs
use serde::{Deserialize, Serialize};
use crate::{Did, Timestamp, ContentAddress};
/// The 9-field LogLine tuple. Every field is mandatory.
/// This is the atomic unit of verifiable action.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LogLine {
/// The actor initiating the action
pub who: Did,
/// The verb (canonical action identifier)
pub did: ActionVerb,
/// The payload (typed, schema-validated)
pub this: serde_json::Value,
/// UTC timestamp with nanosecond precision
pub when: Timestamp,
/// Consent provider (None until confirmed)
pub confirmed_by: Option,
/// Success commitment
pub if_ok: Commitment,
/// Uncertainty commitment
pub if_doubt: Commitment,
/// Failure commitment
pub if_not: Commitment,
/// Lifecycle state
pub status: LogLineStatus,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum LogLineStatus {
Draft, // Being composed
Pending, // Awaiting evaluation
Committed, // Executed successfully
Ghost, // Denied or expired
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Commitment {
pub action: CommitmentAction,
pub target: Option,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum CommitmentAction {
Emit(String), // Emit an event
Escalate(String), // Escalate to handler
Retry(u32), // Retry with backoff
Abort, // Clean termination
}
impl LogLine {
/// Compute the content address (identity) of this LogLine
pub fn cid(&self) -> ContentAddress {
let bytes = json_atomic::canonize(self);
ContentAddress::from_blake3(&bytes)
}
/// Verify the LogLine signature
pub fn verify(&self, public_key: &PublicKey) -> Result<(), SignatureError> {
let bytes = json_atomic::canonize(self);
public_key.verify(&bytes, &self.signature)
}
/// Transition to Ghost status
pub fn ghost(mut self, reason: GhostReason) -> Self {
self.status = LogLineStatus::Ghost;
self.ghost_reason = Some(reason);
self
}
}