@xemahq/agent-session-runtime 0.7.0 → 0.9.0

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.
Files changed (38) hide show
  1. package/dist/lib/agent-resolution.d.ts.map +1 -1
  2. package/dist/lib/agent-resolution.js.map +1 -1
  3. package/dist/lib/composer.d.ts +25 -2
  4. package/dist/lib/composer.d.ts.map +1 -1
  5. package/dist/lib/composer.js +237 -81
  6. package/dist/lib/composer.js.map +1 -1
  7. package/dist/lib/launch-identity/canonical-contract.d.ts +1 -0
  8. package/dist/lib/launch-identity/canonical-contract.d.ts.map +1 -1
  9. package/dist/lib/launch-identity/canonical-contract.js +14 -2
  10. package/dist/lib/launch-identity/canonical-contract.js.map +1 -1
  11. package/dist/lib/launch-identity/compiled-session-launch-plan.d.ts.map +1 -1
  12. package/dist/lib/launch-identity/compiled-session-launch-plan.js +1 -5
  13. package/dist/lib/launch-identity/compiled-session-launch-plan.js.map +1 -1
  14. package/dist/lib/launch-identity/index.d.ts +2 -0
  15. package/dist/lib/launch-identity/index.d.ts.map +1 -1
  16. package/dist/lib/launch-identity/index.js +2 -0
  17. package/dist/lib/launch-identity/index.js.map +1 -1
  18. package/dist/lib/launch-identity/preparation-generation.d.ts +26 -0
  19. package/dist/lib/launch-identity/preparation-generation.d.ts.map +1 -0
  20. package/dist/lib/launch-identity/preparation-generation.js +88 -0
  21. package/dist/lib/launch-identity/preparation-generation.js.map +1 -0
  22. package/dist/lib/launch-identity/prepared-launch-variant.d.ts +46 -0
  23. package/dist/lib/launch-identity/prepared-launch-variant.d.ts.map +1 -0
  24. package/dist/lib/launch-identity/prepared-launch-variant.js +118 -0
  25. package/dist/lib/launch-identity/prepared-launch-variant.js.map +1 -0
  26. package/dist/lib/launch-identity/runtime-template-key.d.ts.map +1 -1
  27. package/dist/lib/launch-identity/runtime-template-key.js +1 -5
  28. package/dist/lib/launch-identity/runtime-template-key.js.map +1 -1
  29. package/package.json +21 -20
  30. package/src/lib/agent-resolution.ts +15 -4
  31. package/src/lib/composer.ts +396 -135
  32. package/src/lib/launch-identity/canonical-contract.ts +36 -2
  33. package/src/lib/launch-identity/compiled-session-launch-plan.ts +2 -10
  34. package/src/lib/launch-identity/index.ts +2 -0
  35. package/src/lib/launch-identity/preparation-generation.ts +171 -0
  36. package/src/lib/launch-identity/prepared-launch-variant.ts +263 -0
  37. package/src/lib/launch-identity/runtime-template-key.ts +2 -7
  38. package/LICENSE +0 -176
@@ -1,4 +1,5 @@
1
1
  import { canonicalJson, stableHash } from '@xemahq/declarative-reconciler';
2
+ import { Sha256HexSchema } from '@xemahq/kernel-contracts/content-address';
2
3
 
3
4
  export type CanonicalJsonValue =
4
5
  | boolean
@@ -13,7 +14,13 @@ export interface ResourceBindingRef {
13
14
  readonly ref: string;
14
15
  }
15
16
 
16
- const SHA256_PATTERN = /^[a-f0-9]{64}$/u;
17
+ /**
18
+ * OCI immutable-digest prefix. The digest BODY is validated by the canonical
19
+ * {@link Sha256HexSchema} rather than a local `[a-f0-9]{64}` copy, so this
20
+ * package holds no SHA-256 grammar of its own.
21
+ */
22
+ const IMAGE_DIGEST_PREFIX = 'sha256:';
23
+
17
24
  const SECRET_FIELD_PATTERN =
18
25
  /(?:^|[_-])(api[_-]?key|authorization|cookie|credential|password|private[_-]?key|secret|token)(?:$|[_-])/iu;
19
26
  const SECRET_CAMEL_CASE_PATTERN =
@@ -70,12 +77,39 @@ export function requireBoolean(value: unknown, path: string): boolean {
70
77
 
71
78
  export function requireSha256(value: unknown, path: string): string {
72
79
  const hash = requireNonEmptyString(value, path);
73
- if (!SHA256_PATTERN.test(hash)) {
80
+ if (!Sha256HexSchema.safeParse(hash).success) {
74
81
  throw new TypeError(`${path} must be a lowercase SHA-256 hex digest.`);
75
82
  }
76
83
  return hash;
77
84
  }
78
85
 
86
+ /**
87
+ * Validates an OCI IMMUTABLE image reference — `sha256:<64 lowercase hex>`.
88
+ *
89
+ * The single site for this rule. It was previously spelled as an identical
90
+ * `IMAGE_DIGEST_PATTERN` regex in both `runtime-template-key.ts` and
91
+ * `compiled-session-launch-plan.ts`; two copies of a security-relevant
92
+ * "is this reference immutable" check can drift, and each was also a private
93
+ * restatement of the SHA-256 grammar that `@xemahq/kernel-contracts
94
+ * /content-address` now owns canonically.
95
+ */
96
+ export function requireImageDigest(value: unknown, path: string): string {
97
+ const digest = requireNonEmptyString(value, path);
98
+ if (!digest.startsWith(IMAGE_DIGEST_PREFIX)) {
99
+ throw new TypeError(
100
+ `${path} must be an immutable sha256 image digest.`,
101
+ );
102
+ }
103
+ if (
104
+ !Sha256HexSchema.safeParse(digest.slice(IMAGE_DIGEST_PREFIX.length)).success
105
+ ) {
106
+ throw new TypeError(
107
+ `${path} must be an immutable sha256 image digest.`,
108
+ );
109
+ }
110
+ return digest;
111
+ }
112
+
79
113
  export function requireNonNegativeSafeInteger(
80
114
  value: unknown,
81
115
  path: string,
@@ -6,6 +6,7 @@ import {
6
6
  requireCanonicalJsonObject,
7
7
  requireEnumValue,
8
8
  requireExactObject,
9
+ requireImageDigest,
9
10
  requireNonEmptyString,
10
11
  requireSha256,
11
12
  requireStringRefs,
@@ -155,7 +156,6 @@ const BINDING_SLOT_CARDINALITIES = Object.values(
155
156
  );
156
157
  const TENANT_DEPENDENCIES = Object.values(RuntimeTemplateTenantDependency);
157
158
  const TRUST_POLICIES = Object.values(RuntimeTemplateTrustPolicy);
158
- const IMAGE_DIGEST_PATTERN = /^sha256:[a-f0-9]{64}$/u;
159
159
  const BINDING_SLOT_NAME_PATTERN = /^[a-z][a-z0-9]*(?:[.-][a-z][a-z0-9]*)*$/u;
160
160
  const DYNAMIC_LITERAL_PATTERN =
161
161
  /\$\{\s*(?:input|tenant|org|organization|project|user|session|thread|domain|token|secret|credential)(?:[.:[\]}]|$)/iu;
@@ -377,15 +377,7 @@ function requireRuntimeRequirements(
377
377
  ): CompiledRuntimeRequirements {
378
378
  const path = 'CompiledSessionLaunchPlan.runtimeRequirements';
379
379
  const object = requireExactObject(value, path, RUNTIME_REQUIREMENT_KEYS);
380
- const imageDigest = requireNonEmptyString(
381
- object.imageDigest,
382
- `${path}.imageDigest`,
383
- );
384
- if (!IMAGE_DIGEST_PATTERN.test(imageDigest)) {
385
- throw new TypeError(
386
- `${path}.imageDigest must be an immutable sha256 image digest.`,
387
- );
388
- }
380
+ const imageDigest = requireImageDigest(object.imageDigest, `${path}.imageDigest`);
389
381
  const requirements = requireStaticPayload(
390
382
  object.requirements,
391
383
  `${path}.requirements`,
@@ -1,6 +1,8 @@
1
1
  export * from './canonical-contract';
2
2
  export * from './compiled-session-launch-plan';
3
3
  export * from './compiled-session-launch-plan-store';
4
+ export * from './preparation-generation';
5
+ export * from './prepared-launch-variant';
4
6
  export * from './runtime-template-key';
5
7
  export * from './session-binding';
6
8
  export * from './thread-reuse-key';
@@ -0,0 +1,171 @@
1
+ import {
2
+ canonicalContractJson,
3
+ hashCanonicalContract,
4
+ requireEnumValue,
5
+ requireExactObject,
6
+ requireNonEmptyString,
7
+ requireNonNegativeSafeInteger,
8
+ requireSha256,
9
+ } from './canonical-contract';
10
+
11
+ export enum PreparationGenerationStatus {
12
+ Validating = 'validating',
13
+ Materializing = 'materializing',
14
+ Ready = 'ready',
15
+ Failed = 'failed',
16
+ Superseded = 'superseded',
17
+ }
18
+
19
+ export interface PreparationFailure {
20
+ readonly code: string;
21
+ readonly message: string;
22
+ }
23
+
24
+ export interface PreparationGeneration {
25
+ readonly id: string;
26
+ readonly agentRevisionId: string;
27
+ readonly sourceGenerationHash: string;
28
+ readonly requiredVariantKeyHashes: readonly string[];
29
+ readonly status: PreparationGenerationStatus;
30
+ readonly failure?: PreparationFailure;
31
+ readonly rowVersion: number;
32
+ }
33
+
34
+ const GENERATION_KEYS = [
35
+ 'id',
36
+ 'agentRevisionId',
37
+ 'sourceGenerationHash',
38
+ 'requiredVariantKeyHashes',
39
+ 'status',
40
+ 'rowVersion',
41
+ ] as const;
42
+ const GENERATION_OPTIONAL_KEYS = ['failure'] as const;
43
+ const FAILURE_KEYS = ['code', 'message'] as const;
44
+ const GENERATION_STATUSES = Object.values(PreparationGenerationStatus);
45
+
46
+ export function createPreparationFailure(input: unknown): PreparationFailure {
47
+ const object = requireExactObject(input, 'PreparationFailure', FAILURE_KEYS);
48
+ return Object.freeze({
49
+ code: requireNonEmptyString(object.code, 'PreparationFailure.code'),
50
+ message: requireNonEmptyString(
51
+ object.message,
52
+ 'PreparationFailure.message',
53
+ ),
54
+ });
55
+ }
56
+
57
+ export function createPreparationGeneration(
58
+ input: unknown,
59
+ ): PreparationGeneration {
60
+ const object = requireExactObject(
61
+ input,
62
+ 'PreparationGeneration',
63
+ GENERATION_KEYS,
64
+ GENERATION_OPTIONAL_KEYS,
65
+ );
66
+ const status = requireEnumValue(
67
+ object.status,
68
+ 'PreparationGeneration.status',
69
+ GENERATION_STATUSES,
70
+ );
71
+ const failure = requireFailureForStatus(
72
+ object.failure,
73
+ Object.hasOwn(object, 'failure'),
74
+ status,
75
+ 'PreparationGeneration',
76
+ );
77
+
78
+ return Object.freeze({
79
+ id: requireNonEmptyString(object.id, 'PreparationGeneration.id'),
80
+ agentRevisionId: requireNonEmptyString(
81
+ object.agentRevisionId,
82
+ 'PreparationGeneration.agentRevisionId',
83
+ ),
84
+ sourceGenerationHash: requireSha256(
85
+ object.sourceGenerationHash,
86
+ 'PreparationGeneration.sourceGenerationHash',
87
+ ),
88
+ requiredVariantKeyHashes: requireSortedUniqueHashes(
89
+ object.requiredVariantKeyHashes,
90
+ 'PreparationGeneration.requiredVariantKeyHashes',
91
+ ),
92
+ status,
93
+ ...(failure === undefined ? {} : { failure }),
94
+ rowVersion: requireNonNegativeSafeInteger(
95
+ object.rowVersion,
96
+ 'PreparationGeneration.rowVersion',
97
+ ),
98
+ });
99
+ }
100
+
101
+ export function serializePreparationGeneration(
102
+ input: PreparationGeneration,
103
+ ): string {
104
+ return canonicalContractJson(createPreparationGeneration(input));
105
+ }
106
+
107
+ export function hashPreparationGeneration(
108
+ input: PreparationGeneration,
109
+ ): string {
110
+ return hashCanonicalContract(createPreparationGeneration(input));
111
+ }
112
+
113
+ export function requirePreparationFailureForStatus(
114
+ value: unknown,
115
+ hasFailure: boolean,
116
+ failed: boolean,
117
+ path: string,
118
+ ): PreparationFailure | undefined {
119
+ if (failed) {
120
+ if (!hasFailure) {
121
+ throw new TypeError(`${path}.failure is required when status is failed.`);
122
+ }
123
+ return createPreparationFailure(value);
124
+ }
125
+ if (hasFailure) {
126
+ throw new TypeError(
127
+ `${path}.failure is only supported when status is failed.`,
128
+ );
129
+ }
130
+ return undefined;
131
+ }
132
+
133
+ function requireFailureForStatus(
134
+ value: unknown,
135
+ hasFailure: boolean,
136
+ status: PreparationGenerationStatus,
137
+ path: string,
138
+ ): PreparationFailure | undefined {
139
+ return requirePreparationFailureForStatus(
140
+ value,
141
+ hasFailure,
142
+ status === PreparationGenerationStatus.Failed,
143
+ path,
144
+ );
145
+ }
146
+
147
+ function requireSortedUniqueHashes(
148
+ value: unknown,
149
+ path: string,
150
+ ): readonly string[] {
151
+ if (!Array.isArray(value)) {
152
+ throw new TypeError(`${path} must be an array.`);
153
+ }
154
+
155
+ const hashes = value.map((entry, index) =>
156
+ requireSha256(entry, `${path}[${index}]`),
157
+ );
158
+ for (let index = 1; index < hashes.length; index += 1) {
159
+ const previous = hashes[index - 1];
160
+ const current = hashes[index];
161
+ if (previous === undefined || current === undefined) {
162
+ throw new TypeError(`${path} contains an invalid sparse entry.`);
163
+ }
164
+ if (previous >= current) {
165
+ throw new TypeError(
166
+ `${path} must contain unique hashes in ascending lexical order.`,
167
+ );
168
+ }
169
+ }
170
+ return Object.freeze(hashes);
171
+ }
@@ -0,0 +1,263 @@
1
+ import { WorkerRuntimeKind } from '@xemahq/kernel-contracts/worker-runtime';
2
+
3
+ import {
4
+ canonicalContractJson,
5
+ hashCanonicalContract,
6
+ requireEnumValue,
7
+ requireExactObject,
8
+ requireImageDigest,
9
+ requireNonEmptyString,
10
+ requireNonNegativeSafeInteger,
11
+ requireSha256,
12
+ } from './canonical-contract';
13
+ import {
14
+ requirePreparationFailureForStatus,
15
+ type PreparationFailure,
16
+ } from './preparation-generation';
17
+ import {
18
+ RuntimeTemplateTenantDependency,
19
+ RuntimeTemplateTrustPolicy,
20
+ } from './runtime-template-key';
21
+
22
+ export enum PreparedLaunchVariantStatus {
23
+ Materializing = 'materializing',
24
+ Ready = 'ready',
25
+ Failed = 'failed',
26
+ Retired = 'retired',
27
+ }
28
+
29
+ export type PreparedLaunchTenantDependency =
30
+ | {
31
+ readonly kind: RuntimeTemplateTenantDependency.OrganizationNeutral;
32
+ }
33
+ | {
34
+ readonly kind: RuntimeTemplateTenantDependency.OrganizationBound;
35
+ readonly organizationScopeHash: string;
36
+ };
37
+
38
+ export interface PreparedLaunchVariantKey {
39
+ readonly schemaVersion: 1;
40
+ readonly runtimeKind: WorkerRuntimeKind;
41
+ readonly runtimeRevision: string;
42
+ readonly imageDigest: `sha256:${string}`;
43
+ readonly layoutProfileRef: string;
44
+ readonly staticDependencyClosureHash: string;
45
+ readonly tenantDependency: PreparedLaunchTenantDependency;
46
+ readonly trustPolicy: RuntimeTemplateTrustPolicy;
47
+ readonly policyGenerationHash: string;
48
+ readonly region: string;
49
+ }
50
+
51
+ export interface PreparedLaunchVariant {
52
+ readonly id: string;
53
+ readonly preparationGenerationId: string;
54
+ readonly key: PreparedLaunchVariantKey;
55
+ readonly keyHash: string;
56
+ readonly compiledLaunchPlanHash: string;
57
+ readonly preparedRuntimeTemplateHash: string;
58
+ readonly preparedRuntimeTemplateRef: string;
59
+ readonly status: PreparedLaunchVariantStatus;
60
+ readonly failure?: PreparationFailure;
61
+ readonly rowVersion: number;
62
+ }
63
+
64
+ const KEY_KEYS = [
65
+ 'schemaVersion',
66
+ 'runtimeKind',
67
+ 'runtimeRevision',
68
+ 'imageDigest',
69
+ 'layoutProfileRef',
70
+ 'staticDependencyClosureHash',
71
+ 'tenantDependency',
72
+ 'trustPolicy',
73
+ 'policyGenerationHash',
74
+ 'region',
75
+ ] as const;
76
+ const VARIANT_KEYS = [
77
+ 'id',
78
+ 'preparationGenerationId',
79
+ 'key',
80
+ 'keyHash',
81
+ 'compiledLaunchPlanHash',
82
+ 'preparedRuntimeTemplateHash',
83
+ 'preparedRuntimeTemplateRef',
84
+ 'status',
85
+ 'rowVersion',
86
+ ] as const;
87
+ const VARIANT_OPTIONAL_KEYS = ['failure'] as const;
88
+ const RUNTIME_KINDS = Object.values(WorkerRuntimeKind);
89
+ const TENANT_DEPENDENCY_KINDS = Object.values(RuntimeTemplateTenantDependency);
90
+ const TRUST_POLICIES = Object.values(RuntimeTemplateTrustPolicy);
91
+ const VARIANT_STATUSES = Object.values(PreparedLaunchVariantStatus);
92
+
93
+ export function createPreparedLaunchVariantKey(
94
+ input: unknown,
95
+ ): PreparedLaunchVariantKey {
96
+ const object = requireExactObject(
97
+ input,
98
+ 'PreparedLaunchVariantKey',
99
+ KEY_KEYS,
100
+ );
101
+ if (object.schemaVersion !== 1) {
102
+ throw new TypeError('PreparedLaunchVariantKey.schemaVersion must be 1.');
103
+ }
104
+
105
+ return Object.freeze({
106
+ schemaVersion: 1,
107
+ runtimeKind: requireEnumValue(
108
+ object.runtimeKind,
109
+ 'PreparedLaunchVariantKey.runtimeKind',
110
+ RUNTIME_KINDS,
111
+ ),
112
+ runtimeRevision: requireNonEmptyString(
113
+ object.runtimeRevision,
114
+ 'PreparedLaunchVariantKey.runtimeRevision',
115
+ ),
116
+ imageDigest: requireImageDigest(
117
+ object.imageDigest,
118
+ 'PreparedLaunchVariantKey.imageDigest',
119
+ ) as `sha256:${string}`,
120
+ layoutProfileRef: requireNonEmptyString(
121
+ object.layoutProfileRef,
122
+ 'PreparedLaunchVariantKey.layoutProfileRef',
123
+ ),
124
+ staticDependencyClosureHash: requireSha256(
125
+ object.staticDependencyClosureHash,
126
+ 'PreparedLaunchVariantKey.staticDependencyClosureHash',
127
+ ),
128
+ tenantDependency: requireTenantDependency(object.tenantDependency),
129
+ trustPolicy: requireEnumValue(
130
+ object.trustPolicy,
131
+ 'PreparedLaunchVariantKey.trustPolicy',
132
+ TRUST_POLICIES,
133
+ ),
134
+ policyGenerationHash: requireSha256(
135
+ object.policyGenerationHash,
136
+ 'PreparedLaunchVariantKey.policyGenerationHash',
137
+ ),
138
+ region: requireNonEmptyString(
139
+ object.region,
140
+ 'PreparedLaunchVariantKey.region',
141
+ ),
142
+ });
143
+ }
144
+
145
+ export function serializePreparedLaunchVariantKey(
146
+ input: PreparedLaunchVariantKey,
147
+ ): string {
148
+ return canonicalContractJson(createPreparedLaunchVariantKey(input));
149
+ }
150
+
151
+ export function hashPreparedLaunchVariantKey(
152
+ input: PreparedLaunchVariantKey,
153
+ ): string {
154
+ return hashCanonicalContract(createPreparedLaunchVariantKey(input));
155
+ }
156
+
157
+ export function createPreparedLaunchVariant(
158
+ input: unknown,
159
+ ): PreparedLaunchVariant {
160
+ const object = requireExactObject(
161
+ input,
162
+ 'PreparedLaunchVariant',
163
+ VARIANT_KEYS,
164
+ VARIANT_OPTIONAL_KEYS,
165
+ );
166
+ const key = createPreparedLaunchVariantKey(object.key);
167
+ const keyHash = requireSha256(
168
+ object.keyHash,
169
+ 'PreparedLaunchVariant.keyHash',
170
+ );
171
+ if (keyHash !== hashPreparedLaunchVariantKey(key)) {
172
+ throw new TypeError(
173
+ 'PreparedLaunchVariant.keyHash must equal the canonical key hash.',
174
+ );
175
+ }
176
+ const status = requireEnumValue(
177
+ object.status,
178
+ 'PreparedLaunchVariant.status',
179
+ VARIANT_STATUSES,
180
+ );
181
+ const failure = requirePreparationFailureForStatus(
182
+ object.failure,
183
+ Object.hasOwn(object, 'failure'),
184
+ status === PreparedLaunchVariantStatus.Failed,
185
+ 'PreparedLaunchVariant',
186
+ );
187
+
188
+ return Object.freeze({
189
+ id: requireNonEmptyString(object.id, 'PreparedLaunchVariant.id'),
190
+ preparationGenerationId: requireNonEmptyString(
191
+ object.preparationGenerationId,
192
+ 'PreparedLaunchVariant.preparationGenerationId',
193
+ ),
194
+ key,
195
+ keyHash,
196
+ compiledLaunchPlanHash: requireSha256(
197
+ object.compiledLaunchPlanHash,
198
+ 'PreparedLaunchVariant.compiledLaunchPlanHash',
199
+ ),
200
+ preparedRuntimeTemplateHash: requireSha256(
201
+ object.preparedRuntimeTemplateHash,
202
+ 'PreparedLaunchVariant.preparedRuntimeTemplateHash',
203
+ ),
204
+ preparedRuntimeTemplateRef: requireNonEmptyString(
205
+ object.preparedRuntimeTemplateRef,
206
+ 'PreparedLaunchVariant.preparedRuntimeTemplateRef',
207
+ ),
208
+ status,
209
+ ...(failure === undefined ? {} : { failure }),
210
+ rowVersion: requireNonNegativeSafeInteger(
211
+ object.rowVersion,
212
+ 'PreparedLaunchVariant.rowVersion',
213
+ ),
214
+ });
215
+ }
216
+
217
+ export function serializePreparedLaunchVariant(
218
+ input: PreparedLaunchVariant,
219
+ ): string {
220
+ return canonicalContractJson(createPreparedLaunchVariant(input));
221
+ }
222
+
223
+ export function hashPreparedLaunchVariant(
224
+ input: PreparedLaunchVariant,
225
+ ): string {
226
+ return hashCanonicalContract(createPreparedLaunchVariant(input));
227
+ }
228
+
229
+ function requireTenantDependency(
230
+ input: unknown,
231
+ ): PreparedLaunchTenantDependency {
232
+ const base = requireExactObject(
233
+ input,
234
+ 'PreparedLaunchVariantKey.tenantDependency',
235
+ ['kind'],
236
+ ['organizationScopeHash'],
237
+ );
238
+ const kind = requireEnumValue(
239
+ base.kind,
240
+ 'PreparedLaunchVariantKey.tenantDependency.kind',
241
+ TENANT_DEPENDENCY_KINDS,
242
+ );
243
+ if (kind === RuntimeTemplateTenantDependency.OrganizationNeutral) {
244
+ if (Object.hasOwn(base, 'organizationScopeHash')) {
245
+ throw new TypeError(
246
+ 'PreparedLaunchVariantKey.tenantDependency.organizationScopeHash is forbidden for organization-neutral variants.',
247
+ );
248
+ }
249
+ return Object.freeze({ kind });
250
+ }
251
+ if (!Object.hasOwn(base, 'organizationScopeHash')) {
252
+ throw new TypeError(
253
+ 'PreparedLaunchVariantKey.tenantDependency.organizationScopeHash is required for organization-bound variants.',
254
+ );
255
+ }
256
+ return Object.freeze({
257
+ kind,
258
+ organizationScopeHash: requireSha256(
259
+ base.organizationScopeHash,
260
+ 'PreparedLaunchVariantKey.tenantDependency.organizationScopeHash',
261
+ ),
262
+ });
263
+ }
@@ -4,6 +4,7 @@ import {
4
4
  hashCanonicalContract,
5
5
  requireEnumValue,
6
6
  requireExactObject,
7
+ requireImageDigest,
7
8
  requireNonEmptyString,
8
9
  requireSha256,
9
10
  } from './canonical-contract';
@@ -57,7 +58,6 @@ const REQUIRED_KEYS = [
57
58
 
58
59
  const TENANT_DEPENDENCIES = Object.values(RuntimeTemplateTenantDependency);
59
60
  const TRUST_POLICIES = Object.values(RuntimeTemplateTrustPolicy);
60
- const IMAGE_DIGEST_PATTERN = /^sha256:[a-f0-9]{64}$/u;
61
61
 
62
62
  /**
63
63
  * Validates and canonicalizes the immutable execution dimensions used to
@@ -72,15 +72,10 @@ export function createRuntimeTemplateKey(input: unknown): RuntimeTemplateKey {
72
72
  throw new TypeError('RuntimeTemplateKey.schemaVersion must be 1.');
73
73
  }
74
74
 
75
- const imageDigest = requireNonEmptyString(
75
+ const imageDigest = requireImageDigest(
76
76
  object.imageDigest,
77
77
  'RuntimeTemplateKey.imageDigest',
78
78
  );
79
- if (!IMAGE_DIGEST_PATTERN.test(imageDigest)) {
80
- throw new TypeError(
81
- 'RuntimeTemplateKey.imageDigest must be an immutable sha256 image digest.',
82
- );
83
- }
84
79
 
85
80
  return Object.freeze({
86
81
  schemaVersion: 1,