@phosphorco/bb-identity 0.3.0 → 0.3.1

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.
package/README.md CHANGED
@@ -477,3 +477,7 @@ V1 receipts confirm the frozen `externalContribution`. Reconcile V1 exclusively
477
477
  The default host rendering policy adds the integration sender envelope once. A producer such as Rosetta must select `externalMessageRendering: 'producer'` at binding construction; its prompt bytes remain unchanged. A capable native host projects the person into the native timeline while contribution/attempt history keeps the external origin and optional `personAttribution`. This does not add native-message history queries.
478
478
 
479
479
  Native history containing integration-asserted person authors requires a capable runtime even after queues drain or plugins are downgraded. Do not roll back to a host that only accepts provider-verified person authors. Package-only rollback retains the legacy external path; it does not make old native readers understand newer stored authorship.
480
+
481
+ ### 0.3.1 decoder corrections
482
+
483
+ V1 external subjects are opaque nonempty strings of at most256 characters, including email-shaped or percent-encoded subjects. They are not BB resource IDs. Contribution and attempt-history output normalize additive person snapshot, identity, and presentation fields while preserving strict V1 input decoding. `externalContributionOutcomeV1Codec` from `/host` validates and normalizes persisted V1 outcomes; consumers must additionally compare submitted receipts with the reserved operation ID and frozen contribution. A stored final lacking that evidence remains uncertainty and must not trigger a resend.
@@ -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
  };
@@ -1,4 +1,27 @@
1
1
  // @bun
2
+ // external-person-output-runtime.ts
3
+ function record(value) {
4
+ return value !== null && typeof value === "object" && !Array.isArray(value);
5
+ }
6
+ function normalizeExternalPersonOutput(value) {
7
+ if (!record(value) || !record(value.identity) || !record(value.presentation))
8
+ return value;
9
+ return {
10
+ identity: { kind: value.identity.kind, key: value.identity.key, issuer: value.identity.issuer, subject: value.identity.subject },
11
+ presentation: { displayName: value.presentation.displayName, handle: value.presentation.handle, avatarUrl: value.presentation.avatarUrl },
12
+ evidence: value.evidence
13
+ };
14
+ }
15
+ function normalizeContributionOutput(value) {
16
+ return record(value) && value.personAttribution !== undefined ? { ...value, personAttribution: normalizeExternalPersonOutput(value.personAttribution) } : value;
17
+ }
18
+ function normalizeProvenanceOutput(value) {
19
+ return record(value) && Array.isArray(value.contributions) ? { ...value, contributions: value.contributions.map(normalizeContributionOutput) } : value;
20
+ }
21
+ function normalizeEvidencePageOutput(value, normalize) {
22
+ return record(value) && Array.isArray(value.items) ? { ...value, items: value.items.map(normalize) } : value;
23
+ }
24
+
2
25
  // model-runtime.ts
3
26
  var ID_MAX_LENGTH = 256;
4
27
  var ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:@/-]*$/;
@@ -165,6 +188,7 @@ function hasCompleteCausality(groups, contributions, missing) {
165
188
  }
166
189
  var provenanceCodec = {
167
190
  decode(input) {
191
+ input = normalizeProvenanceOutput(input);
168
192
  if (!isRecord(input) || input.status !== "known" && input.status !== "partial" && input.status !== "unknown") {
169
193
  return err("invalid-input", "Invalid execution provenance.");
170
194
  }
@@ -467,11 +491,11 @@ function createEnhancedDirectory(protocol) {
467
491
  }
468
492
  mappings.set(key, mapping);
469
493
  };
470
- const profile = (source, record, revision) => {
471
- if (!isRecord2(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)
494
+ const profile = (source, record2, revision) => {
495
+ if (!isRecord2(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)
472
496
  return err("incompatible", "Provider directory record is malformed.");
473
- const issuer = record.issuer;
474
- const subject = record.subject;
497
+ const issuer = record2.issuer;
498
+ const subject = record2.subject;
475
499
  if (typeof issuer !== "string" || typeof subject !== "string")
476
500
  return err("incompatible", "Provider directory record is malformed.");
477
501
  let person;
@@ -482,10 +506,10 @@ function createEnhancedDirectory(protocol) {
482
506
  }
483
507
  if (!person.ok)
484
508
  return person;
485
- if (person.value.kind !== "person" || person.value.issuer !== record.issuer || person.value.subject !== record.subject || !identityKeyCodec.decode(person.value.key).ok) {
509
+ if (person.value.kind !== "person" || person.value.issuer !== record2.issuer || person.value.subject !== record2.subject || !identityKeyCodec.decode(person.value.key).ok) {
486
510
  return err("incompatible", "Provider directory returned an invalid person reference.");
487
511
  }
488
- return profileCodec.decode({ identity: person.value, presentation: record.presentation, revision, status: record.status });
512
+ return profileCodec.decode({ identity: person.value, presentation: record2.presentation, revision, status: record2.status });
489
513
  };
490
514
  return {
491
515
  async search(input, options) {
@@ -511,8 +535,8 @@ function createEnhancedDirectory(protocol) {
511
535
  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)
512
536
  return err("incompatible", "Provider directory page is malformed.");
513
537
  const profiles = [];
514
- for (const record of result.value.records) {
515
- const decoded = profile(current.value, record, result.value.revision);
538
+ for (const record2 of result.value.records) {
539
+ const decoded = profile(current.value, record2, result.value.revision);
516
540
  if (!decoded.ok)
517
541
  return decoded;
518
542
  if (input.history === "current" && decoded.value.status !== "current")
@@ -821,7 +845,7 @@ function createHostAdapter(options) {
821
845
  } catch {
822
846
  return err("unavailable", "Contribution history is unavailable.", "after-reconnect");
823
847
  }
824
- return result.ok ? decodeEvidencePage(result.value, isContribution2) : result;
848
+ return result.ok ? decodeEvidencePage(normalizeEvidencePageOutput(result.value, normalizeContributionOutput), isContribution2) : result;
825
849
  },
826
850
  async attempts(query, readOptions) {
827
851
  if (!validAttemptQuery(query))
@@ -834,7 +858,7 @@ function createHostAdapter(options) {
834
858
  } catch {
835
859
  return err("unavailable", "Execution history is unavailable.", "after-reconnect");
836
860
  }
837
- return result.ok ? decodeEvidencePage(result.value, (item) => provenanceCodec.decode(item).ok) : result;
861
+ return result.ok ? decodeEvidencePage(normalizeEvidencePageOutput(result.value, normalizeProvenanceOutput), (item) => provenanceCodec.decode(item).ok) : result;
838
862
  }
839
863
  };
840
864
  const subscription = protocol.subscribe((event) => {
@@ -931,16 +955,12 @@ function createHostAdapter(options) {
931
955
  };
932
956
  }
933
957
  function decodeExternalContribution(value, strict = true) {
934
- if (!isRecord2(value) || value.version !== 1 || !isRecord2(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 && isRecord2(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)) {
958
+ if (!isRecord2(value) || value.version !== 1 || !isRecord2(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 && isRecord2(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)) {
935
959
  return err("invalid-input", "Invalid V1 external contribution.");
936
960
  }
937
961
  let person = value.personAttribution;
938
- if (!strict && isRecord2(person) && isRecord2(person.identity))
939
- person = {
940
- identity: { kind: person.identity.kind, key: person.identity.key, issuer: person.identity.issuer, subject: person.identity.subject },
941
- presentation: isRecord2(person.presentation) ? { displayName: person.presentation.displayName, handle: person.presentation.handle, avatarUrl: person.presentation.avatarUrl } : person.presentation,
942
- evidence: person.evidence
943
- };
962
+ if (!strict)
963
+ person = normalizeExternalPersonOutput(person);
944
964
  if (strict && isRecord2(person) && isRecord2(person.presentation) && Object.keys(person.presentation).some((key) => !["displayName", "handle", "avatarUrl"].includes(key)))
945
965
  return err("invalid-input", "Unexpected presentation field.");
946
966
  const decoded = person === undefined ? null : externalPersonSnapshotCodec.decode(person);
@@ -963,6 +983,20 @@ function canonicalExternal(value) {
963
983
  return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonicalExternal(value[key])}`).join(",")}}`;
964
984
  return JSON.stringify(value);
965
985
  }
986
+ function decodeExternalContributionOutcomeV1(raw) {
987
+ const decoded = decodeAcceptance(raw);
988
+ if (!decoded.ok || decoded.value.status !== "submitted")
989
+ return decoded;
990
+ if (decoded.value.receipt.evidence !== "host-accepted")
991
+ return err("incompatible", "V1 needs durable native acceptance.");
992
+ const receipt = decoded.value.receipt;
993
+ const contribution = decodeExternalContribution(receipt.externalContribution, false);
994
+ return contribution.ok ? ok({ status: "submitted", receipt: { ...decoded.value.receipt, externalContribution: contribution.value } }) : err("incompatible", "Host receipt omitted or changed V1 attribution.");
995
+ }
996
+ var externalContributionOutcomeV1Codec = {
997
+ decode: decodeExternalContributionOutcomeV1,
998
+ encode: (value) => value
999
+ };
966
1000
  function externalContributionMethods(upstream, extension, disposed) {
967
1001
  const support = () => {
968
1002
  if (disposed())
@@ -982,16 +1016,6 @@ function externalContributionMethods(upstream, extension, disposed) {
982
1016
  return err("incompatible", "Malformed V1 external contribution capability.");
983
1017
  return ok(value);
984
1018
  };
985
- const outcome = (raw) => {
986
- const decoded = decodeAcceptance(raw);
987
- if (!decoded.ok || decoded.value.status !== "submitted")
988
- return decoded;
989
- if (decoded.value.receipt.evidence !== "host-accepted")
990
- return err("incompatible", "V1 needs durable native acceptance.");
991
- const receipt = decoded.value.receipt;
992
- const contribution = decodeExternalContribution(receipt.externalContribution, false);
993
- return contribution.ok ? ok({ status: "submitted", receipt: { ...decoded.value.receipt, externalContribution: contribution.value } }) : err("incompatible", "Host receipt omitted or changed V1 attribution.");
994
- };
995
1019
  return {
996
1020
  externalContributionCapabilities() {
997
1021
  const negotiated = support();
@@ -1012,7 +1036,7 @@ function externalContributionMethods(upstream, extension, disposed) {
1012
1036
  const frozen = JSON.parse(JSON.stringify(decoded.value));
1013
1037
  const wire = JSON.parse(JSON.stringify(wireInput(input, upstream.inputCodec)));
1014
1038
  try {
1015
- const result = outcome(await negotiated.value.acceptV1({ contribution: frozen, input: wire }, options));
1039
+ const result = decodeExternalContributionOutcomeV1(await negotiated.value.acceptV1({ contribution: frozen, input: wire }, options));
1016
1040
  if (!result.ok)
1017
1041
  return { status: "indeterminate", operationId, message: result.error.message };
1018
1042
  if (result.value.status === "submitted" && (result.value.receipt.operationId !== operationId || canonicalExternal(result.value.receipt.externalContribution) !== canonicalExternal(frozen))) {
@@ -1034,7 +1058,7 @@ function externalContributionMethods(upstream, extension, disposed) {
1034
1058
  if (!result.ok)
1035
1059
  return result;
1036
1060
  if (isRecord2(result.value) && result.value.status === "final") {
1037
- const decoded = outcome(result.value.outcome);
1061
+ const decoded = decodeExternalContributionOutcomeV1(result.value.outcome);
1038
1062
  if (decoded.ok && decoded.value.status === "submitted" && decoded.value.receipt.operationId !== operationId) {
1039
1063
  return err("incompatible", "Host lookup returned a receipt for another operation.");
1040
1064
  }
@@ -1060,5 +1084,6 @@ export {
1060
1084
  serverSessionCodec,
1061
1085
  inspectForkExtension,
1062
1086
  externalContributionV1Codec,
1087
+ externalContributionOutcomeV1Codec,
1063
1088
  createHostAdapter
1064
1089
  };
@@ -1,4 +1,24 @@
1
1
  // @bun
2
+ // external-person-output-runtime.ts
3
+ function record(value) {
4
+ return value !== null && typeof value === "object" && !Array.isArray(value);
5
+ }
6
+ function normalizeExternalPersonOutput(value) {
7
+ if (!record(value) || !record(value.identity) || !record(value.presentation))
8
+ return value;
9
+ return {
10
+ identity: { kind: value.identity.kind, key: value.identity.key, issuer: value.identity.issuer, subject: value.identity.subject },
11
+ presentation: { displayName: value.presentation.displayName, handle: value.presentation.handle, avatarUrl: value.presentation.avatarUrl },
12
+ evidence: value.evidence
13
+ };
14
+ }
15
+ function normalizeContributionOutput(value) {
16
+ return record(value) && value.personAttribution !== undefined ? { ...value, personAttribution: normalizeExternalPersonOutput(value.personAttribution) } : value;
17
+ }
18
+ function normalizeProvenanceOutput(value) {
19
+ return record(value) && Array.isArray(value.contributions) ? { ...value, contributions: value.contributions.map(normalizeContributionOutput) } : value;
20
+ }
21
+
2
22
  // model-runtime.ts
3
23
  var ID_MAX_LENGTH = 256;
4
24
  var ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:@/-]*$/;
@@ -165,6 +185,7 @@ function hasCompleteCausality(groups, contributions, missing) {
165
185
  }
166
186
  var provenanceCodec = {
167
187
  decode(input) {
188
+ input = normalizeProvenanceOutput(input);
168
189
  if (!isRecord(input) || input.status !== "known" && input.status !== "partial" && input.status !== "unknown") {
169
190
  return err("invalid-input", "Invalid execution provenance.");
170
191
  }
@@ -1,4 +1,24 @@
1
1
  // @bun
2
+ // external-person-output-runtime.ts
3
+ function record(value) {
4
+ return value !== null && typeof value === "object" && !Array.isArray(value);
5
+ }
6
+ function normalizeExternalPersonOutput(value) {
7
+ if (!record(value) || !record(value.identity) || !record(value.presentation))
8
+ return value;
9
+ return {
10
+ identity: { kind: value.identity.kind, key: value.identity.key, issuer: value.identity.issuer, subject: value.identity.subject },
11
+ presentation: { displayName: value.presentation.displayName, handle: value.presentation.handle, avatarUrl: value.presentation.avatarUrl },
12
+ evidence: value.evidence
13
+ };
14
+ }
15
+ function normalizeContributionOutput(value) {
16
+ return record(value) && value.personAttribution !== undefined ? { ...value, personAttribution: normalizeExternalPersonOutput(value.personAttribution) } : value;
17
+ }
18
+ function normalizeProvenanceOutput(value) {
19
+ return record(value) && Array.isArray(value.contributions) ? { ...value, contributions: value.contributions.map(normalizeContributionOutput) } : value;
20
+ }
21
+
2
22
  // model-runtime.ts
3
23
  var ID_MAX_LENGTH = 256;
4
24
  var ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:@/-]*$/;
@@ -165,6 +185,7 @@ function hasCompleteCausality(groups, contributions, missing) {
165
185
  }
166
186
  var provenanceCodec = {
167
187
  decode(input) {
188
+ input = normalizeProvenanceOutput(input);
168
189
  if (!isRecord(input) || input.status !== "known" && input.status !== "partial" && input.status !== "unknown") {
169
190
  return err("invalid-input", "Invalid execution provenance.");
170
191
  }
@@ -1,4 +1,27 @@
1
1
  // @bun
2
+ // external-person-output-runtime.ts
3
+ function record(value) {
4
+ return value !== null && typeof value === "object" && !Array.isArray(value);
5
+ }
6
+ function normalizeExternalPersonOutput(value) {
7
+ if (!record(value) || !record(value.identity) || !record(value.presentation))
8
+ return value;
9
+ return {
10
+ identity: { kind: value.identity.kind, key: value.identity.key, issuer: value.identity.issuer, subject: value.identity.subject },
11
+ presentation: { displayName: value.presentation.displayName, handle: value.presentation.handle, avatarUrl: value.presentation.avatarUrl },
12
+ evidence: value.evidence
13
+ };
14
+ }
15
+ function normalizeContributionOutput(value) {
16
+ return record(value) && value.personAttribution !== undefined ? { ...value, personAttribution: normalizeExternalPersonOutput(value.personAttribution) } : value;
17
+ }
18
+ function normalizeProvenanceOutput(value) {
19
+ return record(value) && Array.isArray(value.contributions) ? { ...value, contributions: value.contributions.map(normalizeContributionOutput) } : value;
20
+ }
21
+ function normalizeEvidencePageOutput(value, normalize) {
22
+ return record(value) && Array.isArray(value.items) ? { ...value, items: value.items.map(normalize) } : value;
23
+ }
24
+
2
25
  // model-runtime.ts
3
26
  var ID_MAX_LENGTH = 256;
4
27
  var ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:@/-]*$/;
@@ -165,6 +188,7 @@ function hasCompleteCausality(groups, contributions, missing) {
165
188
  }
166
189
  var provenanceCodec = {
167
190
  decode(input) {
191
+ input = normalizeProvenanceOutput(input);
168
192
  if (!isRecord(input) || input.status !== "known" && input.status !== "partial" && input.status !== "unknown") {
169
193
  return err("invalid-input", "Invalid execution provenance.");
170
194
  }
@@ -470,11 +494,11 @@ function createEnhancedDirectory(protocol) {
470
494
  }
471
495
  mappings.set(key, mapping);
472
496
  };
473
- const profile = (source, record, revision) => {
474
- if (!isRecord2(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)
497
+ const profile = (source, record2, revision) => {
498
+ if (!isRecord2(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)
475
499
  return err("incompatible", "Provider directory record is malformed.");
476
- const issuer = record.issuer;
477
- const subject = record.subject;
500
+ const issuer = record2.issuer;
501
+ const subject = record2.subject;
478
502
  if (typeof issuer !== "string" || typeof subject !== "string")
479
503
  return err("incompatible", "Provider directory record is malformed.");
480
504
  let person;
@@ -485,10 +509,10 @@ function createEnhancedDirectory(protocol) {
485
509
  }
486
510
  if (!person.ok)
487
511
  return person;
488
- if (person.value.kind !== "person" || person.value.issuer !== record.issuer || person.value.subject !== record.subject || !identityKeyCodec.decode(person.value.key).ok) {
512
+ if (person.value.kind !== "person" || person.value.issuer !== record2.issuer || person.value.subject !== record2.subject || !identityKeyCodec.decode(person.value.key).ok) {
489
513
  return err("incompatible", "Provider directory returned an invalid person reference.");
490
514
  }
491
- return profileCodec.decode({ identity: person.value, presentation: record.presentation, revision, status: record.status });
515
+ return profileCodec.decode({ identity: person.value, presentation: record2.presentation, revision, status: record2.status });
492
516
  };
493
517
  return {
494
518
  async search(input, options) {
@@ -514,8 +538,8 @@ function createEnhancedDirectory(protocol) {
514
538
  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)
515
539
  return err("incompatible", "Provider directory page is malformed.");
516
540
  const profiles = [];
517
- for (const record of result.value.records) {
518
- const decoded = profile(current.value, record, result.value.revision);
541
+ for (const record2 of result.value.records) {
542
+ const decoded = profile(current.value, record2, result.value.revision);
519
543
  if (!decoded.ok)
520
544
  return decoded;
521
545
  if (input.history === "current" && decoded.value.status !== "current")
@@ -824,7 +848,7 @@ function createHostAdapter(options) {
824
848
  } catch {
825
849
  return err("unavailable", "Contribution history is unavailable.", "after-reconnect");
826
850
  }
827
- return result.ok ? decodeEvidencePage(result.value, isContribution2) : result;
851
+ return result.ok ? decodeEvidencePage(normalizeEvidencePageOutput(result.value, normalizeContributionOutput), isContribution2) : result;
828
852
  },
829
853
  async attempts(query, readOptions) {
830
854
  if (!validAttemptQuery(query))
@@ -837,7 +861,7 @@ function createHostAdapter(options) {
837
861
  } catch {
838
862
  return err("unavailable", "Execution history is unavailable.", "after-reconnect");
839
863
  }
840
- return result.ok ? decodeEvidencePage(result.value, (item) => provenanceCodec.decode(item).ok) : result;
864
+ return result.ok ? decodeEvidencePage(normalizeEvidencePageOutput(result.value, normalizeProvenanceOutput), (item) => provenanceCodec.decode(item).ok) : result;
841
865
  }
842
866
  };
843
867
  const subscription = protocol.subscribe((event) => {
@@ -934,16 +958,12 @@ function createHostAdapter(options) {
934
958
  };
935
959
  }
936
960
  function decodeExternalContribution(value, strict = true) {
937
- if (!isRecord2(value) || value.version !== 1 || !isRecord2(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 && isRecord2(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)) {
961
+ if (!isRecord2(value) || value.version !== 1 || !isRecord2(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 && isRecord2(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)) {
938
962
  return err("invalid-input", "Invalid V1 external contribution.");
939
963
  }
940
964
  let person = value.personAttribution;
941
- if (!strict && isRecord2(person) && isRecord2(person.identity))
942
- person = {
943
- identity: { kind: person.identity.kind, key: person.identity.key, issuer: person.identity.issuer, subject: person.identity.subject },
944
- presentation: isRecord2(person.presentation) ? { displayName: person.presentation.displayName, handle: person.presentation.handle, avatarUrl: person.presentation.avatarUrl } : person.presentation,
945
- evidence: person.evidence
946
- };
965
+ if (!strict)
966
+ person = normalizeExternalPersonOutput(person);
947
967
  if (strict && isRecord2(person) && isRecord2(person.presentation) && Object.keys(person.presentation).some((key) => !["displayName", "handle", "avatarUrl"].includes(key)))
948
968
  return err("invalid-input", "Unexpected presentation field.");
949
969
  const decoded = person === undefined ? null : externalPersonSnapshotCodec.decode(person);
@@ -966,6 +986,16 @@ function canonicalExternal(value) {
966
986
  return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonicalExternal(value[key])}`).join(",")}}`;
967
987
  return JSON.stringify(value);
968
988
  }
989
+ function decodeExternalContributionOutcomeV1(raw) {
990
+ const decoded = decodeAcceptance(raw);
991
+ if (!decoded.ok || decoded.value.status !== "submitted")
992
+ return decoded;
993
+ if (decoded.value.receipt.evidence !== "host-accepted")
994
+ return err("incompatible", "V1 needs durable native acceptance.");
995
+ const receipt = decoded.value.receipt;
996
+ const contribution = decodeExternalContribution(receipt.externalContribution, false);
997
+ return contribution.ok ? ok({ status: "submitted", receipt: { ...decoded.value.receipt, externalContribution: contribution.value } }) : err("incompatible", "Host receipt omitted or changed V1 attribution.");
998
+ }
969
999
  function externalContributionMethods(upstream, extension, disposed) {
970
1000
  const support = () => {
971
1001
  if (disposed())
@@ -985,16 +1015,6 @@ function externalContributionMethods(upstream, extension, disposed) {
985
1015
  return err("incompatible", "Malformed V1 external contribution capability.");
986
1016
  return ok(value);
987
1017
  };
988
- const outcome = (raw) => {
989
- const decoded = decodeAcceptance(raw);
990
- if (!decoded.ok || decoded.value.status !== "submitted")
991
- return decoded;
992
- if (decoded.value.receipt.evidence !== "host-accepted")
993
- return err("incompatible", "V1 needs durable native acceptance.");
994
- const receipt = decoded.value.receipt;
995
- const contribution = decodeExternalContribution(receipt.externalContribution, false);
996
- return contribution.ok ? ok({ status: "submitted", receipt: { ...decoded.value.receipt, externalContribution: contribution.value } }) : err("incompatible", "Host receipt omitted or changed V1 attribution.");
997
- };
998
1018
  return {
999
1019
  externalContributionCapabilities() {
1000
1020
  const negotiated = support();
@@ -1015,7 +1035,7 @@ function externalContributionMethods(upstream, extension, disposed) {
1015
1035
  const frozen = JSON.parse(JSON.stringify(decoded.value));
1016
1036
  const wire = JSON.parse(JSON.stringify(wireInput(input, upstream.inputCodec)));
1017
1037
  try {
1018
- const result = outcome(await negotiated.value.acceptV1({ contribution: frozen, input: wire }, options));
1038
+ const result = decodeExternalContributionOutcomeV1(await negotiated.value.acceptV1({ contribution: frozen, input: wire }, options));
1019
1039
  if (!result.ok)
1020
1040
  return { status: "indeterminate", operationId, message: result.error.message };
1021
1041
  if (result.value.status === "submitted" && (result.value.receipt.operationId !== operationId || canonicalExternal(result.value.receipt.externalContribution) !== canonicalExternal(frozen))) {
@@ -1037,7 +1057,7 @@ function externalContributionMethods(upstream, extension, disposed) {
1037
1057
  if (!result.ok)
1038
1058
  return result;
1039
1059
  if (isRecord2(result.value) && result.value.status === "final") {
1040
- const decoded = outcome(result.value.outcome);
1060
+ const decoded = decodeExternalContributionOutcomeV1(result.value.outcome);
1041
1061
  if (decoded.ok && decoded.value.status === "submitted" && decoded.value.receipt.operationId !== operationId) {
1042
1062
  return err("incompatible", "Host lookup returned a receipt for another operation.");
1043
1063
  }
@@ -1991,39 +2011,39 @@ function createBbUpstreamDriver(options) {
1991
2011
  const active = new Set;
1992
2012
  const listeners = new Set;
1993
2013
  const labelledExternal = new WeakSet;
1994
- const terminateScope = (record, scope) => {
2014
+ const terminateScope = (record2, scope) => {
1995
2015
  if (!scope || scope.released)
1996
2016
  return;
1997
2017
  scope.released = true;
1998
- record.controller.signal.removeEventListener("abort", scope.parentAbort);
2018
+ record2.controller.signal.removeEventListener("abort", scope.parentAbort);
1999
2019
  scope.controller.abort();
2000
- record.scopes.delete(scope);
2020
+ record2.scopes.delete(scope);
2001
2021
  };
2002
- const terminate = (record) => {
2003
- for (const scope of [...record.scopes])
2004
- terminateScope(record, scope);
2005
- record.controller.abort();
2006
- active.delete(record);
2022
+ const terminate = (record2) => {
2023
+ for (const scope of [...record2.scopes])
2024
+ terminateScope(record2, scope);
2025
+ record2.controller.abort();
2026
+ active.delete(record2);
2007
2027
  };
2008
2028
  const known = (context) => {
2009
- const record = records.get(context);
2010
- 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);
2029
+ const record2 = records.get(context);
2030
+ 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);
2011
2031
  };
2012
2032
  const admission = (context) => {
2013
- const record = known(context);
2014
- if (!record.ok)
2015
- return record;
2016
- if (record.value.origin === "external")
2033
+ const record2 = known(context);
2034
+ if (!record2.ok)
2035
+ return record2;
2036
+ if (record2.value.origin === "external")
2017
2037
  return err("unauthenticated", "External upstream work uses an explicit external source.");
2018
- return record;
2038
+ return record2;
2019
2039
  };
2020
2040
  const session = (context) => {
2021
- const record = known(context);
2022
- if (!record.ok)
2023
- return unavailableSession(instanceId, record.error.code === "invalid-input" ? "incompatible" : "unauthenticated", record.error.message);
2024
- if (record.value.origin === "interactive-user")
2041
+ const record2 = known(context);
2042
+ if (!record2.ok)
2043
+ return unavailableSession(instanceId, record2.error.code === "invalid-input" ? "incompatible" : "unauthenticated", record2.error.message);
2044
+ if (record2.value.origin === "interactive-user")
2025
2045
  return readySession();
2026
- return record.value.origin === "background" ? machineSession() : unavailableSession(instanceId, "unauthenticated", "External upstream work uses an explicit external source.");
2046
+ return record2.value.origin === "background" ? machineSession() : unavailableSession(instanceId, "unauthenticated", "External upstream work uses an explicit external source.");
2027
2047
  };
2028
2048
  const driver = {
2029
2049
  instanceId,
@@ -2041,20 +2061,20 @@ function createBbUpstreamDriver(options) {
2041
2061
  const allowed = admission(context);
2042
2062
  if (!allowed.ok)
2043
2063
  return allowed;
2044
- const record = allowed.value;
2064
+ const record2 = allowed.value;
2045
2065
  let scope;
2046
- const parentAbort = () => terminateScope(record, scope);
2066
+ const parentAbort = () => terminateScope(record2, scope);
2047
2067
  scope = { controller: new AbortController, parentAbort, released: false };
2048
- record.scopes.add(scope);
2049
- const expire = () => terminateScope(record, scope);
2050
- record.controller.signal.addEventListener("abort", parentAbort, { once: true });
2051
- const exposedSession = record.origin === "background" ? machineSession() : readySession();
2052
- const expectedKey = record.origin === "background" ? machineKey.value : singletonKey;
2068
+ record2.scopes.add(scope);
2069
+ const expire = () => terminateScope(record2, scope);
2070
+ record2.controller.signal.addEventListener("abort", parentAbort, { once: true });
2071
+ const exposedSession = record2.origin === "background" ? machineSession() : readySession();
2072
+ const expectedKey = record2.origin === "background" ? machineKey.value : singletonKey;
2053
2073
  return ok({
2054
2074
  signal: scope.controller.signal,
2055
2075
  session: exposedSession,
2056
2076
  validate(expected) {
2057
- if (scope.released || scope.controller.signal.aborted || record.controller.signal.aborted || disposed) {
2077
+ if (scope.released || scope.controller.signal.aborted || record2.controller.signal.aborted || disposed) {
2058
2078
  return err("expired", "Upstream request context is no longer live.");
2059
2079
  }
2060
2080
  return expected.actor === expectedKey && expected.session === stamp.value ? ok(undefined) : err("stale-context", "Request expectation does not match the singleton session.");
@@ -2138,22 +2158,22 @@ function createBbUpstreamDriver(options) {
2138
2158
  throw new Error("Upstream binding is disposed.");
2139
2159
  const controller = new AbortController;
2140
2160
  const invocation = Object.freeze({ origin, signal: controller.signal });
2141
- const record = { origin, controller, scopes: new Set };
2142
- records.set(invocation, record);
2143
- active.add(record);
2161
+ const record2 = { origin, controller, scopes: new Set };
2162
+ records.set(invocation, record2);
2163
+ active.add(record2);
2144
2164
  return invocation;
2145
2165
  },
2146
2166
  release(invocation) {
2147
- const record = records.get(invocation);
2148
- if (record)
2149
- terminate(record);
2167
+ const record2 = records.get(invocation);
2168
+ if (record2)
2169
+ terminate(record2);
2150
2170
  },
2151
2171
  dispose() {
2152
2172
  if (disposed)
2153
2173
  return;
2154
2174
  disposed = true;
2155
- for (const record of [...active])
2156
- terminate(record);
2175
+ for (const record2 of [...active])
2176
+ terminate(record2);
2157
2177
  for (const listener of [...listeners]) {
2158
2178
  try {
2159
2179
  listener({ kind: "disposed" });
@@ -2780,8 +2800,8 @@ function createBbIdentityServerBinding(options) {
2780
2800
  const invocations = new WeakMap;
2781
2801
  const expired = () => err("expired", "Invocation has already settled.");
2782
2802
  const recordFor = (invocation) => {
2783
- const record = invocations.get(invocation);
2784
- return disposed || !record || record.settled || record.signal.aborted ? expired() : ok(record);
2803
+ const record2 = invocations.get(invocation);
2804
+ return disposed || !record2 || record2.settled || record2.signal.aborted ? expired() : ok(record2);
2785
2805
  };
2786
2806
  const unavailableSession2 = () => ({
2787
2807
  status: "unavailable",
@@ -2789,25 +2809,25 @@ function createBbIdentityServerBinding(options) {
2789
2809
  error: { code: "expired", message: "Invocation has already settled.", retry: "after-refresh" }
2790
2810
  });
2791
2811
  const openPerson = async (invocation) => {
2792
- const record = recordFor(invocation);
2793
- if (!record.ok)
2794
- return record;
2795
- const opened = await server.personRequest(record.value.request);
2796
- if (record.value.settled || record.value.signal.aborted) {
2812
+ const record2 = recordFor(invocation);
2813
+ if (!record2.ok)
2814
+ return record2;
2815
+ const opened = await server.personRequest(record2.value.request);
2816
+ if (record2.value.settled || record2.value.signal.aborted) {
2797
2817
  if (opened.ok)
2798
2818
  opened.value.dispose();
2799
2819
  return expired();
2800
2820
  }
2801
2821
  if (opened.ok)
2802
- record.value.people.add(opened.value);
2822
+ record2.value.people.add(opened.value);
2803
2823
  return opened;
2804
2824
  };
2805
2825
  const guardEndpoint = async (invocation, operation) => {
2806
- const record = recordFor(invocation);
2807
- if (!record.ok)
2808
- return record;
2809
- const result = await operation(record.value.request);
2810
- return record.value.settled || record.value.signal.aborted ? expired() : result;
2826
+ const record2 = recordFor(invocation);
2827
+ if (!record2.ok)
2828
+ return record2;
2829
+ const result = await operation(record2.value.request);
2830
+ return record2.value.settled || record2.value.signal.aborted ? expired() : result;
2811
2831
  };
2812
2832
  let disposeBinding = () => {};
2813
2833
  const guardedHistory = {
@@ -2828,11 +2848,11 @@ function createBbIdentityServerBinding(options) {
2828
2848
  instanceId: server.instanceId,
2829
2849
  commits: server.commits,
2830
2850
  async session(invocation) {
2831
- const record = recordFor(invocation);
2832
- if (!record.ok)
2851
+ const record2 = recordFor(invocation);
2852
+ if (!record2.ok)
2833
2853
  return unavailableSession2();
2834
- const session = await server.session(record.value.request);
2835
- return record.value.settled || record.value.signal.aborted ? unavailableSession2() : session;
2854
+ const session = await server.session(record2.value.request);
2855
+ return record2.value.settled || record2.value.signal.aborted ? unavailableSession2() : session;
2836
2856
  },
2837
2857
  selfProfile: (invocation) => guardEndpoint(invocation, (request) => server.selfProfile(request)),
2838
2858
  personRequest: openPerson,
@@ -2909,18 +2929,18 @@ function createBbIdentityServerBinding(options) {
2909
2929
  return endpointRegistered;
2910
2930
  }
2911
2931
  const createInvocation = (context) => {
2912
- const record = { request: context.request, signal: context.signal, people: new Set, settled: false };
2932
+ const record2 = { request: context.request, signal: context.signal, people: new Set, settled: false };
2913
2933
  const invocation = {
2914
2934
  origin: context.origin,
2915
2935
  signal: context.signal,
2916
2936
  person: () => context.origin === "external" ? Promise.resolve(err("unauthenticated", "External invocation uses an explicit external source.")) : openPerson(invocation)
2917
2937
  };
2918
- invocations.set(invocation, record);
2938
+ invocations.set(invocation, record2);
2919
2939
  const finish = () => {
2920
- record.settled = true;
2921
- for (const request of record.people)
2940
+ record2.settled = true;
2941
+ for (const request of record2.people)
2922
2942
  request.dispose();
2923
- record.people.clear();
2943
+ record2.people.clear();
2924
2944
  };
2925
2945
  return { invocation, finish };
2926
2946
  };
@@ -3136,7 +3156,7 @@ function createMultiUserProtocol(instanceId, initial) {
3136
3156
  }
3137
3157
  };
3138
3158
  };
3139
- const record = (subject) => {
3159
+ const record2 = (subject) => {
3140
3160
  const person = people.get(subject);
3141
3161
  return person ? { issuer: ISSUER, subject, presentation: { displayName: person.name, handle: null, avatarUrl: null }, status: "current" } : null;
3142
3162
  };
@@ -3146,11 +3166,11 @@ function createMultiUserProtocol(instanceId, initial) {
3146
3166
  generation: brand("provider-generation", "wire-fake-provider"),
3147
3167
  async directory(input) {
3148
3168
  const query = input.query.toLowerCase();
3149
- const records = [...people.keys()].map(record).filter((r) => r !== null && (r.subject.includes(query) || r.presentation.displayName.toLowerCase().includes(query))).slice(0, input.limit);
3169
+ const records = [...people.keys()].map(record2).filter((r) => r !== null && (r.subject.includes(query) || r.presentation.displayName.toLowerCase().includes(query))).slice(0, input.limit);
3150
3170
  return ok({ records, nextCursor: null, revision: revision() });
3151
3171
  },
3152
3172
  async lookup(subjects) {
3153
- return ok({ revision: revision(), records: subjects.map((s) => ({ issuer: s.issuer, subject: s.subject, record: s.issuer === ISSUER ? record(s.subject) : null })) });
3173
+ return ok({ revision: revision(), records: subjects.map((s) => ({ issuer: s.issuer, subject: s.subject, record: s.issuer === ISSUER ? record2(s.subject) : null })) });
3154
3174
  },
3155
3175
  person(issuer, subject) {
3156
3176
  const key = identityKeyCodec.decode(`${issuer}:${subject}`);
@@ -3402,8 +3422,8 @@ function canonical(value) {
3402
3422
  return JSON.stringify(value) ?? "undefined";
3403
3423
  if (Array.isArray(value))
3404
3424
  return "[" + value.map(canonical).join(",") + "]";
3405
- const record = value;
3406
- return "{" + Object.keys(record).filter((k) => record[k] !== undefined).sort().map((k) => JSON.stringify(k) + ":" + canonical(record[k])).join(",") + "}";
3425
+ const record2 = value;
3426
+ return "{" + Object.keys(record2).filter((k) => record2[k] !== undefined).sort().map((k) => JSON.stringify(k) + ":" + canonical(record2[k])).join(",") + "}";
3407
3427
  }
3408
3428
  function id(kind, value) {
3409
3429
  const decoded = idCodec(kind).decode(value);
package/host.d.ts CHANGED
@@ -49,6 +49,7 @@ export type ExternalContributionOutcomeV1 = Exclude<AcceptanceOutcome, { status:
49
49
  readonly externalContribution: ExternalContributionV1;
50
50
  };
51
51
  };
52
+ export declare const externalContributionOutcomeV1Codec: Codec<ExternalContributionOutcomeV1>;
52
53
  /** Context-free support; native storage written by V1 requires a capable host on rollback. */
53
54
  export interface ExternalContributionCapabilityV1 {
54
55
  readonly version: 1; readonly personKinds: readonly ['person'];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phosphorco/bb-identity",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "Portable identity, provenance, and state contracts for BB integrations.",