@cortexkit/common-auth 0.3.0 → 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.
Files changed (59) hide show
  1. package/dist/cachekeep/manager.d.ts +18 -6
  2. package/dist/cachekeep/manager.js +40 -10
  3. package/dist/claustrum/consumer.d.ts +13 -4
  4. package/dist/claustrum/consumer.js +11 -3
  5. package/dist/claustrum/custody.d.ts +47 -6
  6. package/dist/claustrum/custody.js +37 -7
  7. package/dist/claustrum/errors.d.ts +1 -1
  8. package/dist/claustrum/index.d.ts +3 -3
  9. package/dist/claustrum/index.js +2 -2
  10. package/dist/claustrum/interlock.d.ts +15 -17
  11. package/dist/claustrum/interlock.js +19 -26
  12. package/dist/claustrum/roster.d.ts +96 -6
  13. package/dist/claustrum/roster.js +209 -44
  14. package/dist/commands/builtins.d.ts +1 -1
  15. package/dist/commands/builtins.js +6 -1
  16. package/dist/commands/index.d.ts +2 -2
  17. package/dist/commands/index.js +1 -1
  18. package/dist/commands/menu.d.ts +8 -0
  19. package/dist/commands/menu.js +34 -13
  20. package/dist/commands/model.d.ts +9 -0
  21. package/dist/commands/seam.d.ts +40 -4
  22. package/dist/commands/seam.js +132 -19
  23. package/dist/dump/index.d.ts +94 -0
  24. package/dist/dump/index.js +236 -9
  25. package/dist/logger/engine.d.ts +52 -17
  26. package/dist/logger/engine.js +178 -135
  27. package/dist/logger/index.d.ts +2 -2
  28. package/dist/logger/index.js +1 -1
  29. package/dist/opencode2/install.d.ts +8 -3
  30. package/dist/opencode2/install.js +18 -9
  31. package/dist/opencode2/types.d.ts +22 -3
  32. package/dist/quota/projection.d.ts +11 -4
  33. package/dist/quota/projection.js +11 -4
  34. package/dist/routing/admission.js +3 -1
  35. package/dist/routing/index.d.ts +2 -2
  36. package/dist/routing/index.js +1 -1
  37. package/dist/routing/sticky.d.ts +19 -6
  38. package/dist/routing/sticky.js +34 -23
  39. package/dist/rpc/notifications.d.ts +20 -0
  40. package/dist/rpc/notifications.js +21 -0
  41. package/dist/rpc/rpc-server.d.ts +9 -1
  42. package/dist/rpc/rpc-server.js +8 -1
  43. package/dist/sidebar-file/index.d.ts +1 -1
  44. package/dist/sidebar-file/sidebar-file.d.ts +50 -2
  45. package/dist/sidebar-file/sidebar-file.js +92 -21
  46. package/dist/store/attribution.js +11 -2
  47. package/dist/store/errors.d.ts +6 -3
  48. package/dist/store/identity.d.ts +13 -4
  49. package/dist/store/mutate.d.ts +23 -3
  50. package/dist/store/mutate.js +43 -26
  51. package/dist/store/pool.d.ts +8 -1
  52. package/dist/store/pool.js +7 -2
  53. package/dist/store/rows.d.ts +17 -4
  54. package/dist/store/rows.js +82 -33
  55. package/dist/store/schema.d.ts +57 -4
  56. package/dist/store/schema.js +100 -7
  57. package/dist/store/torn.d.ts +29 -0
  58. package/dist/store/torn.js +113 -0
  59. package/package.json +1 -1
@@ -0,0 +1,29 @@
1
+ import { type RowEditor } from './identity.js';
2
+ import { type CredentialBinding, type CredentialStamp, type PoolRow, type QuotaCodec } from './schema.js';
3
+ /** Stamps of rows torn between the two writes of a replace, by row id. */
4
+ export declare function tornStamps(config: Record<string, unknown>, state: Record<string, unknown>, codec: QuotaCodec): Map<string, CredentialStamp & {
5
+ binding: CredentialBinding;
6
+ }>;
7
+ /**
8
+ * The config half of a replacement: the row's entry moves to the new epoch,
9
+ * loses its quota and needs a first reading; its roster row gets the new
10
+ * identity (or loses the old one) and, for an API key, the new endpoint.
11
+ * Disabling later rows that share the new identity is left to the caller
12
+ * (`disableIdentityDuplicates`), so several rows can be bound first.
13
+ */
14
+ export declare function bindReplacement(editor: RowEditor, id: string, credentialEpoch: number, binding: CredentialBinding): void;
15
+ /**
16
+ * The config with every torn row completed as its replace would have left
17
+ * it, and the ids completed. The config passed in is not modified; when
18
+ * nothing is torn it is returned as is.
19
+ */
20
+ export declare function completeTornRows(config: Record<string, unknown>, state: Record<string, unknown>, codec: QuotaCodec): {
21
+ config: Record<string, unknown>;
22
+ torn: string[];
23
+ };
24
+ /**
25
+ * The rows every reader gets. A torn row is shown completed (the identity,
26
+ * endpoint and epoch its stamp names, beside the credential it stamps), is
27
+ * marked `torn` and is never a candidate; every other row is as on disk.
28
+ */
29
+ export declare function loadRows(config: Record<string, unknown>, state: Record<string, unknown>, codec: QuotaCodec): PoolRow[];
@@ -0,0 +1,113 @@
1
+ import { disableIdentityDuplicates } from './identity.js';
2
+ import { buildRawRows, CREDENTIAL_STAMP_KEY, credentialDigest, entryIn, isRecord, parseStamp, rosterRowIn, setEntryIn, } from './schema.js';
3
+ /*
4
+ * A replace changes both files: the state file gets the new credential and
5
+ * the config gets the new epoch, identity or endpoint. It writes the state
6
+ * file first, stamping the credential with the epoch it belongs to and the
7
+ * binding (identity, endpoint) the config is about to get. A crash between
8
+ * the two writes therefore leaves a credential whose stamp is ahead of the
9
+ * config's epoch, and that is how every reader tells such a torn row from a
10
+ * whole one. The stamp holds everything the config write would have written,
11
+ * so a torn row is completed forward: readers are shown the completed row
12
+ * (never a candidate until it is on disk) and the next store write on it
13
+ * writes the config to match.
14
+ *
15
+ * A stamp counts only beside the credential it was written with (its digest
16
+ * matches): a writer that does not know about stamps may put another
17
+ * credential beside an old one, and that stamp then says nothing. A stamp at
18
+ * or behind the config's epoch is never torn: this store's own writes leave
19
+ * the state file ahead of the config, never behind it.
20
+ */
21
+ /** Stamps of rows torn between the two writes of a replace, by row id. */
22
+ export function tornStamps(config, state, codec) {
23
+ const accounts = isRecord(state.accounts) ? state.accounts : {};
24
+ const torn = new Map();
25
+ for (const row of buildRawRows(config, state, codec)) {
26
+ if (row.invalid || !row.credential)
27
+ continue;
28
+ const account = Object.hasOwn(accounts, row.id)
29
+ ? accounts[row.id]
30
+ : undefined;
31
+ if (!isRecord(account))
32
+ continue;
33
+ const stamp = parseStamp(account[CREDENTIAL_STAMP_KEY]);
34
+ if (!stamp?.binding)
35
+ continue;
36
+ if (stamp.digest !== credentialDigest(row.credential))
37
+ continue;
38
+ // A row without an entry is at epoch 1, as everywhere else.
39
+ if (stamp.credentialEpoch <= (row.credentialEpoch ?? 1))
40
+ continue;
41
+ torn.set(row.id, { ...stamp, binding: stamp.binding });
42
+ }
43
+ return torn;
44
+ }
45
+ /**
46
+ * The config half of a replacement: the row's entry moves to the new epoch,
47
+ * loses its quota and needs a first reading; its roster row gets the new
48
+ * identity (or loses the old one) and, for an API key, the new endpoint.
49
+ * Disabling later rows that share the new identity is left to the caller
50
+ * (`disableIdentityDuplicates`), so several rows can be bound first.
51
+ */
52
+ export function bindReplacement(editor, id, credentialEpoch, binding) {
53
+ const entry = editor.entry(id) ?? {};
54
+ const next = {
55
+ ...entry,
56
+ credentialEpoch,
57
+ needsFirstReading: true,
58
+ };
59
+ delete next.quota;
60
+ editor.setEntry(id, next);
61
+ const raw = editor.rosterRow(id);
62
+ if (!raw)
63
+ return;
64
+ if (binding.identity !== undefined)
65
+ raw.accountId = binding.identity;
66
+ else
67
+ delete raw.accountId;
68
+ if (binding.baseURL !== undefined)
69
+ raw.baseURL = binding.baseURL;
70
+ if (binding.authHeader !== undefined)
71
+ raw.authHeader = binding.authHeader;
72
+ }
73
+ /**
74
+ * The config with every torn row completed as its replace would have left
75
+ * it, and the ids completed. The config passed in is not modified; when
76
+ * nothing is torn it is returned as is.
77
+ */
78
+ export function completeTornRows(config, state, codec) {
79
+ const stamps = tornStamps(config, state, codec);
80
+ if (stamps.size === 0)
81
+ return { config, torn: [] };
82
+ const next = structuredClone(config);
83
+ const editor = {
84
+ rows: () => buildRawRows(next, state, codec),
85
+ rosterRow: (id) => rosterRowIn(next, id),
86
+ entry: (id) => entryIn(next, id),
87
+ setEntry: (id, entry) => setEntryIn(next, id, entry),
88
+ };
89
+ for (const [id, stamp] of stamps)
90
+ bindReplacement(editor, id, stamp.credentialEpoch, stamp.binding);
91
+ for (const stamp of stamps.values())
92
+ if (stamp.binding.identity !== undefined)
93
+ disableIdentityDuplicates(editor, stamp.binding.identity);
94
+ return { config: next, torn: [...stamps.keys()] };
95
+ }
96
+ /**
97
+ * The rows every reader gets. A torn row is shown completed (the identity,
98
+ * endpoint and epoch its stamp names, beside the credential it stamps), is
99
+ * marked `torn` and is never a candidate; every other row is as on disk.
100
+ */
101
+ export function loadRows(config, state, codec) {
102
+ const { config: whole, torn } = completeTornRows(config, state, codec);
103
+ const rows = buildRawRows(whole, state, codec);
104
+ if (torn.length === 0)
105
+ return rows;
106
+ for (const row of rows) {
107
+ if (!torn.includes(row.id))
108
+ continue;
109
+ row.torn = true;
110
+ row.candidate = false;
111
+ }
112
+ return rows;
113
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cortexkit/common-auth",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Shared code for the CortexKit auth plugins: account pool, quota and routing, commands and auth menu, OpenCode 2 hooks, Claustrum custody, and plumbing (loopback RPC, file locks, logger, sidebar state, TUI preferences and build).",
5
5
  "license": "MIT",
6
6
  "repository": {