File size: 5,301 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
// SPDX-License-Identifier: Apache-2.0
// © 2026 Lutar, Stephen P. — SZL Holdings
// ORCID: 0009-0001-0110-4173
//
// Layer 6 — a11oy policy gate for AdversarialRobustness
//
// Policy rationale:
//   A composed pipeline is allowed to deploy only when the end-to-end
//   adversarial output perturbation bound ε₂ = L₁·L₂·δ is at or below the
//   configured maximum tolerable perturbation. The Lean theorem proves that
//   if each component is (δ,ε)-robust, the composition is (δ,ε₂)-robust.
//   This gate enforces that the composed system does not amplify adversarial
//   inputs beyond the policy tolerance.
//
//   Lean theorem cited: `robustness_preserved_by_composition`
//   Lean file: Lutar/Composition/AdversarialRobustness.lean
//   Lean commit SHA: 1dca00032dfc9aa8559cc6c2e4b63192fcf52371
//
//   Policy: if adversarialRobustness(opts).epsilon2 ≤ maxEpsilon → allow; else → deny
//
// References:
//   Lean: szl-holdings/lutar-lean Lutar/Composition/AdversarialRobustness.lean
//   Runtime: szl-holdings/ouroboros agentic/formulas/adversarialRobustness.ts
//   Madry et al. 2018, arXiv:1706.06083

export interface PolicyDecision {
  allow:             boolean;
  rationale:         string;
  formula:           string;
  leanTheorem:       string;
  leanFile:          string;
  leanCommitSha:     string;
  epsilon2:          number;
  composedLipschitz: number;
  maxEpsilon:        number;
  lambdaScore:       number;
}

export interface AdversarialRobustnessGateConfig {
  /**
   * Maximum tolerable ε₂ (end-to-end output perturbation).
   * Default: 1.0 (unit ball tolerance).
   */
  maxEpsilon?: number;
}

export interface AdversarialRobustnessGateOpts {
  lipschitz1: number;
  lipschitz2: number;
  delta: number;
}

const LEAN_THEOREM  = "robustness_preserved_by_composition";
const LEAN_FILE     = "Lutar/Composition/AdversarialRobustness.lean";
const LEAN_COMMIT   = "1dca00032dfc9aa8559cc6c2e4b63192fcf52371";
const DEFAULT_EPS   = 1.0;

// ── Inline formula ────────────────────────────────────────────────────────────
// Lean: robustness_preserved_by_composition:
//   IsRobust mX mY f δ ε₁ → IsRobust mY mZ g ε₁ ε₂ → IsRobust mX mZ (f∘g) δ ε₂
//
// Note (PhD audit 2026-05-29): the Lean theorem is stated for abstract metric
// spaces and does not require Lipschitz structure. This gate computes the
// Lipschitz special case: if S₁ has Lipschitz constant L₁, then ε₁ = L₁·δ; if
// S₂ has Lipschitz constant L₂, then ε₂ = L₂·ε₁ = L₁·L₂·δ. Consumers that need
// non-Lipschitz robustness certificates should instantiate the Lean theorem
// directly with their metric model rather than treating this numeric gate as
// the full theorem.
function _composedEpsilon(l1: number, l2: number, delta: number): { epsilon2: number; composedLipschitz: number } {
  return { epsilon2: l1 * l2 * delta, composedLipschitz: l1 * l2 };
}

/**
 * AdversarialRobustness policy gate.
 *
 * Allows pipeline deployment only when the composed adversarial output
 * perturbation bound ε₂ ≤ maxEpsilon.
 *
 * Lean theorem: `robustness_preserved_by_composition`
 * Lean file: Lutar/Composition/AdversarialRobustness.lean (commit 1dca00032dfc9aa8559cc6c2e4b63192fcf52371)
 */
export function adversarialRobustnessGate(
  config: AdversarialRobustnessGateConfig = {}
): (opts: AdversarialRobustnessGateOpts) => PolicyDecision {
  const maxEpsilon = config.maxEpsilon ?? DEFAULT_EPS;
  if (!Number.isFinite(maxEpsilon) || maxEpsilon < 0) {
    throw new Error(`AdversarialRobustnessGate: maxEpsilon must be ≥ 0; got ${maxEpsilon}`);
  }

  return function gate(opts: AdversarialRobustnessGateOpts): PolicyDecision {
    const { lipschitz1, lipschitz2, delta } = opts;
    if (lipschitz1 <= 0 || !Number.isFinite(lipschitz1)) {
      throw new Error(`AdversarialRobustnessGate: lipschitz1 must be > 0; got ${lipschitz1}`);
    }
    if (lipschitz2 <= 0 || !Number.isFinite(lipschitz2)) {
      throw new Error(`AdversarialRobustnessGate: lipschitz2 must be > 0; got ${lipschitz2}`);
    }
    if (delta <= 0 || !Number.isFinite(delta)) {
      throw new Error(`AdversarialRobustnessGate: delta must be > 0; got ${delta}`);
    }

    const { epsilon2, composedLipschitz } = _composedEpsilon(lipschitz1, lipschitz2, delta);
    const lambdaScore = 1 / (1 + epsilon2);
    const allow = epsilon2 <= maxEpsilon;

    const rationale = allow
      ? `AdversarialRobustness ε₂ = ${epsilon2.toExponential(4)} ≤ maxEpsilon ${maxEpsilon}: ` +
        `composed pipeline is (${delta},${epsilon2})-robust. Lean: ${LEAN_THEOREM} @${LEAN_COMMIT.slice(0, 12)}`
      : `AdversarialRobustness ε₂ = ${epsilon2.toExponential(4)} > maxEpsilon ${maxEpsilon}: ` +
        `perturbation amplification exceeds policy tolerance — deny deployment. Lean: ${LEAN_THEOREM} @${LEAN_COMMIT.slice(0, 12)}`;

    return {
      allow,
      rationale,
      formula:           "AdversarialRobustness",
      leanTheorem:       LEAN_THEOREM,
      leanFile:          LEAN_FILE,
      leanCommitSha:     LEAN_COMMIT,
      epsilon2,
      composedLipschitz,
      maxEpsilon,
      lambdaScore,
    };
  };
}