@tinycloud/operations 0.4.0-beta.9 → 0.4.0
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/coverage.json +6 -0
- package/dist/artifacts.cjs +19 -4
- package/dist/artifacts.cjs.map +1 -1
- package/dist/artifacts.d.cts +1 -1
- package/dist/artifacts.d.ts +1 -1
- package/dist/artifacts.js +19 -4
- package/dist/artifacts.js.map +1 -1
- package/dist/cli-runtime.cjs +162 -27
- package/dist/cli-runtime.cjs.map +1 -1
- package/dist/cli-runtime.d.cts +1 -1
- package/dist/cli-runtime.d.ts +1 -1
- package/dist/cli-runtime.js +162 -26
- package/dist/cli-runtime.js.map +1 -1
- package/dist/{contract-CjuEjE-M.d.cts → contract-BRwWJpPs.d.cts} +24 -2
- package/dist/{contract-CjuEjE-M.d.ts → contract-BRwWJpPs.d.ts} +24 -2
- package/dist/delegation-binding.cjs +79 -16
- package/dist/delegation-binding.cjs.map +1 -1
- package/dist/delegation-binding.d.cts +43 -13
- package/dist/delegation-binding.d.ts +43 -13
- package/dist/delegation-binding.js +78 -16
- package/dist/delegation-binding.js.map +1 -1
- package/dist/index.cjs +160 -28
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +160 -27
- package/dist/index.js.map +1 -1
- package/dist/profile.cjs.map +1 -1
- package/dist/profile.d.cts +1 -1
- package/dist/profile.d.ts +1 -1
- package/dist/profile.js.map +1 -1
- package/dist/state.cjs +9 -3
- package/dist/state.cjs.map +1 -1
- package/dist/state.d.cts +7 -0
- package/dist/state.d.ts +7 -0
- package/dist/state.js +9 -3
- package/dist/state.js.map +1 -1
- package/generated/operations.json +2661 -4
- package/package.json +4 -4
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { RuntimeDelegationActivator, PortableDelegation, PermissionEntry
|
|
1
|
+
import { ValidatedRuntimeDelegation, RuntimeDelegationActivator, PortableDelegation, PermissionEntry } from '@tinycloud/node-sdk';
|
|
2
2
|
import { O as OperationSpaceResolver } from './authority-ofwbEqmj.cjs';
|
|
3
|
+
import { S as StoredGrantSkipReason } from './contract-BRwWJpPs.cjs';
|
|
3
4
|
import '@tinycloud/sdk-core';
|
|
4
5
|
|
|
5
6
|
/** Creates the one owner-aware resolver shared by operation matching paths. */
|
|
@@ -19,16 +20,20 @@ declare function operationSpaceResolver(node: unknown, ownerSpace?: string): (sp
|
|
|
19
20
|
* own signed capabilities (`unbound-import:<cid>`).
|
|
20
21
|
* - The one-time migration of records stored before bindings existed: exactly
|
|
21
22
|
* each record's own signed capabilities (`migrated:<cid>`).
|
|
23
|
+
* - The CLI's own grants (`tc auth request --grant`, permission escalation),
|
|
24
|
+
* once checked against the request they answer: exactly each grant's own
|
|
25
|
+
* signed capabilities (`cli-grant:<cid>`). Their signed-login records carry
|
|
26
|
+
* the `siweProof` replay verifies them from.
|
|
22
27
|
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
28
|
+
* Signed-login records the CLI stored before it kept a `siweProof` cannot be
|
|
29
|
+
* verified here and keep their legacy CLI replay.
|
|
25
30
|
*
|
|
26
31
|
* The binding is local profile data. It stops records written by any other
|
|
27
32
|
* path from granting authority. It does not stop someone who can write the
|
|
28
33
|
* profile directory, who already holds the session key and the signed bytes.
|
|
29
34
|
*/
|
|
30
|
-
type BindingSource = "migration" | "unbound-import";
|
|
31
|
-
/** The audit note written beside a binding
|
|
35
|
+
type BindingSource = "migration" | "unbound-import" | "cli-grant";
|
|
36
|
+
/** The audit note written beside a binding the record's writer synthesized. */
|
|
32
37
|
declare const BINDING_NOTES: Record<BindingSource, string>;
|
|
33
38
|
/** The per-profile marker: present once that profile's unbound records were migrated. */
|
|
34
39
|
declare function bindingMigrationPath(profile: string): string;
|
|
@@ -38,9 +43,9 @@ declare function bindingMigrationPath(profile: string): string;
|
|
|
38
43
|
* - `compact` and `signed-login` records go through validated activation and
|
|
39
44
|
* the binding rule.
|
|
40
45
|
* - `other` records keep the CLI's legacy replay: an unbound record whose
|
|
41
|
-
* bytes are not compact and that has no `siweProof`, which is how
|
|
42
|
-
*
|
|
43
|
-
* them.
|
|
46
|
+
* bytes are not compact and that has no `siweProof`, which is how earlier
|
|
47
|
+
* CLI releases stored their own signed-login grants. The operations runtime
|
|
48
|
+
* never installs them.
|
|
44
49
|
* - `refused` records install nothing anywhere.
|
|
45
50
|
*
|
|
46
51
|
* A record is `refused` unless its `delegationHeader` has exactly one own
|
|
@@ -61,6 +66,16 @@ declare function storedDelegationKind(entry: unknown): "compact" | "signed-login
|
|
|
61
66
|
* when the record installs nothing.
|
|
62
67
|
*/
|
|
63
68
|
declare function replayLimit(entry: unknown, migrated: boolean): readonly PermissionEntry[] | "signed" | undefined;
|
|
69
|
+
/**
|
|
70
|
+
* Binds the record of one of the CLI's own grants, already checked against
|
|
71
|
+
* the request it answers, to exactly the capabilities its signed bytes grant.
|
|
72
|
+
* They are read through the validated activation replay uses (nothing is
|
|
73
|
+
* activated), so the binding is the authority replay will derive. Throws when
|
|
74
|
+
* validated activation cannot read them for `node`'s session.
|
|
75
|
+
*/
|
|
76
|
+
declare function bindCliGrant<T extends {
|
|
77
|
+
readonly delegation: PortableDelegation;
|
|
78
|
+
}>(node: RuntimeDelegationActivator, record: T, host: string): Promise<T & Record<"authorityRequest" | "authorityRequestAudit", unknown>>;
|
|
64
79
|
/**
|
|
65
80
|
* Validates and activates a compact delegation that `tc auth import` received
|
|
66
81
|
* without a stored request, through the same validated activation replay uses,
|
|
@@ -110,21 +125,36 @@ declare function prepareStoredDelegationReplay(profile: string, node: RuntimeDel
|
|
|
110
125
|
readonly host: string;
|
|
111
126
|
readonly migrate: boolean;
|
|
112
127
|
}): Promise<boolean>;
|
|
128
|
+
/**
|
|
129
|
+
* What replay did with one stored record. `skipped` records grant nothing and
|
|
130
|
+
* carry a fixed reason code that is safe to publish; `not-replayed` records
|
|
131
|
+
* are routine (expired) and give nothing to act on.
|
|
132
|
+
*/
|
|
133
|
+
type StoredDelegationReplay = {
|
|
134
|
+
readonly status: "installed";
|
|
135
|
+
readonly delegation: ValidatedRuntimeDelegation;
|
|
136
|
+
} | {
|
|
137
|
+
readonly status: "skipped";
|
|
138
|
+
readonly reason: StoredGrantSkipReason;
|
|
139
|
+
} | {
|
|
140
|
+
readonly status: "not-replayed";
|
|
141
|
+
};
|
|
113
142
|
/**
|
|
114
143
|
* Replays one stored record under the binding rule. A record with a valid
|
|
115
144
|
* binding installs only if its signed capabilities fit inside it. Before the
|
|
116
145
|
* profile has migrated, an unbound compact record installs with its signed
|
|
117
146
|
* authority as it always did; after, it installs nothing. A signed-login
|
|
118
|
-
* record installs only with a binding.
|
|
119
|
-
* `
|
|
120
|
-
*
|
|
147
|
+
* record installs only with a binding. A record with neither compact bytes
|
|
148
|
+
* nor a `siweProof` cannot be verified here and installs nothing. Never
|
|
149
|
+
* throws, and never reports exception text: stored data is untrusted
|
|
150
|
+
* transport material, and messages can quote it or a node's response.
|
|
121
151
|
*/
|
|
122
152
|
declare function replayStoredDelegation(node: RuntimeDelegationActivator, entry: unknown, options: {
|
|
123
153
|
readonly host: string;
|
|
124
154
|
readonly migrated: boolean;
|
|
125
155
|
readonly resolveSpace: OperationSpaceResolver;
|
|
126
|
-
}): Promise<
|
|
156
|
+
}): Promise<StoredDelegationReplay>;
|
|
127
157
|
/** An unreadable marker counts as migrated: a damaged marker never reopens migration. */
|
|
128
158
|
declare function bindingMigrationRecorded(profile: string): Promise<boolean>;
|
|
129
159
|
|
|
130
|
-
export { BINDING_NOTES, type BindingSource, activateUnboundCompactImport, bindingMigrationPath, bindingMigrationRecorded, mergeDelegationsWithoutRequest, operationSpaceResolver, prepareStoredDelegationReplay, replayLimit, replayStoredDelegation, storedDelegationKind };
|
|
160
|
+
export { BINDING_NOTES, type BindingSource, type StoredDelegationReplay, activateUnboundCompactImport, bindCliGrant, bindingMigrationPath, bindingMigrationRecorded, mergeDelegationsWithoutRequest, operationSpaceResolver, prepareStoredDelegationReplay, replayLimit, replayStoredDelegation, storedDelegationKind };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { RuntimeDelegationActivator, PortableDelegation, PermissionEntry
|
|
1
|
+
import { ValidatedRuntimeDelegation, RuntimeDelegationActivator, PortableDelegation, PermissionEntry } from '@tinycloud/node-sdk';
|
|
2
2
|
import { O as OperationSpaceResolver } from './authority-ofwbEqmj.js';
|
|
3
|
+
import { S as StoredGrantSkipReason } from './contract-BRwWJpPs.js';
|
|
3
4
|
import '@tinycloud/sdk-core';
|
|
4
5
|
|
|
5
6
|
/** Creates the one owner-aware resolver shared by operation matching paths. */
|
|
@@ -19,16 +20,20 @@ declare function operationSpaceResolver(node: unknown, ownerSpace?: string): (sp
|
|
|
19
20
|
* own signed capabilities (`unbound-import:<cid>`).
|
|
20
21
|
* - The one-time migration of records stored before bindings existed: exactly
|
|
21
22
|
* each record's own signed capabilities (`migrated:<cid>`).
|
|
23
|
+
* - The CLI's own grants (`tc auth request --grant`, permission escalation),
|
|
24
|
+
* once checked against the request they answer: exactly each grant's own
|
|
25
|
+
* signed capabilities (`cli-grant:<cid>`). Their signed-login records carry
|
|
26
|
+
* the `siweProof` replay verifies them from.
|
|
22
27
|
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
28
|
+
* Signed-login records the CLI stored before it kept a `siweProof` cannot be
|
|
29
|
+
* verified here and keep their legacy CLI replay.
|
|
25
30
|
*
|
|
26
31
|
* The binding is local profile data. It stops records written by any other
|
|
27
32
|
* path from granting authority. It does not stop someone who can write the
|
|
28
33
|
* profile directory, who already holds the session key and the signed bytes.
|
|
29
34
|
*/
|
|
30
|
-
type BindingSource = "migration" | "unbound-import";
|
|
31
|
-
/** The audit note written beside a binding
|
|
35
|
+
type BindingSource = "migration" | "unbound-import" | "cli-grant";
|
|
36
|
+
/** The audit note written beside a binding the record's writer synthesized. */
|
|
32
37
|
declare const BINDING_NOTES: Record<BindingSource, string>;
|
|
33
38
|
/** The per-profile marker: present once that profile's unbound records were migrated. */
|
|
34
39
|
declare function bindingMigrationPath(profile: string): string;
|
|
@@ -38,9 +43,9 @@ declare function bindingMigrationPath(profile: string): string;
|
|
|
38
43
|
* - `compact` and `signed-login` records go through validated activation and
|
|
39
44
|
* the binding rule.
|
|
40
45
|
* - `other` records keep the CLI's legacy replay: an unbound record whose
|
|
41
|
-
* bytes are not compact and that has no `siweProof`, which is how
|
|
42
|
-
*
|
|
43
|
-
* them.
|
|
46
|
+
* bytes are not compact and that has no `siweProof`, which is how earlier
|
|
47
|
+
* CLI releases stored their own signed-login grants. The operations runtime
|
|
48
|
+
* never installs them.
|
|
44
49
|
* - `refused` records install nothing anywhere.
|
|
45
50
|
*
|
|
46
51
|
* A record is `refused` unless its `delegationHeader` has exactly one own
|
|
@@ -61,6 +66,16 @@ declare function storedDelegationKind(entry: unknown): "compact" | "signed-login
|
|
|
61
66
|
* when the record installs nothing.
|
|
62
67
|
*/
|
|
63
68
|
declare function replayLimit(entry: unknown, migrated: boolean): readonly PermissionEntry[] | "signed" | undefined;
|
|
69
|
+
/**
|
|
70
|
+
* Binds the record of one of the CLI's own grants, already checked against
|
|
71
|
+
* the request it answers, to exactly the capabilities its signed bytes grant.
|
|
72
|
+
* They are read through the validated activation replay uses (nothing is
|
|
73
|
+
* activated), so the binding is the authority replay will derive. Throws when
|
|
74
|
+
* validated activation cannot read them for `node`'s session.
|
|
75
|
+
*/
|
|
76
|
+
declare function bindCliGrant<T extends {
|
|
77
|
+
readonly delegation: PortableDelegation;
|
|
78
|
+
}>(node: RuntimeDelegationActivator, record: T, host: string): Promise<T & Record<"authorityRequest" | "authorityRequestAudit", unknown>>;
|
|
64
79
|
/**
|
|
65
80
|
* Validates and activates a compact delegation that `tc auth import` received
|
|
66
81
|
* without a stored request, through the same validated activation replay uses,
|
|
@@ -110,21 +125,36 @@ declare function prepareStoredDelegationReplay(profile: string, node: RuntimeDel
|
|
|
110
125
|
readonly host: string;
|
|
111
126
|
readonly migrate: boolean;
|
|
112
127
|
}): Promise<boolean>;
|
|
128
|
+
/**
|
|
129
|
+
* What replay did with one stored record. `skipped` records grant nothing and
|
|
130
|
+
* carry a fixed reason code that is safe to publish; `not-replayed` records
|
|
131
|
+
* are routine (expired) and give nothing to act on.
|
|
132
|
+
*/
|
|
133
|
+
type StoredDelegationReplay = {
|
|
134
|
+
readonly status: "installed";
|
|
135
|
+
readonly delegation: ValidatedRuntimeDelegation;
|
|
136
|
+
} | {
|
|
137
|
+
readonly status: "skipped";
|
|
138
|
+
readonly reason: StoredGrantSkipReason;
|
|
139
|
+
} | {
|
|
140
|
+
readonly status: "not-replayed";
|
|
141
|
+
};
|
|
113
142
|
/**
|
|
114
143
|
* Replays one stored record under the binding rule. A record with a valid
|
|
115
144
|
* binding installs only if its signed capabilities fit inside it. Before the
|
|
116
145
|
* profile has migrated, an unbound compact record installs with its signed
|
|
117
146
|
* authority as it always did; after, it installs nothing. A signed-login
|
|
118
|
-
* record installs only with a binding.
|
|
119
|
-
* `
|
|
120
|
-
*
|
|
147
|
+
* record installs only with a binding. A record with neither compact bytes
|
|
148
|
+
* nor a `siweProof` cannot be verified here and installs nothing. Never
|
|
149
|
+
* throws, and never reports exception text: stored data is untrusted
|
|
150
|
+
* transport material, and messages can quote it or a node's response.
|
|
121
151
|
*/
|
|
122
152
|
declare function replayStoredDelegation(node: RuntimeDelegationActivator, entry: unknown, options: {
|
|
123
153
|
readonly host: string;
|
|
124
154
|
readonly migrated: boolean;
|
|
125
155
|
readonly resolveSpace: OperationSpaceResolver;
|
|
126
|
-
}): Promise<
|
|
156
|
+
}): Promise<StoredDelegationReplay>;
|
|
127
157
|
/** An unreadable marker counts as migrated: a damaged marker never reopens migration. */
|
|
128
158
|
declare function bindingMigrationRecorded(profile: string): Promise<boolean>;
|
|
129
159
|
|
|
130
|
-
export { BINDING_NOTES, type BindingSource, activateUnboundCompactImport, bindingMigrationPath, bindingMigrationRecorded, mergeDelegationsWithoutRequest, operationSpaceResolver, prepareStoredDelegationReplay, replayLimit, replayStoredDelegation, storedDelegationKind };
|
|
160
|
+
export { BINDING_NOTES, type BindingSource, type StoredDelegationReplay, activateUnboundCompactImport, bindCliGrant, bindingMigrationPath, bindingMigrationRecorded, mergeDelegationsWithoutRequest, operationSpaceResolver, prepareStoredDelegationReplay, replayLimit, replayStoredDelegation, storedDelegationKind };
|
|
@@ -257,8 +257,17 @@ function canonicalizeRecapCaveats(caveats) {
|
|
|
257
257
|
}
|
|
258
258
|
return `[${branches.join(",")}]`;
|
|
259
259
|
}
|
|
260
|
+
var EXPLICIT_ONLY_ACTIONS = {
|
|
261
|
+
"tinycloud.kv/sync": true,
|
|
262
|
+
"tinycloud.kv/retain": true
|
|
263
|
+
};
|
|
264
|
+
function isExplicitOnlyAction(action) {
|
|
265
|
+
return Object.prototype.hasOwnProperty.call(EXPLICIT_ONLY_ACTIONS, action);
|
|
266
|
+
}
|
|
260
267
|
function actionContains(grantedAction, requestedAction) {
|
|
261
|
-
if (grantedAction ===
|
|
268
|
+
if (grantedAction === requestedAction) return true;
|
|
269
|
+
if (isExplicitOnlyAction(requestedAction)) return false;
|
|
270
|
+
if (grantedAction === "*") return true;
|
|
262
271
|
if (!grantedAction.endsWith("/*")) return false;
|
|
263
272
|
const service = grantedAction.slice(0, -2);
|
|
264
273
|
return requestedAction === service || requestedAction.startsWith(`${service}/`);
|
|
@@ -573,7 +582,8 @@ async function acquireProfileLock(profile, options) {
|
|
|
573
582
|
const lockPath = profileLockPath(profile);
|
|
574
583
|
const turnDirectory = profileTurnLockPath(profile);
|
|
575
584
|
const directory = profilePath(profile);
|
|
576
|
-
|
|
585
|
+
if (options.requireProfile && !await exists(profileConfigPath(profile))) throw new ProfileDeletedError(profile);
|
|
586
|
+
if (!options.requireProfile) await mkdir(directory, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
577
587
|
await mkdir(dirname(turnDirectory), { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
578
588
|
for (const path of [tinycloudHomePath(), profilesPath(), directory]) {
|
|
579
589
|
await chmod(path, PRIVATE_DIR_MODE).catch((error) => {
|
|
@@ -582,11 +592,15 @@ async function acquireProfileLock(profile, options) {
|
|
|
582
592
|
}
|
|
583
593
|
const { turn, firstWaitedTurn } = await acquireTurn(profile, turnDirectory, deadline);
|
|
584
594
|
try {
|
|
595
|
+
if (options.requireProfile && !await exists(profileConfigPath(profile))) {
|
|
596
|
+
await rmdir(directory).catch(() => void 0);
|
|
597
|
+
throw new ProfileDeletedError(profile);
|
|
598
|
+
}
|
|
585
599
|
const deletion = await readJson(join(turnDirectory, "deleted.json")).catch(() => null);
|
|
586
600
|
const deletedWhileWaiting = typeof deletion?.slot === "number" && deletion.slot >= firstWaitedTurn;
|
|
587
601
|
while (true) {
|
|
588
602
|
const token = randomUUID();
|
|
589
|
-
if (await publishProfileLock(profile, lockPath, token)) {
|
|
603
|
+
if (await publishProfileLock(profile, lockPath, token, options.requireProfile === true)) {
|
|
590
604
|
return {
|
|
591
605
|
deletedWhileWaiting,
|
|
592
606
|
turnSlot: turn.slot,
|
|
@@ -837,12 +851,13 @@ async function collectTurns(profile, turn) {
|
|
|
837
851
|
}
|
|
838
852
|
}
|
|
839
853
|
}
|
|
840
|
-
async function publishProfileLock(profile, lockPath, token) {
|
|
854
|
+
async function publishProfileLock(profile, lockPath, token, requireProfile) {
|
|
841
855
|
try {
|
|
842
856
|
await mkdir(lockPath, { mode: PRIVATE_DIR_MODE });
|
|
843
857
|
} catch (error) {
|
|
844
858
|
if (isErrno(error, "EEXIST")) return false;
|
|
845
859
|
if (!isErrno(error, "ENOENT")) throw error;
|
|
860
|
+
if (requireProfile) throw new ProfileDeletedError(profile);
|
|
846
861
|
await mkdir(profilePath(profile), { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
847
862
|
return false;
|
|
848
863
|
}
|
|
@@ -1137,6 +1152,25 @@ var DelegationImportArtifactSchema = z.object({
|
|
|
1137
1152
|
}).strict();
|
|
1138
1153
|
var DelegationRequestBindingSchema = PermissionRequestArtifactSchema.pick({ requestId: true, requested: true }).strict();
|
|
1139
1154
|
|
|
1155
|
+
// src/contract.ts
|
|
1156
|
+
var STORED_GRANT_SKIP_REASONS = [
|
|
1157
|
+
"malformed",
|
|
1158
|
+
"unbound",
|
|
1159
|
+
"outside_request",
|
|
1160
|
+
"host_mismatch",
|
|
1161
|
+
"expired",
|
|
1162
|
+
"cid_mismatch",
|
|
1163
|
+
"authority_invalid",
|
|
1164
|
+
"proof_missing",
|
|
1165
|
+
"proof_invalid",
|
|
1166
|
+
"owner_mismatch",
|
|
1167
|
+
"audience_mismatch",
|
|
1168
|
+
"resources_mismatch",
|
|
1169
|
+
"unsupported",
|
|
1170
|
+
"activation_rejected",
|
|
1171
|
+
"invalid"
|
|
1172
|
+
];
|
|
1173
|
+
|
|
1140
1174
|
// src/secrets.ts
|
|
1141
1175
|
import * as nodeSdk from "@tinycloud/node-sdk";
|
|
1142
1176
|
|
|
@@ -1217,11 +1251,13 @@ function invalidSecretInput() {
|
|
|
1217
1251
|
// src/delegation-binding.ts
|
|
1218
1252
|
var REQUEST_ID_PREFIX = {
|
|
1219
1253
|
migration: "migrated",
|
|
1220
|
-
"unbound-import": "unbound-import"
|
|
1254
|
+
"unbound-import": "unbound-import",
|
|
1255
|
+
"cli-grant": "cli-grant"
|
|
1221
1256
|
};
|
|
1222
1257
|
var BINDING_NOTES = {
|
|
1223
1258
|
migration: "Stored without a request binding before bindings were required. Bound at migration to exactly its own signed capabilities, so replay refuses any delegation in this record that exceeds them.",
|
|
1224
|
-
"unbound-import": "Imported by `tc auth import` without a stored request. Bound to exactly its own signed capabilities, so replay refuses any delegation in this record that exceeds them."
|
|
1259
|
+
"unbound-import": "Imported by `tc auth import` without a stored request. Bound to exactly its own signed capabilities, so replay refuses any delegation in this record that exceeds them.",
|
|
1260
|
+
"cli-grant": "Granted to this CLI by `tc auth request --grant` or a permission escalation, and checked against the request it answered. Bound to exactly its own signed capabilities, so replay refuses any delegation in this record that exceeds them."
|
|
1225
1261
|
};
|
|
1226
1262
|
var MIGRATION_LOCK_TIMEOUT_MS = 250;
|
|
1227
1263
|
function bindingMigrationPath(profile) {
|
|
@@ -1246,19 +1282,26 @@ function replayLimit(entry, migrated) {
|
|
|
1246
1282
|
if (binding.success) return binding.data.requested;
|
|
1247
1283
|
return !migrated && kind === "compact" && !("authorityRequest" in entry) ? "signed" : void 0;
|
|
1248
1284
|
}
|
|
1249
|
-
function
|
|
1285
|
+
function bindToRequest(record, requested, source, recordedAt = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
1250
1286
|
return {
|
|
1251
1287
|
...record,
|
|
1252
1288
|
authorityRequest: DelegationRequestBindingSchema.parse({
|
|
1253
1289
|
requestId: `${REQUEST_ID_PREFIX[source]}:${record.delegation.cid}`,
|
|
1254
|
-
requested: structuredClone(
|
|
1290
|
+
requested: structuredClone(requested)
|
|
1255
1291
|
}),
|
|
1256
1292
|
authorityRequestAudit: { source, recordedAt, note: BINDING_NOTES[source] }
|
|
1257
1293
|
};
|
|
1258
1294
|
}
|
|
1295
|
+
async function bindCliGrant(node, record, host) {
|
|
1296
|
+
const signed = await signedCapabilities(node, record.delegation, host);
|
|
1297
|
+
if (signed === void 0) {
|
|
1298
|
+
throw new Error("The grant's signed authority could not be validated for this session.");
|
|
1299
|
+
}
|
|
1300
|
+
return bindToRequest(record, signed, "cli-grant");
|
|
1301
|
+
}
|
|
1259
1302
|
async function activateUnboundCompactImport(node, delegation, host) {
|
|
1260
1303
|
const activated = await nodeSdk2.activateValidatedRuntimeDelegation(node, delegation, { host });
|
|
1261
|
-
return
|
|
1304
|
+
return bindToRequest(
|
|
1262
1305
|
// The record keeps the delegation as imported; replay validates it again.
|
|
1263
1306
|
{ delegation, permissions: [...activated.effectivePermissions] },
|
|
1264
1307
|
activated.effectivePermissions,
|
|
@@ -1327,23 +1370,41 @@ async function migratedRecord(node, entry, host, recordedAt) {
|
|
|
1327
1370
|
const signed = await signedCapabilities(node, delegation, host);
|
|
1328
1371
|
if (signed === void 0) return void 0;
|
|
1329
1372
|
try {
|
|
1330
|
-
return
|
|
1373
|
+
return bindToRequest({ ...entry, delegation: { ...entry.delegation, cid: delegation.cid } }, signed, "migration", recordedAt);
|
|
1331
1374
|
} catch {
|
|
1332
1375
|
return void 0;
|
|
1333
1376
|
}
|
|
1334
1377
|
}
|
|
1378
|
+
var NOT_REPLAYED = { status: "not-replayed" };
|
|
1379
|
+
function skipped(reason) {
|
|
1380
|
+
return { status: "skipped", reason };
|
|
1381
|
+
}
|
|
1335
1382
|
async function replayStoredDelegation(node, entry, options) {
|
|
1383
|
+
const kind = storedDelegationKind(entry);
|
|
1336
1384
|
const delegation = normalizeStoredDelegation(entry);
|
|
1337
|
-
if (
|
|
1385
|
+
if (kind === "refused" || delegation === void 0) return skipped("malformed");
|
|
1386
|
+
if (delegation.expiry.getTime() <= Date.now()) return NOT_REPLAYED;
|
|
1387
|
+
if (kind === "other") return skipped("proof_missing");
|
|
1338
1388
|
const limit = replayLimit(entry, options.migrated);
|
|
1339
|
-
if (limit === void 0) return
|
|
1340
|
-
|
|
1389
|
+
if (limit === void 0) return skipped("unbound");
|
|
1390
|
+
let outsideRequest = false;
|
|
1391
|
+
const authorize = limit === "signed" ? () => true : (effective) => {
|
|
1392
|
+
outsideRequest = !delegationWithinRequest(limit, effective, options.resolveSpace);
|
|
1393
|
+
return !outsideRequest;
|
|
1394
|
+
};
|
|
1341
1395
|
try {
|
|
1342
|
-
return
|
|
1343
|
-
|
|
1344
|
-
|
|
1396
|
+
return {
|
|
1397
|
+
status: "installed",
|
|
1398
|
+
delegation: await nodeSdk2.activateValidatedRuntimeDelegation(node, delegation, { host: options.host, authorize })
|
|
1399
|
+
};
|
|
1400
|
+
} catch (error) {
|
|
1401
|
+
return skipped(outsideRequest ? "outside_request" : rejectionReason(error));
|
|
1345
1402
|
}
|
|
1346
1403
|
}
|
|
1404
|
+
function rejectionReason(error) {
|
|
1405
|
+
const reason = isRecord(error) ? error.reason : void 0;
|
|
1406
|
+
return typeof reason === "string" && STORED_GRANT_SKIP_REASONS.includes(reason) ? reason : "invalid";
|
|
1407
|
+
}
|
|
1347
1408
|
async function bindingMigrationRecorded(profile) {
|
|
1348
1409
|
try {
|
|
1349
1410
|
return await readJson(bindingMigrationPath(profile)) !== null;
|
|
@@ -1391,6 +1452,7 @@ function isRecord(value) {
|
|
|
1391
1452
|
export {
|
|
1392
1453
|
BINDING_NOTES,
|
|
1393
1454
|
activateUnboundCompactImport,
|
|
1455
|
+
bindCliGrant,
|
|
1394
1456
|
bindingMigrationPath,
|
|
1395
1457
|
bindingMigrationRecorded,
|
|
1396
1458
|
mergeDelegationsWithoutRequest,
|