# Validation Rules

## Purpose

Define the 10 structural checks applied before the validate phase signs off. This skill documents the rules applied by the `schema-reviewer` agent and enforced by the build and validate gates (`gates/build.js`, `gates/conventions.js`, `gates/validate.js`).

---

## The 10 Readiness Checks

### Check 1: Schema Review Gate

**What**: Verify schema-review.json exists and shows "passed"

```javascript
const schemaReview = load('.connector-dev/<provider>/validation/schema-review.json');

if (!schemaReview) {
  return { status: 'fail', message: 'Schema review not run' };
}

if (schemaReview.verdict !== 'passed') {
  return { status: 'fail', message: `Schema review blocked: ${schemaReview.summary.remainingHigh} high issues` };
}

if (schemaReview.summary.remainingHigh > 0) {
  return { status: 'fail', message: 'High severity schema issues remain' };
}

return { status: 'pass' };
```

**Blocks if**: Schema review not passed or has high-severity issues

---

### Check 2: Connector Files Exist

**What**: Verify all required connector files exist

```javascript
// CONNECTOR_PATH resolved from .connector-dev/<provider>/connection/connection.yaml or build-state.yaml
const required = [
  `${connectorPath}/${provider}.connector.s1.yaml`
];

const partials = glob(`${connectorPath}/${provider}.*.s1.partial.yaml`);

for (const ref of connector.partials) {
  const path = resolve(ref.$ref);
  if (!exists(path)) {
    return { status: 'fail', message: `Missing partial: ${ref.$ref}` };
  }
}

return { status: 'pass', message: `Found connector + ${partials.length} partials` };
```

**Blocks if**: Main connector or any referenced partial missing

---

### Check 3: Action Inventory Complete

**What**: Verify all actions are accounted for

```javascript
const connectorActions = extractActions(connector);
const researchActions = load('.connector-dev/<provider>/research/actions.yaml');

const orphaned = connectorActions.filter(a => !hasPartial(a));
const missing = researchActions.filter(a => !connectorActions.includes(a));

if (orphaned.length > 0) {
  return { status: 'fail', message: `Orphaned actions: ${orphaned.join(', ')}` };
}

return {
  status: 'pass',
  message: `${connectorActions.length} actions mapped`,
  warnings: missing.length > 0 ? [`${missing.length} research actions not in connector`] : []
};
```

**Blocks if**: Actions exist without partial files

---

### Check 4: Research Data Available

**What**: Verify research data exists for resources

```javascript
const resources = extractResources(connector);
const researchDir = '.connector-dev/<provider>/research/endpoints/resources';

const missing = [];
for (const resource of resources) {
  if (!exists(`${researchDir}/${resource}.yaml`)) {
    missing.push(resource);
  }
}

if (missing.length > 0) {
  return {
    status: 'warn',
    message: `No research data for: ${missing.join(', ')}`
  };
}

return { status: 'pass' };
```

**Blocks if**: Never (warning only)

---

### Check 5: Auth Coverage

**What**: Verify authentication is properly configured

```javascript
const auth = connector.authentication;

if (!auth || Object.keys(auth).length === 0) {
  return { status: 'fail', message: 'No authentication configured' };
}

const connection = load('.connector-dev/<provider>/connection/connection.yaml');
const missingAccounts = [];

for (const authType of Object.keys(auth)) {
  if (!connection?.accounts?.[authType]) {
    missingAccounts.push(authType);
  }
}

if (missingAccounts.length === Object.keys(auth).length) {
  return { status: 'fail', message: 'No accounts connected for any auth type' };
}

if (missingAccounts.length > 0) {
  return { status: 'warn', message: `No account for: ${missingAccounts.join(', ')}` };
}

return { status: 'pass' };
```

**Blocks if**: No authentication or no accounts connected

---

### Check 6: Dependency Chain Valid

**What**: Verify no circular or missing dependencies

```javascript
const graph = buildDependencyGraph(connector);

const circular = findCircularDeps(graph);
if (circular.length > 0) {
  return { status: 'fail', message: `Circular dependency: ${circular.join(' -> ')}` };
}

const missing = findMissingDeps(graph);
if (missing.length > 0) {
  return { status: 'fail', message: `Missing dependencies: ${missing.join(', ')}` };
}

return { status: 'pass' };
```

**Blocks if**: Circular dependencies or missing required dependencies

---

### Check 7: Testing Mode Correct

**What**: Verify environment is set up correctly

```javascript
const apiKey = process.env.STACKONE_API_KEY;
const baseUrl = process.env.STACKONE_BASE_URL || process.env.BASE_URL;

if (!apiKey) {
  return { status: 'fail', message: 'STACKONE_API_KEY not set' };
}

if (baseUrl?.includes('api.stackone.com') && !process.env.ALLOW_PRODUCTION) {
  return { status: 'warn', message: 'Pointing to production - use --allow-prod to confirm' };
}

return { status: 'pass', message: `Using ${baseUrl || 'default (production)'}` };
```

**Blocks if**: API key not set

---

### Check 8: Connection Available

**What**: Verify at least one account is connected

```javascript
const connection = load('.connector-dev/<provider>/connection/connection.yaml');

if (!connection) {
  return { status: 'fail', message: 'Connector not found at connector_path' };
}

const accounts = extractAccounts(connection);
if (accounts.length === 0) {
  return { status: 'fail', message: 'No accounts in connection.yaml' };
}

return { status: 'pass', message: `${accounts.length} accounts available` };
```

**Blocks if**: No connection.yaml or no accounts

---

### Check 9: Scenario Constructability

**What**: Verify scenarios can be built for all actions

```javascript
const actions = extractActions(connector);
const unconstructable = [];

for (const action of actions) {
  const result = canConstructScenarios(action);
  if (!result.constructable) {
    unconstructable.push({ action: action.name, reason: result.reason });
  }
}

if (unconstructable.length > 0) {
  return {
    status: 'warn',
    message: `${unconstructable.length} actions may have blocked scenarios`
  };
}

return { status: 'pass' };
```

**Blocks if**: Never (warning only, scenarios marked as blocked)

---

### Check 10: Previous State Clean

**What**: Verify no stale state that could interfere

```javascript
const staleIndicators = [
  '.connector-dev/<provider>/<auth_type>/.lock',
  '.connector-dev/<provider>/<auth_type>/incomplete-run.json',
];

const stale = staleIndicators.filter(exists);

if (stale.length > 0) {
  return {
    status: 'warn',
    message: `Found stale state: ${stale.join(', ')}. Consider cleaning.`
  };
}

return { status: 'pass' };
```

**Blocks if**: Never (warning only)

---

## Verdict Calculation

```javascript
function calculateVerdict(checks) {
  const failed = checks.filter(c => c.status === 'fail');
  const warned = checks.filter(c => c.status === 'warn');

  return {
    ready: failed.length === 0,
    verdict: failed.length === 0 ? 'ready' : 'blocked',
    summary: {
      passed: checks.filter(c => c.status === 'pass').length,
      failed: failed.length,
      warnings: warned.length
    },
    blocking: failed.map(c => c.message),
    warnings: warned.map(c => c.message)
  };
}
```

---

## Usage

These rules are applied by:
1. The `schema-reviewer` agent
2. The `/connector:validate` command (`validate-config` phase)
3. The build and validate gates (`gates/build.js`, `gates/conventions.js`, `gates/validate.js`)

Results written to:
```
.connector-dev/<provider>/validation/schema-review.json
```
