@cloudhal/release-verify 0.0.0-stage → 0.10.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/LICENSE +202 -0
- package/README.md +81 -2
- package/dist/src/attestation.d.ts +42 -0
- package/dist/src/attestation.js +180 -0
- package/dist/src/crypto.d.ts +15 -0
- package/dist/src/crypto.js +87 -0
- package/dist/src/documents.d.ts +23 -0
- package/dist/src/documents.js +167 -0
- package/dist/src/errors.d.ts +13 -0
- package/dist/src/errors.js +48 -0
- package/dist/src/generated/release-keys.d.ts +67 -0
- package/dist/src/generated/release-keys.js +72 -0
- package/dist/src/generated/release-manifest.d.ts +211 -0
- package/dist/src/generated/release-manifest.js +237 -0
- package/dist/src/generated/release-statement.d.ts +61 -0
- package/dist/src/generated/release-statement.js +66 -0
- package/dist/src/generated/release-yank-list.d.ts +73 -0
- package/dist/src/generated/release-yank-list.js +78 -0
- package/dist/src/generated/validators.d.ts +22 -0
- package/dist/src/generated/validators.js +5656 -0
- package/dist/src/index.d.ts +22 -0
- package/dist/src/index.js +48 -0
- package/dist/src/keys.d.ts +28 -0
- package/dist/src/keys.js +84 -0
- package/dist/src/limits.d.ts +24 -0
- package/dist/src/limits.js +30 -0
- package/dist/src/paths.d.ts +14 -0
- package/dist/src/paths.js +59 -0
- package/dist/src/scopes.d.ts +10 -0
- package/dist/src/scopes.js +22 -0
- package/dist/src/tar.d.ts +31 -0
- package/dist/src/tar.js +398 -0
- package/dist/src/trust.d.ts +67 -0
- package/dist/src/trust.js +248 -0
- package/dist/src/urls.d.ts +8 -0
- package/dist/src/urls.js +37 -0
- package/dist/src/verify.d.ts +68 -0
- package/dist/src/verify.js +171 -0
- package/dist/src/yank-memory.d.ts +22 -0
- package/dist/src/yank-memory.js +106 -0
- package/package.json +51 -3
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The statement and the manifest (RFC-0046 §2 steps 1 to 5): sizes, the Ed25519 signature with a
|
|
3
|
+
* trusted key, the binding to the request (invariant 7), both precompiled validators, the
|
|
4
|
+
* invariants the schemas cannot express, and the manifest's listings (paths and §7 limits).
|
|
5
|
+
*/
|
|
6
|
+
import { refuse } from "./errors.js";
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
import { validateReleaseManifest, validateReleaseStatement } from "./generated/validators.js";
|
|
10
|
+
import { channelOf, LIMITS, MANIFEST_FILE, SIGNATURE_FILE, tarballName } from "./limits.js";
|
|
11
|
+
import { checkNoFoldedDuplicates, checkPath } from "./paths.js";
|
|
12
|
+
|
|
13
|
+
const utf8 = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true });
|
|
14
|
+
|
|
15
|
+
/** The JSON document in `bytes`, or the refusal `code`. */
|
|
16
|
+
export function parseJson(
|
|
17
|
+
bytes ,
|
|
18
|
+
code ,
|
|
19
|
+
) {
|
|
20
|
+
try {
|
|
21
|
+
return JSON.parse(utf8.decode(bytes));
|
|
22
|
+
} catch {
|
|
23
|
+
return refuse(
|
|
24
|
+
code,
|
|
25
|
+
`${code === "statement-invalid" ? "the statement" : "the manifest"} is not UTF-8 JSON`,
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const firstError = (errors ) =>
|
|
31
|
+
errors?.[0] ? `${errors[0].instancePath || "/"} ${errors[0].message ?? ""}`.trim() : "invalid";
|
|
32
|
+
|
|
33
|
+
/** §2 step 4: the schema, then invariants 1, 2, 3 and 6. */
|
|
34
|
+
export function checkStatementDoc(doc , serialisedBytes ) {
|
|
35
|
+
if (!validateReleaseStatement(doc))
|
|
36
|
+
refuse("statement-invalid", `the statement: ${firstError(validateReleaseStatement.errors)}`);
|
|
37
|
+
const s = doc;
|
|
38
|
+
if (s.manifest.name !== MANIFEST_FILE)
|
|
39
|
+
refuse("statement-invalid", `statement.manifest.name is not ${MANIFEST_FILE}`);
|
|
40
|
+
if (s.tarball.name !== tarballName(s.version))
|
|
41
|
+
refuse("statement-invalid", `statement.tarball.name is not ${tarballName(s.version)}`);
|
|
42
|
+
if (s.manifest.bytes > LIMITS.manifestBytes)
|
|
43
|
+
refuse("statement-invalid", `the manifest is above ${LIMITS.manifestBytes} bytes`);
|
|
44
|
+
if (s.channel !== channelOf(s.version))
|
|
45
|
+
refuse("statement-invalid", `channel ${s.channel} does not match version ${s.version}`);
|
|
46
|
+
if (serialisedBytes > LIMITS.statementBytes)
|
|
47
|
+
refuse("statement-too-large", `the statement is above ${LIMITS.statementBytes} bytes`);
|
|
48
|
+
return s;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** §2 step 3 (invariant 7): the statement is for the version and channel that were asked for. */
|
|
52
|
+
export function checkRequested(doc , version , channel ) {
|
|
53
|
+
const s = (doc ?? {}) ;
|
|
54
|
+
if (s.version !== version)
|
|
55
|
+
refuse("wrong-version", `the statement is for ${String(s.version)}, not ${version}`);
|
|
56
|
+
const want = channel ?? channelOf(version);
|
|
57
|
+
if (s.channel !== want)
|
|
58
|
+
refuse("wrong-channel", `the statement is for channel ${String(s.channel)}, not ${want}`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** §2 step 5 after the digest: the schema, invariants 4 and 5, the signature form, planVersion. */
|
|
62
|
+
export function checkManifestDoc(
|
|
63
|
+
doc ,
|
|
64
|
+
statement ,
|
|
65
|
+
planVersion ,
|
|
66
|
+
) {
|
|
67
|
+
if (!validateReleaseManifest(doc))
|
|
68
|
+
refuse("manifest-invalid", `the manifest: ${firstError(validateReleaseManifest.errors)}`);
|
|
69
|
+
const m = doc;
|
|
70
|
+
if (m.signing.keyId !== statement.signing.keyId)
|
|
71
|
+
refuse("statement-manifest-mismatch", "the statement and the manifest name different keys");
|
|
72
|
+
for (const k of ["version", "channel", "securityRelease", "commit", "createdAt"] )
|
|
73
|
+
if (m[k] !== statement[k])
|
|
74
|
+
refuse("statement-manifest-mismatch", `the statement's ${k} is not the manifest's`);
|
|
75
|
+
// A project key never signs the legacy manifest.sig form (§1).
|
|
76
|
+
if (m.signing.signature !== SIGNATURE_FILE)
|
|
77
|
+
refuse("manifest-invalid", `the manifest does not name ${SIGNATURE_FILE}`);
|
|
78
|
+
if (m.planVersion > planVersion)
|
|
79
|
+
refuse("plan-version-unsupported", `planVersion ${m.planVersion} is above ${planVersion}`);
|
|
80
|
+
checkListings(m);
|
|
81
|
+
return m;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
/** One part the manifest lists, by its tar path. */
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
/** Every part the manifest lists, keyed by its tar path (`site/modules/x.mjs`, …). */
|
|
98
|
+
export function listedParts(m ) {
|
|
99
|
+
const out = new Map ();
|
|
100
|
+
for (const worker of ["site", "admin"] ) {
|
|
101
|
+
const w = m.workers[worker];
|
|
102
|
+
for (const mod of w.modules)
|
|
103
|
+
out.set(`${worker}/modules/${mod.path}`, { worker, kind: "module", ...mod });
|
|
104
|
+
for (const f of w.assets.files)
|
|
105
|
+
out.set(`${worker}/assets${f.path}`, {
|
|
106
|
+
worker,
|
|
107
|
+
kind: "asset",
|
|
108
|
+
path: f.path,
|
|
109
|
+
bytes: f.bytes,
|
|
110
|
+
sha256: f.sha256,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
if (m.starter.kind === "bundle") {
|
|
114
|
+
const s = m.starter;
|
|
115
|
+
out.set(s.path, {
|
|
116
|
+
worker: null,
|
|
117
|
+
kind: "starter",
|
|
118
|
+
path: s.path,
|
|
119
|
+
bytes: s.bytes,
|
|
120
|
+
sha256: s.sha256,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
return out;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** One Worker's listings: module and asset paths, `mainModule`, folded names and §7's limits. */
|
|
127
|
+
function checkWorker(m , worker ) {
|
|
128
|
+
const w = m.workers[worker];
|
|
129
|
+
for (const mod of w.modules) {
|
|
130
|
+
checkPath(mod.path, "module", `${worker} module`);
|
|
131
|
+
if (mod.bytes > LIMITS.partBytes)
|
|
132
|
+
refuse("limit-exceeded", `${worker} module ${mod.path} is above ${LIMITS.partBytes} bytes`);
|
|
133
|
+
}
|
|
134
|
+
checkNoFoldedDuplicates(
|
|
135
|
+
w.modules.map((x) => x.path),
|
|
136
|
+
`${worker} modules`,
|
|
137
|
+
);
|
|
138
|
+
checkPath(w.mainModule, "module", `${worker} mainModule`);
|
|
139
|
+
if (!w.modules.some((x) => x.path === w.mainModule))
|
|
140
|
+
refuse("manifest-invalid", `${worker} mainModule ${w.mainModule} is not a listed module`);
|
|
141
|
+
const files = w.assets.files;
|
|
142
|
+
if (files.length > LIMITS.assetsPerWorker)
|
|
143
|
+
refuse("limit-exceeded", `${worker}: more than ${LIMITS.assetsPerWorker} assets`);
|
|
144
|
+
for (const f of files) {
|
|
145
|
+
checkPath(f.path, "asset", `${worker} asset`);
|
|
146
|
+
if (f.bytes > LIMITS.assetBytes)
|
|
147
|
+
refuse("limit-exceeded", `${worker} asset ${f.path} is above ${LIMITS.assetBytes} bytes`);
|
|
148
|
+
}
|
|
149
|
+
checkNoFoldedDuplicates(
|
|
150
|
+
files.map((x) => x.path),
|
|
151
|
+
`${worker} assets`,
|
|
152
|
+
);
|
|
153
|
+
return w.modules.length + files.length;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** Paths (§11a) and §7's limits over every listing, before the tarball is read. */
|
|
157
|
+
function checkListings(m ) {
|
|
158
|
+
let parts = checkWorker(m, "site") + checkWorker(m, "admin");
|
|
159
|
+
if (m.starter.kind === "bundle") {
|
|
160
|
+
checkPath(m.starter.path, "entry", "starter");
|
|
161
|
+
if (m.starter.bytes > LIMITS.partBytes)
|
|
162
|
+
refuse("limit-exceeded", `the starter is above ${LIMITS.partBytes} bytes`);
|
|
163
|
+
parts++;
|
|
164
|
+
}
|
|
165
|
+
if (parts > LIMITS.tarEntries)
|
|
166
|
+
refuse("limit-exceeded", `the manifest lists more than ${LIMITS.tarEntries} parts`);
|
|
167
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The refusals of RFC-0046 §11a. Each `code` is public API: consumers branch on it, so a code is
|
|
3
|
+
* never renamed or reused. There is no warning level: every one of them refuses the release.
|
|
4
|
+
*/
|
|
5
|
+
export declare const ERROR_CODES: readonly ["statement-too-large", "signature-size", "key-unknown", "key-revoked", "signature-invalid", "wrong-version", "wrong-channel", "statement-invalid", "manifest-mismatch", "manifest-invalid", "statement-manifest-mismatch", "plan-version-unsupported", "tarball-mismatch", "tar-format", "entry-path", "entry-unlisted", "entry-mismatch", "entry-missing", "limit-exceeded", "key-expired", "yanked", "key-list-invalid", "key-list-expired", "yank-list-invalid", "stale-yank-list"];
|
|
6
|
+
export type ReleaseVerifyErrorCode = (typeof ERROR_CODES)[number];
|
|
7
|
+
export declare class ReleaseVerifyError extends Error {
|
|
8
|
+
readonly name = "ReleaseVerifyError";
|
|
9
|
+
readonly code: ReleaseVerifyErrorCode;
|
|
10
|
+
constructor(code: ReleaseVerifyErrorCode, message: string);
|
|
11
|
+
}
|
|
12
|
+
/** Throws the refusal `code`; typed `never` so checks read as guards. */
|
|
13
|
+
export declare function refuse(code: ReleaseVerifyErrorCode, message: string): never;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The refusals of RFC-0046 §11a. Each `code` is public API: consumers branch on it, so a code is
|
|
3
|
+
* never renamed or reused. There is no warning level: every one of them refuses the release.
|
|
4
|
+
*/
|
|
5
|
+
export const ERROR_CODES = [
|
|
6
|
+
"statement-too-large",
|
|
7
|
+
"signature-size",
|
|
8
|
+
"key-unknown",
|
|
9
|
+
"key-revoked",
|
|
10
|
+
"signature-invalid",
|
|
11
|
+
"wrong-version",
|
|
12
|
+
"wrong-channel",
|
|
13
|
+
"statement-invalid",
|
|
14
|
+
"manifest-mismatch",
|
|
15
|
+
"manifest-invalid",
|
|
16
|
+
"statement-manifest-mismatch",
|
|
17
|
+
"plan-version-unsupported",
|
|
18
|
+
"tarball-mismatch",
|
|
19
|
+
"tar-format",
|
|
20
|
+
"entry-path",
|
|
21
|
+
"entry-unlisted",
|
|
22
|
+
"entry-mismatch",
|
|
23
|
+
"entry-missing",
|
|
24
|
+
"limit-exceeded",
|
|
25
|
+
// RFC-0046 §9 (#226): expiry, the key list and the yank list.
|
|
26
|
+
"key-expired",
|
|
27
|
+
"yanked",
|
|
28
|
+
"key-list-invalid",
|
|
29
|
+
"key-list-expired",
|
|
30
|
+
"yank-list-invalid",
|
|
31
|
+
"stale-yank-list",
|
|
32
|
+
] ;
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
export class ReleaseVerifyError extends Error {
|
|
37
|
+
name = "ReleaseVerifyError";
|
|
38
|
+
code ;
|
|
39
|
+
constructor(code , message ) {
|
|
40
|
+
super(message);
|
|
41
|
+
this.code = code;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Throws the refusal `code`; typed `never` so checks read as guards. */
|
|
46
|
+
export function refuse(code , message ) {
|
|
47
|
+
throw new ReleaseVerifyError(code, message);
|
|
48
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GENERATED FILE — DO NOT EDIT.
|
|
3
|
+
* Source: packages/core-contracts/schemas/release-{manifest,statement,keys,yank-list}.schema.json
|
|
4
|
+
* (run `pnpm gen`; packages/release-verify/scripts/gen-validators.ts)
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* GENERATED FILE — DO NOT EDIT.
|
|
8
|
+
* Source: packages/core-contracts/schemas/*.schema.json (run `pnpm gen`)
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time.
|
|
12
|
+
*
|
|
13
|
+
* This interface was referenced by `ReleaseKeys`'s JSON-Schema
|
|
14
|
+
* via the `definition` "SchemaVersion".
|
|
15
|
+
*/
|
|
16
|
+
export type SchemaVersion = 1;
|
|
17
|
+
/**
|
|
18
|
+
* RFC 3339 / ISO 8601 instant in UTC.
|
|
19
|
+
*
|
|
20
|
+
* This interface was referenced by `ReleaseKeys`'s JSON-Schema
|
|
21
|
+
* via the `definition` "Timestamp".
|
|
22
|
+
*/
|
|
23
|
+
export type Timestamp = string;
|
|
24
|
+
/**
|
|
25
|
+
* This interface was referenced by `ReleaseKeys`'s JSON-Schema
|
|
26
|
+
* via the `definition` "KeyId".
|
|
27
|
+
*/
|
|
28
|
+
export type KeyId = string;
|
|
29
|
+
/**
|
|
30
|
+
* cloudhal.release-keys@1 (RFC-0046 §9): the root-signed list of Cloudhal's release and yank keys, published as keys.json at https://cloudhal.dev/.well-known/cloudhal/keys.json next to keys.json.sig (a raw 64-byte Ed25519 signature by the offline root over the exact bytes). A consumer uses it only to reduce trust in the keys it pins: revoke, mark retiring, narrow a validity window; it never adds a key. Canonical bytes as for the release statement.
|
|
31
|
+
*/
|
|
32
|
+
export interface ReleaseKeys {
|
|
33
|
+
schemaVersion: SchemaVersion;
|
|
34
|
+
format: "cloudhal.release-keys@1";
|
|
35
|
+
issuedAt: Timestamp;
|
|
36
|
+
/**
|
|
37
|
+
* RFC 3339 / ISO 8601 instant in UTC.
|
|
38
|
+
*/
|
|
39
|
+
expires: string;
|
|
40
|
+
signing: {
|
|
41
|
+
/**
|
|
42
|
+
* The root key that signed this list.
|
|
43
|
+
*/
|
|
44
|
+
keyId: string;
|
|
45
|
+
algorithm: "Ed25519";
|
|
46
|
+
signature: "keys.json.sig";
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* @maxItems 64
|
|
50
|
+
*/
|
|
51
|
+
keys: ListedKey[];
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* This interface was referenced by `ReleaseKeys`'s JSON-Schema
|
|
55
|
+
* via the `definition` "ListedKey".
|
|
56
|
+
*/
|
|
57
|
+
export interface ListedKey {
|
|
58
|
+
keyId: KeyId;
|
|
59
|
+
/**
|
|
60
|
+
* The raw 32-byte Ed25519 public key, base64.
|
|
61
|
+
*/
|
|
62
|
+
publicKey: string;
|
|
63
|
+
role: "release" | "yank";
|
|
64
|
+
notBefore: Timestamp;
|
|
65
|
+
notAfter: Timestamp;
|
|
66
|
+
status: "active" | "retiring" | "revoked";
|
|
67
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
/* biome-ignore-all lint: generated */
|
|
3
|
+
/**
|
|
4
|
+
* GENERATED FILE — DO NOT EDIT.
|
|
5
|
+
* Source: packages/core-contracts/schemas/release-{manifest,statement,keys,yank-list}.schema.json
|
|
6
|
+
* (run `pnpm gen`; packages/release-verify/scripts/gen-validators.ts)
|
|
7
|
+
*/
|
|
8
|
+
/* biome-ignore-all lint: generated */
|
|
9
|
+
/**
|
|
10
|
+
* GENERATED FILE — DO NOT EDIT.
|
|
11
|
+
* Source: packages/core-contracts/schemas/*.schema.json (run `pnpm gen`)
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time.
|
|
16
|
+
*
|
|
17
|
+
* This interface was referenced by `ReleaseKeys`'s JSON-Schema
|
|
18
|
+
* via the `definition` "SchemaVersion".
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* RFC 3339 / ISO 8601 instant in UTC.
|
|
23
|
+
*
|
|
24
|
+
* This interface was referenced by `ReleaseKeys`'s JSON-Schema
|
|
25
|
+
* via the `definition` "Timestamp".
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* This interface was referenced by `ReleaseKeys`'s JSON-Schema
|
|
30
|
+
* via the `definition` "KeyId".
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* cloudhal.release-keys@1 (RFC-0046 §9): the root-signed list of Cloudhal's release and yank keys, published as keys.json at https://cloudhal.dev/.well-known/cloudhal/keys.json next to keys.json.sig (a raw 64-byte Ed25519 signature by the offline root over the exact bytes). A consumer uses it only to reduce trust in the keys it pins: revoke, mark retiring, narrow a validity window; it never adds a key. Canonical bytes as for the release statement.
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* This interface was referenced by `ReleaseKeys`'s JSON-Schema
|
|
60
|
+
* via the `definition` "ListedKey".
|
|
61
|
+
*/
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GENERATED FILE — DO NOT EDIT.
|
|
3
|
+
* Source: packages/core-contracts/schemas/release-{manifest,statement,keys,yank-list}.schema.json
|
|
4
|
+
* (run `pnpm gen`; packages/release-verify/scripts/gen-validators.ts)
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* GENERATED FILE — DO NOT EDIT.
|
|
8
|
+
* Source: packages/core-contracts/schemas/*.schema.json (run `pnpm gen`)
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Version of this contract. Readers reject unknown versions; migrations move documents forward one version at a time.
|
|
12
|
+
*
|
|
13
|
+
* This interface was referenced by `ReleaseManifest`'s JSON-Schema
|
|
14
|
+
* via the `definition` "SchemaVersion".
|
|
15
|
+
*/
|
|
16
|
+
export type SchemaVersion = 1;
|
|
17
|
+
/**
|
|
18
|
+
* This interface was referenced by `ReleaseManifest`'s JSON-Schema
|
|
19
|
+
* via the `definition` "CloudhalVersion".
|
|
20
|
+
*/
|
|
21
|
+
export type CloudhalVersion = string;
|
|
22
|
+
/**
|
|
23
|
+
* RFC 3339 / ISO 8601 instant in UTC.
|
|
24
|
+
*
|
|
25
|
+
* This interface was referenced by `ReleaseManifest`'s JSON-Schema
|
|
26
|
+
* via the `definition` "Timestamp".
|
|
27
|
+
*/
|
|
28
|
+
export type Timestamp = string;
|
|
29
|
+
/**
|
|
30
|
+
* This interface was referenced by `ReleaseManifest`'s JSON-Schema
|
|
31
|
+
* via the `definition` "MigrationName".
|
|
32
|
+
*/
|
|
33
|
+
export type MigrationName = string;
|
|
34
|
+
/**
|
|
35
|
+
* This interface was referenced by `ReleaseManifest`'s JSON-Schema
|
|
36
|
+
* via the `definition` "BindingName".
|
|
37
|
+
*/
|
|
38
|
+
export type BindingName = string;
|
|
39
|
+
/**
|
|
40
|
+
* This interface was referenced by `ReleaseManifest`'s JSON-Schema
|
|
41
|
+
* via the `definition` "Sha256Hex".
|
|
42
|
+
*/
|
|
43
|
+
export type Sha256Hex = string;
|
|
44
|
+
/**
|
|
45
|
+
* This interface was referenced by `ReleaseManifest`'s JSON-Schema
|
|
46
|
+
* via the `definition` "SecretName".
|
|
47
|
+
*/
|
|
48
|
+
export type SecretName = "BETTER_AUTH_SECRET" | "MCP_STATE_KEY" | "CLOUDHAL_PREVIEW_SECRET" | "CLOUDHAL_PURGE_TOKEN" | "CLOUDHAL_DEPLOY_TOKEN" | "CLOUDHAL_SETUP_TOKEN" | "CLOUDHAL_COMMENTS_KEY";
|
|
49
|
+
/**
|
|
50
|
+
* This interface was referenced by `ReleaseManifest`'s JSON-Schema
|
|
51
|
+
* via the `definition` "ScopeId".
|
|
52
|
+
*/
|
|
53
|
+
export type ScopeId = string;
|
|
54
|
+
/**
|
|
55
|
+
* This interface was referenced by `ReleaseManifest`'s JSON-Schema
|
|
56
|
+
* via the `definition` "Capability".
|
|
57
|
+
*/
|
|
58
|
+
export type Capability = "account" | "workers" | "d1" | "r2" | "analytics" | "domains";
|
|
59
|
+
/**
|
|
60
|
+
* This interface was referenced by `ReleaseManifest`'s JSON-Schema
|
|
61
|
+
* via the `definition` "SiteId".
|
|
62
|
+
*/
|
|
63
|
+
export type SiteId = string;
|
|
64
|
+
/**
|
|
65
|
+
* cloudhal.release-manifest@1 (RFC-0046): the manifest of one Cloudhal release bundle, published as the release asset manifest.json next to the gzip tarball cloudhal-release-<version>.tar.gz. A project release is signed through release-statement.json, which pins this file's SHA-256 and size (signing.signature = release-statement.sig); the legacy form manifest.sig, a raw 64-byte Ed25519 signature over these exact bytes, is for development keys only. It describes every built Worker module and static asset by size and SHA-256, the binding templates, feature levels, migration range and rollback facts a remote deployer needs to install, update or refuse a version without running a build. Portable on purpose: it references common only for SchemaVersion and Timestamp; every other definition is local.
|
|
66
|
+
*/
|
|
67
|
+
export interface ReleaseManifest {
|
|
68
|
+
schemaVersion: SchemaVersion;
|
|
69
|
+
format: "cloudhal.release-manifest@1";
|
|
70
|
+
version: CloudhalVersion;
|
|
71
|
+
channel: "stable" | "next";
|
|
72
|
+
securityRelease: boolean;
|
|
73
|
+
commit: string;
|
|
74
|
+
createdAt: Timestamp;
|
|
75
|
+
contracts: {
|
|
76
|
+
schemaVersion: 1;
|
|
77
|
+
};
|
|
78
|
+
levels: {
|
|
79
|
+
schedules: number;
|
|
80
|
+
gates: number;
|
|
81
|
+
};
|
|
82
|
+
migrations: {
|
|
83
|
+
first: MigrationName;
|
|
84
|
+
last: MigrationName;
|
|
85
|
+
count: number;
|
|
86
|
+
/**
|
|
87
|
+
* @minItems 1
|
|
88
|
+
*/
|
|
89
|
+
names: [MigrationName, ...MigrationName[]];
|
|
90
|
+
compatibleWithPrevious: boolean;
|
|
91
|
+
};
|
|
92
|
+
durableObjects: {
|
|
93
|
+
bindings: {
|
|
94
|
+
name: BindingName;
|
|
95
|
+
className: string;
|
|
96
|
+
migration: string;
|
|
97
|
+
}[];
|
|
98
|
+
lifecycleChange: boolean;
|
|
99
|
+
};
|
|
100
|
+
runtime: {
|
|
101
|
+
compatibilityDate: string;
|
|
102
|
+
compatibilityFlags: string[];
|
|
103
|
+
};
|
|
104
|
+
toolchain: {
|
|
105
|
+
[k: string]: string | undefined;
|
|
106
|
+
};
|
|
107
|
+
theme: {
|
|
108
|
+
name: string;
|
|
109
|
+
version: string;
|
|
110
|
+
blockTypes: string[];
|
|
111
|
+
settingsSha256: Sha256Hex;
|
|
112
|
+
designSha256: Sha256Hex;
|
|
113
|
+
};
|
|
114
|
+
skills: {
|
|
115
|
+
name: string;
|
|
116
|
+
sha256: Sha256Hex;
|
|
117
|
+
}[];
|
|
118
|
+
workers: {
|
|
119
|
+
site: WorkerArtifact;
|
|
120
|
+
admin: WorkerArtifact;
|
|
121
|
+
};
|
|
122
|
+
bindingTemplates: {
|
|
123
|
+
site: BindingTemplate;
|
|
124
|
+
admin: BindingTemplate;
|
|
125
|
+
};
|
|
126
|
+
requiredScopes: ScopeId[];
|
|
127
|
+
optionalScopes: ScopeId[];
|
|
128
|
+
requiredCapabilities: Capability[];
|
|
129
|
+
optionalCapabilities: Capability[];
|
|
130
|
+
starter: {
|
|
131
|
+
kind: "admin-api";
|
|
132
|
+
} | {
|
|
133
|
+
kind: "bundle";
|
|
134
|
+
path: string;
|
|
135
|
+
bytes: number;
|
|
136
|
+
sha256: Sha256Hex;
|
|
137
|
+
siteId: SiteId;
|
|
138
|
+
};
|
|
139
|
+
features: {
|
|
140
|
+
adminSitesAcceptsSiteId: boolean;
|
|
141
|
+
healthReportsVersion: boolean;
|
|
142
|
+
mediaTransformVar: boolean;
|
|
143
|
+
[k: string]: boolean | undefined;
|
|
144
|
+
};
|
|
145
|
+
planVersion: number;
|
|
146
|
+
signing: {
|
|
147
|
+
keyId: string;
|
|
148
|
+
algorithm: "Ed25519";
|
|
149
|
+
signature: "manifest.sig" | "release-statement.sig";
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* This interface was referenced by `ReleaseManifest`'s JSON-Schema
|
|
154
|
+
* via the `definition` "WorkerArtifact".
|
|
155
|
+
*/
|
|
156
|
+
export interface WorkerArtifact {
|
|
157
|
+
mainModule: string;
|
|
158
|
+
/**
|
|
159
|
+
* @minItems 1
|
|
160
|
+
*/
|
|
161
|
+
modules: [
|
|
162
|
+
{
|
|
163
|
+
path: string;
|
|
164
|
+
type: "esm" | "cjs" | "wasm" | "text" | "data";
|
|
165
|
+
bytes: number;
|
|
166
|
+
sha256: Sha256Hex;
|
|
167
|
+
},
|
|
168
|
+
...{
|
|
169
|
+
path: string;
|
|
170
|
+
type: "esm" | "cjs" | "wasm" | "text" | "data";
|
|
171
|
+
bytes: number;
|
|
172
|
+
sha256: Sha256Hex;
|
|
173
|
+
}[]
|
|
174
|
+
];
|
|
175
|
+
assets: {
|
|
176
|
+
count: number;
|
|
177
|
+
bytes: number;
|
|
178
|
+
/**
|
|
179
|
+
* @maxItems 20000
|
|
180
|
+
*/
|
|
181
|
+
files: {
|
|
182
|
+
path: string;
|
|
183
|
+
bytes: number;
|
|
184
|
+
sha256: Sha256Hex;
|
|
185
|
+
assetHash: string;
|
|
186
|
+
contentType: string;
|
|
187
|
+
}[];
|
|
188
|
+
headers: string;
|
|
189
|
+
config?: {
|
|
190
|
+
not_found_handling?: "none" | "404-page" | "single-page-application";
|
|
191
|
+
run_worker_first?: string[];
|
|
192
|
+
};
|
|
193
|
+
};
|
|
194
|
+
bindingTemplate?: "site" | "admin";
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* This interface was referenced by `ReleaseManifest`'s JSON-Schema
|
|
198
|
+
* via the `definition` "BindingTemplate".
|
|
199
|
+
*/
|
|
200
|
+
export interface BindingTemplate {
|
|
201
|
+
bindings: {
|
|
202
|
+
name: BindingName;
|
|
203
|
+
type: "d1" | "r2_bucket" | "assets" | "service" | "durable_object_namespace" | "analytics_engine" | "analytics_sql" | "ratelimit" | "ai_search" | "send_email" | "ai" | "browser";
|
|
204
|
+
required: boolean;
|
|
205
|
+
aliases?: BindingName[];
|
|
206
|
+
}[];
|
|
207
|
+
vars: BindingName[];
|
|
208
|
+
secrets: SecretName[];
|
|
209
|
+
cache?: boolean;
|
|
210
|
+
cron?: string;
|
|
211
|
+
}
|