@phosphorco/bb-identity 0.2.1 → 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.
@@ -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._:@/-]*$/;
@@ -107,7 +130,7 @@ function isOrigin(value) {
107
130
  return value.kind === "unknown" && (value.reason === "legacy" || value.reason === "upstream-unattributed" || value.reason === "missing-source");
108
131
  }
109
132
  function isContribution(value) {
110
- return isRecord(value) && isContributionReference(value.reference) && isOrigin(value.author) && (value.latestEditor === null || isActorSnapshot(value.latestEditor)) && isBoundedString(value.acceptedAt, 256) && Array.isArray(value.mentionedPeople) && value.mentionedPeople.every(isPersonReference);
133
+ return isRecord(value) && isContributionReference(value.reference) && isOrigin(value.author) && (value.personAttribution === undefined || externalPersonSnapshotCodec.decode(value.personAttribution).ok) && (value.latestEditor === null || isActorSnapshot(value.latestEditor)) && isBoundedString(value.acceptedAt, 256) && Array.isArray(value.mentionedPeople) && value.mentionedPeople.every(isPersonReference);
111
134
  }
112
135
  function isCorrelation(value) {
113
136
  return isRecord(value) && idCodec("thread").decode(value.threadId).ok && isBoundedString(value.turnId) && isBoundedString(value.attemptId) && (value.toolCallId === null || isBoundedString(value.toolCallId));
@@ -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
  }
@@ -184,6 +208,17 @@ var provenanceCodec = {
184
208
  return value;
185
209
  }
186
210
  };
211
+ var externalPersonSnapshotCodec = {
212
+ decode(value) {
213
+ if (!isRecord(value) || Object.keys(value).some((key) => !["identity", "presentation", "evidence"].includes(key)) || value.evidence !== "integration-asserted" || !isActorSnapshot(value) || !isRecord(value.identity) || value.identity.kind !== "person" || Object.keys(value.identity).some((key) => !["kind", "key", "issuer", "subject"].includes(key))) {
214
+ return err("invalid-input", "External person attribution requires a canonical person and integration-asserted evidence.");
215
+ }
216
+ return ok(value);
217
+ },
218
+ encode(value) {
219
+ return value;
220
+ }
221
+ };
187
222
 
188
223
  // host-runtime.ts
189
224
  function isRecord2(value) {
@@ -375,7 +410,7 @@ function isContribution2(value) {
375
410
  if (!isRecord2(origin))
376
411
  return false;
377
412
  const validOrigin = origin.kind === "person" ? snapshot(origin.actor) && isRecord2(origin.actor) && isPersonIdentity(origin.actor.identity) : origin.kind === "machine" ? snapshot(origin.actor) && isRecord2(origin.actor) && isMachineIdentity(origin.actor.identity) : origin.kind === "external" ? snapshot(origin.actor) && isRecord2(origin.actor) && isExternalIdentity(origin.actor.identity) : origin.kind === "agent" ? origin.agentId === null || idCodec("agent").decode(origin.agentId).ok : origin.kind === "system" ? typeof origin.reason === "string" && origin.reason.length > 0 && origin.reason.length <= 1024 : origin.kind === "unknown" && (origin.reason === "legacy" || origin.reason === "upstream-unattributed" || origin.reason === "missing-source");
378
- return validOrigin && (value.latestEditor === null || snapshot(value.latestEditor));
413
+ return (value.personAttribution === undefined || externalPersonSnapshotCodec.decode(value.personAttribution).ok) && validOrigin && (value.latestEditor === null || snapshot(value.latestEditor));
379
414
  }
380
415
  function decodeEvidencePage(value, decodeItem) {
381
416
  if (!isRecord2(value) || typeof value.status !== "string")
@@ -456,11 +491,11 @@ function createEnhancedDirectory(protocol) {
456
491
  }
457
492
  mappings.set(key, mapping);
458
493
  };
459
- const profile = (source, record, revision) => {
460
- 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)
461
496
  return err("incompatible", "Provider directory record is malformed.");
462
- const issuer = record.issuer;
463
- const subject = record.subject;
497
+ const issuer = record2.issuer;
498
+ const subject = record2.subject;
464
499
  if (typeof issuer !== "string" || typeof subject !== "string")
465
500
  return err("incompatible", "Provider directory record is malformed.");
466
501
  let person;
@@ -471,10 +506,10 @@ function createEnhancedDirectory(protocol) {
471
506
  }
472
507
  if (!person.ok)
473
508
  return person;
474
- 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) {
475
510
  return err("incompatible", "Provider directory returned an invalid person reference.");
476
511
  }
477
- 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 });
478
513
  };
479
514
  return {
480
515
  async search(input, options) {
@@ -500,8 +535,8 @@ function createEnhancedDirectory(protocol) {
500
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)
501
536
  return err("incompatible", "Provider directory page is malformed.");
502
537
  const profiles = [];
503
- for (const record of result.value.records) {
504
- 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);
505
540
  if (!decoded.ok)
506
541
  return decoded;
507
542
  if (input.history === "current" && decoded.value.status !== "current")
@@ -710,6 +745,7 @@ function createHostAdapter(options) {
710
745
  });
711
746
  const contexts = new WeakMap;
712
747
  return {
748
+ ...externalContributionMethods(upstream, extension, () => disposed),
713
749
  pluginId: upstream.pluginId,
714
750
  instanceId: upstream.instanceId,
715
751
  session: (context) => upstream.session(context),
@@ -755,6 +791,7 @@ function createHostAdapter(options) {
755
791
  const error = protocolError(extension);
756
792
  const unavailableResult = async () => ({ ok: false, error });
757
793
  return {
794
+ ...externalContributionMethods(upstream, extension, () => disposed),
758
795
  pluginId: upstream.pluginId,
759
796
  instanceId: upstream.instanceId,
760
797
  session: async () => ({ status: error.code === "incompatible" ? "incompatible" : "unavailable", instanceId: upstream.instanceId, error }),
@@ -808,7 +845,7 @@ function createHostAdapter(options) {
808
845
  } catch {
809
846
  return err("unavailable", "Contribution history is unavailable.", "after-reconnect");
810
847
  }
811
- return result.ok ? decodeEvidencePage(result.value, isContribution2) : result;
848
+ return result.ok ? decodeEvidencePage(normalizeEvidencePageOutput(result.value, normalizeContributionOutput), isContribution2) : result;
812
849
  },
813
850
  async attempts(query, readOptions) {
814
851
  if (!validAttemptQuery(query))
@@ -821,7 +858,7 @@ function createHostAdapter(options) {
821
858
  } catch {
822
859
  return err("unavailable", "Execution history is unavailable.", "after-reconnect");
823
860
  }
824
- 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;
825
862
  }
826
863
  };
827
864
  const subscription = protocol.subscribe((event) => {
@@ -830,6 +867,7 @@ function createHostAdapter(options) {
830
867
  });
831
868
  const scopes = new WeakMap;
832
869
  return {
870
+ ...externalContributionMethods(upstream, extension, () => disposed),
833
871
  pluginId: upstream.pluginId,
834
872
  instanceId: protocol.instanceId,
835
873
  async session(context) {
@@ -916,6 +954,123 @@ function createHostAdapter(options) {
916
954
  }
917
955
  };
918
956
  }
957
+ function decodeExternalContribution(value, strict = true) {
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)) {
959
+ return err("invalid-input", "Invalid V1 external contribution.");
960
+ }
961
+ let person = value.personAttribution;
962
+ if (!strict)
963
+ person = normalizeExternalPersonOutput(person);
964
+ if (strict && isRecord2(person) && isRecord2(person.presentation) && Object.keys(person.presentation).some((key) => !["displayName", "handle", "avatarUrl"].includes(key)))
965
+ return err("invalid-input", "Unexpected presentation field.");
966
+ const decoded = person === undefined ? null : externalPersonSnapshotCodec.decode(person);
967
+ if (decoded && !decoded.ok)
968
+ return decoded;
969
+ return ok({
970
+ version: 1,
971
+ author: { subject: value.author.subject, presentation: { displayName: value.author.presentation.displayName, handle: value.author.presentation.handle, avatarUrl: value.author.presentation.avatarUrl } },
972
+ ...decoded?.ok ? { personAttribution: decoded.value } : {}
973
+ });
974
+ }
975
+ var externalContributionV1Codec = {
976
+ decode: (value) => decodeExternalContribution(value),
977
+ encode: (value) => value
978
+ };
979
+ function canonicalExternal(value) {
980
+ if (Array.isArray(value))
981
+ return `[${value.map(canonicalExternal).join(",")}]`;
982
+ if (isRecord2(value))
983
+ return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonicalExternal(value[key])}`).join(",")}}`;
984
+ return JSON.stringify(value);
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
+ };
1000
+ function externalContributionMethods(upstream, extension, disposed) {
1001
+ const support = () => {
1002
+ if (disposed())
1003
+ return err("disposed", "Identity host is disposed.");
1004
+ if (extension.status === "absent")
1005
+ return ok(null);
1006
+ if (extension.status !== "supported")
1007
+ return { ok: false, error: protocolError(extension) };
1008
+ const value = extension.protocol.externalContributions;
1009
+ if (value === undefined)
1010
+ return ok(null);
1011
+ if (!isRecord2(value) || !Array.isArray(value.versions) || !value.versions.length || !value.versions.every((version) => Number.isInteger(version) && version > 0))
1012
+ return err("incompatible", "Malformed external contribution capability.");
1013
+ if (!value.versions.includes(1))
1014
+ return ok(null);
1015
+ if (!Array.isArray(value.personKinds) || value.personKinds.length !== 1 || value.personKinds[0] !== "person" || value.history !== "accepted-and-queued" || value.rollback !== "requires-capable-host" || typeof value.acceptV1 !== "function" || typeof value.lookupV1 !== "function")
1016
+ return err("incompatible", "Malformed V1 external contribution capability.");
1017
+ return ok(value);
1018
+ };
1019
+ return {
1020
+ externalContributionCapabilities() {
1021
+ const negotiated = support();
1022
+ return !negotiated.ok ? negotiated : ok(negotiated.value ? { version: 1, personKinds: ["person"], history: "accepted-and-queued", rollback: "requires-capable-host" } : null);
1023
+ },
1024
+ async sendExternalContribution(contribution, input, options) {
1025
+ const operationId = input.operationId;
1026
+ const decoded = decodeExternalContribution(contribution);
1027
+ if (!decoded.ok)
1028
+ return { status: "rejected", error: decoded.error };
1029
+ const negotiated = support();
1030
+ if (!negotiated.ok)
1031
+ return { status: "rejected", error: negotiated.error };
1032
+ if (!negotiated.value)
1033
+ return { status: "rejected", error: { code: "unsupported", message: "Native external-person attribution V1 is unavailable.", retry: "never" } };
1034
+ if (options?.signal?.aborted)
1035
+ return { status: "rejected", error: { code: "expired", message: "The send was cancelled before dispatch.", retry: "never" } };
1036
+ const frozen = JSON.parse(JSON.stringify(decoded.value));
1037
+ const wire = JSON.parse(JSON.stringify(wireInput(input, upstream.inputCodec)));
1038
+ try {
1039
+ const result = decodeExternalContributionOutcomeV1(await negotiated.value.acceptV1({ contribution: frozen, input: wire }, options));
1040
+ if (!result.ok)
1041
+ return { status: "indeterminate", operationId, message: result.error.message };
1042
+ if (result.value.status === "submitted" && (result.value.receipt.operationId !== operationId || canonicalExternal(result.value.receipt.externalContribution) !== canonicalExternal(frozen))) {
1043
+ return { status: "indeterminate", operationId, message: "Host receipt did not confirm the reserved attribution." };
1044
+ }
1045
+ return result.value;
1046
+ } catch {
1047
+ return { status: "indeterminate", operationId, message: "External contribution dispatch outcome is unknown; reconcile this operation." };
1048
+ }
1049
+ },
1050
+ async lookupExternalContribution(operationId, options) {
1051
+ const negotiated = support();
1052
+ if (!negotiated.ok)
1053
+ return negotiated;
1054
+ if (!negotiated.value)
1055
+ return ok({ status: "unknown", reason: "unsupported" });
1056
+ try {
1057
+ const result = asResult(await negotiated.value.lookupV1(operationId, options));
1058
+ if (!result.ok)
1059
+ return result;
1060
+ if (isRecord2(result.value) && result.value.status === "final") {
1061
+ const decoded = decodeExternalContributionOutcomeV1(result.value.outcome);
1062
+ if (decoded.ok && decoded.value.status === "submitted" && decoded.value.receipt.operationId !== operationId) {
1063
+ return err("incompatible", "Host lookup returned a receipt for another operation.");
1064
+ }
1065
+ return decoded.ok ? ok({ status: "final", outcome: decoded.value }) : decoded;
1066
+ }
1067
+ return decodeLookup(result.value);
1068
+ } catch {
1069
+ return err("unavailable", "External contribution lookup is unavailable.", "after-reconnect");
1070
+ }
1071
+ }
1072
+ };
1073
+ }
919
1074
  function inspectForkExtension(value) {
920
1075
  if (value === undefined)
921
1076
  return { status: "absent" };
@@ -928,5 +1083,7 @@ function inspectForkExtension(value) {
928
1083
  export {
929
1084
  serverSessionCodec,
930
1085
  inspectForkExtension,
1086
+ externalContributionV1Codec,
1087
+ externalContributionOutcomeV1Codec,
931
1088
  createHostAdapter
932
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._:@/-]*$/;
@@ -107,7 +127,7 @@ function isOrigin(value) {
107
127
  return value.kind === "unknown" && (value.reason === "legacy" || value.reason === "upstream-unattributed" || value.reason === "missing-source");
108
128
  }
109
129
  function isContribution(value) {
110
- return isRecord(value) && isContributionReference(value.reference) && isOrigin(value.author) && (value.latestEditor === null || isActorSnapshot(value.latestEditor)) && isBoundedString(value.acceptedAt, 256) && Array.isArray(value.mentionedPeople) && value.mentionedPeople.every(isPersonReference);
130
+ return isRecord(value) && isContributionReference(value.reference) && isOrigin(value.author) && (value.personAttribution === undefined || externalPersonSnapshotCodec.decode(value.personAttribution).ok) && (value.latestEditor === null || isActorSnapshot(value.latestEditor)) && isBoundedString(value.acceptedAt, 256) && Array.isArray(value.mentionedPeople) && value.mentionedPeople.every(isPersonReference);
111
131
  }
112
132
  function isCorrelation(value) {
113
133
  return isRecord(value) && idCodec("thread").decode(value.threadId).ok && isBoundedString(value.turnId) && isBoundedString(value.attemptId) && (value.toolCallId === null || isBoundedString(value.toolCallId));
@@ -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
  }
@@ -184,6 +205,17 @@ var provenanceCodec = {
184
205
  return value;
185
206
  }
186
207
  };
208
+ var externalPersonSnapshotCodec = {
209
+ decode(value) {
210
+ if (!isRecord(value) || Object.keys(value).some((key) => !["identity", "presentation", "evidence"].includes(key)) || value.evidence !== "integration-asserted" || !isActorSnapshot(value) || !isRecord(value.identity) || value.identity.kind !== "person" || Object.keys(value.identity).some((key) => !["kind", "key", "issuer", "subject"].includes(key))) {
211
+ return err("invalid-input", "External person attribution requires a canonical person and integration-asserted evidence.");
212
+ }
213
+ return ok(value);
214
+ },
215
+ encode(value) {
216
+ return value;
217
+ }
218
+ };
187
219
  function defaultIdentityKey(instance) {
188
220
  return `local:${instance}`;
189
221
  }
@@ -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._:@/-]*$/;
@@ -107,7 +127,7 @@ function isOrigin(value) {
107
127
  return value.kind === "unknown" && (value.reason === "legacy" || value.reason === "upstream-unattributed" || value.reason === "missing-source");
108
128
  }
109
129
  function isContribution(value) {
110
- return isRecord(value) && isContributionReference(value.reference) && isOrigin(value.author) && (value.latestEditor === null || isActorSnapshot(value.latestEditor)) && isBoundedString(value.acceptedAt, 256) && Array.isArray(value.mentionedPeople) && value.mentionedPeople.every(isPersonReference);
130
+ return isRecord(value) && isContributionReference(value.reference) && isOrigin(value.author) && (value.personAttribution === undefined || externalPersonSnapshotCodec.decode(value.personAttribution).ok) && (value.latestEditor === null || isActorSnapshot(value.latestEditor)) && isBoundedString(value.acceptedAt, 256) && Array.isArray(value.mentionedPeople) && value.mentionedPeople.every(isPersonReference);
111
131
  }
112
132
  function isCorrelation(value) {
113
133
  return isRecord(value) && idCodec("thread").decode(value.threadId).ok && isBoundedString(value.turnId) && isBoundedString(value.attemptId) && (value.toolCallId === null || isBoundedString(value.toolCallId));
@@ -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
  }
@@ -184,6 +205,17 @@ var provenanceCodec = {
184
205
  return value;
185
206
  }
186
207
  };
208
+ var externalPersonSnapshotCodec = {
209
+ decode(value) {
210
+ if (!isRecord(value) || Object.keys(value).some((key) => !["identity", "presentation", "evidence"].includes(key)) || value.evidence !== "integration-asserted" || !isActorSnapshot(value) || !isRecord(value.identity) || value.identity.kind !== "person" || Object.keys(value.identity).some((key) => !["kind", "key", "issuer", "subject"].includes(key))) {
211
+ return err("invalid-input", "External person attribution requires a canonical person and integration-asserted evidence.");
212
+ }
213
+ return ok(value);
214
+ },
215
+ encode(value) {
216
+ return value;
217
+ }
218
+ };
187
219
  function defaultIdentityKey(instance) {
188
220
  return `local:${instance}`;
189
221
  }
@@ -202,6 +234,7 @@ export {
202
234
  newConflictToken,
203
235
  identityKeyCodec,
204
236
  idCodec,
237
+ externalPersonSnapshotCodec,
205
238
  err,
206
239
  defaultIdentityKey
207
240
  };
@@ -187,6 +187,9 @@ function createIdentityServer(options) {
187
187
  },
188
188
  toolProvenance: (context) => disposed ? Promise.resolve(err("disposed", "Identity server is disposed.")) : host.readToolProvenance(context),
189
189
  sendExternal: (author, input, readOptions) => disposed ? Promise.resolve({ status: "rejected", error: { code: "disposed", message: "Identity server is disposed.", retry: "never" } }) : host.sendExternal(author, input, readOptions),
190
+ externalContributionCapabilities: () => disposed ? err("disposed", "Identity server is disposed.") : host.externalContributionCapabilities(),
191
+ sendExternalContribution: (contribution, input, readOptions) => disposed ? Promise.resolve({ status: "rejected", error: { code: "disposed", message: "Identity server is disposed.", retry: "never" } }) : host.sendExternalContribution(contribution, input, readOptions),
192
+ lookupExternalContribution: (operationId, readOptions) => disposed ? Promise.resolve(err("disposed", "Identity server is disposed.")) : host.lookupExternalContribution(operationId, readOptions),
190
193
  lookupOperation: (operationId, readOptions) => disposed ? Promise.resolve(err("disposed", "Identity server is disposed.")) : host.lookupOperation(operationId, readOptions),
191
194
  dispose
192
195
  };