diff --git a/SAS/TMSS/backend/services/scheduling/lib/subtask_scheduling.py b/SAS/TMSS/backend/services/scheduling/lib/subtask_scheduling.py
index 4ca2887f4bc7ce9c82fa6068964db11081cb4e85..9a1fd362797441554ac6f4567bda806922c54bc7 100644
--- a/SAS/TMSS/backend/services/scheduling/lib/subtask_scheduling.py
+++ b/SAS/TMSS/backend/services/scheduling/lib/subtask_scheduling.py
@@ -65,25 +65,29 @@ class TMSSSubTaskSchedulingEventMessageHandler(TMSSEventMessageHandler):
                 try:
                     suc_subtask_id = successor['id']
                     suc_subtask_state = successor['state_value']
-
-                    if suc_subtask_state == "defined":
-                        successor_predecessors = self.tmss_client.get_subtask_predecessors(suc_subtask_id)
-
-                        if any([suc_pred['state_value']!='finished' for suc_pred in successor_predecessors]):
-                            logger.info("skipping scheduling of successor subtask %s for finished subtask %s because not all its other predecessor subtasks are finished", suc_subtask_id, id)
+                    suc_subtask_obsolete_since = successor['obsolete_since']
+
+                    if suc_subtask_obsolete_since is None:
+                        if suc_subtask_state == "defined":
+                            successor_predecessors = self.tmss_client.get_subtask_predecessors(suc_subtask_id)
+
+                            if any([suc_pred['state_value'] != 'finished' and suc_pred['obsolete_since'] is None for suc_pred in successor_predecessors]):
+                                logger.info("skipping scheduling of successor subtask %s for finished subtask %s because not all its other (non-obsolete) predecessor subtasks are finished", suc_subtask_id, id)
+                            else:
+                                logger.info("trying to schedule successor subtask %s for finished subtask %s", suc_subtask_id, id)
+                                # try scheduling the subtask.
+                                # if it succeeds, then the state will be 'scheduled' afterwards
+                                # if there is a specification error, then the state will be 'error' afterwards
+                                # if there is another kind of error (like needing ingest-permission), then the state will be 'defined' afterwards, so you can retry.
+                                #   for the ingest-permission we will retry automatically when that permission is granted
+                                scheduled_successor = self.tmss_client.schedule_subtask(suc_subtask_id)
+                                suc_subtask_state = scheduled_successor['state_value']
+                                logger.log(logging.INFO if suc_subtask_state=='scheduled' else logging.WARNING,
+                                           "successor subtask %s for finished subtask %s now has state '%s', see %s", suc_subtask_id, id, suc_subtask_state, scheduled_successor['url'])
                         else:
-                            logger.info("trying to schedule successor subtask %s for finished subtask %s", suc_subtask_id, id)
-                            # try scheduling the subtask.
-                            # if it succeeds, then the state will be 'scheduled' afterwards
-                            # if there is a specification error, then the state will be 'error' afterwards
-                            # if there is another kind of error (like needing ingest-permission), then the state will be 'defined' afterwards, so you can retry.
-                            #   for the ingest-permission we will retry automatically when that permission is granted
-                            scheduled_successor = self.tmss_client.schedule_subtask(suc_subtask_id)
-                            suc_subtask_state = scheduled_successor['state_value']
-                            logger.log(logging.INFO if suc_subtask_state=='scheduled' else logging.WARNING,
-                                       "successor subtask %s for finished subtask %s now has state '%s', see %s", suc_subtask_id, id, suc_subtask_state, scheduled_successor['url'])
+                            logger.warning("skipping scheduling of successor subtask %s for finished subtask %s because its state is '%s'", suc_subtask_id, id, suc_subtask_state)
                     else:
-                        logger.warning("skipping scheduling of successor subtask %s for finished subtask %s because its state is '%s'", suc_subtask_id, id, suc_subtask_state)
+                        logger.warning("skipping scheduling of successor subtask %s for finished subtask %s because it is markes obsolete since %s", suc_subtask_id, id, suc_subtask_obsolete_since)
 
                 except Exception as e:
                     logger.error(e)
@@ -96,7 +100,7 @@ class TMSSSubTaskSchedulingEventMessageHandler(TMSSEventMessageHandler):
                 if subtask['state_value'] == 'defined':
                     subtask_template = self.tmss_client.get_url_as_json_object(subtask['specifications_template'])
                     if subtask_template['type_value'] == 'ingest':
-                        if all(pred['state_value'] == 'finished' for pred in self.tmss_client.get_subtask_predecessors(subtask['id'])):
+                        if all(pred['state_value'] == 'finished' or pred['obsolete_since'] is not None for pred in self.tmss_client.get_subtask_predecessors(subtask['id'])):
                             logger.info("trying to schedule ingest subtask id=%s for scheduling_unit_blueprint id=%s...", subtask['id'], id)
                             self.tmss_client.schedule_subtask(subtask['id'])
 
diff --git a/SAS/TMSS/backend/src/CMakeLists.txt b/SAS/TMSS/backend/src/CMakeLists.txt
index f80c85dc26bfdd8d5a769f107b6b4e0830c00016..167c3911f70d60354827352a249b0b3c8fc75394 100644
--- a/SAS/TMSS/backend/src/CMakeLists.txt
+++ b/SAS/TMSS/backend/src/CMakeLists.txt
@@ -32,7 +32,6 @@ find_python_module(swagger_spec_validator REQUIRED) # pip3 install swagger-spec-
 
 set(_py_files
     manage.py
-    remakemigrations.py
     )
 
 python_install(${_py_files}
diff --git a/SAS/TMSS/backend/src/remakemigrations.py b/SAS/TMSS/backend/src/remakemigrations.py
deleted file mode 100755
index ceb04af2b7220d8f40cc0ebb7c75d17b8edd48dd..0000000000000000000000000000000000000000
--- a/SAS/TMSS/backend/src/remakemigrations.py
+++ /dev/null
@@ -1,283 +0,0 @@
-#!/usr/bin/env python3
-
-# Copyright (C) 2018    ASTRON (Netherlands Institute for Radio Astronomy)
-# P.O. Box 2, 7990 AA Dwingeloo, The Netherlands
-#
-# This file is part of the LOFAR software suite.
-# The LOFAR software suite is free software: you can redistribute it and/or
-# modify it under the terms of the GNU General Public License as published
-# by the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# The LOFAR software suite is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.    See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>.
-
-# $Id:  $
-
-# This script automates the procedure to replace the existing migrations on the source tree with initital migrations
-# based on the current datamodel. Django offers a call 'makemigrations' through manage.py, which creates new migrations
-# after the datamodel implementation has changed. These additional migrations apply those changes to an existing
-# database reflecting the previous datamodel.
-# This is a very nice feature for production, but there are a few downsides that this script tackles:
-#
-# 1. During development, where the datamodel constantly changes, whe typically don't want a ton of iterative migrations,
-# but just have a clean start with a fresh initial database state without the whole provenance is perfectly fine. (We
-# start up a fresh database anyway for every test or test deployment.) This can be achieved by removing all existing
-# migrations prior to creating new ones.
-# A difficulty with this approach is that we do have a manual migration to populate the database with fixtures.
-# This migration needs to be restored or re-created after Django created fresh migrations for the database itself.
-#
-# 2. Since in settings.py we refer to the tmss app in the lofar environment, Django uses the build or installed version.
-# A consequence is that the created migrations are placed in there and need to be copied to the source tree.
-#
-# This script requires a running postgres database instance to work against.
-# To use specific database credentials, run e.g. ./remakemigrations.py -C b5f881c4-d41a-4f24-b9f5-23cd6a7f37d0
-
-
-import os
-from glob import glob
-import subprocess as sp
-import logging
-import argparse
-from shutil import copy
-import lofar.sas.tmss
-
-logger = logging.getLogger(__file__)
-
-# set up paths
-tmss_source_directory = os.path.dirname(__file__)
-if tmss_source_directory == '':
-    tmss_source_directory = '.'
-tmss_env_directory = os.path.dirname(lofar.sas.tmss.__file__)
-relative_migrations_directory = '/tmss/tmssapp/migrations/'
-
-# template for manual changes and fixture (applied last):
-template = """
-#
-# auto-generated by remakemigrations.py
-#
-# ! Please make sure to apply any changes to the template in that script !
-#
-from django.db import migrations
-
-from lofar.sas.tmss.tmss.tmssapp.populate import *
-
-class Migration(migrations.Migration):
-
-    dependencies = [
-        ('tmssapp', '{migration_dependency}'),
-    ]
-
-    operations = [ migrations.RunSQL('ALTER SEQUENCE tmssapp_SubTask_id_seq RESTART WITH 2000000;'), # Start SubTask id with 2 000 000 to avoid overlap with 'old' (test/production) OTDB
-                   # add an SQL trigger in the database enforcing correct state transitions.
-                   # it is crucial that illegal subtask state transitions are block at the "lowest level" (i.e.: in the database) so we can guarantee that the subtask state machine never breaks.
-                   # see: https://support.astron.nl/confluence/display/TMSS/Subtask+State+Machine
-                   # Explanation of SQl below: A trigger function is called upon each create/update of the subtask.
-                   # If the state changes, then it is checked if the state transition from old to new is present in the SubtaskAllowedStateTransitions table.
-                   # If not an Exception is raised, thus enforcing a rollback, thus enforcing the state machine to follow the design.
-                   # It is thereby enforced upon the user/caller to handle these blocked illegal state transitions, and act more wisely.
-                   migrations.RunSQL('''CREATE OR REPLACE FUNCTION tmssapp_check_subtask_state_transition()
-                                     RETURNS trigger AS
-                                     $BODY$
-                                     BEGIN
-                                       IF TG_OP = 'INSERT' THEN
-                                         IF NOT (SELECT EXISTS(SELECT id FROM tmssapp_subtaskallowedstatetransitions WHERE old_state_id IS NULL AND new_state_id=NEW.state_id)) THEN
-                                            RAISE EXCEPTION 'ILLEGAL SUBTASK STATE TRANSITION FROM % TO %', NULL, NEW.state_id;
-                                         END IF;
-                                       END IF;
-                                       IF TG_OP = 'UPDATE' THEN
-                                         IF OLD.state_id <> NEW.state_id AND NOT (SELECT EXISTS(SELECT id FROM tmssapp_subtaskallowedstatetransitions WHERE old_state_id=OLD.state_id AND new_state_id=NEW.state_id)) THEN
-                                           RAISE EXCEPTION 'ILLEGAL SUBTASK STATE TRANSITION FROM "%" TO "%"', OLD.state_id, NEW.state_id;
-                                         END IF;
-                                       END IF;
-                                     RETURN NEW;
-                                     END;
-                                     $BODY$
-                                     LANGUAGE plpgsql VOLATILE;
-                                     DROP TRIGGER IF EXISTS tmssapp_trigger_on_check_subtask_state_transition ON tmssapp_SubTask ;
-                                     CREATE TRIGGER tmssapp_trigger_on_check_subtask_state_transition
-                                     BEFORE INSERT OR UPDATE ON tmssapp_SubTask
-                                     FOR EACH ROW EXECUTE PROCEDURE tmssapp_check_subtask_state_transition();'''),
-
-                   # use database triggers to block updates on blueprint tables for immutable fields
-                   migrations.RunSQL('''CREATE OR REPLACE FUNCTION tmssapp_block_scheduling_unit_blueprint_immutable_fields_update()
-                                     RETURNS trigger AS
-                                     $BODY$
-                                     BEGIN
-                                       IF OLD.specifications_template_id <> NEW.specifications_template_id OR
-                                          OLD.name <> NEW.name OR
-                                          OLD.description <> NEW.description THEN
-                                         RAISE EXCEPTION 'ILLEGAL UPDATE OF IMMUTABLE BLUEPRINT FIELD';
-                                       END IF;
-                                     RETURN NEW;
-                                     END;
-                                     $BODY$
-                                     LANGUAGE plpgsql VOLATILE;
-                                     DROP TRIGGER IF EXISTS tmssapp_trigger_block_scheduling_unit_blueprint_immutable_fields_update ON tmssapp_SchedulingUnitBlueprint ;
-                                     CREATE TRIGGER tmssapp_block_scheduling_unit_blueprint_immutable_fields_update
-                                     BEFORE UPDATE ON tmssapp_SchedulingUnitBlueprint
-                                     FOR EACH ROW EXECUTE PROCEDURE tmssapp_block_scheduling_unit_blueprint_immutable_fields_update();
-                                     '''),
-                   migrations.RunSQL('''CREATE OR REPLACE FUNCTION tmssapp_block_task_blueprint_immutable_fields_update()
-                                     RETURNS trigger AS
-                                     $BODY$
-                                     BEGIN
-                                       IF OLD.specifications_doc <> NEW.specifications_doc OR
-                                          OLD.name <> NEW.name OR
-                                          OLD.description <> NEW.description OR
-                                          OLD.short_description <> NEW.short_description OR
-                                          OLD.specifications_template_id <> NEW.specifications_template_id OR
-                                          OLD.scheduling_unit_blueprint_id <> NEW.scheduling_unit_blueprint_id THEN
-                                         RAISE EXCEPTION 'ILLEGAL UPDATE OF IMMUTABLE BLUEPRINT FIELD';
-                                       END IF;
-                                     RETURN NEW;
-                                     END;
-                                     $BODY$
-                                     LANGUAGE plpgsql VOLATILE;
-                                     DROP TRIGGER IF EXISTS tmssapp_trigger_block_task_blueprint_immutable_fields_update ON tmssapp_TaskBlueprint ;
-                                     CREATE TRIGGER tmssapp_block_task_blueprint_immutable_fields_update
-                                     BEFORE UPDATE ON tmssapp_TaskBlueprint
-                                     FOR EACH ROW EXECUTE PROCEDURE tmssapp_block_task_blueprint_immutable_fields_update();
-                                     '''),
-                   migrations.RunSQL('''CREATE OR REPLACE FUNCTION tmssapp_block_task_relation_blueprint_immutable_fields_update()
-                                     RETURNS trigger AS
-                                     $BODY$
-                                     BEGIN
-                                       IF OLD.selection_doc <> NEW.selection_doc OR
-                                          OLD.producer_id <> NEW.producer_id OR
-                                          OLD.consumer_id <> NEW.consumer_id OR
-                                          OLD.input_role_id <> NEW.input_role_id OR
-                                          OLD.output_role_id <> NEW.output_role_id OR
-                                          OLD.input_role_id <> NEW.input_role_id OR
-                                          OLD.selection_template_id <> NEW.selection_template_id THEN
-                                         RAISE EXCEPTION 'ILLEGAL UPDATE OF IMMUTABLE BLUEPRINT FIELD';
-                                       END IF;
-                                     RETURN NEW;
-                                     END;
-                                     $BODY$
-                                     LANGUAGE plpgsql VOLATILE;
-                                     DROP TRIGGER IF EXISTS tmssapp_trigger_block_task_relation_blueprint_immutable_fields_update ON tmssapp_TaskRelationBlueprint ;
-                                     CREATE TRIGGER tmssapp_block_task_relation_blueprint_immutable_fields_update
-                                     BEFORE UPDATE ON tmssapp_TaskRelationBlueprint
-                                     FOR EACH ROW EXECUTE PROCEDURE tmssapp_block_task_relation_blueprint_immutable_fields_update();
-                                     '''),
-                   migrations.RunSQL('''CREATE OR REPLACE FUNCTION tmssapp_block_subtask_immutable_fields_update()
-                                     RETURNS trigger AS
-                                     $BODY$
-                                     BEGIN
-                                       IF OLD.specifications_doc <> NEW.specifications_doc OR
-                                          OLD.primary <> NEW.primary OR
-                                          OLD.task_blueprint_id <> NEW.task_blueprint_id OR
-                                          OLD.specifications_template_id <> NEW.specifications_template_id OR
-                                          OLD.cluster_id <> NEW.cluster_id THEN
-                                         RAISE EXCEPTION 'ILLEGAL UPDATE OF IMMUTABLE SUBTASK FIELD';
-                                       END IF;
-                                     RETURN NEW;
-                                     END;
-                                     $BODY$
-                                     LANGUAGE plpgsql VOLATILE;
-                                     DROP TRIGGER IF EXISTS tmssapp_trigger_block_subtask_immutable_fields_update ON tmssapp_Subtask ;
-                                     CREATE TRIGGER tmssapp_block_subtask_immutable_fields_update
-                                     BEFORE UPDATE ON tmssapp_Subtask
-                                     FOR EACH ROW EXECUTE PROCEDURE tmssapp_block_subtask_immutable_fields_update();
-                                     '''),
-                   migrations.RunPython(populate_choices),
-                   migrations.RunPython(populate_subtask_allowed_state_transitions),
-                   migrations.RunPython(populate_settings),
-                   migrations.RunPython(populate_misc),
-                   migrations.RunPython(populate_resources),
-                   migrations.RunPython(populate_cycles),
-                   migrations.RunPython(populate_projects) ]
-
-"""
-
-
-def execute_and_log(cmd):
-
-    logger.info('COMMAND: %s' % cmd)
-    p = sp.Popen(cmd, shell=True, stdout=sp.PIPE, stderr=sp.PIPE)
-    out, err = p.communicate()
-    if out is not None:
-        logger.info("STDOUT: %s" % out.decode('utf-8').strip())
-    if err is not None:
-        logger.info("STDERR: %s" % err.decode('utf-8').strip())
-
-
-def delete_old_migrations():
-    logger.info('Removing old migrations...')
-
-    files = glob_migrations()
-    for f in [path for path in files if ("initial" in path or "auto" in path or "populate" in path)]:
-        logger.info('Deleting: %s' % f)
-        os.remove(f)
-
-
-def make_django_migrations(dbcredentials=None):
-
-    logger.info('Making Django migrations...')
-    if dbcredentials:
-        os.environ['TMSS_DBCREDENTIALS'] = dbcredentials
-    execute_and_log('/usr/bin/env python3 %s/manage.py makemigrations' % tmss_source_directory)
-
-
-def make_populate_migration():
-
-    logger.info('Making migration for populating database...')
-    last_migration = determine_last_migration()
-    migration = template.format(migration_dependency=last_migration)
-
-    path = tmss_env_directory + relative_migrations_directory + '%s_populate.py' % str(int(last_migration.split('_')[0])+1).zfill(4)
-    logger.info('Writing to: %s' % path)
-    with open(path,'w') as f:
-        f.write(migration)
-
-
-def glob_migrations(directories=(tmss_source_directory, tmss_env_directory)):
-    paths = []
-    for directory in directories:
-        paths += glob(directory + '/' + relative_migrations_directory + '0*_*')
-    return paths
-
-
-def copy_migrations_to_source():
-    logger.info('Copying over migrations to source directory...')
-    files = glob_migrations(directories=[tmss_env_directory])
-    for file in files:
-        logger.info('Copying %s to %s' % (file, tmss_source_directory + '/' + relative_migrations_directory))
-        copy(file, tmss_source_directory + '/' + relative_migrations_directory)
-
-
-def determine_last_migration():
-    logger.info('Determining last migration...')
-    files = glob_migrations()
-    files = [os.path.basename(path) for path in files]
-    f = max(files)
-    last_migration = f.split('.py')[0]
-    logger.info('Determined last migration: %s' % last_migration)
-    return last_migration
-
-
-def remake_migrations(dbcredentials=None):
-    delete_old_migrations()
-    make_django_migrations(dbcredentials)
-    make_populate_migration()
-    copy_migrations_to_source()
-
-
-if __name__ == "__main__":
-
-    logger.setLevel(logging.DEBUG)
-
-    handler = logging.StreamHandler()
-    handler.setLevel(logging.INFO)
-    logger.addHandler(handler)
-
-    parser = argparse.ArgumentParser()
-    parser.add_argument("-C", action="store", dest="dbcredentials", help="use database specified in these dbcredentials")
-    args = parser.parse_args()
-    remake_migrations(args.dbcredentials)
diff --git a/SAS/TMSS/backend/src/tmss/tmssapp/migrations/0004_subtask_error_reason.py b/SAS/TMSS/backend/src/tmss/tmssapp/migrations/0004_subtask_error_reason.py
new file mode 100644
index 0000000000000000000000000000000000000000..a784361a44d64cb70e4f95effc9cc722dbeb8ef0
--- /dev/null
+++ b/SAS/TMSS/backend/src/tmss/tmssapp/migrations/0004_subtask_error_reason.py
@@ -0,0 +1,55 @@
+# Generated by Django 3.0.9 on 2021-10-15 07:51
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('tmssapp', '0003_taskblueprint_shortdescription'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='subtask',
+            name='error_reason',
+            field=models.CharField(help_text='Reason why the Subtask went to error.', max_length=200, null=True),
+        ),
+        migrations.RunSQL('''CREATE OR REPLACE FUNCTION tmssapp_block_subtask_immutable_fields_update()
+                          RETURNS trigger AS
+                          $BODY$
+                          BEGIN
+                            IF TG_OP = 'UPDATE' THEN
+                              IF OLD.specifications_doc <> NEW.specifications_doc OR
+                                 OLD.primary <> NEW.primary OR
+                                 OLD.task_blueprint_id <> NEW.task_blueprint_id OR
+                                 OLD.specifications_template_id <> NEW.specifications_template_id OR
+                                 OLD.cluster_id <> NEW.cluster_id THEN
+                                RAISE EXCEPTION 'ILLEGAL UPDATE OF IMMUTABLE SUBTASK FIELD';
+                              END IF;
+
+                              IF OLD.error_reason <> NEW.error_reason OR (OLD.error_reason IS NULL AND NEW.error_reason IS NOT NULL) THEN
+                                IF OLD.error_reason IS NOT NULL THEN
+                                  RAISE EXCEPTION 'subtask.error_reason may only be set once';
+                                END IF;
+                                IF NEW.error_reason IS NOT NULL AND NEW.state_id <> 'error' THEN
+                                  RAISE EXCEPTION 'subtask.error_reason may only be set when state==error';
+                                END IF;
+                              END IF;
+                            END IF;
+
+                            IF TG_OP = 'INSERT' THEN
+                                IF NEW.error_reason IS NOT NULL AND NEW.state_id <> 'error' THEN
+                                  RAISE EXCEPTION 'subtask.error_reason may only be set when state==error';
+                                END IF;
+                            END IF;
+                          RETURN NEW;
+                          END;
+                          $BODY$
+                          LANGUAGE plpgsql VOLATILE;
+                          DROP TRIGGER IF EXISTS tmssapp_block_subtask_immutable_fields_update ON tmssapp_Subtask ;
+                          CREATE TRIGGER tmssapp_block_subtask_immutable_fields_update
+                          BEFORE UPDATE OR INSERT ON tmssapp_Subtask
+                          FOR EACH ROW EXECUTE PROCEDURE tmssapp_block_subtask_immutable_fields_update();
+                          '''),
+    ]
diff --git a/SAS/TMSS/backend/src/tmss/tmssapp/migrations/0005_subtask_obsolete_since.py b/SAS/TMSS/backend/src/tmss/tmssapp/migrations/0005_subtask_obsolete_since.py
new file mode 100644
index 0000000000000000000000000000000000000000..998c4e047f5f6cae7f0a5a1ffb136f1e2ef2e65f
--- /dev/null
+++ b/SAS/TMSS/backend/src/tmss/tmssapp/migrations/0005_subtask_obsolete_since.py
@@ -0,0 +1,18 @@
+# Generated by Django 3.0.9 on 2021-10-12 07:07
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('tmssapp', '0004_subtask_error_reason'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='subtask',
+            name='obsolete_since',
+            field=models.DateTimeField(help_text='When this subtask was marked obsolete, or NULL if not obsolete (NULLable).', null=True),
+        ),
+    ]
diff --git a/SAS/TMSS/backend/src/tmss/tmssapp/models/scheduling.py b/SAS/TMSS/backend/src/tmss/tmssapp/models/scheduling.py
index 2b7f642cf5e9bfb8a9625c6831845a22810b765c..1d743ff33d751b4b14738e57d0b915d4dafbcfd8 100644
--- a/SAS/TMSS/backend/src/tmss/tmssapp/models/scheduling.py
+++ b/SAS/TMSS/backend/src/tmss/tmssapp/models/scheduling.py
@@ -50,7 +50,6 @@ class SubtaskState(AbstractChoice):
         CANCELLED = "cancelled"
         ERROR = "error"
         UNSCHEDULABLE = "unschedulable"
-        OBSOLETE = "obsolete"
 
 
 class SubtaskType(AbstractChoice):
@@ -150,9 +149,11 @@ class Subtask(BasicCommon, ProjectPropertyMixin, TemplateSchemaMixin):
     cluster = ForeignKey('Cluster', null=True, on_delete=PROTECT, help_text='Where the Subtask is scheduled to run (NULLable).')
     # resource_claim = ForeignKey("ResourceClaim", null=False, on_delete=PROTECT) # todo <-- how is this external reference supposed to work?
     created_or_updated_by_user = ForeignKey(User, null=True, editable=False, on_delete=PROTECT, help_text='The user who created / updated the subtask.')
+    error_reason = CharField(null=True, max_length=200, help_text='Reason why the Subtask went to error.')
     raw_feedback = CharField(null=True, max_length=1048576, help_text='The raw feedback for this Subtask')
     global_identifier = OneToOneField('SIPidentifier', null=False, editable=False, on_delete=PROTECT, help_text='The global unique identifier for LTA SIP.')
     path_to_project = 'task_blueprint__scheduling_unit_blueprint__draft__scheduling_set__project'
+    obsolete_since = DateTimeField(null=True, help_text='When this subtask was marked obsolete, or NULL if not obsolete (NULLable).')
 
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
@@ -160,6 +161,17 @@ class Subtask(BasicCommon, ProjectPropertyMixin, TemplateSchemaMixin):
         # keep original state for logging
         self.__original_state_id = self.state_id
 
+        # keep original obsolete_since to detect changes on save
+        # Note: we cannot use self.obsolete_since here since that causes an infinite loop of update_from_db
+        if 'obsolete_since' in kwargs.keys():
+            self.__original_obsolete_since = kwargs['obsolete_since']
+        else:
+            field_names = [f.name for f in self._meta.fields]
+            if len(args) == len(field_names):
+                self.__original_obsolete_since = args[field_names.index('obsolete_since')]
+            else:
+                self.__original_obsolete_since = None
+
     @property
     def duration(self) -> timedelta:
         '''the duration of this subtask (stop-start), or 0 if start/stop are None'''
@@ -315,6 +327,10 @@ class Subtask(BasicCommon, ProjectPropertyMixin, TemplateSchemaMixin):
             if self.scheduled_on_sky_start_time is None:
                 raise SubtaskSchedulingException("Cannot schedule subtask id=%s when start time is 'None'." % (self.pk, ))
 
+        # make sure that obsolete_since can only be set when it is None, but prevents changes:
+        if self.obsolete_since != self.__original_obsolete_since and self.__original_obsolete_since is not None:
+            raise ValidationError("This Subtask has been marked obsolete on %s and that cannot be changed to %s" % (self.__original_obsolete_since, self.obsolete_since))
+
         # set actual_process_start_time when subtask goes to STARTED state
         if self.state.value == SubtaskState.Choices.STARTED.value and self.__original_state_id == SubtaskState.Choices.STARTING.value:
             self.actual_process_start_time = datetime.utcnow()
diff --git a/SAS/TMSS/backend/src/tmss/tmssapp/models/specification.py b/SAS/TMSS/backend/src/tmss/tmssapp/models/specification.py
index 5e7ac1e262f1b2e3f208f503d04e4b94c34b0714..bdf01cc87506a545bfe81e82c17b865221fffd87 100644
--- a/SAS/TMSS/backend/src/tmss/tmssapp/models/specification.py
+++ b/SAS/TMSS/backend/src/tmss/tmssapp/models/specification.py
@@ -6,7 +6,7 @@ import logging
 logger = logging.getLogger(__name__)
 
 from django.db import transaction
-from django.db.models import Model, CharField, DateTimeField, BooleanField, ForeignKey, CASCADE, IntegerField, FloatField, SET_NULL, PROTECT, ManyToManyField, UniqueConstraint, QuerySet, OneToOneField, Q
+from django.db.models import Model, CharField, DateTimeField, BooleanField, ForeignKey, CASCADE, IntegerField, FloatField, SET_NULL, PROTECT, ManyToManyField, UniqueConstraint, QuerySet, OneToOneField, Q, Max
 from django.contrib.postgres.fields import JSONField
 from enum import Enum
 from django.db.models.expressions import RawSQL
@@ -604,7 +604,6 @@ class SchedulingUnitBlueprint(ProjectPropertyMixin, TemplateSchemaMixin, NamedCo
         SCHEDULED = "scheduled"
         SCHEDULABLE = "schedulable"
         UNSCHEDULABLE = "unschedulable"
-        OBSOLETE = "obsolete"
 
     # todo: are many of these fields supposed to be immutable in the database?
     #  Or are we fine to just not allow most users to change them?
@@ -630,7 +629,11 @@ class SchedulingUnitBlueprint(ProjectPropertyMixin, TemplateSchemaMixin, NamedCo
         if 'scheduling_constraints_doc' in kwargs.keys():
             self.__original_scheduling_constraints_doc = kwargs['scheduling_constraints_doc']
         else:
-            self.__original_scheduling_constraints_doc = None
+            field_names = [f.name for f in self._meta.fields]
+            if len(args) == len(field_names):
+                self.__original_scheduling_constraints_doc = args[field_names.index('scheduling_constraints_doc')]
+            else:
+                self.__original_scheduling_constraints_doc = None
         self.__original_scheduling_constraints_template_id = self.scheduling_constraints_template_id
 
     def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
@@ -864,8 +867,9 @@ class SchedulingUnitBlueprint(ProjectPropertyMixin, TemplateSchemaMixin, NamedCo
         status_overview_counter = Counter()
         status_overview_counter_per_type = {type.value: Counter() for type in TaskType.Choices}
         for tb in self.task_blueprints.select_related('specifications_template').all():
-            status_overview_counter[tb.status] += 1
-            status_overview_counter_per_type[tb.specifications_template.type.value][tb.status] += 1
+            if tb.obsolete_since is None:
+                status_overview_counter[tb.status] += 1
+                status_overview_counter_per_type[tb.specifications_template.type.value][tb.status] += 1
 
         # The actual determination of the SchedulingunitBlueprint status
         if not self._task_graph_instantiated():
@@ -876,9 +880,6 @@ class SchedulingUnitBlueprint(ProjectPropertyMixin, TemplateSchemaMixin, NamedCo
             return SchedulingUnitBlueprint.Status.CANCELLED.value
         elif self._any_task_error(status_overview_counter):
             return SchedulingUnitBlueprint.Status.ERROR.value
-        elif self._any_task_obsolete(status_overview_counter):
-            # TODO: in TMSS-850 implement the various conditional aggregations for the 'obsolete' vs 'finished' states
-            return SchedulingUnitBlueprint.Status.OBSOLETE.value
         elif self._any_task_unschedulable(status_overview_counter):
             return SchedulingUnitBlueprint.Status.UNSCHEDULABLE.value
         elif self._any_task_started_observed_finished(status_overview_counter):
@@ -1146,13 +1147,12 @@ class TaskDraft(NamedCommon, TemplateSchemaMixin, ProjectPropertyMixin):
     #         return None
 
 
-class TaskBlueprint(ProjectPropertyMixin, TemplateSchemaMixin, NamedCommon):
+class TaskBlueprint(ProjectPropertyMixin, TemplateSchemaMixin, RefreshFromDbInvalidatesCachedPropertiesMixin, NamedCommon):
     class Status(Enum):
         DEFINED = "defined"
         FINISHED = "finished"
         CANCELLED = "cancelled"
         ERROR = "error"
-        OBSOLETE = "obsolete"
         OBSERVED = "observed"
         STARTED = "started"
         SCHEDULED = "scheduled"
@@ -1265,6 +1265,17 @@ class TaskBlueprint(ProjectPropertyMixin, TemplateSchemaMixin, NamedCommon):
         else:
             return None
 
+    @cached_property
+    def obsolete_since(self) -> datetime or None:
+        '''return the earliest obsolete_since time of all subtasks of this task
+        '''
+        if self.subtasks.filter(obsolete_since__isnull=True).exists():
+            # there is at least one subtask that is not obsolete, so this task is not obsolete as well
+            return None
+
+        # return latest subtask obsolete_since timestamp, since this is when this task got obsolete
+        return self.subtasks.filter(obsolete_since__isnull=False).values('obsolete_since').aggregate(Max('obsolete_since'))['obsolete_since__max']
+
     @property
     def status(self):
         """
@@ -1274,7 +1285,8 @@ class TaskBlueprint(ProjectPropertyMixin, TemplateSchemaMixin, NamedCommon):
         """
 
         # one call to the db fetching only the needed values
-        subtasks = tuple(self.subtasks.values('state', 'specifications_template__type_id').all())
+        # we ignore non-primary subtasks that have been marked obsolete
+        subtasks = tuple(self.subtasks.exclude(primary=False, obsolete_since__isnull=False).values('state', 'specifications_template__type_id').all())
 
         # simple python filtering counting by state and type. Saves many round-trip-db calls!
         nr_of_subtasks = len(subtasks)
@@ -1293,10 +1305,6 @@ class TaskBlueprint(ProjectPropertyMixin, TemplateSchemaMixin, NamedCommon):
         if any(s for s in subtasks if s['state'] == 'error'):
             return TaskBlueprint.Status.ERROR.value
 
-        # TODO: in TMSS-850 implement the various conditional aggregations for the 'obsolete' vs 'finished' states
-        if any(s for s in subtasks if s['state'] == 'obsolete'):
-            return TaskBlueprint.Status.OBSOLETE.value
-
         observations = [s for s in subtasks if s['specifications_template__type_id'] == 'observation']
         if observations and all(obs and obs['state'] in ('finishing', 'finished') for obs in observations):
             return TaskBlueprint.Status.OBSERVED.value
diff --git a/SAS/TMSS/backend/src/tmss/tmssapp/populate.py b/SAS/TMSS/backend/src/tmss/tmssapp/populate.py
index 671fba35fcf444def92a7250f789ab31b8774581..e7c757539af412c484ff799f7bb48ad9fe8b4056 100644
--- a/SAS/TMSS/backend/src/tmss/tmssapp/populate.py
+++ b/SAS/TMSS/backend/src/tmss/tmssapp/populate.py
@@ -151,7 +151,6 @@ def populate_subtask_allowed_state_transitions(apps, schema_editor):
     CANCELLED = SubtaskState.objects.get(value=SubtaskState.Choices.CANCELLED.value)
     ERROR = SubtaskState.objects.get(value=SubtaskState.Choices.ERROR.value)
     UNSCHEDULABLE = SubtaskState.objects.get(value=SubtaskState.Choices.UNSCHEDULABLE.value)
-    OBSOLETE = SubtaskState.objects.get(value=SubtaskState.Choices.OBSOLETE.value)
 
     SubtaskAllowedStateTransitions.objects.bulk_create([
         SubtaskAllowedStateTransitions(old_state=None, new_state=DEFINING),
@@ -182,10 +181,6 @@ def populate_subtask_allowed_state_transitions(apps, schema_editor):
         SubtaskAllowedStateTransitions(old_state=FINISHING, new_state=ERROR),
         SubtaskAllowedStateTransitions(old_state=CANCELLING, new_state=ERROR),
 
-        # allow transition from the "end"-states cancelled/error to obsolete to indicate user-intent
-        SubtaskAllowedStateTransitions(old_state=CANCELLED, new_state=OBSOLETE),
-        SubtaskAllowedStateTransitions(old_state=ERROR, new_state=OBSOLETE),
-
         SubtaskAllowedStateTransitions(old_state=DEFINED, new_state=CANCELLING),
         SubtaskAllowedStateTransitions(old_state=SCHEDULED, new_state=CANCELLING),
         SubtaskAllowedStateTransitions(old_state=QUEUED, new_state=CANCELLING),
diff --git a/SAS/TMSS/backend/src/tmss/tmssapp/serializers/scheduling.py b/SAS/TMSS/backend/src/tmss/tmssapp/serializers/scheduling.py
index 72c0cf92be4ea0be4c810804f9cb2443e757667c..92746b6c3c340e7b90ed9683c8d60c5e033c1f80 100644
--- a/SAS/TMSS/backend/src/tmss/tmssapp/serializers/scheduling.py
+++ b/SAS/TMSS/backend/src/tmss/tmssapp/serializers/scheduling.py
@@ -6,6 +6,7 @@ import logging
 logger = logging.getLogger(__name__)
 
 from rest_framework import serializers
+from rest_framework.exceptions import ValidationError
 from .. import models
 from .widgets import JSONEditorField
 from .common import FloatDurationField, RelationalHyperlinkedModelSerializer, AbstractTemplateSerializer, DynamicRelationalHyperlinkedModelSerializer
@@ -74,7 +75,6 @@ class SubtaskSerializer(DynamicRelationalHyperlinkedModelSerializer):
     input_dataproducts = serializers.HyperlinkedRelatedField(many=True, read_only=True, view_name='dataproduct-detail')
     output_dataproducts = serializers.HyperlinkedRelatedField(many=True, read_only=True, view_name='dataproduct-detail')
 
-
     class Meta:
         model = models.Subtask
         fields = '__all__'
diff --git a/SAS/TMSS/backend/src/tmss/tmssapp/serializers/specification.py b/SAS/TMSS/backend/src/tmss/tmssapp/serializers/specification.py
index d9371f15ae3a707c18449d3968d69eb60dd1b36c..d17bf85c214e9cf38e3167fd278e9ddc415d966e 100644
--- a/SAS/TMSS/backend/src/tmss/tmssapp/serializers/specification.py
+++ b/SAS/TMSS/backend/src/tmss/tmssapp/serializers/specification.py
@@ -290,7 +290,8 @@ class TaskBlueprintSerializer(DynamicRelationalHyperlinkedModelSerializer):
         model = models.TaskBlueprint
         fields = '__all__'
         extra_fields = ['subtasks', 'produced_by', 'consumed_by', 'first_scheduling_relation', 'second_scheduling_relation', 'duration',
-                        'start_time', 'stop_time', 'relative_start_time', 'relative_stop_time', 'status', 'task_type']
+                        'start_time', 'stop_time', 'relative_start_time', 'relative_stop_time', 'status', 'task_type',
+                        'obsolete_since']
         expandable_fields = {
             'draft': 'lofar.sas.tmss.tmss.tmssapp.serializers.TaskDraftSerializer',
             'scheduling_unit_blueprint': 'lofar.sas.tmss.tmss.tmssapp.serializers.SchedulingUnitBlueprintSerializer',
diff --git a/SAS/TMSS/backend/src/tmss/tmssapp/subtasks.py b/SAS/TMSS/backend/src/tmss/tmssapp/subtasks.py
index ba36e9feff0c0990626f04413ec4d18c5a94b706..7b1d1931bb25ca1dc9921429c3171b077d2afef9 100644
--- a/SAS/TMSS/backend/src/tmss/tmssapp/subtasks.py
+++ b/SAS/TMSS/backend/src/tmss/tmssapp/subtasks.py
@@ -983,8 +983,9 @@ def schedule_subtask(subtask: Subtask) -> Subtask:
                 subtask.state = SubtaskState.objects.get(value=SubtaskState.Choices.UNSCHEDULABLE.value)
                 subtask.save()
             else:
-                # set the subtask to state 'ERROR'. TODO: we should annotate in the db what error occurred.
+                # set the subtask to state 'ERROR'.
                 subtask.state = SubtaskState.objects.get(value=SubtaskState.Choices.ERROR.value)
+                subtask.error_reason = f'{e}'
                 subtask.save()
         except Exception as e2:
             logger.error(e2)
@@ -1095,6 +1096,9 @@ def check_prerequities_for_scheduling(subtask: Subtask) -> bool:
     if subtask.state.value != SubtaskState.Choices.DEFINED.value:
         raise SubtaskSchedulingException("Cannot schedule subtask id=%d because it is not DEFINED. Current state=%s" % (subtask.pk, subtask.state.value))
 
+    if subtask.obsolete_since is not None:
+        raise SubtaskSchedulingException("Cannot schedule subtask id=%d because it is marked as obsolete since %s" % (subtask.pk, subtask.obsolete_since))
+
     for predecessor in subtask.predecessors.all():
         if predecessor.state.value != SubtaskState.Choices.FINISHED.value:
             raise SubtaskSchedulingException("Cannot schedule subtask id=%d because its predecessor id=%s in not FINISHED but state=%s"
@@ -2290,6 +2294,7 @@ def cancel_subtask(subtask: Subtask) -> Subtask:
     except Exception as e:
         logger.error("Error while cancelling subtask id=%s type=%s state=%s '%s'", subtask.id, subtask.specifications_template.type.value, subtask.state.value, e)
         subtask.state = SubtaskState.objects.get(value=SubtaskState.Choices.ERROR.value)
+        subtask.error_reason = f'{e}'
         subtask.save()
         if isinstance(e, SubtaskCancellingException):
             # we intentionally raised the SubtaskCancellingException, so re-raise it and let the caller handle it
diff --git a/SAS/TMSS/backend/src/tmss/tmssapp/tasks.py b/SAS/TMSS/backend/src/tmss/tmssapp/tasks.py
index aa9b0b3a0a68df9fda3df95771a725d07fa1a2fe..7c6de38050c99629252080fa0a147c647489e467 100644
--- a/SAS/TMSS/backend/src/tmss/tmssapp/tasks.py
+++ b/SAS/TMSS/backend/src/tmss/tmssapp/tasks.py
@@ -504,10 +504,9 @@ def cancel_task_blueprint(task_blueprint: TaskBlueprint) -> TaskBlueprint:
 
 def mark_task_blueprint_as_obsolete(task_blueprint: TaskBlueprint) -> TaskBlueprint:
     '''Convenience method: mark all cancelled/error subtasks in the task_blueprint as obsolete'''
-    obsolete_state = models.SubtaskState.objects.get(value=models.SubtaskState.Choices.OBSOLETE.value)
-    transitionable_states = models.SubtaskAllowedStateTransitions.allowed_old_states(obsolete_state)
-    for subtask in task_blueprint.subtasks.filter(state__in=transitionable_states):
-        subtask.state = obsolete_state
+    now = datetime.utcnow()
+    for subtask in task_blueprint.subtasks.all():
+        subtask.obsolete_since = now
         subtask.save()
     task_blueprint.refresh_from_db()
     return task_blueprint
diff --git a/SAS/TMSS/backend/src/tmss/tmssapp/viewsets/scheduling.py b/SAS/TMSS/backend/src/tmss/tmssapp/viewsets/scheduling.py
index 5ab6c1e51a1a535c129b8b895f456bf046e20eba..c5adc99347792fe207ec34d8ff8efa0a331201e4 100644
--- a/SAS/TMSS/backend/src/tmss/tmssapp/viewsets/scheduling.py
+++ b/SAS/TMSS/backend/src/tmss/tmssapp/viewsets/scheduling.py
@@ -127,6 +127,7 @@ class SubTaskFilter(property_filters.PropertyFilterSet):
     id_max = filters.NumberFilter(field_name='id', lookup_expr='lte')
     state = filters.ModelMultipleChoiceFilter(field_name='state', queryset=models.SubtaskState.objects.all())
     name = filters.CharFilter(field_name='task_blueprint__scheduling_unit_blueprint__name', lookup_expr='icontains')   # todo: correct name?
+    error_reason = filters.CharFilter(field_name='error_reason', lookup_expr='icontains')
     on_sky_start_time__lt = property_filters.PropertyIsoDateTimeFilter(field_name='on_sky_start_time', lookup_expr='lt')
     on_sky_start_time__gt = property_filters.PropertyIsoDateTimeFilter(field_name='on_sky_start_time', lookup_expr='gt')
     on_sky_stop_time__lt = property_filters.PropertyIsoDateTimeFilter(field_name='on_sky_stop_time', lookup_expr='lt')
diff --git a/SAS/TMSS/backend/test/CMakeLists.txt b/SAS/TMSS/backend/test/CMakeLists.txt
index d8e4d47175ee8b1a3f33dfbdcb13ef91781d3c71..ef05b8393501c0ec3706c05efec772f66a0d99ae 100644
--- a/SAS/TMSS/backend/test/CMakeLists.txt
+++ b/SAS/TMSS/backend/test/CMakeLists.txt
@@ -42,6 +42,7 @@ if(BUILD_TESTING)
     lofar_add_test(t_reservations)
     lofar_add_test(t_swagger)
 
+    set_tests_properties(t_adapter PROPERTIES TIMEOUT 300)
     set_tests_properties(t_scheduling PROPERTIES TIMEOUT 300)
     set_tests_properties(t_tmssapp_scheduling_REST_API PROPERTIES TIMEOUT 300)
     set_tests_properties(t_tmssapp_specification_REST_API PROPERTIES TIMEOUT 600)
diff --git a/SAS/TMSS/backend/test/t_scheduling.py b/SAS/TMSS/backend/test/t_scheduling.py
index 2bc761f60cb8a2fd2cfb0b28657a159e747b00e8..6c33a9d5878a7d915a939b3b8a3abf35fbf13d23 100755
--- a/SAS/TMSS/backend/test/t_scheduling.py
+++ b/SAS/TMSS/backend/test/t_scheduling.py
@@ -78,6 +78,8 @@ from lofar.sas.tmss.tmss.tmssapp.tasks import *
 from lofar.sas.tmss.test.test_utils import set_subtask_state_following_allowed_transitions
 from lofar.messaging.rpc import RPCService, ServiceMessageHandler
 import threading
+import dateutil.parser
+
 
 def create_subtask_object_for_testing(subtask_type_value, subtask_state_value):
     """
@@ -230,16 +232,28 @@ class SchedulingTest(unittest.TestCase):
             self.assertEqual('cancelled', subtask['state_value'])
 
             # mark it as obsolete... (the user thereby states that the cancelled subtask will is not to be used again)
+            self.assertIsNone(subtask['obsolete_since'])
+            before = datetime.utcnow()
             subtask = client.mark_subtask_as_obsolete(subtask_id)
-            self.assertEqual('obsolete', subtask['state_value'])
+            after = datetime.utcnow()
+            obsolete_since = dateutil.parser.parse(subtask['obsolete_since'], ignoretz=True)
+            self.assertIsNotNone(obsolete_since)
+            self.assertLess(before, obsolete_since)
+            self.assertGreater(after, obsolete_since)
 
             # scheduling should fail
             with self.assertRaises(Exception):
                 client.schedule_subtask(subtask_id)
 
-            # and status should still be obsolete
+            # marking an obsolete subtask as obsolete again should be prevented
+            with self.assertRaises(Exception) as context:
+                subtask = client.mark_subtask_as_obsolete(subtask_id)
+            self.assertIn("has been marked obsolete on %s" % obsolete_since, str(context.exception))
+
+            # and obsolete_since timestamp should still be the same as before
             subtask = client.get_subtask(subtask_id)
-            self.assertEqual('obsolete', subtask['state_value'])
+            obsolete_since_new = dateutil.parser.parse(subtask['obsolete_since'], ignoretz=True)
+            self.assertEqual(obsolete_since, obsolete_since_new)
 
     def test_cancel_scheduled_observation_subtask(self):
         with tmss_test_env.create_tmss_client() as client:
diff --git a/SAS/TMSS/backend/test/t_subtasks.py b/SAS/TMSS/backend/test/t_subtasks.py
index 9323947f44de942291823bbf2d8440085f532a4c..53d98a77b916deb19018831a5d3295c2fdf7a645 100755
--- a/SAS/TMSS/backend/test/t_subtasks.py
+++ b/SAS/TMSS/backend/test/t_subtasks.py
@@ -754,8 +754,17 @@ class SubtaskAllowedStateTransitionsTest(unittest.TestCase):
 
             # then go to the error state (should be allowed from any of these intermediate states)
             subtask.state = SubtaskState.objects.get(value=SubtaskState.Choices.ERROR.value)
+            ERROR_MESSAGE = 'test_helper_method_set_subtask_state_following_allowed_transitions_error_path'
+            subtask.error_reason = ERROR_MESSAGE
             subtask.save()
             self.assertEqual(SubtaskState.Choices.ERROR.value, subtask.state.value)
+            self.assertEqual(ERROR_MESSAGE, subtask.error_reason)
+
+            # overwriting error reason should not be allowed
+            from django.db.utils import InternalError
+            with self.assertRaises(InternalError) as context:
+                subtask.error_reason = "overwriting error reason"
+                subtask.save()
 
     def test_helper_method_set_subtask_state_following_allowed_transitions_cancel_path(self):
         for desired_end_state_value in (SubtaskState.Choices.CANCELLING.value,SubtaskState.Choices.CANCELLED.value):
@@ -794,17 +803,13 @@ class SubtaskAllowedStateTransitionsTest(unittest.TestCase):
 
     def test_end_states(self):
         '''Check if the end states that we cannot get out of are according to the design'''
-        # there should be only one state to go to from ERROR, that is OBSOLETE
+        # there should be no state to go to from ERROR
         allowed_states_from_ERROR = SubtaskAllowedStateTransitions.objects.filter(old_state__value=SubtaskState.Choices.ERROR.value)
-        self.assertEqual(1, allowed_states_from_ERROR.count())
-        self.assertEqual(SubtaskState.Choices.OBSOLETE.value, allowed_states_from_ERROR[0].new_state.value)
+        self.assertEqual(0, allowed_states_from_ERROR.count())
 
         # there should be no state to go to from FINISHED
         self.assertEqual(0, SubtaskAllowedStateTransitions.objects.filter(old_state__value=SubtaskState.Choices.FINISHED.value).count())
 
-        # there should be no state to go to from OBSOLETE
-        self.assertEqual(0, SubtaskAllowedStateTransitions.objects.filter(old_state__value=SubtaskState.Choices.OBSOLETE.value).count())
-
     def test_illegal_state_transitions(self):
         for state_value in [choice.value for choice in SubtaskState.Choices]:
             # assume helper method set_subtask_state_following_allowed_transitions is working (see other tests above)
diff --git a/SAS/TMSS/backend/test/t_tmssapp_specification_REST_API.py b/SAS/TMSS/backend/test/t_tmssapp_specification_REST_API.py
index af16f953e9eccda2773674ecd9e9a321408658ee..ee4e0ceac36ff5acb8100c810e5d76dbfcb765f3 100755
--- a/SAS/TMSS/backend/test/t_tmssapp_specification_REST_API.py
+++ b/SAS/TMSS/backend/test/t_tmssapp_specification_REST_API.py
@@ -2384,7 +2384,7 @@ class TaskBlueprintTestCase(unittest.TestCase):
 
         #  assert
         response_1 = GET_and_assert_equal_expected_code(self, BASE_URL + '/task_blueprint/?status=defined&status=finished', 200)
-        response_2 = GET_and_assert_equal_expected_code(self, BASE_URL + '/task_blueprint/?status=obsolete', 200)
+        response_2 = GET_and_assert_equal_expected_code(self, BASE_URL + '/task_blueprint/?status=observed', 200)
         GET_and_assert_equal_expected_code(self, BASE_URL + '/task_blueprint/?status=gibberish', 400)
         self.assertGreater(response_1['count'], 0)
         self.assertEqual(response_2['count'], 0)
diff --git a/SAS/TMSS/backend/test/t_tmssapp_specification_django_API.py b/SAS/TMSS/backend/test/t_tmssapp_specification_django_API.py
index 63dc0bc8747294f21aa90d0a2f69b0adf374c4cf..3a0ca74d9d48e9bcff090432026020fbe7cf8e0a 100755
--- a/SAS/TMSS/backend/test/t_tmssapp_specification_django_API.py
+++ b/SAS/TMSS/backend/test/t_tmssapp_specification_django_API.py
@@ -739,6 +739,7 @@ class SchedulingUnitBlueprintTest(unittest.TestCase):
         task_blueprint = models.TaskBlueprint.objects.create(**TaskBlueprint_test_data(scheduling_unit_blueprint=scheduling_unit_blueprint))
         subtask = models.Subtask.objects.create(**Subtask_test_data(task_blueprint=task_blueprint))
         subtask.state = models.SubtaskState.objects.get(value='error')  # the derived SUB status is then also error
+        subtask.error_reason = 'test_SchedulingUnitBlueprint_prevents_updating_scheduling_constraints_template_if_not_in_correct_state'
         subtask.save()
 
         scheduling_unit_blueprint.refresh_from_db()
@@ -779,6 +780,7 @@ class SchedulingUnitBlueprintTest(unittest.TestCase):
         task_blueprint = models.TaskBlueprint.objects.create(**TaskBlueprint_test_data(scheduling_unit_blueprint=scheduling_unit_blueprint))
         subtask = models.Subtask.objects.create(**Subtask_test_data(task_blueprint=task_blueprint))
         subtask.state = models.SubtaskState.objects.get(value='error')  # the derived SUB status is then also error
+        subtask.error_reason = 'test_SchedulingUnitBlueprint_prevents_updating_scheduling_constraints_doc_if_not_in_correct_state'
         subtask.save()
 
         scheduling_unit_blueprint.refresh_from_db()
diff --git a/SAS/TMSS/backend/test/test_utils.py b/SAS/TMSS/backend/test/test_utils.py
index e22f2ecb6a37912694fa32e5b7626ac8e87d1c84..86ac97ee5d768f26f1ce624b8e9b9c7d18148e0f 100644
--- a/SAS/TMSS/backend/test/test_utils.py
+++ b/SAS/TMSS/backend/test/test_utils.py
@@ -92,7 +92,7 @@ def set_subtask_state_following_allowed_transitions(subtask: typing.Union[Subtas
         subtask = Subtask.objects.get(id=subtask)
 
     # end states that we cannot get out of according to the design
-    END_STATE_VALUES = (SubtaskState.Choices.FINISHED.value, SubtaskState.Choices.OBSOLETE.value)
+    END_STATE_VALUES = (SubtaskState.Choices.FINISHED.value)
 
     while subtask.state.value != desired_state_value and (subtask.state.value not in END_STATE_VALUES):
         # handle "unsuccessful path" to cancelled/canceling end state
@@ -111,17 +111,7 @@ def set_subtask_state_following_allowed_transitions(subtask: typing.Union[Subtas
                                                                                                  SubtaskState.Choices.FINISHING.value,
                                                                                                  SubtaskState.Choices.CANCELLING.value):
             subtask.state = SubtaskState.objects.get(value=SubtaskState.Choices.ERROR.value)
-
-        # handle "unsuccessful path" to OBSOLETE end state (via CANCELLED)
-        elif desired_state_value == SubtaskState.Choices.OBSOLETE.value:
-            if subtask.state.value == SubtaskState.Choices.DEFINING.value:
-                subtask.state = SubtaskState.objects.get(value=SubtaskState.Choices.DEFINED.value)
-            elif subtask.state.value in (SubtaskState.Choices.DEFINED.value, SubtaskState.Choices.SCHEDULED.value, SubtaskState.Choices.QUEUED.value, SubtaskState.Choices.ERROR.value):
-                subtask.state = SubtaskState.objects.get(value=SubtaskState.Choices.CANCELLING.value)
-            elif subtask.state.value == SubtaskState.Choices.CANCELLING.value:
-                subtask.state = SubtaskState.objects.get(value=SubtaskState.Choices.CANCELLED.value)
-            elif subtask.state.value == SubtaskState.Choices.CANCELLED.value:
-                subtask.state = SubtaskState.objects.get(value=SubtaskState.Choices.OBSOLETE.value)
+            subtask.error_reason = 'set_subtask_state_following_allowed_transitions'
 
         # handle "unsuccessful path" to unschedulable state
         elif desired_state_value == SubtaskState.Choices.UNSCHEDULABLE.value and (subtask.state.value == SubtaskState.Choices.DEFINED.value or subtask.state.value == SubtaskState.Choices.SCHEDULING.value):
diff --git a/SAS/TMSS/client/lib/mains.py b/SAS/TMSS/client/lib/mains.py
index 75334b8eac55cee4577e20ac6809131459c483bc..2f575a3c9f89da6a84c6510f562a6a3aee4d6594 100644
--- a/SAS/TMSS/client/lib/mains.py
+++ b/SAS/TMSS/client/lib/mains.py
@@ -138,7 +138,7 @@ def main_schedule_subtask():
     try:
         with TMSSsession.create_from_dbcreds_for_ldap(dbcreds_name=args.rest_api_credentials) as session:
             pprint(session.schedule_subtask(args.subtask_id,
-                                            start_time=datetime.datetime.fromisoformat(args.start_time) if args.start_time else None,
+                                            scheduled_on_sky_start_time=datetime.datetime.fromisoformat(args.start_time) if args.start_time else None,
                                             retry_count=3))
     except Exception as e:
         print(e)
diff --git a/SAS/TMSS/client/lib/tmss_http_rest_client.py b/SAS/TMSS/client/lib/tmss_http_rest_client.py
index f3d3ee856da693c0d2538d038e140055dfd20e64..39520ac73daf3382458e2087fb2c0ee0e04bc586 100644
--- a/SAS/TMSS/client/lib/tmss_http_rest_client.py
+++ b/SAS/TMSS/client/lib/tmss_http_rest_client.py
@@ -440,10 +440,16 @@ class TMSSsession(object):
         returns the cancelled scheduling_unit_blueprint upon success, or raises."""
         return self.post_to_path_and_get_result_as_json_object('scheduling_unit_blueprint/%s/cancel' % (scheduling_unit_blueprint_id), retry_count=retry_count)
 
-    def mark_subtask_as_obsolete(self, subtask_id: int, retry_count: int=0) -> {}:
+    def mark_subtask_as_obsolete(self, subtask_id: int) -> {}:
         """mark the subtask for the given subtask_id as obsolete.
         returns the marked_as_obsolete subtask upon success, or raises."""
-        return self.set_subtask_status(subtask_id=subtask_id, status='obsolete')
+        logger.info("marking subtask id=%s as obsolete", subtask_id)
+        response = self.session.patch(url='%s/subtask/%s/' % (self.api_url, subtask_id), json={'obsolete_since': "%s" % datetime.utcnow().isoformat()})
+
+        if response.status_code >= 200 and response.status_code < 300:
+            return json.loads(response.content.decode('utf-8'))
+
+        raise Exception("Could not mark subtask as obsolete with url %s - %s %s - %s" % (response.request.url, response.status_code, responses.get(response.status_code), response.text))
 
     def mark_task_blueprint_as_obsolete(self, task_blueprint_id: int, retry_count: int=0) -> {}:
         """mark the task_blueprint for the given task_blueprint_id as obsolete.
@@ -655,3 +661,4 @@ class TMSSsession(object):
         Returns the created scheduling_unit_draft/blueprint upon success, or raises.
         Use the method get_trigger_specification_doc_for_scheduling_unit_observing_strategy_template to get a valid trigger json doc, edit it, and sumbit using this method."""
         return self.post_to_path_and_get_result_as_json_object('/submit_trigger', json_data=trigger_doc, retry_count=retry_count, retry_interval=retry_interval)
+