558 lines
18 KiB
JavaScript
558 lines
18 KiB
JavaScript
/**
|
|
* Human Authority Gate + Irreversibility Classification
|
|
*
|
|
* Provides typed boundaries between agent, human, and institutional authority,
|
|
* along with irreversibility classification for actions that require elevated
|
|
* proof and pre-commit simulation.
|
|
*
|
|
* AuthorityGate:
|
|
* - Defines authority levels (agent, human, institutional, regulatory)
|
|
* - Maintains a registry of authority scopes and permissions
|
|
* - Checks if a given authority level can perform an action
|
|
* - Determines if escalation is required
|
|
* - Records signed human interventions for audit trails
|
|
*
|
|
* IrreversibilityClassifier:
|
|
* - Classifies actions as reversible, costly-reversible, or irreversible
|
|
* - Uses configurable pattern matching (regex arrays)
|
|
* - Determines required proof levels (standard, elevated, maximum)
|
|
* - Identifies actions requiring pre-commit simulation
|
|
*
|
|
* Human interventions are cryptographically signed using HMAC-SHA256 to
|
|
* create an immutable audit trail of override decisions.
|
|
*
|
|
* @module @claude-flow/guidance/authority
|
|
*/
|
|
import { createHmac, randomUUID } from 'node:crypto';
|
|
import { timingSafeEqual } from './crypto-utils.js';
|
|
// ============================================================================
|
|
// Default Configurations
|
|
// ============================================================================
|
|
/**
|
|
* Default authority scopes for each level.
|
|
*/
|
|
const DEFAULT_AUTHORITY_SCOPES = [
|
|
{
|
|
level: 'agent',
|
|
permissions: [
|
|
'read_file',
|
|
'analyze_code',
|
|
'suggest_changes',
|
|
'run_tests',
|
|
'generate_documentation',
|
|
],
|
|
overrideScope: [],
|
|
escalationRequired: false,
|
|
},
|
|
{
|
|
level: 'human',
|
|
permissions: [
|
|
'write_file',
|
|
'modify_code',
|
|
'deploy_staging',
|
|
'create_branch',
|
|
'merge_pr',
|
|
'delete_resource',
|
|
],
|
|
overrideScope: ['read_file', 'analyze_code', 'suggest_changes', 'run_tests'],
|
|
escalationRequired: false,
|
|
},
|
|
{
|
|
level: 'institutional',
|
|
permissions: [
|
|
'deploy_production',
|
|
'modify_security_policy',
|
|
'grant_access',
|
|
'revoke_access',
|
|
'approve_budget',
|
|
'sign_contract',
|
|
],
|
|
overrideScope: [
|
|
'write_file',
|
|
'modify_code',
|
|
'deploy_staging',
|
|
'create_branch',
|
|
],
|
|
escalationRequired: false,
|
|
},
|
|
{
|
|
level: 'regulatory',
|
|
permissions: [
|
|
'approve_compliance',
|
|
'certify_audit',
|
|
'approve_data_transfer',
|
|
'approve_privacy_policy',
|
|
'issue_license',
|
|
],
|
|
overrideScope: [
|
|
'deploy_production',
|
|
'modify_security_policy',
|
|
'grant_access',
|
|
'approve_budget',
|
|
],
|
|
escalationRequired: false,
|
|
},
|
|
];
|
|
/**
|
|
* Default patterns for irreversible actions.
|
|
*/
|
|
const DEFAULT_IRREVERSIBLE_PATTERNS = [
|
|
'send.*email',
|
|
'publish.*package',
|
|
'process.*payment',
|
|
'execute.*payment',
|
|
'delete.*permanent',
|
|
'drop.*database',
|
|
'revoke.*certificate',
|
|
'propagate.*dns',
|
|
'broadcast.*message',
|
|
'sign.*transaction',
|
|
'commit.*blockchain',
|
|
'release.*funds',
|
|
];
|
|
/**
|
|
* Default patterns for costly-reversible actions.
|
|
*/
|
|
const DEFAULT_COSTLY_REVERSIBLE_PATTERNS = [
|
|
'migrate.*database',
|
|
'deploy.*production',
|
|
'rollback.*deployment',
|
|
'update.*config',
|
|
'modify.*schema',
|
|
'send.*notification',
|
|
'create.*user',
|
|
'delete.*user',
|
|
'grant.*permission',
|
|
'revoke.*permission',
|
|
'scale.*infrastructure',
|
|
'provision.*resource',
|
|
];
|
|
/**
|
|
* Default patterns for reversible actions.
|
|
*/
|
|
const DEFAULT_REVERSIBLE_PATTERNS = [
|
|
'read.*file',
|
|
'analyze.*code',
|
|
'generate.*report',
|
|
'run.*test',
|
|
'preview.*change',
|
|
'simulate.*deployment',
|
|
'validate.*input',
|
|
'check.*status',
|
|
];
|
|
// ============================================================================
|
|
// Authority Hierarchy
|
|
// ============================================================================
|
|
/**
|
|
* Ordered authority hierarchy from lowest to highest.
|
|
*/
|
|
const AUTHORITY_HIERARCHY = [
|
|
'agent',
|
|
'human',
|
|
'institutional',
|
|
'regulatory',
|
|
];
|
|
// ============================================================================
|
|
// AuthorityGate
|
|
// ============================================================================
|
|
/**
|
|
* Gate that enforces authority boundaries and records human interventions.
|
|
*
|
|
* Maintains a registry of authority scopes, checks permissions, determines
|
|
* escalation requirements, and creates cryptographically signed intervention
|
|
* records for audit trails.
|
|
*/
|
|
export class AuthorityGate {
|
|
scopes = new Map();
|
|
interventions = [];
|
|
signatureSecret;
|
|
constructor(config = {}) {
|
|
// Initialize scopes
|
|
const scopesToRegister = config.scopes ?? DEFAULT_AUTHORITY_SCOPES;
|
|
for (const scope of scopesToRegister) {
|
|
this.scopes.set(scope.level, scope);
|
|
}
|
|
// Initialize signature secret
|
|
this.signatureSecret =
|
|
config.signatureSecret ?? randomUUID() + randomUUID();
|
|
}
|
|
/**
|
|
* Check if a given authority level can perform an action.
|
|
*
|
|
* Returns a result indicating whether the action is allowed, the required
|
|
* authority level, and a human-readable explanation.
|
|
*/
|
|
canPerform(level, action) {
|
|
const scope = this.scopes.get(level);
|
|
if (!scope) {
|
|
return {
|
|
allowed: false,
|
|
requiredLevel: 'regulatory',
|
|
currentLevel: level,
|
|
reason: `Unknown authority level: ${level}`,
|
|
};
|
|
}
|
|
// Check if action is in this level's permissions
|
|
if (this.hasPermission(scope, action)) {
|
|
return {
|
|
allowed: true,
|
|
requiredLevel: level,
|
|
currentLevel: level,
|
|
reason: `Action '${action}' is permitted at ${level} authority level`,
|
|
};
|
|
}
|
|
// Find minimum required authority level
|
|
const requiredLevel = this.getMinimumAuthority(action);
|
|
return {
|
|
allowed: false,
|
|
requiredLevel,
|
|
currentLevel: level,
|
|
reason: `Action '${action}' requires ${requiredLevel} authority level (current: ${level})`,
|
|
};
|
|
}
|
|
/**
|
|
* Check if an action requires escalation from the current authority level.
|
|
*/
|
|
requiresEscalation(level, action) {
|
|
const checkResult = this.canPerform(level, action);
|
|
if (checkResult.allowed) {
|
|
return false;
|
|
}
|
|
// Escalation is required if a higher authority level is needed
|
|
const currentIndex = AUTHORITY_HIERARCHY.indexOf(level);
|
|
const requiredIndex = AUTHORITY_HIERARCHY.indexOf(checkResult.requiredLevel);
|
|
return requiredIndex > currentIndex;
|
|
}
|
|
/**
|
|
* Get the minimum authority level required to perform an action.
|
|
*
|
|
* Returns the lowest authority level that has permission for this action.
|
|
* If no level has permission, returns 'regulatory' as the highest level.
|
|
*/
|
|
getMinimumAuthority(action) {
|
|
// Check levels from lowest to highest
|
|
for (const level of AUTHORITY_HIERARCHY) {
|
|
const scope = this.scopes.get(level);
|
|
if (scope && this.hasPermission(scope, action)) {
|
|
return level;
|
|
}
|
|
}
|
|
// If no level has permission, require highest authority
|
|
return 'regulatory';
|
|
}
|
|
/**
|
|
* Record a human intervention with cryptographic signature.
|
|
*
|
|
* Creates an immutable audit record of the intervention decision.
|
|
* The signature is computed using HMAC-SHA256 over the intervention details.
|
|
*/
|
|
recordIntervention(intervention) {
|
|
const id = randomUUID();
|
|
const signature = this.signIntervention({
|
|
id,
|
|
...intervention,
|
|
signature: '', // Placeholder for signature computation
|
|
});
|
|
const signedIntervention = {
|
|
id,
|
|
...intervention,
|
|
signature,
|
|
};
|
|
this.interventions.push(signedIntervention);
|
|
return signedIntervention;
|
|
}
|
|
/**
|
|
* Get all recorded interventions.
|
|
*/
|
|
getInterventions() {
|
|
return [...this.interventions];
|
|
}
|
|
/**
|
|
* Get interventions for a specific action.
|
|
*/
|
|
getInterventionsForAction(action) {
|
|
return this.interventions.filter(i => i.action === action);
|
|
}
|
|
/**
|
|
* Get interventions by authority level.
|
|
*/
|
|
getInterventionsByLevel(level) {
|
|
return this.interventions.filter(i => i.authorityLevel === level);
|
|
}
|
|
/**
|
|
* Verify the signature of an intervention.
|
|
*/
|
|
verifyIntervention(intervention) {
|
|
const expectedSignature = this.signIntervention(intervention);
|
|
return timingSafeEqual(expectedSignature, intervention.signature);
|
|
}
|
|
/**
|
|
* Get the number of recorded interventions.
|
|
*/
|
|
get interventionCount() {
|
|
return this.interventions.length;
|
|
}
|
|
/**
|
|
* Get all registered authority levels.
|
|
*/
|
|
getAuthorityLevels() {
|
|
return [...this.scopes.keys()];
|
|
}
|
|
/**
|
|
* Get the scope for a specific authority level.
|
|
*/
|
|
getScope(level) {
|
|
return this.scopes.get(level);
|
|
}
|
|
/**
|
|
* Add or update an authority scope.
|
|
*/
|
|
registerScope(scope) {
|
|
this.scopes.set(scope.level, scope);
|
|
}
|
|
// ===== Private =====
|
|
/**
|
|
* Check if a scope has permission for an action.
|
|
*
|
|
* Uses exact match and pattern matching (with wildcards).
|
|
*/
|
|
hasPermission(scope, action) {
|
|
// Check exact match
|
|
if (scope.permissions.includes(action)) {
|
|
return true;
|
|
}
|
|
// Check pattern match (treat * as wildcard)
|
|
for (const permission of scope.permissions) {
|
|
if (this.matchesPattern(action, permission)) {
|
|
return true;
|
|
}
|
|
}
|
|
// Check override scope
|
|
if (scope.overrideScope.includes(action)) {
|
|
return true;
|
|
}
|
|
for (const override of scope.overrideScope) {
|
|
if (this.matchesPattern(action, override)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
/**
|
|
* Check if an action matches a permission pattern.
|
|
*
|
|
* Supports simple wildcard patterns (e.g., "deploy_*").
|
|
*/
|
|
matchesPattern(action, pattern) {
|
|
if (!pattern.includes('*')) {
|
|
return action === pattern;
|
|
}
|
|
// Convert wildcard pattern to regex
|
|
const regexPattern = pattern
|
|
.replace(/[.+?^${}()|[\]\\]/g, '\\$&') // Escape regex special chars
|
|
.replace(/\*/g, '.*'); // Replace * with .*
|
|
const regex = new RegExp(`^${regexPattern}$`);
|
|
return regex.test(action);
|
|
}
|
|
/**
|
|
* Sign an intervention using HMAC-SHA256.
|
|
*/
|
|
signIntervention(intervention) {
|
|
const payload = JSON.stringify({
|
|
id: intervention.id,
|
|
timestamp: intervention.timestamp,
|
|
authorityLevel: intervention.authorityLevel,
|
|
action: intervention.action,
|
|
reason: intervention.reason,
|
|
signedBy: intervention.signedBy,
|
|
metadata: intervention.metadata,
|
|
});
|
|
const hmac = createHmac('sha256', this.signatureSecret);
|
|
hmac.update(payload);
|
|
return hmac.digest('hex');
|
|
}
|
|
}
|
|
// ============================================================================
|
|
// IrreversibilityClassifier
|
|
// ============================================================================
|
|
/**
|
|
* Classifies actions by their reversibility to determine required proof levels
|
|
* and whether pre-commit simulation is needed.
|
|
*
|
|
* Uses configurable regex patterns to identify irreversible, costly-reversible,
|
|
* and reversible actions. Irreversible actions require maximum proof and
|
|
* pre-commit simulation.
|
|
*/
|
|
export class IrreversibilityClassifier {
|
|
irreversiblePatterns;
|
|
costlyReversiblePatterns;
|
|
reversiblePatterns;
|
|
constructor(config = {}) {
|
|
this.irreversiblePatterns = (config.irreversiblePatterns ?? DEFAULT_IRREVERSIBLE_PATTERNS).map(p => new RegExp(p, 'i'));
|
|
this.costlyReversiblePatterns = (config.costlyReversiblePatterns ?? DEFAULT_COSTLY_REVERSIBLE_PATTERNS).map(p => new RegExp(p, 'i'));
|
|
this.reversiblePatterns = (config.reversiblePatterns ?? DEFAULT_REVERSIBLE_PATTERNS).map(p => new RegExp(p, 'i'));
|
|
}
|
|
/**
|
|
* Classify an action by its reversibility.
|
|
*
|
|
* Checks patterns in order: irreversible → costly-reversible → reversible.
|
|
* If no patterns match, defaults to 'costly-reversible' as a safe default.
|
|
*/
|
|
classify(action) {
|
|
// Check irreversible patterns first (highest risk)
|
|
const irreversibleMatches = this.findMatches(action, this.irreversiblePatterns);
|
|
if (irreversibleMatches.length > 0) {
|
|
return {
|
|
classification: 'irreversible',
|
|
matchedPatterns: irreversibleMatches,
|
|
requiredProofLevel: 'maximum',
|
|
requiresSimulation: true,
|
|
};
|
|
}
|
|
// Check costly-reversible patterns
|
|
const costlyMatches = this.findMatches(action, this.costlyReversiblePatterns);
|
|
if (costlyMatches.length > 0) {
|
|
return {
|
|
classification: 'costly-reversible',
|
|
matchedPatterns: costlyMatches,
|
|
requiredProofLevel: 'elevated',
|
|
requiresSimulation: true,
|
|
};
|
|
}
|
|
// Check reversible patterns
|
|
const reversibleMatches = this.findMatches(action, this.reversiblePatterns);
|
|
if (reversibleMatches.length > 0) {
|
|
return {
|
|
classification: 'reversible',
|
|
matchedPatterns: reversibleMatches,
|
|
requiredProofLevel: 'standard',
|
|
requiresSimulation: false,
|
|
};
|
|
}
|
|
// Default to costly-reversible if no patterns match (safe default)
|
|
return {
|
|
classification: 'costly-reversible',
|
|
matchedPatterns: [],
|
|
requiredProofLevel: 'elevated',
|
|
requiresSimulation: true,
|
|
};
|
|
}
|
|
/**
|
|
* Get the required proof level for an action.
|
|
*
|
|
* - 'maximum' for irreversible actions
|
|
* - 'elevated' for costly-reversible actions
|
|
* - 'standard' for reversible actions
|
|
*/
|
|
getRequiredProofLevel(action) {
|
|
return this.classify(action).requiredProofLevel;
|
|
}
|
|
/**
|
|
* Check if an action requires pre-commit simulation.
|
|
*
|
|
* Returns true for irreversible and costly-reversible actions.
|
|
*/
|
|
requiresPreCommitSimulation(action) {
|
|
return this.classify(action).requiresSimulation;
|
|
}
|
|
/**
|
|
* Get all configured patterns for a classification.
|
|
*/
|
|
getPatterns(classification) {
|
|
switch (classification) {
|
|
case 'irreversible':
|
|
return this.irreversiblePatterns.map(p => p.source);
|
|
case 'costly-reversible':
|
|
return this.costlyReversiblePatterns.map(p => p.source);
|
|
case 'reversible':
|
|
return this.reversiblePatterns.map(p => p.source);
|
|
}
|
|
}
|
|
/**
|
|
* Add a pattern to a classification.
|
|
*
|
|
* Validates the pattern against ReDoS heuristics before accepting it.
|
|
* Rejects patterns with nested quantifiers (e.g., `(a+)+`) that can
|
|
* cause catastrophic backtracking.
|
|
*
|
|
* @throws Error if the pattern is invalid regex or contains ReDoS-prone constructs
|
|
*/
|
|
addPattern(classification, pattern) {
|
|
// ReDoS heuristic: reject nested quantifiers like (a+)+, (a*)+, (a+)*, etc.
|
|
if (/([+*]|\{[0-9]+,?\})\s*\)[\s]*[+*]|\{[0-9]+,?\}/.test(pattern)) {
|
|
throw new Error(`Pattern rejected: nested quantifiers detected (potential ReDoS): ${pattern}`);
|
|
}
|
|
// Also reject patterns longer than 500 chars as a sanity bound
|
|
if (pattern.length > 500) {
|
|
throw new Error(`Pattern rejected: exceeds maximum length of 500 characters`);
|
|
}
|
|
const regex = new RegExp(pattern, 'i');
|
|
switch (classification) {
|
|
case 'irreversible':
|
|
this.irreversiblePatterns.push(regex);
|
|
break;
|
|
case 'costly-reversible':
|
|
this.costlyReversiblePatterns.push(regex);
|
|
break;
|
|
case 'reversible':
|
|
this.reversiblePatterns.push(regex);
|
|
break;
|
|
}
|
|
}
|
|
// ===== Private =====
|
|
/**
|
|
* Find all patterns that match an action.
|
|
*/
|
|
findMatches(action, patterns) {
|
|
const matches = [];
|
|
for (const pattern of patterns) {
|
|
if (pattern.test(action)) {
|
|
matches.push(pattern.source);
|
|
}
|
|
}
|
|
return matches;
|
|
}
|
|
}
|
|
// ============================================================================
|
|
// Factory Functions
|
|
// ============================================================================
|
|
/**
|
|
* Create an AuthorityGate with optional configuration.
|
|
*/
|
|
export function createAuthorityGate(config) {
|
|
return new AuthorityGate(config);
|
|
}
|
|
/**
|
|
* Create an IrreversibilityClassifier with optional configuration.
|
|
*/
|
|
export function createIrreversibilityClassifier(config) {
|
|
return new IrreversibilityClassifier(config);
|
|
}
|
|
// ============================================================================
|
|
// Helpers
|
|
// ============================================================================
|
|
/**
|
|
* Check if one authority level is higher than another.
|
|
*/
|
|
export function isHigherAuthority(level1, level2) {
|
|
const index1 = AUTHORITY_HIERARCHY.indexOf(level1);
|
|
const index2 = AUTHORITY_HIERARCHY.indexOf(level2);
|
|
return index1 > index2;
|
|
}
|
|
/**
|
|
* Get the next higher authority level, if any.
|
|
*/
|
|
export function getNextHigherAuthority(level) {
|
|
const index = AUTHORITY_HIERARCHY.indexOf(level);
|
|
if (index === -1 || index === AUTHORITY_HIERARCHY.length - 1) {
|
|
return null;
|
|
}
|
|
return AUTHORITY_HIERARCHY[index + 1];
|
|
}
|
|
/**
|
|
* Get the authority hierarchy as an ordered array.
|
|
*/
|
|
export function getAuthorityHierarchy() {
|
|
return [...AUTHORITY_HIERARCHY];
|
|
}
|
|
//# sourceMappingURL=authority.js.map
|