ardent-cli-beta 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  import { randomUUID } from 'node:crypto';
2
- import { acknowledgeBranchCredential, ALL_SCHEMAS_PROFILE, composeCredentialDelivery, CredentialIssueRefused, describeUnadmitted, issuedConnectKey, issueBranchCredential, revokeBranchCredential, waitForBranchCredential } from './credentials.js';
2
+ import { acknowledgeBranchCredential, ALL_SCHEMAS_PROFILE, composeCredentialDelivery, CredentialIssueRefused, describeUnadmitted, issuedConnectKey, issueBranchCredentialWhenMarked, revokeBranchCredential, waitForBranchCredential } from './credentials.js';
3
3
  import { CLIENT_REQUIREMENTS, connectionString } from './drivers.js';
4
4
  import { connectionEnvironment, runPsql } from './psql.js';
5
5
  import { CredentialRecoveryCleanupError, forgetCredentialRecovery, openCredentialWitness, rememberCredentialIssuance, rememberCredentialRequest } from './recovery.js';
@@ -29,7 +29,7 @@ export async function connectToBranch(connection) {
29
29
  let failure;
30
30
  try {
31
31
  try {
32
- const issued = await issueBranchCredential(token, branchID, requestID, credential);
32
+ const issued = await issueBranchCredentialWhenMarked(token, branchID, requestID, credential, say);
33
33
  issuanceID = issued.issuanceID;
34
34
  await rememberCredentialIssuance(recovery, issuanceID);
35
35
  connectKey = issuedConnectKey(issued);
@@ -41,6 +41,7 @@ export type CredentialRequest = {
41
41
  export declare function validSourceRole(role: string): boolean;
42
42
  export declare function refusedBeforeIssuance(error: unknown): boolean;
43
43
  export declare function issueBranchCredential(token: string, branchID: string, requestID: string, credential: CredentialRequest): Promise<IssuedCredential>;
44
+ export declare function issueBranchCredentialWhenMarked(token: string, branchID: string, requestID: string, credential: CredentialRequest, say: (message: string) => void): Promise<IssuedCredential>;
44
45
  export declare function issuedConnectKey(issued: IssuedCredential): string | undefined;
45
46
  export declare function composeCredentialDelivery(delivered: DeliveredCredential, connectKey?: string): DeliveredCredential;
46
47
  export declare function waitForBranchCredential(token: string, issuanceID: string, stop?: AbortSignal, served?: Promise<void>): Promise<DeliveredCredential>;
@@ -9,10 +9,18 @@ export class CredentialIssueRefused extends Error {
9
9
  this.code = code;
10
10
  }
11
11
  }
12
+ // branch_mark_pending is a branch registered ahead of its covering mark. The
13
+ // API refuses its credential before recording anything, and the same request
14
+ // is accepted once the mark binds (issueBranchCredentialWhenMarked).
15
+ const MARK_PENDING = 'branch_mark_pending';
16
+ // How long a credential wait lasts once nothing outside it bounds it: the wait
17
+ // for a branch to bind its covering mark, and the wait for delivery once the
18
+ // branch serves.
19
+ const WAIT_BOUND_MS = 10 * 60 * 1000;
12
20
  // The issuance refusals that come before any issuance is recorded: a request
13
21
  // refused with one of these left nothing to revoke.
14
22
  const issueRefusals = new Set(['invalid_source_role', 'source_role_unsupported', 'source_role_held', 'source_role_revocation_pending', 'connect_key_unsupported',
15
- 'all_schemas_read_write_unsupported', 'all_schemas_read_write_unowned']);
23
+ 'all_schemas_read_write_unsupported', 'all_schemas_read_write_unowned', MARK_PENDING]);
16
24
  // The access profile branch connect asks for without --role, and the one an
17
25
  // earlier release of this CLI asked for, which a recovery record naming no
18
26
  // profile replays.
@@ -74,6 +82,33 @@ export async function issueBranchCredential(token, branchID, requestID, credenti
74
82
  // records and revokes an issuance even when its secret is absent or malformed.
75
83
  return { issuanceID: result.issuance_id, connectKey: result.connect_key, connectKeyRequired: result.connect_key_required };
76
84
  }
85
+ // issueBranchCredentialWhenMarked is issueBranchCredential for a branch that
86
+ // may still be waiting for its covering mark: a branch_mark_pending refusal
87
+ // sends the same request again on the poll cadence, saying so once, until the
88
+ // API accepts it or ten minutes pass. Every other refusal is thrown as it
89
+ // came. Credential recovery calls issueBranchCredential instead, since a
90
+ // branch_mark_pending answer already tells it there is nothing to revoke.
91
+ export async function issueBranchCredentialWhenMarked(token, branchID, requestID, credential, say) {
92
+ const deadline = Date.now() + WAIT_BOUND_MS;
93
+ const pause = pollPauser();
94
+ let announced = false;
95
+ for (;;) {
96
+ try {
97
+ return await issueBranchCredential(token, branchID, requestID, credential);
98
+ }
99
+ catch (error) {
100
+ if (!(error instanceof CredentialIssueRefused) || error.code !== MARK_PENDING)
101
+ throw error;
102
+ if (Date.now() >= deadline) {
103
+ throw new CredentialIssueRefused(`Timed out after ten minutes waiting for the branch's snapshot, so no credential was issued. Check branch status before trying again. ${error.message}`, MARK_PENDING);
104
+ }
105
+ if (!announced)
106
+ say('Waiting for the branch\'s snapshot...');
107
+ announced = true;
108
+ await pause();
109
+ }
110
+ }
111
+ }
77
112
  export function issuedConnectKey(issued) {
78
113
  if (issued.connectKeyRequired === undefined || issued.connectKeyRequired === false) {
79
114
  if (issued.connectKey !== undefined)
@@ -129,7 +164,7 @@ export function composeCredentialDelivery(delivered, connectKey) {
129
164
  // since the control plane delivers nothing before it, and until then the wait
130
165
  // is bounded by whatever bounds served.
131
166
  export async function waitForBranchCredential(token, issuanceID, stop, served) {
132
- const bound = 10 * 60 * 1000;
167
+ const bound = WAIT_BOUND_MS;
133
168
  let deadline = served ? Number.POSITIVE_INFINITY : Date.now() + bound;
134
169
  served?.then(() => { deadline = Date.now() + bound; }, () => { });
135
170
  // A 202 is re-read on the poll cadence; a 5xx or a lost response is retried
@@ -908,5 +908,5 @@
908
908
  ]
909
909
  }
910
910
  },
911
- "version": "0.1.1"
911
+ "version": "0.1.2"
912
912
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ardent-cli-beta",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "The Ardent command-line interface",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",