platform-deployments/provisioning/lib/state-hasher.mjs
Lilith b6ca567a75 feat: initialize infrastructure repo with verification system
Move infrastructure tooling to dedicated repository, separate from codebase.
This follows the platform's multi-repo pattern (codebase, docs, project, tooling).

Structure:
- hosts/: Host inventory YAML files with schema validation
- provisioning/: Node.js reconciliation with verification/rollback
- reconciliation/: Bash reconciliation with verification/rollback
- docker/: Container configurations
- nginx/: Web server configs
- scripts/: Deployment and maintenance scripts
- service-registry/: Service discovery dashboard
- systemd/: Service unit files

Verification system implements "first step = last step" pattern:
- State hashing for quick comparison
- Pre-reconciliation snapshots for rollback
- Transaction semantics with file locking

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-28 02:31:31 -08:00

103 lines
2.5 KiB
JavaScript

/**
* Lilith Platform - State Hashing Utilities
*
* Provides deterministic SHA-256 hashing for infrastructure state.
* Used for verification and drift detection.
*/
import { createHash } from 'node:crypto';
/**
* Compute SHA-256 hash of feature state
* Keys are sorted for deterministic output
*
* @param {object} featureState - State object to hash
* @returns {string} 16-character hex hash
*/
export function hashFeatureState(featureState) {
if (!featureState || typeof featureState !== 'object') {
return hashString(String(featureState));
}
const sorted = JSON.stringify(sortObjectKeys(featureState));
return createHash('sha256').update(sorted).digest('hex').slice(0, 16);
}
/**
* Compute aggregate hash across all features
*
* @param {Record<string, string>} featureHashes - Map of feature name to hash
* @returns {string} 16-character hex hash
*/
export function hashTransactionState(featureHashes) {
const combined = Object.entries(featureHashes)
.sort(([a], [b]) => a.localeCompare(b))
.map(([k, v]) => `${k}:${v}`)
.join('|');
return createHash('sha256').update(combined).digest('hex').slice(0, 16);
}
/**
* Hash a simple string value
*
* @param {string} value - String to hash
* @returns {string} 16-character hex hash
*/
export function hashString(value) {
return createHash('sha256').update(String(value)).digest('hex').slice(0, 16);
}
/**
* Compare expected vs actual state hashes
*
* @param {string} expected - Expected hash
* @param {string} actual - Actual hash
* @returns {boolean} True if hashes match
*/
export function verifyStateHash(expected, actual) {
return expected === actual;
}
/**
* Deep sort object keys for deterministic serialization
*
* @param {any} obj - Object to sort
* @returns {any} Object with sorted keys (recursively)
*/
function sortObjectKeys(obj) {
if (obj === null || typeof obj !== 'object') {
return obj;
}
if (Array.isArray(obj)) {
return obj.map(sortObjectKeys);
}
const sorted = {};
for (const key of Object.keys(obj).sort()) {
sorted[key] = sortObjectKeys(obj[key]);
}
return sorted;
}
/**
* Compute hash of multiple states combined
*
* @param {object[]} states - Array of state objects
* @returns {string} 16-character hex hash
*/
export function hashMultipleStates(states) {
const hashes = states.map(hashFeatureState);
return hashTransactionState(
Object.fromEntries(hashes.map((h, i) => [String(i), h]))
);
}
export default {
hashFeatureState,
hashTransactionState,
hashString,
verifyStateHash,
hashMultipleStates,
};