@phosphorco/bb-identity 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -64,6 +64,29 @@ function retainIdentityHttpResponse(response, invocationSignal, requestSignal, r
64
64
  // bb-upstream-runtime.ts
65
65
  import { z } from "zod";
66
66
 
67
+ // external-person-output-runtime.ts
68
+ function record(value) {
69
+ return value !== null && typeof value === "object" && !Array.isArray(value);
70
+ }
71
+ function normalizeExternalPersonOutput(value) {
72
+ if (!record(value) || !record(value.identity) || !record(value.presentation))
73
+ return value;
74
+ return {
75
+ identity: { kind: value.identity.kind, key: value.identity.key, issuer: value.identity.issuer, subject: value.identity.subject },
76
+ presentation: { displayName: value.presentation.displayName, handle: value.presentation.handle, avatarUrl: value.presentation.avatarUrl },
77
+ evidence: value.evidence
78
+ };
79
+ }
80
+ function normalizeContributionOutput(value) {
81
+ return record(value) && value.personAttribution !== undefined ? { ...value, personAttribution: normalizeExternalPersonOutput(value.personAttribution) } : value;
82
+ }
83
+ function normalizeProvenanceOutput(value) {
84
+ return record(value) && Array.isArray(value.contributions) ? { ...value, contributions: value.contributions.map(normalizeContributionOutput) } : value;
85
+ }
86
+ function normalizeEvidencePageOutput(value, normalize) {
87
+ return record(value) && Array.isArray(value.items) ? { ...value, items: value.items.map(normalize) } : value;
88
+ }
89
+
67
90
  // model-runtime.ts
68
91
  var ID_MAX_LENGTH = 256;
69
92
  var ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:@/-]*$/;
@@ -230,6 +253,7 @@ function hasCompleteCausality(groups, contributions, missing) {
230
253
  }
231
254
  var provenanceCodec = {
232
255
  decode(input) {
256
+ input = normalizeProvenanceOutput(input);
233
257
  if (!isRecord(input) || input.status !== "known" && input.status !== "partial" && input.status !== "unknown") {
234
258
  return err("invalid-input", "Invalid execution provenance.");
235
259
  }
@@ -407,39 +431,39 @@ function createBbUpstreamDriver(options) {
407
431
  const active = new Set;
408
432
  const listeners = new Set;
409
433
  const labelledExternal = new WeakSet;
410
- const terminateScope = (record, scope) => {
434
+ const terminateScope = (record2, scope) => {
411
435
  if (!scope || scope.released)
412
436
  return;
413
437
  scope.released = true;
414
- record.controller.signal.removeEventListener("abort", scope.parentAbort);
438
+ record2.controller.signal.removeEventListener("abort", scope.parentAbort);
415
439
  scope.controller.abort();
416
- record.scopes.delete(scope);
440
+ record2.scopes.delete(scope);
417
441
  };
418
- const terminate = (record) => {
419
- for (const scope of [...record.scopes])
420
- terminateScope(record, scope);
421
- record.controller.abort();
422
- active.delete(record);
442
+ const terminate = (record2) => {
443
+ for (const scope of [...record2.scopes])
444
+ terminateScope(record2, scope);
445
+ record2.controller.abort();
446
+ active.delete(record2);
423
447
  };
424
448
  const known = (context) => {
425
- const record = records.get(context);
426
- return !record ? err("invalid-input", "Invocation context was not issued by this binding.") : disposed ? err("disposed", "Upstream binding is disposed.") : record.controller.signal.aborted ? err("expired", "Invocation context has expired.") : ok(record);
449
+ const record2 = records.get(context);
450
+ return !record2 ? err("invalid-input", "Invocation context was not issued by this binding.") : disposed ? err("disposed", "Upstream binding is disposed.") : record2.controller.signal.aborted ? err("expired", "Invocation context has expired.") : ok(record2);
427
451
  };
428
452
  const admission = (context) => {
429
- const record = known(context);
430
- if (!record.ok)
431
- return record;
432
- if (record.value.origin === "external")
453
+ const record2 = known(context);
454
+ if (!record2.ok)
455
+ return record2;
456
+ if (record2.value.origin === "external")
433
457
  return err("unauthenticated", "External upstream work uses an explicit external source.");
434
- return record;
458
+ return record2;
435
459
  };
436
460
  const session = (context) => {
437
- const record = known(context);
438
- if (!record.ok)
439
- return unavailableSession(instanceId, record.error.code === "invalid-input" ? "incompatible" : "unauthenticated", record.error.message);
440
- if (record.value.origin === "interactive-user")
461
+ const record2 = known(context);
462
+ if (!record2.ok)
463
+ return unavailableSession(instanceId, record2.error.code === "invalid-input" ? "incompatible" : "unauthenticated", record2.error.message);
464
+ if (record2.value.origin === "interactive-user")
441
465
  return readySession();
442
- return record.value.origin === "background" ? machineSession() : unavailableSession(instanceId, "unauthenticated", "External upstream work uses an explicit external source.");
466
+ return record2.value.origin === "background" ? machineSession() : unavailableSession(instanceId, "unauthenticated", "External upstream work uses an explicit external source.");
443
467
  };
444
468
  const driver = {
445
469
  instanceId,
@@ -457,20 +481,20 @@ function createBbUpstreamDriver(options) {
457
481
  const allowed = admission(context);
458
482
  if (!allowed.ok)
459
483
  return allowed;
460
- const record = allowed.value;
484
+ const record2 = allowed.value;
461
485
  let scope;
462
- const parentAbort = () => terminateScope(record, scope);
486
+ const parentAbort = () => terminateScope(record2, scope);
463
487
  scope = { controller: new AbortController, parentAbort, released: false };
464
- record.scopes.add(scope);
465
- const expire = () => terminateScope(record, scope);
466
- record.controller.signal.addEventListener("abort", parentAbort, { once: true });
467
- const exposedSession = record.origin === "background" ? machineSession() : readySession();
468
- const expectedKey = record.origin === "background" ? machineKey.value : singletonKey;
488
+ record2.scopes.add(scope);
489
+ const expire = () => terminateScope(record2, scope);
490
+ record2.controller.signal.addEventListener("abort", parentAbort, { once: true });
491
+ const exposedSession = record2.origin === "background" ? machineSession() : readySession();
492
+ const expectedKey = record2.origin === "background" ? machineKey.value : singletonKey;
469
493
  return ok({
470
494
  signal: scope.controller.signal,
471
495
  session: exposedSession,
472
496
  validate(expected) {
473
- if (scope.released || scope.controller.signal.aborted || record.controller.signal.aborted || disposed) {
497
+ if (scope.released || scope.controller.signal.aborted || record2.controller.signal.aborted || disposed) {
474
498
  return err("expired", "Upstream request context is no longer live.");
475
499
  }
476
500
  return expected.actor === expectedKey && expected.session === stamp.value ? ok(undefined) : err("stale-context", "Request expectation does not match the singleton session.");
@@ -554,22 +578,22 @@ function createBbUpstreamDriver(options) {
554
578
  throw new Error("Upstream binding is disposed.");
555
579
  const controller = new AbortController;
556
580
  const invocation = Object.freeze({ origin, signal: controller.signal });
557
- const record = { origin, controller, scopes: new Set };
558
- records.set(invocation, record);
559
- active.add(record);
581
+ const record2 = { origin, controller, scopes: new Set };
582
+ records.set(invocation, record2);
583
+ active.add(record2);
560
584
  return invocation;
561
585
  },
562
586
  release(invocation) {
563
- const record = records.get(invocation);
564
- if (record)
565
- terminate(record);
587
+ const record2 = records.get(invocation);
588
+ if (record2)
589
+ terminate(record2);
566
590
  },
567
591
  dispose() {
568
592
  if (disposed)
569
593
  return;
570
594
  disposed = true;
571
- for (const record of [...active])
572
- terminate(record);
595
+ for (const record2 of [...active])
596
+ terminate(record2);
573
597
  for (const listener of [...listeners]) {
574
598
  try {
575
599
  listener({ kind: "disposed" });
@@ -858,11 +882,11 @@ function createEnhancedDirectory(protocol) {
858
882
  }
859
883
  mappings.set(key, mapping);
860
884
  };
861
- const profile = (source, record, revision) => {
862
- if (!isRecord3(record) || !source.issuers.includes(record.issuer) || !idCodec("issuer").decode(record.issuer).ok || !idCodec("subject").decode(record.subject).ok || !isProfilePresentation(record.presentation) || record.status !== "current" && record.status !== "historical" || !idCodec("revision").decode(revision).ok)
885
+ const profile = (source, record2, revision) => {
886
+ if (!isRecord3(record2) || !source.issuers.includes(record2.issuer) || !idCodec("issuer").decode(record2.issuer).ok || !idCodec("subject").decode(record2.subject).ok || !isProfilePresentation(record2.presentation) || record2.status !== "current" && record2.status !== "historical" || !idCodec("revision").decode(revision).ok)
863
887
  return err("incompatible", "Provider directory record is malformed.");
864
- const issuer = record.issuer;
865
- const subject = record.subject;
888
+ const issuer = record2.issuer;
889
+ const subject = record2.subject;
866
890
  if (typeof issuer !== "string" || typeof subject !== "string")
867
891
  return err("incompatible", "Provider directory record is malformed.");
868
892
  let person;
@@ -873,10 +897,10 @@ function createEnhancedDirectory(protocol) {
873
897
  }
874
898
  if (!person.ok)
875
899
  return person;
876
- if (person.value.kind !== "person" || person.value.issuer !== record.issuer || person.value.subject !== record.subject || !identityKeyCodec.decode(person.value.key).ok) {
900
+ if (person.value.kind !== "person" || person.value.issuer !== record2.issuer || person.value.subject !== record2.subject || !identityKeyCodec.decode(person.value.key).ok) {
877
901
  return err("incompatible", "Provider directory returned an invalid person reference.");
878
902
  }
879
- return profileCodec.decode({ identity: person.value, presentation: record.presentation, revision, status: record.status });
903
+ return profileCodec.decode({ identity: person.value, presentation: record2.presentation, revision, status: record2.status });
880
904
  };
881
905
  return {
882
906
  async search(input, options) {
@@ -902,8 +926,8 @@ function createEnhancedDirectory(protocol) {
902
926
  if (!Array.isArray(result.value.records) || result.value.records.length > input.limit || result.value.nextCursor !== null && !idCodec("cursor").decode(result.value.nextCursor).ok || !idCodec("revision").decode(result.value.revision).ok)
903
927
  return err("incompatible", "Provider directory page is malformed.");
904
928
  const profiles = [];
905
- for (const record of result.value.records) {
906
- const decoded = profile(current.value, record, result.value.revision);
929
+ for (const record2 of result.value.records) {
930
+ const decoded = profile(current.value, record2, result.value.revision);
907
931
  if (!decoded.ok)
908
932
  return decoded;
909
933
  if (input.history === "current" && decoded.value.status !== "current")
@@ -1212,7 +1236,7 @@ function createHostAdapter(options) {
1212
1236
  } catch {
1213
1237
  return err("unavailable", "Contribution history is unavailable.", "after-reconnect");
1214
1238
  }
1215
- return result.ok ? decodeEvidencePage(result.value, isContribution2) : result;
1239
+ return result.ok ? decodeEvidencePage(normalizeEvidencePageOutput(result.value, normalizeContributionOutput), isContribution2) : result;
1216
1240
  },
1217
1241
  async attempts(query, readOptions) {
1218
1242
  if (!validAttemptQuery(query))
@@ -1225,7 +1249,7 @@ function createHostAdapter(options) {
1225
1249
  } catch {
1226
1250
  return err("unavailable", "Execution history is unavailable.", "after-reconnect");
1227
1251
  }
1228
- return result.ok ? decodeEvidencePage(result.value, (item) => provenanceCodec.decode(item).ok) : result;
1252
+ return result.ok ? decodeEvidencePage(normalizeEvidencePageOutput(result.value, normalizeProvenanceOutput), (item) => provenanceCodec.decode(item).ok) : result;
1229
1253
  }
1230
1254
  };
1231
1255
  const subscription = protocol.subscribe((event) => {
@@ -1322,16 +1346,12 @@ function createHostAdapter(options) {
1322
1346
  };
1323
1347
  }
1324
1348
  function decodeExternalContribution(value, strict = true) {
1325
- if (!isRecord3(value) || value.version !== 1 || !isRecord3(value.author) || strict && Object.keys(value).some((key) => !["version", "author", "personAttribution"].includes(key)) || strict && Object.keys(value.author).some((key) => !["subject", "presentation"].includes(key)) || strict && isRecord3(value.author.presentation) && Object.keys(value.author.presentation).some((key) => !["displayName", "handle", "avatarUrl"].includes(key)) || !idCodec("subject").decode(value.author.subject).ok || !isProfilePresentation(value.author.presentation)) {
1349
+ if (!isRecord3(value) || value.version !== 1 || !isRecord3(value.author) || strict && Object.keys(value).some((key) => !["version", "author", "personAttribution"].includes(key)) || strict && Object.keys(value.author).some((key) => !["subject", "presentation"].includes(key)) || strict && isRecord3(value.author.presentation) && Object.keys(value.author.presentation).some((key) => !["displayName", "handle", "avatarUrl"].includes(key)) || (typeof value.author.subject !== "string" || value.author.subject.length === 0 || value.author.subject.length > 256) || !isProfilePresentation(value.author.presentation)) {
1326
1350
  return err("invalid-input", "Invalid V1 external contribution.");
1327
1351
  }
1328
1352
  let person = value.personAttribution;
1329
- if (!strict && isRecord3(person) && isRecord3(person.identity))
1330
- person = {
1331
- identity: { kind: person.identity.kind, key: person.identity.key, issuer: person.identity.issuer, subject: person.identity.subject },
1332
- presentation: isRecord3(person.presentation) ? { displayName: person.presentation.displayName, handle: person.presentation.handle, avatarUrl: person.presentation.avatarUrl } : person.presentation,
1333
- evidence: person.evidence
1334
- };
1353
+ if (!strict)
1354
+ person = normalizeExternalPersonOutput(person);
1335
1355
  if (strict && isRecord3(person) && isRecord3(person.presentation) && Object.keys(person.presentation).some((key) => !["displayName", "handle", "avatarUrl"].includes(key)))
1336
1356
  return err("invalid-input", "Unexpected presentation field.");
1337
1357
  const decoded = person === undefined ? null : externalPersonSnapshotCodec.decode(person);
@@ -1354,6 +1374,16 @@ function canonicalExternal(value) {
1354
1374
  return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonicalExternal(value[key])}`).join(",")}}`;
1355
1375
  return JSON.stringify(value);
1356
1376
  }
1377
+ function decodeExternalContributionOutcomeV1(raw) {
1378
+ const decoded = decodeAcceptance(raw);
1379
+ if (!decoded.ok || decoded.value.status !== "submitted")
1380
+ return decoded;
1381
+ if (decoded.value.receipt.evidence !== "host-accepted")
1382
+ return err("incompatible", "V1 needs durable native acceptance.");
1383
+ const receipt = decoded.value.receipt;
1384
+ const contribution = decodeExternalContribution(receipt.externalContribution, false);
1385
+ return contribution.ok ? ok({ status: "submitted", receipt: { ...decoded.value.receipt, externalContribution: contribution.value } }) : err("incompatible", "Host receipt omitted or changed V1 attribution.");
1386
+ }
1357
1387
  function externalContributionMethods(upstream, extension, disposed) {
1358
1388
  const support = () => {
1359
1389
  if (disposed())
@@ -1373,16 +1403,6 @@ function externalContributionMethods(upstream, extension, disposed) {
1373
1403
  return err("incompatible", "Malformed V1 external contribution capability.");
1374
1404
  return ok(value);
1375
1405
  };
1376
- const outcome = (raw) => {
1377
- const decoded = decodeAcceptance(raw);
1378
- if (!decoded.ok || decoded.value.status !== "submitted")
1379
- return decoded;
1380
- if (decoded.value.receipt.evidence !== "host-accepted")
1381
- return err("incompatible", "V1 needs durable native acceptance.");
1382
- const receipt = decoded.value.receipt;
1383
- const contribution = decodeExternalContribution(receipt.externalContribution, false);
1384
- return contribution.ok ? ok({ status: "submitted", receipt: { ...decoded.value.receipt, externalContribution: contribution.value } }) : err("incompatible", "Host receipt omitted or changed V1 attribution.");
1385
- };
1386
1406
  return {
1387
1407
  externalContributionCapabilities() {
1388
1408
  const negotiated = support();
@@ -1403,7 +1423,7 @@ function externalContributionMethods(upstream, extension, disposed) {
1403
1423
  const frozen = JSON.parse(JSON.stringify(decoded.value));
1404
1424
  const wire = JSON.parse(JSON.stringify(wireInput(input, upstream.inputCodec)));
1405
1425
  try {
1406
- const result = outcome(await negotiated.value.acceptV1({ contribution: frozen, input: wire }, options));
1426
+ const result = decodeExternalContributionOutcomeV1(await negotiated.value.acceptV1({ contribution: frozen, input: wire }, options));
1407
1427
  if (!result.ok)
1408
1428
  return { status: "indeterminate", operationId, message: result.error.message };
1409
1429
  if (result.value.status === "submitted" && (result.value.receipt.operationId !== operationId || canonicalExternal(result.value.receipt.externalContribution) !== canonicalExternal(frozen))) {
@@ -1425,7 +1445,7 @@ function externalContributionMethods(upstream, extension, disposed) {
1425
1445
  if (!result.ok)
1426
1446
  return result;
1427
1447
  if (isRecord3(result.value) && result.value.status === "final") {
1428
- const decoded = outcome(result.value.outcome);
1448
+ const decoded = decodeExternalContributionOutcomeV1(result.value.outcome);
1429
1449
  if (decoded.ok && decoded.value.status === "submitted" && decoded.value.receipt.operationId !== operationId) {
1430
1450
  return err("incompatible", "Host lookup returned a receipt for another operation.");
1431
1451
  }
@@ -2417,8 +2437,8 @@ function createBbIdentityServerBinding(options) {
2417
2437
  const invocations = new WeakMap;
2418
2438
  const expired = () => err("expired", "Invocation has already settled.");
2419
2439
  const recordFor = (invocation) => {
2420
- const record = invocations.get(invocation);
2421
- return disposed || !record || record.settled || record.signal.aborted ? expired() : ok(record);
2440
+ const record2 = invocations.get(invocation);
2441
+ return disposed || !record2 || record2.settled || record2.signal.aborted ? expired() : ok(record2);
2422
2442
  };
2423
2443
  const unavailableSession2 = () => ({
2424
2444
  status: "unavailable",
@@ -2426,25 +2446,25 @@ function createBbIdentityServerBinding(options) {
2426
2446
  error: { code: "expired", message: "Invocation has already settled.", retry: "after-refresh" }
2427
2447
  });
2428
2448
  const openPerson = async (invocation) => {
2429
- const record = recordFor(invocation);
2430
- if (!record.ok)
2431
- return record;
2432
- const opened = await server.personRequest(record.value.request);
2433
- if (record.value.settled || record.value.signal.aborted) {
2449
+ const record2 = recordFor(invocation);
2450
+ if (!record2.ok)
2451
+ return record2;
2452
+ const opened = await server.personRequest(record2.value.request);
2453
+ if (record2.value.settled || record2.value.signal.aborted) {
2434
2454
  if (opened.ok)
2435
2455
  opened.value.dispose();
2436
2456
  return expired();
2437
2457
  }
2438
2458
  if (opened.ok)
2439
- record.value.people.add(opened.value);
2459
+ record2.value.people.add(opened.value);
2440
2460
  return opened;
2441
2461
  };
2442
2462
  const guardEndpoint = async (invocation, operation) => {
2443
- const record = recordFor(invocation);
2444
- if (!record.ok)
2445
- return record;
2446
- const result = await operation(record.value.request);
2447
- return record.value.settled || record.value.signal.aborted ? expired() : result;
2463
+ const record2 = recordFor(invocation);
2464
+ if (!record2.ok)
2465
+ return record2;
2466
+ const result = await operation(record2.value.request);
2467
+ return record2.value.settled || record2.value.signal.aborted ? expired() : result;
2448
2468
  };
2449
2469
  let disposeBinding = () => {};
2450
2470
  const guardedHistory = {
@@ -2465,11 +2485,11 @@ function createBbIdentityServerBinding(options) {
2465
2485
  instanceId: server.instanceId,
2466
2486
  commits: server.commits,
2467
2487
  async session(invocation) {
2468
- const record = recordFor(invocation);
2469
- if (!record.ok)
2488
+ const record2 = recordFor(invocation);
2489
+ if (!record2.ok)
2470
2490
  return unavailableSession2();
2471
- const session = await server.session(record.value.request);
2472
- return record.value.settled || record.value.signal.aborted ? unavailableSession2() : session;
2491
+ const session = await server.session(record2.value.request);
2492
+ return record2.value.settled || record2.value.signal.aborted ? unavailableSession2() : session;
2473
2493
  },
2474
2494
  selfProfile: (invocation) => guardEndpoint(invocation, (request) => server.selfProfile(request)),
2475
2495
  personRequest: openPerson,
@@ -2546,18 +2566,18 @@ function createBbIdentityServerBinding(options) {
2546
2566
  return endpointRegistered;
2547
2567
  }
2548
2568
  const createInvocation = (context) => {
2549
- const record = { request: context.request, signal: context.signal, people: new Set, settled: false };
2569
+ const record2 = { request: context.request, signal: context.signal, people: new Set, settled: false };
2550
2570
  const invocation = {
2551
2571
  origin: context.origin,
2552
2572
  signal: context.signal,
2553
2573
  person: () => context.origin === "external" ? Promise.resolve(err("unauthenticated", "External invocation uses an explicit external source.")) : openPerson(invocation)
2554
2574
  };
2555
- invocations.set(invocation, record);
2575
+ invocations.set(invocation, record2);
2556
2576
  const finish = () => {
2557
- record.settled = true;
2558
- for (const request of record.people)
2577
+ record2.settled = true;
2578
+ for (const request of record2.people)
2559
2579
  request.dispose();
2560
- record.people.clear();
2580
+ record2.people.clear();
2561
2581
  };
2562
2582
  return { invocation, finish };
2563
2583
  };