@futdevpro/fsm-dynamo 1.20.2 → 1.20.5
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 +28 -8
- package/__documentations/2026-08-22-angular-20-phase1-release.md +39 -39
- package/__documentations/2026-08-23-atomic-rollback-manifest-contract.md +52 -0
- package/build/_modules/deployment/_collections/atomic-rollback-manifest.util.d.ts +9 -0
- package/build/_modules/deployment/_collections/atomic-rollback-manifest.util.d.ts.map +1 -0
- package/build/_modules/deployment/_collections/atomic-rollback-manifest.util.js +354 -0
- package/build/_modules/deployment/_collections/atomic-rollback-manifest.util.js.map +1 -0
- package/build/_modules/deployment/_models/atomic-rollback-manifest.interface.d.ts +54 -0
- package/build/_modules/deployment/_models/atomic-rollback-manifest.interface.d.ts.map +1 -0
- package/build/_modules/deployment/_models/atomic-rollback-manifest.interface.js +3 -0
- package/build/_modules/deployment/_models/atomic-rollback-manifest.interface.js.map +1 -0
- package/build/_modules/deployment/index.d.ts +3 -0
- package/build/_modules/deployment/index.d.ts.map +1 -0
- package/build/_modules/deployment/index.js +6 -0
- package/build/_modules/deployment/index.js.map +1 -0
- package/package.json +10 -1
- package/src/_modules/crypto/_services/artifact-signature.control-service.spec.ts +2 -2
- package/src/_modules/crypto/_services/artifact-signature.control-service.ts +10 -10
- package/src/_modules/deployment/_collections/atomic-rollback-manifest.util.spec.ts +221 -0
- package/src/_modules/deployment/_collections/atomic-rollback-manifest.util.ts +455 -0
- package/src/_modules/deployment/_models/atomic-rollback-manifest.interface.ts +59 -0
- package/src/_modules/deployment/index.ts +2 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/_modules/deployment/index.ts"],"names":[],"mappings":"AAAA,cAAc,8CAA8C,CAAC;AAC7D,cAAc,8CAA8C,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const tslib_1 = require("tslib");
|
|
4
|
+
tslib_1.__exportStar(require("./_collections/atomic-rollback-manifest.util"), exports);
|
|
5
|
+
tslib_1.__exportStar(require("./_models/atomic-rollback-manifest.interface"), exports);
|
|
6
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/_modules/deployment/index.ts"],"names":[],"mappings":";;;AAAA,uFAA6D;AAC7D,uFAA6D"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@futdevpro/fsm-dynamo",
|
|
3
|
-
"version": "1.20.
|
|
3
|
+
"version": "1.20.5",
|
|
4
4
|
"description": "Full Stack Model Collection for Dynamic (NodeJS-Typescript) Framework called Dynamo, by Future Development Ltd.",
|
|
5
5
|
"DyBu_settings": {
|
|
6
6
|
"packageType": "full-stack-package",
|
|
@@ -124,6 +124,12 @@
|
|
|
124
124
|
"types": "./build/_modules/crypto/index.d.ts",
|
|
125
125
|
"typings": "./build/_modules/crypto/index.d.ts"
|
|
126
126
|
},
|
|
127
|
+
"./deployment": {
|
|
128
|
+
"default": "./build/_modules/deployment/index.js",
|
|
129
|
+
"module": "./build/_modules/deployment/index.js",
|
|
130
|
+
"types": "./build/_modules/deployment/index.d.ts",
|
|
131
|
+
"typings": "./build/_modules/deployment/index.d.ts"
|
|
132
|
+
},
|
|
127
133
|
"./custom-data": {
|
|
128
134
|
"default": "./build/_modules/custom-data/index.js",
|
|
129
135
|
"module": "./build/_modules/custom-data/index.js",
|
|
@@ -232,6 +238,9 @@
|
|
|
232
238
|
"crypto": [
|
|
233
239
|
"build/_modules/crypto/index.d.ts"
|
|
234
240
|
],
|
|
241
|
+
"deployment": [
|
|
242
|
+
"build/_modules/deployment/index.d.ts"
|
|
243
|
+
],
|
|
235
244
|
"custom-data": [
|
|
236
245
|
"build/_modules/custom-data/index.d.ts"
|
|
237
246
|
],
|
|
@@ -13,7 +13,7 @@ interface DyFM_ArtifactSignatureSpecKeyMaterial {
|
|
|
13
13
|
describe('| DyFM artifact signature', (): void => {
|
|
14
14
|
const signature_CS: DyFM_ArtifactSignature_ControlService =
|
|
15
15
|
new DyFM_ArtifactSignature_ControlService();
|
|
16
|
-
const subjectBytes: Uint8Array<ArrayBuffer> = new TextEncoder().encode(
|
|
16
|
+
const subjectBytes: Uint8Array<ArrayBuffer> = new TextEncoder().encode(
|
|
17
17
|
'{"contractVersion":"ctm-artifact-canonical/1","bundleSource":"exact"}'
|
|
18
18
|
);
|
|
19
19
|
let keyMaterial: DyFM_ArtifactSignatureSpecKeyMaterial;
|
|
@@ -182,7 +182,7 @@ describe('| DyFM artifact signature', (): void => {
|
|
|
182
182
|
}
|
|
183
183
|
|
|
184
184
|
/** Calculates lowercase SHA-256 through the same platform boundary as production. */
|
|
185
|
-
async function checksum(bytes: Uint8Array<ArrayBuffer>): Promise<string> {
|
|
185
|
+
async function checksum(bytes: Uint8Array<ArrayBuffer>): Promise<string> {
|
|
186
186
|
const digest: ArrayBuffer = await globalThis.crypto.subtle.digest('SHA-256', bytes);
|
|
187
187
|
|
|
188
188
|
return [ ...new Uint8Array(digest) ]
|
|
@@ -34,14 +34,14 @@ export class DyFM_ArtifactSignature_ControlService {
|
|
|
34
34
|
this.validateTimestamp(set.signedAt);
|
|
35
35
|
|
|
36
36
|
const subtle: SubtleCrypto = this.getSubtleCrypto();
|
|
37
|
-
const subjectBytes: Uint8Array<ArrayBuffer> = new Uint8Array(set.subjectBytes);
|
|
37
|
+
const subjectBytes: Uint8Array<ArrayBuffer> = new Uint8Array(set.subjectBytes);
|
|
38
38
|
const actualChecksum: string = await this.checksum(subjectBytes);
|
|
39
39
|
|
|
40
40
|
if (actualChecksum !== set.subjectChecksum) {
|
|
41
41
|
throw this.buildError('SUBJECT_CHECKSUM_MISMATCH');
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
const privateKeyBytes: Uint8Array<ArrayBuffer> = this.decodeBase64UrlStrict(set.privateKeyPkcs8Base64Url);
|
|
44
|
+
const privateKeyBytes: Uint8Array<ArrayBuffer> = this.decodeBase64UrlStrict(set.privateKeyPkcs8Base64Url);
|
|
45
45
|
|
|
46
46
|
if (privateKeyBytes.byteLength < 64 || privateKeyBytes.byteLength > 512) {
|
|
47
47
|
throw this.buildError('PRIVATE_KEY_LENGTH_INVALID');
|
|
@@ -73,7 +73,7 @@ export class DyFM_ArtifactSignature_ControlService {
|
|
|
73
73
|
privateKeyBytes.fill(0);
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
let signatureBytes: Uint8Array<ArrayBuffer>;
|
|
76
|
+
let signatureBytes: Uint8Array<ArrayBuffer>;
|
|
77
77
|
|
|
78
78
|
try {
|
|
79
79
|
signatureBytes = new Uint8Array(await subtle.sign(
|
|
@@ -128,7 +128,7 @@ export class DyFM_ArtifactSignature_ControlService {
|
|
|
128
128
|
return this.result(set.evidence, false, 'DYFM|ARTIFACT_SIGNATURE|SUBJECT_INVALID');
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
-
const subjectBytes: Uint8Array<ArrayBuffer> = new Uint8Array(set.subjectBytes);
|
|
131
|
+
const subjectBytes: Uint8Array<ArrayBuffer> = new Uint8Array(set.subjectBytes);
|
|
132
132
|
const actualChecksum: string = await this.checksum(subjectBytes);
|
|
133
133
|
|
|
134
134
|
if (actualChecksum !== set.evidence.subjectChecksum) {
|
|
@@ -136,7 +136,7 @@ export class DyFM_ArtifactSignature_ControlService {
|
|
|
136
136
|
}
|
|
137
137
|
|
|
138
138
|
let publicKeyJwk: JsonWebKey;
|
|
139
|
-
let signatureBytes: Uint8Array<ArrayBuffer>;
|
|
139
|
+
let signatureBytes: Uint8Array<ArrayBuffer>;
|
|
140
140
|
|
|
141
141
|
try {
|
|
142
142
|
publicKeyJwk = this.validatePublicJwk(set.publicKeyJwk);
|
|
@@ -193,7 +193,7 @@ export class DyFM_ArtifactSignature_ControlService {
|
|
|
193
193
|
return this.checksum(new TextEncoder().encode(canonicalJwk));
|
|
194
194
|
}
|
|
195
195
|
|
|
196
|
-
private async checksum(bytes: Uint8Array<ArrayBuffer>): Promise<string> {
|
|
196
|
+
private async checksum(bytes: Uint8Array<ArrayBuffer>): Promise<string> {
|
|
197
197
|
const digest: ArrayBuffer = await this.getSubtleCrypto().digest('SHA-256', bytes);
|
|
198
198
|
|
|
199
199
|
return [ ...new Uint8Array(digest) ]
|
|
@@ -201,7 +201,7 @@ export class DyFM_ArtifactSignature_ControlService {
|
|
|
201
201
|
.join('');
|
|
202
202
|
}
|
|
203
203
|
|
|
204
|
-
private decodeBase64UrlStrict(value: string): Uint8Array<ArrayBuffer> {
|
|
204
|
+
private decodeBase64UrlStrict(value: string): Uint8Array<ArrayBuffer> {
|
|
205
205
|
if (!value || !/^[A-Za-z0-9_-]+$/u.test(value) || value.length % 4 === 1) {
|
|
206
206
|
throw this.buildError('BASE64URL_INVALID');
|
|
207
207
|
}
|
|
@@ -230,7 +230,7 @@ export class DyFM_ArtifactSignature_ControlService {
|
|
|
230
230
|
}
|
|
231
231
|
}
|
|
232
232
|
|
|
233
|
-
const decoded: Uint8Array<ArrayBuffer> = new Uint8Array(bytes);
|
|
233
|
+
const decoded: Uint8Array<ArrayBuffer> = new Uint8Array(bytes);
|
|
234
234
|
|
|
235
235
|
if (this.encodeBase64Url(decoded) !== value) {
|
|
236
236
|
throw this.buildError('BASE64URL_NON_CANONICAL');
|
|
@@ -360,8 +360,8 @@ export class DyFM_ArtifactSignature_ControlService {
|
|
|
360
360
|
throw this.buildError('PUBLIC_KEY_INVALID');
|
|
361
361
|
}
|
|
362
362
|
|
|
363
|
-
const xBytes: Uint8Array<ArrayBuffer> = this.decodeBase64UrlStrict(value.x);
|
|
364
|
-
const yBytes: Uint8Array<ArrayBuffer> = this.decodeBase64UrlStrict(value.y);
|
|
363
|
+
const xBytes: Uint8Array<ArrayBuffer> = this.decodeBase64UrlStrict(value.x);
|
|
364
|
+
const yBytes: Uint8Array<ArrayBuffer> = this.decodeBase64UrlStrict(value.y);
|
|
365
365
|
|
|
366
366
|
if (xBytes.byteLength !== 32 || yBytes.byteLength !== 32) {
|
|
367
367
|
throw this.buildError('PUBLIC_KEY_COORDINATE_INVALID');
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { DyFM_AtomicRollbackManifest_Util } from './atomic-rollback-manifest.util';
|
|
2
|
+
import {
|
|
3
|
+
DyFM_AtomicRollbackImage_Interface,
|
|
4
|
+
DyFM_AtomicRollbackLocalValidation_Interface,
|
|
5
|
+
DyFM_AtomicRollbackManifest_Interface,
|
|
6
|
+
DyFM_AtomicRollbackPostCheck_Interface
|
|
7
|
+
} from '../_models/atomic-rollback-manifest.interface';
|
|
8
|
+
|
|
9
|
+
/** Builds one fully valid manifest for contract mutation tests. */
|
|
10
|
+
const dyfm_createManifest: () => DyFM_AtomicRollbackManifest_Interface =
|
|
11
|
+
(): DyFM_AtomicRollbackManifest_Interface => {
|
|
12
|
+
const manifest: DyFM_AtomicRollbackManifest_Interface = {
|
|
13
|
+
contractVersion: 'fdp-atomic-rollback-manifest/1',
|
|
14
|
+
operationId: `rollback_${'1'.repeat(32)}`,
|
|
15
|
+
environment: 'test',
|
|
16
|
+
authority: {
|
|
17
|
+
principalId: 'fdp-cdp',
|
|
18
|
+
evidenceId: '2'.repeat(64),
|
|
19
|
+
},
|
|
20
|
+
sourceRevision: '3'.repeat(40),
|
|
21
|
+
configuration: {
|
|
22
|
+
revision: '4'.repeat(40),
|
|
23
|
+
sha256: '5'.repeat(64),
|
|
24
|
+
compatibilityVersion: 'ctm-db/1',
|
|
25
|
+
policyChecksum: '6'.repeat(64),
|
|
26
|
+
},
|
|
27
|
+
images: [
|
|
28
|
+
{
|
|
29
|
+
service: 'company-toolmaker-server',
|
|
30
|
+
version: '0.0.1',
|
|
31
|
+
immutableReference: 'registry.example/company-toolmaker-server:release-0.0.1',
|
|
32
|
+
mutableReference: 'registry.example/company-toolmaker-server:release',
|
|
33
|
+
digest: `sha256:${'7'.repeat(64)}`,
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
service: 'company-toolmaker-client',
|
|
37
|
+
version: '0.0.1',
|
|
38
|
+
immutableReference: 'registry.example/company-toolmaker-client:release-0.0.1',
|
|
39
|
+
mutableReference: 'registry.example/company-toolmaker-client:release',
|
|
40
|
+
digest: `sha256:${'8'.repeat(64)}`,
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
postChecks: [
|
|
44
|
+
{
|
|
45
|
+
checkId: 'server-readiness',
|
|
46
|
+
service: 'company-toolmaker-server',
|
|
47
|
+
endpoint: 'https://test-company-toolmaker.futdevpro.hu/readiness',
|
|
48
|
+
expectedProbeContract: 'ctm-service-probe/4',
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
checkId: 'client-smoke',
|
|
52
|
+
service: 'company-toolmaker-client',
|
|
53
|
+
endpoint: 'https://test-company-toolmaker.futdevpro.hu/',
|
|
54
|
+
expectedProbeContract: 'ctm-client-smoke/1',
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
targetChecksum: '',
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
manifest.targetChecksum = DyFM_AtomicRollbackManifest_Util.computeTargetChecksum(manifest);
|
|
61
|
+
|
|
62
|
+
return manifest;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
describe('| DyFM atomic rollback manifest', (): void => {
|
|
66
|
+
it('| validates an exact manifest with order-independent service and post-check sets', (): void => {
|
|
67
|
+
const manifest: DyFM_AtomicRollbackManifest_Interface = dyfm_createManifest();
|
|
68
|
+
const checksum: string = manifest.targetChecksum;
|
|
69
|
+
|
|
70
|
+
manifest.images.reverse();
|
|
71
|
+
manifest.postChecks.reverse();
|
|
72
|
+
|
|
73
|
+
expect(DyFM_AtomicRollbackManifest_Util.computeTargetChecksum(manifest)).toBe(checksum);
|
|
74
|
+
expect(checksum).toBe('2b416434a8d36c5b3305383eb1f2ecebae4057d30d2f46d986d04728225b58df');
|
|
75
|
+
expect(DyFM_AtomicRollbackManifest_Util.validate(manifest)).toEqual(jasmine.objectContaining({
|
|
76
|
+
valid: true,
|
|
77
|
+
environment: 'test',
|
|
78
|
+
operationId: manifest.operationId,
|
|
79
|
+
targetChecksum: checksum,
|
|
80
|
+
serviceCount: 2,
|
|
81
|
+
postCheckCount: 2,
|
|
82
|
+
gapCodes: [],
|
|
83
|
+
}));
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('| ignores nested object insertion order when calculating the canonical checksum', (): void => {
|
|
87
|
+
const manifest: DyFM_AtomicRollbackManifest_Interface = dyfm_createManifest();
|
|
88
|
+
const reordered: DyFM_AtomicRollbackManifest_Interface = {
|
|
89
|
+
...manifest,
|
|
90
|
+
authority: {
|
|
91
|
+
evidenceId: manifest.authority.evidenceId,
|
|
92
|
+
principalId: manifest.authority.principalId,
|
|
93
|
+
},
|
|
94
|
+
configuration: {
|
|
95
|
+
policyChecksum: manifest.configuration.policyChecksum,
|
|
96
|
+
compatibilityVersion: manifest.configuration.compatibilityVersion,
|
|
97
|
+
sha256: manifest.configuration.sha256,
|
|
98
|
+
revision: manifest.configuration.revision,
|
|
99
|
+
},
|
|
100
|
+
images: manifest.images.map((
|
|
101
|
+
image: DyFM_AtomicRollbackImage_Interface,
|
|
102
|
+
): DyFM_AtomicRollbackImage_Interface => ({
|
|
103
|
+
digest: image.digest,
|
|
104
|
+
mutableReference: image.mutableReference,
|
|
105
|
+
immutableReference: image.immutableReference,
|
|
106
|
+
version: image.version,
|
|
107
|
+
service: image.service,
|
|
108
|
+
})),
|
|
109
|
+
postChecks: manifest.postChecks.map((
|
|
110
|
+
check: DyFM_AtomicRollbackPostCheck_Interface,
|
|
111
|
+
): DyFM_AtomicRollbackPostCheck_Interface => ({
|
|
112
|
+
expectedProbeContract: check.expectedProbeContract,
|
|
113
|
+
endpoint: check.endpoint,
|
|
114
|
+
service: check.service,
|
|
115
|
+
checkId: check.checkId,
|
|
116
|
+
})),
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
expect(DyFM_AtomicRollbackManifest_Util.computeTargetChecksum(reordered)).toBe(manifest.targetChecksum);
|
|
120
|
+
expect(DyFM_AtomicRollbackManifest_Util.validate(reordered).valid).toBeTrue();
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('| rejects missing, duplicated and non-targeted service members', (): void => {
|
|
124
|
+
const missing: DyFM_AtomicRollbackManifest_Interface = dyfm_createManifest();
|
|
125
|
+
|
|
126
|
+
missing.images.pop();
|
|
127
|
+
const duplicated: DyFM_AtomicRollbackManifest_Interface = dyfm_createManifest();
|
|
128
|
+
|
|
129
|
+
duplicated.images[1] = { ...duplicated.images[0] };
|
|
130
|
+
|
|
131
|
+
expect(DyFM_AtomicRollbackManifest_Util.validate(missing).gapCodes).toContain(
|
|
132
|
+
'FDP|ATOMIC_ROLLBACK|POST_CHECK_SERVICE_NOT_TARGETED',
|
|
133
|
+
);
|
|
134
|
+
expect(DyFM_AtomicRollbackManifest_Util.validate(duplicated).gapCodes).toContain(
|
|
135
|
+
'FDP|ATOMIC_ROLLBACK|IMAGE_SERVICE_DUPLICATED',
|
|
136
|
+
);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('| rejects tag, version, digest and repository substitution', (): void => {
|
|
140
|
+
const mutable: DyFM_AtomicRollbackManifest_Interface = dyfm_createManifest();
|
|
141
|
+
|
|
142
|
+
mutable.images[0].immutableReference = 'registry.example/company-toolmaker-server:release';
|
|
143
|
+
const versionMismatch: DyFM_AtomicRollbackManifest_Interface = dyfm_createManifest();
|
|
144
|
+
|
|
145
|
+
versionMismatch.images[0].version = '0.0.2';
|
|
146
|
+
const digestInvalid: DyFM_AtomicRollbackManifest_Interface = dyfm_createManifest();
|
|
147
|
+
|
|
148
|
+
digestInvalid.images[0].digest = 'sha256:not-a-digest';
|
|
149
|
+
const repositoryMismatch: DyFM_AtomicRollbackManifest_Interface = dyfm_createManifest();
|
|
150
|
+
|
|
151
|
+
repositoryMismatch.images[0].mutableReference = 'other.example/company-toolmaker-server:release';
|
|
152
|
+
|
|
153
|
+
expect(DyFM_AtomicRollbackManifest_Util.validate(mutable).gapCodes).toContain(
|
|
154
|
+
'FDP|ATOMIC_ROLLBACK|IMAGE_IMMUTABLE_REFERENCE_INVALID|company-toolmaker-server',
|
|
155
|
+
);
|
|
156
|
+
expect(DyFM_AtomicRollbackManifest_Util.validate(versionMismatch).gapCodes).toContain(
|
|
157
|
+
'FDP|ATOMIC_ROLLBACK|IMAGE_IMMUTABLE_REFERENCE_INVALID|company-toolmaker-server',
|
|
158
|
+
);
|
|
159
|
+
expect(DyFM_AtomicRollbackManifest_Util.validate(digestInvalid).gapCodes).toContain(
|
|
160
|
+
'FDP|ATOMIC_ROLLBACK|IMAGE_DIGEST_INVALID|company-toolmaker-server',
|
|
161
|
+
);
|
|
162
|
+
expect(DyFM_AtomicRollbackManifest_Util.validate(repositoryMismatch).gapCodes).toContain(
|
|
163
|
+
'FDP|ATOMIC_ROLLBACK|IMAGE_REPOSITORY_MISMATCH|company-toolmaker-server',
|
|
164
|
+
);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('| rejects configuration, authority and target-checksum substitution', (): void => {
|
|
168
|
+
const configuration: DyFM_AtomicRollbackManifest_Interface = dyfm_createManifest();
|
|
169
|
+
|
|
170
|
+
configuration.configuration.sha256 = '9'.repeat(64);
|
|
171
|
+
const authority: DyFM_AtomicRollbackManifest_Interface = dyfm_createManifest();
|
|
172
|
+
|
|
173
|
+
authority.authority.evidenceId = 'not-evidence';
|
|
174
|
+
const checksum: DyFM_AtomicRollbackManifest_Interface = dyfm_createManifest();
|
|
175
|
+
|
|
176
|
+
checksum.targetChecksum = 'a'.repeat(64);
|
|
177
|
+
|
|
178
|
+
expect(DyFM_AtomicRollbackManifest_Util.validate(configuration).gapCodes).toEqual([
|
|
179
|
+
'FDP|ATOMIC_ROLLBACK|TARGET_CHECKSUM_MISMATCH',
|
|
180
|
+
]);
|
|
181
|
+
expect(DyFM_AtomicRollbackManifest_Util.validate(authority).gapCodes).toContain(
|
|
182
|
+
'FDP|ATOMIC_ROLLBACK|AUTHORITY_EVIDENCE_INVALID',
|
|
183
|
+
);
|
|
184
|
+
expect(DyFM_AtomicRollbackManifest_Util.validate(checksum).gapCodes).toEqual([
|
|
185
|
+
'FDP|ATOMIC_ROLLBACK|TARGET_CHECKSUM_MISMATCH',
|
|
186
|
+
]);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it('| rejects oversized operational references before they can expand evidence or work', (): void => {
|
|
190
|
+
const imageReference: DyFM_AtomicRollbackManifest_Interface = dyfm_createManifest();
|
|
191
|
+
|
|
192
|
+
imageReference.images[0].immutableReference =
|
|
193
|
+
`registry.example/${'segment/'.repeat(70)}company-toolmaker-server:release-0.0.1`;
|
|
194
|
+
imageReference.targetChecksum = DyFM_AtomicRollbackManifest_Util.computeTargetChecksum(imageReference);
|
|
195
|
+
const endpoint: DyFM_AtomicRollbackManifest_Interface = dyfm_createManifest();
|
|
196
|
+
|
|
197
|
+
endpoint.postChecks[0].endpoint = `https://${'a'.repeat(2040)}.example/readiness`;
|
|
198
|
+
endpoint.targetChecksum = DyFM_AtomicRollbackManifest_Util.computeTargetChecksum(endpoint);
|
|
199
|
+
|
|
200
|
+
expect(DyFM_AtomicRollbackManifest_Util.validate(imageReference).gapCodes).toContain(
|
|
201
|
+
'FDP|ATOMIC_ROLLBACK|IMAGE_IMMUTABLE_REFERENCE_INVALID|company-toolmaker-server',
|
|
202
|
+
);
|
|
203
|
+
expect(DyFM_AtomicRollbackManifest_Util.validate(endpoint).gapCodes).toContain(
|
|
204
|
+
'FDP|ATOMIC_ROLLBACK|POST_CHECK_ENDPOINT_INVALID|server-readiness',
|
|
205
|
+
);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it('| never reflects unknown content in validation evidence', (): void => {
|
|
209
|
+
const candidate: Record<string, unknown> = {
|
|
210
|
+
...dyfm_createManifest(),
|
|
211
|
+
workbookName: 'sensitive-customer.xlsx',
|
|
212
|
+
};
|
|
213
|
+
const report: DyFM_AtomicRollbackLocalValidation_Interface = DyFM_AtomicRollbackManifest_Util.validate(candidate);
|
|
214
|
+
const serialized: string = JSON.stringify(report);
|
|
215
|
+
|
|
216
|
+
expect(report.gapCodes).toEqual([ 'FDP|ATOMIC_ROLLBACK|MANIFEST_SHAPE_INVALID' ]);
|
|
217
|
+
expect(serialized).not.toContain('sensitive-customer');
|
|
218
|
+
expect(serialized).not.toContain('registry.example');
|
|
219
|
+
expect(serialized).not.toContain('workbookName');
|
|
220
|
+
});
|
|
221
|
+
});
|