@sentropic/artifacts 0.1.4 → 0.2.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 +47 -0
- package/dist/adapters/postgres-commit.d.ts +15 -0
- package/dist/adapters/postgres-commit.js +80 -0
- package/dist/adapters/postgres.d.ts +5 -2
- package/dist/adapters/postgres.js +39 -0
- package/dist/http.d.ts +43 -30
- package/dist/http.js +158 -507
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/mcp-core.d.ts +32 -39
- package/dist/mcp-core.js +83 -244
- package/dist/mcp.js +5 -5
- package/dist/profile/contracts.d.ts +67 -0
- package/dist/profile/contracts.js +31 -0
- package/dist/profile/index.d.ts +6 -0
- package/dist/profile/index.js +117 -0
- package/dist/profile/json.d.ts +10 -0
- package/dist/profile/json.js +76 -0
- package/dist/profile/radar.d.ts +3 -0
- package/dist/profile/radar.js +21 -0
- package/dist/profile/schemas.d.ts +6 -0
- package/dist/profile/schemas.js +76 -0
- package/dist/profile/validation.d.ts +5 -0
- package/dist/profile/validation.js +104 -0
- package/dist/profile/validators.d.ts +3 -0
- package/dist/profile/validators.js +51 -0
- package/dist/server/authorized-replay.d.ts +33 -0
- package/dist/server/authorized-replay.js +31 -0
- package/dist/server/command-identity.d.ts +52 -0
- package/dist/server/command-identity.js +71 -0
- package/dist/server/commit/brands.d.ts +7 -0
- package/dist/server/commit/brands.js +3 -0
- package/dist/server/commit/coordinator.d.ts +2 -0
- package/dist/server/commit/coordinator.js +729 -0
- package/dist/server/commit/types.d.ts +382 -0
- package/dist/server/commit/types.js +94 -0
- package/dist/server/commit-contract.d.ts +4 -0
- package/dist/server/commit-contract.js +2 -0
- package/dist/server/failures.d.ts +84 -0
- package/dist/server/failures.js +133 -0
- package/dist/server/gc.d.ts +42 -1
- package/dist/server/gc.js +168 -5
- package/dist/server/idempotency.d.ts +4 -0
- package/dist/server/idempotency.js +14 -0
- package/dist/server/lifecycle.d.ts +2 -1
- package/dist/server/lifecycle.js +15 -1
- package/dist/server/object-intents.d.ts +14 -1
- package/dist/server/object-intents.js +31 -0
- package/dist/server/outcome-contract.d.ts +3 -0
- package/dist/server/outcome-contract.js +3 -0
- package/dist/server/outcome-projection.d.ts +21 -0
- package/dist/server/outcome-projection.js +29 -0
- package/dist/server/preparation-cleanup.d.ts +31 -0
- package/dist/server/preparation-cleanup.js +106 -0
- package/dist/server/publication.d.ts +78 -0
- package/dist/server/publication.js +263 -31
- package/dist/server/service.d.ts +60 -0
- package/dist/server/service.js +181 -0
- package/dist/testing.d.ts +50 -0
- package/dist/testing.js +86 -0
- package/docs/migration-l6.md +134 -0
- package/examples/radar-commit-setup.ts +302 -0
- package/l0-freeze/command-identity-contract.ts +303 -0
- package/l0-freeze/commit-contract.ts +421 -0
- package/l0-freeze/failure-contract.ts +174 -0
- package/l0-freeze/profile-contract.ts +219 -0
- package/migrations/0003_coordinator.sql +28 -0
- package/package.json +27 -1
- package/src/contracts.ts +125 -0
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* L0 frozen profile declaration (immo #861, ask 1 shape, spec r2 section 2).
|
|
3
|
+
*
|
|
4
|
+
* Stable data shapes, bounds and version constants for explicit capability
|
|
5
|
+
* selection and the effective publication profile, plus the frozen
|
|
6
|
+
* provider-profile interfaces L1 compiles. No behavior: L1 wires these
|
|
7
|
+
* shapes into the factories.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { EffectiveArtifactCommand } from './command-identity-contract.js';
|
|
11
|
+
import type { JsonValue } from '../src/contracts.js';
|
|
12
|
+
|
|
13
|
+
/** Version of the frozen effective-profile shape. */
|
|
14
|
+
export const PROFILE_CONTRACT_VERSION = 'artifacts-profile/v1' as const;
|
|
15
|
+
|
|
16
|
+
/** All fifteen existing tool names, frozen (L1 wires the compiler to this set). */
|
|
17
|
+
export const KNOWN_ARTIFACT_TOOL_NAMES = [
|
|
18
|
+
'artifacts_get_template', 'artifacts_get_api_guide', 'artifacts_ensure',
|
|
19
|
+
'artifacts_publish', 'artifacts_create_upload', 'artifacts_commit_upload',
|
|
20
|
+
'artifacts_list', 'artifacts_get', 'artifacts_get_source', 'artifacts_fork',
|
|
21
|
+
'artifacts_set_access', 'artifacts_promote', 'artifacts_renew',
|
|
22
|
+
'artifacts_delete', 'artifacts_restore',
|
|
23
|
+
] as const;
|
|
24
|
+
|
|
25
|
+
export type KnownArtifactToolName = (typeof KNOWN_ARTIFACT_TOOL_NAMES)[number];
|
|
26
|
+
|
|
27
|
+
/** Radar ask-1 selection: the only four tools the Radar host enables. */
|
|
28
|
+
export const RADAR_SELECTED_TOOL_NAMES = [
|
|
29
|
+
'artifacts_ensure', 'artifacts_publish', 'artifacts_list', 'artifacts_get',
|
|
30
|
+
] as const;
|
|
31
|
+
|
|
32
|
+
export type RadarSelectedToolName = (typeof RADAR_SELECTED_TOOL_NAMES)[number];
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Frozen C2 capability routes: selected capability, provider route and
|
|
36
|
+
* Radar command. The adapter accepts exactly these four method/path
|
|
37
|
+
* combinations, not an arbitrary HTTP proxy.
|
|
38
|
+
*/
|
|
39
|
+
export const RADAR_OPERATION_ROUTES = [
|
|
40
|
+
{ capability: 'artifacts_ensure', method: 'POST', path: '/ensure', command: 'ensure' },
|
|
41
|
+
{ capability: 'artifacts_publish', method: 'POST', path: '/publish', command: 'publish' },
|
|
42
|
+
{ capability: 'artifacts_list', method: 'POST', path: '/list', command: 'list' },
|
|
43
|
+
{ capability: 'artifacts_get', method: 'GET', path: '/meta/{artifactId}', command: 'meta' },
|
|
44
|
+
] as const;
|
|
45
|
+
|
|
46
|
+
export type RadarOperationRoute = (typeof RADAR_OPERATION_ROUTES)[number];
|
|
47
|
+
|
|
48
|
+
/** Host-bound invocation scope for the Radar selection. */
|
|
49
|
+
export const RADAR_INVOCATION_SCOPE = 'immo:read' as const;
|
|
50
|
+
|
|
51
|
+
/** Explicit capability selection: unknown and duplicate entries are rejected. */
|
|
52
|
+
export interface ToolSelection {
|
|
53
|
+
readonly tools: readonly string[];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Closed effective-profile MIME allowlist for publish files. */
|
|
57
|
+
export const PROFILE_TEXT_MIME_TYPES = [
|
|
58
|
+
'text/html', 'text/css', 'text/javascript', 'application/json',
|
|
59
|
+
] as const;
|
|
60
|
+
|
|
61
|
+
export type ProfileMimeType = (typeof PROFILE_TEXT_MIME_TYPES)[number];
|
|
62
|
+
|
|
63
|
+
/** First path segments a publish file must never use. */
|
|
64
|
+
export const PROFILE_RESERVED_FIRST_SEGMENTS = [
|
|
65
|
+
'api', '__artifacts', 'livez', 'health',
|
|
66
|
+
] as const;
|
|
67
|
+
|
|
68
|
+
/** Frozen C4 bounds (UTF-16 code units unless noted). */
|
|
69
|
+
export const PROFILE_BOUNDS = {
|
|
70
|
+
bindingNameMinUnits: 1,
|
|
71
|
+
bindingNameMaxUnits: 128,
|
|
72
|
+
idempotencyKeyMaxUnits: 256,
|
|
73
|
+
publishFilesMin: 1,
|
|
74
|
+
publishFilesMax: 32,
|
|
75
|
+
maxDecodedFileBytes: 262144,
|
|
76
|
+
maxDecodedTotalBytes: 262144,
|
|
77
|
+
/** Candidate ceiling inherited from the existing provider validator. */
|
|
78
|
+
pathMaxBytes: 1024,
|
|
79
|
+
listLimitMin: 1,
|
|
80
|
+
listLimitMax: 100,
|
|
81
|
+
listLimitDefault: 20,
|
|
82
|
+
listOffsetDefault: 0,
|
|
83
|
+
/** The HTTP control route keeps its separate encoded-body budget. */
|
|
84
|
+
httpControlMaxEncodedBytes: 2 * 1024 * 1024,
|
|
85
|
+
} as const;
|
|
86
|
+
|
|
87
|
+
/** Inputs that participate in composition integrity evidence. */
|
|
88
|
+
export const COMPOSITION_DIGEST_INPUTS = [
|
|
89
|
+
'selection', 'profile', 'providerContractRevisions',
|
|
90
|
+
] as const;
|
|
91
|
+
|
|
92
|
+
/** One operation profile inside the effective profile. */
|
|
93
|
+
export interface OperationProfile {
|
|
94
|
+
readonly operation: string;
|
|
95
|
+
readonly limits: Record<string, number>;
|
|
96
|
+
readonly defaults: Record<string, JsonValue | undefined>;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Immutable versioned effective profile driving one composition. */
|
|
100
|
+
export interface EffectiveProfile {
|
|
101
|
+
readonly version: typeof PROFILE_CONTRACT_VERSION;
|
|
102
|
+
readonly selection: ToolSelection;
|
|
103
|
+
readonly operations: readonly OperationProfile[];
|
|
104
|
+
readonly contractRevisions: Record<string, string>;
|
|
105
|
+
readonly profileDigest: string;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Frozen provider profile input: explicit selected tools, complete
|
|
110
|
+
* operation profiles and required contract versions. Unknown and
|
|
111
|
+
* duplicate entries are rejected; an empty selection exposes and
|
|
112
|
+
* executes nothing; a missing selection fails construction.
|
|
113
|
+
*/
|
|
114
|
+
export interface ArtifactProviderProfile {
|
|
115
|
+
readonly version: typeof PROFILE_CONTRACT_VERSION;
|
|
116
|
+
readonly selectedCapabilities: readonly string[];
|
|
117
|
+
readonly operations: readonly OperationProfile[];
|
|
118
|
+
readonly contractRevisions: Record<string, string>;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Compiled profile driving schema generation, validation, normalization,
|
|
123
|
+
* defaults, command hashing and runtime limits. Selection, full profile
|
|
124
|
+
* data and provider-contract revisions participate in the digest; a
|
|
125
|
+
* semantic-only change invalidates the evidence even when JSON Schema
|
|
126
|
+
* text is unchanged.
|
|
127
|
+
*/
|
|
128
|
+
export interface CompiledArtifactsProfile extends ArtifactProviderProfile {
|
|
129
|
+
readonly schemas: Record<string, Record<string, unknown>>;
|
|
130
|
+
readonly validators: CompiledValidators;
|
|
131
|
+
readonly profileDigest: string;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Compiled semantic validators and normalizers bound at profile compile
|
|
136
|
+
* time. L1 implements them against the frozen operation profiles; L2/L6
|
|
137
|
+
* consume them instead of reimplementing checks.
|
|
138
|
+
*/
|
|
139
|
+
export interface CompiledValidators {
|
|
140
|
+
readonly validateBindingName: (value: unknown) => boolean;
|
|
141
|
+
readonly validateCallerKey: (value: unknown) => boolean;
|
|
142
|
+
readonly validateFileEntry: (entry: unknown) => boolean;
|
|
143
|
+
readonly normalizeCommand: (command: unknown) => EffectiveArtifactCommand;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** Compiler signature: L1 implements it against these frozen shapes. */
|
|
147
|
+
export type CompileArtifactsProfile = (
|
|
148
|
+
input: ArtifactProviderProfile,
|
|
149
|
+
) => CompiledArtifactsProfile;
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Semantic-only profile pair: identical expressible JSON Schema text with
|
|
153
|
+
* different effective semantics. The digest MUST invalidate on such a
|
|
154
|
+
* change even when schema text is unchanged.
|
|
155
|
+
*/
|
|
156
|
+
export const PROFILE_SEMANTIC_PAIR = {
|
|
157
|
+
schemaText: '{"type":"object","properties":{"files":{"type":"array"}}}',
|
|
158
|
+
first: { operation: 'publish', limits: { maxFiles: 32, maxTotalBytes: 262144 }, defaults: {} },
|
|
159
|
+
second: { operation: 'publish', limits: { maxFiles: 16, maxTotalBytes: 262144 }, defaults: {} },
|
|
160
|
+
} as const;
|
|
161
|
+
|
|
162
|
+
const isKnownToolName = (value: string): boolean =>
|
|
163
|
+
(KNOWN_ARTIFACT_TOOL_NAMES as readonly string[]).includes(value);
|
|
164
|
+
|
|
165
|
+
/** Validate an explicit selection: reject unknown and duplicate entries. */
|
|
166
|
+
export function validateToolSelection(selection: ToolSelection): string[] {
|
|
167
|
+
const errors: string[] = [];
|
|
168
|
+
if (!Array.isArray(selection.tools)) return ['selection tools must be an array'];
|
|
169
|
+
const seen = new Set<string>();
|
|
170
|
+
for (const tool of selection.tools) {
|
|
171
|
+
if (typeof tool !== 'string' || !isKnownToolName(tool)) {
|
|
172
|
+
errors.push(`unknown capability: ${String(tool)}`);
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
if (seen.has(tool)) errors.push(`duplicate capability: ${tool}`);
|
|
176
|
+
seen.add(tool);
|
|
177
|
+
}
|
|
178
|
+
return errors;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** Binding names are required strings of 1-128 UTF-16 code units, unnormalized. */
|
|
182
|
+
export function validateBindingName(value: unknown): boolean {
|
|
183
|
+
return typeof value === 'string' && value.length >= 1 && value.length <= 128;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Caller idempotency keys: original string, trim() must be nonempty,
|
|
188
|
+
* at most 256 UTF-16 code units. Whitespace around a nonempty value is
|
|
189
|
+
* retained; no trimming or Unicode normalization is applied. Lone high
|
|
190
|
+
* or low surrogates are rejected before UTF-8 conversion, derivation or
|
|
191
|
+
* storage so distinct caller keys can never alias one durable identity
|
|
192
|
+
* through replacement-character encoding.
|
|
193
|
+
*/
|
|
194
|
+
export function validateCallerIdempotencyKey(value: unknown): boolean {
|
|
195
|
+
if (typeof value !== 'string') return false;
|
|
196
|
+
if (value.length > 256 || value.trim().length === 0) return false;
|
|
197
|
+
for (let i = 0; i < value.length; i++) {
|
|
198
|
+
const unit = value.charCodeAt(i);
|
|
199
|
+
if (unit < 0xd800 || unit > 0xdfff) continue;
|
|
200
|
+
if (unit > 0xdbff) return false;
|
|
201
|
+
const next = i + 1 < value.length ? value.charCodeAt(i + 1) : 0;
|
|
202
|
+
if (next < 0xdc00 || next > 0xdfff) return false;
|
|
203
|
+
i++;
|
|
204
|
+
}
|
|
205
|
+
return true;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/** Radar publication files carry 1-32 entries within the decoded-byte budget. */
|
|
209
|
+
export function validatePublishFileCount(count: unknown): boolean {
|
|
210
|
+
return typeof count === 'number' && Number.isInteger(count) && count >= 1 && count <= 32;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/** List bounds: integer limit 1-100, nonnegative integer offset. */
|
|
214
|
+
export function validateListBounds(limit: unknown, offset: unknown): boolean {
|
|
215
|
+
return (
|
|
216
|
+
typeof limit === 'number' && Number.isInteger(limit) && limit >= 1 && limit <= 100 &&
|
|
217
|
+
typeof offset === 'number' && Number.isInteger(offset) && offset >= 0
|
|
218
|
+
);
|
|
219
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
-- L3 (immo #861) — atomic finalization coordination (spec r2 §3.1 A4).
|
|
2
|
+
--
|
|
3
|
+
-- Additive only: no ALTER/DROP of existing objects. Two coordinator-owned
|
|
4
|
+
-- tables. artifacts.operation_keys is the per-key serialization row keyed by
|
|
5
|
+
-- (storage_scope_id, internal_key); it carries no success or authorization
|
|
6
|
+
-- assertion. artifacts.operation_receipts holds exactly one success row per
|
|
7
|
+
-- (storage_scope_id, internal_key) with the command codec, command hash and
|
|
8
|
+
-- receipt; the primary key is the uniqueness fence. This migration is
|
|
9
|
+
-- distinct from artifacts.idempotency_keys, whose success-row count MUST
|
|
10
|
+
-- stay unchanged after a denial. The host applies this file through its
|
|
11
|
+
-- existing journal after 0002_lifecycle.sql.
|
|
12
|
+
|
|
13
|
+
CREATE TABLE IF NOT EXISTS artifacts.operation_keys (
|
|
14
|
+
storage_scope_id TEXT NOT NULL,
|
|
15
|
+
internal_key TEXT NOT NULL,
|
|
16
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
17
|
+
PRIMARY KEY (storage_scope_id, internal_key)
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
CREATE TABLE IF NOT EXISTS artifacts.operation_receipts (
|
|
21
|
+
storage_scope_id TEXT NOT NULL,
|
|
22
|
+
internal_key TEXT NOT NULL,
|
|
23
|
+
command_codec TEXT NOT NULL,
|
|
24
|
+
command_hash TEXT NOT NULL,
|
|
25
|
+
receipt JSONB NOT NULL,
|
|
26
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
27
|
+
PRIMARY KEY (storage_scope_id, internal_key)
|
|
28
|
+
);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sentropic/artifacts",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
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",
|
|
@@ -11,6 +11,11 @@
|
|
|
11
11
|
},
|
|
12
12
|
"main": "./dist/index.js",
|
|
13
13
|
"types": "./dist/index.d.ts",
|
|
14
|
+
"imports": {
|
|
15
|
+
"#artifacts-frozen-commit": {
|
|
16
|
+
"types": "./l0-freeze/commit-contract.ts"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
14
19
|
"exports": {
|
|
15
20
|
".": {
|
|
16
21
|
"types": "./dist/index.d.ts",
|
|
@@ -32,6 +37,10 @@
|
|
|
32
37
|
"types": "./dist/adapters/postgres.d.ts",
|
|
33
38
|
"import": "./dist/adapters/postgres.js"
|
|
34
39
|
},
|
|
40
|
+
"./adapters/postgres-commit": {
|
|
41
|
+
"types": "./dist/adapters/postgres-commit.d.ts",
|
|
42
|
+
"import": "./dist/adapters/postgres-commit.js"
|
|
43
|
+
},
|
|
35
44
|
"./migrations/*": "./migrations/*",
|
|
36
45
|
"./server/ports": {
|
|
37
46
|
"types": "./dist/server/ports.d.ts",
|
|
@@ -45,6 +54,18 @@
|
|
|
45
54
|
"types": "./dist/server/authorization.d.ts",
|
|
46
55
|
"import": "./dist/server/authorization.js"
|
|
47
56
|
},
|
|
57
|
+
"./server/commit-contract": {
|
|
58
|
+
"types": "./dist/server/commit-contract.d.ts",
|
|
59
|
+
"import": "./dist/server/commit-contract.js"
|
|
60
|
+
},
|
|
61
|
+
"./server/outcome-contract": {
|
|
62
|
+
"types": "./dist/server/outcome-contract.d.ts",
|
|
63
|
+
"import": "./dist/server/outcome-contract.js"
|
|
64
|
+
},
|
|
65
|
+
"./testing": {
|
|
66
|
+
"types": "./dist/testing.d.ts",
|
|
67
|
+
"import": "./dist/testing.js"
|
|
68
|
+
},
|
|
48
69
|
"./server/bindings": {
|
|
49
70
|
"types": "./dist/server/bindings.d.ts",
|
|
50
71
|
"import": "./dist/server/bindings.js"
|
|
@@ -144,7 +165,11 @@
|
|
|
144
165
|
},
|
|
145
166
|
"files": [
|
|
146
167
|
"dist",
|
|
168
|
+
"l0-freeze",
|
|
169
|
+
"src/contracts.ts",
|
|
147
170
|
"migrations",
|
|
171
|
+
"docs/migration-l6.md",
|
|
172
|
+
"examples/radar-commit-setup.ts",
|
|
148
173
|
"README.md",
|
|
149
174
|
"LICENSE"
|
|
150
175
|
],
|
|
@@ -155,6 +180,7 @@
|
|
|
155
180
|
"scripts": {
|
|
156
181
|
"build": "tsc -p tsconfig.json",
|
|
157
182
|
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
183
|
+
"freeze-types": "tsc --noEmit --strict --skipLibCheck --target es2022 --module nodenext --moduleResolution nodenext --verbatimModuleSyntax l0-freeze/commit-contract.ts l0-freeze/failure-contract.ts l0-freeze/profile-contract.ts l0-freeze/command-identity-contract.ts",
|
|
158
184
|
"test": "vitest run tests"
|
|
159
185
|
},
|
|
160
186
|
"peerDependencies": {
|
package/src/contracts.ts
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* L-5a — artifact domain contracts (spec-v8 §9).
|
|
3
|
+
*
|
|
4
|
+
* Generic, host-independent state: StorageScope, Artifact, Binding, Release,
|
|
5
|
+
* File/BlobRef, Source, ACL, hostname reservation, Upload, BlobPin,
|
|
6
|
+
* ObjectIntent and operation idempotency. Schema types are independent of
|
|
7
|
+
* ORM rows; no server, driver or cluster-mesh imports; no eager storage
|
|
8
|
+
* connections or DDL on import. Identities are server-allocated UUIDs.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export const ARTIFACT_SCHEMA_VERSIONS = ['artifacts/v1'] as const;
|
|
12
|
+
|
|
13
|
+
export type ArtifactSchemaVersion = (typeof ARTIFACT_SCHEMA_VERSIONS)[number];
|
|
14
|
+
|
|
15
|
+
/** Refuse unknown schema versions (fail-closed on version skew). */
|
|
16
|
+
export function assertKnownSchemaVersion(version: unknown): asserts version is ArtifactSchemaVersion {
|
|
17
|
+
if (!ARTIFACT_SCHEMA_VERSIONS.includes(version as ArtifactSchemaVersion)) {
|
|
18
|
+
throw new Error(`unknown artifact schema version: ${String(version)}`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const UUID_PATTERN =
|
|
23
|
+
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
24
|
+
|
|
25
|
+
/** True for server-allocated UUID identities. */
|
|
26
|
+
export function isArtifactId(value: unknown): value is string {
|
|
27
|
+
return typeof value === 'string' && UUID_PATTERN.test(value);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** JSON value (undefined only as an absent property, never serialized). */
|
|
31
|
+
export type JsonValue = string | number | boolean | null | JsonObject | JsonValue[];
|
|
32
|
+
export interface JsonObject {
|
|
33
|
+
[key: string]: JsonValue | undefined;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Content-addressed manifest: canonical bytes + SHA-256 digest, versioned
|
|
37
|
+
* schema. `digest` is optional: construction precedes sealing, so working
|
|
38
|
+
* documents carry no digest and sealed documents must verify against it.
|
|
39
|
+
*/
|
|
40
|
+
export interface CanonicalManifest {
|
|
41
|
+
schemaVersion: string;
|
|
42
|
+
digest?: string;
|
|
43
|
+
[key: string]: unknown;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Allocate a server-side identity (UUID v4). */
|
|
47
|
+
export function createArtifactId(): string {
|
|
48
|
+
return crypto.randomUUID();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Opaque storage scope isolating physical keys from org reindexing. */
|
|
52
|
+
export interface StorageScope {
|
|
53
|
+
readonly storageScopeId: string;
|
|
54
|
+
readonly createdAt: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** An artifact: one logical view across releases. */
|
|
58
|
+
export interface Artifact {
|
|
59
|
+
readonly id: string;
|
|
60
|
+
readonly schemaVersion: ArtifactSchemaVersion;
|
|
61
|
+
readonly storageScopeId: string;
|
|
62
|
+
readonly ownerId: string;
|
|
63
|
+
readonly createdAt: string;
|
|
64
|
+
readonly deletedAt?: string;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** A stable owner-scoped binding resolving to one artifact. */
|
|
68
|
+
export interface Binding {
|
|
69
|
+
readonly binding: string;
|
|
70
|
+
readonly artifactId: string;
|
|
71
|
+
readonly ownerId: string;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** An immutable release of an artifact's manifest + bytes. */
|
|
75
|
+
export interface Release {
|
|
76
|
+
readonly id: string;
|
|
77
|
+
readonly artifactId: string;
|
|
78
|
+
readonly manifestDigest: string;
|
|
79
|
+
readonly createdAt: string;
|
|
80
|
+
readonly state: 'pending' | 'ready' | 'withdrawn' | 'purged';
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** A content blob referenced by digest. */
|
|
84
|
+
export interface BlobRef {
|
|
85
|
+
readonly digest: string;
|
|
86
|
+
readonly size: number;
|
|
87
|
+
readonly mimeType: string;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Provenance of a release's sources. */
|
|
91
|
+
export type ArtifactSource =
|
|
92
|
+
| { readonly kind: 'inline-original' }
|
|
93
|
+
| { readonly kind: 'recipe'; readonly baseCommit: string; readonly recipe: string };
|
|
94
|
+
|
|
95
|
+
/** Object-level access grant. */
|
|
96
|
+
export interface AccessGrant {
|
|
97
|
+
readonly artifactId: string;
|
|
98
|
+
readonly subjectId: string;
|
|
99
|
+
readonly rights: readonly ('read' | 'source-export' | 'write')[];
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** A bounded upload session staging bytes before commit. */
|
|
103
|
+
export interface Upload {
|
|
104
|
+
readonly id: string;
|
|
105
|
+
readonly artifactId: string;
|
|
106
|
+
readonly prefix: string;
|
|
107
|
+
readonly createdAt: string;
|
|
108
|
+
readonly state: 'open' | 'committed' | 'aborted';
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** A pin retaining a blob against concurrent GC. */
|
|
112
|
+
export interface BlobPin {
|
|
113
|
+
readonly digest: string;
|
|
114
|
+
readonly artifactId: string;
|
|
115
|
+
readonly reason: 'release' | 'fork' | 'upload';
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Recorded intent before every external object operation. */
|
|
119
|
+
export interface ObjectIntent {
|
|
120
|
+
readonly idempotencyKey: string;
|
|
121
|
+
readonly artifactId: string;
|
|
122
|
+
readonly operation: string;
|
|
123
|
+
readonly recordedAt: string;
|
|
124
|
+
readonly state: 'recorded' | 'applied' | 'failed';
|
|
125
|
+
}
|