File size: 11,156 Bytes
518343a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
/**
 * axis_collision.test.ts
 * Doctrine v6 R3 — Vertical Governance Receipts
 * 10 Axis-Collision adversarial tests
 *
 * Tests for conflicts, contradictions, and impossible combinations across
 * Λ-axis assignments in policy documents. Collision scenarios include:
 * - Same clause mapping to conflicting axes
 * - mandatory_axes referencing axes not present in any clause
 * - Axis weight distribution violations (e.g., all weights = 0)
 * - Cross-policy axis coverage below minimum_lambda_coverage
 * - mandatory axis appearing only with enforcement: "informational"
 *
 * Some of these MUST be detected at the policy_loader level (semantic validation),
 * since JSON Schema alone cannot express cross-field semantic constraints.
 *
 * Test framework: Jest / ts-jest
 * Run: npx jest tests/adversarial/axis_collision.test.ts
 */

import Ajv from "ajv";
import addFormats from "ajv-formats";
import * as fs from "fs";
import * as path from "path";

const SCHEMA_PATH = path.resolve(__dirname, "../../a11oy-knowledge.schema.json");

function buildValidator() {
  const ajv = new Ajv({ allErrors: true, strict: false });
  addFormats(ajv);
  const schema = JSON.parse(fs.readFileSync(SCHEMA_PATH, "utf8"));
  return ajv.compile(schema);
}

/** Compute distinct Λ-axes covered by all clauses */
function coveredAxes(clauses: any[]): Set<string> {
  const axes = new Set<string>();
  for (const c of clauses) {
    for (const m of c.lambda_axes ?? []) axes.add(m.axis);
  }
  return axes;
}

/** Semantic check: mandatory_axes must be covered by clauses */
function mandatoryAxesCovered(policy: any): boolean {
  const covered = coveredAxes(policy.regulatory_clauses ?? []);
  const mandatory: string[] = policy.compliance_thresholds?.mandatory_axes ?? [];
  return mandatory.every((ax) => covered.has(ax));
}

/** Semantic check: covered axis count ≥ minimum_lambda_coverage */
function coverageAboveMinimum(policy: any): boolean {
  const covered = coveredAxes(policy.regulatory_clauses ?? []);
  return covered.size >= (policy.compliance_thresholds?.minimum_lambda_coverage ?? 0);
}

/**
 * Semantic check: EVERY mandatory axis must carry at least one mandatory
 * enforcement mapping. Returns false if any axis listed in mandatory_axes is
 * either never mapped or appears only with non-mandatory enforcement
 * (informational / recommended). The earlier implementation returned true on
 * the first mandatory axis that happened to have a mandatory mapping, which
 * masked the case where a different mandatory axis had been downgraded.
 */
function mandatoryAxisHasMandatoryEnforcement(policy: any): boolean {
  const mandatory = new Set<string>(policy.compliance_thresholds?.mandatory_axes ?? []);
  if (mandatory.size === 0) return true;
  const axisHasMandatoryMapping = new Map<string, boolean>();
  for (const ax of mandatory) axisHasMandatoryMapping.set(ax, false);
  for (const clause of policy.regulatory_clauses ?? []) {
    for (const mapping of clause.lambda_axes ?? []) {
      if (mandatory.has(mapping.axis) && mapping.enforcement === "mandatory") {
        axisHasMandatoryMapping.set(mapping.axis, true);
      }
    }
  }
  for (const ax of mandatory) {
    if (!axisHasMandatoryMapping.get(ax)) return false;
  }
  return true;
}

function basePolicy(): any {
  return {
    schema_version: "1.0.0",
    vertical: "energy",
    regime: "NERC-CIP/FERC-887",
    effective_date: "2025-07-01",
    jurisdiction: "US-FERC/NERC",
    meta: {
      title: "Axis collision base policy for adversarial testing",
      description: "Policy used for axis collision adversarial scenario testing.",
      authority: "NERC CIP-002 through CIP-014; FERC Order No. 887",
      receipt_chain_required: true,
      merkle_root_algorithm: "SHA3-256",
    },
    regulatory_clauses: Array.from({ length: 8 }, (_, i) => ({
      clause_id: `NERC-COL-${String(i + 1).padStart(3, "0")}`,
      title: `Collision test clause ${i + 1}`,
      citation: `18 CFR § 40.${i + 1}`,
      full_ref: `18 C.F.R. § 40.${i + 1} — axis collision test clause with full reference detail`,
      lambda_axes: [
        { axis: "Λ6", label: "Security", weight: 0.9, enforcement: "mandatory",
          rationale: "BES Cyber System protection requires mandatory security receipt logging." },
        { axis: "Λ7", label: "Auditability", weight: 0.85, enforcement: "mandatory",
          rationale: "CIP-007-6 audit log tamper-evidence via Merkle DAG." },
      ],
    })),
    compliance_thresholds: {
      minimum_lambda_coverage: 7,
      mandatory_axes: ["Λ5", "Λ6", "Λ7"],
      receipt_retention_days: 2190,
    },
    receipt_chain: {
      algorithm: "SHA3-256",
      chaining: "merkle_dag",
      quorum: "2-of-3",
      nodes: ["primary", "air-gapped-ot", "regulatory-archive"],
    },
  };
}

describe("Adversarial — Axis Collision (10 tests)", () => {
  const validate = buildValidator();

  // AC-001: mandatory_axes include Λ5 but no clause maps to Λ5 — semantic violation
  test("AC-001: mandatory axis Λ5 not covered by any clause — semantic violation detected", () => {
    const p = basePolicy();
    // All clauses only cover Λ6, Λ7 — Λ5 is mandatory but uncovered
    // Schema accepts this; semantic check must reject
    expect(validate(p)).toBe(true);                   // Schema: OK
    expect(mandatoryAxesCovered(p)).toBe(false);       // Semantic: FAIL
  });

  // AC-002: minimum_lambda_coverage = 5 but only 2 distinct axes present — coverage gap
  test("AC-002: covered axes (2) < minimum_lambda_coverage (5) — semantic violation", () => {
    const p = basePolicy();
    p.compliance_thresholds.minimum_lambda_coverage = 5;
    // Clauses cover only Λ6 and Λ7 (2 axes) — below minimum 5
    expect(validate(p)).toBe(true);
    expect(coverageAboveMinimum(p)).toBe(false);
  });

  // AC-003: Same clause maps same Λ-axis twice (duplicate axis in lambda_axes)
  test("AC-003: duplicate Λ7 in same clause lambda_axes — schema accepts, loader must deduplicate", () => {
    const p = basePolicy();
    p.regulatory_clauses[0].lambda_axes = [
      { axis: "Λ7", label: "Auditability", weight: 1.0, enforcement: "mandatory",
        rationale: "First mapping for Λ7 in this clause for deduplication test." },
      { axis: "Λ7", label: "Auditability", weight: 0.5, enforcement: "recommended",
        rationale: "Second mapping for Λ7 — duplicate with conflicting weight." },
    ];
    // Schema allows up to 4 axis mappings per clause without uniqueItems constraint on axis
    expect(validate(p)).toBe(true);
    // Loader must detect and reject or merge duplicate axis mappings
    const dup = p.regulatory_clauses[0].lambda_axes.filter((m: any) => m.axis === "Λ7");
    expect(dup.length).toBe(2); // Two mappings to same axis — collision detected
  });

  // AC-004: mandatory axis appearing ONLY with enforcement "informational" — enforcement gap
  test("AC-004: mandatory axis Λ6 only with informational enforcement — enforcement collision", () => {
    const p = basePolicy();
    // Change all Λ6 mappings to informational
    for (const clause of p.regulatory_clauses) {
      for (const m of clause.lambda_axes) {
        if (m.axis === "Λ6") m.enforcement = "informational";
      }
    }
    expect(validate(p)).toBe(true);  // Schema accepts any enforcement value
    expect(mandatoryAxisHasMandatoryEnforcement(p)).toBe(false); // Semantic fail
  });

  // AC-005: All weights set to 0.0 — zero-weight mandatory axis
  test("AC-005: all lambda weights = 0.0 — policy carries no compliance signal", () => {
    const p = basePolicy();
    for (const clause of p.regulatory_clauses) {
      for (const m of clause.lambda_axes) { m.weight = 0.0; }
    }
    expect(validate(p)).toBe(true);  // Schema: 0.0 is valid minimum
    // Compute total weight for mandatory axes
    const totalMandatoryWeight = p.regulatory_clauses
      .flatMap((c: any) => c.lambda_axes)
      .filter((m: any) => m.enforcement === "mandatory")
      .reduce((sum: number, m: any) => sum + m.weight, 0);
    expect(totalMandatoryWeight).toBe(0); // Semantic warning: all mandatory weights zero
  });

  // AC-006: minimum_lambda_coverage > 10 (impossible — only 10 axes exist)
  test("AC-006: minimum_lambda_coverage = 11 exceeds available axes (schema rejects > 10)", () => {
    const p = basePolicy();
    p.compliance_thresholds.minimum_lambda_coverage = 11;
    expect(validate(p)).toBe(false); // Schema enforces maximum: 10
  });

  // AC-007: Label mismatch — axis "Λ1" labeled "Privacy" (Λ3's label)
  test("AC-007: Λ1 axis labeled 'Privacy' — axis↔label mismatch (schema accepts, loader rejects)", () => {
    const p = basePolicy();
    p.regulatory_clauses[0].lambda_axes.push({
      axis: "Λ1",
      label: "Privacy",  // Correct label for Λ1 is "Transparency"
      weight: 0.5,
      enforcement: "recommended",
      rationale: "Incorrect label test for axis-label collision detection scenario.",
    });
    expect(validate(p)).toBe(true); // Schema: both "Λ1" and "Privacy" are valid enum values
    // Policy loader must validate axis↔label coherence using the axis-label map
  });

  // AC-008: Cross-vertical vertical+regime mismatch (healthcare vertical with SOX regime)
  test("AC-008: vertical 'healthcare' with regime 'SOX/Dodd-Frank' — semantic mismatch", () => {
    const p = basePolicy();
    p.vertical = "healthcare";
    p.regime = "SOX/Dodd-Frank";
    expect(validate(p)).toBe(true); // Schema: no cross-field constraint
    // Loader must check vertical↔regime coherence matrix
  });

  // AC-009: receipt_chain quorum 5-of-3 (quorum numerator > denominator — impossible)
  test("AC-009: quorum '5-of-3' (impossible — 5 signatures from 3 nodes) is accepted by schema pattern", () => {
    const p = basePolicy();
    p.receipt_chain.quorum = "5-of-3";
    // Pattern ^\d+-of-\d+$ matches — schema accepts
    expect(validate(p)).toBe(true);
    // Extract and validate quorum numerically
    const [n, d] = p.receipt_chain.quorum.split("-of-").map(Number);
    expect(n).toBeGreaterThan(d); // Semantic violation: impossible quorum
  });

  // AC-010: mandatory_axes contains Λ10 but jurisdiction is "US-Federal" (no sovereignty concern)
  test("AC-010: Λ10 (Sovereignty) mandatory for domestic US-Federal jurisdiction — semantic question", () => {
    const p = basePolicy();
    p.jurisdiction = "US-Federal";
    p.compliance_thresholds.mandatory_axes = ["Λ5", "Λ6", "Λ7", "Λ10"];
    // Schema: no constraint — passes
    expect(validate(p)).toBe(true);
    // Loader may issue warning: Λ10 (Sovereignty) is typically relevant for
    // cross-border/international jurisdictions. Flagging for human review.
    const hasSovereignty = p.compliance_thresholds.mandatory_axes.includes("Λ10");
    const isInternational = !p.jurisdiction.startsWith("US-Federal");
    // Not an error but a review flag
    if (hasSovereignty && !isInternational) {
      // Expect loader to emit a WARNING (not error) for sovereignty axis on domestic regime
      expect(true).toBe(true); // test documents the pattern
    }
  });
});