relic-mcp 0.3.0 → 0.3.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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/dist/relic-mcp.js +352 -36
- package/package.json +1 -1
- package/skills/relic/SKILL.md +30 -6
- package/src/publish.ts +115 -1
- package/src/server.ts +210 -12
- package/src/state.ts +312 -33
package/src/publish.ts
CHANGED
|
@@ -21,7 +21,12 @@ import {
|
|
|
21
21
|
type RendererClass,
|
|
22
22
|
relicUrl,
|
|
23
23
|
} from '@relic/format';
|
|
24
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
loadPublishedSource,
|
|
26
|
+
resolveSourceIdentity,
|
|
27
|
+
type SourceIdentity,
|
|
28
|
+
savePublishState,
|
|
29
|
+
} from './state.ts';
|
|
25
30
|
|
|
26
31
|
/**
|
|
27
32
|
* Codes for the legs the app server is not in.
|
|
@@ -37,6 +42,7 @@ export type ClientCode =
|
|
|
37
42
|
| 'source_is_directory'
|
|
38
43
|
| 'source_not_regular_file'
|
|
39
44
|
| 'source_unreadable'
|
|
45
|
+
| 'source_already_published'
|
|
40
46
|
| 'local_size_precheck_failed'
|
|
41
47
|
| 'upload_failed'
|
|
42
48
|
| 'service_unreachable'
|
|
@@ -96,6 +102,11 @@ export interface PublishInput {
|
|
|
96
102
|
* to survive the wire.
|
|
97
103
|
*/
|
|
98
104
|
readonly ttl_days?: number | undefined;
|
|
105
|
+
/**
|
|
106
|
+
* Bypass prior-publish refusal only when a second independent URL is
|
|
107
|
+
* intentional. False and undefined both preserve the existing URL.
|
|
108
|
+
*/
|
|
109
|
+
readonly force_new?: boolean | undefined;
|
|
99
110
|
}
|
|
100
111
|
|
|
101
112
|
export interface PublishResult {
|
|
@@ -121,15 +132,117 @@ export interface PublishDeps {
|
|
|
121
132
|
readonly files: FileReader;
|
|
122
133
|
readonly fetch: typeof globalThis.fetch;
|
|
123
134
|
readonly clientName: string;
|
|
135
|
+
/** Test seam for a filesystem that does not exist outside memory. */
|
|
136
|
+
readonly identifySource?: (path: string) => Promise<SourceIdentity>;
|
|
124
137
|
/** Retries on a colliding ID, which format.md 1.4 obliges the client to do. */
|
|
125
138
|
readonly maxCollisionRetries?: number;
|
|
126
139
|
}
|
|
127
140
|
|
|
141
|
+
export interface RepublishToolCall {
|
|
142
|
+
readonly name: 'relic_republish';
|
|
143
|
+
readonly arguments: {
|
|
144
|
+
readonly relic_id: string;
|
|
145
|
+
readonly path: string;
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export interface PriorPublish {
|
|
150
|
+
readonly relic_id: string;
|
|
151
|
+
readonly version: number;
|
|
152
|
+
readonly source: SourceIdentity;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export interface PublishedSourceLookup {
|
|
156
|
+
readonly resolved_path: string;
|
|
157
|
+
readonly source: SourceIdentity;
|
|
158
|
+
readonly match?: PriorPublish | undefined;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** The complete call an agent can make without retaining an earlier session. */
|
|
162
|
+
export function republishToolCall(
|
|
163
|
+
relicId: string,
|
|
164
|
+
path: string
|
|
165
|
+
): RepublishToolCall {
|
|
166
|
+
return {
|
|
167
|
+
name: 'relic_republish',
|
|
168
|
+
arguments: { relic_id: relicId, path },
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async function lookupResolvedSource(
|
|
173
|
+
resolvedPath: string,
|
|
174
|
+
deps: PublishDeps
|
|
175
|
+
): Promise<PublishedSourceLookup> {
|
|
176
|
+
let source: SourceIdentity;
|
|
177
|
+
try {
|
|
178
|
+
source = await (deps.identifySource ?? resolveSourceIdentity)(resolvedPath);
|
|
179
|
+
} catch (error) {
|
|
180
|
+
const code = (error as NodeJS.ErrnoException).code;
|
|
181
|
+
throw new PublishError(
|
|
182
|
+
code === 'ENOENT' ? 'source_not_found' : 'source_unreadable',
|
|
183
|
+
`could not resolve source identity for ${resolvedPath}: ` +
|
|
184
|
+
(error as Error).message,
|
|
185
|
+
{ path: resolvedPath }
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
try {
|
|
190
|
+
const published = await loadPublishedSource(source);
|
|
191
|
+
return {
|
|
192
|
+
resolved_path: resolvedPath,
|
|
193
|
+
source,
|
|
194
|
+
...(published === undefined
|
|
195
|
+
? {}
|
|
196
|
+
: {
|
|
197
|
+
match: {
|
|
198
|
+
relic_id: published.relic_id,
|
|
199
|
+
version: published.state.version,
|
|
200
|
+
source: published.source,
|
|
201
|
+
},
|
|
202
|
+
}),
|
|
203
|
+
};
|
|
204
|
+
} catch (error) {
|
|
205
|
+
throw new PublishError('local_state_unreadable', (error as Error).message);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/** Look up a path in local state without reading its bytes or calling a server. */
|
|
210
|
+
export async function lookupPublishedSource(
|
|
211
|
+
path: string,
|
|
212
|
+
deps: PublishDeps
|
|
213
|
+
): Promise<PublishedSourceLookup> {
|
|
214
|
+
return lookupResolvedSource(deps.files.resolve(path), deps);
|
|
215
|
+
}
|
|
216
|
+
|
|
128
217
|
export async function publish(
|
|
129
218
|
input: PublishInput,
|
|
130
219
|
deps: PublishDeps
|
|
131
220
|
): Promise<PublishResult> {
|
|
132
221
|
const source = await readSource(input.path, deps.files);
|
|
222
|
+
const sourceLookup = await lookupResolvedSource(source.resolvedPath, deps);
|
|
223
|
+
if (sourceLookup.match !== undefined && input.force_new !== true) {
|
|
224
|
+
const match = sourceLookup.match;
|
|
225
|
+
const republishCall = republishToolCall(
|
|
226
|
+
match.relic_id,
|
|
227
|
+
source.resolvedPath
|
|
228
|
+
);
|
|
229
|
+
const cost = 'a second URL that nobody holding the first one will ever see';
|
|
230
|
+
throw new PublishError(
|
|
231
|
+
'source_already_published',
|
|
232
|
+
`${match.source.description} is already version ${match.version} of ` +
|
|
233
|
+
`relic ${match.relic_id}. Publishing it as new would cost ${cost}. ` +
|
|
234
|
+
`Call relic_republish(${JSON.stringify(republishCall.arguments)}) ` +
|
|
235
|
+
'instead. Set force_new to true only when you intend a separate relic.',
|
|
236
|
+
{
|
|
237
|
+
relic_id: match.relic_id,
|
|
238
|
+
version: match.version,
|
|
239
|
+
source_identity: match.source.identity,
|
|
240
|
+
source_description: match.source.description,
|
|
241
|
+
cost,
|
|
242
|
+
republish_call: republishCall,
|
|
243
|
+
}
|
|
244
|
+
);
|
|
245
|
+
}
|
|
133
246
|
|
|
134
247
|
// 1. Challenge. This returns the cap before a grant is requested, so the
|
|
135
248
|
// precheck below uses a number that came from the server moments ago
|
|
@@ -243,6 +356,7 @@ export async function publish(
|
|
|
243
356
|
key: encodeKey(key),
|
|
244
357
|
publish_token: publishToken,
|
|
245
358
|
version: 1,
|
|
359
|
+
source: sourceLookup.source,
|
|
246
360
|
});
|
|
247
361
|
} catch (error) {
|
|
248
362
|
// The relic itself is live and the URL works, so both are handed over
|
package/src/server.ts
CHANGED
|
@@ -33,9 +33,11 @@ import {
|
|
|
33
33
|
unsupportedVersionError,
|
|
34
34
|
} from './protocol.ts';
|
|
35
35
|
import {
|
|
36
|
+
lookupPublishedSource,
|
|
36
37
|
type PublishDeps,
|
|
37
38
|
PublishError,
|
|
38
39
|
publish,
|
|
40
|
+
republishToolCall,
|
|
39
41
|
ServerRefusal,
|
|
40
42
|
} from './publish.ts';
|
|
41
43
|
import { republish } from './republish.ts';
|
|
@@ -86,6 +88,15 @@ export const DESCRIBE_TOOL_NAME = 'relic_describe_client';
|
|
|
86
88
|
*/
|
|
87
89
|
export const REPUBLISH_TOOL_NAME = 'relic_republish';
|
|
88
90
|
|
|
91
|
+
/**
|
|
92
|
+
* Source lookup is a separate read-only tool.
|
|
93
|
+
*
|
|
94
|
+
* An agent needs the id before it chooses publish or republish, and a lookup
|
|
95
|
+
* hidden inside either write tool would only be observable after choosing the
|
|
96
|
+
* wrong one. This call reads local state and never contacts the service.
|
|
97
|
+
*/
|
|
98
|
+
export const LOOKUP_TOOL_NAME = 'relic_lookup_source';
|
|
99
|
+
|
|
89
100
|
/**
|
|
90
101
|
* The ceiling on a publisher-supplied lifetime, matching the grant
|
|
91
102
|
* contract's `maxTtlDays`. Refusing here keeps a typo like 36500 from
|
|
@@ -98,9 +109,12 @@ export const TOOL_DEFINITION = {
|
|
|
98
109
|
name: TOOL_NAME,
|
|
99
110
|
title: 'Publish a relic',
|
|
100
111
|
description:
|
|
101
|
-
'Encrypt a file on this machine and publish it as a relic, returning
|
|
102
|
-
'shareable URL.
|
|
103
|
-
'
|
|
112
|
+
'Encrypt a file on this machine and publish it as a new relic, returning ' +
|
|
113
|
+
'a shareable URL. Publishing an update this way costs a second URL that ' +
|
|
114
|
+
'nobody holding the first one will ever see; use relic_republish instead ' +
|
|
115
|
+
'so the existing URL keeps working. The encryption key is generated ' +
|
|
116
|
+
'locally and never sent to the service. Takes a filesystem path, never ' +
|
|
117
|
+
'inline content.',
|
|
104
118
|
inputSchema: {
|
|
105
119
|
type: 'object',
|
|
106
120
|
properties: {
|
|
@@ -123,6 +137,14 @@ export const TOOL_DEFINITION = {
|
|
|
123
137
|
'relic is kept until it is deleted. Shorter is better for ' +
|
|
124
138
|
'sensitive content.',
|
|
125
139
|
},
|
|
140
|
+
force_new: {
|
|
141
|
+
type: 'boolean',
|
|
142
|
+
default: false,
|
|
143
|
+
description:
|
|
144
|
+
'Optional. Publish a deliberately separate relic even when this ' +
|
|
145
|
+
'machine already published the same source. Defaults to false. Use ' +
|
|
146
|
+
'only when you want two independent URLs for one file.',
|
|
147
|
+
},
|
|
126
148
|
},
|
|
127
149
|
required: ['path'],
|
|
128
150
|
additionalProperties: false,
|
|
@@ -228,6 +250,64 @@ export const REPUBLISH_TOOL_DEFINITION = {
|
|
|
228
250
|
},
|
|
229
251
|
} as const;
|
|
230
252
|
|
|
253
|
+
export const LOOKUP_TOOL_DEFINITION = {
|
|
254
|
+
name: LOOKUP_TOOL_NAME,
|
|
255
|
+
title: 'Look up a published source',
|
|
256
|
+
description:
|
|
257
|
+
'Look up whether this machine already published a file and return the ' +
|
|
258
|
+
'relic id needed by relic_republish. Reads local publish state only and ' +
|
|
259
|
+
'never calls the service.',
|
|
260
|
+
inputSchema: {
|
|
261
|
+
type: 'object',
|
|
262
|
+
properties: {
|
|
263
|
+
path: {
|
|
264
|
+
type: 'string',
|
|
265
|
+
description: 'Filesystem path to the source to look up.',
|
|
266
|
+
},
|
|
267
|
+
},
|
|
268
|
+
required: ['path'],
|
|
269
|
+
additionalProperties: false,
|
|
270
|
+
},
|
|
271
|
+
outputSchema: {
|
|
272
|
+
type: 'object',
|
|
273
|
+
properties: {
|
|
274
|
+
found: { type: 'boolean' },
|
|
275
|
+
relic_id: { type: ['string', 'null'] },
|
|
276
|
+
version: { type: ['integer', 'null'], minimum: 1 },
|
|
277
|
+
resolved_path: { type: 'string' },
|
|
278
|
+
source_identity: { type: 'string' },
|
|
279
|
+
source_description: { type: 'string' },
|
|
280
|
+
republish_call: {
|
|
281
|
+
type: ['object', 'null'],
|
|
282
|
+
properties: {
|
|
283
|
+
name: { type: 'string', const: REPUBLISH_TOOL_NAME },
|
|
284
|
+
arguments: {
|
|
285
|
+
type: 'object',
|
|
286
|
+
properties: {
|
|
287
|
+
relic_id: { type: 'string' },
|
|
288
|
+
path: { type: 'string' },
|
|
289
|
+
},
|
|
290
|
+
required: ['relic_id', 'path'],
|
|
291
|
+
additionalProperties: false,
|
|
292
|
+
},
|
|
293
|
+
},
|
|
294
|
+
required: ['name', 'arguments'],
|
|
295
|
+
additionalProperties: false,
|
|
296
|
+
},
|
|
297
|
+
},
|
|
298
|
+
required: [
|
|
299
|
+
'found',
|
|
300
|
+
'relic_id',
|
|
301
|
+
'version',
|
|
302
|
+
'resolved_path',
|
|
303
|
+
'source_identity',
|
|
304
|
+
'source_description',
|
|
305
|
+
'republish_call',
|
|
306
|
+
],
|
|
307
|
+
additionalProperties: false,
|
|
308
|
+
},
|
|
309
|
+
} as const;
|
|
310
|
+
|
|
231
311
|
export const DESCRIBE_TOOL_DEFINITION = {
|
|
232
312
|
name: DESCRIBE_TOOL_NAME,
|
|
233
313
|
title: 'Describe the Relic client',
|
|
@@ -262,6 +342,44 @@ export const SERVER_INFO = {
|
|
|
262
342
|
|
|
263
343
|
export const CAPABILITIES = { tools: {} } as const;
|
|
264
344
|
|
|
345
|
+
/**
|
|
346
|
+
* Server-level guidance, returned on the handshake so a client can put it in
|
|
347
|
+
* the model's context before any tool is called.
|
|
348
|
+
*
|
|
349
|
+
* The plugin ships a skill with the same facts, but a skill only reaches
|
|
350
|
+
* Claude Code, and only when somebody installs the plugin rather than wiring
|
|
351
|
+
* this server directly. Every other client saw tool descriptions and nothing
|
|
352
|
+
* else, which left five things an agent cannot read off a schema.
|
|
353
|
+
*
|
|
354
|
+
* Item five is the reason this exists at all rather than living only in the
|
|
355
|
+
* publish result. The result is returned after the file is written, which is
|
|
356
|
+
* too late for an agent that already linked a stylesheet from a CDN. This
|
|
357
|
+
* lands before generation, which is the only moment the advice can be taken.
|
|
358
|
+
*
|
|
359
|
+
* It costs context on every session, so it stays short and it stays true.
|
|
360
|
+
* Anything that needs a paragraph belongs in the skill or the disclosure.
|
|
361
|
+
*/
|
|
362
|
+
export const INSTRUCTIONS = `Relic encrypts a file on this machine and uploads \
|
|
363
|
+
only ciphertext. The key lives in the URL fragment, which browsers never send \
|
|
364
|
+
to a server.
|
|
365
|
+
|
|
366
|
+
Five things that change how you should act:
|
|
367
|
+
|
|
368
|
+
1. The link is the credential. Anyone holding it, fragment included, can read \
|
|
369
|
+
the file. Do not paste it into a tracker, a log, or a public channel.
|
|
370
|
+
2. Publishing puts the key in this transcript. That is structural rather than \
|
|
371
|
+
a defect, and worth saying plainly when you hand the link over.
|
|
372
|
+
3. If a source was published before, use relic_republish so its URL keeps \
|
|
373
|
+
working. Publishing it as new costs a second URL that nobody holding the first \
|
|
374
|
+
one will ever see. relic_publish refuses by default, relic_lookup_source \
|
|
375
|
+
recovers the id, and force_new is only for a deliberate second link.
|
|
376
|
+
4. A relic can be republished only from the machine that published it, which \
|
|
377
|
+
is where its key and publish token are stored. Anywhere else it refuses, and \
|
|
378
|
+
no retry changes that.
|
|
379
|
+
5. Rendered HTML and JSX run in an isolated frame with no network access. \
|
|
380
|
+
Inline the styles, scripts, fonts, and images a page needs, because a CDN \
|
|
381
|
+
reference renders as nothing. Decide that before you write the file.`;
|
|
382
|
+
|
|
265
383
|
/**
|
|
266
384
|
* Handle one JSON-RPC message.
|
|
267
385
|
*
|
|
@@ -297,6 +415,7 @@ export async function handleMessage(
|
|
|
297
415
|
protocolVersions: [...SUPPORTED_PROTOCOL_VERSIONS],
|
|
298
416
|
capabilities: CAPABILITIES,
|
|
299
417
|
serverInfo: SERVER_INFO,
|
|
418
|
+
instructions: INSTRUCTIONS,
|
|
300
419
|
},
|
|
301
420
|
};
|
|
302
421
|
|
|
@@ -312,6 +431,7 @@ export async function handleMessage(
|
|
|
312
431
|
protocolVersion: isSupportedVersion(asked) ? asked : PROTOCOL_VERSION,
|
|
313
432
|
capabilities: CAPABILITIES,
|
|
314
433
|
serverInfo: SERVER_INFO,
|
|
434
|
+
instructions: INSTRUCTIONS,
|
|
315
435
|
},
|
|
316
436
|
};
|
|
317
437
|
}
|
|
@@ -326,6 +446,7 @@ export async function handleMessage(
|
|
|
326
446
|
result: {
|
|
327
447
|
tools: [
|
|
328
448
|
TOOL_DEFINITION,
|
|
449
|
+
LOOKUP_TOOL_DEFINITION,
|
|
329
450
|
REPUBLISH_TOOL_DEFINITION,
|
|
330
451
|
DESCRIBE_TOOL_DEFINITION,
|
|
331
452
|
],
|
|
@@ -382,8 +503,9 @@ async function callTool(
|
|
|
382
503
|
plaintext_transmitted_to_service: false,
|
|
383
504
|
ciphertext_destination: 'object storage, via a signed URL',
|
|
384
505
|
local_publish_state:
|
|
385
|
-
'relic id, key, and publish token per relic,
|
|
386
|
-
'the user config directory;
|
|
506
|
+
'relic id, source identity, key, and publish token per relic, ' +
|
|
507
|
+
'written 0600 under the user config directory; key and token ' +
|
|
508
|
+
'are never printed or sent',
|
|
387
509
|
service_origin: deps.serviceOrigin,
|
|
388
510
|
},
|
|
389
511
|
isError: false,
|
|
@@ -391,6 +513,10 @@ async function callTool(
|
|
|
391
513
|
};
|
|
392
514
|
}
|
|
393
515
|
|
|
516
|
+
if (params['name'] === LOOKUP_TOOL_NAME) {
|
|
517
|
+
return callLookup(id, params, deps);
|
|
518
|
+
}
|
|
519
|
+
|
|
394
520
|
if (params['name'] === REPUBLISH_TOOL_NAME) {
|
|
395
521
|
return callRepublish(id, params, deps);
|
|
396
522
|
}
|
|
@@ -426,9 +552,23 @@ async function callTool(
|
|
|
426
552
|
);
|
|
427
553
|
}
|
|
428
554
|
|
|
555
|
+
const forceNew = args['force_new'];
|
|
556
|
+
if (forceNew !== undefined && typeof forceNew !== 'boolean') {
|
|
557
|
+
return errorResponse(
|
|
558
|
+
id,
|
|
559
|
+
ERROR_CODES.invalidParams,
|
|
560
|
+
'`force_new` must be a boolean or omitted'
|
|
561
|
+
);
|
|
562
|
+
}
|
|
563
|
+
|
|
429
564
|
try {
|
|
430
565
|
const result = await publish(
|
|
431
|
-
{
|
|
566
|
+
{
|
|
567
|
+
path,
|
|
568
|
+
filename,
|
|
569
|
+
ttl_days: ttlDays.days,
|
|
570
|
+
force_new: forceNew === true,
|
|
571
|
+
},
|
|
432
572
|
deps
|
|
433
573
|
);
|
|
434
574
|
return {
|
|
@@ -471,6 +611,63 @@ async function callTool(
|
|
|
471
611
|
}
|
|
472
612
|
}
|
|
473
613
|
|
|
614
|
+
async function callLookup(
|
|
615
|
+
id: string | number | null,
|
|
616
|
+
params: Record<string, unknown>,
|
|
617
|
+
deps: PublishDeps
|
|
618
|
+
): Promise<JsonRpcResponse> {
|
|
619
|
+
const args = (params['arguments'] ?? {}) as Record<string, unknown>;
|
|
620
|
+
const path = args['path'];
|
|
621
|
+
if (typeof path !== 'string' || path.length === 0) {
|
|
622
|
+
return errorResponse(
|
|
623
|
+
id,
|
|
624
|
+
ERROR_CODES.invalidParams,
|
|
625
|
+
'`path` is required and must be a string'
|
|
626
|
+
);
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
try {
|
|
630
|
+
const lookup = await lookupPublishedSource(path, deps);
|
|
631
|
+
const match = lookup.match;
|
|
632
|
+
const republishCall =
|
|
633
|
+
match === undefined
|
|
634
|
+
? null
|
|
635
|
+
: republishToolCall(match.relic_id, lookup.resolved_path);
|
|
636
|
+
const structuredContent = {
|
|
637
|
+
found: match !== undefined,
|
|
638
|
+
relic_id: match?.relic_id ?? null,
|
|
639
|
+
version: match?.version ?? null,
|
|
640
|
+
resolved_path: lookup.resolved_path,
|
|
641
|
+
source_identity: lookup.source.identity,
|
|
642
|
+
source_description: lookup.source.description,
|
|
643
|
+
republish_call: republishCall,
|
|
644
|
+
};
|
|
645
|
+
return {
|
|
646
|
+
jsonrpc: '2.0',
|
|
647
|
+
id,
|
|
648
|
+
result: {
|
|
649
|
+
content: [
|
|
650
|
+
{
|
|
651
|
+
type: 'text',
|
|
652
|
+
text:
|
|
653
|
+
match === undefined
|
|
654
|
+
? `No prior relic is recorded for ${lookup.source.description}.`
|
|
655
|
+
: `Found ${lookup.source.description} as version ` +
|
|
656
|
+
`${match.version} of relic ${match.relic_id}.\n` +
|
|
657
|
+
`Call relic_republish(${JSON.stringify(
|
|
658
|
+
republishCall?.arguments
|
|
659
|
+
)}).`,
|
|
660
|
+
},
|
|
661
|
+
],
|
|
662
|
+
structuredContent,
|
|
663
|
+
isError: false,
|
|
664
|
+
},
|
|
665
|
+
};
|
|
666
|
+
} catch (error) {
|
|
667
|
+
return { jsonrpc: '2.0', id, result: toolError(error) };
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
|
|
474
671
|
async function callRepublish(
|
|
475
672
|
id: string | number | null,
|
|
476
673
|
params: Record<string, unknown>,
|
|
@@ -655,13 +852,14 @@ What the service operator can see: that a relic exists, roughly how big it is,
|
|
|
655
852
|
what coarse class it was declared as, the publishing IP, and when it was
|
|
656
853
|
fetched. Never the contents, and never the key.
|
|
657
854
|
|
|
658
|
-
What this keeps on disk: for each relic you publish, its id,
|
|
659
|
-
publish token, in a 0600 file under your user config directory.
|
|
660
|
-
|
|
855
|
+
What this keeps on disk: for each relic you publish, its id, source identity,
|
|
856
|
+
key, and publish token, in a 0600 file under your user config directory. The
|
|
857
|
+
source index lets a fresh session find the id for relic_republish. The key and
|
|
858
|
+
token let that republish keep the same URL, and they are why republishing works
|
|
661
859
|
only on the machine that published. The token's SHA-256 is the only copy the
|
|
662
|
-
service ever holds, and neither secret is ever printed or logged. Deleting
|
|
663
|
-
|
|
664
|
-
|
|
860
|
+
service ever holds, and neither secret is ever printed or logged. Deleting the
|
|
861
|
+
file changes nothing for existing links; it only ends this machine's ability
|
|
862
|
+
to update those relics.
|
|
665
863
|
|
|
666
864
|
What this does NOT protect against: the key is returned to your agent in the
|
|
667
865
|
URL, so it enters the model's context and your session transcript. That is
|