synomem 0.9.0 → 0.9.2
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/CHANGELOG.md +17 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +5 -5
- package/dist/cli.js.map +1 -1
- package/dist/credentials.d.ts +13 -4
- package/dist/credentials.d.ts.map +1 -1
- package/dist/credentials.js +121 -19
- package/dist/credentials.js.map +1 -1
- package/dist/oauth.d.ts +1 -0
- package/dist/oauth.d.ts.map +1 -1
- package/dist/oauth.js +18 -1
- package/dist/oauth.js.map +1 -1
- package/dist/profiles.d.ts.map +1 -1
- package/dist/profiles.js +18 -4
- package/dist/profiles.js.map +1 -1
- package/dist/resolvers.d.ts +11 -4
- package/dist/resolvers.d.ts.map +1 -1
- package/dist/resolvers.js +91 -10
- package/dist/resolvers.js.map +1 -1
- package/package.json +1 -1
- package/skills/synomem/SKILL.md +15 -0
- package/src/cli.ts +5 -10
- package/src/credentials.ts +126 -19
- package/src/oauth.ts +20 -1
- package/src/profiles.ts +21 -4
- package/src/resolvers.ts +96 -10
package/src/resolvers.ts
CHANGED
|
@@ -8,7 +8,18 @@
|
|
|
8
8
|
* each other's attribution. The resolver narrows (pins a context, restricts to a
|
|
9
9
|
* preset); the server alone authorizes.
|
|
10
10
|
*/
|
|
11
|
-
import {
|
|
11
|
+
import { randomBytes } from 'node:crypto';
|
|
12
|
+
import {
|
|
13
|
+
closeSync,
|
|
14
|
+
existsSync,
|
|
15
|
+
openSync,
|
|
16
|
+
readFileSync,
|
|
17
|
+
rmSync,
|
|
18
|
+
statSync,
|
|
19
|
+
writeSync,
|
|
20
|
+
} from 'node:fs';
|
|
21
|
+
import { join } from 'node:path';
|
|
22
|
+
import { atomicWriteFile } from './fs-utils.js';
|
|
12
23
|
import { SynomemClient } from './client.js';
|
|
13
24
|
import { describeIdentity, discoverContexts } from './discover.js';
|
|
14
25
|
import { SynomemError } from './errors.js';
|
|
@@ -68,15 +79,90 @@ function required(): SynomemError {
|
|
|
68
79
|
}
|
|
69
80
|
|
|
70
81
|
/**
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
82
|
+
* The stable local context id for one actor in one store, from the store's own
|
|
83
|
+
* context registry (`<home>/contexts.json`).
|
|
84
|
+
*
|
|
85
|
+
* The id is cryptographically random (128 bits) and minted the first time the
|
|
86
|
+
* actor is addressed, never derived from a name or from the canonical tuple:
|
|
87
|
+
* an id reveals nothing about its target and cannot be guessed. It survives
|
|
88
|
+
* renames and moving the store (the registry moves with it), and an unrelated
|
|
89
|
+
* store with a same-named agent has its own registry and so its own id.
|
|
90
|
+
* Minting is serialized across processes so two harnesses opening the store
|
|
91
|
+
* at once agree on one id.
|
|
74
92
|
*/
|
|
75
|
-
export function localContextId(
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
93
|
+
export function localContextId(home: string, actor: ActorIdentity): string {
|
|
94
|
+
const path = join(home, 'contexts.json');
|
|
95
|
+
const key = `${actor.kind}:${actor.id}`;
|
|
96
|
+
const existing = readRegistry(path).contexts[key];
|
|
97
|
+
if (existing) return existing;
|
|
98
|
+
return withRegistryLock(home, () => {
|
|
99
|
+
const registry = readRegistry(path);
|
|
100
|
+
const current = registry.contexts[key];
|
|
101
|
+
if (current) return current;
|
|
102
|
+
const id = `lctx_${randomBytes(16).toString('hex')}`;
|
|
103
|
+
registry.contexts[key] = id;
|
|
104
|
+
atomicWriteFile(path, `${JSON.stringify(registry, null, 2)}\n`, 0o600);
|
|
105
|
+
return id;
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
interface ContextRegistry {
|
|
110
|
+
version: 1;
|
|
111
|
+
contexts: Record<string, string>;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function readRegistry(path: string): ContextRegistry {
|
|
115
|
+
if (!existsSync(path)) return { version: 1, contexts: {} };
|
|
116
|
+
try {
|
|
117
|
+
const parsed = JSON.parse(readFileSync(path, 'utf8')) as Partial<ContextRegistry>;
|
|
118
|
+
if (parsed.version === 1 && parsed.contexts && typeof parsed.contexts === 'object') {
|
|
119
|
+
return { version: 1, contexts: { ...parsed.contexts } };
|
|
120
|
+
}
|
|
121
|
+
} catch {
|
|
122
|
+
/* fall through */
|
|
123
|
+
}
|
|
124
|
+
throw new SynomemError('CONFIG_INVALID', `${path} is not a valid Synomem context registry.`);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** A short synchronous O_EXCL lock; a dead or old owner is broken. */
|
|
128
|
+
function withRegistryLock<T>(home: string, operation: () => T): T {
|
|
129
|
+
const lock = join(home, 'contexts.json.lock');
|
|
130
|
+
const deadline = Date.now() + 10_000;
|
|
131
|
+
const sleeper = new Int32Array(new SharedArrayBuffer(4));
|
|
132
|
+
for (;;) {
|
|
133
|
+
try {
|
|
134
|
+
const descriptor = openSync(lock, 'wx', 0o600);
|
|
135
|
+
writeSync(descriptor, `${process.pid}\n`);
|
|
136
|
+
closeSync(descriptor);
|
|
137
|
+
break;
|
|
138
|
+
} catch (error) {
|
|
139
|
+
if ((error as NodeJS.ErrnoException).code !== 'EEXIST') throw error;
|
|
140
|
+
try {
|
|
141
|
+
const pid = Number(readFileSync(lock, 'utf8').trim());
|
|
142
|
+
const old = Date.now() - statSync(lock).mtimeMs > 5_000;
|
|
143
|
+
let alive = Number.isSafeInteger(pid) && pid > 0;
|
|
144
|
+
if (alive) {
|
|
145
|
+
try {
|
|
146
|
+
process.kill(pid, 0);
|
|
147
|
+
} catch (probe) {
|
|
148
|
+
alive = (probe as NodeJS.ErrnoException).code === 'EPERM';
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
if (!alive || old) rmSync(lock, { force: true });
|
|
152
|
+
} catch {
|
|
153
|
+
/* released meanwhile */
|
|
154
|
+
}
|
|
155
|
+
if (Date.now() > deadline) {
|
|
156
|
+
throw new SynomemError('DATABASE_BUSY', 'The local context registry is locked. Try again.');
|
|
157
|
+
}
|
|
158
|
+
Atomics.wait(sleeper, 0, 0, 20);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
try {
|
|
162
|
+
return operation();
|
|
163
|
+
} finally {
|
|
164
|
+
rmSync(lock, { force: true });
|
|
165
|
+
}
|
|
80
166
|
}
|
|
81
167
|
|
|
82
168
|
/**
|
|
@@ -240,7 +326,7 @@ export function createLocalResolver(
|
|
|
240
326
|
return {
|
|
241
327
|
service: client,
|
|
242
328
|
context: {
|
|
243
|
-
contextId: localContextId(
|
|
329
|
+
contextId: localContextId(options.home, actor),
|
|
244
330
|
organizationId: null,
|
|
245
331
|
workspaceId: capabilities.binding.workspaceId,
|
|
246
332
|
actor,
|