How the Pending Exception Store Manages 24-Hour Allow-Once Codes in DCG
The pending exception store embeds a 24-hour expiration timestamp directly into each record, automatically pruning expired entries every time the JSONL store is accessed, ensuring allow-once short codes invalidate after exactly one day without external schedulers.
The destructive_command_guard (dcg) repository implements a self-healing persistence layer for temporary command exceptions. When dcg blocks a destructive command, it generates a short code that grants a one-time bypass, but understanding how the pending exception store manages 24-hour allow-once codes requires examining the record lifecycle from creation to automatic expiration.
Record Structure and TTL Embedding
The 24-hour window is hardcoded as a constant in src/pending_exceptions.rs:
const EXPIRY_HOURS: i64 = 24;
When PendingExceptionRecord::new creates a block record, it calculates the expiration timestamp by adding this duration to the current UTC time:
let created_at = format_timestamp(timestamp);
let expires_at = format_timestamp(timestamp + Duration::hours(EXPIRY_HOURS));
This expires_at field is stored as an ISO-8601 string within the JSONL record. The same TTL propagates to AllowOnceEntry when derived from a pending record via AllowOnceEntry::from_pending, ensuring consistency across both the pending exception store and the allow-once store.
Short-Code Generation from Cryptographic Hashes
The 6-digit decimal short code that users type into the CLI is derived from a SHA-256 hash of the record's metadata. The compute_full_hash function (or HMAC-SHA-256 when the DCG_ALLOW_ONCE_SECRET environment variable is set) hashes the concatenation of created_at, cwd, and command_raw:
let full_hash = compute_full_hash(&created_at, cwd, command_raw);
let short_code = short_code_from_hash(&full_hash);
The short_code_from_hash function extracts the last 8 hexadecimal characters of the full hash and converts them to a 6-digit decimal code, stored in the short_code field. This deterministic generation ensures the same command in the same directory produces the same code within the 24-hour window, while the cryptographic backing prevents collision attacks.
Persistent JSONL Storage with Exclusive Locking
The PendingExceptionStore persists records to pending_exceptions.jsonl using advisory file locking to prevent corruption during concurrent access. The record_block method in src/pending_exceptions.rs orchestrates the write:
let mut file = open_locked(&self.path)?;
let (active, maintenance) = load_active_from_file(&mut file, now, allow_once_audit);
// ... pruning and rotation logic ...
append_record(&mut file, &record)?;
The open_locked function ensures exclusive access during the critical section where the file is read, pruned, and appended to. This lock-free outside/locked-inside pattern guarantees that multiple dcg processes can safely issue allow-once codes simultaneously without data races.
Automatic Pruning of Expired Entries
Every read operation triggers garbage collection. The load_active_from_file function iterates through the JSONL lines and uses is_expired to filter records:
if is_expired(&record.expires_at, now) {
maintenance.pruned_expired += 1;
continue;
}
The is_expired helper parses the ISO-8601 expires_at string and compares it against the current timestamp. Expired records are counted in PendingMaintenance::pruned_expired and omitted from the returned active list. This lazy pruning strategy ensures the store never returns stale codes while amortizing cleanup costs across regular operations.
Size Capping and File Rotation
To prevent unbounded growth, the store implements aggressive size management. The MAX_PENDING_LINES constant limits active records:
pub(crate) const MAX_PENDING_LINES: usize = 10_000;
When the limit is exceeded, load_active_from_file rotates the file by keeping the newest half and archiving the oldest half to pending_exceptions.jsonl.1:
let keep_from = records.len() - (MAX_PENDING_LINES / 2);
let archive_slice = &records[..keep_from];
let keep = records[keep_from..].to_vec();
Additionally, a hard byte limit (MAX_PENDING_BYTES = 10 MiB) prevents new entries if the file would exceed this threshold, protecting against disk exhaustion attacks.
Allow-Once Lookup and Consumption
When a user executes dcg allow-once <code>, the AllowOnceStore::match_command routine validates the entry. For single-use codes, the system marks the entry as consumed and rewrites the file atomically:
let idx = active.iter().position(|e| e.command_raw == command && e.matches_scope(cwd));
if active[idx].single_use {
selected.consumed_at = Some(format_timestamp(now));
active.remove(idx);
rewrite_allow_once_records(&mut file, &active)?;
}
Consumed entries are tracked in PendingMaintenance::pruned_consumed and removed on subsequent loads. This consumption pattern ensures that even within the 24-hour window, a single-use code cannot be replayed.
Structured Audit Logging
Every lifecycle event—issuance, resolution, grant, consumption, and expiry—is emitted through log_allow_once_event. The logging infrastructure writes either human-readable text or JSON (AllowOnceLogEntry) to the configured audit file, including the expires_at timestamp:
let entry = AllowOnceLogEntry::entry_expired(...);
let _ = log_allow_once_event(audit.log_file, &expired, audit.format);
This audit trail allows external security tools to verify that expired codes were properly invalidated according to the 24-hour policy.
Practical Code Examples
Blocking a Command and Emitting a Short Code
use dcg::pending_exceptions::{PendingExceptionStore, AllowOnceAuditConfig};
use dcg::logging::RedactionConfig;
use chrono::Utc;
let store = PendingExceptionStore::new(PendingExceptionStore::default_path(None));
let redaction = RedactionConfig::default();
let audit = AllowOnceAuditConfig {
log_file: "dcg.log",
format: dcg::pending_exceptions::AllowOnceLogFormat::Json,
redaction: &redaction,
};
let (record, _maint) = store.record_block(
"git reset --hard HEAD",
"/home/user/repo",
"Blocking destructive reset",
&redaction,
false, // not single-use
None,
Some(&audit),
).expect("failed to write pending exception");
println!("Allow-once code: {}", record.short_code);
Resolving a Short Code to Allow Execution
use dcg::pending_exceptions::AllowOnceStore;
use std::path::Path;
use chrono::Utc;
let allow_store = AllowOnceStore::new(AllowOnceStore::default_path(None));
let cwd = Path::new("/home/user/repo");
let code = "a1b2c3";
let (matches, _) = allow_store.lookup_by_code(code, Utc::now())
.expect("lookup failed");
if let Some(pending) = matches.first() {
let entry = AllowOnceEntry::from_pending(
pending,
Utc::now(),
dcg::pending_exceptions::AllowOnceScopeKind::Cwd,
"/home/user/repo",
false,
false,
&redaction,
);
allow_store.add_entry(&entry, Utc::now()).expect("add entry failed");
}
Matching Commands Against Active Entries
use dcg::pending_exceptions::AllowOnceStore;
use chrono::Utc;
use std::path::Path;
let allow_store = AllowOnceStore::new(AllowOnceStore::default_path(None));
let cwd = Path::new("/home/user/repo");
let cmd = "git reset --hard HEAD";
if let Some(entry) = allow_store
.match_command(cmd, cwd, Utc::now(), None)
.expect("match failed")
{
println!("Command allowed via entry {}", entry.source_short_code);
}
Summary
- TTL Embedding: The
EXPIRY_HOURSconstant (24) is baked into everyPendingExceptionRecordat creation time via theexpires_atfield. - Deterministic Codes: Short codes are derived from the last 8 hex characters of a SHA-256 hash of metadata, ensuring uniqueness within the 24-hour window.
- Lazy Pruning: The
is_expiredcheck inload_active_from_fileautomatically filters stale records every time the store is accessed. - Safe Persistence: Exclusive file locking via
open_lockedand atomic rotations prevent data corruption during concurrent operations. - Bounded Storage: Hard limits on line count (
MAX_PENDING_LINES) and byte size (MAX_PENDING_BYTES) prevent resource exhaustion. - Audit Trail: Structured logging tracks the full lifecycle, including expiry events, for compliance verification.
Frequently Asked Questions
How does dcg ensure allow-once codes expire exactly after 24 hours?
The system calculates an expires_at timestamp by adding Duration::hours(24) to the creation time during PendingExceptionRecord::new. Every subsequent read operation via load_active_from_file checks this timestamp against the current time and filters out expired records before they can be matched or displayed.
What happens if the pending_exceptions.jsonl file grows too large?
When the record count exceeds MAX_PENDING_LINES (10,000), the store automatically rotates the file by archiving the oldest half to pending_exceptions.jsonl.1 and keeping the newest half active. A hard byte limit of 10 MiB also prevents new writes if the file would exceed this threshold.
Can a single allow-once code be used multiple times within the 24-hour window?
Only if the entry is created without the single_use flag. When single_use is true (the default for CLI-generated codes), AllowOnceStore::match_command marks the entry as consumed by setting consumed_at and rewriting the file, preventing replay attacks even before the 24-hour expiration.
Where does dcg store the allow-once entries permanently?
Pending exceptions live in pending_exceptions.jsonl in the user configuration directory (e.g., ~/.config/dcg/). When a user resolves a short code via dcg allow-once, the system may create a permanent AllowOnceEntry in allow_once.jsonl, depending on the scope and configuration flags passed to AllowOnceEntry::from_pending.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →