@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.
- package/README.md +18 -0
- package/bb.d.ts +3 -0
- package/dist/bb-entry-runtime.js +235 -70
- package/dist/host-runtime.js +169 -12
- package/dist/index-runtime.js +33 -1
- package/dist/model-runtime.js +34 -1
- package/dist/server-runtime.js +3 -0
- package/dist/testing-runtime.js +240 -75
- package/host.d.ts +29 -0
- package/model.d.ts +7 -0
- package/package.json +1 -1
- package/server.d.ts +5 -1
package/dist/host-runtime.js
CHANGED
|
@@ -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,
|
|
460
|
-
if (!isRecord2(
|
|
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 =
|
|
463
|
-
const 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 !==
|
|
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:
|
|
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
|
|
504
|
-
const decoded = profile(current.value,
|
|
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
|
};
|
package/dist/index-runtime.js
CHANGED
|
@@ -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
|
}
|
package/dist/model-runtime.js
CHANGED
|
@@ -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
|
};
|
package/dist/server-runtime.js
CHANGED
|
@@ -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
|
};
|