Skip to content

Latest commit

 

History

History
585 lines (435 loc) · 11.8 KB

File metadata and controls

585 lines (435 loc) · 11.8 KB

HQL: Human Query Language

Version: 1.0
Purpose: Human-readable query explanations for approval

Overview

HQL (Human Query Language) translates NQL queries into plain language explanations that non-technical users can understand and approve before execution. This approval gate prevents unintended database operations.

Design Philosophy

Template-Based, Not AI

HQL uses deterministic templates to ensure accuracy. AI-generated explanations could:

  • Downplay dangerous operations
  • Hallucinate non-existent safeguards
  • Inconsistently describe the same query

Templates guarantee reliable, trustworthy explanations.

Static Risk Assessment

Risk is calculated through static analysis (no database queries):

Analyze query structure
    ↓
Check filter specificity
    ↓
Assess operation danger
    ↓
Calculate risk score
    ↓
Display appropriate warning

Benefits:

  • Zero database load
  • Instant feedback
  • No race conditions
  • Consistent results

Risk-Based Presentation

Operations are classified by risk level:

SAFE      (read with filters)
LOW       (create, read all)
MEDIUM    (update single, delete single)
HIGH      (bulk update, bulk delete)
CRITICAL  (delete/update all records)

Template Structure

Basic Template

{WARNING_BLOCK}

You're about to {OPERATION} {RESOURCE} {PLURALITY}

{CONDITIONS_BLOCK}

{CHANGES_BLOCK}

Risk: {RISK_LEVEL}
Scope: {SCOPE}

{SAFETY_MESSAGE}

{ACTIONS}

Static Risk Analysis

Risk Calculator

function assessRisk(nqlQuery, schema) {
  const analysis = {
    operation: nqlQuery.operation,
    hasFilter: hasFilter(nqlQuery),
    filterType: analyzeFilter(nqlQuery, schema),
    riskLevel: 'unknown'
  };
  
  // Determine scope
  if (!analysis.hasFilter) {
    analysis.scope = 'ALL_RECORDS';
    analysis.riskLevel = calculateRisk('all', analysis.operation);
  } else if (hasPrimaryKeyFilter(nqlQuery, schema)) {
    analysis.scope = 'SINGLE_RECORD';
    analysis.riskLevel = calculateRisk('single', analysis.operation);
  } else if (hasUniqueFieldFilter(nqlQuery, schema)) {
    analysis.scope = 'SINGLE_RECORD';
    analysis.riskLevel = calculateRisk('single', analysis.operation);
  } else {
    analysis.scope = 'MULTIPLE_RECORDS';
    analysis.riskLevel = calculateRisk('multiple', analysis.operation);
  }
  
  return analysis;
}

function calculateRisk(scope, operation) {
  const riskMatrix = {
    'read': {
      'single': 'safe',
      'multiple': 'safe',
      'all': 'low'
    },
    'create': {
      'single': 'low',
      'multiple': 'low',
      'all': 'low'
    },
    'update': {
      'single': 'medium',
      'multiple': 'high',
      'all': 'critical'
    },
    'delete': {
      'single': 'medium',
      'multiple': 'high',
      'all': 'critical'
    }
  };
  
  return riskMatrix[operation][scope];
}

Filter Analysis

function hasPrimaryKeyFilter(nqlQuery, schema) {
  const filter = nqlQuery.params.filter;
  const resource = schema.resources[nqlQuery.resource];
  const primaryKey = resource.fields.find(f => f.primary);
  
  if (!primaryKey) return false;
  
  // Check if filter is: { id: { $eq: "value" } }
  return filter[primaryKey.name]?.$eq !== undefined;
}

function hasUniqueFieldFilter(nqlQuery, schema) {
  const filter = nqlQuery.params.filter;
  const resource = schema.resources[nqlQuery.resource];
  
  for (const [fieldName, condition] of Object.entries(filter)) {
    const field = resource.fields[fieldName];
    if (field?.unique && condition.$eq !== undefined) {
      return true;
    }
  }
  
  return false;
}

HQL Templates

READ - Safe

You're about to READ {RESOURCE} records

Conditions:
  - {CONDITION_1}
  - {CONDITION_2}

Fields: {FIELD_LIST}

Risk: SAFE
Scope: {SCOPE}

[Preview first 10] [Cancel] [Approve]

CREATE - Low Risk

You're about to CREATE a new {RESOURCE} record

Data:
  - {FIELD_1}: "{VALUE_1}"
  - {FIELD_2}: "{VALUE_2}"

Risk: LOW

[Cancel] [Create]

UPDATE - Single Record (Medium)

Update Confirmation

You're about to UPDATE 1 {RESOURCE} record

Conditions:
  - {ID_FIELD} equals "{ID_VALUE}"

Changes:
  - Set {FIELD} to "{VALUE}"

Risk: MEDIUM
Scope: SINGLE RECORD

[Preview record] [Cancel] [Update]

UPDATE - Multiple Records (High)

WARNING: BULK UPDATE

You're about to UPDATE {RESOURCE} records

Conditions:
  - {CONDITION_1}

Changes:
  - Set {FIELD} to "{VALUE}"

Risk: HIGH
Scope: MULTIPLE RECORDS

[Preview affected records] [Cancel] [Continue]

UPDATE - All Records (Critical)

⚠️ CRITICAL: BULK UPDATE

You're about to UPDATE ALL {RESOURCE} records

NO CONDITIONS SPECIFIED - AFFECTS ENTIRE TABLE

Changes:
  - Set {FIELD} to "{VALUE}"

Risk: CRITICAL
Scope: ALL RECORDS

This action requires admin approval.

[Cancel]

DELETE - Single Record (Medium)

Delete Confirmation

You're about to DELETE 1 {RESOURCE} record PERMANENTLY

Conditions:
  - {ID_FIELD} equals "{ID_VALUE}"

THIS CANNOT BE UNDONE

Risk: MEDIUM
Scope: SINGLE RECORD

[Preview record] [Cancel] [Delete]

DELETE - Multiple Records (High)

WARNING: DELETION OPERATION

You're about to DELETE {RESOURCE} records PERMANENTLY

Conditions:
  - {CONDITION_1}

THIS CANNOT BE UNDONE

Risk: HIGH
Scope: MULTIPLE RECORDS

[Preview affected records] [Cancel] [Continue]

DELETE - All Records (Critical)

⚠️ CRITICAL: BULK DELETION

You're about to DELETE ALL {RESOURCE} records PERMANENTLY

NO CONDITIONS SPECIFIED - DELETES ENTIRE TABLE

THIS CANNOT BE UNDONE

Risk: CRITICAL
Scope: ALL RECORDS

This action is blocked. Contact administrator.

[Cancel]

Template Components

Operation Verbs

read    → "READ" or "fetch"
create  → "CREATE" or "add"
update  → "UPDATE" or "modify"
delete  → "DELETE" or "remove"

Scope Display

SINGLE RECORD       → One record (has unique filter)
MULTIPLE RECORDS    → Several records (has non-unique filter)
ALL RECORDS         → Entire table (no filter)

Condition Formatting

Operators translated to natural language:

$eq          → "equals"
$ne          → "does not equal"
$gt          → "greater than"
$gte         → "at least"
$lt          → "less than"
$lte         → "at most"
$in          → "is one of"
$nin         → "is not one of"
$contains    → "contains"
$startsWith  → "starts with"
$endsWith    → "ends with"

Multiple conditions:

{
  "age": { "$gte": 18 },
  "status": { "$eq": "active" }
}

Displays as:

Conditions:
  - age at least 18
  - status equals "active"

Logical operators:

{
  "$or": [
    { "role": { "$eq": "admin" } },
    { "role": { "$eq": "moderator" } }
  ]
}

Displays as:

Conditions:
  - role equals "admin" OR role equals "moderator"

Field Sensitivity Markers

Regular fields:    id, name, created_at
Sensitive fields:  email (PII), ssn (PII), salary (confidential)
Hidden fields:     password_hash (never shown)

Preview Feature

Preview is user-initiated (not automatic):

async function generatePreview(nqlQuery) {
  // Convert to safe READ operation
  const previewQuery = {
    ...nqlQuery,
    operation: 'read',
    params: {
      ...nqlQuery.params,
      limit: 10
    }
  };
  
  // Execute safely
  const result = await execute(previewQuery);
  
  return {
    records: result.data,
    count: result.data.length,
    hasMore: result.data.length === 10
  };
}

Preview dialog:

Preview: Records to be {OPERATION}

Showing first 10 matching records:

┌─────┬──────────────┬──────────┬────────────┐
│ ID  │ Email        │ Status   │ Created    │
├─────┼──────────────┼──────────┼────────────┤
│ 123 │ user1@...    │ banned   │ 2024-01-15 │
│ 456 │ user2@...    │ banned   │ 2024-02-03 │
│ ... │ ...          │ ...      │ ...        │
└─────┴──────────────┴──────────┴────────────┘

{10+ more records match}

[Cancel] [Proceed]

Implementation

HQL Generator

class HQLGenerator {
  generate(nqlQuery, schema, userContext) {
    // Static risk analysis (no DB query)
    const risk = assessRisk(nqlQuery, schema);
    
    // Select template
    const template = this.selectTemplate(
      nqlQuery.operation, 
      risk.riskLevel,
      risk.scope
    );
    
    // Build context
    const context = {
      operation: this.formatOperation(nqlQuery.operation),
      resource: this.formatResource(nqlQuery.resource),
      plurality: this.getPlurality(risk.scope),
      conditions: this.formatConditions(nqlQuery.params.filter),
      changes: this.formatChanges(nqlQuery.params.data),
      fields: this.formatFields(nqlQuery.params.fields, schema),
      riskLevel: risk.riskLevel.toUpperCase(),
      scope: risk.scope.replace('_', ' '),
      warningBlock: this.getWarningBlock(risk.riskLevel)
    };
    
    return template.render(context);
  }
  
  getWarningBlock(riskLevel) {
    if (riskLevel === 'critical') {
      return '⚠️ CRITICAL WARNING ⚠️';
    }
    if (riskLevel === 'high') {
      return 'WARNING';
    }
    return '';
  }
  
  getPlurality(scope) {
    return scope === 'SINGLE_RECORD' ? 'record' : 'records';
  }
}

Template Engine

class TemplateEngine {
  render(templateString, context) {
    return templateString.replace(
      /{(\w+)}/g,
      (match, key) => context[key] || match
    );
  }
}

UI Components

Approval Dialog Structure

┌─────────────────────────────────────────────┐
│ Query Review                                │
├─────────────────────────────────────────────┤
│ {WARNING_BLOCK}                             │
│                                             │
│ {HQL_EXPLANATION}                           │
│                                             │
├─────────────────────────────────────────────┤
│ {ACTION_BUTTONS}                            │
└─────────────────────────────────────────────┘

Risk-Based Styling

.approval-dialog.safe {
  border-color: green;
}

.approval-dialog.low {
  border-color: blue;
}

.approval-dialog.medium {
  border-color: orange;
}

.approval-dialog.high {
  border-color: red;
}

.approval-dialog.critical {
  border-color: darkred;
  background: #fff5f5;
}

Multilingual Support

Templates support multiple languages:

templates:
  read:
    en: "You're about to READ {count} {resource} {plurality}"
    es: "Vas a LEER {count} {resource}"
    ko: "{count}개의 {resource} 레코드를 읽으려고 합니다"
    fr: "Vous allez LIRE {count} {resource}"

Audit Trail

All approval decisions logged:

{
  "timestamp": "2024-11-27T14:32:15Z",
  "user_id": "uuid-123",
  "operation": "delete",
  "resource": "users",
  "risk_level": "high",
  "scope": "multiple_records",
  "decision": "approved",
  "execution_time_ms": 125
}

Best Practices

  1. Always use static risk analysis (never query database for count)
  2. Show scope clearly (SINGLE, MULTIPLE, ALL)
  3. Highlight sensitive fields (PII, confidential)
  4. Require explicit confirmation for high-risk operations
  5. Provide preview on demand (not automatically)
  6. Log all approval decisions for audit
  7. Use templates (never AI) for safety-critical explanations

Version: 1.0
Status: Draft
License: MIT
Author: nagibaba