@sentropic/artifacts 0.1.0 → 0.1.1
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/README.md +15 -6
- package/dist/content/grants.d.ts +7 -9
- package/dist/content/grants.js +13 -4
- package/dist/content/handler.js +3 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# @sentropic/artifacts — generic artifact contracts (L-5a)
|
|
2
2
|
|
|
3
3
|
Generic, host-independent artifact domain state for the Sentropic artifacts
|
|
4
|
-
provider (`sentropic.artifacts`, spec-v8 §9)
|
|
5
|
-
|
|
4
|
+
provider (`sentropic.artifacts`, spec-v8 §9), covering L-5a–5e. Version
|
|
5
|
+
`0.1.1` fixes activation ticket release and channel binding (FL-ACT).
|
|
6
6
|
|
|
7
7
|
## What's here (L-5a)
|
|
8
8
|
|
|
@@ -51,7 +51,8 @@ No Immo defaults, URLs or bindings in generic source.
|
|
|
51
51
|
## What's here (L-5d–5e)
|
|
52
52
|
|
|
53
53
|
- `src/content/grants.ts` — 55s HMAC content grants (60s cutoff budget) and
|
|
54
|
-
single-use
|
|
54
|
+
single-use activation tickets bound to subject, artifact, exact release
|
|
55
|
+
and channel (`stable` or `pinned`); secrets and clocks are injected.
|
|
55
56
|
- `src/content/routing.ts` — stable (`<artifactId>.<base>`) and pinned
|
|
56
57
|
(`<releaseId>.pinned.<base>`) single-UUID origins; safe path resolution.
|
|
57
58
|
- `src/content/headers.ts` — no-store/nosniff/frame-denial/CSP headers and
|
|
@@ -68,11 +69,19 @@ No Immo defaults, URLs or bindings in generic source.
|
|
|
68
69
|
`@sentropic/mcp-platform` kernel registry definition (the platform is
|
|
69
70
|
an optional peer, required only by the `./mcp` subpath).
|
|
70
71
|
|
|
72
|
+
Ticket issuance and redemption both require `subjectId`, `artifactId`,
|
|
73
|
+
`releaseId` and `channel`. The handler checks the release resolved from the
|
|
74
|
+
requested origin and its channel before minting a grant. A stable ticket
|
|
75
|
+
issued for r1 cannot activate r2 after republish, and a stable ticket cannot
|
|
76
|
+
activate a pinned origin (or vice versa). Mismatches return 401 without a
|
|
77
|
+
grant cookie and preserve the ticket for a matching request. Tickets expire
|
|
78
|
+
at `expiresAt` (120 seconds by default) and can be redeemed only once.
|
|
79
|
+
|
|
71
80
|
## First publish (bootstrap)
|
|
72
81
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
82
|
+
The steady-state OIDC job requires an npm trusted publisher. For the first
|
|
83
|
+
publication, use the current package version and this bootstrap sequence
|
|
84
|
+
(owner-run, never automated here):
|
|
76
85
|
|
|
77
86
|
1. Trigger the `bootstrap-publish` workflow manually with
|
|
78
87
|
`bootstrap_publish_target=artifacts` (runs
|
package/dist/content/grants.d.ts
CHANGED
|
@@ -24,11 +24,12 @@ export declare function createHmacGrantSigner(options: {
|
|
|
24
24
|
clock?: () => number;
|
|
25
25
|
ttlSeconds?: number;
|
|
26
26
|
}): GrantSigner;
|
|
27
|
-
export interface
|
|
27
|
+
export interface TicketBinding extends GrantIssueInput {
|
|
28
|
+
releaseId: string;
|
|
29
|
+
channel: 'stable' | 'pinned';
|
|
30
|
+
}
|
|
31
|
+
export interface TicketRecord extends TicketBinding {
|
|
28
32
|
ticketId: string;
|
|
29
|
-
subjectId: string;
|
|
30
|
-
artifactId: string;
|
|
31
|
-
releaseId?: string;
|
|
32
33
|
expiresAt: number;
|
|
33
34
|
}
|
|
34
35
|
export interface TicketStore {
|
|
@@ -41,14 +42,11 @@ export interface TicketStore {
|
|
|
41
42
|
/** In-memory TicketStore double for unit tests. */
|
|
42
43
|
export declare function createMemoryTicketStore(): TicketStore;
|
|
43
44
|
export interface TicketService {
|
|
44
|
-
issue(input:
|
|
45
|
+
issue(input: TicketBinding): Promise<{
|
|
45
46
|
ticketId: string;
|
|
46
47
|
expiresAt: number;
|
|
47
48
|
}>;
|
|
48
|
-
redeem(ticketId: string, expected:
|
|
49
|
-
subjectId: string;
|
|
50
|
-
artifactId: string;
|
|
51
|
-
}): Promise<TicketRecord>;
|
|
49
|
+
redeem(ticketId: string, expected: TicketBinding): Promise<TicketRecord>;
|
|
52
50
|
}
|
|
53
51
|
/** Single-use bound tickets; mismatched attempts do not consume the ticket. */
|
|
54
52
|
export declare function createTicketService(options: {
|
package/dist/content/grants.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* host-injected secret, bound to subject + artifact (+ pinned release),
|
|
8
8
|
* and verified fail-closed (signature, shape, lifetime cap, expiry,
|
|
9
9
|
* future-issued beyond the skew budget). Activation tickets are
|
|
10
|
-
* single-use and bound
|
|
10
|
+
* single-use and bound to an exact release and channel; ticket IDs come from an injected
|
|
11
11
|
* port and MUST be unguessable (default: crypto UUIDs). A grant never
|
|
12
12
|
* replaces the per-request ACL recheck in the content handler.
|
|
13
13
|
*/
|
|
@@ -101,15 +101,23 @@ export function createTicketService(options) {
|
|
|
101
101
|
const clock = options.clock ?? (() => Date.now());
|
|
102
102
|
const ids = options.ids ?? (() => crypto.randomUUID());
|
|
103
103
|
const ttlMs = (options.ttlSeconds ?? CONTENT_TICKET_TTL_SECONDS) * 1000;
|
|
104
|
+
if (!Number.isFinite(ttlMs) || ttlMs <= 0)
|
|
105
|
+
throw new Error('invalid ticket: lifetime');
|
|
104
106
|
return {
|
|
105
107
|
async issue(input) {
|
|
108
|
+
if (typeof input.subjectId !== 'string' || input.subjectId.length === 0 ||
|
|
109
|
+
typeof input.artifactId !== 'string' || input.artifactId.length === 0 ||
|
|
110
|
+
typeof input.releaseId !== 'string' || input.releaseId.length === 0 ||
|
|
111
|
+
(input.channel !== 'stable' && input.channel !== 'pinned'))
|
|
112
|
+
throw new Error('invalid ticket: binding');
|
|
106
113
|
const ticketId = ids();
|
|
107
114
|
const expiresAt = clock() + ttlMs;
|
|
108
115
|
await options.store.put({
|
|
109
116
|
ticketId,
|
|
110
117
|
subjectId: input.subjectId,
|
|
111
118
|
artifactId: input.artifactId,
|
|
112
|
-
|
|
119
|
+
releaseId: input.releaseId,
|
|
120
|
+
channel: input.channel,
|
|
113
121
|
expiresAt,
|
|
114
122
|
});
|
|
115
123
|
return { ticketId, expiresAt };
|
|
@@ -118,11 +126,12 @@ export function createTicketService(options) {
|
|
|
118
126
|
const record = await options.store.peek(ticketId);
|
|
119
127
|
if (record === undefined)
|
|
120
128
|
throw new Error('invalid ticket');
|
|
121
|
-
if (clock()
|
|
129
|
+
if (clock() >= record.expiresAt) {
|
|
122
130
|
await options.store.remove(ticketId);
|
|
123
131
|
throw new Error('ticket expired');
|
|
124
132
|
}
|
|
125
|
-
if (record.subjectId !== expected.subjectId || record.artifactId !== expected.artifactId
|
|
133
|
+
if (record.subjectId !== expected.subjectId || record.artifactId !== expected.artifactId ||
|
|
134
|
+
record.releaseId !== expected.releaseId || record.channel !== expected.channel) {
|
|
126
135
|
throw new Error('invalid ticket: binding mismatch');
|
|
127
136
|
}
|
|
128
137
|
const taken = await options.store.take(ticketId);
|
package/dist/content/handler.js
CHANGED
|
@@ -80,7 +80,9 @@ export function createContentHandler(deps) {
|
|
|
80
80
|
if (request.actorId === undefined || request.actorId.length === 0)
|
|
81
81
|
return fail(401);
|
|
82
82
|
try {
|
|
83
|
-
await deps.tickets.redeem(request.ticket, {
|
|
83
|
+
await deps.tickets.redeem(request.ticket, {
|
|
84
|
+
subjectId: request.actorId, artifactId, releaseId: release.releaseId, channel: origin.kind,
|
|
85
|
+
});
|
|
84
86
|
}
|
|
85
87
|
catch {
|
|
86
88
|
return fail(401);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sentropic/artifacts",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Generic configurable artifacts MCP and artifact service: StorageScope/Artifact/Binding/Release model, canonical manifests, object authorization, idempotent intents (spec-v8 §9).
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Generic configurable artifacts MCP and artifact service: StorageScope/Artifact/Binding/Release model, canonical manifests, object authorization, idempotent intents (spec-v8 §9).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": {
|