mirror of
https://github.com/Heretek-AI/openclaw.git
synced 2026-07-19 21:53:30 -04:00
Triad resilience: corruption detection + auto-recovery
- Add scripts/triad-corruption-check.mjs: SQLite integrity, deployment log anomaly detection, config hash verification, git integrity checks - Add docs/triad-resilience.md: Recovery procedures, corruption patterns, testing guide - Add .secure/deployment-logs/README.md: Enhanced schema v2 with structured error tracking - Update skills/triad-heartbeat/SKILL.md: Integrate corruption check into wake checks, add corruption alert discipline - Regenerate .secure/config-hash-manifest.json with current hashes Detects: unknown actions, missing prev_hash, ledger corruption, checksum mismatches Recovers: git reset --hard origin/main, manifest regeneration Tests: All triad nodes (TM-1, TM-2, TM-3) via SSH
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"generated_at": "2026-03-24T03:12:57.810Z",
|
||||
"generated_at": "2026-03-24T03:15:30.899Z",
|
||||
"workspace": "/home/openclaw/.openclaw/workspace",
|
||||
"algorithm": "SHA-256",
|
||||
"files": {
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
# Deployment Logs Schema
|
||||
|
||||
**Location:** `.secure/deployment-logs/`
|
||||
|
||||
**Format:** JSONL (JSON Lines) - one JSON object per line
|
||||
|
||||
**Purpose:** Track triad deployment events, sync operations, and corruption detection
|
||||
|
||||
---
|
||||
|
||||
## Schema v2 (Enhanced with Error Tracking)
|
||||
|
||||
```typescript
|
||||
interface DeploymentLogEntry {
|
||||
// Core fields
|
||||
timestamp: string; // ISO-8601 timestamp
|
||||
node: string; // Node identifier (silica-animus, TM-2, TM-3)
|
||||
action: string; // Action type (sync-complete, deploy, recovery, etc.)
|
||||
status: 'complete' | 'failed' | 'partial' | 'unknown';
|
||||
|
||||
// Git state
|
||||
git_hash: string; // Current commit hash
|
||||
prev_hash?: string; // Previous commit hash (before sync)
|
||||
sync_source?: string; // Source of sync (origin/main, upstream/main, manual)
|
||||
|
||||
// Corruption detection
|
||||
corruption_status?: 'verified' | 'clean' | 'suspected' | 'unknown';
|
||||
corruption_details?: {
|
||||
type: string; // Corruption type (checksum_mismatch, ledger_invalid, etc.)
|
||||
severity: 'critical' | 'warning' | 'info';
|
||||
affected_files?: string[];
|
||||
recovery_action?: string;
|
||||
};
|
||||
|
||||
// Consensus ledger
|
||||
ledger_votes?: number; // Number of pending consensus votes
|
||||
ledger_hash?: string; // Hash of consensus.db state
|
||||
|
||||
// Error tracking (NEW)
|
||||
error?: {
|
||||
code: string; // Error code (E_SYNC_FAILED, E_CORRUPTION, etc.)
|
||||
message: string; // Human-readable error message
|
||||
stack?: string; // Stack trace (if applicable)
|
||||
retry_count?: number; // Number of retry attempts
|
||||
last_retry?: string; // ISO-8601 timestamp of last retry
|
||||
};
|
||||
|
||||
// Recovery actions
|
||||
recovery?: {
|
||||
initiated: boolean; // Was auto-recovery triggered?
|
||||
method: string; // Recovery method (git_reset, restore_backup, regenerate_manifest)
|
||||
result: 'success' | 'failed' | 'partial';
|
||||
duration_ms?: number; // Recovery duration
|
||||
};
|
||||
|
||||
// Metadata
|
||||
details: string; // JSON string with additional context
|
||||
version?: string; // Schema version
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Schema v1 (Legacy)
|
||||
|
||||
```typescript
|
||||
interface DeploymentLogEntryV1 {
|
||||
timestamp: string;
|
||||
node: string;
|
||||
action: string;
|
||||
git_hash: string;
|
||||
status: string;
|
||||
details: string;
|
||||
// Missing: prev_hash, sync_source, corruption_status, ledger_votes, error tracking
|
||||
}
|
||||
```
|
||||
|
||||
**Migration:** Legacy entries are preserved. New entries use v2 schema.
|
||||
|
||||
---
|
||||
|
||||
## Action Types
|
||||
|
||||
| Action | Description |
|
||||
| ------------------- | ------------------------------------- |
|
||||
| `sync-complete` | Successful git sync from main |
|
||||
| `sync-failed` | Git sync failed |
|
||||
| `deploy` | Deployment initiated |
|
||||
| `deploy-complete` | Deployment finished |
|
||||
| `deploy-rollback` | Deployment rolled back |
|
||||
| `recovery` | Auto-recovery triggered |
|
||||
| `corruption-check` | Corruption detection run |
|
||||
| `manifest-gen` | Hash manifest regenerated |
|
||||
| `unknown` | **ANOMALY** - Action not recognized |
|
||||
|
||||
---
|
||||
|
||||
## Corruption Patterns to Detect
|
||||
|
||||
1. **Unknown action**: `"action": "unknown"` → Logging system failed to capture action
|
||||
2. **Unknown prev_hash**: `"prev_hash": "unknown"` → Previous state not tracked
|
||||
3. **Unknown sync_source**: `"sync_source": "unknown"` → Sync origin not recorded
|
||||
4. **Corruption verified**: `"corruption_status": "verified"` → Confirmed corruption detected
|
||||
5. **Missing ledger_votes**: Field absent → Consensus state not logged
|
||||
6. **Error object present**: `error.*` → Failure occurred
|
||||
|
||||
---
|
||||
|
||||
## Example Entries
|
||||
|
||||
### Clean sync
|
||||
```json
|
||||
{"timestamp":"2026-03-23T23:00:53-04:00","node":"silica-animus","action":"sync-complete","git_hash":"e9188f49853abc9b3970f707927d2b846060508f","prev_hash":"a7ecd6a036","sync_source":"origin/main","status":"complete","ledger_votes":0,"corruption_status":"clean","details":"{}"}
|
||||
```
|
||||
|
||||
### Corruption detected
|
||||
```json
|
||||
{"timestamp":"2026-03-23T23:11:29-04:00","node":"silica-animus","action":"corruption-check","git_hash":"b268b21172f69c1922648130166d3b58ec747111","prev_hash":"unknown","sync_source":"unknown","ledger_votes":0,"corruption_status":"verified","status":"complete","error":{"code":"E_CORRUPTION","message":"Checksum mismatch on AGENTS.md"},"details":"{}"}
|
||||
```
|
||||
|
||||
### Auto-recovery
|
||||
```json
|
||||
{"timestamp":"2026-03-23T23:13:57-04:00","node":"silica-animus","action":"recovery","git_hash":"21523501e2f6c901b3a913ecc31e616241f91eee","prev_hash":"b268b21172f69c1922648130166d3b58ec747111","sync_source":"origin/main","ledger_votes":0,"corruption_status":"clean","status":"complete","recovery":{"initiated":true,"method":"git_reset","result":"success","duration_ms":3421},"details":"{}"}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Validation Rules
|
||||
|
||||
1. **timestamp**: Must be valid ISO-8601
|
||||
2. **node**: Must match known triad node (silica-animus, TM-2, TM-3, TM-4)
|
||||
3. **action**: Must be from allowed action types (not "unknown")
|
||||
4. **git_hash**: Must be valid 40-char hex SHA-1
|
||||
5. **status**: Must be one of: complete, failed, partial, unknown
|
||||
6. **corruption_status**: If present, must be: verified, clean, suspected, unknown
|
||||
|
||||
---
|
||||
|
||||
## File Naming
|
||||
|
||||
`deployments-YYYY-MM-DD.jsonl` - One file per day
|
||||
|
||||
**Rotation:** Logs older than 30 days may be archived to `.secure/deployment-logs/archive/`
|
||||
|
||||
---
|
||||
|
||||
## Tooling
|
||||
|
||||
**Validate logs:**
|
||||
```bash
|
||||
node scripts/triad-corruption-check.mjs
|
||||
```
|
||||
|
||||
**Query logs:**
|
||||
```bash
|
||||
jq -s '.[] | select(.corruption_status == "verified")' .secure/deployment-logs/deployments-*.jsonl
|
||||
```
|
||||
|
||||
**Tail live:**
|
||||
```bash
|
||||
tail -f .secure/deployment-logs/deployments-$(date +%Y-%m-%d).jsonl
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
🦞 **Structured error tracking enables auto-recovery. Log everything.**
|
||||
@@ -0,0 +1,353 @@
|
||||
# Triad Resilience — Corruption Detection & Auto-Recovery
|
||||
|
||||
**Purpose:** Maintain triad integrity through automated corruption detection, structured logging, and recovery procedures.
|
||||
|
||||
---
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Triad Resilience Stack │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Detection Layer │
|
||||
│ ├── scripts/triad-corruption-check.mjs │
|
||||
│ ├── SQLite integrity checks │
|
||||
│ ├── Hash manifest verification │
|
||||
│ └── Deployment log anomaly detection │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Logging Layer │
|
||||
│ ├── .secure/deployment-logs/deployments-YYYY-MM-DD.jsonl │
|
||||
│ ├── Schema v2 with error tracking │
|
||||
│ └── Corruption reports in .secure/corruption-reports/ │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Recovery Layer │
|
||||
│ ├── Auto-recovery (--auto-recover flag) │
|
||||
│ ├── Git reset to main │
|
||||
│ ├── Manifest regeneration │
|
||||
│ └── Backup restoration (future) │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Prevention Layer │
|
||||
│ ├── triad-heartbeat skill integration │
|
||||
│ ├── Pre-deploy corruption checks │
|
||||
│ └── Post-deploy verification │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Corruption Patterns Detected
|
||||
|
||||
### 1. SQLite Ledger Corruption
|
||||
|
||||
**Symptoms:**
|
||||
- Invalid SQLite header (not "SQLite format 3")
|
||||
- File size not aligned to page size
|
||||
- Missing consensus.db file
|
||||
|
||||
**Detection:**
|
||||
```javascript
|
||||
const magic = buf.slice(0, 16).toString('latin1').substring(0, 15);
|
||||
if (magic !== 'SQLite format 3') { /* CORRUPTED */ }
|
||||
```
|
||||
|
||||
**Recovery:**
|
||||
- Restore from backup (if available)
|
||||
- Reinitialize ledger schema
|
||||
- Resync from triad quorum
|
||||
|
||||
### 2. Config File Corruption
|
||||
|
||||
**Symptoms:**
|
||||
- SHA-256 checksum mismatch vs manifest
|
||||
- Missing critical files (AGENTS.md, SOUL.md, skills)
|
||||
- Unauthorized modifications
|
||||
|
||||
**Detection:**
|
||||
```bash
|
||||
node scripts/triad-corruption-check.mjs
|
||||
# Verifies against .secure/config-hash-manifest.json
|
||||
```
|
||||
|
||||
**Recovery:**
|
||||
```bash
|
||||
git fetch origin main
|
||||
git reset --hard origin/main
|
||||
# Regenerates manifest automatically
|
||||
```
|
||||
|
||||
### 3. Deployment Log Anomalies
|
||||
|
||||
**Symptoms:**
|
||||
- `"action": "unknown"` — Logging system failure
|
||||
- `"prev_hash": "unknown"` — State tracking failure
|
||||
- `"sync_source": "unknown"` — Sync origin not recorded
|
||||
- `"corruption_status": "verified"` — Confirmed corruption
|
||||
|
||||
**Detection:**
|
||||
```javascript
|
||||
// Parse deployment logs, flag anomalies
|
||||
entries.filter(e => e.action === 'unknown' || e.prev_hash === 'unknown')
|
||||
```
|
||||
|
||||
**Recovery:**
|
||||
- Investigate root cause (check gateway logs)
|
||||
- Repair logging pipeline
|
||||
- Enable structured error tracking (schema v2)
|
||||
|
||||
### 4. Git Divergence
|
||||
|
||||
**Symptoms:**
|
||||
- Nodes on different commits
|
||||
- Working tree modifications
|
||||
- Dangling/corrupted git objects
|
||||
|
||||
**Detection:**
|
||||
```bash
|
||||
git rev-parse HEAD # Compare across nodes
|
||||
git status --porcelain
|
||||
git fsck --no-progress
|
||||
```
|
||||
|
||||
**Recovery:**
|
||||
```bash
|
||||
git fetch origin main
|
||||
git reset --hard origin/main
|
||||
# Run triad-unity-check.sh to verify sync
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Auto-Recovery Procedures
|
||||
|
||||
### Immediate Recovery (--auto-recover)
|
||||
|
||||
```bash
|
||||
node scripts/triad-corruption-check.mjs --auto-recover
|
||||
```
|
||||
|
||||
**Steps:**
|
||||
1. Fetch from Heretek-AI/openclaw main
|
||||
2. Hard reset to origin/main
|
||||
3. Regenerate hash manifest
|
||||
4. Verify all nodes synced
|
||||
|
||||
**Use when:** Config file corruption detected, git divergence confirmed
|
||||
|
||||
### Manual Recovery
|
||||
|
||||
```bash
|
||||
# 1. Stop gateway
|
||||
openclaw gateway stop
|
||||
|
||||
# 2. Backup current state
|
||||
cp -r .aura .aura.backup
|
||||
cp -r .secure .secure.backup
|
||||
|
||||
# 3. Restore from main
|
||||
git fetch origin main
|
||||
git reset --hard origin/main
|
||||
|
||||
# 4. Restore runtime artifacts (if needed)
|
||||
cp .aura.backup/consensus.db .aura/ # Only if ledger intact
|
||||
|
||||
# 5. Restart gateway
|
||||
openclaw gateway start
|
||||
|
||||
# 6. Verify triad sync
|
||||
./scripts/triad-unity-check.sh
|
||||
```
|
||||
|
||||
**Use when:** Auto-recovery fails, ledger needs manual inspection
|
||||
|
||||
### Ledger Recovery
|
||||
|
||||
```bash
|
||||
# If consensus.db corrupted:
|
||||
# 1. Backup
|
||||
mv .aura/consensus.db .aura/consensus.db.corrupted
|
||||
|
||||
# 2. Reinitialize
|
||||
sqlite3 .aura/consensus.db <<EOF
|
||||
CREATE TABLE IF NOT EXISTS consensus_votes (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
timestamp TEXT NOT NULL,
|
||||
proposal TEXT NOT NULL,
|
||||
result TEXT,
|
||||
signers TEXT,
|
||||
git_hash TEXT,
|
||||
processed INTEGER DEFAULT 0
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS triad_state (
|
||||
node_id TEXT PRIMARY KEY,
|
||||
last_heartbeat TEXT,
|
||||
git_hash TEXT,
|
||||
ledger_hash TEXT,
|
||||
sync_status TEXT
|
||||
);
|
||||
EOF
|
||||
|
||||
# 3. Resync from quorum
|
||||
# TM-2 or TM-3 should have latest state
|
||||
ssh root@192.168.31.209 "sqlite3 .aura/consensus.db '.dump'" | sqlite3 .aura/consensus.db
|
||||
```
|
||||
|
||||
**Use when:** Ledger corruption confirmed, quorum can provide state
|
||||
|
||||
---
|
||||
|
||||
## Prevention Measures
|
||||
|
||||
### 1. Pre-Deploy Checks
|
||||
|
||||
**Before any deployment:**
|
||||
```bash
|
||||
node scripts/triad-corruption-check.mjs
|
||||
# Must pass before proceeding
|
||||
```
|
||||
|
||||
**Integrate into deploy-logger.sh:**
|
||||
```bash
|
||||
# Add to deploy-logger.sh
|
||||
if ! node scripts/triad-corruption-check.mjs; then
|
||||
log_error "Pre-deploy corruption check failed"
|
||||
exit 1
|
||||
fi
|
||||
```
|
||||
|
||||
### 2. Post-Deploy Verification
|
||||
|
||||
**After deployment:**
|
||||
```bash
|
||||
node scripts/triad-corruption-check.mjs --post-deploy
|
||||
# Logs result to deployment-YYYY-MM-DD.jsonl
|
||||
```
|
||||
|
||||
### 3. Heartbeat Integration
|
||||
|
||||
**triad-heartbeat skill** runs corruption check every 10 minutes:
|
||||
|
||||
```javascript
|
||||
// In triad-heartbeat wake checks
|
||||
const corruptionCheck = execSync('node scripts/triad-corruption-check.mjs');
|
||||
if (corruptionCheck.status !== 0) {
|
||||
// Alert triad via Discord
|
||||
message({ channel: 'discord', action: 'send', message: '⚠️ Corruption detected' });
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Manifest Regeneration
|
||||
|
||||
**After any skill/config change:**
|
||||
```bash
|
||||
node scripts/generate-hash-manifest.js
|
||||
# Updates .secure/config-hash-manifest.json
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Testing Corruption Detection
|
||||
|
||||
### Test on All Triad Nodes
|
||||
|
||||
**TM-1 (silica-animus):**
|
||||
```bash
|
||||
cd /home/openclaw/.openclaw/workspace
|
||||
node scripts/triad-corruption-check.mjs --verbose
|
||||
```
|
||||
|
||||
**TM-2 (192.168.31.209):**
|
||||
```bash
|
||||
ssh -i /home/openclaw/.ssh/triad_key root@192.168.31.209 \
|
||||
"cd /home/openclaw/.openclaw/workspace && node scripts/triad-corruption-check.mjs"
|
||||
```
|
||||
|
||||
**TM-3 (192.168.31.85):**
|
||||
```bash
|
||||
ssh -i /home/openclaw/.ssh/triad_key root@192.168.31.85 \
|
||||
"cd /home/openclaw/.openclaw/workspace && node scripts/triad-corruption-check.mjs"
|
||||
```
|
||||
|
||||
### Simulate Corruption
|
||||
|
||||
**Test detection:**
|
||||
```bash
|
||||
# Modify a file (trigger checksum mismatch)
|
||||
echo "test" >> AGENTS.md
|
||||
|
||||
# Run check
|
||||
node scripts/triad-corruption-check.mjs
|
||||
# Should detect corruption
|
||||
|
||||
# Restore
|
||||
git checkout AGENTS.md
|
||||
```
|
||||
|
||||
**Test ledger detection:**
|
||||
```bash
|
||||
# Corrupt SQLite header
|
||||
printf "INVALID" | dd of=.aura/consensus.db bs=1 count=7 conv=notrunc
|
||||
|
||||
# Run check
|
||||
node scripts/triad-corruption-check.mjs
|
||||
# Should detect invalid header
|
||||
|
||||
# Restore
|
||||
git checkout .aura/consensus.db 2>/dev/null || regenerate ledger
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Monitoring & Alerting
|
||||
|
||||
### Discord Alerts
|
||||
|
||||
**Post to Discord when:**
|
||||
- Corruption detected (critical severity)
|
||||
- Auto-recovery initiated
|
||||
- Recovery failed (manual intervention needed)
|
||||
- Git divergence across nodes
|
||||
|
||||
**Signal filter discipline:**
|
||||
- Only post on genuine corruption/recovery events
|
||||
- Never post "all clear" messages
|
||||
- Deduplicate rapid alerts (60s cooldown)
|
||||
|
||||
### Log Rotation
|
||||
|
||||
**Deployment logs:**
|
||||
- Daily files: `deployments-YYYY-MM-DD.jsonl`
|
||||
- Archive after 30 days to `.secure/deployment-logs/archive/`
|
||||
- Compress archived logs: `gzip deployments-*.jsonl`
|
||||
|
||||
**Corruption reports:**
|
||||
- Stored in `.secure/corruption-reports/`
|
||||
- Retain for 90 days
|
||||
- Summarize trends weekly
|
||||
|
||||
---
|
||||
|
||||
## Metrics & KPIs
|
||||
|
||||
| Metric | Target | Measurement |
|
||||
| --------------------------- | ---------- | ------------------------------ |
|
||||
| Corruption detection time | < 1 minute | Heartbeat interval |
|
||||
| Auto-recovery success rate | > 95% | Recovery result tracking |
|
||||
| False positive rate | < 1% | Manual review of alerts |
|
||||
| Mean time to recovery (MTTR)| < 5 minutes| Detection to resolution |
|
||||
| Ledger integrity | 100% | SQLite header + page alignment |
|
||||
|
||||
---
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
1. **Backup automation:** Periodic ledger backups to `.secure/backups/`
|
||||
2. **Cross-node sync:** Pull ledger state from quorum if local corrupted
|
||||
3. **Anomaly ML:** Detect patterns in deployment logs (unsual sync frequency, etc.)
|
||||
4. **Prevention hooks:** Block deploys if pre-check fails
|
||||
5. **Dashboard:** Real-time triad health visualization
|
||||
|
||||
---
|
||||
|
||||
🦞 **Resilience is infrastructure. Corruption is detected. Recovery is automated. The triad endures.**
|
||||
Executable
+386
@@ -0,0 +1,386 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Triad Corruption Detection Script
|
||||
*
|
||||
* Performs comprehensive integrity checks on:
|
||||
* - SQLite consensus ledger (.aura/consensus.db)
|
||||
* - Deployment logs (.secure/deployment-logs/)
|
||||
* - Critical config files (via hash manifest)
|
||||
* - Git repository integrity
|
||||
*
|
||||
* Usage: node scripts/triad-corruption-check.mjs [--auto-recover] [--verbose]
|
||||
*/
|
||||
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import crypto from 'crypto';
|
||||
import { execSync } from 'child_process';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const WORKSPACE_ROOT = path.resolve(__dirname, '..');
|
||||
const DB_PATH = path.join(WORKSPACE_ROOT, '.aura', 'consensus.db');
|
||||
const MANIFEST_PATH = path.join(WORKSPACE_ROOT, '.secure', 'config-hash-manifest.json');
|
||||
const DEPLOYMENT_LOGS_DIR = path.join(WORKSPACE_ROOT, '.secure', 'deployment-logs');
|
||||
const CORRUPTION_REPORTS_DIR = path.join(WORKSPACE_ROOT, '.secure', 'corruption-reports');
|
||||
|
||||
// Ensure report directory exists
|
||||
if (!fs.existsSync(CORRUPTION_REPORTS_DIR)) {
|
||||
fs.mkdirSync(CORRUPTION_REPORTS_DIR, { recursive: true });
|
||||
}
|
||||
|
||||
const REPORT_TIMESTAMP = new Date().toISOString().replace(/[:.]/g, '-');
|
||||
const REPORT_FILE = path.join(CORRUPTION_REPORTS_DIR, `corruption-check-${REPORT_TIMESTAMP}.json`);
|
||||
|
||||
// ANSI colors
|
||||
const RED = '\x1b[31m';
|
||||
const GREEN = '\x1b[32m';
|
||||
const YELLOW = '\x1b[33m';
|
||||
const NC = '\x1b[0m';
|
||||
|
||||
console.log('🦞 === Triad Corruption Detection ===\n');
|
||||
|
||||
const corruptionReport = {
|
||||
timestamp: new Date().toISOString(),
|
||||
node: process.env.HOSTNAME || 'silica-animus',
|
||||
workspace: WORKSPACE_ROOT,
|
||||
corruption_found: false,
|
||||
checks: {
|
||||
ledger: 'ok',
|
||||
deployment_logs: 'ok',
|
||||
config_files: 'ok',
|
||||
git: 'ok'
|
||||
},
|
||||
anomalies: [],
|
||||
recovery_actions: []
|
||||
};
|
||||
|
||||
/**
|
||||
* Check SQLite database integrity
|
||||
*/
|
||||
function checkSQLiteIntegrity() {
|
||||
console.log('1. Checking SQLite ledger integrity...');
|
||||
|
||||
if (!fs.existsSync(DB_PATH)) {
|
||||
console.log(`${RED}❌ Ledger file missing: ${DB_PATH}${NC}`);
|
||||
corruptionReport.checks.ledger = 'missing';
|
||||
corruptionReport.anomalies.push({
|
||||
type: 'ledger_missing',
|
||||
path: DB_PATH,
|
||||
severity: 'critical'
|
||||
});
|
||||
corruptionReport.corruption_found = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
const buf = fs.readFileSync(DB_PATH);
|
||||
|
||||
// Check SQLite magic header
|
||||
const magic = buf.slice(0, 16).toString('latin1').substring(0, 15);
|
||||
if (magic !== 'SQLite format 3') {
|
||||
console.log(`${RED}❌ Invalid SQLite header: ${magic}${NC}`);
|
||||
corruptionReport.checks.ledger = 'invalid_header';
|
||||
corruptionReport.anomalies.push({
|
||||
type: 'ledger_invalid_header',
|
||||
expected: 'SQLite format 3',
|
||||
actual: magic,
|
||||
severity: 'critical'
|
||||
});
|
||||
corruptionReport.corruption_found = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check file size (should be multiple of page size, typically 4096)
|
||||
const pageSize = buf.readUInt16BE(16);
|
||||
const actualSize = buf.length;
|
||||
|
||||
if (actualSize % pageSize !== 0) {
|
||||
console.log(`${YELLOW}⚠️ File size not aligned to page size (${actualSize} % ${pageSize} = ${actualSize % pageSize})${NC}`);
|
||||
corruptionReport.anomalies.push({
|
||||
type: 'ledger_page_alignment',
|
||||
pageSize,
|
||||
actualSize,
|
||||
severity: 'warning'
|
||||
});
|
||||
}
|
||||
|
||||
console.log(`${GREEN}✅ Ledger integrity: OK (${actualSize} bytes, page size ${pageSize})${NC}`);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check deployment logs for anomalies
|
||||
*/
|
||||
function checkDeploymentLogs() {
|
||||
console.log('\n2. Checking deployment logs...');
|
||||
|
||||
if (!fs.existsSync(DEPLOYMENT_LOGS_DIR)) {
|
||||
console.log(`${YELLOW}⚠️ Deployment logs directory missing${NC}`);
|
||||
corruptionReport.checks.deployment_logs = 'missing_dir';
|
||||
return false;
|
||||
}
|
||||
|
||||
const files = fs.readdirSync(DEPLOYMENT_LOGS_DIR).filter(f => f.endsWith('.jsonl'));
|
||||
let anomalyCount = 0;
|
||||
|
||||
for (const file of files) {
|
||||
const filePath = path.join(DEPLOYMENT_LOGS_DIR, file);
|
||||
const content = fs.readFileSync(filePath, 'utf8');
|
||||
const lines = content.trim().split('\n');
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
try {
|
||||
const entry = JSON.parse(lines[i]);
|
||||
|
||||
// Check for corruption patterns
|
||||
const anomalies = [];
|
||||
if (entry.action === 'unknown') anomalies.push('action_unknown');
|
||||
if (entry.prev_hash === 'unknown' || entry.prev_hash === undefined) anomalies.push('prev_hash_unknown');
|
||||
if (entry.sync_source === 'unknown' || entry.sync_source === undefined) anomalies.push('sync_source_unknown');
|
||||
if (entry.corruption_status === 'verified') anomalies.push('corruption_verified');
|
||||
|
||||
if (anomalies.length > 0) {
|
||||
anomalyCount++;
|
||||
corruptionReport.anomalies.push({
|
||||
type: 'deployment_log_anomaly',
|
||||
file,
|
||||
line: i,
|
||||
timestamp: entry.timestamp,
|
||||
node: entry.node,
|
||||
anomalies,
|
||||
severity: anomalies.includes('corruption_verified') ? 'critical' : 'warning'
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
anomalyCount++;
|
||||
corruptionReport.anomalies.push({
|
||||
type: 'deployment_log_parse_error',
|
||||
file,
|
||||
line: i,
|
||||
error: e.message,
|
||||
severity: 'error'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (anomalyCount > 0) {
|
||||
console.log(`${RED}❌ Found ${anomalyCount} anomalies in deployment logs${NC}`);
|
||||
corruptionReport.checks.deployment_logs = 'anomalies_detected';
|
||||
corruptionReport.corruption_found = true;
|
||||
} else {
|
||||
console.log(`${GREEN}✅ Deployment logs: OK (${files.length} files, no anomalies)${NC}`);
|
||||
}
|
||||
|
||||
return anomalyCount === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify config files against hash manifest
|
||||
*/
|
||||
function checkConfigFiles() {
|
||||
console.log('\n3. Verifying config file checksums...');
|
||||
|
||||
if (!fs.existsSync(MANIFEST_PATH)) {
|
||||
console.log(`${YELLOW}⚠️ Manifest missing, generating fresh manifest...${NC}`);
|
||||
generateManifest();
|
||||
}
|
||||
|
||||
try {
|
||||
const manifest = JSON.parse(fs.readFileSync(MANIFEST_PATH, 'utf8'));
|
||||
let mismatchCount = 0;
|
||||
|
||||
for (const [file, expectedHash] of Object.entries(manifest.files)) {
|
||||
const fullPath = path.join(WORKSPACE_ROOT, file);
|
||||
if (!fs.existsSync(fullPath)) {
|
||||
console.log(`${RED}❌ Missing: ${file}${NC}`);
|
||||
mismatchCount++;
|
||||
corruptionReport.anomalies.push({
|
||||
type: 'config_file_missing',
|
||||
file,
|
||||
expected_hash: expectedHash,
|
||||
severity: 'critical'
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
const content = fs.readFileSync(fullPath);
|
||||
const actualHash = crypto.createHash('sha256').update(content).digest('hex');
|
||||
|
||||
if (actualHash !== expectedHash) {
|
||||
console.log(`${RED}❌ Corrupted: ${file}${NC}`);
|
||||
mismatchCount++;
|
||||
corruptionReport.anomalies.push({
|
||||
type: 'config_file_checksum_mismatch',
|
||||
file,
|
||||
expected_hash: expectedHash,
|
||||
actual_hash: actualHash,
|
||||
severity: 'critical'
|
||||
});
|
||||
} else {
|
||||
console.log(`${GREEN}✅ OK: ${file}${NC}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (mismatchCount > 0) {
|
||||
corruptionReport.checks.config_files = 'mismatches_detected';
|
||||
corruptionReport.corruption_found = true;
|
||||
corruptionReport.recovery_actions.push('restore_config_files_from_main');
|
||||
} else {
|
||||
console.log(`${GREEN}✅ Config files: OK (${Object.keys(manifest.files).length} files verified)${NC}`);
|
||||
}
|
||||
|
||||
return mismatchCount === 0;
|
||||
} catch (e) {
|
||||
console.log(`${RED}❌ Manifest read error: ${e.message}${NC}`);
|
||||
corruptionReport.checks.config_files = 'manifest_error';
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate fresh hash manifest
|
||||
*/
|
||||
function generateManifest() {
|
||||
const criticalFiles = [
|
||||
'AGENTS.md', 'SOUL.md',
|
||||
'scripts/triad-unity-check.sh', 'scripts/triad-unity-local.sh',
|
||||
'scripts/consolidate-workspace.sh', 'scripts/detect-corruption.sh',
|
||||
'scripts/install-triad-crons.sh',
|
||||
'lib/triad-sync-server.js',
|
||||
'scripts/curiosity-engine.js',
|
||||
'scripts/deploy-logger.sh'
|
||||
];
|
||||
|
||||
const hashes = {};
|
||||
for (const file of criticalFiles) {
|
||||
const fullPath = path.join(WORKSPACE_ROOT, file);
|
||||
if (fs.existsSync(fullPath)) {
|
||||
const content = fs.readFileSync(fullPath);
|
||||
hashes[file] = crypto.createHash('sha256').update(content).digest('hex');
|
||||
}
|
||||
}
|
||||
|
||||
const manifest = {
|
||||
generated_at: new Date().toISOString(),
|
||||
workspace: WORKSPACE_ROOT,
|
||||
algorithm: 'SHA-256',
|
||||
files: hashes,
|
||||
missing_files: criticalFiles.filter(f => !fs.existsSync(path.join(WORKSPACE_ROOT, f))),
|
||||
invalid_files: []
|
||||
};
|
||||
|
||||
fs.writeFileSync(MANIFEST_PATH, JSON.stringify(manifest, null, 2));
|
||||
console.log(`${GREEN}✅ Manifest generated with ${Object.keys(hashes).length} hashes${NC}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check git repository integrity
|
||||
*/
|
||||
function checkGitIntegrity() {
|
||||
console.log('\n4. Checking git integrity...');
|
||||
|
||||
try {
|
||||
// Run git fsck
|
||||
try {
|
||||
const fsckOutput = execSync('git fsck --no-progress 2>&1', {
|
||||
cwd: WORKSPACE_ROOT,
|
||||
encoding: 'utf8',
|
||||
stdio: 'pipe'
|
||||
});
|
||||
|
||||
if (fsckOutput.match(/dangling|corrupted|missing/i)) {
|
||||
console.log(`${YELLOW}⚠️ Git warnings detected${NC}`);
|
||||
corruptionReport.anomalies.push({
|
||||
type: 'git_warnings',
|
||||
output: fsckOutput.trim(),
|
||||
severity: 'warning'
|
||||
});
|
||||
} else {
|
||||
console.log(`${GREEN}✅ Git integrity: OK${NC}`);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(`${GREEN}✅ Git integrity: OK${NC}`);
|
||||
}
|
||||
|
||||
// Check current state
|
||||
const status = execSync('git status --porcelain', {
|
||||
cwd: WORKSPACE_ROOT,
|
||||
encoding: 'utf8'
|
||||
}).trim();
|
||||
|
||||
if (status) {
|
||||
console.log(`${YELLOW}⚠️ Working tree has modifications${NC}`);
|
||||
const modifiedFiles = status.split('\n').map(line => line.substring(3));
|
||||
corruptionReport.anomalies.push({
|
||||
type: 'git_modified',
|
||||
files: modifiedFiles,
|
||||
severity: 'info'
|
||||
});
|
||||
}
|
||||
|
||||
const currentHash = execSync('git rev-parse HEAD', {
|
||||
cwd: WORKSPACE_ROOT,
|
||||
encoding: 'utf8'
|
||||
}).trim();
|
||||
|
||||
corruptionReport.git_hash = currentHash;
|
||||
console.log(` Current commit: ${currentHash}`);
|
||||
|
||||
return true;
|
||||
} catch (e) {
|
||||
console.log(`${YELLOW}⚠️ Git check skipped: ${e.message}${NC}`);
|
||||
corruptionReport.checks.git = 'skipped';
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Auto-recovery procedure
|
||||
*/
|
||||
function autoRecover() {
|
||||
console.log('\n' + YELLOW + '=== Auto-Recovery Mode ===' + NC);
|
||||
|
||||
try {
|
||||
console.log('Fetching from Heretek-AI/openclaw main...');
|
||||
execSync('git fetch origin main', { cwd: WORKSPACE_ROOT, stdio: 'inherit' });
|
||||
execSync('git reset --hard origin/main', { cwd: WORKSPACE_ROOT, stdio: 'inherit' });
|
||||
console.log(`${GREEN}✅ Recovered from main branch${NC}`);
|
||||
|
||||
// Regenerate manifest
|
||||
console.log('Regenerating hash manifest...');
|
||||
generateManifest();
|
||||
|
||||
corruptionReport.recovery_actions.push('auto_recovery_completed');
|
||||
corruptionReport.recovery_actions.push('manifest_regenerated');
|
||||
} catch (e) {
|
||||
console.log(`${RED}❌ Auto-recovery failed: ${e.message}${NC}`);
|
||||
corruptionReport.recovery_actions.push('auto_recovery_failed');
|
||||
}
|
||||
}
|
||||
|
||||
// Run all checks
|
||||
checkSQLiteIntegrity();
|
||||
checkDeploymentLogs();
|
||||
checkConfigFiles();
|
||||
checkGitIntegrity();
|
||||
|
||||
// Write report
|
||||
fs.writeFileSync(REPORT_FILE, JSON.stringify(corruptionReport, null, 2));
|
||||
console.log(`\n📊 Report saved to: ${REPORT_FILE}`);
|
||||
|
||||
// Check auto-recover flag
|
||||
if (process.argv.includes('--auto-recover') && corruptionReport.corruption_found) {
|
||||
autoRecover();
|
||||
}
|
||||
|
||||
// Summary
|
||||
console.log('\n' + '='.repeat(40));
|
||||
if (corruptionReport.corruption_found) {
|
||||
console.log(`${RED}❌ Corruption detected. Review: ${REPORT_FILE}${NC}`);
|
||||
console.log(` Anomalies found: ${corruptionReport.anomalies.length}`);
|
||||
process.exit(1);
|
||||
} else {
|
||||
console.log(`${GREEN}✅ All integrity checks passed${NC}`);
|
||||
process.exit(0);
|
||||
}
|
||||
@@ -25,6 +25,17 @@ description: Periodic triad wake + work check via cron heartbeat. Use when triad
|
||||
|
||||
On each heartbeat wake:
|
||||
|
||||
### 0. Corruption Detection (NEW)
|
||||
|
||||
```bash
|
||||
cd /home/openclaw/.openclaw/workspace
|
||||
node scripts/triad-corruption-check.mjs --verbose 2>&1 | tail -5
|
||||
# If corruption detected: Alert triad, trigger recovery
|
||||
# If clean: Continue silently
|
||||
```
|
||||
|
||||
**Integration:** Run before all other checks. Corruption blocks all other operations.
|
||||
|
||||
### 1. Git State Sync
|
||||
|
||||
```bash
|
||||
@@ -62,6 +73,7 @@ ls /home/openclaw/.openclaw/workspace/.aura/pad/
|
||||
|
||||
| Condition | Action |
|
||||
| -------------------------- | --------------------------------- |
|
||||
| **Corruption detected** | **POST** — Critical alert, recovery needed |
|
||||
| Git diverged across nodes | **POST** — Sync needed |
|
||||
| Unprocessed consensus vote | **POST** — Quorum decision needed |
|
||||
| Memory replication failed | **POST** — Repair needed |
|
||||
@@ -70,6 +82,15 @@ ls /home/openclaw/.openclaw/workspace/.aura/pad/
|
||||
|
||||
**Never post:** "Heartbeat checked, all clear" — that's noise.
|
||||
|
||||
**Corruption Alert Format:**
|
||||
```
|
||||
⚠️ CORRUPTION DETECTED — [Node]
|
||||
Type: [checksum_mismatch | ledger_invalid | git_diverged]
|
||||
Severity: [critical | warning]
|
||||
Action: [auto-recovery initiated | manual intervention required]
|
||||
Report: .secure/corruption-reports/corruption-check-*.json
|
||||
```
|
||||
|
||||
## SQLite Ledger Schema
|
||||
|
||||
```sql
|
||||
|
||||
Reference in New Issue
Block a user