diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,19 @@
 # Revision history for stim-parser
 
+## 0.3.0.0 -- 2026-07-20
+
+* Add optional tag support for DEM detector and observable declarations.
+  Stim's DEM format allows `detector[tag](...) D#` and
+  `logical_observable[tag] L#`; these are now parsed and exposed in the AST.
+  This is a **breaking API change**: `DEMDetector` and `DEMObservable`
+  gained new `ddTag` / `doTag` fields of type `Maybe Tag`.
+* Move `parseTag` from `StimParser.Parse` to `StimParser.ParseUtils` so it
+  can be shared by the DEM parser. Modules importing `parseTag` from
+  `StimParser.Parse` should import it from `StimParser.ParseUtils` instead.
+* Add `Eq` instance for `StimParser.Expr.Tag`.
+* Add DEM parser regression tests for tagged detectors/observables and
+  rejection of double tags.
+
 ## 0.2.0.1 -- 2026-07-18
 
 * Fix noise-channel parsing order in `parseStim`.
diff --git a/src/StimParser/DEM/Expr.hs b/src/StimParser/DEM/Expr.hs
--- a/src/StimParser/DEM/Expr.hs
+++ b/src/StimParser/DEM/Expr.hs
@@ -1,6 +1,8 @@
 {-# LANGUAGE InstanceSigs #-}
 module StimParser.DEM.Expr where
 
+import StimParser.Expr (Tag)
+
 -- | A detector index (check node in the bipartite graph).
 newtype DetectorId = DetectorId { unDetectorId :: Int }
   deriving (Show, Eq, Ord)
@@ -30,11 +32,13 @@
 data DEMDetector = DEMDetector
     { ddId     :: !DetectorId
     , ddCoords :: ![Double]
+    , ddTag    :: !(Maybe Tag)
     } deriving (Show, Eq)
 
 -- | A logical observable declaration: logical_observable L0
 data DEMObservable = DEMObservable
-    { doId :: !ObservableId
+    { doId  :: !ObservableId
+    , doTag :: !(Maybe Tag)
     } deriving (Show, Eq)
 
 -- | Shift detectors instruction: shift_detectors(1, 0) 0
@@ -93,11 +97,11 @@
     DEMInstrRepeat n body ->
       flattenInstrs coordShift detShift (concat (replicate n body) ++ is)
 
-    DEMInstrDetector (DEMDetector (DetectorId did) coords) ->
+    DEMInstrDetector (DEMDetector (DetectorId did) coords tag) ->
       let shiftPadded = padCoords (length coords) coordShift
           shiftedCoords = zipWith (+) shiftPadded coords
           shiftedId = DetectorId (did + detShift)
-      in DEMInstrDetector (DEMDetector shiftedId shiftedCoords) : flattenInstrs coordShift detShift is
+      in DEMInstrDetector (DEMDetector shiftedId shiftedCoords tag) : flattenInstrs coordShift detShift is
 
     DEMInstrError err ->
       DEMInstrError err : flattenInstrs coordShift detShift is
diff --git a/src/StimParser/DEM/Parse.hs b/src/StimParser/DEM/Parse.hs
--- a/src/StimParser/DEM/Parse.hs
+++ b/src/StimParser/DEM/Parse.hs
@@ -2,9 +2,10 @@
 module StimParser.DEM.Parse where
 
 import Text.Megaparsec
-import Text.Megaparsec.Char
+import Text.Megaparsec.Char ()
 
 import StimParser.ParseUtils
+import StimParser.Expr (Tag)
 import StimParser.DEM.Expr
 
 -- | Parse a complete DEM string.
@@ -40,16 +41,18 @@
 parseDEMDetector :: Parser DEMDetector
 parseDEMDetector = do
   lstring "detector"
+  tag <- optional parseTag :: Parser (Maybe Tag)
   coords <- parseTupleNumber
   did <- DetectorId <$> (lstring "D" *> parseInt)
-  return $ DEMDetector did coords
+  return $ DEMDetector did coords tag
 
 -- | Parse a logical observable declaration: logical_observable L0
 parseDEMObservable :: Parser DEMObservable
 parseDEMObservable = do
   lstring "logical_observable"
+  tag <- optional parseTag :: Parser (Maybe Tag)
   oid <- ObservableId <$> (lstring "L" *> parseInt)
-  return $ DEMObservable oid
+  return $ DEMObservable oid tag
 
 -- | Parse a shift_detectors instruction: shift_detectors(1, 0) 0 or shift_detectors 96
 --
diff --git a/src/StimParser/Expr.hs b/src/StimParser/Expr.hs
--- a/src/StimParser/Expr.hs
+++ b/src/StimParser/Expr.hs
@@ -151,7 +151,7 @@
 -- General Instruction Tag (v1.15+)
 -- Examples: TICK[100ns], X_ERROR[custom](0.1) 0
 newtype Tag = Tag String
-  deriving (Show)
+  deriving (Show, Eq)
 
 -- Annotations 
 data AnnTy = 
diff --git a/src/StimParser/Parse.hs b/src/StimParser/Parse.hs
--- a/src/StimParser/Parse.hs
+++ b/src/StimParser/Parse.hs
@@ -88,15 +88,6 @@
 parseEnum [] = error "value error"
 parseEnum xs = msum $ map parseShowCI $ sortOn (negate . length . show) xs
 
--- | Parse an optional tag: [tag_content]
--- Tag content can be any character except ], \r, \n
-parseTag :: Parser Tag
-parseTag = do
-  lstring "["
-  content <- many $ satisfy (\c -> c /= ']' && c /= '\r' && c /= '\n')
-  lstring "]"
-  return $ Tag content
-
 parseGateTy :: Parser GateTy
 parseGateTy = parseEnum gateTyList
 
diff --git a/src/StimParser/ParseUtils.hs b/src/StimParser/ParseUtils.hs
--- a/src/StimParser/ParseUtils.hs
+++ b/src/StimParser/ParseUtils.hs
@@ -17,6 +17,8 @@
 import Data.Maybe
 import Data.Char (toLower)
 
+import StimParser.Expr (Tag (..))
+
 type Parser = Parsec Void String
 -- type Parser = ParsecT Void String (State Env)
 
@@ -100,6 +102,18 @@
 parseVar :: Parser String
 parseVar = do
   lexeme $ safeManyTill L.charLiteral (lookAhead (try space1 <|> eof))
+
+-- | Parse an optional tag: [tag_content]
+-- Tag content can be any character except ], \r, \n
+tagContentForbidden :: Char -> Bool
+tagContentForbidden c = c == ']' || c == '\r' || c == '\n'
+
+parseTag :: Parser Tag
+parseTag = do
+  lstring "["
+  content <- Text.Megaparsec.many $ satisfy (not . tagContentForbidden)
+  lstring "]"
+  return $ Tag content
 
 excludePredict :: Parser a -> Parser ()
 excludePredict p = lookAhead $ notFollowedBy p
diff --git a/stim-parser.cabal b/stim-parser.cabal
--- a/stim-parser.cabal
+++ b/stim-parser.cabal
@@ -20,7 +20,7 @@
 -- PVP summary:     +-+------- breaking API changes
 --                  | | +----- non-breaking API additions
 --                  | | | +--- code changes with no API change
-version:            0.2.0.1
+version:            0.3.0.0
 
 -- A short (one-line) description of the package.
 synopsis:           A parser combinator library for STIM quantum circuit files
diff --git a/test/Test/DEM/Expr.hs b/test/Test/DEM/Expr.hs
--- a/test/Test/DEM/Expr.hs
+++ b/test/Test/DEM/Expr.hs
@@ -31,8 +31,8 @@
       let dem = DEM
             [ DEMInstrError (DEMError 0.01 [TargetDetector (DetectorId 0)])
             , DEMInstrError (DEMError 0.02 [TargetDetector (DetectorId 1), TargetObservable (ObservableId 0)])
-            , DEMInstrDetector (DEMDetector (DetectorId 0) [0.0, 0.0])
-            , DEMInstrObservable (DEMObservable (ObservableId 0))
+            , DEMInstrDetector (DEMDetector (DetectorId 0) [0.0, 0.0] Nothing)
+            , DEMInstrObservable (DEMObservable (ObservableId 0) Nothing)
             ]
       in dem ~=? flattenDEM dem
   ]
@@ -73,25 +73,25 @@
   [ "flatten shift detectors" ~:
       let input = DEM
             [ DEMInstrShift (DEMShift [1.0, 0.0] 0)
-            , DEMInstrDetector (DEMDetector (DetectorId 0) [0.0, 0.0])
-            , DEMInstrDetector (DEMDetector (DetectorId 1) [2.0, 3.0])
+            , DEMInstrDetector (DEMDetector (DetectorId 0) [0.0, 0.0] Nothing)
+            , DEMInstrDetector (DEMDetector (DetectorId 1) [2.0, 3.0] Nothing)
             ]
           expected = DEM
-            [ DEMInstrDetector (DEMDetector (DetectorId 0) [1.0, 0.0])
-            , DEMInstrDetector (DEMDetector (DetectorId 1) [3.0, 3.0])
+            [ DEMInstrDetector (DEMDetector (DetectorId 0) [1.0, 0.0] Nothing)
+            , DEMInstrDetector (DEMDetector (DetectorId 1) [3.0, 3.0] Nothing)
             ]
       in expected ~=? flattenDEM input
 
   , "flatten cumulative shift" ~:
       let input = DEM
             [ DEMInstrShift (DEMShift [1.0] 0)
-            , DEMInstrDetector (DEMDetector (DetectorId 0) [0.0])
+            , DEMInstrDetector (DEMDetector (DetectorId 0) [0.0] Nothing)
             , DEMInstrShift (DEMShift [2.0] 0)
-            , DEMInstrDetector (DEMDetector (DetectorId 1) [0.0])
+            , DEMInstrDetector (DEMDetector (DetectorId 1) [0.0] Nothing)
             ]
           expected = DEM
-            [ DEMInstrDetector (DEMDetector (DetectorId 0) [1.0])
-            , DEMInstrDetector (DEMDetector (DetectorId 1) [3.0])
+            [ DEMInstrDetector (DEMDetector (DetectorId 0) [1.0] Nothing)
+            , DEMInstrDetector (DEMDetector (DetectorId 1) [3.0] Nothing)
             ]
       in expected ~=? flattenDEM input
   ]
@@ -103,15 +103,15 @@
       let input = DEM
             [ DEMInstrRepeat 2
                 [ DEMInstrShift (DEMShift [1.0] 0)
-                , DEMInstrDetector (DEMDetector (DetectorId 0) [0.0])
+                , DEMInstrDetector (DEMDetector (DetectorId 0) [0.0] Nothing)
                 ]
             ]
           -- Note: shifts are cumulative across repeat iterations.
           -- Iteration 1: shift=[1.0], detector gets [1.0]
           -- Iteration 2: shift becomes [2.0], detector gets [2.0]
           expected = DEM
-            [ DEMInstrDetector (DEMDetector (DetectorId 0) [1.0])
-            , DEMInstrDetector (DEMDetector (DetectorId 0) [2.0])
+            [ DEMInstrDetector (DEMDetector (DetectorId 0) [1.0] Nothing)
+            , DEMInstrDetector (DEMDetector (DetectorId 0) [2.0] Nothing)
             ]
       in expected ~=? flattenDEM input
   ]
@@ -122,22 +122,22 @@
   [ "flatten detector id shift" ~:
       let input = DEM
             [ DEMInstrShift (DEMShift [0.0] 5)
-            , DEMInstrDetector (DEMDetector (DetectorId 0) [1.0])
-            , DEMInstrDetector (DEMDetector (DetectorId 1) [2.0])
+            , DEMInstrDetector (DEMDetector (DetectorId 0) [1.0] Nothing)
+            , DEMInstrDetector (DEMDetector (DetectorId 1) [2.0] Nothing)
             ]
           expected = DEM
-            [ DEMInstrDetector (DEMDetector (DetectorId 5) [1.0])
-            , DEMInstrDetector (DEMDetector (DetectorId 6) [2.0])
+            [ DEMInstrDetector (DEMDetector (DetectorId 5) [1.0] Nothing)
+            , DEMInstrDetector (DEMDetector (DetectorId 6) [2.0] Nothing)
             ]
       in expected ~=? flattenDEM input
 
   , "flatten combined coord and id shift" ~:
       let input = DEM
             [ DEMInstrShift (DEMShift [1.0, 0.0] 10)
-            , DEMInstrDetector (DEMDetector (DetectorId 0) [0.0, 0.0])
+            , DEMInstrDetector (DEMDetector (DetectorId 0) [0.0, 0.0] Nothing)
             ]
           expected = DEM
-            [ DEMInstrDetector (DEMDetector (DetectorId 10) [1.0, 0.0])
+            [ DEMInstrDetector (DEMDetector (DetectorId 10) [1.0, 0.0] Nothing)
             ]
       in expected ~=? flattenDEM input
   ]
@@ -148,20 +148,20 @@
   [ "2D shift on 3D detector" ~:
       let input = DEM
             [ DEMInstrShift (DEMShift [1.0, 0.0] 0)
-            , DEMInstrDetector (DEMDetector (DetectorId 0) [0.0, 0.0, 0.0])
+            , DEMInstrDetector (DEMDetector (DetectorId 0) [0.0, 0.0, 0.0] Nothing)
             ]
           expected = DEM
-            [ DEMInstrDetector (DEMDetector (DetectorId 0) [1.0, 0.0, 0.0])
+            [ DEMInstrDetector (DEMDetector (DetectorId 0) [1.0, 0.0, 0.0] Nothing)
             ]
       in expected ~=? flattenDEM input
 
   , "3D shift on 2D detector" ~:
       let input = DEM
             [ DEMInstrShift (DEMShift [1.0, 0.0, 0.0] 0)
-            , DEMInstrDetector (DEMDetector (DetectorId 0) [0.0, 0.0])
+            , DEMInstrDetector (DEMDetector (DetectorId 0) [0.0, 0.0] Nothing)
             ]
           expected = DEM
-            [ DEMInstrDetector (DEMDetector (DetectorId 0) [1.0, 0.0])
+            [ DEMInstrDetector (DEMDetector (DetectorId 0) [1.0, 0.0] Nothing)
             ]
       in expected ~=? flattenDEM input
   ]
@@ -172,23 +172,23 @@
   [ "bare shift preserves coords" ~:
       let input = DEM
             [ DEMInstrShift (DEMShift [1.0, 0.5] 0)
-            , DEMInstrDetector (DEMDetector (DetectorId 0) [0.0, 0.0])
+            , DEMInstrDetector (DEMDetector (DetectorId 0) [0.0, 0.0] Nothing)
             , DEMInstrShift (DEMShift [] 10)
-            , DEMInstrDetector (DEMDetector (DetectorId 1) [0.0, 0.0])
+            , DEMInstrDetector (DEMDetector (DetectorId 1) [0.0, 0.0] Nothing)
             ]
           expected = DEM
-            [ DEMInstrDetector (DEMDetector (DetectorId 0) [1.0, 0.5])
-            , DEMInstrDetector (DEMDetector (DetectorId 11) [1.0, 0.5])
+            [ DEMInstrDetector (DEMDetector (DetectorId 0) [1.0, 0.5] Nothing)
+            , DEMInstrDetector (DEMDetector (DetectorId 11) [1.0, 0.5] Nothing)
             ]
       in expected ~=? flattenDEM input
 
   , "bare shift only det id" ~:
       let input = DEM
             [ DEMInstrShift (DEMShift [] 5)
-            , DEMInstrDetector (DEMDetector (DetectorId 0) [2.0, 3.0])
+            , DEMInstrDetector (DEMDetector (DetectorId 0) [2.0, 3.0] Nothing)
             ]
           expected = DEM
-            [ DEMInstrDetector (DEMDetector (DetectorId 5) [2.0, 3.0])
+            [ DEMInstrDetector (DEMDetector (DetectorId 5) [2.0, 3.0] Nothing)
             ]
       in expected ~=? flattenDEM input
 
@@ -197,10 +197,10 @@
             [ DEMInstrShift (DEMShift [1.0] 0)
             , DEMInstrShift (DEMShift [] 5)
             , DEMInstrShift (DEMShift [2.0] 0)
-            , DEMInstrDetector (DEMDetector (DetectorId 0) [0.0])
+            , DEMInstrDetector (DEMDetector (DetectorId 0) [0.0] Nothing)
             ]
           expected = DEM
-            [ DEMInstrDetector (DEMDetector (DetectorId 5) [3.0])
+            [ DEMInstrDetector (DEMDetector (DetectorId 5) [3.0] Nothing)
             ]
       in expected ~=? flattenDEM input
   ]
@@ -211,8 +211,8 @@
   [ "flatten is idempotent on simple" ~:
       let dem = DEM
             [ DEMInstrError (DEMError 0.01 [TargetDetector (DetectorId 0)])
-            , DEMInstrDetector (DEMDetector (DetectorId 1) [1.0, 2.0])
-            , DEMInstrObservable (DEMObservable (ObservableId 0))
+            , DEMInstrDetector (DEMDetector (DetectorId 1) [1.0, 2.0] Nothing)
+            , DEMInstrObservable (DEMObservable (ObservableId 0) Nothing)
             ]
       in flattenDEM dem ~=? flattenDEM (flattenDEM dem)
 
@@ -220,7 +220,7 @@
       let dem = DEM
             [ DEMInstrRepeat 2
                 [ DEMInstrShift (DEMShift [1.0] 0)
-                , DEMInstrDetector (DEMDetector (DetectorId 0) [0.0])
+                , DEMInstrDetector (DEMDetector (DetectorId 0) [0.0] Nothing)
                 ]
             ]
       in flattenDEM dem ~=? flattenDEM (flattenDEM dem)
diff --git a/test/Test/DEM/Parse.hs b/test/Test/DEM/Parse.hs
--- a/test/Test/DEM/Parse.hs
+++ b/test/Test/DEM/Parse.hs
@@ -3,6 +3,7 @@
 import Test.HUnit
 import Text.Megaparsec (runParser)
 
+import StimParser.Expr (Tag (..))
 import StimParser.DEM.Expr
 import StimParser.DEM.Parse
 import StimParser.ParseUtils (run)
@@ -30,6 +31,7 @@
   , testParseDEMEmpty
   , testParseDEMCaret
   , testParseDEMEofEnforced
+  , testParseDEMTagged
   ]
 
 -- | Parse individual error instructions
@@ -76,19 +78,19 @@
 testParseDEMDetector :: Test
 testParseDEMDetector = TestList
   [ "detector 2D" ~:
-      DEMDetector (DetectorId 0) [0.0, 0.0]
+      DEMDetector (DetectorId 0) [0.0, 0.0] Nothing
       ~=? run parseDEMDetector "detector(0, 0) D0"
 
   , "detector 3D" ~:
-      DEMDetector (DetectorId 5) [1.0, 2.0, 0.0]
+      DEMDetector (DetectorId 5) [1.0, 2.0, 0.0] Nothing
       ~=? run parseDEMDetector "detector(1, 2, 0) D5"
 
   , "detector negative coords" ~:
-      DEMDetector (DetectorId 1) [-1.0, 0.5]
+      DEMDetector (DetectorId 1) [-1.0, 0.5] Nothing
       ~=? run parseDEMDetector "detector(-1, 0.5) D1"
 
   , "detector 1D" ~:
-      DEMDetector (DetectorId 3) [2.0]
+      DEMDetector (DetectorId 3) [2.0] Nothing
       ~=? run parseDEMDetector "detector(2) D3"
   ]
 
@@ -96,11 +98,11 @@
 testParseDEMObservable :: Test
 testParseDEMObservable = TestList
   [ "observable L0" ~:
-      DEMObservable (ObservableId 0)
+      DEMObservable (ObservableId 0) Nothing
       ~=? run parseDEMObservable "logical_observable L0"
 
   , "observable L5" ~:
-      DEMObservable (ObservableId 5)
+      DEMObservable (ObservableId 5) Nothing
       ~=? run parseDEMObservable "logical_observable L5"
   ]
 
@@ -167,8 +169,8 @@
           expected = DEM
             [ DEMInstrError (DEMError 0.01 [TargetDetector (DetectorId 0), TargetDetector (DetectorId 1), TargetObservable (ObservableId 0)])
             , DEMInstrError (DEMError 0.005 [TargetDetector (DetectorId 1), TargetDetector (DetectorId 2)])
-            , DEMInstrDetector (DEMDetector (DetectorId 0) [0.0, 0.0])
-            , DEMInstrObservable (DEMObservable (ObservableId 0))
+            , DEMInstrDetector (DEMDetector (DetectorId 0) [0.0, 0.0] Nothing)
+            , DEMInstrObservable (DEMObservable (ObservableId 0) Nothing)
             ]
       in expected ~=? run parseDEM input
 
@@ -176,7 +178,7 @@
       let input = "repeat 2 { error(0.01) D0 } detector(0, 0) D0"
           expected = DEM
             [ DEMInstrRepeat 2 [DEMInstrError (DEMError 0.01 [TargetDetector (DetectorId 0)])]
-            , DEMInstrDetector (DEMDetector (DetectorId 0) [0.0, 0.0])
+            , DEMInstrDetector (DEMDetector (DetectorId 0) [0.0, 0.0] Nothing)
             ]
       in expected ~=? run parseDEM input
 
@@ -207,7 +209,7 @@
       ~=? run parseDEMError "error( 0.01 ) D0"
 
   , "detector with tabs" ~:
-      DEMDetector (DetectorId 0) [1.0, 2.0]
+      DEMDetector (DetectorId 0) [1.0, 2.0] Nothing
       ~=? run parseDEMDetector "detector(1,\t2)\tD0"
   ]
 
@@ -246,7 +248,7 @@
       ~=? run parseDEMError "error(0.01) D0 # end of line comment"
 
   , "block comment in tuple" ~:
-      DEMDetector (DetectorId 0) [1.0, 2.0]
+      DEMDetector (DetectorId 0) [1.0, 2.0] Nothing
       ~=? run parseDEMDetector "detector(1, (* x *) 2) D0"
   ]
 
@@ -254,11 +256,11 @@
 testParseDEMScientificCoords :: Test
 testParseDEMScientificCoords = TestList
   [ "scientific notation in detector coords" ~:
-      DEMDetector (DetectorId 0) [150.0, 0.0]
+      DEMDetector (DetectorId 0) [150.0, 0.0] Nothing
       ~=? run parseDEMDetector "detector(1.5e2, 0) D0"
 
   , "negative scientific in coords" ~:
-      DEMDetector (DetectorId 1) [-1.5e-2, 2.0]
+      DEMDetector (DetectorId 1) [-1.5e-2, 2.0] Nothing
       ~=? run parseDEMDetector "detector(-1.5e-2, 2) D1"
   ]
 
@@ -353,4 +355,50 @@
       case result of
         Left _ -> return ()  -- expected to fail
         Right _ -> assertFailure "parser should fail on unclosed parenthesis"
+  ]
+
+
+-- | Tagged detectors and observables (Stim v1.15+).
+testParseDEMTagged :: Test
+testParseDEMTagged = TestList
+  [ "tagged detector" ~:
+      DEMDetector (DetectorId 1) [1.0, 0.0] (Just (Tag "choi"))
+      ~=? run parseDEMDetector "detector[choi](1, 0) D1"
+
+  , "tagged observable" ~:
+      DEMObservable (ObservableId 0) (Just (Tag "choi"))
+      ~=? run parseDEMObservable "logical_observable[choi] L0"
+
+  , "untagged detector still parses" ~:
+      DEMDetector (DetectorId 0) [0.0, 0.0] Nothing
+      ~=? run parseDEMDetector "detector(0, 0) D0"
+
+  , "untagged observable still parses" ~:
+      DEMObservable (ObservableId 5) Nothing
+      ~=? run parseDEMObservable "logical_observable L5"
+
+  , "tagged detector in full DEM" ~:
+      let input = unlines
+            [ "error(0.01) D0"
+            , "detector[choi](1, 0) D1"
+            , "logical_observable[choi] L0"
+            ]
+          expected = DEM
+            [ DEMInstrError (DEMError 0.01 [TargetDetector (DetectorId 0)])
+            , DEMInstrDetector (DEMDetector (DetectorId 1) [1.0, 0.0] (Just (Tag "choi")))
+            , DEMInstrObservable (DEMObservable (ObservableId 0) (Just (Tag "choi")))
+            ]
+      in expected ~=? run parseDEM input
+
+  , "double tag on detector rejected" ~: TestCase $ do
+      let result = runParser parseDEM "" "detector[t1][t2](0, 0) D0"
+      case result of
+        Left _ -> return ()
+        Right _ -> assertFailure "double tags should be rejected"
+
+  , "double tag on observable rejected" ~: TestCase $ do
+      let result = runParser parseDEM "" "logical_observable[t1][t2] L0"
+      case result of
+        Left _ -> return ()
+        Right _ -> assertFailure "double tags should be rejected"
   ]
