@cortexkit/opencode-openai-auth 0.8.0 → 0.9.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 +1 -1
- package/dist/{index-3w4vmckx.js → index-4effjmr6.js} +2 -2
- package/dist/{index-507jh8bv.js → index-9ehhb051.js} +1 -1
- package/dist/index-g3hsdjh4.js +2 -0
- package/dist/index-hh52v8sb.js +2 -0
- package/dist/{index-944gsj1s.js → index-jwn4r3sw.js} +1 -1
- package/dist/{index-02h7g5f9.js → index-sdt2d2ra.js} +1 -1
- package/dist/index-sq36b120.js +211 -0
- package/dist/index.d.ts +172 -341
- package/dist/index.js +65 -65
- package/dist/internal-BTndPmW2.d.ts +914 -0
- package/dist/quota-normalize-08hpp51v.js +1 -0
- package/dist/rpc/port-file.js +1 -1
- package/dist/rpc/protocol.d.ts +1 -1
- package/dist/rpc/rpc-client.d.ts +2 -1
- package/dist/rpc/rpc-client.js +1 -1
- package/dist/rpc/rpc-dir.js +1 -1
- package/dist/sidebar-state-CoKYKcLn.d.ts +220 -0
- package/dist/sidebar-state.d.ts +2 -172
- package/dist/sidebar-state.js +1 -1
- package/dist/{token-jdvpwk60.js → token-9823y0pc.js} +1 -1
- package/dist/token-util-ypgz7rge.js +1 -0
- package/dist/tui-preferences.js +1 -1
- package/dist/tui.d.ts +27 -5
- package/package.json +3 -2
- package/src/tui-compiled/raw/core-custody-state.ts +243 -0
- package/src/tui-compiled/raw/sidebar-state.ts +168 -12
- package/src/tui-compiled/raw/tui-command-dialogs.tsx +89 -39
- package/src/tui-compiled/raw/tui.tsx +138 -18
- package/src/tui-compiled/runtime/core-custody-state.ts +243 -0
- package/src/tui-compiled/runtime/sidebar-state.ts +168 -12
- package/src/tui-compiled/runtime/tui-command-dialogs.tsx +70 -33
- package/src/tui-compiled/runtime/tui.tsx +241 -143
- package/src/tui-compiled/shared/logger.ts +9 -2
- package/dist/index-2xppkvkx.js +0 -2
- package/dist/index-nshbgkz1.js +0 -198
- package/dist/index-xyg86jgt.js +0 -2
- package/dist/protocol-Cv9ZmU0p.d.ts +0 -24
- package/dist/quota-normalize-wap0rne8.js +0 -1
- package/dist/token-util-zzm7paz9.js +0 -1
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
resolveSessionSidebarRouting,
|
|
34
34
|
resolveSessionStickyAccount,
|
|
35
35
|
type SidebarState,
|
|
36
|
+
type SpendControlReading,
|
|
36
37
|
} from './sidebar-state.ts'
|
|
37
38
|
import { openCommandDialog } from './tui-command-dialogs.tsx'
|
|
38
39
|
import {
|
|
@@ -241,28 +242,97 @@ function CollapsedRow(props: {
|
|
|
241
242
|
}
|
|
242
243
|
|
|
243
244
|
export interface QuotaDisplayRow {
|
|
244
|
-
key: 'primary' | 'secondary'
|
|
245
|
+
key: 'primary' | 'secondary' | 'spendControl'
|
|
245
246
|
label: string
|
|
247
|
+
labelWidth: number
|
|
246
248
|
window: QuotaWindow
|
|
247
249
|
pacing: QuotaPacing | null
|
|
248
250
|
}
|
|
249
251
|
|
|
252
|
+
const SPEND_CONTROL_LABEL = 'credits'
|
|
253
|
+
|
|
254
|
+
// Labels a quota snapshot contributes to the sidebar, independent of pacing or
|
|
255
|
+
// the wall clock — the label column is sized from these alone.
|
|
256
|
+
function quotaRowLabels(quota: AccountQuota | null): string[] {
|
|
257
|
+
const labels = getPresentQuotaWindows(quota).map((row) => row.label)
|
|
258
|
+
if (quota?.spendControl) labels.push(SPEND_CONTROL_LABEL)
|
|
259
|
+
return labels
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// The label column is a property of the whole sidebar, not of one account's row
|
|
263
|
+
// set: every account's bars must start at the same column, so the width is
|
|
264
|
+
// derived once from every account rendered (main plus fallbacks) and threaded
|
|
265
|
+
// into each account's rows. The +1 keeps a separator before the longest label's
|
|
266
|
+
// bar; 3 is the floor the short 5h/7d labels already relied on.
|
|
267
|
+
/**
|
|
268
|
+
* Every quota the sidebar will draw, in one list.
|
|
269
|
+
*
|
|
270
|
+
* The label column is shared by all of them, so the width has to be measured
|
|
271
|
+
* over this exact set — measuring a subset is what made stacked accounts
|
|
272
|
+
* disagree about where the bar starts. Named rather than inlined at each call
|
|
273
|
+
* site because the set is the part that can be got wrong, and a call site
|
|
274
|
+
* cannot be tested without the host's rendering runtime.
|
|
275
|
+
*/
|
|
276
|
+
export function renderedQuotas(state: {
|
|
277
|
+
main?: { quota?: AccountQuota | null } | null
|
|
278
|
+
fallbacks?: ReadonlyArray<{ enabled?: boolean; quota?: AccountQuota | null }>
|
|
279
|
+
}): ReadonlyArray<AccountQuota | null | undefined> {
|
|
280
|
+
return [
|
|
281
|
+
state.main?.quota ?? null,
|
|
282
|
+
...(state.fallbacks ?? [])
|
|
283
|
+
.filter((fallback) => fallback.enabled)
|
|
284
|
+
.map((fallback) => fallback.quota),
|
|
285
|
+
]
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
export function computeQuotaLabelWidth(
|
|
289
|
+
quotas: ReadonlyArray<AccountQuota | null | undefined>,
|
|
290
|
+
): number {
|
|
291
|
+
let width = 3
|
|
292
|
+
for (const quota of quotas) {
|
|
293
|
+
for (const label of quotaRowLabels(quota ?? null)) {
|
|
294
|
+
width = Math.max(width, label.length + 1)
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
return width
|
|
298
|
+
}
|
|
299
|
+
|
|
250
300
|
// Maps present quota windows onto display rows and computes pacing against each
|
|
251
301
|
// dynamic duration or the historical 5h/7d duration for legacy snapshots.
|
|
302
|
+
// `labelWidth` comes from computeQuotaLabelWidth for rendered sidebars; callers
|
|
303
|
+
// that only inspect pacing may omit it and get this row set's own width.
|
|
252
304
|
export function buildQuotaRowsForDisplay(
|
|
253
305
|
quota: AccountQuota | null,
|
|
254
306
|
now: number,
|
|
255
307
|
pacingEnabled: boolean,
|
|
308
|
+
labelWidth?: number,
|
|
256
309
|
): QuotaDisplayRow[] {
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
310
|
+
const rows: Array<Omit<QuotaDisplayRow, 'labelWidth'>> =
|
|
311
|
+
getPresentQuotaWindows(quota).map((row) => ({
|
|
312
|
+
key: row.key,
|
|
313
|
+
label: row.label,
|
|
314
|
+
window: row.window,
|
|
315
|
+
pacing:
|
|
316
|
+
pacingEnabled && row.windowMs !== null
|
|
317
|
+
? computeQuotaPacing(row.window, row.windowMs, now)
|
|
318
|
+
: null,
|
|
319
|
+
}))
|
|
320
|
+
const spendControl = quota?.spendControl
|
|
321
|
+
if (spendControl) {
|
|
322
|
+
rows.push({
|
|
323
|
+
key: 'spendControl',
|
|
324
|
+
label: SPEND_CONTROL_LABEL,
|
|
325
|
+
window: {
|
|
326
|
+
usedPercent: spendControl.usedPercent,
|
|
327
|
+
remainingPercent: spendControl.remainingPercent,
|
|
328
|
+
resetsAt: spendControl.resetsAt,
|
|
329
|
+
},
|
|
330
|
+
pacing: null,
|
|
331
|
+
})
|
|
332
|
+
}
|
|
333
|
+
const width =
|
|
334
|
+
labelWidth ?? Math.max(3, ...rows.map((row) => row.label.length + 1))
|
|
335
|
+
return rows.map((row) => ({ ...row, labelWidth: width }))
|
|
266
336
|
}
|
|
267
337
|
|
|
268
338
|
export function isQuotaLoaded(quota: AccountQuota | null): boolean {
|
|
@@ -280,12 +350,39 @@ export function getQuotaMetadataRows(
|
|
|
280
350
|
return rows
|
|
281
351
|
}
|
|
282
352
|
|
|
353
|
+
function formatGroupedAmount(value: number): string {
|
|
354
|
+
return Math.round(value).toLocaleString('en-US')
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// The unit names the budget the amounts are counted in, so it agrees with the
|
|
358
|
+
// total rather than staying singular against a plural quantity.
|
|
359
|
+
function pluralizeUnit(unit: string, count: number): string {
|
|
360
|
+
if (count === 1) return unit
|
|
361
|
+
return unit.endsWith('s') ? unit : `${unit}s`
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
export function formatSpendControlAmounts(
|
|
365
|
+
spendControl: SpendControlReading,
|
|
366
|
+
): string {
|
|
367
|
+
const unit = pluralizeUnit(spendControl.unit ?? 'unit', spendControl.limit)
|
|
368
|
+
return `${formatGroupedAmount(spendControl.used)} / ${formatGroupedAmount(spendControl.limit)} ${unit}`
|
|
369
|
+
}
|
|
370
|
+
|
|
283
371
|
export function getAccountMetadataRows(
|
|
284
372
|
resetCredits: number | undefined,
|
|
373
|
+
spendControl?: SpendControlReading,
|
|
285
374
|
): Array<{ label: string; value: string }> {
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
375
|
+
const rows: Array<{ label: string; value: string }> = []
|
|
376
|
+
if (spendControl) {
|
|
377
|
+
rows.push({
|
|
378
|
+
label: 'credits',
|
|
379
|
+
value: formatSpendControlAmounts(spendControl),
|
|
380
|
+
})
|
|
381
|
+
}
|
|
382
|
+
if (resetCredits !== undefined) {
|
|
383
|
+
rows.push({ label: 'resets', value: String(resetCredits) })
|
|
384
|
+
}
|
|
385
|
+
return rows
|
|
289
386
|
}
|
|
290
387
|
|
|
291
388
|
// Quota window row: muted label left, tone-colored bar + percentage right,
|
|
@@ -296,6 +393,7 @@ function QuotaRow(props: {
|
|
|
296
393
|
theme: ThemeCurrent
|
|
297
394
|
appearance: AppearancePrefs
|
|
298
395
|
label: string
|
|
396
|
+
labelWidth: number
|
|
299
397
|
window: { usedPercent: number; resetsAt?: string } | undefined
|
|
300
398
|
pacing: QuotaPacing | null
|
|
301
399
|
}) {
|
|
@@ -315,7 +413,9 @@ function QuotaRow(props: {
|
|
|
315
413
|
when={props.window}
|
|
316
414
|
fallback={
|
|
317
415
|
<box width='100%' flexDirection='row'>
|
|
318
|
-
<text fg={props.theme.textMuted}>
|
|
416
|
+
<text fg={props.theme.textMuted}>
|
|
417
|
+
{props.label.padEnd(props.labelWidth)}
|
|
418
|
+
</text>
|
|
319
419
|
<text fg={props.theme.textMuted}>{'\u2014'}</text>
|
|
320
420
|
</box>
|
|
321
421
|
}
|
|
@@ -325,7 +425,9 @@ function QuotaRow(props: {
|
|
|
325
425
|
the right edge so reset times align in their own right column. */}
|
|
326
426
|
<box width='100%' flexDirection='row' justifyContent='space-between'>
|
|
327
427
|
<box flexDirection='row'>
|
|
328
|
-
<text fg={props.theme.textMuted}>
|
|
428
|
+
<text fg={props.theme.textMuted}>
|
|
429
|
+
{props.label.padEnd(props.labelWidth)}
|
|
430
|
+
</text>
|
|
329
431
|
<For each={quotaBarSegments(used(), props.appearance, props.pacing)}>
|
|
330
432
|
{(segment) => (
|
|
331
433
|
<text fg={toneColor(props.theme, segment.tone)}>
|
|
@@ -345,7 +447,7 @@ function QuotaRow(props: {
|
|
|
345
447
|
</box>
|
|
346
448
|
<Show when={paceLine()}>
|
|
347
449
|
<box width='100%' flexDirection='row'>
|
|
348
|
-
<text fg={props.theme.textMuted}>{'
|
|
450
|
+
<text fg={props.theme.textMuted}>{''.padEnd(props.labelWidth)}</text>
|
|
349
451
|
<text
|
|
350
452
|
fg={toneColor(
|
|
351
453
|
props.theme,
|
|
@@ -371,6 +473,7 @@ function AccountBlock(props: {
|
|
|
371
473
|
killed: boolean
|
|
372
474
|
active: boolean
|
|
373
475
|
pacingEnabled: boolean
|
|
476
|
+
labelWidth: number
|
|
374
477
|
resetCredits?: number
|
|
375
478
|
marginTop?: number
|
|
376
479
|
}) {
|
|
@@ -379,7 +482,12 @@ function AccountBlock(props: {
|
|
|
379
482
|
const statusTone = (): Tone =>
|
|
380
483
|
props.killed ? 'err' : props.active ? 'ok' : 'muted'
|
|
381
484
|
const rows = () =>
|
|
382
|
-
buildQuotaRowsForDisplay(
|
|
485
|
+
buildQuotaRowsForDisplay(
|
|
486
|
+
props.quota,
|
|
487
|
+
Date.now(),
|
|
488
|
+
props.pacingEnabled,
|
|
489
|
+
props.labelWidth,
|
|
490
|
+
)
|
|
383
491
|
return (
|
|
384
492
|
<box width='100%' flexDirection='column' marginTop={props.marginTop ?? 0}>
|
|
385
493
|
<box width='100%' flexDirection='row' justifyContent='space-between'>
|
|
@@ -404,6 +512,7 @@ function AccountBlock(props: {
|
|
|
404
512
|
theme={props.theme}
|
|
405
513
|
appearance={props.appearance}
|
|
406
514
|
label={row.label}
|
|
515
|
+
labelWidth={row.labelWidth}
|
|
407
516
|
window={row.window}
|
|
408
517
|
pacing={row.pacing}
|
|
409
518
|
/>
|
|
@@ -411,7 +520,12 @@ function AccountBlock(props: {
|
|
|
411
520
|
</For>
|
|
412
521
|
</Show>
|
|
413
522
|
</Show>
|
|
414
|
-
<For
|
|
523
|
+
<For
|
|
524
|
+
each={getAccountMetadataRows(
|
|
525
|
+
props.resetCredits,
|
|
526
|
+
props.quota?.spendControl,
|
|
527
|
+
)}
|
|
528
|
+
>
|
|
415
529
|
{(row) => (
|
|
416
530
|
<StatRow
|
|
417
531
|
theme={props.theme}
|
|
@@ -453,6 +567,7 @@ function QuotaDialogContent(props: {
|
|
|
453
567
|
const enabledFallbacks = () =>
|
|
454
568
|
(state().fallbacks ?? []).filter((f) => f.enabled)
|
|
455
569
|
const activeId = () => resolveQuotaDialogActiveId(state(), props.sessionId)
|
|
570
|
+
const quotaLabelWidth = () => computeQuotaLabelWidth(renderedQuotas(state()))
|
|
456
571
|
return (
|
|
457
572
|
<box flexDirection='column' padding={2} width='100%' alignItems='center'>
|
|
458
573
|
<box flexDirection='column' width={58}>
|
|
@@ -469,6 +584,7 @@ function QuotaDialogContent(props: {
|
|
|
469
584
|
killed={state().main?.killed ?? false}
|
|
470
585
|
active={activeId() === 'main'}
|
|
471
586
|
pacingEnabled={prefs().sections.pacing}
|
|
587
|
+
labelWidth={quotaLabelWidth()}
|
|
472
588
|
resetCredits={state().main?.resetCredits}
|
|
473
589
|
/>
|
|
474
590
|
<Show when={prefs().sections.fallbackAccounts}>
|
|
@@ -482,6 +598,7 @@ function QuotaDialogContent(props: {
|
|
|
482
598
|
killed={fb.killed}
|
|
483
599
|
active={activeId() === fb.id}
|
|
484
600
|
pacingEnabled={prefs().sections.pacing}
|
|
601
|
+
labelWidth={quotaLabelWidth()}
|
|
485
602
|
resetCredits={fb.resetCredits}
|
|
486
603
|
marginTop={1}
|
|
487
604
|
/>
|
|
@@ -662,6 +779,7 @@ function QuotaSidebar(props: {
|
|
|
662
779
|
route: sessionRouting().route,
|
|
663
780
|
})
|
|
664
781
|
const activeAccount = () => resolveActiveAccount(sessionState())
|
|
782
|
+
const quotaLabelWidth = () => computeQuotaLabelWidth(renderedQuotas(state()))
|
|
665
783
|
const activeQuotaSummary = () =>
|
|
666
784
|
getCollapsedQuotaSummary(activeAccount().quota)
|
|
667
785
|
const activePacingDeficit = () => {
|
|
@@ -794,6 +912,7 @@ function QuotaSidebar(props: {
|
|
|
794
912
|
killed={state().main?.killed ?? false}
|
|
795
913
|
active={sessionRouting().activeId === 'main'}
|
|
796
914
|
pacingEnabled={prefs().sections.pacing}
|
|
915
|
+
labelWidth={quotaLabelWidth()}
|
|
797
916
|
resetCredits={state().main?.resetCredits}
|
|
798
917
|
/>
|
|
799
918
|
<Show when={prefs().sections.fallbackAccounts}>
|
|
@@ -807,6 +926,7 @@ function QuotaSidebar(props: {
|
|
|
807
926
|
killed={fb.killed}
|
|
808
927
|
active={sessionRouting().activeId === fb.id}
|
|
809
928
|
pacingEnabled={prefs().sections.pacing}
|
|
929
|
+
labelWidth={quotaLabelWidth()}
|
|
810
930
|
resetCredits={fb.resetCredits}
|
|
811
931
|
marginTop={1}
|
|
812
932
|
/>
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
export type CustodyMode = 'local' | 'claustrum'
|
|
2
|
+
|
|
3
|
+
export type ManifestCustodyState = 'absent' | 'present' | 'unreadable'
|
|
4
|
+
|
|
5
|
+
export type LocalCustodyState =
|
|
6
|
+
| 'real'
|
|
7
|
+
| 'tombstone'
|
|
8
|
+
| 'empty'
|
|
9
|
+
| 'gone'
|
|
10
|
+
| 'slot-absent'
|
|
11
|
+
|
|
12
|
+
export type VaultCustodyState =
|
|
13
|
+
| 'serves'
|
|
14
|
+
| 'cold'
|
|
15
|
+
| 'needs_reauth'
|
|
16
|
+
| 'identity_mismatch'
|
|
17
|
+
| 'no_handle'
|
|
18
|
+
|
|
19
|
+
export const CUSTODY_INERT_REASONS = [
|
|
20
|
+
'needs-login',
|
|
21
|
+
'mode-mismatch',
|
|
22
|
+
'corrupt-under-binding',
|
|
23
|
+
'manifest-unreadable',
|
|
24
|
+
'unbound-under-claustrum',
|
|
25
|
+
'orphan-tombstone',
|
|
26
|
+
'vault-cold',
|
|
27
|
+
'vault-reauth',
|
|
28
|
+
'identity-mismatch',
|
|
29
|
+
'no-handle',
|
|
30
|
+
'takeover-incomplete',
|
|
31
|
+
'new-local-family-under-claustrum',
|
|
32
|
+
'takeover-incomplete/vault-unavailable',
|
|
33
|
+
'takeover-incomplete/slot-absent',
|
|
34
|
+
] as const
|
|
35
|
+
|
|
36
|
+
export type CustodyInertReason = (typeof CUSTODY_INERT_REASONS)[number]
|
|
37
|
+
|
|
38
|
+
export type CustodyVerdict =
|
|
39
|
+
| { kind: 'LOCAL' }
|
|
40
|
+
| {
|
|
41
|
+
kind: 'VAULT'
|
|
42
|
+
canonicalize?: boolean
|
|
43
|
+
installTombstone?: boolean
|
|
44
|
+
}
|
|
45
|
+
| {
|
|
46
|
+
kind: 'INERT'
|
|
47
|
+
reason: CustodyInertReason
|
|
48
|
+
installTombstone?: boolean
|
|
49
|
+
canonicalize?: boolean
|
|
50
|
+
}
|
|
51
|
+
| { kind: 'NEEDS_LOGIN'; reason?: 'corrupt' }
|
|
52
|
+
|
|
53
|
+
export interface EvaluateCustodyStartupInput {
|
|
54
|
+
mode: CustodyMode
|
|
55
|
+
manifest: ManifestCustodyState
|
|
56
|
+
local: LocalCustodyState
|
|
57
|
+
vault: () => VaultCustodyState
|
|
58
|
+
manifestFailureReason?: string
|
|
59
|
+
fingerprintMatch?: boolean
|
|
60
|
+
verifiedInProcessLogin: boolean
|
|
61
|
+
isMain?: boolean
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function unreachable(value: never): never {
|
|
65
|
+
return value
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function withCanonicalization<
|
|
69
|
+
T extends Extract<CustodyVerdict, { kind: 'VAULT' | 'INERT' }>,
|
|
70
|
+
>(local: LocalCustodyState, verdict: T): T | (T & { canonicalize: true }) {
|
|
71
|
+
return local === 'empty' ? { ...verdict, canonicalize: true } : verdict
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function evaluateLocal(local: LocalCustodyState): CustodyVerdict {
|
|
75
|
+
switch (local) {
|
|
76
|
+
case 'real':
|
|
77
|
+
return { kind: 'LOCAL' }
|
|
78
|
+
case 'tombstone':
|
|
79
|
+
case 'empty':
|
|
80
|
+
case 'slot-absent':
|
|
81
|
+
return { kind: 'NEEDS_LOGIN' }
|
|
82
|
+
case 'gone':
|
|
83
|
+
return { kind: 'NEEDS_LOGIN', reason: 'corrupt' }
|
|
84
|
+
}
|
|
85
|
+
return unreachable(local)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function evaluateLocalWithBinding(
|
|
89
|
+
local: LocalCustodyState,
|
|
90
|
+
verifiedInProcessLogin: boolean,
|
|
91
|
+
): CustodyVerdict {
|
|
92
|
+
switch (local) {
|
|
93
|
+
case 'real':
|
|
94
|
+
return verifiedInProcessLogin
|
|
95
|
+
? { kind: 'LOCAL' }
|
|
96
|
+
: { kind: 'INERT', reason: 'needs-login' }
|
|
97
|
+
case 'tombstone':
|
|
98
|
+
case 'empty':
|
|
99
|
+
case 'slot-absent':
|
|
100
|
+
return { kind: 'INERT', reason: 'mode-mismatch' }
|
|
101
|
+
case 'gone':
|
|
102
|
+
return { kind: 'INERT', reason: 'corrupt-under-binding' }
|
|
103
|
+
}
|
|
104
|
+
return unreachable(local)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function evaluateUnboundClaustrum(local: LocalCustodyState): CustodyVerdict {
|
|
108
|
+
switch (local) {
|
|
109
|
+
case 'tombstone':
|
|
110
|
+
case 'empty':
|
|
111
|
+
return { kind: 'INERT', reason: 'orphan-tombstone' }
|
|
112
|
+
case 'real':
|
|
113
|
+
case 'gone':
|
|
114
|
+
case 'slot-absent':
|
|
115
|
+
return { kind: 'INERT', reason: 'unbound-under-claustrum' }
|
|
116
|
+
}
|
|
117
|
+
return unreachable(local)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function evaluateTombstone(
|
|
121
|
+
local: Extract<LocalCustodyState, 'tombstone' | 'empty'>,
|
|
122
|
+
vault: VaultCustodyState,
|
|
123
|
+
): CustodyVerdict {
|
|
124
|
+
switch (vault) {
|
|
125
|
+
case 'serves':
|
|
126
|
+
return withCanonicalization(local, { kind: 'VAULT' })
|
|
127
|
+
case 'cold':
|
|
128
|
+
return withCanonicalization(local, {
|
|
129
|
+
kind: 'INERT',
|
|
130
|
+
reason: 'vault-cold',
|
|
131
|
+
})
|
|
132
|
+
case 'needs_reauth':
|
|
133
|
+
return withCanonicalization(local, {
|
|
134
|
+
kind: 'INERT',
|
|
135
|
+
reason: 'vault-reauth',
|
|
136
|
+
})
|
|
137
|
+
case 'identity_mismatch':
|
|
138
|
+
return withCanonicalization(local, {
|
|
139
|
+
kind: 'INERT',
|
|
140
|
+
reason: 'identity-mismatch',
|
|
141
|
+
})
|
|
142
|
+
case 'no_handle':
|
|
143
|
+
return withCanonicalization(local, { kind: 'INERT', reason: 'no-handle' })
|
|
144
|
+
}
|
|
145
|
+
return unreachable(vault)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function evaluateReal(
|
|
149
|
+
vault: VaultCustodyState,
|
|
150
|
+
fingerprintMatch: boolean | undefined,
|
|
151
|
+
): CustodyVerdict {
|
|
152
|
+
switch (vault) {
|
|
153
|
+
case 'serves':
|
|
154
|
+
return fingerprintMatch === false
|
|
155
|
+
? { kind: 'INERT', reason: 'new-local-family-under-claustrum' }
|
|
156
|
+
: { kind: 'INERT', reason: 'takeover-incomplete' }
|
|
157
|
+
case 'cold':
|
|
158
|
+
case 'needs_reauth':
|
|
159
|
+
return { kind: 'INERT', reason: 'takeover-incomplete/vault-unavailable' }
|
|
160
|
+
case 'identity_mismatch':
|
|
161
|
+
return { kind: 'INERT', reason: 'identity-mismatch' }
|
|
162
|
+
case 'no_handle':
|
|
163
|
+
return { kind: 'INERT', reason: 'no-handle' }
|
|
164
|
+
}
|
|
165
|
+
return unreachable(vault)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function evaluateGone(vault: VaultCustodyState): CustodyVerdict {
|
|
169
|
+
switch (vault) {
|
|
170
|
+
case 'serves':
|
|
171
|
+
return { kind: 'VAULT', installTombstone: true }
|
|
172
|
+
case 'cold':
|
|
173
|
+
return { kind: 'INERT', reason: 'vault-cold', installTombstone: true }
|
|
174
|
+
case 'needs_reauth':
|
|
175
|
+
return { kind: 'INERT', reason: 'vault-reauth', installTombstone: true }
|
|
176
|
+
case 'identity_mismatch':
|
|
177
|
+
return { kind: 'INERT', reason: 'identity-mismatch' }
|
|
178
|
+
case 'no_handle':
|
|
179
|
+
return { kind: 'INERT', reason: 'no-handle' }
|
|
180
|
+
}
|
|
181
|
+
return unreachable(vault)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function evaluateSlotAbsent(vault: VaultCustodyState): CustodyVerdict {
|
|
185
|
+
switch (vault) {
|
|
186
|
+
case 'serves':
|
|
187
|
+
case 'cold':
|
|
188
|
+
case 'needs_reauth':
|
|
189
|
+
return { kind: 'INERT', reason: 'takeover-incomplete/slot-absent' }
|
|
190
|
+
case 'identity_mismatch':
|
|
191
|
+
return { kind: 'INERT', reason: 'identity-mismatch' }
|
|
192
|
+
case 'no_handle':
|
|
193
|
+
return { kind: 'INERT', reason: 'no-handle' }
|
|
194
|
+
}
|
|
195
|
+
return unreachable(vault)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function evaluatePresentClaustrum(
|
|
199
|
+
local: LocalCustodyState,
|
|
200
|
+
vault: VaultCustodyState,
|
|
201
|
+
fingerprintMatch: boolean | undefined,
|
|
202
|
+
): CustodyVerdict {
|
|
203
|
+
switch (local) {
|
|
204
|
+
case 'tombstone':
|
|
205
|
+
case 'empty':
|
|
206
|
+
return evaluateTombstone(local, vault)
|
|
207
|
+
case 'real':
|
|
208
|
+
return evaluateReal(vault, fingerprintMatch)
|
|
209
|
+
case 'gone':
|
|
210
|
+
return evaluateGone(vault)
|
|
211
|
+
case 'slot-absent':
|
|
212
|
+
return evaluateSlotAbsent(vault)
|
|
213
|
+
}
|
|
214
|
+
return unreachable(local)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export function evaluateCustodyStartup(
|
|
218
|
+
input: EvaluateCustodyStartupInput,
|
|
219
|
+
): CustodyVerdict {
|
|
220
|
+
if (input.manifest === 'unreadable') {
|
|
221
|
+
return { kind: 'INERT', reason: 'manifest-unreadable' }
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
switch (input.mode) {
|
|
225
|
+
case 'local':
|
|
226
|
+
return input.manifest === 'present'
|
|
227
|
+
? evaluateLocalWithBinding(input.local, input.verifiedInProcessLogin)
|
|
228
|
+
: evaluateLocal(input.local)
|
|
229
|
+
case 'claustrum':
|
|
230
|
+
if (input.manifest === 'absent') {
|
|
231
|
+
return evaluateUnboundClaustrum(input.local)
|
|
232
|
+
}
|
|
233
|
+
if (input.manifest === 'present') {
|
|
234
|
+
return evaluatePresentClaustrum(
|
|
235
|
+
input.local,
|
|
236
|
+
input.vault(),
|
|
237
|
+
input.fingerprintMatch,
|
|
238
|
+
)
|
|
239
|
+
}
|
|
240
|
+
return unreachable(input.manifest)
|
|
241
|
+
}
|
|
242
|
+
return unreachable(input.mode)
|
|
243
|
+
}
|