Repository: spacetelescope/rad (language: python)

Issue:
Schema use of `datatype` allows safe castable datatypes
rad makes extensive use of `datatype` validation in schemas. For example the `dq` sub-schema defines a `datatype`:
https://github.com/spacetelescope/rad/blob/269b368eef1a41cfd742a8939651eaa846437af0/src/rad/resources/schemas/wfi_image-1.0.0.yaml#L35-L38
The `datatype` is not part of the standard JSONSchema and is instead validated by `asdf`.

By default `asdf` will allow datatypes that can be safely cast to the datatype listed in the schema (assigning a `uint8` to `dq` is valid). This allowance of "safe-castable" datatypes can be disabled by adding `exact_datatype: true` to the schema containing the `datatype` definition.

@schlafly Is this allowance of "safe-castable" datatypes intended or a bug?


Discussion / hints:
This issue and the solution was noted in https://github.com/spacetelescope/roman_datamodels/issues/27
Thanks Brett.  Sorry for being slow.  I presume the safe-casting actually occurs, so that if I make some uint8s and try to put them in dq the uint8s get silently casted to uint32s?  I think we can safely call that a feature rather than a bug.  I guess @PaulHuwe is the original reporter, so he may feel differently?
@schlafly Thanks for the response. Neither `roman_datamodels` nor `asdf` will cast the dtype.

To expand on the the `wfi_image` schema example, where `datatype` is `uint32` for `dq`. This means that assigning a `uint8` is valid and results in a 'uint8' saved to the file:

```python
m.dq = np.zeros(m.dq.shape, dtype='uint8')  # this is valid
assert m.dq.dtype == 'uint8'  # dq is now uint8
m.save('foo.asdf')
m = rdm.open('foo.asdf')
assert m.dq.dtype == 'uint8'  # and is still uint8 after a write/read
```

asdf doesn't cast the values it merely [checks the dtypes can be safely cast](https://github.com/asdf-format/asdf/blob/7ae2d7eb198b4ef433eed6be8394a18ac22af619/asdf/tags/core/ndarray.py#L548).
Ah, thanks.  My sense is that we probably want the schema to be right; if we say that there are uint32s in there that better be what they are.  For these kind of safe-castable things I'd be on board with the safe-casting occurring automatically but less on board if we are allowed to write out files that aren't what we say they are.  I presume "automatic safe cast to correct type" isn't an option, so I guess I'd be arguing for not allowing safe casting?
Unfortunately, I'm not aware of any way to do an automatic safe cast with `asdf` or `roman_datamodels`. Perhaps a plan to address this issue would be:
- update the schemas to use `exact_datatype: true`
- run the regression tests to see if any failures occur due to the more strict dtype requirement

A "complete" fix may be more involved as I just ran a quick test here where I added `exact_datatype: true` to the `dq` portion of the `wfi_image` schema. Running the above example:
```python
m.dq = np.zeros(m.dq.shape, dtype='uint8')  # this still passes and likely should not given the validation on assignment
m.save('foo.asdf')  # with exact_datatype this now raises a ValidationError
```

It turns out things are worse than expected for assignment validation where even float and complex dtypes work.
```python
m.dq = np.zeros(m.dq.shape, dtype='float64')   # no issue
m.dq = np.zeros(m.dq.shape, dtype='complex64')   # no issue
```

This appears to be due to `roman_datamodels` using only one custom `type` validator and the default yaml validators (which do not include the `datatype` validator).
https://github.com/spacetelescope/roman_datamodels/blob/2706ac1e502c9fdd98073145e6213c51b2515c31/src/roman_datamodels/stnode/_node.py#L26-L27

That seems like a separate roman_datamodels issue (where it is not doing any datatype validation on assignment) and fixing the schemas will at least allow asdf to catch the error when the file is written.
Thanks for looking at this.  For my two cents, I don't really feel strongly about validate-on-assignment vs. validate-on-save; either way it won't be long until the problem bites us.  The nicest feature of validate-on-assignment is that when we fail we'll see exactly where we failed, but I wouldn't go through too many contortions to achieve that.

I agree with you that fixing the schemas sounds like the way to go at this point, ignoring the roman_datamodels issue (which also of course is seeing a lot of flux at the moment...).
I agree with adding the `exact_datatype: true` line to the appropriate metadata keywords. I have made ticket RAD-148 in Jira for this work.
_This issue is tracked on JIRA as [RAD-148](https://jira.stsci.edu/browse/RAD-148)._

Fix patch:
diff --git a/CHANGES.rst b/CHANGES.rst
index 7f5978f7..b645cfce 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -3,6 +3,8 @@
 
 - Convert tag keywords to wildcards for external tags. [#370]
 
+- Added ``exact_datatype`` arguments to prevent ASDF from casting array
+  datatypes during save. [#369]
 
 0.19.0 (2024-02-09)
 -------------------
@@ -13,7 +15,6 @@
 
 - Add wcs tag to wfi_image and wfi_mosaic schemas. [#351]
 
-
 0.18.0 (2023-11-06)
 -------------------
 
diff --git a/src/rad/resources/schemas/guidewindow-1.0.0.yaml b/src/rad/resources/schemas/guidewindow-1.0.0.yaml
index bd0f8e60..3251710a 100644
--- a/src/rad/resources/schemas/guidewindow-1.0.0.yaml
+++ b/src/rad/resources/schemas/guidewindow-1.0.0.yaml
@@ -213,6 +213,7 @@ properties:
       value:
         tag: tag:stsci.edu:asdf/core/ndarray-1.*
         datatype: uint16
+        exact_datatype: true
         ndim: 5
       unit:
         tag: tag:astropy.org:astropy/units/unit-1.*
@@ -226,6 +227,7 @@ properties:
       value:
         tag: tag:stsci.edu:asdf/core/ndarray-1.*
         datatype: uint16
+        exact_datatype: true
         ndim: 5
       unit:
         tag: tag:astropy.org:astropy/units/unit-1.*
@@ -239,6 +241,7 @@ properties:
       value:
         tag: tag:stsci.edu:asdf/core/ndarray-1.*
         datatype: uint16
+        exact_datatype: true
         ndim: 5
       unit:
         tag: tag:astropy.org:astropy/units/unit-1.*
diff --git a/src/rad/resources/schemas/msos_stack-1.0.0.yaml b/src/rad/resources/schemas/msos_stack-1.0.0.yaml
index 844d55ee..08609ca3 100644
--- a/src/rad/resources/schemas/msos_stack-1.0.0.yaml
+++ b/src/rad/resources/schemas/msos_stack-1.0.0.yaml
@@ -20,24 +20,28 @@ properties:
     value:
       tag: tag:stsci.edu:asdf/core/ndarray-1.*
       datatype: float64
+      exact_datatype: true
       ndim: 2
   uncertainty:
     title: uncertainty data
     value:
       tag: tag:stsci.edu:asdf/core/ndarray-1.*
       datatype: float64
+      exact_datatype: true
       ndim: 2
   mask:
     title: mask data
     value:
       tag: tag:stsci.edu:asdf/core/ndarray-1.*
       datatype: uint8
+      exact_datatype: true
       ndim: 2
   coverage:
     title: coverage data
     value:
       tag: tag:stsci.edu:asdf/core/ndarray-1.*
       datatype: uint8
+      exact_datatype: true
       ndim: 2
 propertyOrder: [meta, data, uncertainty, mask, coverage]
 flowStyle: block
diff --git a/src/rad/resources/schemas/ramp-1.0.0.yaml b/src/rad/resources/schemas/ramp-1.0.0.yaml
index 5029262c..9c8d2c07 100644
--- a/src/rad/resources/schemas/ramp-1.0.0.yaml
+++ b/src/rad/resources/schemas/ramp-1.0.0.yaml
@@ -18,6 +18,7 @@ properties:
       value:
         tag: tag:stsci.edu:asdf/core/ndarray-1.*
         datatype: float32
+        exact_datatype: true
         ndim: 3
       unit:
         tag: tag:astropy.org:astropy/units/unit-1.*
@@ -27,11 +28,13 @@ properties:
     tag: tag:stsci.edu:asdf/core/ndarray-1.*
     ndim: 2
     datatype: uint32
+    exact_datatype: true
   groupdq:
     title: 3-D data quality array (plane dq for each group)
     tag: tag:stsci.edu:asdf/core/ndarray-1.*
     ndim: 3
     datatype: uint8
+    exact_datatype: true
   err:
     title: Error array containing the square root of the exposure-level combined variance
     tag: tag:stsci.edu:asdf/unit/quantity-1.*
@@ -39,6 +42,7 @@ properties:
       value:
         tag: tag:stsci.edu:asdf/core/ndarray-1.*
         datatype: float32
+        exact_datatype: true
         ndim: 3
       unit:
         tag: tag:astropy.org:astropy/units/unit-1.*
@@ -50,6 +54,7 @@ properties:
       value:
         tag: tag:stsci.edu:asdf/core/ndarray-1.*
         datatype: uint16
+        exact_datatype: true
         ndim: 3
       unit:
         tag: tag:astropy.org:astropy/units/unit-1.*
@@ -60,6 +65,7 @@ properties:
     value:
       tag: tag:stsci.edu:asdf/core/ndarray-1.*
       datatype: float32
+      exact_datatype: true
       ndim: 3
     unit:
       tag: tag:astropy.org:astropy/units/unit-1.*
@@ -71,6 +77,7 @@ properties:
       value:
         tag: tag:stsci.edu:asdf/core/ndarray-1.*
         datatype: float32
+        exact_datatype: true
         ndim: 3
       unit:
         tag: tag:astropy.org:astropy/units/unit-1.*
@@ -82,6 +89,7 @@ properties:
       value:
         tag: tag:stsci.edu:asdf/core/ndarray-1.*
         datatype: float32
+        exact_datatype: true
         ndim: 3
       unit:
         tag: tag:astropy.org:astropy/units/unit-1.*
@@ -93,6 +101,7 @@ properties:
       value:
         tag: tag:stsci.edu:asdf/core/ndarray-1.*
         datatype: float32
+        exact_datatype: true
         ndim: 3
       unit:
         tag: tag:astropy.org:astropy/units/unit-1.*
@@ -101,21 +110,25 @@ properties:
     title: DQ for border reference pixels, on left (from viewers perspective).
     tag: tag:stsci.edu:asdf/core/ndarray-1.*
     datatype: uint32
+    exact_datatype: true
     ndim: 2
   dq_border_ref_pix_right:
     title: DQ for border reference pixels, on right (from viewers perspective).
     tag: tag:stsci.edu:asdf/core/ndarray-1.*
     datatype: uint32
+    exact_datatype: true
     ndim: 2
   dq_border_ref_pix_top:
     title: DQ for border reference pixels, on top.
     tag: tag:stsci.edu:asdf/core/ndarray-1.*
     datatype: uint32
+    exact_datatype: true
     ndim: 2
   dq_border_ref_pix_bottom:
     title: DQ for border reference pixels, on bottom.
     tag: tag:stsci.edu:asdf/core/ndarray-1.*
     datatype: uint32
+    exact_datatype: true
     ndim: 2
 propertyOrder: [meta, data, pixeldq, groupdq, err, amp33, border_ref_pix_left,
                 border_ref_pix_right, border_ref_pix_top,
diff --git a/src/rad/resources/schemas/ramp_fit_output-1.0.0.yaml b/src/rad/resources/schemas/ramp_fit_output-1.0.0.yaml
index d50d0c9f..8cd0a0d6 100644
--- a/src/rad/resources/schemas/ramp_fit_output-1.0.0.yaml
+++ b/src/rad/resources/schemas/ramp_fit_output-1.0.0.yaml
@@ -18,6 +18,7 @@ properties:
       value:
         tag: tag:stsci.edu:asdf/core/ndarray-1.*
         datatype: float32
+        exact_datatype: true
         ndim: 3
       unit:
         tag: tag:astropy.org:astropy/units/unit-1.*
@@ -29,6 +30,7 @@ properties:
       value:
         tag: tag:stsci.edu:asdf/core/ndarray-1.*
         datatype: float32
+        exact_datatype: true
         ndim: 3
       unit:
         tag: tag:astropy.org:astropy/units/unit-1.*
@@ -40,6 +42,7 @@ properties:
       value:
         tag: tag:stsci.edu:asdf/core/ndarray-1.*
         datatype: float32
+        exact_datatype: true
         ndim: 3
       unit:
         tag: tag:astropy.org:astropy/units/unit-1.*
@@ -51,6 +54,7 @@ properties:
       value:
         tag: tag:stsci.edu:asdf/core/ndarray-1.*
         datatype: float32
+        exact_datatype: true
         ndim: 3
       unit:
         tag: tag:astropy.org:astropy/units/unit-1.*
@@ -62,6 +66,7 @@ properties:
       value:
         tag: tag:stsci.edu:asdf/core/ndarray-1.*
         datatype: float32
+        exact_datatype: true
         ndim: 2
       unit:
         tag: tag:astropy.org:astropy/units/unit-1.*
@@ -71,6 +76,7 @@ properties:
     tag: tag:stsci.edu:asdf/core/ndarray-1.*
     ndim: 3
     datatype: float32
+    exact_datatype: true
   crmag:
     title: Approximate CR magnitudes
     tag: tag:stsci.edu:asdf/unit/quantity-1.*
@@ -78,6 +84,7 @@ properties:
       value:
         tag: tag:stsci.edu:asdf/core/ndarray-1.*
         datatype: float32
+        exact_datatype: true
         ndim: 3
       unit:
         tag: tag:astropy.org:astropy/units/unit-1.*
@@ -89,6 +96,7 @@ properties:
       value:
         tag: tag:stsci.edu:asdf/core/ndarray-1.*
         datatype: float32
+        exact_datatype: true
         ndim: 3
       unit:
         tag: tag:astropy.org:astropy/units/unit-1.*
@@ -100,6 +108,7 @@ properties:
       value:
         tag: tag:stsci.edu:asdf/core/ndarray-1.*
         datatype: float32
+        exact_datatype: true
         ndim: 3
       unit:
         tag: tag:astropy.org:astropy/units/unit-1.*
diff --git a/src/rad/resources/schemas/reference_files/dark-1.0.0.yaml b/src/rad/resources/schemas/reference_files/dark-1.0.0.yaml
index bf17d764..150b7417 100644
--- a/src/rad/resources/schemas/reference_files/dark-1.0.0.yaml
+++ b/src/rad/resources/schemas/reference_files/dark-1.0.0.yaml
@@ -49,6 +49,7 @@ properties:
       value:
         tag: tag:stsci.edu:asdf/core/ndarray-1.*
         datatype: float32
+        exact_datatype: true
         ndim: 3
       unit:
         tag: tag:astropy.org:astropy/units/unit-1.*
@@ -57,6 +58,7 @@ properties:
     title: 2-D data quality array for all planes
     tag: tag:stsci.edu:asdf/core/ndarray-1.*
     datatype: uint32
+    exact_datatype: true
     ndim: 2
   dark_slope:
     title: Dark current slope array
@@ -69,6 +71,7 @@ properties:
       value:
         tag: tag:stsci.edu:asdf/core/ndarray-1.*
         datatype: float32
+        exact_datatype: true
         ndim: 2
       unit:
         tag: tag:astropy.org:astropy/units/unit-1.*
@@ -80,6 +83,7 @@ properties:
       value:
         tag: tag:stsci.edu:asdf/core/ndarray-1.*
         datatype: float32
+        exact_datatype: true
         ndim: 2
       unit:
         tag: tag:astropy.org:astropy/units/unit-1.*
diff --git a/src/rad/resources/schemas/reference_files/flat-1.0.0.yaml b/src/rad/resources/schemas/reference_files/flat-1.0.0.yaml
index 4e9b1ffd..1a3152af 100644
--- a/src/rad/resources/schemas/reference_files/flat-1.0.0.yaml
+++ b/src/rad/resources/schemas/reference_files/flat-1.0.0.yaml
@@ -22,16 +22,19 @@ properties:
     title: Flat data array
     tag: tag:stsci.edu:asdf/core/ndarray-1.*
     datatype: float32
+    exact_datatype: true
     ndim: 2
   dq:
     title: Data quality array
     tag: tag:stsci.edu:asdf/core/ndarray-1.*
     datatype: uint32
+    exact_datatype: true
     ndim: 2
   err:
     title: Error array
     tag: tag:stsci.edu:asdf/core/ndarray-1.*
     datatype: float32
+    exact_datatype: true
     ndim: 2
 required: [meta, data, dq, err]
 flowStyle: block
diff --git a/src/rad/resources/schemas/reference_files/gain-1.0.0.yaml b/src/rad/resources/schemas/reference_files/gain-1.0.0.yaml
index fcb4e344..ff829f54 100644
--- a/src/rad/resources/schemas/reference_files/gain-1.0.0.yaml
+++ b/src/rad/resources/schemas/reference_files/gain-1.0.0.yaml
@@ -24,6 +24,7 @@ properties:
       value:
         tag: tag:stsci.edu:asdf/core/ndarray-1.*
         datatype: float32
+        exact_datatype: true
         ndim: 2
       unit:
         tag: tag:astropy.org:astropy/units/unit-1.*
diff --git a/src/rad/resources/schemas/reference_files/inverselinearity-1.0.0.yaml b/src/rad/resources/schemas/reference_files/inverselinearity-1.0.0.yaml
index 525c072c..a331e6f7 100644
--- a/src/rad/resources/schemas/reference_files/inverselinearity-1.0.0.yaml
+++ b/src/rad/resources/schemas/reference_files/inverselinearity-1.0.0.yaml
@@ -34,12 +34,14 @@ properties:
       of DN. The coefficients have units that contain various powers of DN.
     tag: tag:stsci.edu:asdf/core/ndarray-1.*
     datatype: float32
+    exact_datatype: true
     # Dimensions: numcoeffs, ysize, xsize
     ndim: 3
   dq:
     title: 2-D data quality array for all planes
     tag: tag:stsci.edu:asdf/core/ndarray-1.*
     datatype: uint32
+    exact_datatype: true
     ndim: 2
 required: [meta, coeffs, dq]
 flowStyle: block
diff --git a/src/rad/resources/schemas/reference_files/ipc-1.0.0.yaml b/src/rad/resources/schemas/reference_files/ipc-1.0.0.yaml
index 04ca254d..a8b6f674 100644
--- a/src/rad/resources/schemas/reference_files/ipc-1.0.0.yaml
+++ b/src/rad/resources/schemas/reference_files/ipc-1.0.0.yaml
@@ -25,6 +25,7 @@ properties:
       for interpixel capacitance
     tag: tag:stsci.edu:asdf/core/ndarray-1.*
     datatype: float32
+    exact_datatype: true
     ndim: 2
 required: [meta, data]
 flowStyle: block
diff --git a/src/rad/resources/schemas/reference_files/linearity-1.0.0.yaml b/src/rad/resources/schemas/reference_files/linearity-1.0.0.yaml
index 993bccde..5e7d38ed 100644
--- a/src/rad/resources/schemas/reference_files/linearity-1.0.0.yaml
+++ b/src/rad/resources/schemas/reference_files/linearity-1.0.0.yaml
@@ -35,12 +35,14 @@ properties:
       have units that contain various powers of DN.
     tag: tag:stsci.edu:asdf/core/ndarray-1.*
     datatype: float32
+    exact_datatype: true
     # Dimensions: numcoeffs, ysize, xsize
     ndim: 3
   dq:
     title: 2-D data quality array for all planes
     tag: tag:stsci.edu:asdf/core/ndarray-1.*
     datatype: uint32
+    exact_datatype: true
     ndim: 2
 required: [meta, coeffs, dq]
 flowStyle: block
diff --git a/src/rad/resources/schemas/reference_files/mask-1.0.0.yaml b/src/rad/resources/schemas/reference_files/mask-1.0.0.yaml
index 0168a978..6ef2eead 100644
--- a/src/rad/resources/schemas/reference_files/mask-1.0.0.yaml
+++ b/src/rad/resources/schemas/reference_files/mask-1.0.0.yaml
@@ -21,6 +21,7 @@ properties:
     title: Data quality mask array
     tag: tag:stsci.edu:asdf/core/ndarray-1.*
     datatype: uint32
+    exact_datatype: true
     ndim: 2
 required: [meta, dq]
 flowStyle: block
diff --git a/src/rad/resources/schemas/reference_files/pixelarea-1.0.0.yaml b/src/rad/resources/schemas/reference_files/pixelarea-1.0.0.yaml
index 7cefcf05..9557b183 100644
--- a/src/rad/resources/schemas/reference_files/pixelarea-1.0.0.yaml
+++ b/src/rad/resources/schemas/reference_files/pixelarea-1.0.0.yaml
@@ -49,6 +49,7 @@ properties:
     title: Pixel area array
     tag: tag:stsci.edu:asdf/core/ndarray-1.*
     datatype: float32
+    exact_datatype: true
     ndim: 2
 required: [meta, data]
 flowStyle: block
diff --git a/src/rad/resources/schemas/reference_files/readnoise-1.0.0.yaml b/src/rad/resources/schemas/reference_files/readnoise-1.0.0.yaml
index 198b3fd4..43238396 100644
--- a/src/rad/resources/schemas/reference_files/readnoise-1.0.0.yaml
+++ b/src/rad/resources/schemas/reference_files/readnoise-1.0.0.yaml
@@ -24,6 +24,7 @@ properties:
       value:
         tag: tag:stsci.edu:asdf/core/ndarray-1.*
         datatype: float32
+        exact_datatype: true
         ndim: 2
       unit:
         tag: tag:astropy.org:astropy/units/unit-1.*
diff --git a/src/rad/resources/schemas/reference_files/refpix-1.0.0.yaml b/src/rad/resources/schemas/reference_files/refpix-1.0.0.yaml
index 6265fd6e..c382cfcd 100644
--- a/src/rad/resources/schemas/reference_files/refpix-1.0.0.yaml
+++ b/src/rad/resources/schemas/reference_files/refpix-1.0.0.yaml
@@ -31,18 +31,21 @@ properties:
     title: Left column correction coefficients
     tag: tag:stsci.edu:asdf/core/ndarray-1.*
     datatype: complex128
+    exact_datatype: true
     ndim: 2
 
   zeta:
     title: Right column correction coefficients
     tag: tag:stsci.edu:asdf/core/ndarray-1.*
     datatype: complex128
+    exact_datatype: true
     ndim: 2
 
   alpha:
     title: Reference output correction coefficients
     tag: tag:stsci.edu:asdf/core/ndarray-1.*
     datatype: complex128
+    exact_datatype: true
     ndim: 2
 
 required: [meta, gamma, zeta, alpha]
diff --git a/src/rad/resources/schemas/reference_files/saturation-1.0.0.yaml b/src/rad/resources/schemas/reference_files/saturation-1.0.0.yaml
index 7595d1ca..a10bb7ba 100644
--- a/src/rad/resources/schemas/reference_files/saturation-1.0.0.yaml
+++ b/src/rad/resources/schemas/reference_files/saturation-1.0.0.yaml
@@ -24,6 +24,7 @@ properties:
       value:
         tag: tag:stsci.edu:asdf/core/ndarray-1.*
         datatype: float32
+        exact_datatype: true
         ndim: 2
       unit:
         tag: tag:astropy.org:astropy/units/unit-1.*
@@ -32,6 +33,7 @@ properties:
     title: 2-D data quality array for all planes
     tag: tag:stsci.edu:asdf/core/ndarray-1.*
     datatype: uint32
+    exact_datatype: true
     ndim: 2
 required: [meta, data, dq]
 flowStyle: block
diff --git a/src/rad/resources/schemas/reference_files/superbias-1.0.0.yaml b/src/rad/resources/schemas/reference_files/superbias-1.0.0.yaml
index 340a18bf..36426f26 100644
--- a/src/rad/resources/schemas/reference_files/superbias-1.0.0.yaml
+++ b/src/rad/resources/schemas/reference_files/superbias-1.0.0.yaml
@@ -21,16 +21,19 @@ properties:
     title: 2-D super-bias array
     tag: tag:stsci.edu:asdf/core/ndarray-1.*
     datatype: float32
+    exact_datatype: true
     ndim: 2
   dq:
     title: 2-D data quality array for all planes
     tag: tag:stsci.edu:asdf/core/ndarray-1.*
     datatype: uint32
+    exact_datatype: true
     ndim: 2
   err:
     title: 2-D Error array
     tag: tag:stsci.edu:asdf/core/ndarray-1.*
     datatype: float32
+    exact_datatype: true
     ndim: 2
 required: [meta, data, dq, err]
 flowStyle: block
diff --git a/src/rad/resources/schemas/wfi_image-1.0.0.yaml b/src/rad/resources/schemas/wfi_image-1.0.0.yaml
index 2f37d11d..3f5b0984 100644
--- a/src/rad/resources/schemas/wfi_image-1.0.0.yaml
+++ b/src/rad/resources/schemas/wfi_image-1.0.0.yaml
@@ -33,6 +33,7 @@ properties:
       value:
         tag: tag:stsci.edu:asdf/core/ndarray-1.*
         datatype: float32
+        exact_datatype: true
         ndim: 2
       unit:
         tag: tag:astropy.org:astropy/units/unit-1.*
@@ -40,6 +41,7 @@ properties:
   dq:
     tag: tag:stsci.edu:asdf/core/ndarray-1.*
     datatype: uint32
+    exact_datatype: true
     ndim: 2
   err:
     tag: tag:stsci.edu:asdf/unit/quantity-1.*
@@ -47,6 +49,7 @@ properties:
       value:
         tag: tag:stsci.edu:asdf/core/ndarray-1.*
         datatype: float32
+        exact_datatype: true
         ndim: 2
       unit:
         tag: tag:astropy.org:astropy/units/unit-1.*
@@ -57,6 +60,7 @@ properties:
       value:
         tag: tag:stsci.edu:asdf/core/ndarray-1.*
         datatype: float32
+        exact_datatype: true
         ndim: 2
       unit:
         tag: tag:astropy.org:astropy/units/unit-1.*
@@ -67,6 +71,7 @@ properties:
       value:
         tag: tag:stsci.edu:asdf/core/ndarray-1.*
         datatype: float32
+        exact_datatype: true
         ndim: 2
       unit:
         tag: tag:astropy.org:astropy/units/unit-1.*
@@ -77,6 +82,7 @@ properties:
       value:
         tag: tag:stsci.edu:asdf/core/ndarray-1.*
         datatype: float32
+        exact_datatype: true
         ndim: 2
       unit:
         tag: tag:astropy.org:astropy/units/unit-1.*
@@ -88,6 +94,7 @@ properties:
       value:
         tag: tag:stsci.edu:asdf/core/ndarray-1.*
         datatype: uint16
+        exact_datatype: true
         ndim: 3
       unit:
         tag: tag:astropy.org:astropy/units/unit-1.*
@@ -99,6 +106,7 @@ properties:
       value:
         tag: tag:stsci.edu:asdf/core/ndarray-1.*
         datatype: float32
+        exact_datatype: true
         ndim: 3
       unit:
         tag: tag:astropy.org:astropy/units/unit-1.*
@@ -110,6 +118,7 @@ properties:
       value:
         tag: tag:stsci.edu:asdf/core/ndarray-1.*
         datatype: float32
+        exact_datatype: true
         ndim: 3
       unit:
         tag: tag:astropy.org:astropy/units/unit-1.*
@@ -121,6 +130,7 @@ properties:
       value:
         tag: tag:stsci.edu:asdf/core/ndarray-1.*
         datatype: float32
+        exact_datatype: true
         ndim: 3
       unit:
         tag: tag:astropy.org:astropy/units/unit-1.*
@@ -132,6 +142,7 @@ properties:
       value:
         tag: tag:stsci.edu:asdf/core/ndarray-1.*
         datatype: float32
+        exact_datatype: true
         ndim: 3
       unit:
         tag: tag:astropy.org:astropy/units/unit-1.*
@@ -140,21 +151,25 @@ properties:
     title: DQ for border reference pixels, on left (from viewers perspective).
     tag: tag:stsci.edu:asdf/core/ndarray-1.*
     datatype: uint32
+    exact_datatype: true
     ndim: 2
   dq_border_ref_pix_right:
     title: DQ for border reference pixels, on right (from viewers perspective).
     tag: tag:stsci.edu:asdf/core/ndarray-1.*
     datatype: uint32
+    exact_datatype: true
     ndim: 2
   dq_border_ref_pix_top:
     title: DQ for border reference pixels, on top.
     tag: tag:stsci.edu:asdf/core/ndarray-1.*
     datatype: uint32
+    exact_datatype: true
     ndim: 2
   dq_border_ref_pix_bottom:
     title: DQ for border reference pixels, on bottom.
     tag: tag:stsci.edu:asdf/core/ndarray-1.*
     datatype: uint32
+    exact_datatype: true
     ndim: 2
   cal_logs:
     tag: asdf://stsci.edu/datamodels/roman/tags/cal_logs-1.0.0
diff --git a/src/rad/resources/schemas/wfi_mosaic-1.0.0.yaml b/src/rad/resources/schemas/wfi_mosaic-1.0.0.yaml
index 3074ba06..d3ed9b1f 100644
--- a/src/rad/resources/schemas/wfi_mosaic-1.0.0.yaml
+++ b/src/rad/resources/schemas/wfi_mosaic-1.0.0.yaml
@@ -49,6 +49,7 @@ properties:
       value:
         tag: tag:stsci.edu:asdf/core/ndarray-1.*
         datatype: float32
+        exact_datatype: true
         ndim: 2
       unit:
         tag: tag:stsci.edu:asdf/unit/unit-1.*
@@ -59,6 +60,7 @@ properties:
       value:
         tag: tag:stsci.edu:asdf/core/ndarray-1.*
         datatype: float32
+        exact_datatype: true
         ndim: 2
       unit:
         tag: tag:stsci.edu:asdf/unit/unit-1.*
@@ -66,10 +68,12 @@ properties:
   context:
     tag: tag:stsci.edu:asdf/core/ndarray-1.*
     datatype: uint32
+    exact_datatype: true
     ndim: 3
   weight:
     tag: tag:stsci.edu:asdf/core/ndarray-1.*
     datatype: float32
+    exact_datatype: true
     ndim: 2
   var_poisson:
     tag: tag:stsci.edu:asdf/unit/quantity-1.*
@@ -77,6 +81,7 @@ properties:
       value:
         tag: tag:stsci.edu:asdf/core/ndarray-1.*
         datatype: float32
+        exact_datatype: true
         ndim: 2
       unit:
         tag: tag:stsci.edu:asdf/unit/unit-1.*
@@ -87,6 +92,7 @@ properties:
       value:
         tag: tag:stsci.edu:asdf/core/ndarray-1.*
         datatype: float32
+        exact_datatype: true
         ndim: 2
       unit:
         tag: tag:stsci.edu:asdf/unit/unit-1.*
@@ -97,6 +103,7 @@ properties:
       value:
         tag: tag:stsci.edu:asdf/core/ndarray-1.*
         datatype: float32
+        exact_datatype: true
         ndim: 2
       unit:
         tag: tag:stsci.edu:asdf/unit/unit-1.*
diff --git a/src/rad/resources/schemas/wfi_science_raw-1.0.0.yaml b/src/rad/resources/schemas/wfi_science_raw-1.0.0.yaml
index a5a3cfff..cd7b6c44 100644
--- a/src/rad/resources/schemas/wfi_science_raw-1.0.0.yaml
+++ b/src/rad/resources/schemas/wfi_science_raw-1.0.0.yaml
@@ -21,6 +21,7 @@ properties:
       value:
         tag: tag:stsci.edu:asdf/core/ndarray-1.*
         datatype: uint16
+        exact_datatype: true
         ndim: 3
       unit:
         tag: tag:astropy.org:astropy/units/unit-1.*
@@ -33,6 +34,7 @@ properties:
       value:
         tag: tag:stsci.edu:asdf/core/ndarray-1.*
         datatype: uint16
+        exact_datatype: true
         ndim: 3
       unit:
         tag: tag:astropy.org:astropy/units/unit-1.*
@@ -43,6 +45,7 @@ properties:
     tag: tag:stsci.edu:asdf/core/ndarray-1.*
     ndim: 3
     datatype: uint8
+    exact_datatype: true
 propertyOrder: [meta, data, amp33, resultantdq]
 flowStyle: block
 required: [meta, data, amp33]


Test patch:
diff --git a/tests/test_schemas.py b/tests/test_schemas.py
index 7781e8bd..3bf429eb 100644
--- a/tests/test_schemas.py
+++ b/tests/test_schemas.py
@@ -176,8 +176,8 @@ def test_matched_optical_element_entries():
         assert r.search(element_str)
 
 
-# Confirm that the p_keyword version of exposure type match the enum version
 def test_matched_p_exptype_entries():
+    """Confirm that the p_keyword version of exposure type match the enum version."""
     p_exptype = asdf.schema.load_schema("asdf://stsci.edu/datamodels/roman/schemas/reference_files/ref_exposure_type-1.0.0")[
         "properties"
     ]["exposure"]["properties"]["p_exptype"]["pattern"]
@@ -186,6 +186,49 @@ def test_matched_p_exptype_entries():
         assert r.search(element_str + "|")
 
 
+def _find_ndarrays(key, schema):
+    """
+    Find all the ndarray entries in the schema
+    """
+    entries = []
+    if isinstance(schema, dict):
+        for new_key, value in schema.items():
+            if isinstance(value, str) and value.startswith("tag:stsci.edu:asdf/core/ndarray-"):
+                entries.append((key,))
+            else:
+                entries.extend((key, *key_) for key_ in _find_ndarrays(new_key, value))
+    elif isinstance(schema, list):
+        for index, value in enumerate(schema):
+            entries.extend((key, *key_) for key_ in _find_ndarrays(index, value))
+
+    return entries
+
+
+def _get_ndarray_entry(schema, entry):
+    """
+    Get the ndarray portion of the schema for the entry
+    """
+    current = schema
+
+    for key in entry[1:]:
+        current = current[key]
+
+    return current
+
+
+def test_exact_datatype(schema):
+    """Confirm that `exact_datatype` is defined for all arrays"""
+    entries = _find_ndarrays("", schema)
+
+    if entries:
+        for entry in entries:
+            if "datatype" in (ndarray_entry := _get_ndarray_entry(schema, entry)):
+                assert "exact_datatype" in ndarray_entry, f"extact_datatype needed for {'.'.join(entry[1:])}"
+                assert ndarray_entry["exact_datatype"] is True
+            else:
+                raise ValueError(f"datatype not found for {'.'.join(entry[1:])}")
+
+
 def _get_reftype(schema):
     """
     Extract the reftype from the schema