@openlfcp/client 0.2.2 → 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.
Files changed (43) hide show
  1. package/dist/data-unit.d.ts +11 -0
  2. package/dist/data-unit.js +9 -0
  3. package/dist/identity/accept.d.ts +115 -0
  4. package/dist/identity/accept.js +335 -0
  5. package/dist/identity/context.d.ts +214 -0
  6. package/dist/identity/context.js +495 -0
  7. package/dist/identity/devices.d.ts +127 -0
  8. package/dist/identity/devices.js +221 -0
  9. package/dist/identity/document.d.ts +112 -0
  10. package/dist/identity/document.js +54 -0
  11. package/dist/identity/index.d.ts +17 -0
  12. package/dist/identity/index.js +17 -0
  13. package/dist/identity/journal.d.ts +139 -0
  14. package/dist/identity/journal.js +202 -0
  15. package/dist/identity/network.d.ts +88 -0
  16. package/dist/identity/network.js +243 -0
  17. package/dist/identity/pairing-request.d.ts +147 -0
  18. package/dist/identity/pairing-request.js +422 -0
  19. package/dist/identity/pairing.d.ts +225 -0
  20. package/dist/identity/pairing.js +624 -0
  21. package/dist/identity/phases.d.ts +75 -0
  22. package/dist/identity/phases.js +104 -0
  23. package/dist/identity/proof.d.ts +45 -0
  24. package/dist/identity/proof.js +71 -0
  25. package/dist/identity/reconcile.d.ts +73 -0
  26. package/dist/identity/reconcile.js +273 -0
  27. package/dist/identity/resource.d.ts +74 -0
  28. package/dist/identity/resource.js +201 -0
  29. package/dist/identity/revocation.d.ts +106 -0
  30. package/dist/identity/revocation.js +403 -0
  31. package/dist/identity/roster.d.ts +89 -0
  32. package/dist/identity/roster.js +130 -0
  33. package/dist/identity/rotation.d.ts +131 -0
  34. package/dist/identity/rotation.js +363 -0
  35. package/dist/index.d.ts +2 -1
  36. package/dist/index.js +2 -1
  37. package/dist/revoke.d.ts +8 -0
  38. package/dist/revoke.js +12 -8
  39. package/dist/storage.d.ts +18 -0
  40. package/dist/storage.js +26 -0
  41. package/dist/sync-client.d.ts +2 -0
  42. package/dist/sync-client.js +1 -0
  43. package/package.json +5 -5
@@ -31,6 +31,10 @@ export interface CreateDataUnitOptions<T> {
31
31
  readonly previousUnitId: DataUnitId | null;
32
32
  readonly profile: DataProfileCodec<T>;
33
33
  readonly value: T;
34
+ /** The Resource's durable write barrier (writeBarrier), if any: a set barrier refuses the write (§8.6). */
35
+ readonly writeBarrier?: {
36
+ readonly state: string;
37
+ } | undefined;
34
38
  }
35
39
  export interface CreatedDataUnit {
36
40
  /** The exact signed bytes: opaque, the only form sent to servers and peers. */
@@ -45,6 +49,13 @@ export interface CreatedDataUnit {
45
49
  * Resource's, that the actor holds data/write at the head and that the DEK
46
50
  * is the one committed for the head's current epoch; then it reserves the
47
51
  * next sequence and seals the unit (sealDataUnit).
52
+ *
53
+ * Contract (IDENTITY-PROFILE-01 §8.6): this primitive sees no storage, so
54
+ * it cannot know that the device was refused or proven revoked after the
55
+ * view was validated. Callers that write for a stored Resource pass its
56
+ * durable `writeBarrier` (or call `checkWriteBarrier` first); the
57
+ * storage-backed paths (createQueuedDataUnit, commitOperation, the
58
+ * identity flows) do it themselves.
48
59
  */
49
60
  export declare function createDataUnit<T>(options: CreateDataUnitOptions<T>): Promise<CreatedDataUnit>;
50
61
  //# sourceMappingURL=data-unit.d.ts.map
package/dist/data-unit.js CHANGED
@@ -7,8 +7,17 @@ import { ABILITY, hasAbility, sealDataUnit, } from "@openlfcp/wire";
7
7
  * Resource's, that the actor holds data/write at the head and that the DEK
8
8
  * is the one committed for the head's current epoch; then it reserves the
9
9
  * next sequence and seals the unit (sealDataUnit).
10
+ *
11
+ * Contract (IDENTITY-PROFILE-01 §8.6): this primitive sees no storage, so
12
+ * it cannot know that the device was refused or proven revoked after the
13
+ * view was validated. Callers that write for a stored Resource pass its
14
+ * durable `writeBarrier` (or call `checkWriteBarrier` first); the
15
+ * storage-backed paths (createQueuedDataUnit, commitOperation, the
16
+ * identity flows) do it themselves.
10
17
  */
11
18
  export async function createDataUnit(options) {
19
+ if (options.writeBarrier !== undefined)
20
+ throw new LfcpError("AUTHORIZATION_FAILED", `this device must not write to the Resource: ${options.writeBarrier.state} (IDENTITY-PROFILE-01 §8.6)`);
12
21
  const atHead = options.view.stateAt(options.controlHead);
13
22
  if (atHead === undefined)
14
23
  throw new LfcpError("MISSING_DEPENDENCY", "the Control Head is not on the validated chain");
@@ -0,0 +1,115 @@
1
+ import { type ResourceId } from "@openlfcp/core";
2
+ import { type CertificateChain, type PrincipalDescriptor, type Signer } from "@openlfcp/wire";
3
+ import type { InvitationLink } from "../invite.js";
4
+ import { type KeyDelivery } from "./context.js";
5
+ import { type RegistrationResult } from "./devices.js";
6
+ import { type LinearChain } from "./phases.js";
7
+ import { type IdentityFlowOptions } from "./resource.js";
8
+ export interface DeviceResourceResult {
9
+ /** Packages to the root (§7.2 step 2, §7.3). */
10
+ readonly rootKeys: KeyDelivery;
11
+ /** The root's other devices registered here by this managing device; empty without `12`. */
12
+ readonly siblings: readonly RegistrationResult[];
13
+ /** The roster entry was written in `I`. */
14
+ readonly roster: boolean;
15
+ }
16
+ /** §7.2: the claim of an invitation for the root, signed by the Invitation Principal (W §18.1 rule 5). */
17
+ export declare const claimForRootRecord: (chain: LinearChain, invitee: Signer, invitationGrantId: Uint8Array, root: PrincipalDescriptor, abilities: readonly bigint[]) => Uint8Array;
18
+ /** §7.2 step 1: the device's own first registration (W §17.4 rule 6). */
19
+ export declare const selfRegistrationRecord: (chain: LinearChain, device: Signer, certificates: CertificateChain) => Uint8Array;
20
+ /**
21
+ * The Resource is joined or created and keyed, but its roster entry could
22
+ * not be written: the operation stays open; resume it with `operation`
23
+ * (§7.2 step 4, §7.3).
24
+ */
25
+ export interface IncompleteDeviceResource extends DeviceResourceResult {
26
+ readonly kind: "incomplete";
27
+ readonly operation: string;
28
+ readonly resource: ResourceId;
29
+ }
30
+ export type DeviceAcceptResult = IncompleteDeviceResource | ({
31
+ readonly kind: "claimed";
32
+ readonly operation: string;
33
+ readonly resource: ResourceId;
34
+ /** The claim record: the root's grant (§18.1). */
35
+ readonly grantId: Uint8Array;
36
+ } & DeviceResourceResult)
37
+ /**
38
+ * This device cannot write the roster (`holds(D_B, 2)` in `I` is false,
39
+ * e.g. a Read device): the invitation stays unclaimed; complete it on an
40
+ * installation that can write `I` (§7.2).
41
+ */
42
+ | {
43
+ readonly kind: "needs-writer";
44
+ }
45
+ /** The coordinator refused the claim (e.g. the invitation is used up). */
46
+ | {
47
+ readonly kind: "refused";
48
+ readonly operation: string;
49
+ readonly code: string;
50
+ }
51
+ /** The link has no secret, or its secret is not the grant's subject (W §18.2). */
52
+ | {
53
+ readonly kind: "invalid-invitation";
54
+ readonly reason: string;
55
+ } | {
56
+ readonly kind: "conflict";
57
+ } | {
58
+ readonly kind: "unavailable";
59
+ readonly operation?: string;
60
+ readonly reason: string;
61
+ };
62
+ /**
63
+ * §7.2: accepts an invitation on a device `D_B` of root `R`. The claim
64
+ * names `R` (claimant = `R`'s descriptor, known from `I`'s Genesis) and is
65
+ * signed by the Invitation Principal; then, as one durable operation, the
66
+ * device registers itself (W §17.4 rule 6), seals every epoch it holds to
67
+ * `R`, registers and keys `R`'s other devices when it has `12`, and adds
68
+ * the Resource to the roster. When the installation is the root itself,
69
+ * the claim names it as today and only the last two steps remain.
70
+ */
71
+ export declare function acceptInvitationAsDevice(o: IdentityFlowOptions & {
72
+ readonly link: string | InvitationLink;
73
+ /** The coordinator to use (default: the link's first endpoint). */
74
+ readonly url?: string;
75
+ readonly name?: string;
76
+ /** Resume this operation. */
77
+ readonly operation?: string;
78
+ }): Promise<DeviceAcceptResult>;
79
+ export type DeviceCreateResult = IncompleteDeviceResource | ({
80
+ readonly kind: "created";
81
+ readonly operation: string;
82
+ readonly resource: ResourceId;
83
+ readonly chain: LinearChain;
84
+ } & DeviceResourceResult)
85
+ /** Only a personal device with `13` creates Resources for its root (§7.3). */
86
+ | {
87
+ readonly kind: "not-owner-acting";
88
+ } | {
89
+ readonly kind: "refused";
90
+ readonly operation: string;
91
+ readonly code: string;
92
+ } | {
93
+ readonly kind: "conflict";
94
+ } | {
95
+ readonly kind: "unavailable";
96
+ readonly operation?: string;
97
+ readonly reason: string;
98
+ };
99
+ /**
100
+ * §7.3: a personal device with `13` creates a Resource owned by its root:
101
+ * Genesis field 1 = the root's descriptor, field 5 = the device's chain,
102
+ * signed by the device (W §15), hosted by the device's session; then the
103
+ * epoch-0 DEK sealed to the root, the root's other devices registered and
104
+ * keyed when the device has `12`, and the roster entry — so the root opens
105
+ * the Resource even if the creating device goes offline right after.
106
+ */
107
+ export declare function createResourceAsDevice(o: IdentityFlowOptions & {
108
+ readonly dataProfile: string;
109
+ /** Route and coordinator of the new Resource. */
110
+ readonly url: string;
111
+ readonly name?: string;
112
+ /** Resume this operation. */
113
+ readonly operation?: string;
114
+ }): Promise<DeviceCreateResult>;
115
+ //# sourceMappingURL=accept.d.ts.map
@@ -0,0 +1,335 @@
1
+ import { bytesEqual, dataEpoch, fromHex, generateResourceId, principalId, resourceId, secureRandom, toHex, } from "@openlfcp/core";
2
+ import { dekCommitment, generateResourceDEK } from "@openlfcp/crypto";
3
+ import { ABILITY, activeDevice, DEVICE_ABILITY, DEVICE_KIND, hasAbility, invitationPrincipal, parseInviteUri, signControlRecord, validateControlChain, verifyInvitationSecret, } from "@openlfcp/wire";
4
+ import { deliverKeys, fetchDeks, genesisOwner, IdentityConflict, runControlPhase, storeDek, syncChain, } from "./context.js";
5
+ import { registerDevice } from "./devices.js";
6
+ import { newOperationId, OperationJournal, PackageLedger, phaseOf, } from "./journal.js";
7
+ import { ChannelPool, IdentityRefused, IdentityUnavailable } from "./network.js";
8
+ import { classifyRegister } from "./phases.js";
9
+ import { identityTarget } from "./resource.js";
10
+ import { writeRosterEntry } from "./roster.js";
11
+ /**
12
+ * Invitations accepted by a device and Resources created from a device
13
+ * (IDENTITY-PROFILE-01 §7.2, §7.3). Inviting is unchanged; a device that
14
+ * accepts claims for its root, registers itself, keys the root and (as a
15
+ * managing device) its siblings, and adds the Resource to the roster, as
16
+ * one durable operation, so no accepted or created Resource is ever
17
+ * unreachable for the human's other installations.
18
+ */
19
+ /** The root's descriptor: `I`'s Genesis owner (§7.2). */
20
+ function rootDescriptor(I) {
21
+ const g = I.records[0];
22
+ if (g?.body.type !== "GENESIS")
23
+ throw new Error("the Identity Resource's chain has no Genesis");
24
+ return g.body.owner;
25
+ }
26
+ /** This installation's chain in `I`, when it is an active personal device of the root. */
27
+ function myChain(I, me) {
28
+ const d = activeDevice(I.state, me);
29
+ return d !== undefined && d.kind === DEVICE_KIND.PERSONAL && d.chain !== null
30
+ ? d.chain
31
+ : undefined;
32
+ }
33
+ /** The other active personal devices of the root in `I`, with their chains (§7.2 step 3, §7.3). */
34
+ function siblings(I, me) {
35
+ const R = genesisOwner(I);
36
+ const out = [];
37
+ for (const [hex, d] of I.state.devices) {
38
+ const p = principalId(fromHex(hex));
39
+ if (bytesEqual(p, me) || d.kind !== DEVICE_KIND.PERSONAL || d.chain === null)
40
+ continue;
41
+ if (!bytesEqual(d.root, R) || activeDevice(I.state, p) === undefined)
42
+ continue;
43
+ out.push(d.chain);
44
+ }
45
+ return out;
46
+ }
47
+ /**
48
+ * The common tail of §7.2 steps 2–4 and §7.3: seals every epoch held of
49
+ * `X` to the root, registers and keys the root's other active personal
50
+ * devices when this installation manages devices, and adds `X` to the
51
+ * roster of `I`. Each part is idempotent (ledger, §9.2 classification,
52
+ * journaled unit), so a resumed operation repeats nothing.
53
+ */
54
+ async function keyAndList(o, pool, journal, operation, I, iUrl, X, url, entry) {
55
+ const me = o.installation.signer;
56
+ const R = rootDescriptor(I);
57
+ const channel = await pool.get(me, url);
58
+ const chainX = await syncChain(o.storage, channel, X);
59
+ const deks = await fetchDeks(o.storage, o.secrets, channel, chainX, o.installation);
60
+ const rootKeys = bytesEqual(R.principalId, me.descriptor.principalId)
61
+ ? { sent: [], complete: 0, skipped: [] }
62
+ : await deliverKeys({
63
+ channel,
64
+ ledger: new PackageLedger(o.storage),
65
+ chain: chainX,
66
+ sender: me,
67
+ recipients: [R],
68
+ deks,
69
+ });
70
+ const mine = activeDevice(I.state, me.descriptor.principalId);
71
+ const manages = bytesEqual(R.principalId, me.descriptor.principalId) ||
72
+ mine?.mask.includes(DEVICE_ABILITY.DEVICE_MANAGE);
73
+ const others = [];
74
+ if (manages)
75
+ for (const chain of siblings(I, me.descriptor.principalId)) {
76
+ const r = await registerDevice({ ...o, chain, targets: [{ resource: X, url }] });
77
+ others.push(...r.results);
78
+ }
79
+ // §7.2 step 4, §7.3: the roster entry, so the human's other installations reach X.
80
+ const written = await writeRosterEntry(o, await pool.get(me, iUrl), journal, operation, I.state.resourceId, X, entry);
81
+ const roster = written.state !== "refused";
82
+ return { rootKeys, siblings: Object.freeze(others), roster };
83
+ }
84
+ /** §7.2: the claim of an invitation for the root, signed by the Invitation Principal (W §18.1 rule 5). */
85
+ export const claimForRootRecord = (chain, invitee, invitationGrantId, root, abilities) => signControlRecord({
86
+ resourceId: chain.state.resourceId,
87
+ controlSeq: chain.state.seq + 1n,
88
+ prevControlId: chain.state.head,
89
+ }, {
90
+ type: "CAPABILITY_CLAIM",
91
+ invitationGrantId: invitationGrantId,
92
+ claimant: root,
93
+ abilities,
94
+ }, invitee).bytes;
95
+ /** §7.2 step 1: the device's own first registration (W §17.4 rule 6). */
96
+ export const selfRegistrationRecord = (chain, device, certificates) => signControlRecord({
97
+ resourceId: chain.state.resourceId,
98
+ controlSeq: chain.state.seq + 1n,
99
+ prevControlId: chain.state.head,
100
+ }, { type: "DEVICE_GRANT", root: certificates.root, chain: certificates }, device).bytes;
101
+ /**
102
+ * §7.2: accepts an invitation on a device `D_B` of root `R`. The claim
103
+ * names `R` (claimant = `R`'s descriptor, known from `I`'s Genesis) and is
104
+ * signed by the Invitation Principal; then, as one durable operation, the
105
+ * device registers itself (W §17.4 rule 6), seals every epoch it holds to
106
+ * `R`, registers and keys `R`'s other devices when it has `12`, and adds
107
+ * the Resource to the roster. When the installation is the root itself,
108
+ * the claim names it as today and only the last two steps remain.
109
+ */
110
+ export async function acceptInvitationAsDevice(o) {
111
+ const invitation = parseInviteUri(typeof o.link === "string" ? o.link : o.link.reveal());
112
+ const secret = invitation.secret;
113
+ if (secret === undefined)
114
+ return { kind: "invalid-invitation", reason: "the link has no #secret= fragment" };
115
+ const X = invitation.resourceId;
116
+ const url = o.url ?? invitation.endpoints[0];
117
+ const me = o.installation.signer;
118
+ const target = await identityTarget(o);
119
+ const journal = new OperationJournal(o.storage);
120
+ const pool = new ChannelPool(o.network);
121
+ let operation = o.operation;
122
+ try {
123
+ const iChannel = await pool.get(me, target.url);
124
+ const I = await syncChain(o.storage, iChannel, target.resource);
125
+ const R = rootDescriptor(I);
126
+ const isRoot = bytesEqual(R.principalId, me.descriptor.principalId);
127
+ const chainB = isRoot ? undefined : myChain(I, me.descriptor.principalId);
128
+ // §7.2: no claim unless this device can write the roster entry in I.
129
+ if (!isRoot &&
130
+ (chainB === undefined || !hasAbility(I.state, me.descriptor.principalId, ABILITY.DATA_WRITE)))
131
+ return { kind: "needs-writer" };
132
+ // The Invitation Principal opens X, verifies its secret and opens its package (W §73).
133
+ const invitee = { key: secret.signingKey, descriptor: invitationPrincipal(secret) };
134
+ const ch = await pool.get(invitee, url);
135
+ let chainX = await syncChain(o.storage, ch, X);
136
+ const claimSigner = verifyInvitationSecretOn(chainX, invitation.grantId, secret);
137
+ if (typeof claimSigner === "string")
138
+ return { kind: "invalid-invitation", reason: claimSigner };
139
+ await fetchDeks(o.storage, o.secrets, ch, chainX, {
140
+ signer: invitee,
141
+ agreement: secret.agreementKey,
142
+ });
143
+ const grant = chainX.state.grants.get(toHex(invitation.grantId));
144
+ if (grant === undefined)
145
+ return { kind: "invalid-invitation", reason: "the grant is not on the chain" };
146
+ const delegable = grant.delegable.includes(ABILITY.INVITE_CLAIM);
147
+ const abilities = grant.abilities.filter((a) => a !== ABILITY.INVITE_CLAIM || delegable);
148
+ if (operation === undefined || (await journal.get(operation)) === undefined) {
149
+ operation = operation ?? newOperationId("accept-invitation", secureRandom(8));
150
+ await journal.begin({
151
+ id: operation,
152
+ kind: "accept-invitation",
153
+ createdAt: o.now(),
154
+ intent: {
155
+ resource: toHex(X),
156
+ url,
157
+ grant: toHex(invitation.grantId),
158
+ root: toHex(R.principalId),
159
+ },
160
+ });
161
+ }
162
+ const op = operation;
163
+ // The claim, for R (W §18.1 rule 5): a claim already on the chain for R is this operation's or complete.
164
+ const claim = await runControlPhase(o.storage, ch, journal, op, {
165
+ id: "claim",
166
+ kind: "claim",
167
+ resource: X,
168
+ classify: (chain, isOwn) => {
169
+ const done = chain.records.find((r) => r.body.type === "CAPABILITY_CLAIM" &&
170
+ bytesEqual(r.body.invitationGrantId, invitation.grantId) &&
171
+ bytesEqual(r.body.claimant.principalId, R.principalId));
172
+ if (done === undefined)
173
+ return "pending";
174
+ return isOwn(toHex(done.signed.id)) ? "committed-own" : "complete";
175
+ },
176
+ build: (chain) => claimForRootRecord(chain, claimSigner, invitation.grantId, R, abilities),
177
+ });
178
+ if (claim.state === "refused") {
179
+ await journal.setStatus(op, "abandoned");
180
+ return { kind: "refused", operation: op, code: claim.code ?? "refused" };
181
+ }
182
+ chainX = claim.chain;
183
+ const grantId = chainX.records.find((r) => r.body.type === "CAPABILITY_CLAIM" &&
184
+ bytesEqual(r.body.invitationGrantId, invitation.grantId))?.signed.id;
185
+ // Step 1: self-registration (W §17.4 rule 6), proposed through the Invitation Principal's session.
186
+ if (chainB !== undefined) {
187
+ const reg = await runControlPhase(o.storage, ch, journal, op, {
188
+ id: `register:${toHex(X)}`,
189
+ kind: "register",
190
+ resource: X,
191
+ classify: (chain, isOwn) => classifyRegister(chain, { root: R.principalId, chain: chainB, isOwnRecord: isOwn }),
192
+ build: (chain) => selfRegistrationRecord(chain, me, chainB),
193
+ });
194
+ if (reg.state === "refused" || reg.state === "superseded")
195
+ return { kind: "refused", operation: op, code: reg.code ?? reg.state };
196
+ }
197
+ // Steps 2–4.
198
+ const rest = await keyAndList(o, pool, journal, op, I, target.url, X, url, {
199
+ profile: chainX.state.dataProfile,
200
+ endpoints: [...invitation.endpoints],
201
+ coordinator: chainX.state.route.coordinatorUrl,
202
+ ...(o.name === undefined ? {} : { name: o.name }),
203
+ });
204
+ // The roster entry is the point of §7.2: without it the operation stays open.
205
+ if (!rest.roster)
206
+ return { kind: "incomplete", operation: op, resource: X, ...rest };
207
+ await journal.setStatus(op, "done");
208
+ return { kind: "claimed", operation: op, resource: X, grantId, ...rest };
209
+ }
210
+ catch (e) {
211
+ if (e instanceof IdentityUnavailable || e instanceof IdentityRefused)
212
+ return {
213
+ kind: "unavailable",
214
+ ...(operation === undefined ? {} : { operation }),
215
+ reason: e.message,
216
+ };
217
+ if (e instanceof IdentityConflict)
218
+ return { kind: "conflict" };
219
+ throw e;
220
+ }
221
+ finally {
222
+ pool.closeAll();
223
+ }
224
+ }
225
+ function verifyInvitationSecretOn(chain, grantId, secret) {
226
+ try {
227
+ return verifyInvitationSecret(chain.state, grantId, secret);
228
+ }
229
+ catch (e) {
230
+ return e instanceof Error ? e.message : String(e);
231
+ }
232
+ }
233
+ /**
234
+ * §7.3: a personal device with `13` creates a Resource owned by its root:
235
+ * Genesis field 1 = the root's descriptor, field 5 = the device's chain,
236
+ * signed by the device (W §15), hosted by the device's session; then the
237
+ * epoch-0 DEK sealed to the root, the root's other devices registered and
238
+ * keyed when the device has `12`, and the roster entry — so the root opens
239
+ * the Resource even if the creating device goes offline right after.
240
+ */
241
+ export async function createResourceAsDevice(o) {
242
+ const me = o.installation.signer;
243
+ const target = await identityTarget(o);
244
+ const journal = new OperationJournal(o.storage);
245
+ const pool = new ChannelPool(o.network);
246
+ let operation = o.operation;
247
+ try {
248
+ const iChannel = await pool.get(me, target.url);
249
+ const I = await syncChain(o.storage, iChannel, target.resource);
250
+ const R = rootDescriptor(I);
251
+ let op;
252
+ if (operation === undefined || (await journal.get(operation)) === undefined) {
253
+ const chain = myChain(I, me.descriptor.principalId);
254
+ if (chain === undefined || !chain.leaf.payload.mask.includes(DEVICE_ABILITY.OWNER_ACT))
255
+ return { kind: "not-owner-acting" };
256
+ const X = generateResourceId();
257
+ const dek = generateResourceDEK();
258
+ const genesis = signControlRecord({ resourceId: X, controlSeq: 0n, prevControlId: null }, {
259
+ type: "GENESIS",
260
+ dataProfile: o.dataProfile,
261
+ owner: R,
262
+ dekCommitment: dekCommitment(X, dataEpoch(0n), dek),
263
+ endpoints: [{ url: o.url, priority: 0n }],
264
+ coordinatorUrl: o.url,
265
+ deviceChain: chain,
266
+ }, me);
267
+ const start = validateControlChain([genesis.bytes]);
268
+ if (start.kind !== "linear")
269
+ throw new Error(`the Genesis does not validate: ${start.kind === "invalid" ? start.error.message : start.kind}`);
270
+ // The DEK first, then the journal that names the Genesis (§9.1).
271
+ await storeDek(o.storage, o.secrets, start, dataEpoch(0n), dek);
272
+ operation = operation ?? newOperationId("create-resource", secureRandom(8));
273
+ op = await journal.begin({
274
+ id: operation,
275
+ kind: "create-resource",
276
+ createdAt: o.now(),
277
+ intent: {
278
+ resource: toHex(X),
279
+ url: o.url,
280
+ profile: o.dataProfile,
281
+ ...(o.name === undefined ? {} : { name: o.name }),
282
+ },
283
+ phases: [
284
+ {
285
+ id: "host",
286
+ kind: "host",
287
+ resource: toHex(X),
288
+ state: "pending",
289
+ bytes: toHex(genesis.bytes),
290
+ },
291
+ ],
292
+ });
293
+ }
294
+ else
295
+ op = await journal.load(operation);
296
+ const X = resourceId(fromHex(op.intent.resource));
297
+ const url = op.intent.url;
298
+ const host = phaseOf(op, "host");
299
+ if (host !== undefined && host.state !== "committed-own") {
300
+ const answer = await (await pool.get(me, url)).host(fromHex(host.bytes));
301
+ if (answer.kind === "nack") {
302
+ await journal.putPhase(op.id, { ...host, state: "refused", code: answer.code });
303
+ await journal.setStatus(op.id, "abandoned");
304
+ return { kind: "refused", operation: op.id, code: answer.code };
305
+ }
306
+ await journal.putPhase(op.id, { ...host, state: "committed-own" });
307
+ }
308
+ const rest = await keyAndList(o, pool, journal, op.id, I, target.url, X, url, {
309
+ profile: op.intent.profile,
310
+ endpoints: [url],
311
+ coordinator: url,
312
+ ...(op.intent.name === undefined ? {} : { name: op.intent.name }),
313
+ });
314
+ const chain = await syncChain(o.storage, await pool.get(me, url), X);
315
+ if (!rest.roster)
316
+ return { kind: "incomplete", operation: op.id, resource: X, ...rest };
317
+ await journal.setStatus(op.id, "done");
318
+ return { kind: "created", operation: op.id, resource: X, chain, ...rest };
319
+ }
320
+ catch (e) {
321
+ if (e instanceof IdentityUnavailable || e instanceof IdentityRefused)
322
+ return {
323
+ kind: "unavailable",
324
+ ...(operation === undefined ? {} : { operation }),
325
+ reason: e.message,
326
+ };
327
+ if (e instanceof IdentityConflict)
328
+ return { kind: "conflict" };
329
+ throw e;
330
+ }
331
+ finally {
332
+ pool.closeAll();
333
+ }
334
+ }
335
+ //# sourceMappingURL=accept.js.map