# How the Pending Exception Store Manages 24-Hour Allow-Once Codes in DCG

> Learn how the pending exception store in DCG manages 24-hour allow-once codes. It automatically prunes expired entries, ensuring codes invalidate daily without external schedulers.

- Repository: [Jeff Emanuel/destructive_command_guard](https://github.com/Dicklesworthstone/destructive_command_guard)
- Tags: internals
- Published: 2026-07-14

---

**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`](https://github.com/Dicklesworthstone/destructive_command_guard/blob/main/src/pending_exceptions.rs):

```rust
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:

```rust
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`:

```rust
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`](https://github.com/Dicklesworthstone/destructive_command_guard/blob/main/src/pending_exceptions.rs) orchestrates the write:

```rust
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:

```rust
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:

```rust
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`:

```rust
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:

```rust
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:

```rust
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

```rust
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

```rust
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

```rust
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_HOURS` constant (24) is baked into every `PendingExceptionRecord` at creation time via the `expires_at` field.
- **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_expired` check in `load_active_from_file` automatically filters stale records every time the store is accessed.
- **Safe Persistence**: Exclusive file locking via `open_locked` and 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`.