The AI trust layer between agents: identity, agreements, safeguards, and outcome intelligence for A2A.

Viewing files at commit 32cb43fd4001Commit detailsView latest

apps/dashboard/src/components/landing-scene.tsx 362 lines · 9.6 KB · tsx
1
import { useEffect, useRef } from 'react';
2
import { ClaspMark } from '@/components/clasp-mark';
3
4
const LUM = ' .:-=+*#%@';
5
const COLS = 88;
6
const ROWS = 40;
7
const reduceMotion =
8
  typeof matchMedia === 'function' && matchMedia('(prefers-reduced-motion: reduce)').matches;
9
10
function clockStamp() {
11
  return new Date().toISOString().slice(11, 19);
12
}
13
14
function rotateX(x: number, y: number, z: number, angle: number) {
15
  const cos = Math.cos(angle);
16
  const sin = Math.sin(angle);
17
  return [x, y * cos - z * sin, y * sin + z * cos] as const;
18
}
19
20
function rotateY(x: number, y: number, z: number, angle: number) {
21
  const cos = Math.cos(angle);
22
  const sin = Math.sin(angle);
23
  return [x * cos + z * sin, y, -x * sin + z * cos] as const;
24
}
25
26
function shade(nx: number, ny: number, nz: number) {
27
  return nx * 0.16 + ny * 0.38 + nz * 0.86;
28
}
29
30
function plot(pixels: string[][], depth: number[], x: number, y: number, z: number, light: number) {
31
  const depthOffset = 4.2;
32
  const scale = COLS * 1.04;
33
  const zz = z + depthOffset;
34
  if (zz <= 0.25) return;
35
  const inverse = 1 / zz;
36
  const column = (COLS / 2 + scale * inverse * x) | 0;
37
  const row = (ROWS / 2 - scale * inverse * y * 0.56) | 0;
38
  if (column < 0 || column >= COLS || row < 0 || row >= ROWS) return;
39
  const cell = row * COLS + column;
40
  if (inverse <= depth[cell]) return;
41
  depth[cell] = inverse;
42
  pixels[row][column] = LUM[Math.min(LUM.length - 1, Math.max(1, (1 + light * 8) | 0))] ?? '@';
43
}
44
45
function project(
46
  pixels: string[][],
47
  depth: number[],
48
  x: number,
49
  y: number,
50
  z: number,
51
  nx: number,
52
  ny: number,
53
  nz: number,
54
  yaw: number,
55
  pitch: number,
56
) {
57
  [x, y, z] = rotateY(x, y, z, yaw);
58
  [x, y, z] = rotateX(x, y, z, pitch);
59
  [nx, ny, nz] = rotateY(nx, ny, nz, yaw);
60
  [nx, ny, nz] = rotateX(nx, ny, nz, pitch);
61
  const light = shade(nx, ny, nz);
62
  if (light < 0.08) return;
63
  plot(pixels, depth, x, y, z, light);
64
}
65
66
function plotCapsule(
67
  pixels: string[][],
68
  depth: number[],
69
  x1: number,
70
  y1: number,
71
  z1: number,
72
  x2: number,
73
  y2: number,
74
  z2: number,
75
  radius: number,
76
  yaw: number,
77
  pitch: number,
78
) {
79
  const dx = x2 - x1;
80
  const dy = y2 - y1;
81
  const dz = z2 - z1;
82
  const length = Math.hypot(dx, dy, dz) || 1;
83
  const ax = dx / length;
84
  const ay = dy / length;
85
  const az = dz / length;
86
  let px = ay;
87
  let py = -ax;
88
  let pz = 0;
89
  if (Math.hypot(px, py, pz) < 0.12) {
90
    px = 0;
91
    py = az;
92
    pz = -ay;
93
  }
94
  const plen = Math.hypot(px, py, pz) || 1;
95
  px /= plen;
96
  py /= plen;
97
  pz /= plen;
98
  const qx = ay * pz - az * py;
99
  const qy = az * px - ax * pz;
100
  const qz = ax * py - ay * px;
101
102
  for (let t = 0; t <= 1; t += 0.035) {
103
    const cx = x1 + dx * t;
104
    const cy = y1 + dy * t;
105
    const cz = z1 + dz * t;
106
    for (let angle = 0; angle < Math.PI * 2; angle += 0.09) {
107
      const cos = Math.cos(angle);
108
      const sin = Math.sin(angle);
109
      const nx = px * cos + qx * sin;
110
      const ny = py * cos + qy * sin;
111
      const nz = pz * cos + qz * sin;
112
      project(
113
        pixels,
114
        depth,
115
        cx + nx * radius,
116
        cy + ny * radius,
117
        cz + nz * radius,
118
        nx,
119
        ny,
120
        nz,
121
        yaw,
122
        pitch,
123
      );
124
    }
125
  }
126
}
127
128
function plotTorusArc(
129
  pixels: string[][],
130
  depth: number[],
131
  major: number,
132
  minor: number,
133
  thetaStart: number,
134
  thetaEnd: number,
135
  yaw: number,
136
  pitch: number,
137
  flip: boolean,
138
) {
139
  for (let theta = thetaStart; theta <= thetaEnd; theta += 0.04) {
140
    const cosT = Math.cos(theta);
141
    const sinT = Math.sin(theta);
142
    for (let phi = 0; phi < Math.PI * 2; phi += 0.08) {
143
      const cosP = Math.cos(phi);
144
      const sinP = Math.sin(phi);
145
      let x = (major + minor * cosP) * cosT;
146
      let y = (major + minor * cosP) * sinT;
147
      let z = minor * sinP;
148
      let nx = cosP * cosT;
149
      let ny = cosP * sinT;
150
      let nz = sinP;
151
      if (flip) {
152
        x = -x;
153
        y = -y;
154
        z = -z;
155
        nx = -nx;
156
        ny = -ny;
157
        nz = -nz;
158
      }
159
      project(pixels, depth, x, y, z, nx, ny, nz, yaw, pitch);
160
    }
161
  }
162
}
163
164
function renderMark(yaw: number, pitch: number, pixels: string[][], depth: number[]): string {
165
  for (let y = 0; y < ROWS; y += 1) {
166
    pixels[y].fill(' ');
167
    for (let x = 0; x < COLS; x += 1) depth[y * COLS + x] = 0;
168
  }
169
170
  const major = 1.08;
171
  const minor = 0.155;
172
  const thetaStart = (102 * Math.PI) / 180;
173
  const thetaEnd = (256 * Math.PI) / 180;
174
175
  for (const flip of [false, true]) {
176
    plotTorusArc(pixels, depth, major, minor, thetaStart, thetaEnd, yaw, pitch, flip);
177
    let x1 = major * Math.cos(thetaEnd);
178
    let y1 = major * Math.sin(thetaEnd);
179
    let x2 = 0.28;
180
    let y2 = -0.68;
181
    if (flip) {
182
      x1 = -x1;
183
      y1 = -y1;
184
      x2 = -x2;
185
      y2 = -y2;
186
    }
187
    plotCapsule(pixels, depth, x1, y1, 0, x2, y2, 0, minor, yaw, pitch);
188
  }
189
190
  return pixels.map((row) => row.join('')).join('\n');
191
}
192
193
export function LandingClock() {
194
  const ref = useRef<HTMLSpanElement>(null);
195
196
  useEffect(() => {
197
    const node = ref.current;
198
    if (!node) return;
199
    const write = () => {
200
      node.textContent = clockStamp();
201
    };
202
    write();
203
    if (reduceMotion) return;
204
    const timer = window.setInterval(write, 1000);
205
    return () => window.clearInterval(timer);
206
  }, []);
207
208
  return <span ref={ref} className="landingClock" />;
209
}
210
211
export function LandingBackdrop() {
212
  const preRef = useRef<HTMLPreElement>(null);
213
214
  useEffect(() => {
215
    const node = preRef.current;
216
    if (!node) return;
217
218
    const pixels = Array.from({ length: ROWS }, () => Array<string>(COLS).fill(' '));
219
    const depth = new Array<number>(ROWS * COLS).fill(0);
220
    let time = 0;
221
222
    const paint = () => {
223
      const yaw = reduceMotion ? 0.25 : time * 0.62;
224
      const pitch = reduceMotion ? 0.18 : 0.22 + time * 0.31;
225
      node.textContent = renderMark(yaw, pitch, pixels, depth);
226
    };
227
228
    paint();
229
    if (reduceMotion) return;
230
231
    let frame = 0;
232
    let last = 0;
233
    const tick = (now: number) => {
234
      if (now - last >= 50) {
235
        last = now;
236
        time += 0.032;
237
        paint();
238
      }
239
      frame = requestAnimationFrame(tick);
240
    };
241
    frame = requestAnimationFrame(tick);
242
    return () => cancelAnimationFrame(frame);
243
  }, []);
244
245
  return (
246
    <div className="landingBackdrop" aria-hidden="true">
247
      <pre ref={preRef} className="liveAscii" />
248
    </div>
249
  );
250
}
251
252
const RISKS = [
253
  {
254
    code: '01',
255
    title: 'Spend authority is unverified',
256
    detail: 'The agent has not proved it is authorized to commit this amount.',
257
  },
258
  {
259
    code: '02',
260
    title: 'The supplier payment destination changed',
261
    detail: 'No independent verification exists for the new beneficiary account.',
262
  },
263
  {
264
    code: '03',
265
    title: 'Acceptance terms are incomplete',
266
    detail: 'The agreement does not define inspection, rejection, or refund conditions.',
267
  },
268
] as const;
269
270
function ExchangeAgent({
271
  initials,
272
  role,
273
  align,
274
}: {
275
  initials: string;
276
  role: string;
277
  align: 'start' | 'end';
278
}) {
279
  return (
280
    <article className={`exchangeAgent is-${align}`}>
281
      <span className="exchangeGlyph">{initials}</span>
282
      <strong>{role}</strong>
283
    </article>
284
  );
285
}
286
287
function ExchangeRail({ inbound }: { inbound: boolean }) {
288
  return <span className={`exchangeRail${inbound ? ' is-inbound' : ''}`} aria-hidden="true" />;
289
}
290
291
export function LandingDiagram() {
292
  return (
293
    <div
294
      className="asciiDiagram"
295
      role="group"
296
      aria-label="Example of OpenClasp sitting between two agents to predict whether an agreement will complete"
297
    >
298
      <span className="sceneCorner sceneTL">+</span>
299
      <span className="sceneCorner sceneTR">+</span>
300
      <span className="sceneCorner sceneBL">+</span>
301
      <div className="diagramHud">
302
        <span>LIVE ASSESSMENT</span>
303
        <span className="diagramLive">
304
          <LandingClock />
305
        </span>
306
      </div>
307
      <div className="exchangeLane">
308
        <ExchangeAgent initials="RA" role="requesting agent" align="start" />
309
        <ExchangeRail inbound />
310
        <div className="claspHub">
311
          <ClaspMark className="hubMark" size={52} />
312
          <strong>openclasp</strong>
313
        </div>
314
        <ExchangeRail inbound={false} />
315
        <ExchangeAgent initials="PA" role="procurement agent" align="end" />
316
      </div>
317
      <div className="exchangeStem" aria-hidden="true" />
318
      <div className="insightSubject">
319
        <span>EXACT AGREEMENT</span>
320
        <strong>Place a $24,800 inventory order by Tuesday</strong>
321
      </div>
322
      <section className="insightStream" aria-labelledby="risk-title">
323
        <div className="insightScore">
324
          <div>
325
            <span>EXPERIMENTAL SUCCESS PREDICTION</span>
326
            <strong id="risk-title">
327
              <b className="insightScoreValue">64</b>
328
              <span className="insightScoreUnit">% likely to complete</span>
329
            </strong>
330
          </div>
331
          <small>42% confidence · cold start</small>
332
        </div>
333
        <ol className="insightRisks">
334
          {RISKS.map((risk) => (
335
            <li key={risk.code}>
336
              <span>{risk.code}</span>
337
              <div>
338
                <strong>{risk.title}</strong>
339
                <p>{risk.detail}</p>
340
              </div>
341
            </li>
342
          ))}
343
        </ol>
344
        <div className="insightNext">
345
          <article>
346
            <span>ASK</span>
347
            <strong id="guidance-title">
348
              Prove the approved spend limit and verified supplier payment destination.
349
            </strong>
350
          </article>
351
          <article>
352
            <span>LOCK</span>
353
            <strong>Human approval · lock supplier and maximum spend</strong>
354
          </article>
355
        </div>
356
      </section>
357
      <p className="diagramSpec">
358
        no conversation storage · predictions are advisory, not guarantees
359
      </p>
360
    </div>
361
  );
362
}