@cortexkit/common-auth 0.6.0 → 0.8.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.
@@ -24,6 +24,22 @@ export function disableIn(tx, id, reason) {
24
24
  const entry = tx.entry(id) ?? { credentialEpoch: 1, needsFirstReading: true };
25
25
  tx.setEntry(id, { ...entry, disabledReason: reason });
26
26
  }
27
+ /**
28
+ * Marks a row enabled: `enabled: true` in the roster row and no
29
+ * `disabledReason` in its entry. A row without an entry is not given one.
30
+ */
31
+ export function enableIn(tx, id) {
32
+ const raw = tx.rosterRow(id);
33
+ if (!raw)
34
+ return;
35
+ raw.enabled = true;
36
+ const entry = tx.entry(id);
37
+ if (entry && 'disabledReason' in entry) {
38
+ const next = { ...entry };
39
+ delete next.disabledReason;
40
+ tx.setEntry(id, next);
41
+ }
42
+ }
27
43
  /**
28
44
  * Two enabled OAuth rows with one wire identity are the same account: the
29
45
  * earlier row in roster order stays enabled and every later one is disabled
@@ -6,12 +6,13 @@ export { countUnknownIdentityRows, DUPLICATE_IDENTITY_REASON, } from './identity
6
6
  export type { HoldPoint, InitializeOutcome, WriteStep } from './mutate.js';
7
7
  export type { OpenPoolStoreOptions, PoolLoad, PoolStore, } from './pool.js';
8
8
  export { openPoolStore } from './pool.js';
9
- export type { ProviderStateMutator, UpdateProviderStateResult, } from './provider-state.js';
9
+ export type { ProviderStateMutator, RowTransitionMutator, UpdateProviderStateResult, } from './provider-state.js';
10
+ export { DECLINE_TRANSITION } from './provider-state.js';
10
11
  export type { PullHook, PullRequest } from './pull.js';
11
12
  export type { ProviderRefresh, ProviderRefreshResult, RefreshOptions, RefreshOutcome, } from './refresh.js';
12
13
  export type { LockEvent, PoolLockOptions, PoolLockSpec, } from './refresh-lock.js';
13
14
  export { POOL_LOCK_DEFAULTS } from './refresh-lock.js';
14
- export type { AddInput, AddResult, CredentialWriteInput, FailureHook, RemoveOptions, RemoveResult, RemoveView, ReorderOptions, ReorderResult, RowOperationOptions, RowToggleOptions, } from './rows.js';
15
+ export type { AddInput, AddResult, CredentialWriteInput, FailureHook, RemoveOptions, RemoveResult, RemoveView, ReorderOptions, ReorderResult, RowOperationOptions, RowToggleOptions, RowTransitionOptions, RowTransitionResult, } from './rows.js';
15
16
  export type { PullReason } from './runtime.js';
16
17
  export type { ApiKeyCredential, CredentialStampStatus, OAuthCredential, PoolCredential, PoolRow, ProviderStateCodec, ProviderStateDrop, ProviderStateReplacement, QuotaCodec, RotateCredential, StoredCredential, } from './schema.js';
17
18
  export { fingerprintOf, LEGACY_STORE_VERSION, POOL_KEY, POOL_ROWS_KEY, POOL_SCHEMA_VERSION, PROVIDER_STATE_KEY, REFRESH_STAMP_TOLERANCE_MS, rowLockKey, } from './schema.js';
@@ -1,6 +1,7 @@
1
1
  export { PoolOperationError, PoolReentryError } from './errors.js';
2
2
  export { countUnknownIdentityRows, DUPLICATE_IDENTITY_REASON, } from './identity.js';
3
3
  export { openPoolStore } from './pool.js';
4
+ export { DECLINE_TRANSITION } from './provider-state.js';
4
5
  export { POOL_LOCK_DEFAULTS } from './refresh-lock.js';
5
6
  export { fingerprintOf, LEGACY_STORE_VERSION, POOL_KEY, POOL_ROWS_KEY, POOL_SCHEMA_VERSION, PROVIDER_STATE_KEY, REFRESH_STAMP_TOLERANCE_MS, rowLockKey, } from './schema.js';
6
7
  export { POOL_OWNED_KEYS } from './settings.js';
@@ -118,7 +118,11 @@ export declare class Transaction {
118
118
  * Writes the config: legacy `version: 1` and the legacy roster beside
119
119
  * `commonAuthPool`, every other top-level key and every unrecognised pool
120
120
  * key untouched. Entries for ids no longer in the roster are dropped here,
121
- * and remembered so the id is not reused in this process.
121
+ * and remembered so the id is not reused in this process. Every id the
122
+ * write drops (a roster row the files held when the transaction read them,
123
+ * or an entry left without one) has its epoch recorded in the config (see
124
+ * `retireEpochsIn`), which is what keeps a later `add` of the id, from any
125
+ * process, past every epoch an attribution could name.
122
126
  */
123
127
  commitConfig(options?: {
124
128
  counted?: boolean;
@@ -4,7 +4,7 @@ import { LockContentionError, LockOwnershipError } from '../fs/with-lock.js';
4
4
  import { PoolOperationError, } from './errors.js';
5
5
  import { callFailureHook } from './hooks.js';
6
6
  import { LockStack, } from './refresh-lock.js';
7
- import { classifyConfig, classifyState, ensureEntries, entryIn, isRecord, LEGACY_STORE_VERSION, POOL_KEY, POOL_ROWS_KEY, POOL_SCHEMA_VERSION, rosterRowIn, setEntryIn, } from './schema.js';
7
+ import { classifyConfig, classifyState, ensureEntries, entryIn, isRecord, LEGACY_STORE_VERSION, POOL_KEY, POOL_ROWS_KEY, POOL_SCHEMA_VERSION, retireEpochsIn, rosterOf, rosterRowIn, setEntryIn, } from './schema.js';
8
8
  import { completeTornRows, loadRows } from './torn.js';
9
9
  async function readJson(path) {
10
10
  let text;
@@ -169,7 +169,11 @@ export class Transaction {
169
169
  * Writes the config: legacy `version: 1` and the legacy roster beside
170
170
  * `commonAuthPool`, every other top-level key and every unrecognised pool
171
171
  * key untouched. Entries for ids no longer in the roster are dropped here,
172
- * and remembered so the id is not reused in this process.
172
+ * and remembered so the id is not reused in this process. Every id the
173
+ * write drops (a roster row the files held when the transaction read them,
174
+ * or an entry left without one) has its epoch recorded in the config (see
175
+ * `retireEpochsIn`), which is what keeps a later `add` of the id, from any
176
+ * process, past every epoch an attribution could name.
173
177
  */
174
178
  async commitConfig(options = {}) {
175
179
  const roster = this.roster();
@@ -177,6 +181,14 @@ export class Transaction {
177
181
  for (const raw of roster)
178
182
  if (isRecord(raw) && typeof raw.id === 'string')
179
183
  rosterIds.add(raw.id);
184
+ const dropped = new Set();
185
+ for (const raw of rosterOf(this.snapshot.config))
186
+ if (isRecord(raw) && typeof raw.id === 'string' && !rosterIds.has(raw.id))
187
+ dropped.add(raw.id);
188
+ for (const id of Object.keys(this.entries()))
189
+ if (!rosterIds.has(id))
190
+ dropped.add(id);
191
+ retireEpochsIn(this.config, dropped);
180
192
  const entries = this.entries();
181
193
  const kept = {};
182
194
  for (const [id, entry] of Object.entries(entries)) {
@@ -6,7 +6,7 @@ import { type ProviderStateMutator, type UpdateProviderStateResult } from './pro
6
6
  import { type PullHook } from './pull.js';
7
7
  import { type ProviderRefresh, type RefreshOptions, type RefreshOutcome } from './refresh.js';
8
8
  import { type LockEnvironment, type PoolLockOptions, type PoolLockSpec } from './refresh-lock.js';
9
- import { type AddInput, type AddResult, type CredentialWriteInput, type RemoveOptions, type RemoveResult, type ReorderOptions, type ReorderResult, type RowOperationOptions, type RowToggleOptions } from './rows.js';
9
+ import { type AddInput, type AddResult, type CredentialWriteInput, type RemoveOptions, type RemoveResult, type ReorderOptions, type ReorderResult, type RowOperationOptions, type RowToggleOptions, type RowTransitionOptions, type RowTransitionResult } from './rows.js';
10
10
  import { type PoolCredential, type PoolRow, type ProviderStateCodec, type QuotaCodec, type RotateCredential, type StoredCredential } from './schema.js';
11
11
  import { type SettingsMutator, type SettingsRead, type UpdateSettingsOptions, type UpdateSettingsResult } from './settings.js';
12
12
  export interface OpenPoolStoreOptions {
@@ -88,6 +88,13 @@ export interface PoolStore {
88
88
  }): Promise<{
89
89
  status: InitializeOutcome;
90
90
  }>;
91
+ /**
92
+ * Adds a row, or completes or rotates the row already holding the id or
93
+ * the secret. A new row starts at credential epoch 1; since 0.8.0 one
94
+ * whose id the pool held before starts one past the highest epoch that id
95
+ * held (see `Attribution`), and an id that held `Number.MAX_SAFE_INTEGER`
96
+ * refuses (`id-removed`) before writing.
97
+ */
91
98
  add(input: AddInput, options?: RowOperationOptions): Promise<AddResult>;
92
99
  /**
93
100
  * Gives a row a new credential and a new credential epoch. Since 0.6.0 the
@@ -119,19 +126,18 @@ export interface PoolStore {
119
126
  /**
120
127
  * Sets `enabled: false` and the entry's `disabledReason`. Takes the row
121
128
  * lock, then `extraLocks`, then the store locks (the row lock and
122
- * `extraLocks` since 0.2.3).
129
+ * `extraLocks` since 0.2.3). Since 0.7.0 it may be fenced on the
130
+ * credential the caller's evidence is about (`attribution`) and carry a
131
+ * provider-state change that lands with it (`providerState`); see
132
+ * `RowTransitionOptions`.
123
133
  */
124
- disable(id: string, reason: string, options?: RowToggleOptions): Promise<{
125
- id: string;
126
- }>;
134
+ disable(id: string, reason: string, options?: RowTransitionOptions): Promise<RowTransitionResult>;
127
135
  /**
128
136
  * Clears `enabled: false` and `disabledReason` (since 0.2.3); refuses with
129
137
  * `duplicate-identity` when another enabled OAuth row holds the row's
130
- * identity. Locks as `disable`.
138
+ * identity. Locks as `disable`, and takes the same options since 0.7.0.
131
139
  */
132
- enable(id: string, options?: RowToggleOptions): Promise<{
133
- id: string;
134
- }>;
140
+ enable(id: string, options?: RowTransitionOptions): Promise<RowTransitionResult>;
135
141
  /**
136
142
  * Deletes the roster row, its per-row entry and its state-file credential
137
143
  * (since 0.2.3). Locks as `disable`; `protect` can refuse the id.
@@ -1,5 +1,6 @@
1
1
  import type { Attribution } from './attribution.js';
2
2
  import type { PoolOperation } from './errors.js';
3
+ import { type Transaction } from './mutate.js';
3
4
  import type { RowToggleOptions } from './rows.js';
4
5
  import { type StoreRuntime } from './runtime.js';
5
6
  import { type PoolRow, type ProviderStateCodec } from './schema.js';
@@ -84,3 +85,47 @@ export type UpdateProviderStateResult = {
84
85
  * `replace` stamps such a row.
85
86
  */
86
87
  export declare function updateProviderStateRow(rt: StoreRuntime, id: string, fence: Attribution, mutator: ProviderStateMutator, options?: RowToggleOptions): Promise<UpdateProviderStateResult>;
88
+ /**
89
+ * Returned by the provider-state mutator of an attributed `disable` or
90
+ * `enable` to decline the whole transition: nothing is written, neither the
91
+ * provider state nor the row's enabled flag, and the call resolves with
92
+ * `declined: true`. A mutator declines when the state it is shown is newer
93
+ * than what its caller saw, such as an eligibility recorded after the
94
+ * request whose refusal is being acted on. It is a value of its own because
95
+ * `undefined` already means "clear the provider state". `Symbol.for` keeps it
96
+ * equal across two copies of this module loaded in one process.
97
+ */
98
+ export declare const DECLINE_TRANSITION: unique symbol;
99
+ /**
100
+ * The provider-state mutator of an attributed `disable` or `enable`: as
101
+ * `ProviderStateMutator`, and it may also return `DECLINE_TRANSITION`.
102
+ */
103
+ export type RowTransitionMutator = (current: unknown | undefined, row: PoolRow) => unknown | typeof DECLINE_TRANSITION | Promise<unknown | typeof DECLINE_TRANSITION>;
104
+ /**
105
+ * What a provider-state mutator asks of a row, worked out under the locks
106
+ * before anything is written. `changed` carries the row's whole next
107
+ * state-file account entry (the value, and the stamp rebound to it when its
108
+ * credential-bound part moved); `value` is the next value, absent when it is
109
+ * cleared.
110
+ */
111
+ export type ProviderStatePlan = {
112
+ kind: 'declined';
113
+ } | {
114
+ kind: 'unchanged';
115
+ value?: unknown;
116
+ } | {
117
+ kind: 'changed';
118
+ value?: unknown;
119
+ account: Record<string, unknown>;
120
+ };
121
+ /**
122
+ * Runs a provider-state mutator for a row loaded under every lock and
123
+ * already checked by the caller (present, valid, inside its attribution
124
+ * fence), and plans the write. Refuses (`no-credential`) a row holding no
125
+ * credential, and (`unbound-credential`) one whose credential carries no
126
+ * stamp of this store at the row's epoch and identity: no stamp could bind
127
+ * the value, so no reader would ever show it. `DECLINE_TRANSITION` is
128
+ * honoured only when `declinable` is set; elsewhere it is not JSON and is
129
+ * refused as such.
130
+ */
131
+ export declare function planProviderStateIn(tx: Transaction, codec: ProviderStateCodec, operation: PoolOperation, row: PoolRow, mutator: ProviderStateMutator | RowTransitionMutator, declinable?: boolean): Promise<ProviderStatePlan>;
@@ -132,55 +132,92 @@ export async function updateProviderStateRow(rt, id, fence, mutator, options = {
132
132
  const epoch = row.credentialEpoch ?? 1;
133
133
  if (epoch !== captured || row.identity !== fence.identity)
134
134
  throw refusal(operation, id, 'attribution', `the provider state for ${id} was read for a credential or account the row no longer holds`, true);
135
- const account = tx.stateAccount(id);
136
- const rawStamp = account?.[CREDENTIAL_STAMP_KEY];
137
- const stamp = parseStamp(rawStamp);
138
- if (!isRecord(rawStamp) ||
139
- !stamp?.binding ||
140
- stamp.digest !== credentialDigest(row.credential) ||
141
- stamp.credentialEpoch !== epoch ||
142
- stamp.binding.identity !== row.identity)
143
- throw refusal(operation, id, 'unbound-credential', `row ${id}'s credential carries no stamp of this store to bind a provider state to (stamp ${row.stamp}); rotate or replace it first`);
144
- const current = row.providerState === undefined
145
- ? undefined
146
- : structuredClone(row.providerState);
147
- const returned = await runInsideHook(operation, () => mutator(current, row));
148
- const next = returned === undefined
149
- ? undefined
150
- : acceptProviderState(codec, operation, id, returned, 'the provider state the mutator returned');
151
- const held = account !== undefined && Object.hasOwn(account, PROVIDER_STATE_KEY);
152
- if (next === undefined
153
- ? !held
154
- : row.providerState !== undefined &&
155
- JSON.stringify(row.providerState) === JSON.stringify(next))
135
+ const plan = await planProviderStateIn(tx, codec, operation, row, mutator);
136
+ if (plan.kind !== 'changed')
156
137
  return {
157
138
  id,
158
139
  outcome: 'unchanged',
159
- ...(next !== undefined ? { providerState: next } : {}),
140
+ ...(plan.kind === 'unchanged' && plan.value !== undefined
141
+ ? { providerState: plan.value }
142
+ : {}),
160
143
  };
161
- const nextAccount = { ...account };
162
- if (next === undefined) {
163
- delete nextAccount[PROVIDER_STATE_KEY];
164
- const nextStamp = { ...rawStamp };
165
- delete nextStamp.providerState;
166
- nextAccount[CREDENTIAL_STAMP_KEY] = nextStamp;
167
- }
168
- else {
169
- nextAccount[PROVIDER_STATE_KEY] = next;
170
- const digest = providerStateDigest(codec, next);
171
- // A change confined to the part the codec does not bind to the
172
- // credential leaves the stamp exactly as it was.
173
- if (stamp.providerState !== digest)
174
- nextAccount[CREDENTIAL_STAMP_KEY] = {
175
- ...rawStamp,
176
- providerState: digest,
177
- };
178
- }
179
- tx.setStateAccount(id, nextAccount);
144
+ tx.setStateAccount(id, plan.account);
180
145
  await tx.commitState();
181
- return next === undefined
146
+ return plan.value === undefined
182
147
  ? { id, outcome: 'cleared' }
183
- : { id, outcome: 'updated', providerState: next };
148
+ : { id, outcome: 'updated', providerState: plan.value };
184
149
  });
185
150
  });
186
151
  }
152
+ /**
153
+ * Returned by the provider-state mutator of an attributed `disable` or
154
+ * `enable` to decline the whole transition: nothing is written, neither the
155
+ * provider state nor the row's enabled flag, and the call resolves with
156
+ * `declined: true`. A mutator declines when the state it is shown is newer
157
+ * than what its caller saw, such as an eligibility recorded after the
158
+ * request whose refusal is being acted on. It is a value of its own because
159
+ * `undefined` already means "clear the provider state". `Symbol.for` keeps it
160
+ * equal across two copies of this module loaded in one process.
161
+ */
162
+ export const DECLINE_TRANSITION = Symbol.for('@cortexkit/common-auth/store/decline-transition');
163
+ /**
164
+ * Runs a provider-state mutator for a row loaded under every lock and
165
+ * already checked by the caller (present, valid, inside its attribution
166
+ * fence), and plans the write. Refuses (`no-credential`) a row holding no
167
+ * credential, and (`unbound-credential`) one whose credential carries no
168
+ * stamp of this store at the row's epoch and identity: no stamp could bind
169
+ * the value, so no reader would ever show it. `DECLINE_TRANSITION` is
170
+ * honoured only when `declinable` is set; elsewhere it is not JSON and is
171
+ * refused as such.
172
+ */
173
+ export async function planProviderStateIn(tx, codec, operation, row, mutator, declinable = false) {
174
+ const id = row.id;
175
+ const credential = row.credential;
176
+ if (!credential)
177
+ throw refusal(operation, id, 'no-credential', `row ${id} holds no credential`);
178
+ const epoch = row.credentialEpoch ?? 1;
179
+ const account = tx.stateAccount(id);
180
+ const rawStamp = account?.[CREDENTIAL_STAMP_KEY];
181
+ const stamp = parseStamp(rawStamp);
182
+ if (!isRecord(rawStamp) ||
183
+ !stamp?.binding ||
184
+ stamp.digest !== credentialDigest(credential) ||
185
+ stamp.credentialEpoch !== epoch ||
186
+ stamp.binding.identity !== row.identity)
187
+ throw refusal(operation, id, 'unbound-credential', `row ${id}'s credential carries no stamp of this store to bind a provider state to (stamp ${row.stamp}); rotate or replace it first`);
188
+ const current = row.providerState === undefined
189
+ ? undefined
190
+ : structuredClone(row.providerState);
191
+ const returned = await runInsideHook(operation, () => mutator(current, row));
192
+ if (declinable && returned === DECLINE_TRANSITION)
193
+ return { kind: 'declined' };
194
+ const next = returned === undefined
195
+ ? undefined
196
+ : acceptProviderState(codec, operation, id, returned, 'the provider state the mutator returned');
197
+ const held = account !== undefined && Object.hasOwn(account, PROVIDER_STATE_KEY);
198
+ if (next === undefined
199
+ ? !held
200
+ : row.providerState !== undefined &&
201
+ JSON.stringify(row.providerState) === JSON.stringify(next))
202
+ return { kind: 'unchanged', ...(next !== undefined ? { value: next } : {}) };
203
+ const nextAccount = { ...account };
204
+ if (next === undefined) {
205
+ delete nextAccount[PROVIDER_STATE_KEY];
206
+ const nextStamp = { ...rawStamp };
207
+ delete nextStamp.providerState;
208
+ nextAccount[CREDENTIAL_STAMP_KEY] = nextStamp;
209
+ }
210
+ else {
211
+ nextAccount[PROVIDER_STATE_KEY] = next;
212
+ const digest = providerStateDigest(codec, next);
213
+ // A change confined to the part the codec does not bind to the
214
+ // credential leaves the stamp exactly as it was.
215
+ if (stamp.providerState !== digest)
216
+ nextAccount[CREDENTIAL_STAMP_KEY] = { ...rawStamp, providerState: digest };
217
+ }
218
+ return {
219
+ kind: 'changed',
220
+ ...(next !== undefined ? { value: next } : {}),
221
+ account: nextAccount,
222
+ };
223
+ }
@@ -1,7 +1,7 @@
1
1
  import type { Attribution } from './attribution.js';
2
2
  import { PoolOperationError } from './errors.js';
3
3
  import { type Transaction } from './mutate.js';
4
- import { type ProviderStateWrite } from './provider-state.js';
4
+ import { type ProviderStateWrite, type RowTransitionMutator, type UpdateProviderStateResult } from './provider-state.js';
5
5
  import type { PoolLockSpec } from './refresh-lock.js';
6
6
  import { type StoreRuntime } from './runtime.js';
7
7
  import { type CredentialBinding, type PoolCredential, type PoolRow, type RotateCredential, type StoredCredential } from './schema.js';
@@ -134,23 +134,57 @@ export declare function rotateRow(rt: StoreRuntime, id: string, credential: Rota
134
134
  credential: StoredCredential;
135
135
  }>;
136
136
  /**
137
- * Marks a row disabled. Since 0.2.3 it takes the row lock and the caller's
138
- * extra locks before the store locks, as the other row writes do, so it waits
139
- * for a refresh of the row instead of landing during its provider call.
137
+ * Options of `disable` and `enable`. A call that passes neither
138
+ * `attribution` nor `providerState` behaves exactly as it did before 0.7.0.
140
139
  */
141
- export declare function disableRow(rt: StoreRuntime, id: string, reason: string, options?: RowToggleOptions): Promise<{
140
+ export interface RowTransitionOptions extends RowToggleOptions {
141
+ /**
142
+ * The credential epoch and recorded identity the caller's evidence for the
143
+ * transition was obtained under (as `recordQuota`'s attribution: an
144
+ * identity left out means the row had none). The call is refused
145
+ * (`attribution`, retryable, nothing written) once the row holds another
146
+ * epoch or identity, so a provider's late answer about a replaced
147
+ * credential never disables, or switches back on, the row now holding its
148
+ * successor.
149
+ */
150
+ attribution?: Attribution;
151
+ /**
152
+ * A provider-state change made in the same transaction as the transition,
153
+ * under the rules of `updateProviderState` (codec validation, the stamp
154
+ * rebound to the value, `unbound-credential` for a row no stamp of this
155
+ * store can bind it to); it requires `attribution`. The value and the
156
+ * enabled flag land together: no reader, and no crash at any write point,
157
+ * shows one without the other. Returning `DECLINE_TRANSITION` declines the
158
+ * whole call and writes nothing.
159
+ */
160
+ providerState?: RowTransitionMutator;
161
+ }
162
+ export interface RowTransitionResult {
142
163
  id: string;
143
- }>;
164
+ /** The provider-state mutator declined: nothing was written. */
165
+ declined?: true;
166
+ /**
167
+ * Set when a provider-state mutator ran and did not decline: what it did
168
+ * to the value, as `updateProviderState` reports it.
169
+ */
170
+ providerStateOutcome?: UpdateProviderStateResult['outcome'];
171
+ /** The provider state the row now holds, when a mutator ran and left one. */
172
+ providerState?: unknown;
173
+ }
174
+ /**
175
+ * Marks a row disabled with a reason. See `RowTransitionOptions` for the
176
+ * attributed form, which may change the provider state with it.
177
+ */
178
+ export declare function disableRow(rt: StoreRuntime, id: string, reason: string, options?: RowTransitionOptions): Promise<RowTransitionResult>;
144
179
  /**
145
180
  * Clears a row's `enabled: false` and its `disabledReason` in one config
146
181
  * write. An OAuth row whose recorded identity another enabled OAuth row holds
147
182
  * stays disabled and the call refuses (`duplicate-identity`): the same rule
148
183
  * that makes `add` store such a row disabled. Enabling a row that is already
149
- * enabled writes nothing.
184
+ * enabled writes nothing. See `RowTransitionOptions` for the attributed
185
+ * form, which may change the provider state with it.
150
186
  */
151
- export declare function enableRow(rt: StoreRuntime, id: string, options?: RowToggleOptions): Promise<{
152
- id: string;
153
- }>;
187
+ export declare function enableRow(rt: StoreRuntime, id: string, options?: RowTransitionOptions): Promise<RowTransitionResult>;
154
188
  /**
155
189
  * Deletes a row: its roster row and per-row entry (quota, epoch; the identity
156
190
  * lives in the roster row) in one config write, then its credential and
@@ -158,7 +192,9 @@ export declare function enableRow(rt: StoreRuntime, id: string, options?: RowTog
158
192
  * between the two leaves a row every reader already sees as removed, with
159
193
  * only an orphaned state entry that no reader loads; calling `remove` again
160
194
  * drops that entry (`completed`). As with every id the store drops, the id is
161
- * not reused by `add` in this process.
195
+ * not reused by `add` in this process, and the config write records the
196
+ * row's credential epoch, so an `add` of the id in any other process starts
197
+ * past it (see `nextAddEpochIn`).
162
198
  */
163
199
  export declare function removeRow(rt: StoreRuntime, id: string, options?: RemoveOptions): Promise<RemoveResult>;
164
200
  /**