@cortexkit/common-auth 0.7.0 → 0.9.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/dist/logger/engine.d.ts +24 -3
- package/dist/logger/engine.js +5 -1
- package/dist/logger/index.d.ts +1 -1
- package/dist/logger/redact.js +42 -4
- package/dist/rpc/notifications.d.ts +25 -1
- package/dist/rpc/notifications.js +28 -6
- package/dist/rpc/port-file.d.ts +10 -1
- package/dist/rpc/port-file.js +50 -16
- package/dist/rpc/rpc-client.d.ts +20 -3
- package/dist/rpc/rpc-client.js +132 -30
- package/dist/rpc/rpc-server.d.ts +18 -0
- package/dist/rpc/rpc-server.js +98 -14
- package/dist/sidebar-file/sidebar-file.d.ts +10 -0
- package/dist/sidebar-file/sidebar-file.js +1 -0
- package/dist/store/attribution.d.ts +13 -2
- package/dist/store/errors.d.ts +1 -1
- package/dist/store/identity.d.ts +2 -0
- package/dist/store/identity.js +9 -1
- package/dist/store/mutate.d.ts +5 -1
- package/dist/store/mutate.js +14 -2
- package/dist/store/pool.d.ts +7 -0
- package/dist/store/refresh.d.ts +17 -1
- package/dist/store/refresh.js +35 -2
- package/dist/store/rows.d.ts +9 -2
- package/dist/store/rows.js +28 -10
- package/dist/store/schema.d.ts +40 -0
- package/dist/store/schema.js +87 -3
- package/dist/store/torn.js +8 -1
- package/dist/tui-build/build-tui.js +39 -4
- package/package.json +7 -5
package/dist/store/schema.js
CHANGED
|
@@ -5,6 +5,15 @@ export const POOL_KEY = 'commonAuthPool';
|
|
|
5
5
|
export const POOL_SCHEMA_VERSION = 1;
|
|
6
6
|
/** Property of `commonAuthPool` holding the per-row entries, keyed by local id. */
|
|
7
7
|
export const POOL_ROWS_KEY = 'rows';
|
|
8
|
+
/**
|
|
9
|
+
* Property of `commonAuthPool` (since 0.8.0) holding, per id, the highest
|
|
10
|
+
* credential epoch a row with that id held when the store last dropped it
|
|
11
|
+
* from the pool. A row added later under the same id starts past it (see
|
|
12
|
+
* `nextAddEpochIn`), so an attribution taken for the dropped row never
|
|
13
|
+
* matches the new one. Older readers ignore it, and older writers keep it
|
|
14
|
+
* as they keep every pool key they do not know.
|
|
15
|
+
*/
|
|
16
|
+
export const POOL_RETIRED_EPOCHS_KEY = 'retiredEpochs';
|
|
8
17
|
/** The `version` older readers of the same files expect at the top level. */
|
|
9
18
|
export const LEGACY_STORE_VERSION = 1;
|
|
10
19
|
/**
|
|
@@ -410,11 +419,15 @@ function credentialFor(raw, stateEntry) {
|
|
|
410
419
|
export function rosterRowIn(config, id) {
|
|
411
420
|
return rosterOf(config).find((raw) => isRecord(raw) && raw.id === id);
|
|
412
421
|
}
|
|
413
|
-
/** The
|
|
414
|
-
|
|
422
|
+
/** The pool object of a config, created (empty) when absent. */
|
|
423
|
+
function ensurePool(config) {
|
|
415
424
|
if (!isRecord(config[POOL_KEY]))
|
|
416
425
|
config[POOL_KEY] = {};
|
|
417
|
-
|
|
426
|
+
return config[POOL_KEY];
|
|
427
|
+
}
|
|
428
|
+
/** The per-row entries of a config, created (empty) when absent. */
|
|
429
|
+
export function ensureEntries(config) {
|
|
430
|
+
const pool = ensurePool(config);
|
|
418
431
|
if (!isRecord(pool[POOL_ROWS_KEY]))
|
|
419
432
|
pool[POOL_ROWS_KEY] = {};
|
|
420
433
|
return pool[POOL_ROWS_KEY];
|
|
@@ -433,6 +446,77 @@ export function setEntryIn(config, id, entry) {
|
|
|
433
446
|
configurable: true,
|
|
434
447
|
});
|
|
435
448
|
}
|
|
449
|
+
/**
|
|
450
|
+
* The credential epoch recorded for a dropped id (see
|
|
451
|
+
* `POOL_RETIRED_EPOCHS_KEY`), or undefined when none is. A value that is not
|
|
452
|
+
* a credential epoch counts as none.
|
|
453
|
+
*/
|
|
454
|
+
export function retiredEpochIn(config, id) {
|
|
455
|
+
const pool = config[POOL_KEY];
|
|
456
|
+
if (!isRecord(pool))
|
|
457
|
+
return undefined;
|
|
458
|
+
const retired = pool[POOL_RETIRED_EPOCHS_KEY];
|
|
459
|
+
if (!isRecord(retired) || !Object.hasOwn(retired, id))
|
|
460
|
+
return undefined;
|
|
461
|
+
const epoch = retired[id];
|
|
462
|
+
return isCredentialEpoch(epoch) ? epoch : undefined;
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* The epoch a per-row entry claims, read without validating the rest of the
|
|
466
|
+
* entry, or undefined when it names none a reader would accept.
|
|
467
|
+
*/
|
|
468
|
+
function entryEpochIn(config, id) {
|
|
469
|
+
const epoch = entryIn(config, id)?.credentialEpoch;
|
|
470
|
+
return isCredentialEpoch(epoch) ? epoch : undefined;
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* The credential epoch `add` gives a new row with this id: one past the
|
|
474
|
+
* highest epoch the id is known to have held, which is the epoch recorded
|
|
475
|
+
* when the store dropped it, or the epoch of an entry left behind by a writer
|
|
476
|
+
* that removed only its roster row; 1 for an id the pool never held.
|
|
477
|
+
*
|
|
478
|
+
* An attribution names a row by id and credential epoch (and identity), and
|
|
479
|
+
* an id is chosen by the plugin, so it is often the same one again (`main`).
|
|
480
|
+
* Were a re-added row to start at epoch 1 again, an attribution taken for the
|
|
481
|
+
* removed row's credential would match the new credential exactly, in this
|
|
482
|
+
* process or any other. Starting past every earlier epoch makes such an
|
|
483
|
+
* attribution fail as it does after a `replace`. The result may lie past the
|
|
484
|
+
* safe integers (an id whose last row was at `Number.MAX_SAFE_INTEGER`);
|
|
485
|
+
* `add` refuses such an id.
|
|
486
|
+
*/
|
|
487
|
+
export function nextAddEpochIn(config, id) {
|
|
488
|
+
return (Math.max(retiredEpochIn(config, id) ?? 0, entryEpochIn(config, id) ?? 0) + 1);
|
|
489
|
+
}
|
|
490
|
+
/**
|
|
491
|
+
* Records, in a config being written, the epochs of the ids it drops: for
|
|
492
|
+
* each, the epoch its entry claims (1 for a row without one, the epoch such a
|
|
493
|
+
* row is at), kept only when above what is already recorded, so the record
|
|
494
|
+
* for an id never goes down. Valid recorded values of other ids are kept; a
|
|
495
|
+
* record that is not an object, or a value in it that is not an epoch, says
|
|
496
|
+
* nothing and is replaced. An entry whose epoch cannot be read records 1.
|
|
497
|
+
*/
|
|
498
|
+
export function retireEpochsIn(config, dropped) {
|
|
499
|
+
const ids = [...dropped];
|
|
500
|
+
if (ids.length === 0)
|
|
501
|
+
return;
|
|
502
|
+
const pool = ensurePool(config);
|
|
503
|
+
const previous = isRecord(pool[POOL_RETIRED_EPOCHS_KEY])
|
|
504
|
+
? pool[POOL_RETIRED_EPOCHS_KEY]
|
|
505
|
+
: {};
|
|
506
|
+
const next = {};
|
|
507
|
+
const define = (id, epoch) => Object.defineProperty(next, id, {
|
|
508
|
+
value: epoch,
|
|
509
|
+
enumerable: true,
|
|
510
|
+
writable: true,
|
|
511
|
+
configurable: true,
|
|
512
|
+
});
|
|
513
|
+
for (const [id, epoch] of Object.entries(previous))
|
|
514
|
+
if (isCredentialEpoch(epoch))
|
|
515
|
+
define(id, epoch);
|
|
516
|
+
for (const id of ids)
|
|
517
|
+
define(id, Math.max(retiredEpochIn(config, id) ?? 0, entryEpochIn(config, id) ?? 1));
|
|
518
|
+
pool[POOL_RETIRED_EPOCHS_KEY] = next;
|
|
519
|
+
}
|
|
436
520
|
/**
|
|
437
521
|
* Builds the rows of a ready pool from the files exactly as they are, without
|
|
438
522
|
* looking at credential stamps (see `loadRows` for the rows every reader
|
package/dist/store/torn.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { disableIdentityDuplicates, disableIn, enableIn, recordIdentityIn, } from './identity.js';
|
|
1
|
+
import { disableIdentityDuplicates, disableIn, enableIn, IDENTITY_CONTRADICTED_REASON_PREFIX, recordIdentityIn, } from './identity.js';
|
|
2
2
|
import { buildRawRows, CREDENTIAL_STAMP_KEY, credentialDigest, dispatchDigest, entryIn, isRecord, parseStamp, rosterRowIn, setEntryIn, } from './schema.js';
|
|
3
3
|
/*
|
|
4
4
|
* A replace changes both files: the state file gets the new credential and
|
|
@@ -209,6 +209,13 @@ export function bindReplacement(editor, id, credentialEpoch, binding) {
|
|
|
209
209
|
needsFirstReading: true,
|
|
210
210
|
};
|
|
211
211
|
delete next.quota;
|
|
212
|
+
// An explicit identity supplied with a replacement is the adapter's validated
|
|
213
|
+
// account binding. Clear its quarantine on completion, including crash recovery,
|
|
214
|
+
// but leave the row disabled until a separate enable.
|
|
215
|
+
if (binding.identity !== undefined &&
|
|
216
|
+
typeof next.disabledReason === 'string' &&
|
|
217
|
+
next.disabledReason.startsWith(IDENTITY_CONTRADICTED_REASON_PREFIX))
|
|
218
|
+
delete next.disabledReason;
|
|
212
219
|
editor.setEntry(id, next);
|
|
213
220
|
const raw = editor.rosterRow(id);
|
|
214
221
|
if (!raw)
|
|
@@ -2,7 +2,7 @@ import { createHash } from 'node:crypto';
|
|
|
2
2
|
import { existsSync, realpathSync } from 'node:fs';
|
|
3
3
|
import { mkdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|
4
4
|
import { createRequire } from 'node:module';
|
|
5
|
-
import { basename, dirname, extname, join, relative, resolve } from 'node:path';
|
|
5
|
+
import { basename, dirname, extname, isAbsolute, join, relative, resolve, sep, } from 'node:path';
|
|
6
6
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
7
7
|
import { isBareSpecifier, parseImports } from './walker.js';
|
|
8
8
|
export const runtimeModules = [
|
|
@@ -109,6 +109,35 @@ export async function buildTui(entryFile, variant, destinationDir, options = {})
|
|
|
109
109
|
selector: 'selector.js',
|
|
110
110
|
externals: new Set(),
|
|
111
111
|
};
|
|
112
|
+
// Every name below is computed from the entry's real directory: an entry
|
|
113
|
+
// reached through a symlink (macOS's /var -> /private/var temp directory,
|
|
114
|
+
// a linked checkout) must not make its own siblings look like outside files.
|
|
115
|
+
const entryDirectory = dirname(realpathSync(entry));
|
|
116
|
+
const portable = (path) => path.split(sep).join('/');
|
|
117
|
+
// A shared copy's name must be the same wherever the checkout lives, so it
|
|
118
|
+
// is derived from a key that holds no absolute path. A file inside an
|
|
119
|
+
// inlined package is keyed by that package's name and its path within the
|
|
120
|
+
// package; any other file by its path relative to the entry's directory.
|
|
121
|
+
// Both are one-to-one: one root per package name (the innermost root wins
|
|
122
|
+
// when roots nest, so a file always picks the same one), a fixed base for
|
|
123
|
+
// relative paths, and distinct prefixes for the two kinds. The guard in
|
|
124
|
+
// `visit` turns the remaining risk, two keys sharing a truncated hash, into
|
|
125
|
+
// an error instead of one copy silently overwriting another.
|
|
126
|
+
const namingRoots = [...roots]
|
|
127
|
+
.map(([name, root]) => ({
|
|
128
|
+
name,
|
|
129
|
+
root: existsSync(root) ? realpathSync(root) : root,
|
|
130
|
+
}))
|
|
131
|
+
.sort((a, b) => b.root.length - a.root.length);
|
|
132
|
+
const sharedKey = (source) => {
|
|
133
|
+
for (const { name, root } of namingRoots) {
|
|
134
|
+
const path = relative(root, source);
|
|
135
|
+
if (path && !path.startsWith(`..${sep}`) && !isAbsolute(path))
|
|
136
|
+
return `package:${name}/${portable(path)}`;
|
|
137
|
+
}
|
|
138
|
+
return `path:${portable(relative(entryDirectory, source))}`;
|
|
139
|
+
};
|
|
140
|
+
const sharedOwners = new Map();
|
|
112
141
|
const copied = new Map();
|
|
113
142
|
const relativeImport = (from, to) => {
|
|
114
143
|
const path = relative(dirname(from), to).split('\\').join('/');
|
|
@@ -116,17 +145,23 @@ export async function buildTui(entryFile, variant, destinationDir, options = {})
|
|
|
116
145
|
};
|
|
117
146
|
const visit = async (source, shared) => {
|
|
118
147
|
source = realpathSync(source);
|
|
119
|
-
if (relative(
|
|
148
|
+
if (relative(entryDirectory, source).startsWith(`..${sep}`))
|
|
120
149
|
shared = true;
|
|
121
150
|
const previous = copied.get(source);
|
|
122
151
|
if (previous)
|
|
123
152
|
return previous;
|
|
124
153
|
const extension = variant === 'raw' && extname(source) === '.tsx' ? '.tsx' : '.js';
|
|
125
154
|
const name = shared
|
|
126
|
-
? `shared/${createHash('sha256').update(source).digest('hex').slice(0, 16)}-${basename(source).replace(/\.[^.]+$/, '')}${extension}`
|
|
127
|
-
: relative(
|
|
155
|
+
? `shared/${createHash('sha256').update(sharedKey(source)).digest('hex').slice(0, 16)}-${basename(source).replace(/\.[^.]+$/, '')}${extension}`
|
|
156
|
+
: portable(relative(entryDirectory, source)).replace(/\.[^.]+$/, extension);
|
|
128
157
|
if (name.startsWith('../'))
|
|
129
158
|
throw new Error(`Entry dependency escapes source directory: ${source}`);
|
|
159
|
+
if (shared) {
|
|
160
|
+
const owner = sharedOwners.get(name);
|
|
161
|
+
if (owner !== undefined)
|
|
162
|
+
throw new Error(`Shared module name ${name} is claimed by both ${owner} and ${source}`);
|
|
163
|
+
sharedOwners.set(name, source);
|
|
164
|
+
}
|
|
130
165
|
copied.set(source, name);
|
|
131
166
|
let code = await readFile(source, 'utf8');
|
|
132
167
|
if (transform && /\.[jt]sx$/.test(source))
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cortexkit/common-auth",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.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": {
|
|
@@ -89,16 +89,18 @@
|
|
|
89
89
|
"format:check": "biome format .",
|
|
90
90
|
"lint": "biome check .",
|
|
91
91
|
"prepublishOnly": "bun run build",
|
|
92
|
-
"check:ranges": "bun scripts/check-installed-ranges.mjs"
|
|
92
|
+
"check:ranges": "bun scripts/check-installed-ranges.mjs",
|
|
93
|
+
"check:claustrum-040": "bun scripts/check-claustrum-040.mjs",
|
|
94
|
+
"check:opentui-0511": "bun scripts/check-opentui-0511.mjs"
|
|
93
95
|
},
|
|
94
96
|
"dependencies": {
|
|
95
97
|
"jsonc-parser": "^3.3.1"
|
|
96
98
|
},
|
|
97
99
|
"peerDependencies": {
|
|
98
|
-
"@opentui/core": ">=0.5.
|
|
99
|
-
"@opentui/solid": ">=0.5.
|
|
100
|
+
"@opentui/core": ">=0.5.11",
|
|
101
|
+
"@opentui/solid": ">=0.5.11",
|
|
100
102
|
"solid-js": "1.9.12",
|
|
101
|
-
"@cortexkit/claustrum-client": ">=0.
|
|
103
|
+
"@cortexkit/claustrum-client": ">=0.4.0",
|
|
102
104
|
"@opencode/plugin": ">=2.0.21"
|
|
103
105
|
},
|
|
104
106
|
"peerDependenciesMeta": {
|