@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 +4 -0
- package/dist/bb-entry-runtime.js +107 -87
- package/dist/host-runtime.js +54 -29
- package/dist/index-runtime.js +21 -0
- package/dist/model-runtime.js +21 -0
- package/dist/testing-runtime.js +112 -92
- package/host.d.ts +1 -0
- package/package.json +1 -1
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.
|
package/dist/bb-entry-runtime.js
CHANGED
|
@@ -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 = (
|
|
434
|
+
const terminateScope = (record2, scope) => {
|
|
411
435
|
if (!scope || scope.released)
|
|
412
436
|
return;
|
|
413
437
|
scope.released = true;
|
|
414
|
-
|
|
438
|
+
record2.controller.signal.removeEventListener("abort", scope.parentAbort);
|
|
415
439
|
scope.controller.abort();
|
|
416
|
-
|
|
440
|
+
record2.scopes.delete(scope);
|
|
417
441
|
};
|
|
418
|
-
const terminate = (
|
|
419
|
-
for (const scope of [...
|
|
420
|
-
terminateScope(
|
|
421
|
-
|
|
422
|
-
active.delete(
|
|
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
|
|
426
|
-
return !
|
|
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
|
|
430
|
-
if (!
|
|
431
|
-
return
|
|
432
|
-
if (
|
|
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
|
|
458
|
+
return record2;
|
|
435
459
|
};
|
|
436
460
|
const session = (context) => {
|
|
437
|
-
const
|
|
438
|
-
if (!
|
|
439
|
-
return unavailableSession(instanceId,
|
|
440
|
-
if (
|
|
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
|
|
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
|
|
484
|
+
const record2 = allowed.value;
|
|
461
485
|
let scope;
|
|
462
|
-
const parentAbort = () => terminateScope(
|
|
486
|
+
const parentAbort = () => terminateScope(record2, scope);
|
|
463
487
|
scope = { controller: new AbortController, parentAbort, released: false };
|
|
464
|
-
|
|
465
|
-
const expire = () => terminateScope(
|
|
466
|
-
|
|
467
|
-
const exposedSession =
|
|
468
|
-
const expectedKey =
|
|
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 ||
|
|
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
|
|
558
|
-
records.set(invocation,
|
|
559
|
-
active.add(
|
|
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
|
|
564
|
-
if (
|
|
565
|
-
terminate(
|
|
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
|
|
572
|
-
terminate(
|
|
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,
|
|
862
|
-
if (!isRecord3(
|
|
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 =
|
|
865
|
-
const 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 !==
|
|
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:
|
|
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
|
|
906
|
-
const decoded = profile(current.value,
|
|
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)) ||
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
2421
|
-
return disposed || !
|
|
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
|
|
2430
|
-
if (!
|
|
2431
|
-
return
|
|
2432
|
-
const opened = await server.personRequest(
|
|
2433
|
-
if (
|
|
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
|
-
|
|
2459
|
+
record2.value.people.add(opened.value);
|
|
2440
2460
|
return opened;
|
|
2441
2461
|
};
|
|
2442
2462
|
const guardEndpoint = async (invocation, operation) => {
|
|
2443
|
-
const
|
|
2444
|
-
if (!
|
|
2445
|
-
return
|
|
2446
|
-
const result = await operation(
|
|
2447
|
-
return
|
|
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
|
|
2469
|
-
if (!
|
|
2488
|
+
const record2 = recordFor(invocation);
|
|
2489
|
+
if (!record2.ok)
|
|
2470
2490
|
return unavailableSession2();
|
|
2471
|
-
const session = await server.session(
|
|
2472
|
-
return
|
|
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
|
|
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,
|
|
2575
|
+
invocations.set(invocation, record2);
|
|
2556
2576
|
const finish = () => {
|
|
2557
|
-
|
|
2558
|
-
for (const request of
|
|
2577
|
+
record2.settled = true;
|
|
2578
|
+
for (const request of record2.people)
|
|
2559
2579
|
request.dispose();
|
|
2560
|
-
|
|
2580
|
+
record2.people.clear();
|
|
2561
2581
|
};
|
|
2562
2582
|
return { invocation, finish };
|
|
2563
2583
|
};
|
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._:@/-]*$/;
|
|
@@ -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,
|
|
471
|
-
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)
|
|
472
496
|
return err("incompatible", "Provider directory record is malformed.");
|
|
473
|
-
const issuer =
|
|
474
|
-
const 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 !==
|
|
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:
|
|
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
|
|
515
|
-
const decoded = profile(current.value,
|
|
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)) ||
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
};
|
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._:@/-]*$/;
|
|
@@ -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
|
}
|
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._:@/-]*$/;
|
|
@@ -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
|
}
|
package/dist/testing-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._:@/-]*$/;
|
|
@@ -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,
|
|
474
|
-
if (!isRecord2(
|
|
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 =
|
|
477
|
-
const 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 !==
|
|
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:
|
|
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
|
|
518
|
-
const decoded = profile(current.value,
|
|
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)) ||
|
|
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
|
|
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 =
|
|
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 =
|
|
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 = (
|
|
2014
|
+
const terminateScope = (record2, scope) => {
|
|
1995
2015
|
if (!scope || scope.released)
|
|
1996
2016
|
return;
|
|
1997
2017
|
scope.released = true;
|
|
1998
|
-
|
|
2018
|
+
record2.controller.signal.removeEventListener("abort", scope.parentAbort);
|
|
1999
2019
|
scope.controller.abort();
|
|
2000
|
-
|
|
2020
|
+
record2.scopes.delete(scope);
|
|
2001
2021
|
};
|
|
2002
|
-
const terminate = (
|
|
2003
|
-
for (const scope of [...
|
|
2004
|
-
terminateScope(
|
|
2005
|
-
|
|
2006
|
-
active.delete(
|
|
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
|
|
2010
|
-
return !
|
|
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
|
|
2014
|
-
if (!
|
|
2015
|
-
return
|
|
2016
|
-
if (
|
|
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
|
|
2038
|
+
return record2;
|
|
2019
2039
|
};
|
|
2020
2040
|
const session = (context) => {
|
|
2021
|
-
const
|
|
2022
|
-
if (!
|
|
2023
|
-
return unavailableSession(instanceId,
|
|
2024
|
-
if (
|
|
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
|
|
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
|
|
2064
|
+
const record2 = allowed.value;
|
|
2045
2065
|
let scope;
|
|
2046
|
-
const parentAbort = () => terminateScope(
|
|
2066
|
+
const parentAbort = () => terminateScope(record2, scope);
|
|
2047
2067
|
scope = { controller: new AbortController, parentAbort, released: false };
|
|
2048
|
-
|
|
2049
|
-
const expire = () => terminateScope(
|
|
2050
|
-
|
|
2051
|
-
const exposedSession =
|
|
2052
|
-
const expectedKey =
|
|
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 ||
|
|
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
|
|
2142
|
-
records.set(invocation,
|
|
2143
|
-
active.add(
|
|
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
|
|
2148
|
-
if (
|
|
2149
|
-
terminate(
|
|
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
|
|
2156
|
-
terminate(
|
|
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
|
|
2784
|
-
return disposed || !
|
|
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
|
|
2793
|
-
if (!
|
|
2794
|
-
return
|
|
2795
|
-
const opened = await server.personRequest(
|
|
2796
|
-
if (
|
|
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
|
-
|
|
2822
|
+
record2.value.people.add(opened.value);
|
|
2803
2823
|
return opened;
|
|
2804
2824
|
};
|
|
2805
2825
|
const guardEndpoint = async (invocation, operation) => {
|
|
2806
|
-
const
|
|
2807
|
-
if (!
|
|
2808
|
-
return
|
|
2809
|
-
const result = await operation(
|
|
2810
|
-
return
|
|
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
|
|
2832
|
-
if (!
|
|
2851
|
+
const record2 = recordFor(invocation);
|
|
2852
|
+
if (!record2.ok)
|
|
2833
2853
|
return unavailableSession2();
|
|
2834
|
-
const session = await server.session(
|
|
2835
|
-
return
|
|
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
|
|
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,
|
|
2938
|
+
invocations.set(invocation, record2);
|
|
2919
2939
|
const finish = () => {
|
|
2920
|
-
|
|
2921
|
-
for (const request of
|
|
2940
|
+
record2.settled = true;
|
|
2941
|
+
for (const request of record2.people)
|
|
2922
2942
|
request.dispose();
|
|
2923
|
-
|
|
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
|
|
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(
|
|
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 ?
|
|
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
|
|
3406
|
-
return "{" + Object.keys(
|
|
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'];
|