@cortexkit/antigravity-auth-core 3.0.3 → 3.0.5

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 (42) hide show
  1. package/dist/agy-request-metadata.d.ts.map +1 -1
  2. package/dist/agy-request-metadata.js +9 -0
  3. package/dist/agy-request-metadata.js.map +1 -1
  4. package/dist/common-auth-embedded/claustrum/enrollment.d.ts +3 -2
  5. package/dist/common-auth-embedded/claustrum/enrollment.js +84 -42
  6. package/dist/common-auth-embedded/claustrum/roster.js +2 -2
  7. package/dist/common-auth-embedded/fs/atomic-write.d.ts +5 -0
  8. package/dist/common-auth-embedded/fs/atomic-write.js +21 -0
  9. package/dist/common-auth-embedded/fs/index.d.ts +2 -0
  10. package/dist/common-auth-embedded/fs/index.js +1 -0
  11. package/dist/common-auth-embedded/fs/private-files.d.ts +66 -0
  12. package/dist/common-auth-embedded/fs/private-files.js +302 -0
  13. package/dist/common-auth-embedded/fs/refresh-file-lock.d.ts +1 -1
  14. package/dist/common-auth-embedded/fs/refresh-file-lock.js +254 -37
  15. package/dist/common-auth-embedded/fs/with-lock.d.ts +3 -0
  16. package/dist/common-auth-embedded/fs/with-lock.js +4 -0
  17. package/dist/common-auth-embedded/logger/engine.d.ts +6 -8
  18. package/dist/common-auth-embedded/logger/engine.js +126 -11
  19. package/dist/common-auth-embedded/pi-slot/slot.d.ts +2 -0
  20. package/dist/common-auth-embedded/pi-slot/slot.js +34 -6
  21. package/dist/common-auth-embedded/source-output.json +83 -67
  22. package/dist/common-auth-embedded/store/mutate.js +2 -2
  23. package/dist/common-auth-embedded/store/pool.d.ts +1 -0
  24. package/dist/common-auth-embedded/store/pool.js +3 -0
  25. package/dist/common-auth-embedded/store/refresh-lock.d.ts +4 -0
  26. package/dist/common-auth-embedded/store/refresh-lock.js +5 -0
  27. package/dist/fingerprint.d.ts +2 -2
  28. package/dist/fingerprint.js +2 -2
  29. package/dist/model-registry.d.ts +4 -1
  30. package/dist/model-registry.d.ts.map +1 -1
  31. package/dist/model-registry.js +61 -104
  32. package/dist/model-registry.js.map +1 -1
  33. package/dist/quota-manager.d.ts.map +1 -1
  34. package/dist/quota-manager.js +3 -1
  35. package/dist/quota-manager.js.map +1 -1
  36. package/dist/transform/agy-generation.d.ts.map +1 -1
  37. package/dist/transform/agy-generation.js +11 -2
  38. package/dist/transform/agy-generation.js.map +1 -1
  39. package/dist/transform/model-resolver.d.ts.map +1 -1
  40. package/dist/transform/model-resolver.js +18 -4
  41. package/dist/transform/model-resolver.js.map +1 -1
  42. package/package.json +3 -2
@@ -0,0 +1,302 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { lstat, readFile, rename, unlink } from 'node:fs/promises';
3
+ import { dirname, resolve } from 'node:path';
4
+ export const windowsPrivateFiles = process.platform === 'win32';
5
+ const migrationNotices = new Set();
6
+ function ioCode(detail) {
7
+ // Native errors retain their Win32 code. Only demonstrated absence/exclusive
8
+ // collisions are normalized; access/sharing failures never become lost races.
9
+ if (detail?.reason === 'io-error') {
10
+ if (detail.win32Code === 2 || detail.win32Code === 3)
11
+ return 'ENOENT';
12
+ if (detail.win32Code === 80 || detail.win32Code === 183)
13
+ return 'EEXIST';
14
+ }
15
+ return detail?.code ?? 'ERR_PRIVATE_FILE';
16
+ }
17
+ // Production loads this package only on Windows; POSIX keeps its own directory policy.
18
+ let backend;
19
+ /** Tests inject a backend here; production always imports the private-files package. */
20
+ export function setPrivateFilesBackendForTest(api) {
21
+ backend = api ? Promise.resolve(api) : undefined;
22
+ }
23
+ function loadBackend() {
24
+ backend ??= import('@cortexkit/private-files');
25
+ return backend;
26
+ }
27
+ export class PrivateFileError extends Error {
28
+ path;
29
+ stage;
30
+ code;
31
+ win32Code;
32
+ reason;
33
+ published = false;
34
+ durabilityConfirmed = false;
35
+ fileDurable = false;
36
+ attempts = 0;
37
+ elapsedMs = 0;
38
+ orphanPath;
39
+ constructor(path, stage, cause) {
40
+ const detail = cause;
41
+ super(`Private file refused at ${stage}: ${path} (${detail?.reason ?? detail?.code ?? 'io-error'}). Use a safe parent directory. Repair only a named secret file in safe ancestry whose owner the published policy trusts, without changing its owner or any existing directory. Previously exposed credentials may need rotation. Windows requires the private-files native prebuild and an ACL-persisting local filesystem such as NTFS.`, { cause });
42
+ this.path = path;
43
+ this.stage = stage;
44
+ this.name = 'PrivateFileError';
45
+ this.code = ioCode(detail);
46
+ this.win32Code = detail?.win32Code;
47
+ this.reason = detail?.reason;
48
+ }
49
+ }
50
+ export async function privateOperation(path, stage, operation) {
51
+ try {
52
+ return await operation();
53
+ }
54
+ catch (cause) {
55
+ if (cause instanceof PrivateFileError)
56
+ throw cause;
57
+ throw new PrivateFileError(path, stage, cause);
58
+ }
59
+ }
60
+ // Tests inject metadata into safe-parent checks, including checks deep inside
61
+ // callers that take no policy options. Production never sets this.
62
+ let safeParentOptionsForTest;
63
+ /** Tests only: policy options used by safe-parent checks that pass none. */
64
+ export function setSafeParentOptionsForTest(options) {
65
+ safeParentOptionsForTest = options;
66
+ }
67
+ /** Existing containing directories use the shared entry-integrity policy, not secret-file permissions. */
68
+ export async function verifySafeDirectory(path, options) {
69
+ path = resolve(path);
70
+ options ??= safeParentOptionsForTest;
71
+ await privateOperation(path, 'safe-parent', async () => {
72
+ const result = await (await loadBackend()).verifySafeParent(path, options);
73
+ if (result.ok === false) {
74
+ // Preserve an absent parent's ENOENT so callers can create it privately.
75
+ if (result.reason === 'io-error' || result.reason === 'wrong-kind')
76
+ await lstat(path);
77
+ throw new PrivateFileError(result.level ?? result.path, 'safe-parent', {
78
+ ...result,
79
+ code: 'ERR_PRIVATE_FILES_SECURITY',
80
+ });
81
+ }
82
+ });
83
+ }
84
+ /** Repair only the specific owned secret file supplied by a caller, never an existing directory tree. */
85
+ export async function verifyPrivate(path, kind, migrate = false) {
86
+ path = resolve(path);
87
+ if (kind === 'directory')
88
+ return verifySafeDirectory(path);
89
+ await privateOperation(path, 'verify', async () => {
90
+ const api = await loadBackend();
91
+ const result = await api.verifyOwnerOnly(path);
92
+ let repaired = false;
93
+ if (!result.ok) {
94
+ const { reason } = result.refusal;
95
+ // The result API intentionally omits native IO codes. Use the throwing
96
+ // API to preserve absence/collision details, never infer them from text.
97
+ if (reason === 'io-error')
98
+ await api.verifyPrivatePath(path, kind);
99
+ // DACL replacement repairs permissions only, never ownership or unsafe ancestry.
100
+ if (migrate &&
101
+ [
102
+ api.EXTRA_ACE,
103
+ api.NOT_PROTECTED,
104
+ api.NULL_DACL,
105
+ api.EMPTY_DACL,
106
+ ].includes(reason)) {
107
+ await verifySafeDirectory(dirname(path));
108
+ await api.securePrivatePath(path, kind);
109
+ repaired = true;
110
+ }
111
+ else {
112
+ throw Object.assign(new Error('Owner-only verification refused'), {
113
+ reason,
114
+ code: 'ERR_PRIVATE_FILES_SECURITY',
115
+ });
116
+ }
117
+ }
118
+ // Verify the expected object kind and its permissions by handle after repair.
119
+ await api.verifyPrivatePath(path, kind);
120
+ if (repaired && !migrationNotices.has(path)) {
121
+ migrationNotices.add(path);
122
+ process.emitWarning(`Private permissions tightened: ${path}. This object may have been readable before; rotate credentials if needed.`, 'PrivateFileMigration');
123
+ }
124
+ });
125
+ }
126
+ /** Create missing components privately; inspect existing ancestors without modifying them. */
127
+ export async function ensurePrivateDirectory(path, _migrate = false, options) {
128
+ path = resolve(path);
129
+ await privateOperation(path, 'directory', async () => {
130
+ const api = await loadBackend();
131
+ try {
132
+ await lstat(path);
133
+ return await verifySafeDirectory(path, options);
134
+ }
135
+ catch (error) {
136
+ if (ioCode(error) !== 'ENOENT')
137
+ throw error;
138
+ }
139
+ const parent = dirname(path);
140
+ if (parent === path)
141
+ throw Object.assign(new Error('Missing root'), { code: 'ENOENT' });
142
+ await ensurePrivateDirectory(parent, false, options);
143
+ try {
144
+ await api.createOwnerOnly(path, { kind: 'dir' });
145
+ }
146
+ catch (error) {
147
+ if (ioCode(error) !== 'EEXIST')
148
+ throw error;
149
+ }
150
+ await verifySafeDirectory(path, options);
151
+ });
152
+ }
153
+ export async function readPrivateText(path, migrate = false) {
154
+ if (!windowsPrivateFiles)
155
+ return readFile(path, 'utf8');
156
+ return (await readPrivateBuffer(path, migrate)).toString('utf8');
157
+ }
158
+ export async function readPrivateBuffer(path, migrate = false) {
159
+ if (!windowsPrivateFiles)
160
+ return readFile(path);
161
+ path = resolve(path);
162
+ if (migrate) {
163
+ await verifySafeDirectory(dirname(path));
164
+ await verifyPrivate(path, 'file', true);
165
+ }
166
+ return privateOperation(path, 'read', async () => (await loadBackend()).readOwnerOnly(path));
167
+ }
168
+ export async function createPrivateBytes(path, bytes) {
169
+ await verifySafeDirectory(dirname(resolve(path)));
170
+ await privateOperation(path, 'create', async () => {
171
+ // The backend verifies the creation handle while empty, before consuming bytes.
172
+ await (await loadBackend()).createPrivateFile(resolve(path), bytes);
173
+ });
174
+ }
175
+ export async function createPrivateDirectoryExclusive(path) {
176
+ await verifySafeDirectory(dirname(resolve(path)));
177
+ await privateOperation(path, 'directory-create', async () => {
178
+ await (await loadBackend()).createOwnerOnly(resolve(path), { kind: 'dir' });
179
+ });
180
+ }
181
+ /** Retry Windows rename sharing/access errors within a budget; they never prove lease contention. */
182
+ export async function replaceWindowsFile(from, to, options = {}) {
183
+ from = resolve(from);
184
+ to = resolve(to);
185
+ const now = options.now ?? (() => performance.now());
186
+ const start = now();
187
+ const deadline = Math.min(start + 500, options.deadline ?? Infinity);
188
+ const delays = [0, 10, 20, 40, 80, 100];
189
+ let original;
190
+ let attempts = 0;
191
+ for (const delay of delays) {
192
+ if (now() >= deadline)
193
+ break;
194
+ if (delay)
195
+ await (options.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms))))(Math.min(delay, Math.max(0, deadline - now())));
196
+ if (now() >= deadline)
197
+ break;
198
+ await options.beforeRename?.();
199
+ if (now() >= deadline)
200
+ break;
201
+ attempts++;
202
+ try {
203
+ await (options.rename ?? rename)(from, to);
204
+ return { attempts, elapsedMs: now() - start };
205
+ }
206
+ catch (error) {
207
+ original ??= error;
208
+ const detail = error;
209
+ const candidate = detail.win32Code !== undefined
210
+ ? detail.win32Code === 32 || detail.win32Code === 33
211
+ : ['EBUSY', 'EPERM', 'EACCES'].includes(detail.code ?? '') &&
212
+ !detail.reason;
213
+ if (!candidate)
214
+ break;
215
+ }
216
+ }
217
+ const error = new PrivateFileError(to, 'rename', original ??
218
+ Object.assign(new Error('Publication deadline expired'), {
219
+ code: 'ETIMEDOUT',
220
+ }));
221
+ error.attempts = attempts;
222
+ error.elapsedMs = now() - start;
223
+ throw error;
224
+ }
225
+ export async function writePrivateAtomic(path, bytes, options = {}) {
226
+ path = resolve(path);
227
+ await ensurePrivateDirectory(dirname(path));
228
+ try {
229
+ await verifyPrivate(path, 'file', true);
230
+ }
231
+ catch (error) {
232
+ if (error.code !== 'ENOENT')
233
+ throw error;
234
+ }
235
+ const stage = resolve(options.stagePath ?? `${path}.${randomUUID()}.tmp`);
236
+ if (dirname(stage) !== dirname(path))
237
+ throw new PrivateFileError(path, 'stage-directory', { code: 'EXDEV' });
238
+ let created = false;
239
+ let published = false;
240
+ let outcome = { attempts: 0, elapsedMs: 0 };
241
+ let stageName = 'stage-create';
242
+ const reportOutcome = (error) => {
243
+ try {
244
+ options.onOutcome?.({
245
+ path,
246
+ stage: error?.stage ?? 'published',
247
+ published,
248
+ fileDurable: published,
249
+ durabilityConfirmed: false,
250
+ attempts: error?.attempts ?? outcome.attempts,
251
+ elapsedMs: error?.elapsedMs ?? outcome.elapsedMs,
252
+ ...(error && { code: error.code, win32Code: error.win32Code }),
253
+ });
254
+ }
255
+ catch {
256
+ // Outcome observers cannot change whether publication succeeded.
257
+ }
258
+ };
259
+ try {
260
+ await createPrivateBytes(stage, bytes);
261
+ created = true;
262
+ stageName = 'before-rename';
263
+ outcome = await replaceWindowsFile(stage, path, options);
264
+ created = false;
265
+ published = true;
266
+ stageName = 'published-callback';
267
+ options.onRenamed?.();
268
+ stageName = 'published-verify';
269
+ await verifyPrivate(path, 'file');
270
+ // createPrivateFile flushes the file. Directory metadata durability has not
271
+ // been qualified on Windows, so never promise the POSIX directory fsync.
272
+ if (options.durable && options.durability !== 'file-only-on-windows')
273
+ throw new PrivateFileError(path, 'directory-sync-unsupported', {
274
+ code: 'ENOTSUP',
275
+ });
276
+ reportOutcome();
277
+ }
278
+ catch (cause) {
279
+ const error = cause instanceof PrivateFileError
280
+ ? cause
281
+ : new PrivateFileError(path, stageName, cause);
282
+ error.published = published;
283
+ error.fileDurable = published;
284
+ if (published)
285
+ Object.assign(error, outcome);
286
+ if (created) {
287
+ try {
288
+ await unlink(stage);
289
+ }
290
+ catch {
291
+ error.orphanPath = stage;
292
+ }
293
+ created = false;
294
+ }
295
+ reportOutcome(error);
296
+ throw error;
297
+ }
298
+ finally {
299
+ if (created)
300
+ await unlink(stage).catch(() => { });
301
+ }
302
+ }
@@ -29,5 +29,5 @@ export declare function acquireRefreshFileLock(options: {
29
29
  onContended?: () => void;
30
30
  /** Synchronous, never awaited; throws and returned promise/thenable rejections are isolated. */
31
31
  onRenewalTimer?: (event: 'scheduled' | 'cancelled') => void;
32
- onStep?: (step: 'stale-marker-stat' | 'stale-marker-claimed' | 'stale-lock-observed' | 'stale-lock-confirmed' | 'stale-lock-removed' | 'stale-lock-recreated' | 'eviction-marker-acquired' | 'renewal-owner-confirmed' | 'renewal-marker-unavailable' | 'renewal-write-fenced' | 'renewal-write-ready' | 'relinquish-read' | 'renewal-finished' | 'release-owner-confirmed') => void | Promise<void>;
32
+ onStep?: (step: 'stale-marker-stat' | 'stale-marker-claimed' | 'stale-lock-observed' | 'lock-read-ready' | 'lock-missing' | 'stale-lock-confirmed' | 'stale-lock-removed' | 'stale-lock-recreated' | 'eviction-marker-acquired' | 'renewal-owner-confirmed' | 'renewal-marker-unavailable' | 'renewal-write-fenced' | 'renewal-write-ready' | 'renewal-write-finished' | 'relinquish-read' | 'renewal-finished' | 'release-owner-confirmed') => void | Promise<void>;
33
33
  }): Promise<RefreshFileLock | null>;