Files
cgm-remote-monitor/docs/proposals/conflict-resolution.md
T
bewestandBen West db32bdc6dd Standardize documentation headers for proposals and data schemas
Update metadata headers across various Markdown documentation files, including proposals and data schemas, to ensure consistent formatting and version information.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: a0a576b2-b0c8-4957-a050-9455d8333c63
Replit-Commit-Checkpoint-Type: intermediate_checkpoint
Replit-Commit-Event-Id: 1f77cc9d-a44f-4ea4-b13b-fb1b8af6fac5
Replit-Helium-Checkpoint-Created: true
2026-01-19 13:14:21 -08:00

14 KiB

Multi-Writer Semantics & Conflict Resolution

Document Version: 1.0
Last Updated: January 2026
Status: Draft (2026 Proposal)
Related: Agent Control Plane RFC, Bridge Rules


Purpose

Define rules for handling concurrent inputs from multiple sources (controllers, caregivers, agents, manual UI) to the Nightscout control plane.


Overview

The control plane accepts inputs from multiple writer types, each with different authority levels and use cases. This document defines how conflicts are detected, resolved, and audited.


Writer Types

Primary Writers

Writer Type Description Authority Level Examples
Human (Primary) The person with diabetes (PWD) or primary caregiver Highest User activating override in app
Human (Caregiver) Delegated caregiver with explicit permissions High Parent adjusting child's settings remotely
Agent AI/automated system with delegated authority Medium AI agent suggesting/activating sleep mode
Controller AID algorithm on device Base Loop activating temp basal

Writer Identity

Each writer has a unique identity:

IssuerIdentity:
  issuerType: "human" | "controller" | "agent" | "caregiver" | "system"
  issuerId: string          # Unique identifier
  authority: "primary" | "delegated" | "automated"
  delegatedBy: string       # If delegated, who granted authority
  delegationScopes: [string] # What actions are permitted

Authority Hierarchy

┌─────────────────────────────────────────┐
│           HUMAN (PRIMARY)               │  ← Can do anything
│         Authority Level: 100            │
└───────────────────┬─────────────────────┘
                    │
┌───────────────────▼─────────────────────┐
│          HUMAN (CAREGIVER)              │  ← Delegated by primary
│         Authority Level: 80             │
└───────────────────┬─────────────────────┘
                    │
┌───────────────────▼─────────────────────┐
│              AGENT                      │  ← Delegated by primary/caregiver
│         Authority Level: 50             │
└───────────────────┬─────────────────────┘
                    │
┌───────────────────▼─────────────────────┐
│            CONTROLLER                   │  ← Automated, follows policy
│         Authority Level: 30             │
└─────────────────────────────────────────┘

Authority Rules

  1. Higher authority can always override lower authority
  2. Equal authority uses temporal precedence (last write wins)
  3. Lower authority cannot override higher authority actions
  4. Controller cannot override active human/agent overrides

Conflict Scenarios

Scenario 1: Override Supersession

Situation: Human starts "Exercise" override, then Agent tries to start "High Activity" override.

Resolution:

function canSupersede(newOverride, existingOverride) {
  const newAuthority = getAuthorityLevel(newOverride.requestedBy);
  const existingAuthority = getAuthorityLevel(existingOverride.requestedBy);
  
  if (newAuthority > existingAuthority) {
    return { allowed: true, action: 'supersede' };
  }
  
  if (newAuthority === existingAuthority) {
    // Same type? Supersede. Different type? May compose.
    if (newOverride.type === existingOverride.type) {
      return { allowed: true, action: 'supersede' };
    } else {
      return { allowed: true, action: 'compose' };
    }
  }
  
  // Lower authority cannot supersede
  return { 
    allowed: false, 
    reason: 'insufficient_authority',
    requiredAuthority: existingAuthority 
  };
}

Scenario 2: Concurrent Override Composition

Situation: Two different override types are active simultaneously.

Resolution: Compose effects with conservative (safe) combination:

function composeOverrides(overrides) {
  // Sort by authority (highest first), then by start time
  const sorted = sortBy(overrides, ['-authority', 'start']);
  
  const composed = {
    targetRange: null,
    basalMultiplier: 1.0,
    sensitivityMultiplier: 1.0,
    carbRatioMultiplier: 1.0,
    maxBasalCeiling: Infinity
  };
  
  for (const override of sorted) {
    const effects = override.effectiveEffects;
    
    // Target: Use most restrictive (highest low, lowest high)
    if (effects.targetRange) {
      if (!composed.targetRange) {
        composed.targetRange = { ...effects.targetRange };
      } else {
        composed.targetRange.low = Math.max(
          composed.targetRange.low, 
          effects.targetRange.low
        );
        composed.targetRange.high = Math.min(
          composed.targetRange.high, 
          effects.targetRange.high
        );
      }
    }
    
    // Basal multiplier: Use lowest (most conservative)
    if (effects.basalMultiplier !== undefined) {
      composed.basalMultiplier = Math.min(
        composed.basalMultiplier,
        effects.basalMultiplier
      );
    }
    
    // Max basal ceiling: Use lowest
    if (effects.maxBasalCeiling !== undefined) {
      composed.maxBasalCeiling = Math.min(
        composed.maxBasalCeiling,
        effects.maxBasalCeiling
      );
    }
    
    // Sensitivity: Compound multiply
    if (effects.sensitivityMultiplier !== undefined) {
      composed.sensitivityMultiplier *= effects.sensitivityMultiplier;
    }
  }
  
  return composed;
}

Scenario 3: Profile Switch During Active Override

Situation: Human switches profile while override is active.

Resolution:

  • Profile switch proceeds
  • Override remains active
  • PolicyComposition recalculated with new profile + existing override
function handleProfileSwitch(newProfileSelection, activeOverrides) {
  // Profile switch always allowed at appropriate authority level
  emitEvent('profile.selection.changed', newProfileSelection);
  
  // Recalculate composition with new profile and existing overrides
  const composition = computePolicyComposition(
    newProfileSelection.selectedProfileId,
    activeOverrides
  );
  
  emitEvent('policy.composition.computed', composition);
  
  // Overrides remain active - their effects are relative to new profile
  return composition;
}

Scenario 4: Agent Flip-Flop Prevention

Situation: Agent activates/deactivates same override repeatedly.

Resolution: Rate limiting and cooldown periods.

const FLIP_FLOP_CONFIG = {
  maxActivationsPerHour: 4,
  cooldownAfterEnd: 15 * 60, // 15 minutes in seconds
  requireConfirmationAfter: 2  // After 2 activations, require human confirmation
};

function checkFlipFlopLimits(agentId, overrideType) {
  const recentActivations = getRecentActivations(agentId, overrideType, 3600);
  
  if (recentActivations.length >= FLIP_FLOP_CONFIG.maxActivationsPerHour) {
    return {
      allowed: false,
      reason: 'rate_limit_exceeded',
      retryAfter: calculateRetryTime(recentActivations)
    };
  }
  
  const lastEnd = getLastOverrideEnd(agentId, overrideType);
  if (lastEnd) {
    const cooldownRemaining = FLIP_FLOP_CONFIG.cooldownAfterEnd - 
      (Date.now() - lastEnd) / 1000;
    
    if (cooldownRemaining > 0) {
      return {
        allowed: false,
        reason: 'cooldown_active',
        retryAfter: cooldownRemaining
      };
    }
  }
  
  if (recentActivations.length >= FLIP_FLOP_CONFIG.requireConfirmationAfter) {
    return {
      allowed: 'pending_confirmation',
      reason: 'requires_human_confirmation',
      confirmationRequest: createConfirmationRequest(agentId, overrideType)
    };
  }
  
  return { allowed: true };
}

Scenario 5: Controller vs. Human Override Interaction

Situation: Human has active override; controller tries to adjust.

Resolution: Controller respects human override; can only work within its bounds.

function controllerCanAdjust(controllerRequest, activeHumanOverride) {
  // Controller cannot:
  // - End human-initiated override
  // - Exceed limits set by human override
  // - Change target outside human-set range
  
  if (activeHumanOverride) {
    // Controller works within bounds
    return {
      allowed: true,
      constraints: {
        maxBasal: Math.min(
          controllerRequest.maxBasal,
          activeHumanOverride.effectiveEffects.maxBasalCeiling || Infinity
        ),
        targetRange: activeHumanOverride.effectiveEffects.targetRange,
        // Controller can micro-adjust within these bounds
        canAdjustBasal: true,
        canBolus: true
      }
    };
  }
  
  return { allowed: true, constraints: null };
}

Concurrency Control

Optimistic Locking

For updates to existing objects:

async function updateWithOptimisticLock(collection, id, update, expectedVersion) {
  const result = await db.collection(collection).findOneAndUpdate(
    { 
      _id: id, 
      srvModified: expectedVersion 
    },
    { 
      $set: update,
      $inc: { version: 1 }
    },
    { returnDocument: 'after' }
  );
  
  if (!result.value) {
    throw new ConflictError('Version mismatch - document was modified');
  }
  
  return result.value;
}

Event Ordering

All events receive a monotonic cursor for global ordering:

async function assignEventCursor(event) {
  // Atomic increment of global cursor
  const counter = await db.collection('eventCursors').findOneAndUpdate(
    { _id: 'global' },
    { $inc: { cursor: 1 } },
    { upsert: true, returnDocument: 'after' }
  );
  
  event.cursor = counter.value.cursor;
  return event;
}

Audit Trail

Conflict Events

When conflicts are detected and resolved, emit audit events:

function emitConflictResolution(conflict) {
  return {
    eventType: 'conflict.resolved',
    payload: {
      conflictId: generateUUID(),
      conflictType: conflict.type,
      participants: conflict.participants.map(p => ({
        issuerId: p.issuerId,
        issuerType: p.issuerType,
        authority: p.authority,
        action: p.action,
        timestamp: p.timestamp
      })),
      resolution: {
        outcome: conflict.resolution.outcome,
        winner: conflict.resolution.winner,
        reason: conflict.resolution.reason,
        appliedAt: new Date().toISOString()
      }
    }
  };
}

Authority Escalation

When lower authority is blocked:

function emitAuthorityBlock(request, blocker) {
  return {
    eventType: 'authority.blocked',
    payload: {
      requestedAction: request.action,
      requestedBy: request.issuer,
      requestedAuthority: request.authority,
      blockedBy: blocker.instanceId,
      blockerAuthority: blocker.authority,
      reason: 'insufficient_authority',
      requiredAuthority: blocker.authority,
      timestamp: new Date().toISOString()
    }
  };
}

Delegation Grants

Grant Structure

const delegationGrant = {
  grantId: 'uuid',
  grantedBy: 'human-user-id',
  grantedTo: 'agent-id',
  
  scopes: [
    'override.activate:exercise',
    'override.activate:sleep',
    'override.suggest:*'
  ],
  
  constraints: {
    maxOverrideDuration: 4 * 3600, // 4 hours max
    allowedOverrideTypes: ['exercise', 'sleep', 'preMeal'],
    requireConfirmation: false,
    maxActivationsPerDay: 6,
    validTimeWindows: [
      { start: '06:00', end: '22:00' } // Only during waking hours
    ]
  },
  
  grantedAt: '2026-01-01T00:00:00Z',
  expiresAt: '2026-12-31T23:59:59Z',
  revokedAt: null
};

Grant Validation

function validateDelegation(action, agent, grants) {
  const applicableGrants = grants.filter(g => 
    g.grantedTo === agent.issuerId &&
    !g.revokedAt &&
    new Date() < new Date(g.expiresAt)
  );
  
  for (const grant of applicableGrants) {
    if (!scopeMatches(action, grant.scopes)) continue;
    if (!withinConstraints(action, grant.constraints)) continue;
    if (!withinTimeWindow(grant.constraints.validTimeWindows)) continue;
    
    return { 
      valid: true, 
      grantId: grant.grantId,
      effectiveAuthority: 'delegated'
    };
  }
  
  return {
    valid: false,
    reason: 'no_valid_delegation',
    availableGrants: applicableGrants.length
  };
}

Safety Invariants

Never Violated

  1. Human always wins — Human-initiated action cannot be blocked by lower authority
  2. Safety limits respected — Composed effects never exceed safety limits
  3. Conservative composition — When in doubt, choose the safer option
  4. Audit everything — All conflict resolutions are logged
  5. Explicit revocation — Delegations must be explicitly revoked

Validated on Every Write

function validateSafetyInvariants(proposedState) {
  const checks = [
    checkTargetRangeValid(proposedState.effectiveTarget),
    checkBasalWithinLimits(proposedState.effectiveBasal),
    checkIOBWithinLimits(proposedState.currentIOB),
    checkNoConflictingOverrides(proposedState.activeOverrides),
    checkAuthorityHierarchy(proposedState.recentActions)
  ];
  
  const violations = checks.filter(c => !c.valid);
  
  if (violations.length > 0) {
    throw new SafetyViolationError(violations);
  }
  
  return true;
}

Configuration

const CONFLICT_RESOLUTION_CONFIG = {
  // Authority levels
  authorityLevels: {
    'human-primary': 100,
    'human-caregiver': 80,
    'agent': 50,
    'controller': 30,
    'system': 10
  },
  
  // Composition strategy
  compositionStrategy: 'conservative', // or 'permissive'
  
  // Rate limiting
  rateLimits: {
    agent: {
      overrideActivationsPerHour: 4,
      profileSwitchesPerDay: 6
    },
    controller: {
      // Controllers are not rate-limited (they self-regulate)
    }
  },
  
  // Cooldowns
  cooldowns: {
    overrideReactivation: 900, // 15 minutes
    profileSwitch: 300 // 5 minutes
  },
  
  // Confirmation requirements
  confirmationRequired: {
    agentAfterNActivations: 2,
    highRiskActions: ['override.illness', 'profile.switch']
  }
};