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>
103 lines
2.5 KiB
JavaScript
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,
|
|
};
|